diff --git a/agents-common/src/main/resources/service-defs/ranger-servicedef-ozone.json b/agents-common/src/main/resources/service-defs/ranger-servicedef-ozone.json index 2bff90d47b..025e5fa08e 100755 --- a/agents-common/src/main/resources/service-defs/ranger-servicedef-ozone.json +++ b/agents-common/src/main/resources/service-defs/ranger-servicedef-ozone.json @@ -25,6 +25,7 @@ "uiHint":"", "label": "Ozone Volume", "description": "Ozone Volume", + "accessTypeRestrictions": [ "read", "write", "create", "list", "delete", "read_acl", "write_acl", "all" ], "isValidLeaf": true }, { @@ -44,9 +45,9 @@ "uiHint":"", "label": "Ozone Bucket", "description": "Ozone Bucket", + "accessTypeRestrictions": [ "read", "write", "create", "list", "delete", "read_acl", "write_acl", "all" ], "isValidLeaf": true }, - { "itemId": 3, "name": "key", @@ -64,7 +65,25 @@ "uiHint":"", "label": "Ozone Key", "description": "Ozone Key", + "accessTypeRestrictions": [ "read", "write", "create", "list", "delete", "read_acl", "write_acl", "all" ], "isValidLeaf": true + }, + { + "itemId": 4, + "name": "role", + "type": "string", + "level": 10, + "parent": "", + "mandatory": true, + "lookupSupported": true, + "recursiveSupported": false, + "excludesSupported": false, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { "wildCard":true, "ignoreCase":false }, + "label": "Role", + "description": "Role", + "accessTypeRestrictions": [ "assume_role" ], + "isValidLeaf": true } ], @@ -130,6 +149,12 @@ "name": "write_acl", "label": "Write_ACL", "category": "UPDATE" + }, + { + "itemId": 8, + "name": "assume_role", + "label": "Assume_Role", + "category": "MANAGE" } ], diff --git a/plugin-ozone/pom.xml b/plugin-ozone/pom.xml index 4009242978..d119745218 100644 --- a/plugin-ozone/pom.xml +++ b/plugin-ozone/pom.xml @@ -145,9 +145,21 @@ limitations under the License. ${org.bouncycastle.bcpkix-jdk18on} - org.slf4j - log4j-over-slf4j - ${slf4j.version} + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + test + + + org.mockito + mockito-inline + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} test diff --git a/plugin-ozone/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java b/plugin-ozone/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java index c08e9c0f91..4414f22ada 100644 --- a/plugin-ozone/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java +++ b/plugin-ozone/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java @@ -20,6 +20,10 @@ package org.apache.ranger.authorization.ozone.authorizer; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.security.acl.AssumeRoleRequest; +import org.apache.hadoop.ozone.security.acl.AssumeRoleRequest.OzoneGrant; import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; import org.apache.hadoop.ozone.security.acl.IOzoneObj; import org.apache.hadoop.ozone.security.acl.OzoneObj; @@ -27,31 +31,48 @@ import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.thirdparty.com.google.common.collect.Sets; import org.apache.ranger.audit.provider.MiscUtil; +import org.apache.ranger.authz.util.RangerResourceNameParser; import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler; +import org.apache.ranger.plugin.model.RangerInlinePolicy; +import org.apache.ranger.plugin.model.RangerPrincipal; import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResult; import org.apache.ranger.plugin.service.RangerBasePlugin; +import org.apache.ranger.plugin.util.JsonUtilsV2; import org.apache.ranger.plugin.util.RangerPerfTracer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; public class RangerOzoneAuthorizer implements IAccessAuthorizer { private static final Logger LOG = LoggerFactory.getLogger(RangerOzoneAuthorizer.class); private static final Logger PERF_OZONEAUTH_REQUEST_LOG = RangerPerfTracer.getPerfLogger("ozoneauth.request"); - public static final String ACCESS_TYPE_READ = "read"; - public static final String ACCESS_TYPE_WRITE = "write"; - public static final String ACCESS_TYPE_CREATE = "create"; - public static final String ACCESS_TYPE_LIST = "list"; - public static final String ACCESS_TYPE_DELETE = "delete"; - public static final String ACCESS_TYPE_READ_ACL = "read_acl"; - public static final String ACCESS_TYPE_WRITE_ACL = "write_acl"; - public static final String KEY_RESOURCE_VOLUME = "volume"; - public static final String KEY_RESOURCE_BUCKET = "bucket"; - public static final String KEY_RESOURCE_KEY = "key"; + public static final String ACCESS_TYPE_READ = "read"; + public static final String ACCESS_TYPE_WRITE = "write"; + public static final String ACCESS_TYPE_CREATE = "create"; + public static final String ACCESS_TYPE_LIST = "list"; + public static final String ACCESS_TYPE_DELETE = "delete"; + public static final String ACCESS_TYPE_READ_ACL = "read_acl"; + public static final String ACCESS_TYPE_WRITE_ACL = "write_acl"; + public static final String ACCESS_TYPE_ASSUME_ROLE = "assume_role"; + public static final String ACCESS_TYPE_ALL = "all"; + + public static final String KEY_RESOURCE_VOLUME = "volume"; + public static final String KEY_RESOURCE_BUCKET = "bucket"; + public static final String KEY_RESOURCE_KEY = "key"; + public static final String KEY_RESOURCE_ROLE = "role"; + + private static final String S3_VOLUME_NAME = "s3Vol"; private static volatile RangerBasePlugin rangerPlugin; @@ -77,6 +98,11 @@ public RangerOzoneAuthorizer() { } } + // for testing only + RangerOzoneAuthorizer(RangerBasePlugin plugin) { + rangerPlugin = plugin; + } + @Override public boolean checkAccess(IOzoneObj ozoneObject, RequestContext context) { boolean returnValue = false; @@ -149,12 +175,7 @@ public boolean checkAccess(IOzoneObj ozoneObject, RequestContext context) { if (ozoneObj.getResourceType() == OzoneObj.ResourceType.VOLUME) { rangerResource.setValue(KEY_RESOURCE_VOLUME, ozoneObj.getVolumeName()); } else if (ozoneObj.getResourceType() == OzoneObj.ResourceType.BUCKET || ozoneObj.getResourceType() == OzoneObj.ResourceType.KEY) { - if (ozoneObj.getStoreType() == OzoneObj.StoreType.S3) { - rangerResource.setValue(KEY_RESOURCE_VOLUME, "s3Vol"); - } else { - rangerResource.setValue(KEY_RESOURCE_VOLUME, ozoneObj.getVolumeName()); - } - + rangerResource.setValue(KEY_RESOURCE_VOLUME, ozoneObj.getStoreType() == OzoneObj.StoreType.S3 ? S3_VOLUME_NAME : ozoneObj.getVolumeName()); rangerResource.setValue(KEY_RESOURCE_BUCKET, ozoneObj.getBucketName()); if (ozoneObj.getResourceType() == OzoneObj.ResourceType.KEY) { @@ -170,6 +191,10 @@ public boolean checkAccess(IOzoneObj ozoneObject, RequestContext context) { } try { + if (StringUtils.isNotBlank(context.getSessionPolicy())) { + rangerRequest.setInlinePolicy(JsonUtilsV2.jsonToObj(context.getSessionPolicy(), RangerInlinePolicy.class)); + } + RangerAccessResult result = plugin.isAccessAllowed(rangerRequest); if (result == null) { @@ -188,6 +213,61 @@ public boolean checkAccess(IOzoneObj ozoneObject, RequestContext context) { return returnValue; } + @Override + public String generateAssumeRoleSessionPolicy(AssumeRoleRequest assumeRoleRequest) throws OMException { + LOG.debug("==> RangerOzoneAuthorizer.generateAssumeRoleSessionPolicy(assumeRoleRequest={})", assumeRoleRequest); + + if (assumeRoleRequest == null) { + throw new OMException("invalid request: null", OMException.ResultCodes.INVALID_REQUEST); + } else if (assumeRoleRequest.getClientUgi() == null) { + throw new OMException("invalid request: request.clientUgi null", OMException.ResultCodes.INVALID_REQUEST); + } else if (assumeRoleRequest.getTargetRoleName() == null) { + throw new OMException("invalid request: request.targetRoleName null", OMException.ResultCodes.INVALID_REQUEST); + } + + RangerBasePlugin plugin = rangerPlugin; + + if (plugin == null) { + throw new OMException("Ranger authorizer not initialized", OMException.ResultCodes.INTERNAL_ERROR); + } + + UserGroupInformation ugi = assumeRoleRequest.getClientUgi(); + RangerAccessResourceImpl resource = new RangerAccessResourceImpl(Collections.singletonMap(KEY_RESOURCE_ROLE, assumeRoleRequest.getTargetRoleName())); + RangerAccessRequestImpl request = new RangerAccessRequestImpl(resource, ACCESS_TYPE_ASSUME_ROLE, ugi.getShortUserName(), Sets.newHashSet(ugi.getGroupNames()), null); + + try { + RangerAccessResult result = plugin.isAccessAllowed(request); + + if (result != null && result.getIsAccessDetermined() && result.getIsAllowed()) { + final Set ozoneGrants = assumeRoleRequest.getGrants(); + final List inlineGrants; + + if (ozoneGrants == null) { // allow all permissions + inlineGrants = null; + } else if (ozoneGrants.isEmpty()) { // don't allow any permission + inlineGrants = Collections.singletonList(new RangerInlinePolicy.Grant()); + } else { // allow explicitly specified permissions + inlineGrants = ozoneGrants.stream().map(g -> toRangerGrant(g, plugin)).collect(Collectors.toList()); + } + + RangerInlinePolicy inlinePolicy = new RangerInlinePolicy(RangerPrincipal.PREFIX_ROLE + assumeRoleRequest.getTargetRoleName(), RangerInlinePolicy.Mode.INLINE, inlineGrants, ugi.getShortUserName()); + String ret = JsonUtilsV2.objToJson(inlinePolicy); + + LOG.debug("<== RangerOzoneAuthorizer.generateAssumeRoleSessionPolicy(assumeRoleRequest={}): ret={}", assumeRoleRequest, ret); + + return ret; + } else { + throw new OMException("Permission denied", OMException.ResultCodes.ACCESS_DENIED); + } + } catch (OMException excp) { + throw excp; + } catch (Throwable t) { + LOG.error("isAccessAllowed() failed. request = {}", request, t); + + throw new OMException("Ranger authorizer failed", t, OMException.ResultCodes.INTERNAL_ERROR); + } + } + private String mapToRangerAccessType(ACLType operation) { final String rangerAccessType; @@ -221,4 +301,82 @@ private String mapToRangerAccessType(ACLType operation) { return rangerAccessType; } + + private static RangerInlinePolicy.Grant toRangerGrant(OzoneGrant ozoneGrant, RangerBasePlugin plugin) { + RangerInlinePolicy.Grant ret = new RangerInlinePolicy.Grant(); + + if (ozoneGrant.getObjects() != null) { + ret.setResources(ozoneGrant.getObjects().stream().map(o -> toRrn(o, plugin)).filter(Objects::nonNull).collect(Collectors.toSet())); + } + + if (ozoneGrant.getPermissions() != null) { + ret.setPermissions(ozoneGrant.getPermissions().stream().map(RangerOzoneAuthorizer::toRangerPermission).filter(Objects::nonNull).collect(Collectors.toSet())); + } + + LOG.debug("toRangerGrant(ozoneGrant={}): ret={}", ozoneGrant, ret); + + return ret; + } + + private static String toRrn(IOzoneObj obj, RangerBasePlugin plugin) { + OzoneObj ozoneObj = (OzoneObj) obj; + Map resource = new HashMap<>(); + String resType = null; + + switch (ozoneObj.getResourceType()) { + case VOLUME: + resType = KEY_RESOURCE_VOLUME; + + resource.put(KEY_RESOURCE_VOLUME, ozoneObj.getVolumeName()); + break; + + case BUCKET: + resType = KEY_RESOURCE_BUCKET; + + resource.put(KEY_RESOURCE_VOLUME, ozoneObj.getStoreType() == OzoneObj.StoreType.S3 ? S3_VOLUME_NAME : ozoneObj.getVolumeName()); + resource.put(KEY_RESOURCE_BUCKET, ozoneObj.getBucketName()); + break; + + case KEY: + resType = KEY_RESOURCE_KEY; + + resource.put(KEY_RESOURCE_VOLUME, ozoneObj.getStoreType() == OzoneObj.StoreType.S3 ? S3_VOLUME_NAME : ozoneObj.getVolumeName()); + resource.put(KEY_RESOURCE_BUCKET, ozoneObj.getBucketName()); + resource.put(KEY_RESOURCE_KEY, ozoneObj.getKeyName()); + break; + } + + RangerResourceNameParser rrnParser = resType != null ? plugin.getServiceDefHelper().getRrnParser(resType) : null; + String ret = rrnParser != null ? (resType + RangerResourceNameParser.RRN_RESOURCE_TYPE_SEP + rrnParser.toResourceName(resource)) : null; + + LOG.debug("toRrn(ozoneObj={}): ret={}", ozoneObj, ret); + + return ret; + } + + private static String toRangerPermission(ACLType acl) { + switch (acl) { + case READ: + return ACCESS_TYPE_READ; + case WRITE: + return ACCESS_TYPE_WRITE; + case CREATE: + return ACCESS_TYPE_CREATE; + case LIST: + return ACCESS_TYPE_LIST; + case DELETE: + return ACCESS_TYPE_DELETE; + case READ_ACL: + return ACCESS_TYPE_READ_ACL; + case WRITE_ACL: + return ACCESS_TYPE_WRITE_ACL; + case ALL: + return ACCESS_TYPE_ALL; + case NONE: + case ASSUME_ROLE: // ASSUME_ROLE is not supported in session policy + return null; + } + + return null; + } } diff --git a/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java b/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java new file mode 100644 index 0000000000..df3aa65ac5 --- /dev/null +++ b/plugin-ozone/src/test/java/org/apache/ranger/authorization/ozone/authorizer/TestRangerOzoneAuthorizer.java @@ -0,0 +1,196 @@ +/* + * 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.ranger.authorization.ozone.authorizer; + +import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.security.acl.AssumeRoleRequest; +import org.apache.hadoop.ozone.security.acl.AssumeRoleRequest.OzoneGrant; +import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; +import org.apache.hadoop.ozone.security.acl.OzoneObj; +import org.apache.hadoop.ozone.security.acl.OzoneObjInfo; +import org.apache.hadoop.ozone.security.acl.RequestContext; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig; +import org.apache.ranger.plugin.model.RangerInlinePolicy; +import org.apache.ranger.plugin.service.RangerBasePlugin; +import org.apache.ranger.plugin.util.JsonUtilsV2; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestRangerOzoneAuthorizer { + private static final String RANGER_SERVICE_TYPE = "ozone"; + private static final String RANGER_APP_ID = "om"; + private static final String OZONE_SERVICE_ID = "om"; + private static final String OWNER_NAME = "ozone"; + + private static RangerOzoneAuthorizer ozoneAuthorizer; + + private final String hostname = "localhost"; + private final InetAddress ipAddress = InetAddress.getLoopbackAddress(); + private final UserGroupInformation user1 = UserGroupInformation.createRemoteUser("user1"); + private final UserGroupInformation user2 = UserGroupInformation.createRemoteUser("user2"); + private final String role1 = "role1"; + + private final OzoneObj vol1 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.VOLUME).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol1").build(); + private final OzoneObj buck1 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.BUCKET).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol1").setBucketName("buck1").build(); + private final OzoneObj key1 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.KEY).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol1").setBucketName("buck1").setKeyName("key1").build(); + private final OzoneObj vol2 = new OzoneObjInfo.Builder().setResType(OzoneObj.ResourceType.VOLUME).setStoreType(OzoneObj.StoreType.OZONE).setVolumeName("vol2").build(); + private final OzoneGrant grantList = new OzoneGrant(new HashSet<>(Arrays.asList(vol1, buck1)), Collections.singleton(IAccessAuthorizer.ACLType.LIST)); + private final OzoneGrant grantRead = new OzoneGrant(Collections.singleton(key1), Collections.singleton(IAccessAuthorizer.ACLType.READ)); + + @BeforeAll + public static void setUpBeforeClass() { + RangerPluginConfig pluginConfig = new RangerPluginConfig(RANGER_SERVICE_TYPE, null, RANGER_APP_ID, null, null, null); // loads ranger-ozone-security.xml + RangerBasePlugin plugin = new RangerBasePlugin(pluginConfig); + + // loads policies from om_dev_ozone.json, by EmbeddedResourcePolicySource configured in ranger-ozone-security.xml + plugin.init(); + + ozoneAuthorizer = new RangerOzoneAuthorizer(plugin); + + assertNotNull(ozoneAuthorizer); + } + + @Test + public void testAssumeRoleDeny() { + // user2 should not be allowed to assume role1 - no Ranger policy grants this permission + AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user2, role1, null); + + assertThrows(OMException.class, () -> ozoneAuthorizer.generateAssumeRoleSessionPolicy(request)); + } + + @Test + public void testAssumeRoleWithEmptyGrants() throws Exception { + Set grants = Collections.emptySet(); + AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user1, role1, grants); + + // user1 should be allowed to assume role1 - Ranger policy #100 grants this permission + String sessionPolicy = ozoneAuthorizer.generateAssumeRoleSessionPolicy(request); + + assertNotNull(sessionPolicy); + assertNotEquals("", sessionPolicy); + + RangerInlinePolicy inlinePolicy = JsonUtilsV2.jsonToObj(sessionPolicy, RangerInlinePolicy.class); + + assertEquals("r:role1", inlinePolicy.getGrantor()); + assertEquals("user1", inlinePolicy.getCreatedBy()); + assertEquals(RangerInlinePolicy.Mode.INLINE, inlinePolicy.getMode()); + assertNotNull(inlinePolicy.getGrants()); + + RequestContext ctxListWithoutSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.LIST, OWNER_NAME); + RequestContext ctxReadWithoutSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.READ, OWNER_NAME); + RequestContext ctxListWithSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.LIST, OWNER_NAME, false, sessionPolicy); + RequestContext ctxReadWithSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.READ, OWNER_NAME, false, sessionPolicy); + + // user1 doesn't have access without session-policy + assertFalse(ozoneAuthorizer.checkAccess(vol1, ctxListWithoutSessionPolicy), "session-policy should not allow list on volume vol1"); + assertFalse(ozoneAuthorizer.checkAccess(vol2, ctxListWithoutSessionPolicy), "session-policy should not allow list on volume vol2"); + assertFalse(ozoneAuthorizer.checkAccess(buck1, ctxListWithoutSessionPolicy), "session-policy should not allow list on bucket vol1/buck1"); + assertFalse(ozoneAuthorizer.checkAccess(key1, ctxReadWithoutSessionPolicy), "session-policy should not allow read on key vol1/buck1/key1"); + + // user1 should not have access with session-policy as well, due to empty grants + assertFalse(ozoneAuthorizer.checkAccess(vol1, ctxListWithSessionPolicy), "session-policy should not allow list on volume vol1"); + assertFalse(ozoneAuthorizer.checkAccess(vol2, ctxListWithSessionPolicy), "session-policy should not allow list on volume vol2"); + assertFalse(ozoneAuthorizer.checkAccess(buck1, ctxListWithSessionPolicy), "session-policy should not allow list on bucket vol1/buck1"); + assertFalse(ozoneAuthorizer.checkAccess(key1, ctxReadWithSessionPolicy), "session-policy should not allow read on key vol1/buck1/key1"); + } + + @Test + public void testAssumeRoleWithNullGrants() throws Exception { + Set grants = null; + AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user1, role1, grants); + + // user1 should be allowed to assume role1 - Ranger policy #100 grants this permission + String sessionPolicy = ozoneAuthorizer.generateAssumeRoleSessionPolicy(request); + + assertNotNull(sessionPolicy); + assertNotEquals("", sessionPolicy); + + RangerInlinePolicy inlinePolicy = JsonUtilsV2.jsonToObj(sessionPolicy, RangerInlinePolicy.class); + + assertEquals("r:role1", inlinePolicy.getGrantor()); + assertEquals("user1", inlinePolicy.getCreatedBy()); + assertEquals(RangerInlinePolicy.Mode.INLINE, inlinePolicy.getMode()); + assertNull(inlinePolicy.getGrants()); + + RequestContext ctxListWithoutSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.LIST, OWNER_NAME); + RequestContext ctxReadWithoutSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.READ, OWNER_NAME); + RequestContext ctxListWithSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.LIST, OWNER_NAME, false, sessionPolicy); + RequestContext ctxReadWithSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.READ, OWNER_NAME, false, sessionPolicy); + + // user1 doesn't have access without session-policy + assertFalse(ozoneAuthorizer.checkAccess(vol1, ctxListWithoutSessionPolicy), "session-policy should not allow list on volume vol1"); + assertFalse(ozoneAuthorizer.checkAccess(vol2, ctxListWithoutSessionPolicy), "session-policy should not allow list on volume vol2"); + assertFalse(ozoneAuthorizer.checkAccess(buck1, ctxListWithoutSessionPolicy), "session-policy should not allow list on bucket vol1/buck1"); + assertFalse(ozoneAuthorizer.checkAccess(key1, ctxReadWithoutSessionPolicy), "session-policy should not allow read on key vol1/buck1/key1"); + + // user1 should have access with session-policy, due to null grants which allows all accesses granted to role1 + assertTrue(ozoneAuthorizer.checkAccess(vol1, ctxListWithSessionPolicy), "session-policy should allow list on volume vol1"); + assertTrue(ozoneAuthorizer.checkAccess(vol2, ctxListWithSessionPolicy), "session-policy should allow list on volume vol2"); + assertTrue(ozoneAuthorizer.checkAccess(buck1, ctxListWithSessionPolicy), "session-policy should allow list on bucket vol1/buck1"); + assertTrue(ozoneAuthorizer.checkAccess(key1, ctxReadWithSessionPolicy), "session-policy should allow read on key vol1/buck1/key1"); + } + + @Test + public void testAssumeRoleWithGrants() throws Exception { + Set grants = new HashSet<>(Arrays.asList(grantList, grantRead)); + AssumeRoleRequest request = new AssumeRoleRequest(hostname, ipAddress, user1, role1, grants); + + // user1 should be allowed to assume role1 - Ranger policy #100 grants this permission + String sessionPolicy = ozoneAuthorizer.generateAssumeRoleSessionPolicy(request); + + assertNotNull(sessionPolicy); + assertNotEquals("", sessionPolicy); + + RangerInlinePolicy inlinePolicy = JsonUtilsV2.jsonToObj(sessionPolicy, RangerInlinePolicy.class); + + assertEquals("r:role1", inlinePolicy.getGrantor()); + assertEquals("user1", inlinePolicy.getCreatedBy()); + assertEquals(RangerInlinePolicy.Mode.INLINE, inlinePolicy.getMode()); + assertNotNull(inlinePolicy.getGrants()); + assertEquals(2, inlinePolicy.getGrants().size()); + + assertTrue(inlinePolicy.getGrants().contains(new RangerInlinePolicy.Grant(null, new HashSet<>(Arrays.asList("volume:vol1", "bucket:vol1/buck1")), Collections.singleton("list")))); + assertTrue(inlinePolicy.getGrants().contains(new RangerInlinePolicy.Grant(null, Collections.singleton("key:vol1/buck1/key1"), Collections.singleton("read")))); + + RequestContext ctxListWithSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.LIST, OWNER_NAME, false, sessionPolicy); + RequestContext ctxReadWithSessionPolicy = new RequestContext(hostname, ipAddress, user1, OZONE_SERVICE_ID, IAccessAuthorizer.ACLIdentityType.ANONYMOUS, IAccessAuthorizer.ACLType.READ, OWNER_NAME, false, sessionPolicy); + + // user1 should have access with sessionPolicy + assertTrue(ozoneAuthorizer.checkAccess(vol1, ctxListWithSessionPolicy), "session-policy should allow list on volume vol1"); + assertFalse(ozoneAuthorizer.checkAccess(vol2, ctxListWithSessionPolicy), "session-policy should not allow list on volume vol2"); + assertTrue(ozoneAuthorizer.checkAccess(buck1, ctxListWithSessionPolicy), "session-policy should allow list on bucket vol1/buck1"); + assertTrue(ozoneAuthorizer.checkAccess(key1, ctxReadWithSessionPolicy), "session-policy should allow read on key vol1/buck1/key1"); + } +} diff --git a/plugin-ozone/src/test/resources/om_dev_ozone.json b/plugin-ozone/src/test/resources/om_dev_ozone.json new file mode 100644 index 0000000000..e8d761801a --- /dev/null +++ b/plugin-ozone/src/test/resources/om_dev_ozone.json @@ -0,0 +1,46 @@ +{ + "serviceName": "dev_ozone", "serviceId": 1, + "serviceDef": { + "id": 1, "name":"ozone", + "resources":[ + { "name": "volume", "level": 1, "parent": "", "matcher":"org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Volume", "description": "Volume" }, + { "name": "bucket", "level": 2, "parent": "volume", "matcher":"org.apache.ranger.plugin.resourcematcher.RangerURLResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Bucket", "description": "Bucket" }, + { "name": "key", "level": 3, "parent": "bucket", "matcher":"org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": true }, "label": "Key", "description": "Key" }, + { "name": "role", "level": 4, "parent": "", "matcher":"org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", "matcherOptions": { "wildCard": true, "ignoreCase": false }, "label": "Role", "description": "Role" } + ], + "accessTypes":[ + { "name": "read", "label": "Read" }, + { "name": "write", "label": "Write" }, + { "name": "create", "label": "Create" }, + { "name": "list", "label": "List" }, + { "name": "delete", "label": "Delete" }, + { "name": "read_acl", "label": "Read_ACL" }, + { "name": "write_acl", "label": "Write_ACL" }, + { "name": "all", "label": "All", "impliedGrants": [ "read", "write", "create", "list", "delete", "read_acl", "write_acl" ] }, + { "name": "assume_role", "label": "Assume_Role" } + ] + }, + "policies": [ + { "id": 100, "name": "role: role1", "isEnabled": true, "isAuditEnabled": true, + "resources": { "role": { "values": [ "role1" ] } }, + "policyItems":[ + { "accesses": [ { "type": "assume_role" } ], "users": [ "user1" ] } + ] + }, + { "id": 101, "name": "volume: vol1, bucket: vol1/buck1", "isEnabled": true, "isAuditEnabled": true, + "resources": { "volume": { "values": [ "vol1", "vol2" ] } }, + "additionalResources": [ + { "volume": { "values": [ "vol1" ] }, "bucket": { "values": [ "buck1" ] } } + ], + "policyItems":[ + { "accesses": [ { "type": "list" } ], "roles": [ "role1" ] } + ] + }, + { "id": 102, "name": "key: vol1/buck1/key1", "isEnabled": true, "isAuditEnabled": true, + "resources": { "volume": { "values": [ "vol1" ] }, "bucket": { "values": [ "buck1" ] }, "key": { "values": [ "key1" ] } }, + "policyItems":[ + { "accesses": [ { "type": "read" } ], "roles": [ "role1" ] } + ] + } + ] +} \ No newline at end of file diff --git a/plugin-ozone/src/test/resources/ranger-ozone-security.xml b/plugin-ozone/src/test/resources/ranger-ozone-security.xml new file mode 100644 index 0000000000..05c47ff828 --- /dev/null +++ b/plugin-ozone/src/test/resources/ranger-ozone-security.xml @@ -0,0 +1,29 @@ + + + + + + ranger.plugin.ozone.service.name + dev_ozone + + + + ranger.plugin.ozone.policy.source.impl + org.apache.ranger.admin.client.EmbeddedResourcePolicySource + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index d12acce9e9..db387ca011 100644 --- a/pom.xml +++ b/pom.xml @@ -180,7 +180,7 @@ 1.79 1.79 20211018.2 - 1.4.0 + 2.1.0 2.3 5.2.2 @@ -793,6 +793,11 @@ jetbrains-intellij-dependencies https://packages.jetbrains.team/maven/p/ij/intellij-dependencies + + ozone-2.1.0-rc + ozone-2.1.0-rc + https://repository.apache.org/content/repositories/orgapacheozone-1061 + diff --git a/ranger-ozone-plugin-shim/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java b/ranger-ozone-plugin-shim/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java index 0b461d3b08..7658d4aeaf 100644 --- a/ranger-ozone-plugin-shim/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java +++ b/ranger-ozone-plugin-shim/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java @@ -20,6 +20,7 @@ package org.apache.ranger.authorization.ozone.authorizer; import org.apache.hadoop.ozone.om.exceptions.OMException; +import org.apache.hadoop.ozone.security.acl.AssumeRoleRequest; import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer; import org.apache.hadoop.ozone.security.acl.IOzoneObj; import org.apache.hadoop.ozone.security.acl.RequestContext; @@ -59,6 +60,21 @@ public boolean checkAccess(IOzoneObj ozoneObject, RequestContext context) throws } } + @Override + public String generateAssumeRoleSessionPolicy(AssumeRoleRequest assumeRoleRequest) throws OMException { + LOG.debug("==> RangerOzoneAuthorizer.generateAssumeRoleSessionPolicy()"); + + try { + activatePluginClassLoader(); + + return ozoneAuthorizationProviderImpl.generateAssumeRoleSessionPolicy(assumeRoleRequest); + } finally { + deactivatePluginClassLoader(); + + LOG.debug("<== RangerOzoneAuthorizer.generateAssumeRoleSessionPolicy()"); + } + } + private void init() { LOG.debug("==> RangerOzoneAuthorizer.init()");