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]