blob: 3ec2eedab99e7ec66e2dc69b9c8af8eb482cac08 [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.metastore;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.metastore.MetaStoreEventListener;
import org.apache.hadoop.hive.metastore.api.MetaException;
import org.apache.hadoop.hive.metastore.api.Partition;
import org.apache.hadoop.hive.metastore.events.AddPartitionEvent;
import org.apache.hadoop.hive.metastore.events.AlterPartitionEvent;
import org.apache.hadoop.hive.metastore.events.AlterTableEvent;
import org.apache.hadoop.hive.metastore.events.CreateDatabaseEvent;
import org.apache.hadoop.hive.metastore.events.CreateTableEvent;
import org.apache.hadoop.hive.metastore.events.DropDatabaseEvent;
import org.apache.hadoop.hive.metastore.events.DropPartitionEvent;
import org.apache.hadoop.hive.metastore.events.DropTableEvent;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.sentry.core.common.exception.SentryUserException;
import org.apache.sentry.binding.hive.conf.HiveAuthzConf;
import org.apache.sentry.binding.hive.conf.HiveAuthzConf.AuthzConfVars;
import org.apache.sentry.core.common.Authorizable;
import org.apache.sentry.core.model.db.Database;
import org.apache.sentry.core.model.db.Server;
import org.apache.sentry.core.model.db.Table;
import org.apache.sentry.provider.db.SentryMetastoreListenerPlugin;
import org.apache.sentry.provider.db.service.thrift.SentryPolicyServiceClient;
import org.apache.sentry.service.thrift.SentryServiceClientFactory;
import org.apache.sentry.service.thrift.ServiceConstants.ConfUtilties;
import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SentryMetastorePostEventListener class is HMS plugin for listening to
* all DDL events and deliver those events to Sentry server. This class
* sends all DDL events to the Sentry server through thrift API.
*
* In case any actual event fails, skipping deliver the event to Sentry server.
*/
public class SentryMetastorePostEventListener extends MetaStoreEventListener {
private static final Logger LOGGER = LoggerFactory.getLogger(SentryMetastoreListenerPlugin.class);
private final HiveAuthzConf authzConf;
private final Server server;
private List<SentryMetastoreListenerPlugin> sentryPlugins = new ArrayList<SentryMetastoreListenerPlugin>();
public SentryMetastorePostEventListener(Configuration config) {
super(config);
if (!(config instanceof HiveConf)) {
String error = "Could not initialize Plugin - Configuration is not an instanceof HiveConf";
LOGGER.error(error);
throw new RuntimeException(error);
}
authzConf = HiveAuthzConf.getAuthzConf((HiveConf)config);
server = new Server(authzConf.get(AuthzConfVars.AUTHZ_SERVER_NAME.getVar()));
Iterable<String> pluginClasses = ConfUtilties.CLASS_SPLITTER
.split(config.get(ServerConfig.SENTRY_METASTORE_PLUGINS, ServerConfig.SENTRY_METASTORE_PLUGINS_DEFAULT).trim());
try {
for (String pluginClassStr : pluginClasses) {
Class<?> clazz = config.getClassByName(pluginClassStr);
if (!SentryMetastoreListenerPlugin.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException("Class \\"
+ pluginClassStr + "\\ is not a "
+ SentryMetastoreListenerPlugin.class.getName());
}
SentryMetastoreListenerPlugin plugin = (SentryMetastoreListenerPlugin) clazz
.getConstructor(Configuration.class, Configuration.class)
.newInstance(config, authzConf);
sentryPlugins.add(plugin);
}
} catch (Exception e) {
LOGGER.error("Could not initialize HMS Plugin: SentryMetastorePostEventListener !!", e);
throw new RuntimeException(e);
}
}
@Override
public void onCreateTable (CreateTableEvent tableEvent) throws MetaException {
// don't sync paths/privileges if the operation has failed
if (!tableEvent.getStatus()) {
LOGGER.debug("Skip sync paths/privileges with Sentry server for onCreateTable event," +
" since the operation failed. \n");
return;
}
if (tableEvent.getTable().getSd().getLocation() != null) {
String authzObj = tableEvent.getTable().getDbName() + "."
+ tableEvent.getTable().getTableName();
String path = tableEvent.getTable().getSd().getLocation();
for (SentryMetastoreListenerPlugin plugin : sentryPlugins) {
plugin.addPath(authzObj, path);
}
}
// drop the privileges on the given table, in case if anything was left
// behind during the drop
if (!syncWithPolicyStore(AuthzConfVars.AUTHZ_SYNC_CREATE_WITH_POLICY_STORE)) {
return;
}
dropSentryTablePrivilege(tableEvent.getTable().getDbName(),
tableEvent.getTable().getTableName());
}
@Override
public void onDropTable(DropTableEvent tableEvent) throws MetaException {
// don't sync paths/privileges if the operation has failed
if (!tableEvent.getStatus()) {
LOGGER.debug("Skip syncing paths/privileges with Sentry server for onDropTable event," +
" since the operation failed. \n");
return;
}
if (tableEvent.getTable().getSd().getLocation() != null) {
String authzObj = tableEvent.getTable().getDbName() + "."
+ tableEvent.getTable().getTableName();
for (SentryMetastoreListenerPlugin plugin : sentryPlugins) {
plugin.removeAllPaths(authzObj, null);
}
}
// drop the privileges on the given table
if (!syncWithPolicyStore(AuthzConfVars.AUTHZ_SYNC_DROP_WITH_POLICY_STORE)) {
return;
}
if (!tableEvent.getStatus()) {
return;
}
dropSentryTablePrivilege(tableEvent.getTable().getDbName(),
tableEvent.getTable().getTableName());
}
@Override
public void onCreateDatabase(CreateDatabaseEvent dbEvent)
throws MetaException {
// don't sync paths/privileges if the operation has failed
if (!dbEvent.getStatus()) {
LOGGER.debug("Skip syncing paths/privileges with Sentry server for onCreateDatabase event," +
" since the operation failed. \n");
return;
}
if (dbEvent.getDatabase().getLocationUri() != null) {
String authzObj = dbEvent.getDatabase().getName();
String path = dbEvent.getDatabase().getLocationUri();
for (SentryMetastoreListenerPlugin plugin : sentryPlugins) {
plugin.addPath(authzObj, path);
}
}
// drop the privileges on the database, in case anything left behind during
// last drop db
if (!syncWithPolicyStore(AuthzConfVars.AUTHZ_SYNC_CREATE_WITH_POLICY_STORE)) {
return;
}
dropSentryDbPrivileges(dbEvent.getDatabase().getName());
}
/**
* Drop the privileges on the database. Note that child tables will be
* dropped individually by client, so we just need to handle the removing
* the db privileges. The table drop should cleanup the table privileges.
*/
@Override
public void onDropDatabase(DropDatabaseEvent dbEvent) throws MetaException {
// don't sync paths/privileges if the operation has failed
if (!dbEvent.getStatus()) {
LOGGER.debug("Skip syncing paths/privileges with Sentry server for onDropDatabase event," + " since the operation failed. \n");
return;
}
String authzObj = dbEvent.getDatabase().getName();
for (SentryMetastoreListenerPlugin plugin : sentryPlugins) {
List<String> tNames = dbEvent.getHandler().get_all_tables(authzObj);
plugin.removeAllPaths(authzObj, tNames);
}
if (!syncWithPolicyStore(AuthzConfVars.AUTHZ_SYNC_DROP_WITH_POLICY_STORE)) {
return;
}
dropSentryDbPrivileges(dbEvent.getDatabase().getName());
}
/**
* Adjust the privileges when table is renamed
*/
@Override
public void onAlterTable (AlterTableEvent tableEvent) throws MetaException {
// don't sync privileges if the operation has failed
if (!tableEvent.getStatus()) {
LOGGER.debug("Skip syncing privileges with Sentry server for onAlterTable event," +
" since the operation failed. \n");
return;
}
String oldLoc = null, newLoc = null;
org.apache.hadoop.hive.metastore.api.Table oldTal = tableEvent.getOldTable();
org.apache.hadoop.hive.metastore.api.Table newTal = tableEvent.getNewTable();
if(oldTal != null && oldTal.getSd() !=null) {
oldLoc = oldTal.getSd().getLocation();
}
if (newTal != null && newTal.getSd() != null) {
newLoc = newTal.getSd().getLocation();
}
if(oldLoc != null && newLoc != null && !oldLoc.equals(newLoc)) {
String oldDbName = tableEvent.getOldTable().getDbName();
String oldTbName = tableEvent.getOldTable().getTableName();
String newTbName = tableEvent.getNewTable().getTableName();
String newDbName = tableEvent.getNewTable().getDbName();
renameSentryTablePrivilege(oldDbName, oldTbName, oldLoc, newDbName, newTbName, newLoc);
}
}
@Override
public void onAlterPartition(AlterPartitionEvent partitionEvent)
throws MetaException {
// don't sync privileges if the operation has failed
if (!partitionEvent.getStatus()) {
LOGGER.debug("Skip syncing privileges with Sentry server for onAlterPartition event," +
" since the operation failed. \n");
return;
}
String oldLoc = null, newLoc = null;
if (partitionEvent.getOldPartition() != null) {
oldLoc = partitionEvent.getOldPartition().getSd().getLocation();
}
if (partitionEvent.getNewPartition() != null) {
newLoc = partitionEvent.getNewPartition().getSd().getLocation();
}
if (oldLoc != null && newLoc != null && !oldLoc.equals(newLoc)) {
String authzObj =
partitionEvent.getOldPartition().getDbName() + "."
+ partitionEvent.getOldPartition().getTableName();
for (SentryMetastoreListenerPlugin plugin : sentryPlugins) {
plugin.renameAuthzObject(authzObj, oldLoc,
authzObj, newLoc);
}
}
}
@Override
public void onAddPartition(AddPartitionEvent partitionEvent)
throws MetaException {
// don't sync path if the operation has failed
if (!partitionEvent.getStatus()) {
LOGGER.debug("Skip syncing path with Sentry server for onAddPartition event," + " since the operation failed. \n");
return;
}
for (Partition part : partitionEvent.getPartitions()) {
if (part.getSd() != null && part.getSd().getLocation() != null) {
String authzObj = part.getDbName() + "." + part.getTableName();
String path = part.getSd().getLocation();
for (SentryMetastoreListenerPlugin plugin : sentryPlugins) {
plugin.addPath(authzObj, path);
}
}
}
super.onAddPartition(partitionEvent);
}
@Override
public void onDropPartition(DropPartitionEvent partitionEvent)
throws MetaException {
// don't sync path if the operation has failed
if (!partitionEvent.getStatus()) {
LOGGER.debug("Skip syncing path with Sentry server for onDropPartition event," +
" since the operation failed. \n");
return;
}
String authzObj = partitionEvent.getTable().getDbName() + "."
+ partitionEvent.getTable().getTableName();
String path = partitionEvent.getPartition().getSd().getLocation();
for (SentryMetastoreListenerPlugin plugin : sentryPlugins) {
plugin.removePath(authzObj, path);
}
super.onDropPartition(partitionEvent);
}
private SentryPolicyServiceClient getSentryServiceClient()
throws MetaException {
try {
return SentryServiceClientFactory.create(authzConf);
} catch (Exception e) {
throw new MetaException("Failed to connect to Sentry service "
+ e.getMessage());
}
}
private void dropSentryDbPrivileges(String dbName) throws MetaException {
List<Authorizable> authorizableTable = new ArrayList<Authorizable>();
authorizableTable.add(server);
authorizableTable.add(new Database(dbName));
try {
dropSentryPrivileges(authorizableTable);
} catch (SentryUserException e) {
throw new MetaException("Failed to remove Sentry policies for drop DB "
+ dbName + " Error: " + e.getMessage());
} catch (IOException e) {
throw new MetaException("Failed to find local user " + e.getMessage());
}
}
private void dropSentryTablePrivilege(String dbName, String tabName)
throws MetaException {
List<Authorizable> authorizableTable = new ArrayList<Authorizable>();
authorizableTable.add(server);
authorizableTable.add(new Database(dbName));
authorizableTable.add(new Table(tabName));
try {
dropSentryPrivileges(authorizableTable);
} catch (SentryUserException e) {
throw new MetaException(
"Failed to remove Sentry policies for drop table " + dbName + "."
+ tabName + " Error: " + e.getMessage());
} catch (IOException e) {
throw new MetaException("Failed to find local user " + e.getMessage());
}
}
private void dropSentryPrivileges(
List<? extends Authorizable> authorizableTable)
throws SentryUserException, IOException, MetaException {
String requestorUserName = UserGroupInformation.getCurrentUser()
.getShortUserName();
try (SentryPolicyServiceClient sentryClient = SentryServiceClientFactory.create(authzConf)) {
sentryClient.dropPrivileges(requestorUserName, authorizableTable);
} catch (Exception e) {
throw new MetaException("Failed to connect to Sentry service "
+ e.getMessage());
}
}
private void renameSentryTablePrivilege(String oldDbName, String oldTabName,
String oldPath, String newDbName, String newTabName, String newPath)
throws MetaException {
List<Authorizable> oldAuthorizableTable = new ArrayList<Authorizable>();
oldAuthorizableTable.add(server);
oldAuthorizableTable.add(new Database(oldDbName));
oldAuthorizableTable.add(new Table(oldTabName));
List<Authorizable> newAuthorizableTable = new ArrayList<Authorizable>();
newAuthorizableTable.add(server);
newAuthorizableTable.add(new Database(newDbName));
newAuthorizableTable.add(new Table(newTabName));
if (!oldTabName.equalsIgnoreCase(newTabName)
&& syncWithPolicyStore(AuthzConfVars.AUTHZ_SYNC_ALTER_WITH_POLICY_STORE)) {
SentryPolicyServiceClient sentryClient = getSentryServiceClient();
try {
String requestorUserName = UserGroupInformation.getCurrentUser()
.getShortUserName();
sentryClient.renamePrivileges(requestorUserName, oldAuthorizableTable, newAuthorizableTable);
} catch (SentryUserException e) {
throw new MetaException(
"Failed to remove Sentry policies for rename table " + oldDbName
+ "." + oldTabName + "to " + newDbName + "." + newTabName
+ " Error: " + e.getMessage());
} catch (IOException e) {
throw new MetaException("Failed to find local user " + e.getMessage());
} finally {
// Close the connection after renaming privileges is done.
try {
sentryClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// The HDFS plugin needs to know if it's a path change (set location)
for (SentryMetastoreListenerPlugin plugin : sentryPlugins) {
plugin.renameAuthzObject(oldDbName + "." + oldTabName, oldPath,
newDbName + "." + newTabName, newPath);
}
}
private boolean syncWithPolicyStore(AuthzConfVars syncConfVar) {
return Boolean.parseBoolean(authzConf.get(syncConfVar.getVar(), "true"));
}
}