Skip to content

Commit

Permalink
HDDS-4715. ACL on link bucket (#4559)
Browse files Browse the repository at this point in the history
  • Loading branch information
whbing committed May 24, 2023
1 parent c848b16 commit 4e32dba
Show file tree
Hide file tree
Showing 16 changed files with 468 additions and 87 deletions.
4 changes: 3 additions & 1 deletion hadoop-hdds/docs/content/design/volume-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,11 @@ Note: Sanjay is added to the authors as the original proposal of this approach.
* `bucket link` operation creates a link bucket. Links are like regular buckets, stored in DB the same way, but with two new, optional pieces of information: source volume and bucket. (The bucket being referenced by the link is called "source", not "target", to follow symlink terminology.)
* Link buckets share the namespace with regular buckets. If a bucket or link with the same name already exists, a `BUCKET_ALREADY_EXISTS` result is returned.
* Link buckets are not inherently specific to a user, access is restricted only by ACL.
* Link buckets retain their owner ACLs, which are inherited from the default ACLs of their volume. Additionally, link buckets allow anyone to have READ and WRITE permissions, which is similar to Linux POSIX symbolic.
* All add/set/remove ACL operation proxy to the source bucket. Getacl operation of a link bucket shows link bucket's ACL.
* Links are persistent, ie. they can be used until they are deleted.
* Existing bucket operations (info, delete, ACL) work on the link object in the same way as they do on regular buckets. No new link-specific RPC is required.
* Links are followed for key operations (list, get, put, etc.). Read permission on the link is required for this.
* Links are followed for key operations (list, get, put, etc.). Read permission on the source bucket is required for this.
* Checks for existence of the source bucket, as well as ACL, are performed only when following the link (similar to symlinks). Source bucket is not checked when operating on the link bucket itself (eg. deleting it). This avoids the need for reverse checks for each bucket delete or ACL change.
* Bucket links are generic, not restricted to the `s3v` volume.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -166,6 +167,8 @@
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_CLIENT_REQUIRED_OM_VERSION_MIN_KEY;
import static org.apache.hadoop.ozone.OzoneConsts.OLD_QUOTA_DEFAULT;
import static org.apache.hadoop.ozone.OzoneConsts.OZONE_MAXIMUM_ACCESS_ID_LENGTH;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.READ;
import static org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType.WRITE;

import org.apache.logging.log4j.util.Strings;
import org.apache.ratis.protocol.ClientId;
Expand Down Expand Up @@ -643,6 +646,11 @@ public void createBucket(
if (bucketArgs.getAcls() != null) {
listOfAcls.addAll(bucketArgs.getAcls());
}
// Link bucket default acl
if (bucketArgs.getSourceVolume() != null
&& bucketArgs.getSourceBucket() != null) {
listOfAcls.add(linkBucketDefaultAcl());
}

OmBucketInfo.Builder builder = OmBucketInfo.newBuilder();
builder.setVolumeName(volumeName)
Expand Down Expand Up @@ -737,6 +745,19 @@ private List<OzoneAcl> getAclList() {
userRights, groupRights);
}

/**
* Link bucket default acl defined [world::rw]
* which is similar to Linux POSIX symbolic.
*
* @return OzoneAcl
*/
private OzoneAcl linkBucketDefaultAcl() {
BitSet aclRights = new BitSet();
aclRights.set(READ.ordinal());
aclRights.set(WRITE.ordinal());
return new OzoneAcl(ACLIdentityType.WORLD, "", aclRights, ACCESS);
}

/**
* Get a valid Delegation Token.
*
Expand Down
72 changes: 53 additions & 19 deletions hadoop-ozone/dist/src/main/smoketest/basic/links.robot
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Library OperatingSystem
Resource ../commonlib.robot
Resource ../ozone-lib/shell.robot
Test Setup Run Keyword if '${SECURITY_ENABLED}' == 'true' Kinit test user testuser testuser.keytab
Test Timeout 2 minute
Test Timeout 4 minute
Suite Setup Create volumes

*** Variables ***
Expand All @@ -39,35 +39,44 @@ Setup ACL tests
Execute ozone sh bucket create ${source}/readable-bucket
Execute ozone sh key put ${source}/readable-bucket/key-in-readable-bucket /etc/passwd
Execute ozone sh bucket create ${source}/unreadable-bucket
Execute ozone sh bucket link ${source}/readable-bucket ${target}/readable-link
Execute ozone sh bucket link ${source}/readable-bucket ${target}/unreadable-link

Execute ozone sh bucket link ${source}/unreadable-bucket ${target}/link-to-unreadable-bucket
Execute ozone sh volume addacl --acl user:testuser2:r[DEFAULT] ${target}

Execute ozone sh bucket link ${source}/readable-bucket ${target}/readable-link
Execute ozone sh bucket link ${source}/readable-bucket ${target}/readable-link2

Execute ozone sh volume addacl --acl user:testuser2:r ${target}
Execute ozone sh volume addacl --acl user:testuser2:rl ${source}
Execute ozone sh bucket addacl --acl user:testuser2:rl ${source}/readable-bucket
Execute ozone sh bucket addacl --acl user:testuser2:r ${target}/readable-link
Execute ozone sh bucket addacl --acl user:testuser2:r ${target}/link-to-unreadable-bucket

Verify Bucket ACL
[arguments] ${source_option} ${object} ${type} ${name} ${acls}
${actual_acls} = Execute ozone sh bucket getacl ${source_option} ${object} | jq -r '.[] | select(.type == "${type}") | select(.name == "${name}") | .aclList[]' | xargs
Should Be Equal ${acls} ${actual_acls}

Can follow link with read access
Execute kdestroy
Run Keyword Kinit test user testuser2 testuser2.keytab
${result} = Execute And Ignore Error ozone sh key list ${target}/readable-link
Should Contain ${result} key-in-readable-bucket
${result} = Execute And Ignore Error ozone sh key list ${target}/readable-link2
Should Contain ${result} key-in-readable-bucket

Cannot follow link without read access
Execute kdestroy
Run Keyword Kinit test user testuser2 testuser2.keytab
${result} = Execute And Ignore Error ozone sh key list ${target}/unreadable-link
${result} = Execute And Ignore Error ozone sh key list ${target}/link-to-unreadable-bucket
Should Contain ${result} PERMISSION_DENIED
${result} = Execute And Ignore Error ozone sh key list ${source}/unreadable-bucket
Should Contain ${result} PERMISSION_DENIED

ACL verified on source bucket
ACL verified on source and target bucket
Execute kdestroy
Run Keyword Kinit test user testuser2 testuser2.keytab
${result} = Execute ozone sh bucket info ${target}/link-to-unreadable-bucket
Should Contain ${result} link-to-unreadable-bucket
Should Not Contain ${result} PERMISSION_DENIED
${result} = Execute And Ignore Error ozone sh key list ${target}/link-to-unreadable-bucket
${result} = Execute And Ignore Error ozone sh bucket info ${source}/unreadable-bucket
Should Contain ${result} PERMISSION_DENIED

Create link loop
Expand Down Expand Up @@ -124,14 +133,39 @@ Bucket info shows source
Should Contain ${result} creationTime
Should Not contain ${result} metadata

Source and target have separate ACLs
Execute ozone sh bucket addacl --acl user:user1:rwxy ${target}/link1
Verify ACL bucket ${target}/link1 USER user1 READ WRITE READ_ACL WRITE_ACL
Verify ACL bucket ${source}/bucket1 USER user1 ${EMPTY}

Execute ozone sh bucket addacl --acl group:group2:r ${source}/bucket1
Verify ACL bucket ${target}/link1 GROUP group2 ${EMPTY}
Verify ACL bucket ${source}/bucket1 GROUP group2 READ
Source and target bucket have different ACLs
Execute ozone sh bucket addacl --acl user:user1:rwxy ${target}/link1
Verify ACL bucket ${target}/link1 USER user1 ${EMPTY}
Verify Bucket ACL --source=false ${target}/link1 USER user1 ${EMPTY}
Verify Bucket ACL --source ${target}/link1 USER user1 READ WRITE READ_ACL WRITE_ACL
Verify ACL bucket ${source}/bucket1 USER user1 READ WRITE READ_ACL WRITE_ACL

Execute ozone sh bucket removeacl --acl user:user1:y ${target}/link1
Verify ACL bucket ${target}/link1 USER user1 ${EMPTY}
Verify Bucket ACL --source ${target}/link1 USER user1 READ WRITE READ_ACL

Execute ozone sh bucket setacl --acl user:user1:rw ${source}/bucket1
Verify ACL bucket ${target}/link1 USER user1 ${EMPTY}
Verify Bucket ACL --source ${target}/link1 USER user1 READ WRITE

Execute ozone sh bucket addacl --acl group:group2:r ${source}/bucket1
Verify ACL bucket ${target}/link1 GROUP group2 ${EMPTY}
Verify Bucket ACL --source ${target}/link1 GROUP group2 READ

Source and target key have same ACLs
Execute ozone sh key addacl --acl user:user1:rwxy ${source}/bucket1/key1
Verify ACL key ${target}/link1/key1 USER user1 READ WRITE READ_ACL WRITE_ACL
Verify ACL key ${source}/bucket1/key1 USER user1 READ WRITE READ_ACL WRITE_ACL
Execute ozone sh key removeacl --acl user:user1:y ${target}/link1/key1
Verify ACL key ${target}/link1/key1 USER user1 READ WRITE READ_ACL
Verify ACL key ${source}/bucket1/key1 USER user1 READ WRITE READ_ACL
Execute ozone sh key setacl --acl user:user1:rw ${source}/bucket1/key1
Verify ACL key ${target}/link1/key1 USER user1 READ WRITE
Verify ACL key ${source}/bucket1/key1 USER user1 READ WRITE

Execute ozone sh key addacl --acl group:group2:r ${source}/bucket1/key1
Verify ACL key ${target}/link1/key1 GROUP group2 READ
Verify ACL key ${source}/bucket1/key1 GROUP group2 READ

Buckets and links share namespace
Execute ozone sh bucket link ${source}/bucket2 ${target}/link2
Expand All @@ -148,8 +182,8 @@ Can follow link with read access
Cannot follow link without read access
Run Keyword if '${SECURITY_ENABLED}' == 'true' Cannot follow link without read access

ACL verified on source bucket
Run Keyword if '${SECURITY_ENABLED}' == 'true' ACL verified on source bucket
ACL verified on source and target bucket
Run Keyword if '${SECURITY_ENABLED}' == 'true' ACL verified on source and target bucket

Loop in link chain is detected
[setup] Create link loop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.apache.hadoop.ozone.MiniOzoneCluster;
import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
import org.apache.hadoop.ozone.OzoneConfigKeys;
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;
Expand Down Expand Up @@ -259,6 +260,40 @@ protected OzoneBucket setupBucket() throws Exception {
return ozoneBucket;
}

protected OzoneBucket linkBucket(OzoneBucket srcBuk) throws Exception {
String userName = "user" + RandomStringUtils.randomNumeric(5);
String adminName = "admin" + RandomStringUtils.randomNumeric(5);
String linkedVolName = "volume-link-" + RandomStringUtils.randomNumeric(5);

VolumeArgs createVolumeArgs = VolumeArgs.newBuilder()
.setOwner(userName)
.setAdmin(adminName)
.build();

BucketArgs createBucketArgs = new BucketArgs.Builder()
.setSourceVolume(srcBuk.getVolumeName())
.setSourceBucket(srcBuk.getName())
.build();

objectStore.createVolume(linkedVolName, createVolumeArgs);
OzoneVolume linkedVolumeInfo = objectStore.getVolume(linkedVolName);

Assert.assertTrue(linkedVolumeInfo.getName().equals(linkedVolName));
Assert.assertTrue(linkedVolumeInfo.getOwner().equals(userName));
Assert.assertTrue(linkedVolumeInfo.getAdmin().equals(adminName));

String linkedBucketName = UUID.randomUUID().toString();
linkedVolumeInfo.createBucket(linkedBucketName, createBucketArgs);

OzoneBucket linkedBucket = linkedVolumeInfo.getBucket(linkedBucketName);

Assert.assertTrue(linkedBucket.getName().equals(linkedBucketName));
Assert.assertTrue(linkedBucket.getVolumeName().equals(linkedVolName));
Assert.assertTrue(linkedBucket.isLink());

return linkedBucket;
}

/**
* Stop the current leader OM.
*
Expand Down

0 comments on commit 4e32dba

Please sign in to comment.