blob: 391f6619b1bc921f27b02d8040a35f41ce46ddb2 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.ozone.om;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.hadoop.hdds.conf.OzoneConfiguration;
import org.apache.hadoop.hdds.protocol.StorageType;
import org.apache.hadoop.ozone.MiniOzoneCluster;
import org.apache.hadoop.ozone.OzoneAcl;
import org.apache.hadoop.ozone.client.BucketArgs;
import org.apache.hadoop.ozone.client.ObjectStore;
import org.apache.hadoop.ozone.client.OzoneBucket;
import org.apache.hadoop.ozone.client.OzoneClient;
import org.apache.hadoop.ozone.client.OzoneVolume;
import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
import org.apache.hadoop.ozone.security.acl.OzoneObj;
import org.apache.hadoop.ozone.security.acl.OzoneObjInfo;
import org.apache.hadoop.security.UserGroupInformation;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_AUTHORIZER_CLASS;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_AUTHORIZER_CLASS_NATIVE;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED;
import static org.apache.hadoop.ozone.security.acl.OzoneObj.StoreType.OZONE;
/**
* Test recursive acl checks for delete and rename for FSO Buckets.
*/
public class TestRecursiveAclWithFSO {
@Rule public Timeout timeout = Timeout.seconds(120);
private MiniOzoneCluster cluster;
private final UserGroupInformation adminUser =
UserGroupInformation.createUserForTesting("om", new String[] {"ozone"});
private final UserGroupInformation user1 = UserGroupInformation
.createUserForTesting("user1", new String[] {"test1"});
private final UserGroupInformation user2 = UserGroupInformation
.createUserForTesting("user2", new String[] {"test2"});
@Before
public void init() throws Exception {
// loginUser is the user running this test.
// Implication: loginUser is automatically added to the OM admin list.
UserGroupInformation.setLoginUser(adminUser);
// ozone.acl.enabled = true
// start a cluster
startCluster();
}
@Test
public void testKeyDeleteAndRenameWithoutPermission() throws Exception {
List<String> keys = new ArrayList<>();
// Create volumes with user1
OzoneClient client = cluster.getClient();
ObjectStore objectStore = client.getObjectStore();
/* r = READ, w = WRITE, c = CREATE, d = DELETE
l = LIST, a = ALL, n = NONE, x = READ_ACL, y = WRITE_ACL */
String aclWorldAll = "world::a";
createVolumeWithOwnerAndAcl(objectStore, "volume1", "user1", aclWorldAll);
// Login as user1, create directories and keys
UserGroupInformation.setLoginUser(user1);
client = cluster.getClient();
objectStore = client.getObjectStore();
OzoneVolume volume = objectStore.getVolume("volume1");
BucketArgs omBucketArgs =
BucketArgs.newBuilder().setStorageType(StorageType.DISK).build();
// create bucket with user1
volume.createBucket("bucket1", omBucketArgs);
setBucketAcl(objectStore, volume.getName(), "bucket1", aclWorldAll);
OzoneBucket ozoneBucket = volume.getBucket("bucket1");
/**
* buck-1
* |
* a
* |
* ------------------------------------
* | | | |
* b1 b2 b3 file1
* ----- ------ -----
* | | | | | |
* c1 c2 d1 d2 e1 e2
* | | | | | |
* f1 f2 f3 -------- f5 f6
* | |
* d21 file2
* |
* f4
*
* Test Case 1 :
* Remove delete acl from file File2
* Try deleting b2
*
* Test case 2:
* Remove delete acl fro dir c2
* Try deleting b1
*
* Test case 3
* try deleting b3
*/
String keyf1 = "a/b1/c1/f1";
String keyf2 = "a/b1/c2/f2";
String keyf3 = "a/b2/d1/f3";
String keyf4 = "a/b2/d2/d21/f4";
String keyf5 = "/a/b3/e1/f5";
String keyf6 = "/a/b3/e2/f6";
String file1 = "a/" + "file" + RandomStringUtils.randomNumeric(5);
String file2 = "a/b2/d2/" + "file" + RandomStringUtils.randomNumeric(5);
keys.add(keyf1);
keys.add(keyf2);
keys.add(keyf3);
keys.add(keyf4);
keys.add(keyf5);
keys.add(keyf6);
keys.add(file1);
keys.add(file2);
createKeys(objectStore, ozoneBucket, keys);
// Test case 1
// Remove acls from file2
// Delete/Rename on directory a/b2 should throw permission denied
// (since file2 is a child)
removeAclsFromKey(objectStore, ozoneBucket, file2);
OzoneObj ozoneObj;
List<OzoneAcl> aclList1;
UserGroupInformation.setLoginUser(user2);
client = cluster.getClient();
objectStore = client.getObjectStore();
volume = objectStore.getVolume("volume1");
ozoneBucket = volume.getBucket("bucket1");
// perform delete
try {
ozoneBucket.deleteDirectory("a/b2", true);
Assert.fail("Should throw permission denied !");
} catch (OMException ome) {
// expect permission error
Assert.assertEquals("Permission check failed",
OMException.ResultCodes.PERMISSION_DENIED, ome.getResult());
}
// perform rename
try {
ozoneBucket.renameKey("a/b2", "a/b2_renamed");
Assert.fail("Should throw permission denied !");
} catch (OMException ome) {
// expect permission error
Assert.assertEquals("Permission check failed",
OMException.ResultCodes.PERMISSION_DENIED, ome.getResult());
}
// Test case 2
// Remove acl from directory c2, delete/rename a/b1 should throw
// permission denied since c2 is a subdirectory
UserGroupInformation.setLoginUser(user1);
removeAclsFromKey(objectStore, ozoneBucket, "a/b1/c2");
UserGroupInformation.setLoginUser(user2);
// perform delete
try {
ozoneBucket.deleteDirectory("a/b1", true);
Assert.fail("Should throw permission denied !");
} catch (OMException ome) {
// expect permission error
Assert.assertEquals("Permission check failed",
OMException.ResultCodes.PERMISSION_DENIED, ome.getResult());
}
// perform rename
try {
ozoneBucket.renameKey("a/b1", "a/b1_renamed");
Assert.fail("Should throw permission denied !");
} catch (OMException ome) {
// expect permission error
Assert.assertEquals("Permission check failed",
OMException.ResultCodes.PERMISSION_DENIED, ome.getResult());
}
// Test case 3
// delete b3 and this shouldn't throw exception because acls have not
// been removed from subpaths.
ozoneBucket.deleteDirectory("a/b3", true);
}
private void removeAclsFromKey(ObjectStore objectStore,
OzoneBucket ozoneBucket, String key) throws IOException {
OzoneObj ozoneObj = OzoneObjInfo.Builder.newBuilder().setKeyName(key)
.setBucketName(ozoneBucket.getName())
.setVolumeName(ozoneBucket.getVolumeName())
.setStoreType(OzoneObj.StoreType.OZONE)
.setResType(OzoneObj.ResourceType.KEY).build();
List<OzoneAcl> aclList1 = objectStore.getAcl(ozoneObj);
for (OzoneAcl acl : aclList1) {
objectStore.removeAcl(ozoneObj, acl);
}
}
/**
* Create a MiniOzoneCluster for testing.
*/
private void startCluster() throws Exception {
OzoneConfiguration conf = new OzoneConfiguration();
String clusterId = UUID.randomUUID().toString();
String scmId = UUID.randomUUID().toString();
String omId = UUID.randomUUID().toString();
// Use native impl here, default impl doesn't do actual checks
conf.set(OZONE_ACL_AUTHORIZER_CLASS, OZONE_ACL_AUTHORIZER_CLASS_NATIVE);
// Note: OM doesn't support live config reloading
conf.setBoolean(OZONE_ACL_ENABLED, true);
OMRequestTestUtils.configureFSOptimizedPaths(conf, true);
cluster =
MiniOzoneCluster.newBuilder(conf).setClusterId(clusterId)
.setScmId(scmId).setOmId(omId).build();
cluster.waitForClusterToBeReady();
}
@After
public void stopCluster() {
if (cluster != null) {
cluster.shutdown();
}
}
private void createVolumeWithOwnerAndAcl(ObjectStore objectStore,
String volumeName, String ownerName, String aclString)
throws IOException {
ClientProtocol proxy = objectStore.getClientProxy();
objectStore.createVolume(volumeName);
proxy.setVolumeOwner(volumeName, ownerName);
setVolumeAcl(objectStore, volumeName, aclString);
}
/**
* Helper function to set volume ACL.
*/
private void setVolumeAcl(ObjectStore objectStore, String volumeName,
String aclString) throws IOException {
OzoneObj obj = OzoneObjInfo.Builder.newBuilder().setVolumeName(volumeName)
.setResType(OzoneObj.ResourceType.VOLUME).setStoreType(OZONE).build();
Assert.assertTrue(objectStore.setAcl(obj, OzoneAcl.parseAcls(aclString)));
}
/**
* Helper function to set bucket ACL.
*/
private void setBucketAcl(ObjectStore objectStore, String volumeName,
String bucket, String aclString) throws IOException {
OzoneObj obj = OzoneObjInfo.Builder.newBuilder().setVolumeName(volumeName)
.setBucketName(bucket).setResType(OzoneObj.ResourceType.BUCKET)
.setStoreType(OZONE).build();
Assert.assertTrue(objectStore.setAcl(obj, OzoneAcl.parseAcls(aclString)));
}
/**
* Helper function to set key ACL.
*/
private void setKeyAcl(ObjectStore objectStore, String volumeName,
String bucket, String key, String aclString) throws IOException {
OzoneObj obj = OzoneObjInfo.Builder.newBuilder().setVolumeName(volumeName)
.setBucketName(bucket).setKeyName(key)
.setResType(OzoneObj.ResourceType.KEY).setStoreType(OZONE).build();
Assert.assertTrue(objectStore.setAcl(obj, OzoneAcl.parseAcls(aclString)));
}
private void createKeys(ObjectStore objectStore, OzoneBucket ozoneBucket,
List<String> keys) throws Exception {
int length = 10;
String aclWorldAll = "world::a";
byte[] input = new byte[length];
Arrays.fill(input, (byte) 96);
for (String key : keys) {
createKey(ozoneBucket, key, 10, input);
setKeyAcl(objectStore, ozoneBucket.getVolumeName(), ozoneBucket.getName(),
key, aclWorldAll);
}
}
private void createKey(OzoneBucket ozoneBucket, String key, int length,
byte[] input) throws Exception {
OzoneOutputStream ozoneOutputStream = ozoneBucket.createKey(key, length);
ozoneOutputStream.write(input);
ozoneOutputStream.write(input, 0, 10);
ozoneOutputStream.close();
}
}