This is an automated email from the ASF dual-hosted git repository.
sammichen pushed a commit to branch HDDS-13323-sts
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-13323-sts by this push:
new 281a5b3630d HDDS-13848. [STS] Artifacts for Ranger to authorize STS
token (#9214)
281a5b3630d is described below
commit 281a5b3630ddc26343d67c48fada83851b07bdb2
Author: fmorg-git <[email protected]>
AuthorDate: Thu Nov 13 21:05:54 2025 -0800
HDDS-13848. [STS] Artifacts for Ranger to authorize STS token (#9214)
---
.../java/org/apache/hadoop/ozone/OzoneConsts.java | 1 +
.../ozone/security/acl/AssumeRoleRequest.java | 127 +++++++++++++++++++++
.../ozone/security/acl/IAccessAuthorizer.java | 29 ++++-
.../apache/hadoop/ozone/security/acl/OzoneObj.java | 17 +++
.../hadoop/ozone/security/acl/OzoneObjInfo.java | 19 +++
.../hadoop/ozone/security/acl/RequestContext.java | 44 ++++++-
.../ozone/security/acl/TestAssumeRoleRequest.java | 71 ++++++++++++
.../ozone/security/acl/TestRequestContext.java | 37 +++++-
8 files changed, 337 insertions(+), 8 deletions(-)
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
index aecbdfae615..99d78f786fa 100644
--- a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
+++ b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
@@ -65,6 +65,7 @@ public final class OzoneConsts {
public static final String OZONE_ACL_CREATE = "c";
public static final String OZONE_ACL_READ_ACL = "x";
public static final String OZONE_ACL_WRITE_ACL = "y";
+ public static final String OZONE_ACL_ASSUME_ROLE = "m";
public static final String OZONE_DATE_FORMAT =
"EEE, dd MMM yyyy HH:mm:ss zzz";
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 00000000000..1272d5422ec
--- /dev/null
+++
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.Objects;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.apache.hadoop.security.UserGroupInformation;
+
+/**
+ * Represents an S3 AssumeRole request that needs to be authorized by an
IAccessAuthorizer.
+ * The grants parameter can be null if the access must not be limited beyond
the role.
+ * Note that if the grants parameter is the empty set, this means the access
should
+ * be the intersection of the role and the empty set, meaning no access will
be granted.
+ */
+@Immutable
+public class AssumeRoleRequest {
+ private final String host;
+ private final InetAddress ip;
+ private final UserGroupInformation clientUgi;
+ private final String targetRoleName;
+ private final Set<OzoneGrant> grants;
+
+ public AssumeRoleRequest(String host, InetAddress ip, UserGroupInformation
clientUgi, String targetRoleName,
+ Set<OzoneGrant> 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<OzoneGrant> getGrants() {
+ return grants;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else 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);
+ }
+
+ /**
+ * Encapsulates the IOzoneObj and associated permissions.
+ */
+ @Immutable
+ public static class OzoneGrant {
+ private final Set<IOzoneObj> objects;
+ private final Set<IAccessAuthorizer.ACLType> permissions;
+
+ public OzoneGrant(Set<IOzoneObj> objects, Set<IAccessAuthorizer.ACLType>
permissions) {
+ this.objects = objects;
+ this.permissions = permissions;
+ }
+
+ public Set<IOzoneObj> getObjects() {
+ return objects;
+ }
+
+ public Set<IAccessAuthorizer.ACLType> getPermissions() {
+ return permissions;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final OzoneGrant that = (OzoneGrant) o;
+ return Objects.equals(objects, that.objects) &&
Objects.equals(permissions, that.permissions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(objects, permissions);
+ }
+ }
+}
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 f1218a9aa08..8a07bab606b 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
@@ -17,6 +17,8 @@
package org.apache.hadoop.ozone.security.acl;
+import static
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.NOT_SUPPORTED_OPERATION;
+
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
@@ -48,6 +50,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.
+ * <p>
+ * The user making this call must have the {@link ACLType#ASSUME_ROLE}
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#ASSUME_ROLE} permission
+ */
+ default String generateAssumeRoleSessionPolicy(AssumeRoleRequest
assumeRoleRequest) throws OMException {
+ throw new OMException("The generateAssumeRoleSessionPolicy call is not
supported", NOT_SUPPORTED_OPERATION);
+ }
+
/**
* @return true for Ozone-native authorizer
*/
@@ -67,7 +88,9 @@ enum ACLType {
READ_ACL,
WRITE_ACL,
ALL,
- NONE;
+ NONE,
+ ASSUME_ROLE; // ability to create STS tokens
+
private static int length = ACLType.values().length;
static {
if (length > 16) {
@@ -121,6 +144,8 @@ public static ACLType getACLRight(String type) {
return ACLType.ALL;
case OzoneConsts.OZONE_ACL_NONE:
return ACLType.NONE;
+ case OzoneConsts.OZONE_ACL_ASSUME_ROLE:
+ return ACLType.ASSUME_ROLE;
default:
throw new IllegalArgumentException("[" + type + "] ACL right is not " +
"recognized");
@@ -161,6 +186,8 @@ public static String getAclString(ACLType acl) {
return OzoneConsts.OZONE_ACL_ALL;
case NONE:
return OzoneConsts.OZONE_ACL_NONE;
+ case ASSUME_ROLE:
+ return OzoneConsts.OZONE_ACL_ASSUME_ROLE;
default:
throw new IllegalArgumentException("ACL right is not recognized");
}
diff --git
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneObj.java
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneObj.java
index ae3a8ad1592..cb5dcfce8f5 100644
---
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneObj.java
+++
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneObj.java
@@ -22,6 +22,7 @@
import com.google.common.base.Preconditions;
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Objects;
import org.apache.hadoop.ozone.OzoneConsts;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OzoneObj.ObjectType;
@@ -146,4 +147,20 @@ public Map<String, String> toAuditMap() {
return auditMap;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final OzoneObj that = (OzoneObj) o;
+ return resType == that.resType && storeType == that.storeType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(resType, storeType);
+ }
}
diff --git
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneObjInfo.java
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneObjInfo.java
index 36b450bec7f..aa0c05af99c 100644
---
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneObjInfo.java
+++
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/OzoneObjInfo.java
@@ -19,6 +19,7 @@
import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER;
+import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
@@ -237,4 +238,22 @@ public OzoneObjInfo build() {
name, ozonePrefixPath);
}
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (!super.equals(o)) {
+ return false;
+ }
+
+ final OzoneObjInfo that = (OzoneObjInfo) o;
+ return Objects.equals(volumeName, that.volumeName) &&
Objects.equals(bucketName, that.bucketName) &&
+ Objects.equals(name, that.name) && Objects.equals(ozonePrefixPath,
that.ozonePrefixPath);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), volumeName, bucketName, name,
ozonePrefixPath);
+ }
}
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 08724eae5ff..f2d25c2ad23 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,13 +43,21 @@ 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.
+ * The sessionPolicy includes the roleName.
+ */
+ private final String sessionPolicy;
+
@SuppressWarnings("parameternumber")
public RequestContext(String host, InetAddress ip,
UserGroupInformation clientUgi, String serviceId,
ACLIdentityType aclType, ACLType aclRights,
String ownerName) {
this(host, ip, clientUgi, serviceId, aclType, aclRights, ownerName,
- false);
+ false, null);
}
@SuppressWarnings("parameternumber")
@@ -57,6 +65,14 @@ public RequestContext(String host, InetAddress ip,
UserGroupInformation clientUgi, String serviceId,
ACLIdentityType aclType, ACLType aclRights,
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;
@@ -65,6 +81,7 @@ public RequestContext(String host, InetAddress ip,
this.aclRights = aclRights;
this.ownerName = ownerName;
this.recursiveAccessCheck = recursiveAccessCheck;
+ this.sessionPolicy = sessionPolicy;
}
/**
@@ -85,6 +102,7 @@ public static class Builder {
private String ownerName;
private boolean recursiveAccessCheck;
+ private String sessionPolicy;
public Builder setHost(String bHost) {
this.host = bHost;
@@ -130,9 +148,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);
}
}
@@ -144,21 +167,26 @@ public static RequestContext.Builder getBuilder(
UserGroupInformation ugi, InetAddress remoteAddress, String hostName,
ACLType aclType, String ownerName) {
return getBuilder(ugi, remoteAddress, hostName, aclType, ownerName,
- false);
+ false);
}
public static RequestContext.Builder getBuilder(
UserGroupInformation ugi, InetAddress remoteAddress, String hostName,
ACLType aclType, String ownerName, boolean recursiveAccessCheck) {
- RequestContext.Builder contextBuilder = RequestContext.newBuilder()
+ 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)
.setAclType(ACLIdentityType.USER)
.setAclRights(aclType)
.setOwnerName(ownerName)
- .setRecursiveAccessCheck(recursiveAccessCheck);
- return contextBuilder;
+ .setRecursiveAccessCheck(recursiveAccessCheck)
+ .setSessionPolicy(sessionPolicy);
}
public static RequestContext.Builder getBuilder(UserGroupInformation ugi,
@@ -206,4 +234,8 @@ public String getOwnerName() {
public boolean isRecursiveAccessCheck() {
return recursiveAccessCheck;
}
+
+ public String getSessionPolicy() {
+ return sessionPolicy;
+ }
}
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 00000000000..e9d9c519bd1
--- /dev/null
+++
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/TestAssumeRoleRequest.java
@@ -0,0 +1,71 @@
+/*
+ * 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 java.util.HashSet;
+import java.util.Set;
+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 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)));
+
+ final AssumeRoleRequest assumeRoleRequest1 = new AssumeRoleRequest(
+ "host", null, ugi, "roleA", grants);
+ final AssumeRoleRequest assumeRoleRequest2 = new AssumeRoleRequest(
+ "host", null, ugi, "roleA", grants);
+
+ assertEquals("host", assumeRoleRequest1.getHost());
+ assertNull(assumeRoleRequest1.getIp());
+ assertSame(ugi, assumeRoleRequest1.getClientUgi());
+ assertEquals("roleA", assumeRoleRequest1.getTargetRoleName());
+ assertEquals(grants, 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 086704d8236..cb05c2ef626 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,40 @@ 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 +132,3 @@ private RequestContext getUserRequestContext(String username,
type, ownerName).build();
}
}
-
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]