This is an automated email from the ASF dual-hosted git repository.

smengcl pushed a commit to branch ozone-2.1
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/ozone-2.1 by this push:
     new ab2adac2d0f HDDS-15064. [STS] Artifacts for Ranger to Consider S3 
Action when Authorizing (#10316)
ab2adac2d0f is described below

commit ab2adac2d0f3e0205d6220fac033c76fc6b42dfe
Author: fmorg-git <[email protected]>
AuthorDate: Thu May 21 08:46:22 2026 -0700

    HDDS-15064. [STS] Artifacts for Ranger to Consider S3 Action when 
Authorizing (#10316)
---
 .../ozone/security/acl/AssumeRoleRequest.java      | 31 +++++++-
 .../hadoop/ozone/security/acl/RequestContext.java  | 31 ++++++++
 .../ozone/security/acl/TestAssumeRoleRequest.java  | 73 +++++++++++++++---
 .../hadoop/ozone/security/acl/package-info.java    | 21 ++++++
 .../ozone/security/acl/TestRequestContext.java     | 86 ++++++++++++++++++++++
 5 files changed, 230 insertions(+), 12 deletions(-)

diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java
index 1272d5422ec..20278a0ecfd 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java
@@ -18,6 +18,8 @@
 package org.apache.hadoop.ozone.security.acl;
 
 import java.net.InetAddress;
+import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.Objects;
 import java.util.Set;
 import net.jcip.annotations.Immutable;
@@ -93,10 +95,24 @@ public int hashCode() {
   public static class OzoneGrant {
     private final Set<IOzoneObj> objects;
     private final Set<IAccessAuthorizer.ACLType> permissions;
+    /**
+     * S3 action names without the s3: prefix (e.g. GetObject) from the 
session policy.  When present, the permissions
+     * will be further restricted by the set of available S3 actions.  An 
empty (or null) set means this OzoneGrant
+     * does not enforce any restrictions on actions.
+     */
+    private final Set<String> s3Actions;
 
     public OzoneGrant(Set<IOzoneObj> objects, Set<IAccessAuthorizer.ACLType> 
permissions) {
       this.objects = objects;
       this.permissions = permissions;
+      this.s3Actions = Collections.emptySet();
+    }
+
+    public OzoneGrant(Set<IOzoneObj> objects, Set<IAccessAuthorizer.ACLType> 
permissions,
+        Set<String> s3Actions) {
+      this.objects = objects;
+      this.permissions = permissions;
+      this.s3Actions = Collections.unmodifiableSet(new 
LinkedHashSet<>(s3Actions));
     }
 
     public Set<IOzoneObj> getObjects() {
@@ -107,6 +123,10 @@ public Set<IAccessAuthorizer.ACLType> getPermissions() {
       return permissions;
     }
 
+    public Set<String> getS3Actions() {
+      return s3Actions;
+    }
+
     @Override
     public boolean equals(Object o) {
       if (this == o) {
@@ -116,12 +136,19 @@ public boolean equals(Object o) {
       }
 
       final OzoneGrant that = (OzoneGrant) o;
-      return Objects.equals(objects, that.objects) && 
Objects.equals(permissions, that.permissions);
+      return Objects.equals(objects, that.objects) && 
Objects.equals(permissions, that.permissions) &&
+          Objects.equals(s3Actions, that.s3Actions);
     }
 
     @Override
     public int hashCode() {
-      return Objects.hash(objects, permissions);
+      return Objects.hash(objects, permissions, s3Actions);
+    }
+
+    @Override
+    public String toString() {
+      return "OzoneGrant{" + "objects=" + objects + ", permissions=" + 
permissions + ", s3Actions="
+          + s3Actions + '}';
     }
   }
 }
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/RequestContext.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/RequestContext.java
index 44e0f9284ab..2d80fa2d7f9 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/RequestContext.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/RequestContext.java
@@ -50,6 +50,12 @@ public final class RequestContext {
    */
   private final String sessionPolicy;
 
+  /**
+   * S3 action name for this request without the s3: prefix (e.g. PutObject), 
when the call originated from S3 Gateway.
+   * Null for non-S3 clients or when not applicable.
+   */
+  private final String s3Action;
+
   private RequestContext(Builder builder) {
     this.host = builder.host;
     this.ip = builder.ip;
@@ -60,6 +66,7 @@ private RequestContext(Builder builder) {
     this.ownerName = builder.ownerName;
     this.recursiveAccessCheck = builder.recursiveAccessCheck;
     this.sessionPolicy = builder.sessionPolicy;
+    this.s3Action = builder.s3Action;
   }
 
   /**
@@ -81,6 +88,7 @@ public static final class Builder {
 
     private boolean recursiveAccessCheck;
     private String sessionPolicy;
+    private String s3Action;
 
     private Builder() {
 
@@ -135,6 +143,11 @@ public Builder setSessionPolicy(String sessionPolicy) {
       return this;
     }
 
+    public Builder setS3Action(String s3Action) {
+      this.s3Action = s3Action;
+      return this;
+    }
+
     public RequestContext build() {
       return new RequestContext(this);
     }
@@ -185,4 +198,22 @@ public boolean isRecursiveAccessCheck() {
   public String getSessionPolicy() {
     return sessionPolicy;
   }
+
+  public String getS3Action() {
+    return s3Action;
+  }
+
+  public Builder toBuilder() {
+    return newBuilder()
+        .setHost(host)
+        .setIp(ip)
+        .setClientUgi(clientUgi)
+        .setServiceId(serviceId)
+        .setAclType(aclType)
+        .setAclRights(aclRights)
+        .setOwnerName(ownerName)
+        .setRecursiveAccessCheck(recursiveAccessCheck)
+        .setSessionPolicy(sessionPolicy)
+        .setS3Action(s3Action);
+  }
 }
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/TestAssumeRoleRequest.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/TestAssumeRoleRequest.java
index e9d9c519bd1..c163517afd2 100644
--- 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/TestAssumeRoleRequest.java
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/TestAssumeRoleRequest.java
@@ -21,9 +21,11 @@
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.Set;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.junit.jupiter.api.Test;
@@ -36,23 +38,32 @@ public class TestAssumeRoleRequest {
   @Test
   public void testConstructorAndGetters() {
     final UserGroupInformation ugi = 
UserGroupInformation.createRemoteUser("om");
+    final IOzoneObj bucketObj =
+        OzoneObjInfo.Builder.newBuilder()
+            .setResType(OzoneObj.ResourceType.BUCKET)
+            .setStoreType(OzoneObj.StoreType.OZONE)
+            .setVolumeName("s3v")
+            .setBucketName("myBucket")
+            .build();
+    final Set<IOzoneObj> objects = Collections.singleton(bucketObj);
+    final Set<IAccessAuthorizer.ACLType> permissions = 
Collections.singleton(IAccessAuthorizer.ACLType.READ);
+    final Set<String> s3Actions = Collections.emptySet();
+
     final Set<AssumeRoleRequest.OzoneGrant> grants = new HashSet<>();
-    grants.add(
-        new AssumeRoleRequest.OzoneGrant(
-            Collections.singleton(
-                OzoneObjInfo.Builder.newBuilder()
-                    .setResType(OzoneObj.ResourceType.BUCKET)
-                    .setStoreType(OzoneObj.StoreType.OZONE)
-                    .setVolumeName("s3v")
-                    .setBucketName("myBucket")
-                    .build()),
-            Collections.singleton(IAccessAuthorizer.ACLType.READ)));
+    grants.add(new AssumeRoleRequest.OzoneGrant(objects, permissions, 
s3Actions));
 
     final AssumeRoleRequest assumeRoleRequest1 = new AssumeRoleRequest(
         "host", null, ugi, "roleA", grants);
     final AssumeRoleRequest assumeRoleRequest2 = new AssumeRoleRequest(
         "host", null, ugi, "roleA", grants);
 
+    final AssumeRoleRequest.OzoneGrant grant = grants.iterator().next();
+    assertEquals(objects, grant.getObjects());
+    assertEquals(permissions, grant.getPermissions());
+    assertEquals(s3Actions, grant.getS3Actions());
+    // Ensure the s3 actions are not modifiable
+    assertThrows(UnsupportedOperationException.class, () -> 
grant.getS3Actions().add("GetObject"));
+
     assertEquals("host", assumeRoleRequest1.getHost());
     assertNull(assumeRoleRequest1.getIp());
     assertSame(ugi, assumeRoleRequest1.getClientUgi());
@@ -66,6 +77,48 @@ public void testConstructorAndGetters() {
         "host", null, ugi, "roleB", null);
     assertNotEquals(assumeRoleRequest1, assumeRoleRequest3);
   }
+
+  @Test
+  public void testGrantsWithS3Actions() {
+    final UserGroupInformation ugi = 
UserGroupInformation.createRemoteUser("om");
+
+    final IOzoneObj bucketObj =
+        OzoneObjInfo.Builder.newBuilder()
+            .setResType(OzoneObj.ResourceType.BUCKET)
+            .setStoreType(OzoneObj.StoreType.OZONE)
+            .setVolumeName("s3v")
+            .setBucketName("myBucket")
+            .build();
+
+    final Set<IOzoneObj> objects = Collections.singleton(bucketObj);
+    final Set<IAccessAuthorizer.ACLType> permissions = 
Collections.singleton(IAccessAuthorizer.ACLType.READ);
+
+    final Set<String> s3Actions = new LinkedHashSet<>();
+    s3Actions.add("GetObject");
+    s3Actions.add("PutObject");
+
+    final AssumeRoleRequest.OzoneGrant grantWithActions = new 
AssumeRoleRequest.OzoneGrant(
+        objects, permissions, s3Actions);
+    final AssumeRoleRequest.OzoneGrant grantWithoutActions = new 
AssumeRoleRequest.OzoneGrant(
+        objects, permissions, Collections.emptySet());
+
+    assertEquals(objects, grantWithActions.getObjects());
+    assertEquals(permissions, grantWithActions.getPermissions());
+    assertEquals(s3Actions, grantWithActions.getS3Actions());
+    assertNotEquals(grantWithActions, grantWithoutActions);
+    assertNotEquals(grantWithActions.hashCode(), 
grantWithoutActions.hashCode());
+
+    final Set<AssumeRoleRequest.OzoneGrant> grantsWithActionsSet = 
Collections.singleton(grantWithActions);
+    final Set<AssumeRoleRequest.OzoneGrant> grantsWithoutActionsSet = 
Collections.singleton(grantWithoutActions);
+
+    final AssumeRoleRequest requestWithActions = new AssumeRoleRequest(
+        "host", null, ugi, "roleA", grantsWithActionsSet);
+    final AssumeRoleRequest requestWithoutActions = new AssumeRoleRequest(
+        "host", null, ugi, "roleA", grantsWithoutActionsSet);
+
+    assertNotEquals(requestWithActions, requestWithoutActions);
+    assertNotEquals(requestWithActions.hashCode(), 
requestWithoutActions.hashCode());
+  }
 }
 
 
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/package-info.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/package-info.java
new file mode 100644
index 00000000000..ff6060adcb4
--- /dev/null
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+/**
+ * Unit tests related to acl-related functionality.
+ */
+package org.apache.hadoop.ozone.security.acl;
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestRequestContext.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestRequestContext.java
index a0b9bfbc7f8..45272946dac 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestRequestContext.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestRequestContext.java
@@ -22,6 +22,7 @@
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.apache.hadoop.security.UserGroupInformation;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -51,4 +52,89 @@ void testSessionPolicy() {
     builder.setSessionPolicy(policy);
     assertEquals(policy, builder.build().getSessionPolicy());
   }
+
+  @Test
+  public void testToBuilderWithNoModifications() {
+    // Create a RequestContext with all fields set
+    final UserGroupInformation ugi = 
UserGroupInformation.createRemoteUser("testUser");
+    final String host = "testHost";
+    final String serviceId = "testServiceId";
+    final String ownerName = "testOwner";
+    final String sessionPolicy = "{\"Statement\":[{\"Effect\":\"Allow\"}]}";
+    final String s3Action = "GetObject";
+
+    final RequestContext original = RequestContext.newBuilder()
+        .setHost(host)
+        .setClientUgi(ugi)
+        .setServiceId(serviceId)
+        .setAclType(IAccessAuthorizer.ACLIdentityType.USER)
+        .setAclRights(IAccessAuthorizer.ACLType.READ)
+        .setOwnerName(ownerName)
+        .setRecursiveAccessCheck(true)
+        .setSessionPolicy(sessionPolicy)
+        .setS3Action(s3Action)
+        .build();
+
+    // Use toBuilder to create a new builder
+    final RequestContext.Builder builder = original.toBuilder();
+    final RequestContext requestCtxFromToBuilder = builder.build();
+
+    // Verify all fields are preserved
+    assertEquals(original.getHost(), requestCtxFromToBuilder.getHost(), "Host 
should be preserved");
+    assertNull(original.getIp(), "IP should be preserved");
+    assertEquals(original.getClientUgi(), 
requestCtxFromToBuilder.getClientUgi(), "ClientUgi should be preserved");
+    assertEquals(original.getServiceId(), 
requestCtxFromToBuilder.getServiceId(), "ServiceId should be preserved");
+    assertEquals(original.getAclType(), requestCtxFromToBuilder.getAclType(), 
"AclType should be preserved");
+    assertEquals(original.getAclRights(), 
requestCtxFromToBuilder.getAclRights(), "AclRights should be preserved");
+    assertEquals(original.getOwnerName(), 
requestCtxFromToBuilder.getOwnerName(), "OwnerName should be preserved");
+    assertTrue(original.isRecursiveAccessCheck(), "RecursiveAccessCheck should 
be preserved");
+    assertEquals(original.getSessionPolicy(), 
requestCtxFromToBuilder.getSessionPolicy(),
+        "SessionPolicy should be preserved");
+    assertEquals(original.getS3Action(), 
requestCtxFromToBuilder.getS3Action(), "S3 action should be preserved");
+  }
+
+  @Test
+  public void testToBuilderWithModifications() {
+    // Create an original RequestContext
+    final UserGroupInformation originalUgi = 
UserGroupInformation.createRemoteUser("user1");
+    final RequestContext original = RequestContext.newBuilder()
+        .setHost("host1")
+        .setClientUgi(originalUgi)
+        .setServiceId("service1")
+        .setAclType(IAccessAuthorizer.ACLIdentityType.USER)
+        .setAclRights(IAccessAuthorizer.ACLType.READ)
+        .setOwnerName("owner1")
+        .setRecursiveAccessCheck(false)
+        .build();
+
+    // Use toBuilder and modify some fields
+    final UserGroupInformation newUgi = 
UserGroupInformation.createRemoteUser("user2");
+    final RequestContext modified = original.toBuilder()
+        .setHost("host2")
+        .setClientUgi(newUgi)
+        .setAclRights(IAccessAuthorizer.ACLType.WRITE)
+        .setOwnerName("owner2")
+        .setRecursiveAccessCheck(true)
+        .setSessionPolicy("{\"Statement\":[]}")
+        .setS3Action("DeleteObject")
+        .build();
+
+    // Verify original is unchanged
+    assertEquals("host1", original.getHost(), "Original should be unchanged");
+    assertEquals(originalUgi, original.getClientUgi(), "Original UGI should be 
unchanged");
+    assertEquals(IAccessAuthorizer.ACLType.READ, original.getAclRights(), 
"Original ACL rights should be unchanged");
+    assertEquals("owner1", original.getOwnerName(), "Original owner name 
should be unchanged");
+    assertFalse(original.isRecursiveAccessCheck(), "Original recursive flag 
should be unchanged");
+    assertNull(original.getSessionPolicy(), "Original session policy should be 
unchanged");
+    assertNull(original.getS3Action(), "Original S3 action should be 
unchanged");
+
+    // Verify modified has new values
+    assertEquals("host2", modified.getHost(), "Modified host should be 
updated");
+    assertEquals(newUgi, modified.getClientUgi(), "Modified UGI should be 
updated");
+    assertEquals(IAccessAuthorizer.ACLType.WRITE, modified.getAclRights(), 
"Modified ACL rights should be updated");
+    assertEquals("owner2", modified.getOwnerName(), "Modified owner should be 
updated");
+    assertTrue(modified.isRecursiveAccessCheck(), "Modified recursive flag 
should be updated");
+    assertEquals("{\"Statement\":[]}", modified.getSessionPolicy(), "Modified 
session policy should be updated");
+    assertEquals("DeleteObject", modified.getS3Action(), "Modified S3 action 
should be updated");
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to