Repository: incubator-ranger Updated Branches: refs/heads/stack 1f0dccadf -> 5c4b59af7
RANGER-230: HBase plugin updates to: 1) fix generation of duplicate audit messages 2) fix issues in column-family/column filtering logic. Project: http://git-wip-us.apache.org/repos/asf/incubator-ranger/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ranger/commit/5c4b59af Tree: http://git-wip-us.apache.org/repos/asf/incubator-ranger/tree/5c4b59af Diff: http://git-wip-us.apache.org/repos/asf/incubator-ranger/diff/5c4b59af Branch: refs/heads/stack Commit: 5c4b59af7e5f7ea9382d18566ff2835235f63375 Parents: 1f0dcca Author: Alok Lal <[email protected]> Authored: Thu Feb 5 15:33:39 2015 -0800 Committer: Madhan Neethiraj <[email protected]> Committed: Thu Feb 5 15:33:39 2015 -0800 ---------------------------------------------------------------------- .../hbase/AuthorizationSession.java | 5 +- .../hbase/HbaseAuditHandlerImpl.java | 3 +- .../hbase/RangerAuthorizationCoprocessor.java | 25 +++-- .../hbase/RangerAuthorizationFilter.java | 37 +++++-- .../hbase/RangerAuthorizationFilterTest.java | 108 +++++++++++++++++++ 5 files changed, 159 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/5c4b59af/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/AuthorizationSession.java ---------------------------------------------------------------------- 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 e6067ce..977c745 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 @@ -163,14 +163,15 @@ public class AuthorizationSession { resource.setValue("column", _column); String user = _userUtils.getUserAsString(_user); - LOG.debug("AuthorizationSession buildRequest: user[" + user + "], groups[" + _groups + "]"); - RangerAccessRequestImpl request = new RangerAccessRequestImpl(resource, _access, user, _groups); request.setAction(_operation); request.setRequestData(_otherInformation); request.setClientIPAddress(_remoteAddress); _request = request; + if (LOG.isDebugEnabled()) { + LOG.debug("Built request: " + request.toString()); + } return this; } http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/5c4b59af/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandlerImpl.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandlerImpl.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandlerImpl.java index a5d3f16..fb4f8a0 100644 --- a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandlerImpl.java +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandlerImpl.java @@ -41,7 +41,8 @@ public class HbaseAuditHandlerImpl extends RangerDefaultAuditHandler implements _allEvents.add(_mostRecentEvent); } _mostRecentEvent = event; - return event; + // We return null because we don't want default audit handler to audit anything! + return null; } @Override http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/5c4b59af/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationCoprocessor.java ---------------------------------------------------------------------- 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 1a956d3..2e128ec 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 @@ -295,7 +295,7 @@ public class RangerAuthorizationCoprocessor extends RangerAuthorizationCoprocess if (LOG.isDebugEnabled()) { final String format = "evaluateAccess: entered: Operation[%s], access[%s], families[%s]"; Map<String, Set<String>> families = getColumnFamilies(familyMap); - String message = String.format(format, operation, access, families); + String message = String.format(format, operation, access, families.toString()); LOG.debug(message); } @@ -314,7 +314,7 @@ public class RangerAuthorizationCoprocessor extends RangerAuthorizationCoprocess result = new ColumnFamilyAccessResult(true, true, null, null, null, null); if (LOG.isDebugEnabled()) { Map<String, Set<String>> families = getColumnFamilies(familyMap); - String message = String.format(messageTemplate, operation, access, families, result.toString()); + String message = String.format(messageTemplate, operation, access, families.toString(), result.toString()); LOG.debug(message); } return result; @@ -331,8 +331,11 @@ public class RangerAuthorizationCoprocessor extends RangerAuthorizationCoprocess .access(access) .table(table); Map<String, Set<String>> families = getColumnFamilies(familyMap); + if (LOG.isDebugEnabled()) { + LOG.debug("evaluateAccess: families to process: " + families.toString()); + } if (families == null || families.isEmpty()) { - // table level access is desired + LOG.debug("evaluateAccess: Null or empty families collection, ok. Table level access is desired"); session.buildRequest() .authorize(); boolean authorized = session.isAuthorized(); @@ -347,10 +350,12 @@ public class RangerAuthorizationCoprocessor extends RangerAuthorizationCoprocess authorized ? Collections.singletonList(event) : null, authorized ? null : event, null, reason); if (LOG.isDebugEnabled()) { - String message = String.format(messageTemplate, operation, access, families, result.toString()); + String message = String.format(messageTemplate, operation, access, families.toString(), result.toString()); LOG.debug(message); } return result; + } else { + LOG.debug("evaluateAccess: Families collection not null. Skipping table-level check, will do finer level check"); } boolean everythingIsAccessible = true; @@ -365,10 +370,12 @@ public class RangerAuthorizationCoprocessor extends RangerAuthorizationCoprocess for (Map.Entry<String, Set<String>> anEntry : families.entrySet()) { String family = anEntry.getKey(); session.columnFamily(family); - + if (LOG.isDebugEnabled()) { + LOG.debug("evaluateAccess: Processing family: " + family); + } Set<String> columns = anEntry.getValue(); if (columns == null || columns.isEmpty()) { - // family level access is desired. + LOG.debug("evaluateAccess: columns collection null or empty, ok. Family level access is desired."); session.column(null) // zap stale column from prior iteration of this loop, if any .buildRequest() .authorize(); @@ -387,8 +394,12 @@ public class RangerAuthorizationCoprocessor extends RangerAuthorizationCoprocess denialReason = String.format("Insufficient permissions for user â%s',action: %s, tableName:%s, family:%s, no columns found.", user.getName(), operation, table, family); } } 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 for (String column : columns) { + if (LOG.isDebugEnabled()) { + LOG.debug("evaluateAccess: Processing column: " + column); + } session.column(column) .buildRequest() .authorize(); @@ -414,7 +425,7 @@ public class RangerAuthorizationCoprocessor extends RangerAuthorizationCoprocess result = new ColumnFamilyAccessResult(everythingIsAccessible, somethingIsAccessible, authorizedEvents, deniedEvent, accessResultsCache, denialReason); if (LOG.isDebugEnabled()) { - String message = String.format(messageTemplate, operation, access, families, result.toString()); + String message = String.format(messageTemplate, operation, access, families.toString(), result.toString()); LOG.debug(message); } return result; http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/5c4b59af/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilter.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilter.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilter.java index 5a66eb2..ae61a1e 100644 --- a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilter.java +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilter.java @@ -23,12 +23,16 @@ import java.io.IOException; import java.util.Map; import java.util.Set; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.filter.FilterBase; import org.apache.hadoop.hbase.util.Bytes; public class RangerAuthorizationFilter extends FilterBase { + private static final Log LOG = LogFactory.getLog(RangerAuthorizationFilter.class.getName()); final Map<String, Set<String>> _cache; public RangerAuthorizationFilter(Map<String, Set<String>> cache) { @@ -38,31 +42,46 @@ public class RangerAuthorizationFilter extends FilterBase { @SuppressWarnings("deprecation") @Override public ReturnCode filterKeyValue(Cell kv) throws IOException { - // if our cache is null or empty then there is no hope for any access + if (_cache == null || _cache.isEmpty()) { + LOG.debug("filterKeyValue: if cache is null or empty then there is no hope for any access. Denied!"); return ReturnCode.NEXT_COL; } - // null/empty families are denied + byte[] familyBytes = kv.getFamily(); if (familyBytes == null || familyBytes.length == 0) { + LOG.debug("filterKeyValue: null/empty families in request! Denied!"); return ReturnCode.NEXT_COL; } String family = Bytes.toString(familyBytes); - // null/empty columns are also denied + if (LOG.isDebugEnabled()) { + LOG.debug("filterKeyValue: Evaluating family[" + family + "]"); + } + + if (!_cache.containsKey(family)) { + LOG.debug("filterKeyValue: Cache map did not contain the family, i.e. nothing in family has access! Denied!"); + return ReturnCode.NEXT_COL; + } + Set<String> columns = _cache.get(family); + + if (CollectionUtils.isEmpty(columns)) { + LOG.debug("filterKeyValue: empty/null column set in cache for family implies family level access. No need to bother with column level. Allowed!"); + return ReturnCode.INCLUDE; + } byte[] columnBytes = kv.getQualifier(); if (columnBytes == null || columnBytes.length == 0) { + LOG.debug("filterKeyValue: empty/null column set in request implies family level access, which isn't available. Denied!"); return ReturnCode.NEXT_COL; } String column = Bytes.toString(columnBytes); - // allow if cache contains the family/column in it - Set<String> columns = _cache.get(family); - if (columns == null || columns.isEmpty()) { - // column family with a null/empty set of columns means all columns within that family are allowed - return ReturnCode.INCLUDE; + if (LOG.isDebugEnabled()) { + LOG.debug("filterKeyValue: Evaluating column[" + column + "]"); } - else if (columns.contains(column)) { + if (columns.contains(column)) { + LOG.debug("filterKeyValue: cache contains Column in column-family's collection. Access allowed!"); return ReturnCode.INCLUDE; } else { + LOG.debug("filterKeyValue: cache missing Column in column-family's collection. Access denied!"); return ReturnCode.NEXT_COL; } } http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/5c4b59af/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilterTest.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilterTest.java b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilterTest.java new file mode 100644 index 0000000..5575ea7 --- /dev/null +++ b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilterTest.java @@ -0,0 +1,108 @@ +/* + * 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.hbase; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.hadoop.hbase.Cell; +import org.apache.hadoop.hbase.filter.Filter.ReturnCode; +import org.junit.Test; + +import com.google.common.collect.Sets; + +@SuppressWarnings("deprecation") +public class RangerAuthorizationFilterTest { + + @Test + public void testFilterKeyValueCell_happyPath() throws IOException { + + // null/empty column collection in cache for a family implies family level access + Map<String, Set<String>> cache = new HashMap<String, Set<String>>(); + cache.put("family1", Collections.<String> emptySet()); + RangerAuthorizationFilter filter = new RangerAuthorizationFilter(cache); + + Cell aCell = mock(Cell.class); + when(aCell.getFamily()).thenReturn("family1".getBytes()); + when(aCell.getQualifier()).thenReturn("column1".getBytes()); + assertEquals(ReturnCode.INCLUDE, filter.filterKeyValue(aCell)); + + when(aCell.getQualifier()).thenReturn("column2".getBytes()); + assertEquals(ReturnCode.INCLUDE, filter.filterKeyValue(aCell)); + + // null empty column collection in REQUEST implies family level access + when(aCell.getQualifier()).thenReturn(null); + assertEquals(ReturnCode.INCLUDE, filter.filterKeyValue(aCell)); + + // specific columns in cache should be allowed only if there is a match of family and column + cache.clear(); + cache.put("family1", Sets.newHashSet("column11", "column12")); + cache.put("family2", Sets.newHashSet("column21", "column22")); + filter = new RangerAuthorizationFilter(cache); + + when(aCell.getFamily()).thenReturn("family1".getBytes()); + when(aCell.getQualifier()).thenReturn("column11".getBytes()); + assertEquals(ReturnCode.INCLUDE, filter.filterKeyValue(aCell)); + when(aCell.getQualifier()).thenReturn("column12".getBytes()); + assertEquals(ReturnCode.INCLUDE, filter.filterKeyValue(aCell)); + when(aCell.getQualifier()).thenReturn("column13".getBytes()); + assertEquals(ReturnCode.NEXT_COL, filter.filterKeyValue(aCell)); + + when(aCell.getFamily()).thenReturn("family2".getBytes()); + when(aCell.getQualifier()).thenReturn("column22".getBytes()); + assertEquals(ReturnCode.INCLUDE, filter.filterKeyValue(aCell)); + + when(aCell.getFamily()).thenReturn("family3".getBytes()); + when(aCell.getQualifier()).thenReturn("column11".getBytes()); + assertEquals(ReturnCode.NEXT_COL, filter.filterKeyValue(aCell)); + + // asking for family level access when one doesn't exist (colum collection for a family is not null/empty then it should get denied + when(aCell.getFamily()).thenReturn("family1".getBytes()); + when(aCell.getQualifier()).thenReturn(null); + assertEquals(ReturnCode.NEXT_COL, filter.filterKeyValue(aCell)); + } + + @Test + public void testFilterKeyValueCell_firewalling() throws IOException { + // null cache will deny everything. + RangerAuthorizationFilter filter = new RangerAuthorizationFilter(null); + Cell aCell = mock(Cell.class); + when(aCell.getFamily()).thenReturn("family1".getBytes()); + when(aCell.getQualifier()).thenReturn("column1".getBytes()); + assertEquals(ReturnCode.NEXT_COL, filter.filterKeyValue(aCell)); + + // non-null but empty cache should do the same + Map<String, Set<String>> cache = new HashMap<String, Set<String>>(); + filter = new RangerAuthorizationFilter(cache); + assertEquals(ReturnCode.NEXT_COL, filter.filterKeyValue(aCell)); + + // Null family in request would get denied, too + when(aCell.getFamily()).thenReturn(null); + when(aCell.getQualifier()).thenReturn("column1".getBytes()); + assertEquals(ReturnCode.NEXT_COL, filter.filterKeyValue(aCell)); + } +}
