RangerAccessController.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;
import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.authorizer.ranger.doris.DorisAccessType;
import org.apache.doris.common.AuthorizationException;
import org.apache.doris.mysql.privilege.CatalogAccessController;
import org.apache.doris.mysql.privilege.DataMaskPolicy;
import org.apache.doris.mysql.privilege.RangerDataMaskPolicy;
import org.apache.doris.mysql.privilege.RangerRowFilterPolicy;
import org.apache.doris.mysql.privilege.RowFilterPolicy;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.policyengine.RangerAccessResultProcessor;
import org.apache.ranger.plugin.service.RangerBasePlugin;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public abstract class RangerAccessController implements CatalogAccessController {
private static final Logger LOG = LogManager.getLogger(RangerAccessController.class);
protected static final String CLIENT_TYPE_DORIS = "doris";
protected static boolean checkRequestResult(RangerAccessRequestImpl request,
RangerAccessResult result, String name) {
if (result == null) {
LOG.warn("Error getting authorizer result, please check your ranger config. Make sure "
+ "ranger policy engine is initialized. Request: {}", request);
return false;
}
if (result.getIsAllowed()) {
if (LOG.isDebugEnabled()) {
LOG.debug("request {} match policy {}", request, result.getPolicyId());
}
return true;
} else {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format(
"Permission denied: user [%s] does not have privilege for [%s] command on [%s]",
result.getAccessRequest().getUser(), name,
result.getAccessRequest().getResource().getAsString()));
}
return false;
}
}
public static void checkRequestResults(Collection<RangerAccessResult> results, String name)
throws AuthorizationException {
for (RangerAccessResult result : results) {
if (LOG.isDebugEnabled()) {
LOG.debug("request {} match policy {}", result.getAccessRequest(), result.getPolicyId());
}
if (!result.getIsAllowed()) {
if (LOG.isDebugEnabled()) {
LOG.debug(result.getReason());
}
throw new AuthorizationException(String.format(
"Permission denied: user [%s] does not have privilege for [%s] command on [%s]",
result.getAccessRequest().getUser(), name,
Optional.ofNullable(result.getAccessRequest().getResource().getAsString())
.orElse("unknown resource").replaceAll("/", ".")));
}
}
}
@Override
public List<? extends RowFilterPolicy> evalRowFilterPolicies(UserIdentity currentUser, String ctl, String db,
String tbl) {
RangerAccessResourceImpl resource = createResource(ctl, db, tbl);
RangerAccessRequestImpl request = createRequest(currentUser);
// If the access type is not set here, it defaults to ANY1 ACCESS.
// The internal logic of the ranger is to traverse all permission items.
// Since the ranger UI will set the access type to 'SELECT',
// we will keep it consistent with the UI here to avoid performance issues
request.setAccessType(DorisAccessType.SELECT.name());
request.setResource(resource);
if (LOG.isDebugEnabled()) {
LOG.debug("ranger request: {}", request);
}
List<RangerRowFilterPolicy> res = Lists.newArrayList();
RangerAccessResult policy = getPlugin().evalRowFilterPolicies(request, getAccessResultProcessor());
if (LOG.isDebugEnabled()) {
LOG.debug("ranger response: {}", policy);
}
if (policy == null) {
return res;
}
String filterExpr = policy.getFilterExpr();
if (StringUtils.isEmpty(filterExpr)) {
return res;
}
res.add(new RangerRowFilterPolicy(currentUser, ctl, db, tbl, policy.getPolicyId(), policy.getPolicyVersion(),
filterExpr));
return res;
}
@Override
public Optional<DataMaskPolicy> evalDataMaskPolicy(UserIdentity currentUser, String ctl, String db, String tbl,
String col) {
RangerAccessResourceImpl resource = createResource(ctl, db, tbl, col);
RangerAccessRequestImpl request = createRequest(currentUser);
request.setAccessType(DorisAccessType.SELECT.name());
request.setResource(resource);
if (LOG.isDebugEnabled()) {
LOG.debug("ranger request: {}", request);
}
RangerAccessResult policy = getPlugin().evalDataMaskPolicies(request, getAccessResultProcessor());
if (LOG.isDebugEnabled()) {
LOG.debug("ranger response: {}", policy);
}
if (policy == null) {
return Optional.empty();
}
String maskType = policy.getMaskType();
if (StringUtils.isEmpty(maskType)) {
return Optional.empty();
}
switch (maskType) {
case "MASK_NULL":
return Optional.of(new RangerDataMaskPolicy(currentUser, ctl, db, tbl, col, policy.getPolicyId(),
policy.getPolicyVersion(), maskType, "NULL"));
case "MASK_NONE":
return Optional.empty();
case "CUSTOM":
String maskedValue = policy.getMaskedValue();
if (StringUtils.isEmpty(maskedValue)) {
return Optional.empty();
}
return Optional.of(new RangerDataMaskPolicy(currentUser, ctl, db, tbl, col, policy.getPolicyId(),
policy.getPolicyVersion(), maskType, maskedValue.replace("{col}", col)));
default:
String transformer = policy.getMaskTypeDef().getTransformer();
if (StringUtils.isEmpty(transformer)) {
return Optional.empty();
}
return Optional.of(new RangerDataMaskPolicy(currentUser, ctl, db, tbl, col, policy.getPolicyId(),
policy.getPolicyVersion(), maskType, transformer.replace("{col}", col)));
}
}
protected abstract RangerAccessRequestImpl createRequest(UserIdentity currentUser);
protected abstract RangerAccessResourceImpl createResource(String ctl, String db, String tbl);
protected abstract RangerAccessResourceImpl createResource(String ctl, String db, String tbl, String col);
protected abstract RangerBasePlugin getPlugin();
protected abstract RangerAccessResultProcessor getAccessResultProcessor();
}