RangerHiveAuditHandler.java
// 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.doris.catalog.authorizer.ranger.hive;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.ql.security.authorization.plugin.HiveOperationType;
import org.apache.ranger.audit.model.AuthzAuditEvent;
import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler;
import org.apache.ranger.plugin.model.RangerPolicy;
import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
import org.apache.ranger.plugin.policyengine.RangerAccessResource;
import org.apache.ranger.plugin.policyengine.RangerAccessResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RangerHiveAuditHandler extends RangerDefaultAuditHandler {
public static final String ACCESS_TYPE_ROWFILTER = "ROW_FILTER";
public static final String ACCESS_TYPE_INSERT = "INSERT";
public static final String ACCESS_TYPE_UPDATE = "UPDATE";
public static final String ACCESS_TYPE_DELETE = "DELETE";
public static final String ACCESS_TYPE_TRUNCATE = "TRUNCATE";
public static final String ACTION_TYPE_METADATA_OPERATION = "METADATA OPERATION";
public static final String CONF_AUDIT_QUERY_REQUEST_SIZE = "xasecure.audit.solr.limit.query.req.size";
public static final int DEFAULT_CONF_AUDIT_QUERY_REQUEST_SIZE = Integer.MAX_VALUE;
private static final Logger LOG = LoggerFactory.getLogger(RangerDefaultAuditHandler.class);
private static final Set<String> ROLE_OPS = new HashSet<>();
static {
for (HiveOperationType e : EnumSet.of(HiveOperationType.CREATEROLE, HiveOperationType.DROPROLE,
HiveOperationType.SHOW_ROLES, HiveOperationType.SHOW_ROLE_GRANT, HiveOperationType.SHOW_ROLE_PRINCIPALS,
HiveOperationType.GRANT_ROLE, HiveOperationType.REVOKE_ROLE)) {
ROLE_OPS.add(e.name());
}
}
private final int requestQuerySize;
private final Collection<AuthzAuditEvent> auditEvents = new ArrayList<>();
private boolean deniedExists = false;
public RangerHiveAuditHandler() {
super();
requestQuerySize = DEFAULT_CONF_AUDIT_QUERY_REQUEST_SIZE;
}
public RangerHiveAuditHandler(Configuration config) {
super(config);
int configRequestQuerySize = config.getInt(CONF_AUDIT_QUERY_REQUEST_SIZE,
DEFAULT_CONF_AUDIT_QUERY_REQUEST_SIZE);
requestQuerySize = (configRequestQuerySize < 1) ? DEFAULT_CONF_AUDIT_QUERY_REQUEST_SIZE :
configRequestQuerySize;
}
AuthzAuditEvent createAuditEvent(RangerAccessResult result, String accessType, String resourcePath) {
RangerAccessRequest request = result.getAccessRequest();
RangerAccessResource resource = request.getResource();
String resourceType = resource != null ? resource.getLeafName() : null;
AuthzAuditEvent auditEvent = super.getAuthzEvents(result);
String resourcePathComputed = resourcePath;
if (LOG.isDebugEnabled()) {
LOG.debug("requestQuerySize = " + requestQuerySize);
}
if (StringUtils.isNotBlank(request.getRequestData()) && request.getRequestData().length() > requestQuerySize) {
auditEvent.setRequestData(request.getRequestData().substring(0, requestQuerySize));
} else {
auditEvent.setRequestData(request.getRequestData());
}
auditEvent.setAccessType(accessType);
auditEvent.setResourcePath(resourcePathComputed);
auditEvent.setResourceType("@" + resourceType); // to be consistent with earlier release
if (request instanceof RangerAccessRequestImpl && resource instanceof RangerHiveResource) {
RangerAccessRequestImpl hiveAccessRequest = (RangerAccessRequestImpl) request;
RangerHiveResource hiveResource = (RangerHiveResource) resource;
String hiveAccessType = hiveAccessRequest.getAccessType();
if (HiveAccessType.USE.toString().equalsIgnoreCase(hiveAccessType) && hiveResource.getObjectType()
== HiveObjectType.DATABASE && StringUtils.isBlank(hiveResource.getDatabase())) {
// this should happen only for SHOWDATABASES
auditEvent.setTags(null);
}
}
return auditEvent;
}
AuthzAuditEvent createAuditEvent(RangerAccessResult result) {
final AuthzAuditEvent ret;
RangerAccessRequest request = result.getAccessRequest();
RangerAccessResource resource = request.getResource();
String resourcePath = resource != null ? resource.getAsString() : null;
int policyType = result.getPolicyType();
if (policyType == RangerPolicy.POLICY_TYPE_DATAMASK && result.isMaskEnabled()) {
ret = createAuditEvent(result, result.getMaskType(), resourcePath);
} else if (policyType == RangerPolicy.POLICY_TYPE_ROWFILTER) {
ret = createAuditEvent(result, ACCESS_TYPE_ROWFILTER, resourcePath);
} else if (policyType == RangerPolicy.POLICY_TYPE_ACCESS) {
String accessType = null;
if (request instanceof RangerAccessRequestImpl) {
RangerAccessRequestImpl hiveRequest = (RangerAccessRequestImpl) request;
accessType = hiveRequest.getAccessType();
String action = request.getAction();
if (ACTION_TYPE_METADATA_OPERATION.equals(action)) {
accessType = ACTION_TYPE_METADATA_OPERATION;
} else if (HiveAccessType.UPDATE.toString().equalsIgnoreCase(accessType)) {
String commandStr = request.getRequestData();
if (StringUtils.isNotBlank(commandStr)) {
if (StringUtils.startsWithIgnoreCase(commandStr, ACCESS_TYPE_INSERT)) {
accessType = ACCESS_TYPE_INSERT;
} else if (StringUtils.startsWithIgnoreCase(commandStr, ACCESS_TYPE_UPDATE)) {
accessType = ACCESS_TYPE_UPDATE;
} else if (StringUtils.startsWithIgnoreCase(commandStr, ACCESS_TYPE_DELETE)) {
accessType = ACCESS_TYPE_DELETE;
} else if (StringUtils.startsWithIgnoreCase(commandStr, ACCESS_TYPE_TRUNCATE)) {
accessType = ACCESS_TYPE_TRUNCATE;
}
}
}
}
if (StringUtils.isEmpty(accessType)) {
accessType = request.getAccessType();
}
ret = createAuditEvent(result, accessType, resourcePath);
} else {
ret = null;
}
return ret;
}
List<AuthzAuditEvent> createAuditEvents(Collection<RangerAccessResult> results) {
Map<Long, AuthzAuditEvent> auditEventsMap = new HashMap<>();
Iterator<RangerAccessResult> iterator = results.iterator();
AuthzAuditEvent deniedAuditEvent = null;
while (iterator.hasNext() && deniedAuditEvent == null) {
RangerAccessResult result = iterator.next();
if (result.getIsAudited()) {
if (!result.getIsAllowed()) {
deniedAuditEvent = createAuditEvent(result);
} else {
long policyId = result.getPolicyId();
// add this result to existing event by updating column values
if (auditEventsMap.containsKey(policyId)) {
AuthzAuditEvent auditEvent = auditEventsMap.get(policyId);
RangerAccessRequestImpl request = (RangerAccessRequestImpl) result.getAccessRequest();
RangerHiveResource resource = (RangerHiveResource) request.getResource();
String resourcePath = auditEvent.getResourcePath() + "," + resource.getColumn();
auditEvent.setResourcePath(resourcePath);
Set<String> tags = getTags(request);
if (tags != null) {
auditEvent.getTags().addAll(tags);
}
} else { // new event as this approval was due to a different policy.
AuthzAuditEvent auditEvent = createAuditEvent(result);
if (auditEvent != null) {
auditEventsMap.put(policyId, auditEvent);
}
}
}
}
}
final List<AuthzAuditEvent> result = (deniedAuditEvent == null) ? new ArrayList<>(auditEventsMap.values())
: Collections.singletonList(deniedAuditEvent);
return result;
}
@Override
public void processResult(RangerAccessResult result) {
if (result == null || !result.getIsAudited()) {
return;
}
if (skipFilterOperationAuditing(result)) {
return;
}
AuthzAuditEvent auditEvent = createAuditEvent(result);
if (auditEvent != null) {
addAuthzAuditEvent(auditEvent);
}
}
/**
* This method is expected to be called ONLY to process the results for multiple-columns in a table.
* To ensure this, RangerHiveAccessController should call isAccessAllowed(Collection<requests>) only for this
* condition
*/
@Override
public void processResults(Collection<RangerAccessResult> results) {
List<AuthzAuditEvent> result = createAuditEvents(results);
for (AuthzAuditEvent auditEvent : result) {
addAuthzAuditEvent(auditEvent);
}
}
public void flushAudit() {
for (AuthzAuditEvent auditEvent : auditEvents) {
if (deniedExists && auditEvent.getAccessResult() != 0) { // if deny exists, skip logging for allowed results
continue;
}
super.logAuthzAudit(auditEvent);
}
}
private void addAuthzAuditEvent(AuthzAuditEvent auditEvent) {
if (auditEvent != null) {
auditEvents.add(auditEvent);
if (auditEvent.getAccessResult() == 0) {
deniedExists = true;
}
}
}
private boolean skipFilterOperationAuditing(RangerAccessResult result) {
boolean ret = false;
RangerAccessRequest accessRequest = result.getAccessRequest();
if (accessRequest != null) {
String action = accessRequest.getAction();
if (ACTION_TYPE_METADATA_OPERATION.equals(action) && !result.getIsAllowed()) {
ret = true;
}
}
return ret;
}
}