From ddb67e94da68faa95ffdee908aad859da327bdec Mon Sep 17 00:00:00 2001 From: Fabian Morgan Date: Tue, 28 Oct 2025 13:59:24 -0700 Subject: [PATCH 1/2] artifacts for Ranger to authorizer STS token --- .../ozone/security/acl/AssumeRoleRequest.java | 95 +++++++++++++++++++ .../ozone/security/acl/IAccessAuthorizer.java | 23 ++++- .../ozone/security/acl/RequestContext.java | 39 ++++++-- 3 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java 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 new file mode 100644 index 000000000000..71cb388a14c7 --- /dev/null +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +package org.apache.hadoop.ozone.security.acl; + +import java.net.InetAddress; +import java.util.AbstractMap; +import java.util.Objects; +import java.util.Set; +import net.jcip.annotations.Immutable; +import org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver; +import org.apache.hadoop.security.UserGroupInformation; + +/** + * Represents an S3 AssumeRole request that needs to be authorized by an IAccessAuthorizer. + * The grants parameter can be obtained via a call to + * {@link IamSessionPolicyResolver#resolve(String, String, IamSessionPolicyResolver.AuthorizerType)}, + * or it can be null if the access must not be limited beyond the role. + */ +@Immutable +public class AssumeRoleRequest { + private final String host; + private final InetAddress ip; + private final UserGroupInformation clientUgi; + private final String targetRoleName; + private final Set, Set>> grants; + + public AssumeRoleRequest( + String host, + InetAddress ip, + UserGroupInformation clientUgi, + String targetRoleName, + Set, Set>> grants + ) { + + this.host = host; + this.ip = ip; + this.clientUgi = clientUgi; + this.targetRoleName = targetRoleName; + this.grants = grants; + } + + public String getHost() { + return host; + } + + public InetAddress getIp() { + return ip; + } + + public UserGroupInformation getClientUgi() { + return clientUgi; + } + + public String getTargetRoleName() { + return targetRoleName; + } + + public Set, Set>> getGrants() { + return grants; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + + final AssumeRoleRequest that = (AssumeRoleRequest) o; + return Objects.equals(host, that.host) && + Objects.equals(ip, that.ip) && + Objects.equals(clientUgi, that.clientUgi) && + Objects.equals(targetRoleName, that.targetRoleName) && + Objects.equals(grants, that.grants); + } + + @Override + public int hashCode() { + return Objects.hash(host, ip, clientUgi, targetRoleName, grants); + } +} diff --git a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/IAccessAuthorizer.java b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/IAccessAuthorizer.java index f1218a9aa088..890d8a96de82 100644 --- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/IAccessAuthorizer.java +++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/IAccessAuthorizer.java @@ -48,6 +48,25 @@ public interface IAccessAuthorizer { boolean checkAccess(IOzoneObj ozoneObject, RequestContext context) throws OMException; + /** + * Attempts to authorize an STS AssumeRole request. If authorized, returns a String + * representation of the authorized session policy. This return value must be supplied on the subsequent + * {@link IAccessAuthorizer#checkAccess(IOzoneObj, RequestContext)} call, using the + * {@link RequestContext.Builder#setSessionPolicy(String)} parameter, and the authorizer will + * use the Role permissions and the session policy permissions to determine if + * the attempted action should be allowed for the given STS token. + *

+ * The user making this call must have the {@link ACLType#GEN_ACCESS_TOKEN} permission. + * + * @param assumeRoleRequest the AssumeRole request containing role and optional limited scope policy grants + * @return a String representing the permissions granted according to the authorizer. + * @throws OMException if the caller is not authorized, either for the role and/or policy or for the + * {@link ACLType#GEN_ACCESS_TOKEN} permission + */ + default String generateAssumeRoleSessionPolicy(AssumeRoleRequest assumeRoleRequest) throws OMException { + return null; + } + /** * @return true for Ozone-native authorizer */ @@ -67,7 +86,9 @@ enum ACLType { READ_ACL, WRITE_ACL, ALL, - NONE; + NONE, + GEN_ACCESS_TOKEN; // ability to create STS tokens + private static int length = ACLType.values().length; static { if (length > 16) { 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 08724eae5ff2..7f92cc3d2671 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 @@ -43,20 +43,27 @@ public class RequestContext { */ private final boolean recursiveAccessCheck; + /** + * Represents optional session policy JSON for Ranger to use when authorizing + * an STS token. This value would have come as a result of a previous + * {@link IAccessAuthorizer#generateAssumeRoleSessionPolicy(AssumeRoleRequest)} call. + */ + private final String sessionPolicy; + @SuppressWarnings("parameternumber") public RequestContext(String host, InetAddress ip, UserGroupInformation clientUgi, String serviceId, ACLIdentityType aclType, ACLType aclRights, - String ownerName) { + String ownerName, String sessionPolicy) { this(host, ip, clientUgi, serviceId, aclType, aclRights, ownerName, - false); + false, sessionPolicy); } @SuppressWarnings("parameternumber") public RequestContext(String host, InetAddress ip, UserGroupInformation clientUgi, String serviceId, ACLIdentityType aclType, ACLType aclRights, - String ownerName, boolean recursiveAccessCheck) { + String ownerName, boolean recursiveAccessCheck, String sessionPolicy) { this.host = host; this.ip = ip; this.clientUgi = clientUgi; @@ -65,6 +72,7 @@ public RequestContext(String host, InetAddress ip, this.aclRights = aclRights; this.ownerName = ownerName; this.recursiveAccessCheck = recursiveAccessCheck; + this.sessionPolicy = sessionPolicy; } /** @@ -85,6 +93,7 @@ public static class Builder { private String ownerName; private boolean recursiveAccessCheck; + private String sessionPolicy; public Builder setHost(String bHost) { this.host = bHost; @@ -130,9 +139,14 @@ public Builder setRecursiveAccessCheck(boolean recursiveAccessCheckFlag) { return this; } + public Builder setSessionPolicy(String sessionPolicy) { + this.sessionPolicy = sessionPolicy; + return this; + } + public RequestContext build() { return new RequestContext(host, ip, clientUgi, serviceId, aclType, - aclRights, ownerName, recursiveAccessCheck); + aclRights, ownerName, recursiveAccessCheck, sessionPolicy); } } @@ -142,14 +156,14 @@ public static Builder newBuilder() { public static RequestContext.Builder getBuilder( UserGroupInformation ugi, InetAddress remoteAddress, String hostName, - ACLType aclType, String ownerName) { + ACLType aclType, String ownerName, String sessionPolicy) { return getBuilder(ugi, remoteAddress, hostName, aclType, ownerName, - false); + false, sessionPolicy); } public static RequestContext.Builder getBuilder( UserGroupInformation ugi, InetAddress remoteAddress, String hostName, - ACLType aclType, String ownerName, boolean recursiveAccessCheck) { + ACLType aclType, String ownerName, boolean recursiveAccessCheck, String sessionPolicy) { RequestContext.Builder contextBuilder = RequestContext.newBuilder() .setClientUgi(ugi) .setIp(remoteAddress) @@ -157,16 +171,17 @@ public static RequestContext.Builder getBuilder( .setAclType(ACLIdentityType.USER) .setAclRights(aclType) .setOwnerName(ownerName) - .setRecursiveAccessCheck(recursiveAccessCheck); + .setRecursiveAccessCheck(recursiveAccessCheck) + .setSessionPolicy(sessionPolicy); return contextBuilder; } public static RequestContext.Builder getBuilder(UserGroupInformation ugi, - ACLType aclType, String ownerName) { + ACLType aclType, String ownerName, String sessionPolicy) { return getBuilder(ugi, ProtobufRpcEngine.Server.getRemoteIp(), ProtobufRpcEngine.Server.getRemoteIp().getHostName(), - aclType, ownerName); + aclType, ownerName, sessionPolicy); } public String getHost() { @@ -206,4 +221,8 @@ public String getOwnerName() { public boolean isRecursiveAccessCheck() { return recursiveAccessCheck; } + + public String getSessionPolicy() { + return sessionPolicy; + } } From 6eafbc47b961e922472fcc8704af4507e6871f91 Mon Sep 17 00:00:00 2001 From: Fabian Morgan Date: Tue, 28 Oct 2025 16:56:28 -0700 Subject: [PATCH 2/2] fix compilation issues and add unit tests --- .../ozone/security/acl/RequestContext.java | 43 +++++++++--- .../security/acl/TestAssumeRoleRequest.java | 70 +++++++++++++++++++ .../security/acl/TestRequestContext.java | 63 ++++++++++++++++- 3 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/TestAssumeRoleRequest.java 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 7f92cc3d2671..8bb364ccac1e 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 @@ -54,16 +54,30 @@ public class RequestContext { public RequestContext(String host, InetAddress ip, UserGroupInformation clientUgi, String serviceId, ACLIdentityType aclType, ACLType aclRights, - String ownerName, String sessionPolicy) { + String ownerName) { this(host, ip, clientUgi, serviceId, aclType, aclRights, ownerName, - false, sessionPolicy); + false, null); } @SuppressWarnings("parameternumber") public RequestContext(String host, InetAddress ip, UserGroupInformation clientUgi, String serviceId, ACLIdentityType aclType, ACLType aclRights, - String ownerName, boolean recursiveAccessCheck, String sessionPolicy) { + String ownerName, boolean recursiveAccessCheck) { + this(host, ip, clientUgi, serviceId, aclType, aclRights, ownerName, + recursiveAccessCheck, null); + } + + @SuppressWarnings("parameternumber") + public RequestContext(String host, + InetAddress ip, + UserGroupInformation clientUgi, + String serviceId, + ACLIdentityType aclType, + ACLType aclRights, + String ownerName, + boolean recursiveAccessCheck, + String sessionPolicy) { this.host = host; this.ip = ip; this.clientUgi = clientUgi; @@ -156,15 +170,25 @@ public static Builder newBuilder() { public static RequestContext.Builder getBuilder( UserGroupInformation ugi, InetAddress remoteAddress, String hostName, - ACLType aclType, String ownerName, String sessionPolicy) { + ACLType aclType, String ownerName) { return getBuilder(ugi, remoteAddress, hostName, aclType, ownerName, - false, sessionPolicy); + false); } public static RequestContext.Builder getBuilder( UserGroupInformation ugi, InetAddress remoteAddress, String hostName, - ACLType aclType, String ownerName, boolean recursiveAccessCheck, String sessionPolicy) { - RequestContext.Builder contextBuilder = RequestContext.newBuilder() + ACLType aclType, String ownerName, boolean recursiveAccessCheck) { + return getBuilder(ugi, remoteAddress, hostName, aclType, ownerName, recursiveAccessCheck, null); + } + + public static RequestContext.Builder getBuilder(UserGroupInformation ugi, + InetAddress remoteAddress, + String hostName, + ACLType aclType, + String ownerName, + boolean recursiveAccessCheck, + String sessionPolicy) { + return RequestContext.newBuilder() .setClientUgi(ugi) .setIp(remoteAddress) .setHost(hostName) @@ -173,15 +197,14 @@ public static RequestContext.Builder getBuilder( .setOwnerName(ownerName) .setRecursiveAccessCheck(recursiveAccessCheck) .setSessionPolicy(sessionPolicy); - return contextBuilder; } public static RequestContext.Builder getBuilder(UserGroupInformation ugi, - ACLType aclType, String ownerName, String sessionPolicy) { + ACLType aclType, String ownerName) { return getBuilder(ugi, ProtobufRpcEngine.Server.getRemoteIp(), ProtobufRpcEngine.Server.getRemoteIp().getHostName(), - aclType, ownerName, sessionPolicy); + aclType, ownerName); } public String getHost() { 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 new file mode 100644 index 000000000000..935885a44cfa --- /dev/null +++ b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/TestAssumeRoleRequest.java @@ -0,0 +1,70 @@ +/* + * 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. + */ + +package org.apache.hadoop.ozone.security.acl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +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 java.util.Collections; +import org.apache.hadoop.security.UserGroupInformation; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link AssumeRoleRequest}. + */ +public class TestAssumeRoleRequest { + + @Test + public void testConstructorAndGetters() { + final UserGroupInformation ugi = UserGroupInformation.createRemoteUser("om"); + + final AssumeRoleRequest assumeRoleRequest1 = new AssumeRoleRequest("host", + null, + ugi, + "roleA", + Collections.emptySet() + ); + final AssumeRoleRequest assumeRoleRequest2 = new AssumeRoleRequest("host", + null, + ugi, + "roleA", + Collections.emptySet() + ); + + assertEquals("host", assumeRoleRequest1.getHost()); + assertNull(assumeRoleRequest1.getIp()); + assertSame(ugi, assumeRoleRequest1.getClientUgi()); + assertEquals("roleA", assumeRoleRequest1.getTargetRoleName()); + assertEquals(Collections.emptySet(), assumeRoleRequest1.getGrants()); + + assertEquals(assumeRoleRequest1, assumeRoleRequest2); + assertEquals(assumeRoleRequest1.hashCode(), assumeRoleRequest2.hashCode()); + + final AssumeRoleRequest assumeRoleRequest3 = new AssumeRoleRequest("host", + null, + ugi, + "roleB", + null + ); + assertNotEquals(assumeRoleRequest1, assumeRoleRequest3); + } +} + + 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 086704d8236d..1651b4620996 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 @@ -17,7 +17,9 @@ package org.apache.hadoop.ozone.security.acl; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; @@ -80,6 +82,66 @@ public void testRecursiveAccessFlag() throws IOException { "Wrongly sets recursive flag value"); } + @Test + public void testSessionPolicy() { + final RequestContext.Builder builder = new RequestContext.Builder(); + RequestContext context = builder.build(); + assertNull(context.getSessionPolicy(), "sessionPolicy should default to null"); + + final String policy = "{\"Statement\":[]}"; + context = new RequestContext.Builder() + .setSessionPolicy(policy) + .build(); + assertEquals(policy, + context.getSessionPolicy(), + "sessionPolicy should be set via builder" + ); + + context = new RequestContext("host", + null, + null, + "serviceId", + IAccessAuthorizer.ACLIdentityType.GROUP, + IAccessAuthorizer.ACLType.CREATE, + "owner", + true, + policy + ); + assertTrue(context.isRecursiveAccessCheck(), "recursiveAccessCheck should be true"); + assertEquals(policy, + context.getSessionPolicy(), + "sessionPolicy should be set via constructor" + ); + + context = RequestContext.getBuilder(UserGroupInformation.createRemoteUser("user1"), + null, + null, + IAccessAuthorizer.ACLType.CREATE, + "volume1", + true + ) + .setSessionPolicy(policy) + .build(); + assertEquals(policy, + context.getSessionPolicy(), + "sessionPolicy should be set via getBuilder + builder" + ); + + context = RequestContext.getBuilder(UserGroupInformation.createRemoteUser("user1"), + null, + null, + IAccessAuthorizer.ACLType.CREATE, + "volume1", + true, + policy + ) + .build(); + assertEquals(policy, + context.getSessionPolicy(), + "sessionPolicy should be set via getBuilder (all params) + builder" + ); + } + private RequestContext getUserRequestContext(String username, IAccessAuthorizer.ACLType type, boolean isOwner, String ownerName, boolean recursiveAccessCheck) throws IOException { @@ -96,4 +158,3 @@ private RequestContext getUserRequestContext(String username, type, ownerName).build(); } } -