blob: a4b664bc7998446d8d413af4f8fa2c09734c4c2b [file] [log] [blame]
/*
* 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.sentry.binding.hive.authz;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.HashSet;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hive.common.JavaUtils;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hadoop.hive.metastore.api.FieldSchema;
import org.apache.hadoop.hive.ql.exec.FunctionRegistry;
import org.apache.hadoop.hive.ql.exec.Task;
import org.apache.hadoop.hive.ql.exec.Utilities;
import org.apache.hadoop.hive.ql.hooks.Entity;
import org.apache.hadoop.hive.ql.hooks.Entity.Type;
import org.apache.hadoop.hive.ql.hooks.Hook;
import org.apache.hadoop.hive.ql.hooks.ReadEntity;
import org.apache.hadoop.hive.ql.hooks.WriteEntity;
import org.apache.hadoop.hive.ql.metadata.AuthorizationException;
import org.apache.hadoop.hive.ql.parse.ASTNode;
import org.apache.hadoop.hive.ql.parse.AbstractSemanticAnalyzerHook;
import org.apache.hadoop.hive.ql.parse.BaseSemanticAnalyzer;
import org.apache.hadoop.hive.ql.parse.HiveParser;
import org.apache.hadoop.hive.ql.parse.HiveSemanticAnalyzerHookContext;
import org.apache.hadoop.hive.ql.parse.SemanticException;
import org.apache.hadoop.hive.ql.plan.HiveOperation;
import org.apache.hadoop.hive.ql.plan.PlanUtils;
import org.apache.hadoop.hive.ql.session.SessionState;
import org.apache.sentry.binding.hive.SentryOnFailureHook;
import org.apache.sentry.binding.hive.SentryOnFailureHookContext;
import org.apache.sentry.binding.hive.SentryOnFailureHookContextImpl;
import org.apache.sentry.binding.hive.authz.HiveAuthzPrivileges.HiveOperationScope;
import org.apache.sentry.binding.hive.authz.HiveAuthzPrivileges.HiveOperationType;
import org.apache.sentry.binding.hive.conf.HiveAuthzConf;
import org.apache.sentry.binding.hive.conf.HiveAuthzConf.AuthzConfVars;
import org.apache.sentry.core.common.Subject;
import org.apache.sentry.core.common.exception.SentryGroupNotFoundException;
import org.apache.sentry.core.common.utils.PathUtils;
import org.apache.sentry.core.model.db.AccessURI;
import org.apache.sentry.core.model.db.Column;
import org.apache.sentry.core.model.db.DBModelAction;
import org.apache.sentry.core.model.db.DBModelAuthorizable;
import org.apache.sentry.core.model.db.DBModelAuthorizable.AuthorizableType;
import org.apache.sentry.core.model.db.Database;
import org.apache.sentry.core.model.db.Table;
import org.apache.sentry.policy.common.PrivilegeFactory;
import org.apache.sentry.provider.cache.PrivilegeCache;
import org.apache.sentry.provider.common.AuthorizationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import static org.apache.hadoop.hive.metastore.MetaStoreUtils.DEFAULT_DATABASE_NAME;
public abstract class HiveAuthzBindingHookBase extends AbstractSemanticAnalyzerHook {
private static final Logger LOG = LoggerFactory
.getLogger(HiveAuthzBindingHookBase.class);
protected final HiveAuthzBinding hiveAuthzBinding;
static HiveAuthzConf authzConf;
protected Database currDB = Database.ALL;
protected Table currTab;
protected List<AccessURI> udfURIs;
protected AccessURI serdeURI;
protected AccessURI partitionURI;
protected AccessURI indexURI;
protected Table currOutTab = null;
protected Database currOutDB = null;
protected final List<String> serdeWhiteList;
protected boolean serdeURIPrivilegesEnabled;
protected final static HiveAuthzPrivileges columnMetaDataPrivilege =
new HiveAuthzPrivileges.AuthzPrivilegeBuilder()
.addInputObjectPriviledge(AuthorizableType.Column,
EnumSet.of(DBModelAction.SELECT, DBModelAction.INSERT, DBModelAction.ALTER))
.setOperationScope(HiveOperationScope.COLUMN).setOperationType(HiveOperationType.INFO)
.build();
// True if this is a basic DESCRIBE <table> operation. False for other DESCRIBE variants
// like DESCRIBE [FORMATTED|EXTENDED]. Required because Hive treats these stmts as the same
// HiveOperationType, but we want to enforces different privileges on each statement.
// Basic DESCRIBE <table> is allowed with only column-level privs, while the variants
// require table-level privileges.
protected boolean isDescTableBasic = false;
// Flag that specifies if the operation to validate is an ALTER VIEW AS SELECT.
// Note: Hive sends CREATEVIEW even if ALTER VIEW AS SELECT is used.
protected boolean isAlterViewAs = false;
public HiveAuthzBindingHookBase() throws Exception {
SessionState session = SessionState.get();
if(session == null) {
throw new IllegalStateException("Session has not been started");
}
// HACK: set a random classname to force the Auth V2 in Hive
SessionState.get().setAuthorizer(null);
HiveConf hiveConf = session.getConf();
if(hiveConf == null) {
throw new IllegalStateException("Session HiveConf is null");
}
authzConf = loadAuthzConf(hiveConf);
udfURIs = Lists.newArrayList();
hiveAuthzBinding = new HiveAuthzBinding(hiveConf, authzConf);
String serdeWhiteLists =
authzConf.get(HiveAuthzConf.HIVE_SENTRY_SERDE_WHITELIST,
HiveAuthzConf.HIVE_SENTRY_SERDE_WHITELIST_DEFAULT);
serdeWhiteList = Arrays.asList(serdeWhiteLists.split(","));
serdeURIPrivilegesEnabled =
authzConf.getBoolean(HiveAuthzConf.HIVE_SENTRY_SERDE_URI_PRIVILIEGES_ENABLED,
HiveAuthzConf.HIVE_SENTRY_SERDE_URI_PRIVILIEGES_ENABLED_DEFAULT);
FunctionRegistry.setupPermissionsForBuiltinUDFs("", HiveAuthzConf.HIVE_UDF_BLACK_LIST);
}
public static HiveAuthzConf loadAuthzConf(HiveConf hiveConf) {
boolean depreicatedConfigFile = false;
HiveAuthzConf newAuthzConf = null;
String hiveAuthzConf = hiveConf.get(HiveAuthzConf.HIVE_SENTRY_CONF_URL);
if(hiveAuthzConf == null || (hiveAuthzConf = hiveAuthzConf.trim()).isEmpty()) {
hiveAuthzConf = hiveConf.get(HiveAuthzConf.HIVE_ACCESS_CONF_URL);
depreicatedConfigFile = true;
}
if(hiveAuthzConf == null || (hiveAuthzConf = hiveAuthzConf.trim()).isEmpty()) {
throw new IllegalArgumentException("Configuration key " + HiveAuthzConf.HIVE_SENTRY_CONF_URL
+ " value '" + hiveAuthzConf + "' is invalid.");
}
try {
newAuthzConf = new HiveAuthzConf(new URL(hiveAuthzConf));
} catch (MalformedURLException e) {
if (depreicatedConfigFile) {
throw new IllegalArgumentException("Configuration key " + HiveAuthzConf.HIVE_ACCESS_CONF_URL
+ " specifies a malformed URL '" + hiveAuthzConf + "'", e);
} else {
throw new IllegalArgumentException("Configuration key " + HiveAuthzConf.HIVE_SENTRY_CONF_URL
+ " specifies a malformed URL '" + hiveAuthzConf + "'", e);
}
}
return newAuthzConf;
}
@Override
public abstract ASTNode preAnalyze(HiveSemanticAnalyzerHookContext context, ASTNode ast)
throws SemanticException;
/**
* Post analyze hook that invokes hive auth bindings
*/
@Override
public abstract void postAnalyze(HiveSemanticAnalyzerHookContext context,
List<Task<? extends Serializable>> rootTasks) throws SemanticException;
protected void executeOnFailureHooks(HiveSemanticAnalyzerHookContext context,
HiveOperation hiveOp, AuthorizationException e) {
SentryOnFailureHookContext hookCtx = new SentryOnFailureHookContextImpl(
context.getCommand(), context.getInputs(), context.getOutputs(),
hiveOp, currDB, currTab, udfURIs, null, context.getUserName(),
context.getIpAddress(), e, context.getConf());
String csHooks = authzConf.get(
HiveAuthzConf.AuthzConfVars.AUTHZ_ONFAILURE_HOOKS.getVar(), "").trim();
try {
for (Hook aofh : getHooks(csHooks)) {
((SentryOnFailureHook)aofh).run(hookCtx);
}
} catch (Exception ex) {
LOG.error("Error executing hook:", ex);
}
}
/**
* The command 'create function ... using jar <jar resources>' can create a function
* with the supplied jar resources in the command, which is translated into ASTNode being
* [functionName functionClass resourceList] and resourceList being [resourceType resourcePath].
* This function collects all the jar paths for the supplied jar resources.
*
* @param ast the AST node for the command
* @return the jar path list if any or an empty list
*/
protected List<String> getFunctionJars(ASTNode ast) {
ASTNode resourcesNode = (ASTNode) ast.getFirstChildWithType(HiveParser.TOK_RESOURCE_LIST);
List<String> resources = new ArrayList<String>();
if (resourcesNode != null) {
for (int idx = 0; idx < resourcesNode.getChildCount(); ++idx) {
ASTNode resNode = (ASTNode) resourcesNode.getChild(idx);
ASTNode resTypeNode = (ASTNode) resNode.getChild(0);
ASTNode resUriNode = (ASTNode) resNode.getChild(1);
if (resTypeNode.getType() == HiveParser.TOK_JAR) {
resources.add(PlanUtils.stripQuotes(resUriNode.getText()));
}
}
}
return resources;
}
@VisibleForTesting
protected static AccessURI extractPartition(ASTNode ast) throws SemanticException {
for (int i = 0; i < ast.getChildCount(); i++) {
ASTNode child = (ASTNode)ast.getChild(i);
if (child.getToken().getType() == HiveParser.TOK_PARTITIONLOCATION &&
child.getChildCount() == 1) {
return parseURI(BaseSemanticAnalyzer.
unescapeSQLString(child.getChild(0).getText()));
}
}
return null;
}
@VisibleForTesting
static public AccessURI parseURI(String uri) throws SemanticException {
return parseURI(uri, false);
}
@VisibleForTesting
static public AccessURI parseURI(String uri, boolean isLocal)
throws SemanticException {
try {
HiveConf conf = SessionState.get().getConf();
String warehouseDir = conf.getVar(ConfVars.METASTOREWAREHOUSE);
Path warehousePath = new Path(warehouseDir);
// If warehousePath is an absolute path and a scheme is null and authority is null as well,
// qualified it with default file system scheme and authority.
if (warehousePath.isAbsoluteAndSchemeAuthorityNull()) {
URI defaultUri = FileSystem.getDefaultUri(conf);
warehousePath = warehousePath.makeQualified(defaultUri, warehousePath);
warehouseDir = warehousePath.toUri().toString();
}
return new AccessURI(PathUtils.parseURI(warehouseDir, uri, isLocal));
} catch (Exception e) {
throw new SemanticException("Error parsing URI " + uri + ": " +
e.getMessage(), e);
}
}
// Find the current database for session
protected Database getCanonicalDb() {
return new Database(SessionState.get().getCurrentDatabase());
}
protected void extractDbTableNameFromTOKTABLE(ASTNode astNode) throws SemanticException{
String[] fqTableName = BaseSemanticAnalyzer.getQualifiedTableName(astNode);
Preconditions.checkArgument(fqTableName.length == 2, "BaseSemanticAnalyzer.getQualifiedTableName should return " +
"an array with dbName and tableName");
currOutDB = new Database(fqTableName[0]);
currOutTab = new Table(fqTableName[1]);
}
/*TODO: Deprecate */
protected Database extractDatabase(ASTNode ast) throws SemanticException {
String tableName = BaseSemanticAnalyzer.getUnescapedName(ast);
if (tableName.contains(".")) {
return new Database(tableName.split("\\.")[0]);
} else {
return getCanonicalDb();
}
}
/*TODO: Deprecate */
protected Table extractTable(ASTNode ast) throws SemanticException {
String tableName = BaseSemanticAnalyzer.getUnescapedName(ast);
if (tableName.contains(".")) {
return new Table(tableName.split("\\.")[1]);
} else {
return new Table(tableName);
}
}
protected static AccessURI extractTableLocation(ASTNode ast) throws SemanticException {
ASTNode locationChild = (ASTNode)ast.getFirstChildWithType(HiveParser.TOK_TABLELOCATION);
if (locationChild == null) {
LOG.debug("Token HiveParser.TOK_TABLELOCATION not found in ast. "
+ "This means command does not have a location clause");
return null;
}
if (locationChild.getChildCount() != 1) {
LOG.error("Found Token HiveParser.TOK_TABLELOCATION, but was expecting the URI as its only "
+ "child. This means it is possible that permissions on the URI are not checked for this "
+ "command ");
return null;
}
return parseURI(BaseSemanticAnalyzer.unescapeSQLString(locationChild.getChild(0).getText()));
}
public static void runFailureHook(SentryOnFailureHookContext hookContext,
String csHooks) {
try {
for (Hook aofh : getHooks(csHooks)) {
((SentryOnFailureHook) aofh).run(hookContext);
}
} catch (Exception ex) {
LOG.error("Error executing hook:", ex);
}
}
/**
* Convert the input/output entities into authorizables. generate
* authorizables for cases like Database and metadata operations where the
* compiler doesn't capture entities. invoke the hive binding to validate
* permissions
*
* @param context
* @param stmtAuthObject
* @param stmtOperation
* @throws AuthorizationException
*/
protected void authorizeWithHiveBindings(HiveSemanticAnalyzerHookContext context,
HiveAuthzPrivileges stmtAuthObject, HiveOperation stmtOperation) throws AuthorizationException {
Set<ReadEntity> inputs = context.getInputs();
Set<WriteEntity> outputs = context.getOutputs();
Set<List<DBModelAuthorizable>> inputHierarchy = new HashSet<List<DBModelAuthorizable>>();
Set<List<DBModelAuthorizable>> outputHierarchy = new HashSet<List<DBModelAuthorizable>>();
if(LOG.isDebugEnabled()) {
LOG.debug("context.getInputs() = " + context.getInputs());
LOG.debug("context.getOutputs() = " + context.getOutputs());
}
// Workaround to allow DESCRIBE <table> to be executed with only column-level privileges, while
// still authorizing DESCRIBE [EXTENDED|FORMATTED] as table-level.
// This is done by treating DESCRIBE <table> the same as SHOW COLUMNS, which only requires column
// level privs.
if (isDescTableBasic) {
stmtAuthObject = columnMetaDataPrivilege;
}
switch (stmtAuthObject.getOperationScope()) {
case SERVER :
// validate server level privileges if applicable. Eg create UDF,register jar etc ..
List<DBModelAuthorizable> serverHierarchy = new ArrayList<DBModelAuthorizable>();
serverHierarchy.add(hiveAuthzBinding.getAuthServer());
inputHierarchy.add(serverHierarchy);
break;
case DATABASE:
// workaround for database scope statements (create/alter/drop db)
List<DBModelAuthorizable> dbHierarchy = new ArrayList<DBModelAuthorizable>();
dbHierarchy.add(hiveAuthzBinding.getAuthServer());
dbHierarchy.add(currDB);
inputHierarchy.add(dbHierarchy);
if (currOutDB != null) {
List<DBModelAuthorizable> outputDbHierarchy = new ArrayList<DBModelAuthorizable>();
outputDbHierarchy.add(hiveAuthzBinding.getAuthServer());
outputDbHierarchy.add(currOutDB);
outputHierarchy.add(outputDbHierarchy);
} else {
outputHierarchy.add(dbHierarchy);
}
getInputHierarchyFromInputs(inputHierarchy, inputs);
if (serdeURI != null) {
List<DBModelAuthorizable> serdeUriHierarchy = new ArrayList<DBModelAuthorizable>();
serdeUriHierarchy.add(hiveAuthzBinding.getAuthServer());
serdeUriHierarchy.add(serdeURI);
outputHierarchy.add(serdeUriHierarchy);
}
break;
case TABLE:
// workaround for add partitions
if(partitionURI != null) {
inputHierarchy.add(ImmutableList.of(hiveAuthzBinding.getAuthServer(), partitionURI));
}
if(indexURI != null) {
outputHierarchy.add(ImmutableList.of(hiveAuthzBinding.getAuthServer(), indexURI));
}
getInputHierarchyFromInputs(inputHierarchy, inputs);
for (WriteEntity writeEntity: outputs) {
if (filterWriteEntity(writeEntity)) {
continue;
}
List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
entityHierarchy.add(hiveAuthzBinding.getAuthServer());
entityHierarchy.addAll(getAuthzHierarchyFromEntity(writeEntity));
outputHierarchy.add(entityHierarchy);
}
// workaround for metadata queries.
// Capture the table name in pre-analyze and include that in the input entity list.
// The exception is for ALTERVIEW_AS operations which puts the table to read as input and
// the view as output. Having the view as input again will case extra privileges to be given.
if (currTab != null && stmtOperation != HiveOperation.ALTERVIEW_AS) {
List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
externalAuthorizableHierarchy.add(currDB);
externalAuthorizableHierarchy.add(currTab);
inputHierarchy.add(externalAuthorizableHierarchy);
}
// workaround for DDL statements
// Capture the table name in pre-analyze and include that in the output entity list
if (currOutTab != null) {
List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
externalAuthorizableHierarchy.add(currOutDB);
externalAuthorizableHierarchy.add(currOutTab);
outputHierarchy.add(externalAuthorizableHierarchy);
}
if (serdeURI != null) {
List<DBModelAuthorizable> serdeUriHierarchy = new ArrayList<DBModelAuthorizable>();
serdeUriHierarchy.add(hiveAuthzBinding.getAuthServer());
serdeUriHierarchy.add(serdeURI);
outputHierarchy.add(serdeUriHierarchy);
}
break;
case FUNCTION:
/* The 'FUNCTION' privilege scope currently used for
* - CREATE TEMP FUNCTION
* - DROP TEMP FUNCTION.
*/
if (!udfURIs.isEmpty()) {
List<DBModelAuthorizable> udfUriHierarchy = new ArrayList<DBModelAuthorizable>();
udfUriHierarchy.add(hiveAuthzBinding.getAuthServer());
udfUriHierarchy.addAll(udfURIs);
inputHierarchy.add(udfUriHierarchy);
for (WriteEntity writeEntity : outputs) {
List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
entityHierarchy.add(hiveAuthzBinding.getAuthServer());
entityHierarchy.addAll(getAuthzHierarchyFromEntity(writeEntity));
outputHierarchy.add(entityHierarchy);
}
} else if (currDB != Database.ALL) { //This will be true with DROP FUNCTION commands
List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
entityHierarchy.add(hiveAuthzBinding.getAuthServer());
entityHierarchy.add(currDB);
inputHierarchy.add(entityHierarchy);
}
break;
case CONNECT:
/* The 'CONNECT' is an implicit privilege scope currently used for
* - USE <db>
* It's allowed when the user has any privilege on the current database. For application
* backward compatibility, we allow (optional) implicit connect permission on 'default' db.
*/
List<DBModelAuthorizable> connectHierarchy = new ArrayList<DBModelAuthorizable>();
connectHierarchy.add(hiveAuthzBinding.getAuthServer());
// by default allow connect access to default db
Table currTbl = Table.ALL;
Column currCol = Column.ALL;
if (DEFAULT_DATABASE_NAME.equalsIgnoreCase(currDB.getName()) &&
"false".equalsIgnoreCase(authzConf.
get(HiveAuthzConf.AuthzConfVars.AUTHZ_RESTRICT_DEFAULT_DB.getVar(), "false"))) {
currDB = Database.ALL;
currTbl = Table.SOME;
}
connectHierarchy.add(currDB);
connectHierarchy.add(currTbl);
connectHierarchy.add(currCol);
inputHierarchy.add(connectHierarchy);
outputHierarchy.add(connectHierarchy);
break;
case COLUMN:
for (ReadEntity readEntity: inputs) {
if (readEntity.getAccessedColumns() != null && !readEntity.getAccessedColumns().isEmpty()) {
addColumnHierarchy(inputHierarchy, readEntity);
} else {
List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
entityHierarchy.add(hiveAuthzBinding.getAuthServer());
entityHierarchy.addAll(getAuthzHierarchyFromEntity(readEntity));
entityHierarchy.add(Column.ALL);
inputHierarchy.add(entityHierarchy);
}
}
break;
default:
throw new AuthorizationException("Unknown operation scope type " +
stmtAuthObject.getOperationScope().toString());
}
HiveAuthzBinding binding = null;
try {
binding = getHiveBindingWithPrivilegeCache(hiveAuthzBinding, context.getUserName());
} catch (SemanticException e) {
// Will use the original hiveAuthzBinding
binding = hiveAuthzBinding;
}
// validate permission
binding.authorize(stmtOperation, stmtAuthObject, getCurrentSubject(context), inputHierarchy,
outputHierarchy);
}
// Build the hierarchy of authorizable object for the given entity type.
private List<DBModelAuthorizable> getAuthzHierarchyFromEntity(Entity entity) {
List<DBModelAuthorizable> objectHierarchy = new ArrayList<DBModelAuthorizable>();
switch (entity.getType()) {
case TABLE:
objectHierarchy.add(new Database(entity.getTable().getDbName()));
objectHierarchy.add(new Table(entity.getTable().getTableName()));
break;
case PARTITION:
case DUMMYPARTITION:
objectHierarchy.add(new Database(entity.getPartition().getTable().getDbName()));
objectHierarchy.add(new Table(entity.getPartition().getTable().getTableName()));
break;
case DFS_DIR:
case LOCAL_DIR:
try {
objectHierarchy.add(parseURI(entity.toString(),
entity.getType().equals(Type.LOCAL_DIR)));
} catch (Exception e) {
throw new AuthorizationException("Failed to get File URI", e);
}
break;
case DATABASE:
case FUNCTION:
// TODO use database entities from compiler instead of capturing from AST
break;
default:
throw new UnsupportedOperationException("Unsupported entity type " +
entity.getType().name());
}
return objectHierarchy;
}
/**
* Add column level hierarchy to inputHierarchy
*
* @param inputHierarchy
* @param entity
* @param sentryContext
*/
protected void addColumnHierarchy(Set<List<DBModelAuthorizable>> inputHierarchy,
ReadEntity entity) {
List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
entityHierarchy.add(hiveAuthzBinding.getAuthServer());
entityHierarchy.addAll(getAuthzHierarchyFromEntity(entity));
switch (entity.getType()) {
case TABLE:
case PARTITION:
List<String> cols = entity.getAccessedColumns();
for (String col : cols) {
List<DBModelAuthorizable> colHierarchy = new ArrayList<DBModelAuthorizable>(entityHierarchy);
colHierarchy.add(new Column(col));
inputHierarchy.add(colHierarchy);
}
break;
default:
inputHierarchy.add(entityHierarchy);
}
}
/**
* Get Authorizable from inputs and put into inputHierarchy
*
* @param inputHierarchy
* @param entity
* @param sentryContext
*/
public void getInputHierarchyFromInputs(Set<List<DBModelAuthorizable>> inputHierarchy,
Set<ReadEntity> inputs) {
for (ReadEntity readEntity: inputs) {
// skip the tables/view that are part of expanded view definition
// skip the Hive generated dummy entities created for queries like 'select <expr>'
if (isChildTabForView(readEntity) || isDummyEntity(readEntity)) {
continue;
}
if (readEntity.getAccessedColumns() != null && !readEntity.getAccessedColumns().isEmpty()) {
addColumnHierarchy(inputHierarchy, readEntity);
} else {
List<DBModelAuthorizable> entityHierarchy = new ArrayList<DBModelAuthorizable>();
entityHierarchy.add(hiveAuthzBinding.getAuthServer());
entityHierarchy.addAll(getAuthzHierarchyFromEntity(readEntity));
inputHierarchy.add(entityHierarchy);
}
}
}
// Check if this write entity needs to skipped
private boolean filterWriteEntity(WriteEntity writeEntity)
throws AuthorizationException {
// skip URI validation for session scratch file URIs
if (writeEntity.isTempURI()) {
return true;
}
try {
if (writeEntity.getTyp().equals(Type.DFS_DIR)
|| writeEntity.getTyp().equals(Type.LOCAL_DIR)) {
HiveConf conf = SessionState.get().getConf();
String warehouseDir = conf.getVar(ConfVars.METASTOREWAREHOUSE);
URI scratchURI = new URI(PathUtils.parseDFSURI(warehouseDir,
conf.getVar(ConfVars.SCRATCHDIR)));
URI requestURI = new URI(PathUtils.parseDFSURI(warehouseDir,
writeEntity.getLocation().getPath()));
LOG.debug("scratchURI = " + scratchURI + ", requestURI = " + requestURI);
if (PathUtils.impliesURI(scratchURI, requestURI)) {
return true;
}
URI localScratchURI = new URI(PathUtils.parseLocalURI(conf.getVar(ConfVars.LOCALSCRATCHDIR)));
URI localRequestURI = new URI(PathUtils.parseLocalURI(writeEntity.getLocation().getPath()));
LOG.debug("localScratchURI = " + localScratchURI + ", localRequestURI = " + localRequestURI);
if (PathUtils.impliesURI(localScratchURI, localRequestURI)) {
return true;
}
}
} catch (Exception e) {
throw new AuthorizationException("Failed to extract uri details", e);
}
return false;
}
public static List<String> filterShowTables(
HiveAuthzBinding hiveAuthzBinding, List<String> queryResult,
HiveOperation operation, String userName, String dbName)
throws SemanticException {
List<String> filteredResult = new ArrayList<String>();
Subject subject = new Subject(userName);
HiveAuthzPrivileges tableMetaDataPrivilege = new HiveAuthzPrivileges.AuthzPrivilegeBuilder().
addInputObjectPriviledge(AuthorizableType.Column, EnumSet.of(DBModelAction.SELECT, DBModelAction.INSERT)).
setOperationScope(HiveOperationScope.TABLE).
setOperationType(HiveOperationType.INFO).
build();
HiveAuthzBinding hiveBindingWithPrivilegeCache = getHiveBindingWithPrivilegeCache(hiveAuthzBinding, userName);
for (String tableName : queryResult) {
// if user has privileges on table, add to filtered list, else discard
Table table = new Table(tableName);
Database database;
database = new Database(dbName);
Set<List<DBModelAuthorizable>> inputHierarchy = new HashSet<List<DBModelAuthorizable>>();
Set<List<DBModelAuthorizable>> outputHierarchy = new HashSet<List<DBModelAuthorizable>>();
List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
externalAuthorizableHierarchy.add(database);
externalAuthorizableHierarchy.add(table);
externalAuthorizableHierarchy.add(Column.ALL);
inputHierarchy.add(externalAuthorizableHierarchy);
try {
// do the authorization by new HiveAuthzBinding with PrivilegeCache
hiveBindingWithPrivilegeCache.authorize(operation, tableMetaDataPrivilege, subject,
inputHierarchy, outputHierarchy);
filteredResult.add(table.getName());
} catch (AuthorizationException e) {
// squash the exception, user doesn't have privileges, so the table is
// not added to
// filtered list.
}
}
return filteredResult;
}
public static List<FieldSchema> filterShowColumns(
HiveAuthzBinding hiveAuthzBinding, List<FieldSchema> cols,
HiveOperation operation, String userName, String tableName, String dbName)
throws SemanticException {
List<FieldSchema> filteredResult = new ArrayList<FieldSchema>();
Subject subject = new Subject(userName);
HiveAuthzBinding hiveBindingWithPrivilegeCache = getHiveBindingWithPrivilegeCache(hiveAuthzBinding, userName);
Database database = new Database(dbName);
Table table = new Table(tableName);
for (FieldSchema col : cols) {
// if user has privileges on column, add to filtered list, else discard
Set<List<DBModelAuthorizable>> inputHierarchy = new HashSet<List<DBModelAuthorizable>>();
Set<List<DBModelAuthorizable>> outputHierarchy = new HashSet<List<DBModelAuthorizable>>();
List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
externalAuthorizableHierarchy.add(database);
externalAuthorizableHierarchy.add(table);
externalAuthorizableHierarchy.add(new Column(col.getName()));
inputHierarchy.add(externalAuthorizableHierarchy);
try {
// do the authorization by new HiveAuthzBinding with PrivilegeCache
hiveBindingWithPrivilegeCache.authorize(operation, columnMetaDataPrivilege, subject,
inputHierarchy, outputHierarchy);
filteredResult.add(col);
} catch (AuthorizationException e) {
// squash the exception, user doesn't have privileges, so the column is
// not added to
// filtered list.
}
}
return filteredResult;
}
public static List<String> filterShowDatabases(
HiveAuthzBinding hiveAuthzBinding, List<String> queryResult,
HiveOperation operation, String userName) throws SemanticException {
List<String> filteredResult = new ArrayList<String>();
Subject subject = new Subject(userName);
HiveAuthzBinding hiveBindingWithPrivilegeCache = getHiveBindingWithPrivilegeCache(hiveAuthzBinding, userName);
HiveAuthzPrivileges anyPrivilege = new HiveAuthzPrivileges.AuthzPrivilegeBuilder().
addInputObjectPriviledge(AuthorizableType.Column, EnumSet.of(DBModelAction.SELECT, DBModelAction.INSERT)).
addInputObjectPriviledge(AuthorizableType.URI, EnumSet.of(DBModelAction.SELECT)).
setOperationScope(HiveOperationScope.CONNECT).
setOperationType(HiveOperationType.QUERY).
build();
for (String dbName:queryResult) {
// if user has privileges on database, add to filtered list, else discard
Database database = null;
// if default is not restricted, continue
if (DEFAULT_DATABASE_NAME.equalsIgnoreCase(dbName) && "false".equalsIgnoreCase(
hiveAuthzBinding.getAuthzConf().get(
HiveAuthzConf.AuthzConfVars.AUTHZ_RESTRICT_DEFAULT_DB.getVar(),
"false"))) {
filteredResult.add(DEFAULT_DATABASE_NAME);
continue;
}
database = new Database(dbName);
Set<List<DBModelAuthorizable>> inputHierarchy = new HashSet<List<DBModelAuthorizable>>();
Set<List<DBModelAuthorizable>> outputHierarchy = new HashSet<List<DBModelAuthorizable>>();
List<DBModelAuthorizable> externalAuthorizableHierarchy = new ArrayList<DBModelAuthorizable>();
externalAuthorizableHierarchy.add(hiveAuthzBinding.getAuthServer());
externalAuthorizableHierarchy.add(database);
externalAuthorizableHierarchy.add(Table.ALL);
externalAuthorizableHierarchy.add(Column.ALL);
inputHierarchy.add(externalAuthorizableHierarchy);
try {
// do the authorization by new HiveAuthzBinding with PrivilegeCache
hiveBindingWithPrivilegeCache.authorize(operation, anyPrivilege, subject,
inputHierarchy, outputHierarchy);
filteredResult.add(database.getName());
} catch (AuthorizationException e) {
// squash the exception, user doesn't have privileges, so the table is
// not added to
// filtered list.
}
}
return filteredResult;
}
/**
* Check if the given read entity is a table that has parents of type Table
* Hive compiler performs a query rewrite by replacing view with its definition. In the process, tt captures both
* the original view and the tables/view that it selects from .
* The access authorization is only interested in the top level views and not the underlying tables.
* @param readEntity
* @return
*/
private boolean isChildTabForView(ReadEntity readEntity) {
// If this is a table added for view, then we need to skip that
if (!readEntity.getType().equals(Type.TABLE) && !readEntity.getType().equals(Type.PARTITION)) {
return false;
}
if (readEntity.getParents() != null && readEntity.getParents().size() > 0) {
for (ReadEntity parentEntity : readEntity.getParents()) {
if (!parentEntity.getType().equals(Type.TABLE)) {
return false;
}
}
return true;
} else {
return false;
}
}
/**
* Returns the hooks specified in a configuration variable. The hooks are returned in a list in
* the order they were specified in the configuration variable.
*
* @param hookConfVar The configuration variable specifying a comma separated list of the hook
* class names.
* @return A list of the hooks, in the order they are listed in the value of hookConfVar
* @throws Exception
*/
private static <T extends Hook> List<T> getHooks(String csHooks) throws Exception {
List<T> hooks = new ArrayList<T>();
if (csHooks.isEmpty()) {
return hooks;
}
for (String hookClass : Splitter.on(",").omitEmptyStrings().trimResults().split(csHooks)) {
try {
@SuppressWarnings("unchecked")
T hook =
(T) Class.forName(hookClass, true, JavaUtils.getClassLoader()).newInstance();
hooks.add(hook);
} catch (ClassNotFoundException e) {
LOG.error(hookClass + " Class not found:" + e.getMessage());
throw e;
}
}
return hooks;
}
// Check if the given entity is identified as dummy by Hive compilers.
private boolean isDummyEntity(Entity entity) {
return entity.isDummy();
}
// create hiveBinding with PrivilegeCache
static HiveAuthzBinding getHiveBindingWithPrivilegeCache(HiveAuthzBinding hiveAuthzBinding,
String userName) throws SemanticException {
// get the original HiveAuthzBinding, and get the user's privileges by AuthorizationProvider
AuthorizationProvider authProvider = hiveAuthzBinding.getCurrentAuthProvider();
try {
Set<String> groups;
try {
groups = authProvider.getGroupMapping().getGroups(userName);
} catch (SentryGroupNotFoundException e) {
groups = Collections.emptySet();
LOG.debug("Could not find groups for user: " + userName);
}
Set<String> userPrivileges =
authProvider.getPolicyEngine().getPrivileges(groups, Sets.newHashSet(userName),
hiveAuthzBinding.getActiveRoleSet(), hiveAuthzBinding.getAuthServer());
// create PrivilegeCache using user's privileges
PrivilegeCache privilegeCache = getPrivilegeCache(userPrivileges, hiveAuthzBinding.getPrivilegeFactory());
// create new instance of HiveAuthzBinding whose backend provider should be SimpleCacheProviderBackend
return new HiveAuthzBinding(HiveAuthzBinding.HiveHook.HiveServer2, hiveAuthzBinding.getHiveConf(),
hiveAuthzBinding.getAuthzConf(), privilegeCache);
} catch (Exception e) {
LOG.error("Can not create HiveAuthzBinding with privilege cache.");
throw new SemanticException(e);
}
}
private static PrivilegeCache getPrivilegeCache(Set<String> userPrivileges, PrivilegeFactory inPrivilegeFactory) throws Exception {
String privilegeCacheName = authzConf.get(AuthzConfVars.AUTHZ_PRIVILEGE_CACHE.getVar(),
AuthzConfVars.AUTHZ_PRIVILEGE_CACHE.getDefault());
LOG.info("Using privilege cache " + privilegeCacheName);
try {
// load the privilege cache class that takes privilege factory as input
Constructor<?> cacheConstructor =
Class.forName(privilegeCacheName).getDeclaredConstructor(Set.class, PrivilegeFactory.class);
if (cacheConstructor != null) {
cacheConstructor.setAccessible(true);
return (PrivilegeCache) cacheConstructor.
newInstance(userPrivileges, inPrivilegeFactory);
}
// load the privilege cache class that does not use privilege factory
cacheConstructor = Class.forName(privilegeCacheName).getDeclaredConstructor(Set.class);
cacheConstructor.setAccessible(true);
return (PrivilegeCache) cacheConstructor.
newInstance(userPrivileges);
} catch (Exception ex) {
LOG.error("Exception at creating privilege cache", ex);
throw ex;
}
}
private static boolean hasPrefixMatch(List<String> prefixList, final String str) {
for (String prefix : prefixList) {
if (str.startsWith(prefix)) {
return true;
}
}
return false;
}
/**
* Set the Serde URI privileges. If the URI privileges are not set, which serdeURI will be null,
* the URI authorization checks will be skipped.
*/
protected void setSerdeURI(String serdeClassName) throws SemanticException {
if (!serdeURIPrivilegesEnabled) {
return;
}
// WhiteList Serde Jar can be used by any users. WhiteList checking is
// done by comparing the Java package name. The assumption is cluster
// admin will ensure there is no Java namespace collision.
// e.g org.apache.hadoop.hive.serde2 is used by hive and cluster admin should
// ensure no custom Serde class is introduced under the same namespace.
if (!hasPrefixMatch(serdeWhiteList, serdeClassName)) {
try {
CodeSource serdeSrc =
Class.forName(serdeClassName, true, Utilities.getSessionSpecifiedClassLoader())
.getProtectionDomain().getCodeSource();
if (serdeSrc == null) {
throw new SemanticException("Could not resolve the jar for Serde class " + serdeClassName);
}
String serdeJar = serdeSrc.getLocation().getPath();
if (serdeJar == null || serdeJar.isEmpty()) {
throw new SemanticException("Could not find the jar for Serde class " + serdeClassName
+ "to validate privileges");
}
serdeURI = parseURI(serdeSrc.getLocation().toString(), true);
} catch (ClassNotFoundException e) {
throw new SemanticException("Error retrieving Serde class:" + e.getMessage(), e);
}
}
}
protected Subject getCurrentSubject(HiveSemanticAnalyzerHookContext context) {
// Extract the username from the hook context
return new Subject(context.getUserName());
}
/**
* Returns true if the ASTNode tree is an ALTER VIEW AS SELECT operation.
* <p>
* The ASTNode with an ALTER VIEW AS SELECT is formed as follows:*
* Root: TOK_ALTERVIEW
* Child(0): TOK_TABNAME
* Child(1): TOK_QUERY <-- This is the SELECT operation
*
* @param ast The ASTNode that Hive created while parsing the ALTER VIEW operation.
* @return True if it is an ALTER VIEW AS SELECT operation; False otherwise.
*/
protected boolean isAlterViewAsOperation(ASTNode ast) {
if (ast == null || ast.getToken().getType() != HiveParser.TOK_ALTERVIEW) {
return false;
}
if (ast.getChildCount() <= 1 || ast.getChild(1).getType() != HiveParser.TOK_QUERY) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder strb = new StringBuilder();
strb.append("currDB=").append(currDB).append(", currTab=").append(currTab)
.append(", udfURIs=").append(udfURIs).append(", serdeURI=").append(serdeURI)
.append(", partitionURI=").append(partitionURI).append(", indexURI=").append(indexURI)
.append(", currOutDB=").append(currOutDB).append(", currOutTab=").append(currOutTab)
.append(", serdeWhiteList=").append(serdeWhiteList).append(", serdeURIPrivilegesEnabled=").append(serdeURIPrivilegesEnabled)
.append(", isDescTableBasic=").append(isDescTableBasic);
return strb.toString();
}
}