This is an automated email from the ASF dual-hosted git repository. madhan pushed a commit to branch ranger-2.6 in repository https://gitbox.apache.org/repos/asf/ranger.git
commit 174b0cae93553a09555708ac3ff04263349cfee2 Author: Fateh Singh <[email protected]> AuthorDate: Sun Nov 24 23:47:59 2024 -0800 RANGER-5001: RANGER-4977: Support ignoreDescendantDeny & fix hbase scan authorization (#411) * RANGER-5001: Support ignoreDescendantDeny * RANGER-4977: Fix hbase scan authorization by using ignoreDescendantDeny=false (cherry picked from commit 316049090a560f641d7774bbe7ef6490881eac22) --- .../plugin/policyengine/RangerAccessRequest.java | 2 + .../policyengine/RangerAccessRequestImpl.java | 57 ++++++++++++++-------- .../policyengine/RangerAccessRequestReadOnly.java | 3 ++ .../policyengine/RangerAccessRequestWrapper.java | 3 ++ .../policyengine/RangerPolicyEngineImpl.java | 5 ++ .../policyengine/RangerTagAccessRequest.java | 1 + .../RangerDefaultPolicyEvaluator.java | 8 +-- .../test_policyengine_tag_hive_filebased.json | 26 +++++++++- .../authorization/hbase/AuthorizationSession.java | 8 ++- .../hbase/RangerAuthorizationCoprocessor.java | 2 + .../authorization/hbase/TestPolicyEngine.java | 6 +++ ...st_policyengine_hbase_ignoreDenyDescendant.json | 55 +++++++++++++++++++++ 12 files changed, 152 insertions(+), 24 deletions(-) diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequest.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequest.java index 01848df76..2f3e51d33 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequest.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequest.java @@ -32,6 +32,8 @@ public interface RangerAccessRequest { boolean isAccessTypeAny(); + default boolean ignoreDescendantDeny() { return true; } + boolean isAccessTypeDelegatedAdmin(); String getUser(); diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestImpl.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestImpl.java index 019a0d893..769b40dd8 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestImpl.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestImpl.java @@ -50,13 +50,14 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { private String action; private String requestData; private String sessionId; - private Map<String, Object> context; - private String clusterName; - private String clusterType; - - private boolean isAccessTypeAny; - private boolean isAccessTypeDelegatedAdmin; - private ResourceMatchingScope resourceMatchingScope = ResourceMatchingScope.SELF; + private Map<String, Object> context; + private String clusterName; + private String clusterType; + private Boolean isDescendantDenyIgnored = true; + + private boolean isAccessTypeAny; + private boolean isAccessTypeDelegatedAdmin; + private ResourceMatchingScope resourceMatchingScope = ResourceMatchingScope.SELF; private Map<String, ResourceElementMatchingScope> resourceElementMatchingScopes = Collections.emptyMap(); public RangerAccessRequestImpl() { @@ -80,6 +81,7 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { setSessionId(null); setContext(null); setClusterName(null); + setIgnoreDescendantDeny(null); } public RangerAccessRequestImpl(RangerAccessRequest request) { @@ -101,6 +103,7 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { setResourceElementMatchingScopes(request.getResourceElementMatchingScopes()); setClientIPAddress(request.getClientIPAddress()); setClusterType(request.getClusterType()); + setIgnoreDescendantDeny(request.ignoreDescendantDeny()); } @Override @@ -113,6 +116,11 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { return accessType; } + @Override + public boolean ignoreDescendantDeny() { + return isDescendantDenyIgnored == null || isDescendantDenyIgnored; + } + @Override public String getUser() { return user; @@ -134,7 +142,9 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { } @Override - public String getClientIPAddress() { return clientIPAddress;} + public String getClientIPAddress() { + return clientIPAddress; + } @Override public String getRemoteIPAddress() { @@ -142,7 +152,9 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { } @Override - public List<String> getForwardedAddresses() { return forwardedAddresses; } + public List<String> getForwardedAddresses() { + return forwardedAddresses; + } @Override public String getClientType() { @@ -175,7 +187,9 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { } @Override - public Map<String, ResourceElementMatchingScope> getResourceElementMatchingScopes() { return this.resourceElementMatchingScopes; } + public Map<String, ResourceElementMatchingScope> getResourceElementMatchingScopes() { + return this.resourceElementMatchingScopes; + } @Override public boolean isAccessTypeAny() { @@ -204,6 +218,10 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { isAccessTypeDelegatedAdmin = StringUtils.equals(accessType, RangerPolicyEngine.ADMIN_ACCESS); } + public void setIgnoreDescendantDeny(Boolean isDescendantDenyIgnored) { + this.isDescendantDenyIgnored = isDescendantDenyIgnored == null || isDescendantDenyIgnored; + } + public void setUser(String user) { this.user = user; } @@ -247,7 +265,7 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { public void setSessionId(String sessionId) { this.sessionId = sessionId; } - + public String getClusterName() { return clusterName; } @@ -289,7 +307,7 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { } } - public void extractAndSetClientIPAddress(boolean useForwardedIPAddress, String[]trustedProxyAddresses) { + public void extractAndSetClientIPAddress(boolean useForwardedIPAddress, String[] trustedProxyAddresses) { String ip = getRemoteIPAddress(); if (ip == null) { ip = getClientIPAddress(); @@ -328,7 +346,7 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { } @Override - public String toString( ) { + public String toString() { StringBuilder sb = new StringBuilder(); toString(sb); @@ -344,16 +362,16 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { sb.append("user={").append(user).append("} "); sb.append("userGroups={"); - if(userGroups != null) { - for(String userGroup : userGroups) { + if (userGroups != null) { + for (String userGroup : userGroups) { sb.append(userGroup).append(" "); } } sb.append("} "); sb.append("userRoles={"); - if(userRoles != null) { - for(String role : userRoles) { + if (userRoles != null) { + for (String role : userRoles) { sb.append(role).append(" "); } } @@ -373,8 +391,8 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { sb.append("clusterType={").append(clusterType).append("} "); sb.append("context={"); - if(context != null) { - for(Map.Entry<String, Object> e : context.entrySet()) { + if (context != null) { + for (Map.Entry<String, Object> e : context.entrySet()) { Object val = e.getValue(); if (!(val instanceof RangerAccessRequest)) { // to avoid recursive calls @@ -388,6 +406,7 @@ public class RangerAccessRequestImpl implements RangerAccessRequest { return sb; } + @Override public RangerAccessRequest getReadOnlyCopy() { return new RangerAccessRequestReadOnly(this); diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestReadOnly.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestReadOnly.java index b732dfab6..1669efb1c 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestReadOnly.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestReadOnly.java @@ -53,6 +53,9 @@ public class RangerAccessRequestReadOnly implements RangerAccessRequest { @Override public boolean isAccessTypeAny() { return source.isAccessTypeAny(); } + @Override + public boolean ignoreDescendantDeny() { return source.ignoreDescendantDeny(); } + @Override public boolean isAccessTypeDelegatedAdmin() { return source.isAccessTypeDelegatedAdmin(); } diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestWrapper.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestWrapper.java index 96f851e9a..d309c6204 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestWrapper.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessRequestWrapper.java @@ -47,6 +47,9 @@ public class RangerAccessRequestWrapper implements RangerAccessRequest { @Override public String getAccessType() { return accessType; } + @Override + public boolean ignoreDescendantDeny() { return request.ignoreDescendantDeny(); } + @Override public boolean isAccessTypeAny() { return isAccessTypeAny; } diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPolicyEngineImpl.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPolicyEngineImpl.java index bb4996cbe..bb2d57640 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPolicyEngineImpl.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerPolicyEngineImpl.java @@ -51,6 +51,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static org.apache.ranger.plugin.policyengine.PolicyEvaluatorForTag.MATCH_TYPE_COMPARATOR; @@ -677,6 +678,10 @@ public class RangerPolicyEngineImpl implements RangerPolicyEngine { } RangerAccessRequestUtil.setAllRequestedAccessTypes(request.getContext(), allRequestedAccesses); RangerAccessRequestUtil.setIsAnyAccessInContext(request.getContext(), Boolean.TRUE); + if (!request.ignoreDescendantDeny()) { + Set<Set<String>> accessGroups = allRequestedAccesses.stream().map(Collections::singleton).collect(Collectors.toSet()); + RangerAccessRequestUtil.setAllRequestedAccessTypeGroups(request, accessGroups); + } } ret = evaluatePoliciesForOneAccessTypeNoAudit(request, policyType, zoneName, policyRepository, tagPolicyRepository); diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerTagAccessRequest.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerTagAccessRequest.java index 4b2d70645..24ad6b337 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerTagAccessRequest.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerTagAccessRequest.java @@ -61,6 +61,7 @@ public class RangerTagAccessRequest extends RangerAccessRequestImpl { super.setForwardedAddresses(request.getForwardedAddresses()); super.setSessionId(request.getSessionId()); super.setResourceMatchingScope(request.getResourceMatchingScope()); + super.setIgnoreDescendantDeny(request.ignoreDescendantDeny()); } public RangerPolicyResourceMatcher.MatchType getMatchType() { return matchType; diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyevaluator/RangerDefaultPolicyEvaluator.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyevaluator/RangerDefaultPolicyEvaluator.java index 183d93a4b..e2e3137eb 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/policyevaluator/RangerDefaultPolicyEvaluator.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyevaluator/RangerDefaultPolicyEvaluator.java @@ -535,7 +535,7 @@ public class RangerDefaultPolicyEvaluator extends RangerAbstractPolicyEvaluator LOG.debug("==> RangerDefaultPolicyEvaluator.updateAccessResult(" + result + ", " + matchType +", " + isAllowed + ", " + reason + ", " + getPolicyId() + ")"); } if (!isAllowed) { - if (matchType != RangerPolicyResourceMatcher.MatchType.DESCENDANT) { + if (matchType != RangerPolicyResourceMatcher.MatchType.DESCENDANT || !result.getAccessRequest().ignoreDescendantDeny()) { result.setIsAllowed(false); result.setPolicyPriority(getPolicyPriority()); result.setPolicyId(getPolicyId()); @@ -870,11 +870,13 @@ public class RangerDefaultPolicyEvaluator extends RangerAbstractPolicyEvaluator } } } - /* At least one access is allowed - this evaluator need not be checked for other accesses as the test below + /* At least one access is allowed or denied - this evaluator need not be checked for other accesses as the test below * implies that there is only one access group in the request */ if (oneRequest.isAccessTypeAny() || RangerAccessRequestUtil.getIsAnyAccessInContext(oneRequest.getContext())) { - if (allowResult != null) { + if (oneRequest.ignoreDescendantDeny() && allowResult != null) { + break; + } else if (!oneRequest.ignoreDescendantDeny() && denyResult != null) { break; } } diff --git a/agents-common/src/test/resources/policyengine/test_policyengine_tag_hive_filebased.json b/agents-common/src/test/resources/policyengine/test_policyengine_tag_hive_filebased.json index b3ca12ed6..4f92f82af 100644 --- a/agents-common/src/test/resources/policyengine/test_policyengine_tag_hive_filebased.json +++ b/agents-common/src/test/resources/policyengine/test_policyengine_tag_hive_filebased.json @@ -225,7 +225,7 @@ }, "tests":[ - {"name":"DENY 'select from employee.personal;' for user1 using EXPIRES_ON tag", + {"name":"ALLOW 'select from employee.personal;' for user1 using EXPIRES_ON tag", "request":{ "resource":{"elements":{"database":"employee", "table":"personal"}}, "resourceMatchingScope": "SELF_OR_DESCENDANTS", "accessType":"select","user":"user1","userGroups":[],"requestData":"select from employee.personal;' for user1" @@ -233,6 +233,30 @@ }, "result":{"isAudited":true,"isAllowed":true,"policyId":101} }, + {"name":"DENY 'select from employee.personal;' for user1 using EXPIRES_ON tag with isDescendantDenyIgnored=false", + "request":{ + "resource":{"elements":{"database":"employee", "table":"personal"}}, "resourceMatchingScope": "SELF_OR_DESCENDANTS", "isDescendantDenyIgnored": false, + "accessType":"select","user":"user1","userGroups":[],"requestData":"select from employee.personal;' for user1 with isDescendantDenyIgnored=false" + + }, + "result":{"isAudited":true,"isAllowed":false,"policyId":5} + }, + {"name":"ALLOW 'use employee;' for user1 using EXPIRES_ON tag", + "request":{ + "resource":{"elements":{"database":"employee"}}, + "accessType":"","user":"user1","userGroups":[],"requestData":"use employee;' for user1 using EXPIRES_ON tag" + + }, + "result":{"isAudited":true,"isAllowed":true,"policyId":101} + }, + {"name":"DENY 'use employee;' for user1 using EXPIRES_ON tag with isDescendantDenyIgnored=false", + "request":{ + "resource":{"elements":{"database":"employee"}}, "isDescendantDenyIgnored": false, + "accessType":"","user":"user1","userGroups":[],"requestData":"use employee;' for user1 using EXPIRES_ON tag with isDescendantDenyIgnored=false" + + }, + "result":{"isAudited":true,"isAllowed":false,"policyId":5} + }, {"name":"ALLOW 'select ssn from employee.personal;' for user1 using EXPIRES_ON tag", "request":{ "resource":{"elements":{"database":"employee", "table":"personal", "column":"ssn"}}, diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/AuthorizationSession.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/AuthorizationSession.java index 152c8a697..6e999ba51 100644 --- a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/AuthorizationSession.java +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/AuthorizationSession.java @@ -64,6 +64,8 @@ public class AuthorizationSession { boolean _superUser = false; // is this session for a super user? private RangerAccessRequest.ResourceMatchingScope _resourceMatchingScope = RangerAccessRequest.ResourceMatchingScope.SELF; + private boolean _ignoreDescendantDeny = true; + // internal state per-authorization RangerAccessRequest _request; RangerAccessResult _result; @@ -195,7 +197,7 @@ public class AuthorizationSession { request.setClientIPAddress(_remoteAddress); request.setResourceMatchingScope(_resourceMatchingScope); request.setAccessTime(new Date()); - + request.setIgnoreDescendantDeny(_ignoreDescendantDeny); _request = request; if (LOG.isDebugEnabled()) { LOG.debug("Built request: " + request.toString()); @@ -377,4 +379,8 @@ public class AuthorizationSession { _resourceMatchingScope = scope; return this; } + AuthorizationSession ignoreDescendantDeny(boolean ignoreDescendantDeny) { + _ignoreDescendantDeny = ignoreDescendantDeny; + return this; + } } diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationCoprocessor.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationCoprocessor.java index 15e57fa36..2c9b6b80b 100644 --- a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationCoprocessor.java +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationCoprocessor.java @@ -441,6 +441,7 @@ public class RangerAuthorizationCoprocessor implements AccessControlService.Inte LOG.debug("evaluateAccess: family level access for [" + family + "] is evaluated to " + isColumnFamilyAuthorized + ". Checking if [" + family + "] descendants have access."); } session.resourceMatchingScope(RangerAccessRequest.ResourceMatchingScope.SELF_OR_DESCENDANTS) + .ignoreDescendantDeny(false) .buildRequest() .authorize(); auditEvent = auditHandler.getAndDiscardMostRecentEvent(); // capture it only for failure @@ -488,6 +489,7 @@ public class RangerAuthorizationCoprocessor implements AccessControlService.Inte } // Restore the headMatch setting session.resourceMatchingScope(RangerAccessRequest.ResourceMatchingScope.SELF); + session.ignoreDescendantDeny(true); } else { LOG.debug("evaluateAccess: columns collection not empty. Skipping Family level check, will do finer level access check."); Set<String> accessibleColumns = new HashSet<String>(); // will be used in to populate our results cache for the filter diff --git a/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/TestPolicyEngine.java b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/TestPolicyEngine.java index 771038521..8c120419d 100644 --- a/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/TestPolicyEngine.java +++ b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/TestPolicyEngine.java @@ -82,6 +82,12 @@ public class TestPolicyEngine { runTestsFromResourceFiles(hbaseTestResourceFiles); } + @Test + public void testPolicyEngine_hbase_ignoreDescendantDeny() { + String[] hbaseTestResourceFiles = { "/policyengine/test_policyengine_hbase_ignoreDenyDescendant.json" }; + + runTestsFromResourceFiles(hbaseTestResourceFiles); + } private void runTestsFromResourceFiles(String[] resourceNames) { for(String resourceName : resourceNames) { diff --git a/hbase-agent/src/test/resources/policyengine/test_policyengine_hbase_ignoreDenyDescendant.json b/hbase-agent/src/test/resources/policyengine/test_policyengine_hbase_ignoreDenyDescendant.json new file mode 100644 index 000000000..1256f7e33 --- /dev/null +++ b/hbase-agent/src/test/resources/policyengine/test_policyengine_hbase_ignoreDenyDescendant.json @@ -0,0 +1,55 @@ +{ + "serviceName":"hbasedev", + + "serviceDef":{ + "name":"hbase", + "id":2, + "resources":[ + {"name":"table","level":1,"parent":"","mandatory":true,"lookupSupported":true,"matcher":"org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher","matcherOptions":{"wildCard":true, "ignoreCase":true},"label":"HBase Table","description":"HBase Table"}, + {"name":"column-family","level":2,"parent":"table","mandatory":true,"lookupSupported":true,"matcher":"org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher","matcherOptions":{"wildCard":true, "ignoreCase":true},"label":"HBase Column-Family","description":"HBase Column-Family"}, + {"name":"column","level":3,"parent":"column-family","mandatory":true,"lookupSupported":true,"matcher":"org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher","matcherOptions":{"wildCard":true, "ignoreCase":true},"label":"HBase Column","description":"HBase Column"} + ], + "accessTypes":[ + {"name":"read","label":"Read"}, + {"name":"write","label":"Write"}, + {"name":"create","label":"Create"}, + {"name":"admin","label":"Admin","impliedGrants":["read","write","create"]} + ] + }, + + "policies":[ + {"id":1,"name":"table=finance; column-family=restricted, column=restricted_column","isEnabled":true,"isAuditEnabled":true, + "resources":{"table":{"values":["finance"]},"column-family":{"values":["restricted_cf"]}, "column":{"values":["restricted_column"]}}, + "denyPolicyItems":[ + {"accesses":[{"type":"read","isAllowed":true}],"users":["user1"],"groups":[],"delegateAdmin":false} + ] + } + , + {"id":2,"name":"table=finance; column-family=restricted,column=*","isEnabled":true,"isAuditEnabled":true, + "resources":{"table":{"values":["finance"]},"column-family":{"values":["restricted_cf"]}, "column":{"values":["*"]}}, + "policyItems":[ + {"accesses":[{"type":"read","isAllowed":true}],"users":["user1"],"groups":[],"delegateAdmin":false} + ] + } + ], + + "tests":[ + {"name":"TEST!!! DENY 'get' for restricted column family when isDescendantDenyIgnored=false", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted_cf"}}, + "resourceMatchingScope": "SELF_OR_DESCENDANTS","isDescendantDenyIgnored": "false", + "accessType":"read","user":"user1","requestData":"deny get as there is a restricted column. Expected behavior for scan" + }, + "result":{"isAudited":true,"isAllowed":false,"policyId":1} + }, + {"name":"TEST!!! Allow 'get' for restricted column family when isDescendantDenyIgnored=true", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted_cf"}}, + "resourceMatchingScope": "SELF_OR_DESCENDANTS", + "accessType":"read","user":"user1","requestData":"allow get as restricted column policy not considered. Not expected behavior" + }, + "result":{"isAudited":true,"isAllowed":true,"policyId":2} + } + + ] +}
