|
43 | 43 | import org.apache.hadoop.hdds.utils.IOUtils; |
44 | 44 | import org.apache.hadoop.ozone.MiniOzoneCluster; |
45 | 45 | import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl; |
| 46 | +import org.apache.hadoop.ozone.OzoneAcl; |
46 | 47 | import org.apache.hadoop.ozone.client.BucketArgs; |
47 | 48 | import org.apache.hadoop.ozone.client.ObjectStore; |
48 | 49 | import org.apache.hadoop.ozone.client.OzoneBucket; |
|
56 | 57 | import org.apache.hadoop.ozone.om.helpers.BucketLayout; |
57 | 58 | import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; |
58 | 59 | import org.apache.hadoop.ozone.security.acl.OzoneNativeAuthorizer; |
| 60 | +import org.apache.hadoop.ozone.security.acl.OzoneObj; |
| 61 | +import org.apache.hadoop.ozone.security.acl.OzoneObjInfo; |
59 | 62 | import org.apache.hadoop.security.UserGroupInformation; |
60 | 63 | import org.apache.ozone.test.GenericTestUtils; |
61 | 64 | import org.junit.jupiter.api.AfterAll; |
@@ -857,6 +860,132 @@ public void testKeysRenameAclEnforcementAfterLeadershipChange() throws Exception |
857 | 860 | assertEquals(OMException.ResultCodes.KEY_NOT_FOUND, ex2.getResult()); |
858 | 861 | } |
859 | 862 |
|
| 863 | + /** |
| 864 | + * Tests that key ACL operations (addAcl, removeAcl, setAcl) are enforced in preExecute |
| 865 | + * and are leader-specific. Uses FILE_SYSTEM_OPTIMIZED bucket layout. |
| 866 | + * |
| 867 | + * <p>This test verifies that ACL operations on keys work correctly across leadership changes: |
| 868 | + * <ul> |
| 869 | + * <li>Admin user can modify ACLs on any key when they're admin on the current leader</li> |
| 870 | + * <li>Admin user cannot modify ACLs after leadership transfer to a node where they're not admin</li> |
| 871 | + * <li>ACL checks happen in preExecute phase before any transaction execution</li> |
| 872 | + * </ul> |
| 873 | + */ |
| 874 | + @Test |
| 875 | + public void testKeyAclOperationsEnforcementAfterLeadershipChangeWithFSO() throws Exception { |
| 876 | + ObjectStore adminObjectStore = client.getObjectStore(); |
| 877 | + String testVolume = "keyaclvol-" + |
| 878 | + RandomStringUtils.secure().nextAlphabetic(5).toLowerCase(Locale.ROOT); |
| 879 | + String testBucket = "keyaclbucket-" + |
| 880 | + RandomStringUtils.secure().nextAlphabetic(5).toLowerCase(Locale.ROOT); |
| 881 | + String keyName = "keyacl-" + |
| 882 | + RandomStringUtils.secure().nextAlphabetic(5).toLowerCase(Locale.ROOT); |
| 883 | + |
| 884 | + // Step 1: Create volume, bucket with FSO layout, and key as admin |
| 885 | + VolumeArgs volumeArgs = VolumeArgs.newBuilder() |
| 886 | + .setOwner(adminUserUgi.getShortUserName()) |
| 887 | + .build(); |
| 888 | + adminObjectStore.createVolume(testVolume, volumeArgs); |
| 889 | + OzoneVolume adminVolume = adminObjectStore.getVolume(testVolume); |
| 890 | + |
| 891 | + // Use FILE_SYSTEM_OPTIMIZED bucket layout |
| 892 | + BucketArgs bucketArgs = BucketArgs.newBuilder() |
| 893 | + .setBucketLayout(BucketLayout.FILE_SYSTEM_OPTIMIZED) |
| 894 | + .build(); |
| 895 | + adminVolume.createBucket(testBucket, bucketArgs); |
| 896 | + OzoneBucket adminBucket = adminVolume.getBucket(testBucket); |
| 897 | + |
| 898 | + // Create a key as admin (so test user is NOT the owner) |
| 899 | + try (OzoneOutputStream out = adminBucket.createKey(keyName, 0)) { |
| 900 | + out.write("test data for ACL operations".getBytes(UTF_8)); |
| 901 | + } |
| 902 | + |
| 903 | + OzoneKey key = adminBucket.getKey(keyName); |
| 904 | + assertNotNull(key, "Key should be created successfully"); |
| 905 | + |
| 906 | + // Create OzoneObj for ACL operations |
| 907 | + OzoneObj keyObj = OzoneObjInfo.Builder.newBuilder() |
| 908 | + .setVolumeName(testVolume) |
| 909 | + .setBucketName(testBucket) |
| 910 | + .setKeyName(keyName) |
| 911 | + .setResType(OzoneObj.ResourceType.KEY) |
| 912 | + .setStoreType(OzoneObj.StoreType.OZONE) |
| 913 | + .build(); |
| 914 | + |
| 915 | + int originalAclCount = adminObjectStore.getAcl(keyObj).size(); |
| 916 | + |
| 917 | + // Step 2: Add test user as admin on current leader |
| 918 | + OzoneManager currentLeader = cluster.getOMLeader(); |
| 919 | + String leaderNodeId = currentLeader.getOMNodeId(); |
| 920 | + addAdminToSpecificOM(currentLeader, TEST_USER); |
| 921 | + assertThat(currentLeader.getOmAdminUsernames()).contains(TEST_USER); |
| 922 | + |
| 923 | + // Step 3: Test user performs ACL operations as admin (should succeed) |
| 924 | + UserGroupInformation.setLoginUser(testUserUgi); |
| 925 | + try (OzoneClient userClient = OzoneClientFactory.getRpcClient(OM_SERVICE_ID, cluster.getConf())) { |
| 926 | + ObjectStore userObjectStore = userClient.getObjectStore(); |
| 927 | + |
| 928 | + // Add ACL - should succeed |
| 929 | + OzoneAcl addAcl = OzoneAcl.parseAcl("user:anotheruser:rw[ACCESS]"); |
| 930 | + boolean addResult = userObjectStore.addAcl(keyObj, addAcl); |
| 931 | + assertThat(addResult).isTrue(); |
| 932 | + |
| 933 | + // Verify ACL was added |
| 934 | + List<OzoneAcl> acls = userObjectStore.getAcl(keyObj); |
| 935 | + assertThat(acls).hasSize(originalAclCount + 1); |
| 936 | + assertThat(acls).contains(addAcl); |
| 937 | + |
| 938 | + // Set ACL - should succeed |
| 939 | + OzoneAcl setAcl = OzoneAcl.parseAcl("user:setuser:rwx[ACCESS]"); |
| 940 | + boolean setResult = userObjectStore.setAcl(keyObj, Collections.singletonList(setAcl)); |
| 941 | + assertThat(setResult).isTrue(); |
| 942 | + |
| 943 | + // Verify ACL was set (replaced all previous ACLs) |
| 944 | + acls = userObjectStore.getAcl(keyObj); |
| 945 | + assertThat(acls).hasSize(1); |
| 946 | + assertThat(acls).contains(setAcl); |
| 947 | + |
| 948 | + // Step 4: Transfer leadership to another node where test user is NOT admin |
| 949 | + OzoneManager newLeader = transferLeadershipToAnotherNode(currentLeader); |
| 950 | + assertNotEquals(leaderNodeId, newLeader.getOMNodeId(), |
| 951 | + "Leadership should have transferred to a different node"); |
| 952 | + assertThat(newLeader.getOmAdminUsernames()).doesNotContain(TEST_USER); |
| 953 | + |
| 954 | + // Step 5: Try ACL operations on new leader - should fail with PERMISSION_DENIED |
| 955 | + OzoneAcl anotherAcl = OzoneAcl.parseAcl("user:yetanotheruser:r[ACCESS]"); |
| 956 | + |
| 957 | + // Add ACL should fail |
| 958 | + OMException addException = assertThrows(OMException.class, () -> { |
| 959 | + userObjectStore.addAcl(keyObj, anotherAcl); |
| 960 | + }, "addAcl should fail for non-admin user on new leader"); |
| 961 | + assertEquals(PERMISSION_DENIED, addException.getResult(), |
| 962 | + "Should get PERMISSION_DENIED when ACL check fails in preExecute"); |
| 963 | + |
| 964 | + // Remove ACL should fail |
| 965 | + OMException removeException = assertThrows(OMException.class, () -> { |
| 966 | + userObjectStore.removeAcl(keyObj, setAcl); |
| 967 | + }, "removeAcl should fail for non-admin user on new leader"); |
| 968 | + assertEquals(PERMISSION_DENIED, removeException.getResult(), |
| 969 | + "Should get PERMISSION_DENIED when ACL check fails in preExecute"); |
| 970 | + |
| 971 | + // Set ACL should fail |
| 972 | + OzoneAcl newSetAcl = OzoneAcl.parseAcl("user:failuser:w[ACCESS]"); |
| 973 | + OMException setException = assertThrows(OMException.class, () -> { |
| 974 | + userObjectStore.setAcl(keyObj, Collections.singletonList(newSetAcl)); |
| 975 | + }, "setAcl should fail for non-admin user on new leader"); |
| 976 | + assertEquals(PERMISSION_DENIED, setException.getResult(), |
| 977 | + "Should get PERMISSION_DENIED when ACL check fails in preExecute"); |
| 978 | + |
| 979 | + } finally { |
| 980 | + UserGroupInformation.setLoginUser(adminUserUgi); |
| 981 | + } |
| 982 | + |
| 983 | + // Step 6: Verify the ACLs remain unchanged (operations were rejected in preExecute) |
| 984 | + List<OzoneAcl> finalAcls = adminObjectStore.getAcl(keyObj); |
| 985 | + assertThat(finalAcls).hasSize(1); |
| 986 | + assertThat(finalAcls).contains(OzoneAcl.parseAcl("user:setuser:rwx[ACCESS]")); |
| 987 | + } |
| 988 | + |
860 | 989 | /** |
861 | 990 | * Helper method to check if volume exists. |
862 | 991 | */ |
|
0 commit comments