/* * 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.lang.StringUtils; 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.RangerAccessResourceImpl; import org.apache.ranger.plugin.policyengine.RangerAccessResult; import org.apache.ranger.plugin.service.RangerBasePlugin; import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.google.common.collect.Sets; 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 RangerBasePlugin _authorizer; // Mutable state: Use supplied state information String _operation; String _otherInformation; String _access; String _table; String _column; String _columnFamily; String _remoteAddress; String _clusterName; User _user; Set<String> _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; boolean _superUser = false; // is this session for a super user? private RangerAccessRequest.ResourceMatchingScope _resourceMatchingScope = RangerAccessRequest.ResourceMatchingScope.SELF; // internal state per-authorization RangerAccessRequest _request; RangerAccessResult _result; public AuthorizationSession(RangerBasePlugin 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 clusterName(String clusterName) { _clusterName = clusterName; return this; } AuthorizationSession user(User aUser) { _user = aUser; if (_user == null) { LOG.warn("AuthorizationSession.user: user is null!"); _groups = null; } else { _groups = _userUtils.getUserGroups(_user); if (_groups.isEmpty() && _user.getUGI() != null) { String[] groups = _user.getUGI().getGroupNames(); if (groups != null) { _groups = Sets.newHashSet(groups); } } _superUser = _userUtils.isSuperUser(_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(); } boolean isNameSpaceOperation() { return StringUtils.equals(_operation, "createNamespace") || StringUtils.equals(_operation, "deleteNamespace") || StringUtils.equals(_operation, "modifyNamespace") || StringUtils.equals(_operation, "setUserNamespaceQuota") || StringUtils.equals(_operation, "setNamespaceQuota"); } AuthorizationSession buildRequest() { verifyBuildable(); // session can be reused so reset its state zapAuthorizationState(); // TODO get this via a factory instead RangerAccessResourceImpl resource = new RangerAccessResourceImpl(); // policy engine should deal sensibly with null/empty values, if any if (isNameSpaceOperation() && StringUtils.isNotBlank(_otherInformation)) { resource.setValue("table", _otherInformation + ":"); } else { resource.setValue("table", _table); } resource.setValue("column-family", _columnFamily); resource.setValue("column", _column); String user = _userUtils.getUserAsString(_user); RangerAccessRequestImpl request = new RangerAccessRequestImpl(resource, _access, user, _groups); request.setAction(_operation); request.setRequestData(_otherInformation); request.setClientIPAddress(_remoteAddress); request.setResourceMatchingScope(_resourceMatchingScope); request.setClusterName(_clusterName); _request = request; if (LOG.isDebugEnabled()) { LOG.debug("Built request: " + request.toString()); } return this; } AuthorizationSession authorize() { if (LOG.isDebugEnabled()) { LOG.debug("==> AuthorizationSession.authorize: " + getRequestMessage()); } 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. if (_auditHandler != null && _superUser) { if (LOG.isDebugEnabled()) { LOG.debug("Setting super-user override on audit handler"); } _auditHandler.setSuperUserOverride(_superUser); } _result = _authorizer.isAccessAllowed(_request, _auditHandler); } if (LOG.isDebugEnabled()) { boolean allowed = isAuthorized(); String reason = getDenialReason(); LOG.debug("<== AuthorizationSession.authorize: " + getLogMessage(allowed, reason)); } return this; } void logCapturedEvents() { if (_auditHandler != null) { List<AuthzAuditEvent> events = _auditHandler.getCapturedEvents(); _auditHandler.logAuthzAudits(events); } } void publishResults() throws AccessDeniedException { if (LOG.isDebugEnabled()) { LOG.debug("==> AuthorizationSession.publishResults()"); } boolean authorized = isAuthorized(); if (_auditHandler != null) { List<AuthzAuditEvent> 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<AuthzAuditEvent> theseEvents = _auditHandler.getCapturedEvents(); if (theseEvents != null && !theseEvents.isEmpty()) { events = theseEvents; } } else { AuthzAuditEvent event = _auditHandler.getAndDiscardMostRecentEvent(); 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 + ")"); } if (LOG.isDebugEnabled()) { LOG.debug("<== AuthorizationSession.publishResults()"); } } 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(); } if (!allowed && _superUser) { if (LOG.isDebugEnabled()) { LOG.debug("User [" + _user + "] is a superUser! Overriding policy engine's decision. Request is deemed authorized!"); } allowed = true; } 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) .add("resource-matching-scope", _resourceMatchingScope) .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; } /** * 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; } AuthorizationSession resourceMatchingScope(RangerAccessRequest.ResourceMatchingScope scope) { _resourceMatchingScope = scope; return this; } }