Return-Path: X-Original-To: apmail-ranger-commits-archive@www.apache.org Delivered-To: apmail-ranger-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 63A2617AE5 for ; Sat, 31 Jan 2015 08:43:39 +0000 (UTC) Received: (qmail 27671 invoked by uid 500); 31 Jan 2015 08:43:40 -0000 Delivered-To: apmail-ranger-commits-archive@ranger.apache.org Received: (qmail 27646 invoked by uid 500); 31 Jan 2015 08:43:40 -0000 Mailing-List: contact commits-help@ranger.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ranger.incubator.apache.org Delivered-To: mailing list commits@ranger.incubator.apache.org Received: (qmail 27637 invoked by uid 99); 31 Jan 2015 08:43:40 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 31 Jan 2015 08:43:40 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED,T_RP_MATCHES_RCVD X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO mail.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with SMTP; Sat, 31 Jan 2015 08:43:35 +0000 Received: (qmail 27521 invoked by uid 99); 31 Jan 2015 08:43:15 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 31 Jan 2015 08:43:15 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 4BE34E04B6; Sat, 31 Jan 2015 08:43:15 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: madhan@apache.org To: commits@ranger.incubator.apache.org Date: Sat, 31 Jan 2015 08:43:18 -0000 Message-Id: <8f8f8ff69f8b49deb886fb8dae19fee9@git.apache.org> In-Reply-To: <6349c9e451d146a6b18cae1c6ed9cade@git.apache.org> References: <6349c9e451d146a6b18cae1c6ed9cade@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [4/4] incubator-ranger git commit: RANGER-230 Hbase plugin implementation using new pluggable service model X-Virus-Checked: Checked by ClamAV on apache.org RANGER-230 Hbase plugin implementation using new pluggable service model Project: http://git-wip-us.apache.org/repos/asf/incubator-ranger/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ranger/commit/46633a9e Tree: http://git-wip-us.apache.org/repos/asf/incubator-ranger/tree/46633a9e Diff: http://git-wip-us.apache.org/repos/asf/incubator-ranger/diff/46633a9e Branch: refs/heads/stack Commit: 46633a9ed2ed3499e95ad87409ce5b0460929da7 Parents: 1d6a259 Author: Alok Lal Authored: Sat Jan 31 00:42:21 2015 -0800 Committer: Madhan Neethiraj Committed: Sat Jan 31 00:42:21 2015 -0800 ---------------------------------------------------------------------- .../hbase/AuthorizationSession.java | 342 +++++++++++++++++++ .../authorization/hbase/ColumnIterator.java | 94 +++++ .../authorization/hbase/HbaseAuditHandler.java | 48 +++ .../hbase/HbaseAuditHandlerImpl.java | 69 ++++ .../authorization/hbase/HbaseAuthUtils.java | 33 ++ .../authorization/hbase/HbaseAuthUtilsImpl.java | 66 ++++ .../authorization/hbase/HbaseFactory.java | 58 ++++ .../authorization/hbase/HbaseUserUtils.java | 52 +++ .../authorization/hbase/HbaseUserUtilsImpl.java | 91 +++++ .../hbase/RangerAuthorizationFilter.java | 69 ++++ .../hbase/AuthorizationSessionTest.java | 218 ++++++++++++ .../hbase/HbaseAuthUtilsImplTest.java | 33 ++ .../hbase/RangerCoprocessorTest.java | 33 ++ .../authorization/hbase/TestPolicyEngine.java | 178 ++++++++++ hbase-agent/src/test/resources/log4j.properties | 16 + .../policyengine/test_policyengine_hbase.json | 159 +++++++++ 16 files changed, 1559 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/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 new file mode 100644 index 0000000..e6067ce --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/AuthorizationSession.java @@ -0,0 +1,342 @@ +/* + * 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 java.util.List; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.security.AccessDeniedException; +import org.apache.hadoop.hbase.security.User; +import org.apache.ranger.audit.model.AuthzAuditEvent; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest; +import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; +import org.apache.ranger.plugin.policyengine.RangerAccessResult; +import org.apache.ranger.plugin.policyengine.RangerPolicyEngine; +import org.apache.ranger.plugin.policyengine.RangerResourceImpl; + +import com.google.common.base.Objects; +import com.google.common.collect.Lists; + +public class AuthorizationSession { + + private static final Log LOG = LogFactory.getLog(AuthorizationSession.class.getName()); + // collaborator objects + final HbaseFactory _factory = HbaseFactory.getInstance(); + final HbaseUserUtils _userUtils = _factory.getUserUtils(); + final HbaseAuthUtils _authUtils = _factory.getAuthUtils(); + // immutable state + final RangerPolicyEngine _authorizer; + // Mutable state: Use supplied state information + String _operation; + String _otherInformation; + String _access; + String _table; + String _column; + String _columnFamily; + String _remoteAddress; + User _user; + Set _groups; // this exits to avoid having to get group for a user repeatedly. It is kept in sync with _user; + // Passing a null handler to policy engine would suppress audit logging. + HbaseAuditHandler _auditHandler = null; + + // internal state per-authorization + RangerAccessRequest _request; + RangerAccessResult _result; + + public AuthorizationSession(RangerPolicyEngine authorizer) { + _authorizer = authorizer; + } + + AuthorizationSession operation(String anOperation) { + _operation = anOperation; + return this; + } + + AuthorizationSession otherInformation(String information) { + _otherInformation = information; + return this; + } + + AuthorizationSession remoteAddress(String ipAddress) { + _remoteAddress = ipAddress; + return this; + } + + AuthorizationSession access(String anAccess) { + _access = anAccess; + return this; + } + + AuthorizationSession user(User aUser) { + _user = aUser; + if (_user == null) { + LOG.debug("AuthorizationSession.user: user is null!"); + _groups = null; + } else { + _groups = _userUtils.getUserGroups(_user); + } + return this; + } + AuthorizationSession table(String aTable) { + _table = aTable; + return this; + } + + AuthorizationSession columnFamily(String aColumnFamily) { + _columnFamily = aColumnFamily; + return this; + } + + AuthorizationSession column(String aColumn) { + _column = aColumn; + return this; + } + + void verifyBuildable() { + + String template = "Internal error: Incomplete/inconsisten state: [%s]. Can't build auth request!"; + if (_factory == null) { + String message = String.format(template, "factory is null"); + LOG.error(message); + throw new IllegalStateException(message); + } + if (_access == null || _access.isEmpty()) { + String message = String.format(template, "access is null"); + LOG.error(message); + throw new IllegalStateException(message); + } + if (_user == null) { + String message = String.format(template, "user is null"); + LOG.error(message); + throw new IllegalStateException(message); + } + if (isProvided(_columnFamily) && !isProvided(_table)) { + String message = String.format(template, "Table must be provided if column-family is provided"); + LOG.error(message); + throw new IllegalStateException(message); + } + if (isProvided(_column) && !isProvided(_columnFamily)) { + String message = String.format(template, "Column family must be provided if column is provided"); + LOG.error(message); + throw new IllegalStateException(message); + } + } + + void zapAuthorizationState() { + _request = null; + _result = null; + } + + boolean isProvided(String aString) { + return aString != null && !aString.isEmpty(); + } + + AuthorizationSession buildRequest() { + + verifyBuildable(); + // session can be reused so reset its state + zapAuthorizationState(); + // TODO get this via a factory instead + RangerResourceImpl resource = new RangerResourceImpl(); + // policy engine should deal sensibly with null/empty values, if any + resource.setValue("table", _table); + resource.setValue("column-family", _columnFamily); + 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; + return this; + } + + AuthorizationSession authorize() { + if (LOG.isDebugEnabled()) { + String message = "authorize: " + getRequestMessage(); + LOG.debug(message); + } + if (_request == null) { + String message = String.format("Invalid state transition: buildRequest() must be called before authorize(). This request would ultimately get denied.!"); + throw new IllegalStateException(message); + } else { + // ok to pass potentially null handler to policy engine. Null handler effectively suppresses the audit. + _result = _authorizer.isAccessAllowed(_request, _auditHandler); + } + if (LOG.isDebugEnabled()) { + boolean allowed = isAuthorized(); + String reason = getDenialReason(); + String message = "AuthorizationSession.authorize: " + getLogMessage(allowed, reason); + LOG.debug(message); + } + return this; + } + + void publishResults() throws AccessDeniedException { + + boolean authorized = isAuthorized(); + if (_auditHandler != null) { + List events = null; + /* + * What we log to audit depends on authorization status. For success we log all accumulated events. In case of failure + * we log just the last set of audit messages as we only need to record the cause of overall denial. + */ + if (authorized) { + List theseEvents = _auditHandler.getCapturedEvents(); + if (theseEvents != null && !theseEvents.isEmpty()) { + events = theseEvents; + } + } else { + AuthzAuditEvent event = _auditHandler.discardMostRecentEvent(); + if (event != null) { + events = Lists.newArrayList(event); + } + } + if (LOG.isDebugEnabled()) { + int size = events == null ? 0 : events.size(); + String auditMessage = events == null ? "" : events.toString(); + String message = String.format("Writing %d messages to audit: [%s]", size, auditMessage); + LOG.debug(message); + } + _auditHandler.logAuthzAudits(events); + } + if (!authorized) { + // and throw and exception... callers expect this behavior + String reason = getDenialReason(); + String message = getLogMessage(false, reason); + if (LOG.isDebugEnabled()) { + LOG.debug("AuthorizationSession.publishResults: throwing exception: " + message); + } + throw new AccessDeniedException("Insufficient permissions for user '" + _user.getName() + "' (action=" + _access + ")"); + } + } + + boolean isAudited() { + + boolean audited = false; + if (_result == null) { + String message = String.format("Internal error: _result was null! Assuming no audit. Request[%s]", _request.toString()); + LOG.error(message); + } else { + audited = _result.getIsAudited(); + } + return audited; + } + + boolean isAuthorized() { + boolean allowed = false; + if (_result == null) { + String message = String.format("Internal error: _result was null! Returning false."); + LOG.error(message); + } else { + allowed = _result.getIsAllowed(); + } + return allowed; + } + + String getDenialReason() { + String reason = ""; + if (_result == null) { + String message = String.format("Internal error: _result was null! Returning empty reason."); + LOG.error(message); + } else { + boolean allowed = _result.getIsAllowed(); + if (!allowed) { + reason = _result.getReason(); + } + } + return reason; + } + + String requestToString() { + return Objects.toStringHelper(_request.getClass()) + .add("operation", _operation) + .add("otherInformation", _otherInformation) + .add("access", _access) + .add("user", _user == null ? null : _user.getName()) + .add("groups", _groups) + .add("auditHandler", _auditHandler == null ? null : _auditHandler.getClass().getSimpleName()) + .add("table", _table) + .add("column", _column) + .add("column-family", _columnFamily) + .toString(); + } + + String getPrintableValue(String value) { + if (isProvided(value)) { + return value; + } else { + return ""; + } + } + + String getRequestMessage() { + String format = "Access[%s] by user[%s] belonging to groups[%s] to table[%s] for column-family[%s], column[%s] triggered by operation[%s], otherInformation[%s]"; + String user = _userUtils.getUserAsString(); + String message = String.format(format, getPrintableValue(_access), getPrintableValue(user), _groups, getPrintableValue(_table), + getPrintableValue(_columnFamily), getPrintableValue(_column), getPrintableValue(_operation), getPrintableValue(_otherInformation)); + return message; + } + + String getLogMessage(boolean allowed, String reason) { + String format = " %s: status[%s], reason[%s]"; + String message = String.format(format, getRequestMessage(), allowed ? "allowed" : "denied", reason); + return message; + } + + /** + * Hand creates a result object and set it on the request for cases where we need not go to policy manager. + * @return + */ + AuthorizationSession knownPatternAllowedNotAudited(String reason) { + _result = buildResult(true, false, reason); + return this; + } + + AuthorizationSession knownPatternDisallowedNotAudited(String reason) { + _result = buildResult(false, false, reason); + + return this; + } + + /** + * This method could potentially null out an earlier audit handler -- which effectively would suppress audits. + * @param anAuditHandler + * @return + */ + AuthorizationSession auditHandler(HbaseAuditHandler anAuditHandler) { + _auditHandler = anAuditHandler; + return this; + } + + RangerAccessResult buildResult(boolean allowed, boolean audited, String reason) { + RangerAccessResult result = _authorizer.createAccessResult(_request); + result.setIsAllowed(allowed); + result.setReason(reason); + result.setIsAudited(audited); + return result; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/ColumnIterator.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/ColumnIterator.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/ColumnIterator.java new file mode 100644 index 0000000..2c6f805 --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/ColumnIterator.java @@ -0,0 +1,94 @@ +/* + * 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 java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.KeyValue; +import org.apache.hadoop.hbase.util.Bytes; + +public class ColumnIterator implements Iterator { + // TODO write tests for this class + + private static final Log LOG = LogFactory.getLog(ColumnIterator.class.getName()); + Iterator _setIterator; + Iterator _listIterator; + + @SuppressWarnings("unchecked") + public ColumnIterator(Collection columnCollection) { + if (columnCollection != null) { + if (columnCollection instanceof Set) { + _setIterator = ((Set)columnCollection).iterator(); + } else if (columnCollection instanceof List) { + _listIterator = ((List)columnCollection).iterator(); + } else { // unexpected + // TODO make message better + LOG.error("Unexpected type " + columnCollection.getClass().getName() + " passed as value in column family collection"); + } + } + } + + @Override + public boolean hasNext() { + if (_setIterator != null) { + return _setIterator.hasNext(); + } + if (_listIterator != null) { + _listIterator.hasNext(); + } + return false; + } + + /** + * Never returns a null value. Will return empty string in case of null value. + */ + @Override + public String next() { + String value = ""; + if (_setIterator != null) { + byte[] valueBytes = _setIterator.next(); + if (valueBytes != null) { + value = Bytes.toString(valueBytes); + } + } else if (_listIterator != null) { + KeyValue kv = _listIterator.next(); + byte[] v = kv.getQualifier(); + if (v != null) { + value = Bytes.toString(v); + } + } else { + // TODO make the error message better + throw new NoSuchElementException("Empty values passed in!"); + } + return value; + } + + @Override + public void remove() { + // TODO make the error message better + throw new UnsupportedOperationException("Remove not supported from iterator!"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandler.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandler.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandler.java new file mode 100644 index 0000000..28d41aa --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandler.java @@ -0,0 +1,48 @@ +/* + * 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 java.util.Collection; +import java.util.List; + +import org.apache.ranger.audit.model.AuthzAuditEvent; +import org.apache.ranger.plugin.audit.RangerAuditHandler; + +public interface HbaseAuditHandler extends RangerAuditHandler { + + List getCapturedEvents(); + + void logAuthzAudits(Collection auditEvents); + + /** + * Discards and returns the last audit events captured by the audit handler. Last audit event should be the ones generated during the most recent authorization request. + * However, it won't be all of the audit events called during an authorize call since implementation class may not override the method which takes a list of responses -- in + * which case there would be several audit messages generated by one call but this only allows you to get last of those messages created during single auth request. + * After this call the last set of audit events won't be returned by getCapturedEvents. + * @return + */ + AuthzAuditEvent discardMostRecentEvent(); + + /** + * This is a complement to discardMostRecentEvent to set the most recent events. Often useful to un-pop audit messages that were take out. + * @param capturedEvents + */ + void setMostRecentEvent(AuthzAuditEvent capturedEvents); + +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/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 new file mode 100644 index 0000000..a5d3f16 --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuditHandlerImpl.java @@ -0,0 +1,69 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +import org.apache.ranger.audit.model.AuthzAuditEvent; +import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler; +import org.apache.ranger.plugin.policyengine.RangerAccessResult; + +public class HbaseAuditHandlerImpl extends RangerDefaultAuditHandler implements HbaseAuditHandler { + + static final List _EmptyList = new ArrayList(); + final List _allEvents = new ArrayList(); + // we replace its contents anytime new audit events are generated. + AuthzAuditEvent _mostRecentEvent = null; + + @Override + public AuthzAuditEvent getAuthzEvents(RangerAccessResult result) { + + AuthzAuditEvent event = super.getAuthzEvents(result); + // first accumulate last set of events and then capture these as the most recent ones + if (_mostRecentEvent != null) { + _allEvents.add(_mostRecentEvent); + } + _mostRecentEvent = event; + return event; + } + + @Override + public List getCapturedEvents() { + // construct a new collection since we don't want to lose track of which were the most recent events; + List result = new ArrayList(_allEvents); + if (_mostRecentEvent != null) { + result.add(_mostRecentEvent); + } + + return result; + } + + @Override + public AuthzAuditEvent discardMostRecentEvent() { + AuthzAuditEvent result = _mostRecentEvent; + _mostRecentEvent = null; + return result; + } + + @Override + public void setMostRecentEvent(AuthzAuditEvent event) { + _mostRecentEvent = event; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuthUtils.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuthUtils.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuthUtils.java new file mode 100644 index 0000000..45d4cb4 --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuthUtils.java @@ -0,0 +1,33 @@ +/* + * 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 org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; +import org.apache.hadoop.hbase.security.access.Permission.Action; + +public interface HbaseAuthUtils { + + String getAccess(Action action); + + boolean isReadAccess(String access); + + boolean isWriteAccess(String access); + + String getTable(RegionCoprocessorEnvironment regionServerEnv); +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuthUtilsImpl.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuthUtilsImpl.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuthUtilsImpl.java new file mode 100644 index 0000000..955a85c --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseAuthUtilsImpl.java @@ -0,0 +1,66 @@ +/* + * 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 org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.NamespaceDescriptor; +import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; +import org.apache.hadoop.hbase.security.access.Permission.Action; +import org.apache.hadoop.hbase.util.Bytes; + +public class HbaseAuthUtilsImpl implements HbaseAuthUtils { + + private static final Log LOG = LogFactory.getLog(HbaseAuthUtilsImpl.class.getName()); + + public String getNameSpace(NamespaceDescriptor ns) { + if (ns == null) { + // TODO log an error and Throw an error so the operation is denied? + } + return ns.getName(); + } + + @Override + public String getAccess(Action action) { + return action.toString().toLowerCase(); + } + + @Override + public boolean isReadAccess(String access) { + return getAccess(Action.READ).equals(access); + } + + @Override + public boolean isWriteAccess(String access) { + return getAccess(Action.WRITE).equals(access); + } + + @Override + public String getTable(RegionCoprocessorEnvironment regionServerEnv) { + HRegionInfo hri = regionServerEnv.getRegion().getRegionInfo(); + byte[] tableName = hri.getTable().getName() ; + String tableNameStr = Bytes.toString(tableName); + if (LOG.isDebugEnabled()) { + String message = String.format("getTable: Returning tablename[%s]", tableNameStr); + LOG.debug(message); + } + return tableNameStr; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseFactory.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseFactory.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseFactory.java new file mode 100644 index 0000000..97e70ec --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseFactory.java @@ -0,0 +1,58 @@ +/* + * 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 org.apache.ranger.plugin.policyengine.RangerPolicyEngine; +import org.apache.ranger.plugin.policyengine.RangerPolicyEngineImpl; + + +// TODO remove this in favor of Guice DI +public class HbaseFactory { + + static final HbaseUserUtils _UserUtils = new HbaseUserUtilsImpl(); + static final HbaseAuthUtils _AuthUtils = new HbaseAuthUtilsImpl(); + static final RangerPolicyEngine _PolicyEngine = new RangerPolicyEngineImpl(); + static final HbaseFactory _Factory = new HbaseFactory(); + /** + * This is a singleton + */ + private HbaseFactory() { + // TODO remove this clutch to enforce singleton by moving to a DI framework + } + + static HbaseFactory getInstance() { + return _Factory; + } + + HbaseAuthUtils getAuthUtils() { + return _AuthUtils; + } + + HbaseUserUtils getUserUtils() { + return _UserUtils; + } + + RangerPolicyEngine getPolicyEngine() { + return _PolicyEngine; + } + + HbaseAuditHandler getAuditHandler() { + return new HbaseAuditHandlerImpl(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseUserUtils.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseUserUtils.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseUserUtils.java new file mode 100644 index 0000000..aa85994 --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseUserUtils.java @@ -0,0 +1,52 @@ +/* + * 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 java.util.Set; + +import org.apache.hadoop.hbase.security.User; + +public interface HbaseUserUtils { + /** + * Returns user's short name or empty string if null is passed in. + * @param user + * @return + */ + String getUserAsString(User user); + + /** + * Returns the groups to which user belongs to as known to User object. For null values it returns an empty set. + * @param user + * @return + */ + Set getUserGroups(User user); + + /** + * May return null in case of an error + * @return + */ + User getUser(); + + /** + * Returns the user short name. Returns an empty string if Hbase User of context can't be found. + * @param request + * @return + */ + String getUserAsString(); +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseUserUtilsImpl.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseUserUtilsImpl.java b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseUserUtilsImpl.java new file mode 100644 index 0000000..6b32e54 --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/HbaseUserUtilsImpl.java @@ -0,0 +1,91 @@ +/* + * 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 java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.hbase.ipc.RequestContext; +import org.apache.hadoop.hbase.security.User; + +public class HbaseUserUtilsImpl implements HbaseUserUtils { + + private static final Log LOG = LogFactory.getLog(HbaseUserUtilsImpl.class.getName()); + + static Set _SuperUsers = Collections.synchronizedSet(new HashSet()); + static AtomicBoolean initialized = new AtomicBoolean(false); + + @Override + public String getUserAsString(User user) { + if (user == null) { + throw new IllegalArgumentException("User is null!"); + } + else { + return user.getShortName(); + } + } + + @Override + public Set getUserGroups(User user) { + if (user == null) { + throw new IllegalArgumentException("User is null!"); + } + else { + String[] groupsArray = user.getGroupNames(); + return new HashSet(Arrays.asList(groupsArray)); + } + } + + @Override + public User getUser() { + // current implementation does not use the request object! + User user; + if (RequestContext.isInRequestContext()) { + // this is the more common case + user = RequestContext.getRequestUser(); + } + else { + try { + user = User.getCurrent(); + } catch (IOException e) { + LOG.error("Unable to get current user: User.getCurrent() threw IOException"); + user = null; + } + } + return user; + } + + + @Override + public String getUserAsString() { + User user = getUser(); + if (user == null) { + return ""; + } + else { + return getUserAsString(user); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/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 new file mode 100644 index 0000000..5a66eb2 --- /dev/null +++ b/hbase-agent/src/main/java/org/apache/ranger/authorization/hbase/RangerAuthorizationFilter.java @@ -0,0 +1,69 @@ +/* + * 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 java.io.IOException; +import java.util.Map; +import java.util.Set; + +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 { + + final Map> _cache; + + public RangerAuthorizationFilter(Map> cache) { + _cache = cache; + } + + @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()) { + return ReturnCode.NEXT_COL; + } + // null/empty families are denied + byte[] familyBytes = kv.getFamily(); + if (familyBytes == null || familyBytes.length == 0) { + return ReturnCode.NEXT_COL; + } + String family = Bytes.toString(familyBytes); + // null/empty columns are also denied + byte[] columnBytes = kv.getQualifier(); + if (columnBytes == null || columnBytes.length == 0) { + return ReturnCode.NEXT_COL; + } + String column = Bytes.toString(columnBytes); + // allow if cache contains the family/column in it + Set 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; + } + else if (columns.contains(column)) { + return ReturnCode.INCLUDE; + } else { + return ReturnCode.NEXT_COL; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/AuthorizationSessionTest.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/AuthorizationSessionTest.java b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/AuthorizationSessionTest.java new file mode 100644 index 0000000..1cd0d92 --- /dev/null +++ b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/AuthorizationSessionTest.java @@ -0,0 +1,218 @@ +/* + * 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.*; +import static org.mockito.Mockito.*; + +import org.apache.hadoop.hbase.security.User; +import org.apache.ranger.plugin.policyengine.RangerPolicyEngine; +import org.apache.ranger.plugin.policyengine.RangerPolicyEngineImpl; +import org.junit.Assert; +import org.junit.Test; + +public class AuthorizationSessionTest { + + @Test + public void testAuthorizationSession() { +// fail("Not yet implemented"); + } + + @Test + public void testOperation() { +// fail("Not yet implemented"); + } + + @Test + public void testOtherInformation() { +// fail("Not yet implemented"); + } + + @Test + public void testAccess() { +// fail("Not yet implemented"); + } + + @Test + public void testUser() { +// fail("Not yet implemented"); + } + + @Test + public void testTable() { +// fail("Not yet implemented"); + } + + @Test + public void testColumnFamily() { +// fail("Not yet implemented"); + } + + @Test + public void testColumn() { +// fail("Not yet implemented"); + } + + @Test + public void testIsBuildable() { + RangerPolicyEngine engine = new RangerPolicyEngineImpl(); + AuthorizationSession session = new AuthorizationSession(engine); + try { + session.verifyBuildable(); + Assert.fail("Should have thrown exception"); + } catch (IllegalStateException e) { } + // user and access are the only required ones. + User user = mock(User.class); + when(user.getGroupNames()).thenReturn(new String[] { "groups", "group2" }); + session.access(" "); + session.user(user); + try { + session.verifyBuildable(); + } catch (IllegalStateException e) { + fail("Shouldn't have thrown an exception!"); + } + // setting column-family without table is a problem + session.columnFamily("family"); + try { + session.verifyBuildable(); + fail("Should have thrown an exception"); + } catch (IllegalStateException e) { } + + session.table("table"); + try { + session.verifyBuildable(); + } catch (IllegalStateException e) { + fail("Shouldn't have thrown an exception!"); + } + // setting column without column-family is a problem + session.columnFamily(null); + session.column("col"); + try { + session.verifyBuildable(); + fail("Should have thrown an exception"); + } catch (IllegalStateException e) { } + session.columnFamily("family"); + try { + session.verifyBuildable(); + } catch (IllegalStateException e) { + fail("Should have thrown an exception"); + } + } + + @Test + public void testZapAuthorizationState() { +// fail("Not yet implemented"); + } + + @Test + public void testIsProvided() { + AuthorizationSession session = new AuthorizationSession(null); + assertFalse(session.isProvided(null)); + assertFalse(session.isProvided("")); + assertTrue(session.isProvided(" ")); + assertTrue(session.isProvided("xtq")); + } + + @Test + public void testBuildRequest() { +// fail("Not yet implemented"); + } + + @Test + public void testAuthorize() { + RangerPolicyEngine engine = new RangerPolicyEngineImpl(); + + User user = mock(User.class); + when(user.getShortName()).thenReturn("user1"); + when(user.getGroupNames()).thenReturn(new String[] { "users" } ); + AuthorizationSession session = new AuthorizationSession(engine); + session.access("read") + .user(user) + .table(":meta:") + .buildRequest() + .authorize(); + } + + @Test + public void testPublishResults() { +// fail("Not yet implemented"); + } + + @Test + public void testIsAuthorized() { +// fail("Not yet implemented"); + } + + @Test + public void testGetDenialReason() { +// fail("Not yet implemented"); + } + + @Test + public void testGetResourceType() { +// fail("Not yet implemented"); + } + + @Test + public void testRequestToString() { +// fail("Not yet implemented"); + } + + @Test + public void testAudit() { +// fail("Not yet implemented"); + } + + @Test + public void testGetPrintableValue() { +// fail("Not yet implemented"); + } + + @Test + public void testBuildAccessDeniedMessage() { +// fail("Not yet implemented"); + } + + @Test + public void testBuildAccessDeniedMessageString() { +// fail("Not yet implemented"); + } + + @Test + public void testKnownPatternAllowedNotAudited() { +// fail("Not yet implemented"); + } + + @Test + public void testKnownPatternDisallowedNotAudited() { +// fail("Not yet implemented"); + } + + @Test + public void testAuditHandler() { +// fail("Not yet implemented"); + } + + @Test + public void testBuildResult() { +// fail("Not yet implemented"); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/HbaseAuthUtilsImplTest.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/HbaseAuthUtilsImplTest.java b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/HbaseAuthUtilsImplTest.java new file mode 100644 index 0000000..e40f31a --- /dev/null +++ b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/HbaseAuthUtilsImplTest.java @@ -0,0 +1,33 @@ +/* + * 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.assertTrue; + +import org.junit.Test; + +public class HbaseAuthUtilsImplTest { + + @Test + public void testIsReadAccess() { + HbaseAuthUtilsImpl authUtils = new HbaseAuthUtilsImpl(); + assertTrue(authUtils.isReadAccess("read")); + assertTrue(authUtils.isWriteAccess("write")); + } +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/RangerCoprocessorTest.java ---------------------------------------------------------------------- diff --git a/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/RangerCoprocessorTest.java b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/RangerCoprocessorTest.java new file mode 100644 index 0000000..15d4f93 --- /dev/null +++ b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/RangerCoprocessorTest.java @@ -0,0 +1,33 @@ +/* + * 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.assertNotNull; + +import org.junit.Test; + +public class RangerCoprocessorTest { + + @Test + public void test_canBeNewed() { + RangerAuthorizationCoprocessor _coprocessor = new RangerAuthorizationCoprocessor(); + assertNotNull(_coprocessor); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/TestPolicyEngine.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..ed6bcf0 --- /dev/null +++ b/hbase-agent/src/test/java/org/apache/ranger/authorization/hbase/TestPolicyEngine.java @@ -0,0 +1,178 @@ +/* + * 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.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.util.List; + +import org.apache.hadoop.hbase.security.AccessDeniedException; +import org.apache.hadoop.hbase.security.User; +import org.apache.ranger.authorization.hbase.TestPolicyEngine.PolicyEngineTestCase.TestData; +import org.apache.ranger.plugin.model.RangerPolicy; +import org.apache.ranger.plugin.model.RangerServiceDef; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest; +import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; +import org.apache.ranger.plugin.policyengine.RangerAccessResult; +import org.apache.ranger.plugin.policyengine.RangerPolicyEngineImpl; +import org.apache.ranger.plugin.policyengine.RangerResource; +import org.apache.ranger.plugin.policyengine.RangerResourceImpl; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + + +public class TestPolicyEngine { + static RangerPolicyEngineImpl policyEngine = null; + static Gson gsonBuilder = null; + + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + policyEngine = new RangerPolicyEngineImpl(); + gsonBuilder = new GsonBuilder().setDateFormat("yyyyMMdd-HH:mm:ss.SSS-Z") + .setPrettyPrinting() + .registerTypeAdapter(RangerAccessRequest.class, new RangerAccessRequestDeserializer()) + .registerTypeAdapter(RangerResource.class, new RangerResourceDeserializer()) + .create(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + } + + @Test + public void testPolicyEngine_hbase() { + String[] hbaseTestResourceFiles = { "/policyengine/test_policyengine_hbase.json" }; + + runTestsFromResourceFiles(hbaseTestResourceFiles); + + // lets use that policy engine now + AuthorizationSession session = new AuthorizationSession(policyEngine); + User user = mock(User.class); + when(user.getShortName()).thenReturn("user1"); + when(user.getGroupNames()).thenReturn(new String[] { "users" }); + session.access("read") + .user(user) + .table("finance") + .buildRequest() + .authorize(); + assertTrue(session.isAuthorized()); + try { + session.publishResults(); + } catch (AccessDeniedException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + when(user.getShortName()).thenReturn("user1"); + when(user.getGroupNames()).thenReturn(new String[] { "users" }); + session.access("write") + .buildRequest() + .authorize(); + assertFalse(session.isAuthorized()); + try { + session.publishResults(); + fail("Should have throw exception on denied request!"); + } catch (AccessDeniedException e) { + } + + } + + private void runTestsFromResourceFiles(String[] resourceNames) { + for(String resourceName : resourceNames) { + InputStream inStream = this.getClass().getResourceAsStream(resourceName); + InputStreamReader reader = new InputStreamReader(inStream); + + runTests(reader, resourceName); + } + } + + private void runTests(InputStreamReader reader, String testName) { + try { + PolicyEngineTestCase testCase = gsonBuilder.fromJson(reader, PolicyEngineTestCase.class); + + assertTrue("invalid input: " + testName, testCase != null && testCase.serviceDef != null && testCase.policies != null && testCase.tests != null); + + policyEngine.setPolicies(testCase.serviceName, testCase.serviceDef, testCase.policies); + boolean justBuildingPolicyEngine = true; + if (justBuildingPolicyEngine) { + return; + } else { + for(TestData test : testCase.tests) { + RangerAccessResult expected = test.result; + RangerAccessResult result = policyEngine.isAccessAllowed(test.request, null); + + assertNotNull(test.name, result); + assertEquals(test.name, expected.getIsAllowed(), result.getIsAllowed()); + } + } + } catch(Throwable excp) { + excp.printStackTrace(); + } + + } + + static class PolicyEngineTestCase { + public String serviceName; + public RangerServiceDef serviceDef; + public List policies; + public List tests; + + class TestData { + public String name; + public RangerAccessRequest request; + public RangerAccessResult result; + } + } + + static class RangerAccessRequestDeserializer implements JsonDeserializer { + @Override + public RangerAccessRequest deserialize(JsonElement jsonObj, Type type, + JsonDeserializationContext context) throws JsonParseException { + return gsonBuilder.fromJson(jsonObj, RangerAccessRequestImpl.class); + } + } + + static class RangerResourceDeserializer implements JsonDeserializer { + @Override + public RangerResource deserialize(JsonElement jsonObj, Type type, + JsonDeserializationContext context) throws JsonParseException { + return gsonBuilder.fromJson(jsonObj, RangerResourceImpl.class); + } + } +} + http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/test/resources/log4j.properties ---------------------------------------------------------------------- diff --git a/hbase-agent/src/test/resources/log4j.properties b/hbase-agent/src/test/resources/log4j.properties new file mode 100644 index 0000000..71a8957 --- /dev/null +++ b/hbase-agent/src/test/resources/log4j.properties @@ -0,0 +1,16 @@ +# Define some default values that can be overridden by system properties +ranger.root.logger=DEBUG,console +# Define the root logger to the system property "hbase.root.logger". +log4j.rootLogger=${ranger.root.logger} + +# Logging Threshold +log4j.threshold=ALL + +# +# console +# Add "console" to rootlogger above if you want to use this +# +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.target=System.err +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2}: %m%n \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/46633a9e/hbase-agent/src/test/resources/policyengine/test_policyengine_hbase.json ---------------------------------------------------------------------- diff --git a/hbase-agent/src/test/resources/policyengine/test_policyengine_hbase.json b/hbase-agent/src/test/resources/policyengine/test_policyengine_hbase.json new file mode 100644 index 0000000..f563c28 --- /dev/null +++ b/hbase-agent/src/test/resources/policyengine/test_policyengine_hbase.json @@ -0,0 +1,159 @@ +{ + "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,"table":"database","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*: audit-all-access","isEnabled":true,"isAuditEnabled":true, + "resources":{"table":{"values":["finance"]},"column-family":{"values":["restricted*"]}}, + "policyItems":[ + {"accesses":[],"users":[],"groups":["public"],"delegateAdmin":false} + ] + } + , + {"id":2,"name":"table=finance; column-family=restricted*","isEnabled":true,"isAuditEnabled":true, + "resources":{"table":{"values":["finance"]},"column-family":{"values":["restricted*"]}}, + "policyItems":[ + {"accesses":[{"type":"read","isAllowed":true},{"type":"write","isAllowed":true}],"users":[],"groups":["finance"],"delegateAdmin":false} + , + {"accesses":[{"type":"admin","isAllowed":true}],"users":[],"groups":["finance-admin"],"delegateAdmin":true} + ] + } + , + {"id":3,"name":"table=*; column-family=restricted*","isEnabled":true,"isAuditEnabled":false, + "resources":{"table":{"values":["*"]},"column-family":{"values":["restricted*"],"isExcludes":true}}, + "policyItems":[ + {"accesses":[{"type":"read","isAllowed":true}],"users":[],"groups":["public"],"delegateAdmin":false} + ] + } + ], + + "tests":[ + {"name":"ALLOW 'scan finance restricted-cf;' for finance", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["read"],"user":"user1","userGroups":["users","finance"],"requestData":"scan finance restricted-cf" + }, + "result":{"accessTypeResults":{"read":{"isAllowed":true,"isAudited":true,"policyId":2}}} + } + , + {"name":"ALLOW 'put finance restricted-cf;' for finance", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["write"],"user":"user1","userGroups":["users","finance"],"requestData":"put finance restricted-cf" + }, + "result":{"accessTypeResults":{"write":{"isAllowed":true,"isAudited":true,"policyId":2}}} + } + , + {"name":"DENY 'create finance restricted-cf;' for finance", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["create"],"user":"user1","userGroups":["users","finance"],"requestData":"create finance restricted-cf" + }, + "result":{"accessTypeResults":{"create":{"isAllowed":false,"isAudited":true,"policyId":-1}}} + } + , + {"name":"DENY 'grant finance restricted-cf;' for finance", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["admin"],"user":"user1","userGroups":["users","finance"],"requestData":"grant finance restricted-cf" + }, + "result":{"accessTypeResults":{"admin":{"isAllowed":false,"isAudited":true,"policyId":-1}}} + } + , + {"name":"DENY 'scan finance restricted-cf;' for user1", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["read"],"user":"user1","userGroups":["users"],"requestData":"scan finance restricted-cf" + }, + "result":{"accessTypeResults":{"read":{"isAllowed":false,"isAudited":true,"policyId":-1}}} + } + , + {"name":"DENY 'put finance restricted-cf;' for user1", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["write"],"user":"user1","userGroups":["users"],"requestData":"put finance restricted-cf" + }, + "result":{"accessTypeResults":{"write":{"isAllowed":false,"isAudited":true,"policyId":-1}}} + } + , + {"name":"DENY 'create finance restricted-cf;' for user1", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["create"],"user":"user1","userGroups":["users"],"requestData":"create finance restricted-cf" + }, + "result":{"accessTypeResults":{"create":{"isAllowed":false,"isAudited":true,"policyId":-1}}} + } + , + {"name":"DENY 'grant finance restricted-cf;' for user1", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["admin"],"user":"user1","userGroups":["users"],"requestData":"grant finance restricted-cf" + }, + "result":{"accessTypeResults":{"admin":{"isAllowed":false,"isAudited":true,"policyId":-1}}} + } + , + {"name":"ALLOW 'scan finance restricted-cf;' for finance-admin", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["read"],"user":"user1","userGroups":["users","finance-admin"],"requestData":"scan finance restricted-cf" + }, + "result":{"accessTypeResults":{"read":{"isAllowed":true,"isAudited":true,"policyId":2}}} + } + , + {"name":"ALLOW 'put finance restricted-cf;' for finance-admin", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["write"],"user":"user1","userGroups":["users","finance-admin"],"requestData":"put finance restricted-cf" + }, + "result":{"accessTypeResults":{"write":{"isAllowed":true,"isAudited":true,"policyId":2}}} + } + , + {"name":"ALLOW 'create finance restricted-cf;' for finance-admin", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["create"],"user":"user1","userGroups":["users","finance-admin"],"requestData":"create finance restricted-cf" + }, + "result":{"accessTypeResults":{"create":{"isAllowed":true,"isAudited":true,"policyId":2}}} + } + , + {"name":"ALLOW 'grant finance restricted-cf;' for finance-admin", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"restricted-cf"}}, + "accessTypes":["admin"],"user":"user1","userGroups":["users","finance-admin"],"requestData":"grant finance restricted-cf" + }, + "result":{"accessTypeResults":{"admin":{"isAllowed":true,"isAudited":true,"policyId":2}}} + } + , + {"name":"ALLOW 'scan finance regular-cf;' for user1", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"regular-cf"}}, + "accessTypes":["read"],"user":"user1","userGroups":["users"],"requestData":"scan finance regular-cf" + }, + "result":{"accessTypeResults":{"read":{"isAllowed":true,"isAudited":false,"policyId":3}}} + } + , + {"name":"DENY 'put finance regular-cf;' for user1", + "request":{ + "resource":{"elements":{"table":"finance","column-family":"regular-cf"}}, + "accessTypes":["write"],"user":"user1","userGroups":["users"],"requestData":"put finance regular-cf" + }, + "result":{"accessTypeResults":{"write":{"isAllowed":false,"isAudited":false,"policyId":-1}}} + } + ] +} +