Skip to content

Commit fdd20f0

Browse files
authored
HDDS-15064. [STS] Artifacts for Ranger to Consider S3 Action when Authorizing (#10108)
1 parent b508364 commit fdd20f0

5 files changed

Lines changed: 230 additions & 12 deletions

File tree

hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/AssumeRoleRequest.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
package org.apache.hadoop.ozone.security.acl;
1919

2020
import java.net.InetAddress;
21+
import java.util.Collections;
22+
import java.util.LinkedHashSet;
2123
import java.util.Objects;
2224
import java.util.Set;
2325
import net.jcip.annotations.Immutable;
@@ -93,10 +95,24 @@ public int hashCode() {
9395
public static class OzoneGrant {
9496
private final Set<IOzoneObj> objects;
9597
private final Set<IAccessAuthorizer.ACLType> permissions;
98+
/**
99+
* S3 action names without the s3: prefix (e.g. GetObject) from the session policy. When present, the permissions
100+
* will be further restricted by the set of available S3 actions. An empty (or null) set means this OzoneGrant
101+
* does not enforce any restrictions on actions.
102+
*/
103+
private final Set<String> s3Actions;
96104

97105
public OzoneGrant(Set<IOzoneObj> objects, Set<IAccessAuthorizer.ACLType> permissions) {
98106
this.objects = objects;
99107
this.permissions = permissions;
108+
this.s3Actions = Collections.emptySet();
109+
}
110+
111+
public OzoneGrant(Set<IOzoneObj> objects, Set<IAccessAuthorizer.ACLType> permissions,
112+
Set<String> s3Actions) {
113+
this.objects = objects;
114+
this.permissions = permissions;
115+
this.s3Actions = Collections.unmodifiableSet(new LinkedHashSet<>(s3Actions));
100116
}
101117

102118
public Set<IOzoneObj> getObjects() {
@@ -107,6 +123,10 @@ public Set<IAccessAuthorizer.ACLType> getPermissions() {
107123
return permissions;
108124
}
109125

126+
public Set<String> getS3Actions() {
127+
return s3Actions;
128+
}
129+
110130
@Override
111131
public boolean equals(Object o) {
112132
if (this == o) {
@@ -116,12 +136,19 @@ public boolean equals(Object o) {
116136
}
117137

118138
final OzoneGrant that = (OzoneGrant) o;
119-
return Objects.equals(objects, that.objects) && Objects.equals(permissions, that.permissions);
139+
return Objects.equals(objects, that.objects) && Objects.equals(permissions, that.permissions) &&
140+
Objects.equals(s3Actions, that.s3Actions);
120141
}
121142

122143
@Override
123144
public int hashCode() {
124-
return Objects.hash(objects, permissions);
145+
return Objects.hash(objects, permissions, s3Actions);
146+
}
147+
148+
@Override
149+
public String toString() {
150+
return "OzoneGrant{" + "objects=" + objects + ", permissions=" + permissions + ", s3Actions="
151+
+ s3Actions + '}';
125152
}
126153
}
127154
}

hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/RequestContext.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ public final class RequestContext {
5050
*/
5151
private final String sessionPolicy;
5252

53+
/**
54+
* S3 action name for this request without the s3: prefix (e.g. PutObject), when the call originated from S3 Gateway.
55+
* Null for non-S3 clients or when not applicable.
56+
*/
57+
private final String s3Action;
58+
5359
private RequestContext(Builder builder) {
5460
this.host = builder.host;
5561
this.ip = builder.ip;
@@ -60,6 +66,7 @@ private RequestContext(Builder builder) {
6066
this.ownerName = builder.ownerName;
6167
this.recursiveAccessCheck = builder.recursiveAccessCheck;
6268
this.sessionPolicy = builder.sessionPolicy;
69+
this.s3Action = builder.s3Action;
6370
}
6471

6572
/**
@@ -81,6 +88,7 @@ public static final class Builder {
8188

8289
private boolean recursiveAccessCheck;
8390
private String sessionPolicy;
91+
private String s3Action;
8492

8593
private Builder() {
8694

@@ -135,6 +143,11 @@ public Builder setSessionPolicy(String sessionPolicy) {
135143
return this;
136144
}
137145

146+
public Builder setS3Action(String s3Action) {
147+
this.s3Action = s3Action;
148+
return this;
149+
}
150+
138151
public RequestContext build() {
139152
return new RequestContext(this);
140153
}
@@ -185,4 +198,22 @@ public boolean isRecursiveAccessCheck() {
185198
public String getSessionPolicy() {
186199
return sessionPolicy;
187200
}
201+
202+
public String getS3Action() {
203+
return s3Action;
204+
}
205+
206+
public Builder toBuilder() {
207+
return newBuilder()
208+
.setHost(host)
209+
.setIp(ip)
210+
.setClientUgi(clientUgi)
211+
.setServiceId(serviceId)
212+
.setAclType(aclType)
213+
.setAclRights(aclRights)
214+
.setOwnerName(ownerName)
215+
.setRecursiveAccessCheck(recursiveAccessCheck)
216+
.setSessionPolicy(sessionPolicy)
217+
.setS3Action(s3Action);
218+
}
188219
}

hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/security/acl/TestAssumeRoleRequest.java

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
import static org.junit.jupiter.api.Assertions.assertNotEquals;
2222
import static org.junit.jupiter.api.Assertions.assertNull;
2323
import static org.junit.jupiter.api.Assertions.assertSame;
24+
import static org.junit.jupiter.api.Assertions.assertThrows;
2425

2526
import java.util.Collections;
2627
import java.util.HashSet;
28+
import java.util.LinkedHashSet;
2729
import java.util.Set;
2830
import org.apache.hadoop.security.UserGroupInformation;
2931
import org.junit.jupiter.api.Test;
@@ -36,23 +38,32 @@ public class TestAssumeRoleRequest {
3638
@Test
3739
public void testConstructorAndGetters() {
3840
final UserGroupInformation ugi = UserGroupInformation.createRemoteUser("om");
41+
final IOzoneObj bucketObj =
42+
OzoneObjInfo.Builder.newBuilder()
43+
.setResType(OzoneObj.ResourceType.BUCKET)
44+
.setStoreType(OzoneObj.StoreType.OZONE)
45+
.setVolumeName("s3v")
46+
.setBucketName("myBucket")
47+
.build();
48+
final Set<IOzoneObj> objects = Collections.singleton(bucketObj);
49+
final Set<IAccessAuthorizer.ACLType> permissions = Collections.singleton(IAccessAuthorizer.ACLType.READ);
50+
final Set<String> s3Actions = Collections.emptySet();
51+
3952
final Set<AssumeRoleRequest.OzoneGrant> grants = new HashSet<>();
40-
grants.add(
41-
new AssumeRoleRequest.OzoneGrant(
42-
Collections.singleton(
43-
OzoneObjInfo.Builder.newBuilder()
44-
.setResType(OzoneObj.ResourceType.BUCKET)
45-
.setStoreType(OzoneObj.StoreType.OZONE)
46-
.setVolumeName("s3v")
47-
.setBucketName("myBucket")
48-
.build()),
49-
Collections.singleton(IAccessAuthorizer.ACLType.READ)));
53+
grants.add(new AssumeRoleRequest.OzoneGrant(objects, permissions, s3Actions));
5054

5155
final AssumeRoleRequest assumeRoleRequest1 = new AssumeRoleRequest(
5256
"host", null, ugi, "roleA", grants);
5357
final AssumeRoleRequest assumeRoleRequest2 = new AssumeRoleRequest(
5458
"host", null, ugi, "roleA", grants);
5559

60+
final AssumeRoleRequest.OzoneGrant grant = grants.iterator().next();
61+
assertEquals(objects, grant.getObjects());
62+
assertEquals(permissions, grant.getPermissions());
63+
assertEquals(s3Actions, grant.getS3Actions());
64+
// Ensure the s3 actions are not modifiable
65+
assertThrows(UnsupportedOperationException.class, () -> grant.getS3Actions().add("GetObject"));
66+
5667
assertEquals("host", assumeRoleRequest1.getHost());
5768
assertNull(assumeRoleRequest1.getIp());
5869
assertSame(ugi, assumeRoleRequest1.getClientUgi());
@@ -66,6 +77,48 @@ public void testConstructorAndGetters() {
6677
"host", null, ugi, "roleB", null);
6778
assertNotEquals(assumeRoleRequest1, assumeRoleRequest3);
6879
}
80+
81+
@Test
82+
public void testGrantsWithS3Actions() {
83+
final UserGroupInformation ugi = UserGroupInformation.createRemoteUser("om");
84+
85+
final IOzoneObj bucketObj =
86+
OzoneObjInfo.Builder.newBuilder()
87+
.setResType(OzoneObj.ResourceType.BUCKET)
88+
.setStoreType(OzoneObj.StoreType.OZONE)
89+
.setVolumeName("s3v")
90+
.setBucketName("myBucket")
91+
.build();
92+
93+
final Set<IOzoneObj> objects = Collections.singleton(bucketObj);
94+
final Set<IAccessAuthorizer.ACLType> permissions = Collections.singleton(IAccessAuthorizer.ACLType.READ);
95+
96+
final Set<String> s3Actions = new LinkedHashSet<>();
97+
s3Actions.add("GetObject");
98+
s3Actions.add("PutObject");
99+
100+
final AssumeRoleRequest.OzoneGrant grantWithActions = new AssumeRoleRequest.OzoneGrant(
101+
objects, permissions, s3Actions);
102+
final AssumeRoleRequest.OzoneGrant grantWithoutActions = new AssumeRoleRequest.OzoneGrant(
103+
objects, permissions, Collections.emptySet());
104+
105+
assertEquals(objects, grantWithActions.getObjects());
106+
assertEquals(permissions, grantWithActions.getPermissions());
107+
assertEquals(s3Actions, grantWithActions.getS3Actions());
108+
assertNotEquals(grantWithActions, grantWithoutActions);
109+
assertNotEquals(grantWithActions.hashCode(), grantWithoutActions.hashCode());
110+
111+
final Set<AssumeRoleRequest.OzoneGrant> grantsWithActionsSet = Collections.singleton(grantWithActions);
112+
final Set<AssumeRoleRequest.OzoneGrant> grantsWithoutActionsSet = Collections.singleton(grantWithoutActions);
113+
114+
final AssumeRoleRequest requestWithActions = new AssumeRoleRequest(
115+
"host", null, ugi, "roleA", grantsWithActionsSet);
116+
final AssumeRoleRequest requestWithoutActions = new AssumeRoleRequest(
117+
"host", null, ugi, "roleA", grantsWithoutActionsSet);
118+
119+
assertNotEquals(requestWithActions, requestWithoutActions);
120+
assertNotEquals(requestWithActions.hashCode(), requestWithoutActions.hashCode());
121+
}
69122
}
70123

71124

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* Unit tests related to acl-related functionality.
20+
*/
21+
package org.apache.hadoop.ozone.security.acl;

hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestRequestContext.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.jupiter.api.Assertions.assertNull;
2323
import static org.junit.jupiter.api.Assertions.assertTrue;
2424

25+
import org.apache.hadoop.security.UserGroupInformation;
2526
import org.junit.jupiter.api.Test;
2627

2728
/**
@@ -51,4 +52,89 @@ void testSessionPolicy() {
5152
builder.setSessionPolicy(policy);
5253
assertEquals(policy, builder.build().getSessionPolicy());
5354
}
55+
56+
@Test
57+
public void testToBuilderWithNoModifications() {
58+
// Create a RequestContext with all fields set
59+
final UserGroupInformation ugi = UserGroupInformation.createRemoteUser("testUser");
60+
final String host = "testHost";
61+
final String serviceId = "testServiceId";
62+
final String ownerName = "testOwner";
63+
final String sessionPolicy = "{\"Statement\":[{\"Effect\":\"Allow\"}]}";
64+
final String s3Action = "GetObject";
65+
66+
final RequestContext original = RequestContext.newBuilder()
67+
.setHost(host)
68+
.setClientUgi(ugi)
69+
.setServiceId(serviceId)
70+
.setAclType(IAccessAuthorizer.ACLIdentityType.USER)
71+
.setAclRights(IAccessAuthorizer.ACLType.READ)
72+
.setOwnerName(ownerName)
73+
.setRecursiveAccessCheck(true)
74+
.setSessionPolicy(sessionPolicy)
75+
.setS3Action(s3Action)
76+
.build();
77+
78+
// Use toBuilder to create a new builder
79+
final RequestContext.Builder builder = original.toBuilder();
80+
final RequestContext requestCtxFromToBuilder = builder.build();
81+
82+
// Verify all fields are preserved
83+
assertEquals(original.getHost(), requestCtxFromToBuilder.getHost(), "Host should be preserved");
84+
assertNull(original.getIp(), "IP should be preserved");
85+
assertEquals(original.getClientUgi(), requestCtxFromToBuilder.getClientUgi(), "ClientUgi should be preserved");
86+
assertEquals(original.getServiceId(), requestCtxFromToBuilder.getServiceId(), "ServiceId should be preserved");
87+
assertEquals(original.getAclType(), requestCtxFromToBuilder.getAclType(), "AclType should be preserved");
88+
assertEquals(original.getAclRights(), requestCtxFromToBuilder.getAclRights(), "AclRights should be preserved");
89+
assertEquals(original.getOwnerName(), requestCtxFromToBuilder.getOwnerName(), "OwnerName should be preserved");
90+
assertTrue(original.isRecursiveAccessCheck(), "RecursiveAccessCheck should be preserved");
91+
assertEquals(original.getSessionPolicy(), requestCtxFromToBuilder.getSessionPolicy(),
92+
"SessionPolicy should be preserved");
93+
assertEquals(original.getS3Action(), requestCtxFromToBuilder.getS3Action(), "S3 action should be preserved");
94+
}
95+
96+
@Test
97+
public void testToBuilderWithModifications() {
98+
// Create an original RequestContext
99+
final UserGroupInformation originalUgi = UserGroupInformation.createRemoteUser("user1");
100+
final RequestContext original = RequestContext.newBuilder()
101+
.setHost("host1")
102+
.setClientUgi(originalUgi)
103+
.setServiceId("service1")
104+
.setAclType(IAccessAuthorizer.ACLIdentityType.USER)
105+
.setAclRights(IAccessAuthorizer.ACLType.READ)
106+
.setOwnerName("owner1")
107+
.setRecursiveAccessCheck(false)
108+
.build();
109+
110+
// Use toBuilder and modify some fields
111+
final UserGroupInformation newUgi = UserGroupInformation.createRemoteUser("user2");
112+
final RequestContext modified = original.toBuilder()
113+
.setHost("host2")
114+
.setClientUgi(newUgi)
115+
.setAclRights(IAccessAuthorizer.ACLType.WRITE)
116+
.setOwnerName("owner2")
117+
.setRecursiveAccessCheck(true)
118+
.setSessionPolicy("{\"Statement\":[]}")
119+
.setS3Action("DeleteObject")
120+
.build();
121+
122+
// Verify original is unchanged
123+
assertEquals("host1", original.getHost(), "Original should be unchanged");
124+
assertEquals(originalUgi, original.getClientUgi(), "Original UGI should be unchanged");
125+
assertEquals(IAccessAuthorizer.ACLType.READ, original.getAclRights(), "Original ACL rights should be unchanged");
126+
assertEquals("owner1", original.getOwnerName(), "Original owner name should be unchanged");
127+
assertFalse(original.isRecursiveAccessCheck(), "Original recursive flag should be unchanged");
128+
assertNull(original.getSessionPolicy(), "Original session policy should be unchanged");
129+
assertNull(original.getS3Action(), "Original S3 action should be unchanged");
130+
131+
// Verify modified has new values
132+
assertEquals("host2", modified.getHost(), "Modified host should be updated");
133+
assertEquals(newUgi, modified.getClientUgi(), "Modified UGI should be updated");
134+
assertEquals(IAccessAuthorizer.ACLType.WRITE, modified.getAclRights(), "Modified ACL rights should be updated");
135+
assertEquals("owner2", modified.getOwnerName(), "Modified owner should be updated");
136+
assertTrue(modified.isRecursiveAccessCheck(), "Modified recursive flag should be updated");
137+
assertEquals("{\"Statement\":[]}", modified.getSessionPolicy(), "Modified session policy should be updated");
138+
assertEquals("DeleteObject", modified.getS3Action(), "Modified S3 action should be updated");
139+
}
54140
}

0 commit comments

Comments
 (0)