blob: cbafa0a164d2ebe820db553b96621486dd3f433a [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.hcatalog.security;
import static org.apache.hadoop.hive.metastore.MetaStoreUtils.DEFAULT_DATABASE_NAME;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.EnumSet;
import java.util.List;
import javax.security.auth.login.LoginException;
import org.apache.commons.lang.ArrayUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hive.metastore.Warehouse;
import org.apache.hadoop.hive.metastore.api.Database;
import org.apache.hadoop.hive.metastore.api.MetaException;
import org.apache.hadoop.hive.ql.metadata.AuthorizationException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.metadata.Partition;
import org.apache.hadoop.hive.ql.metadata.Table;
import org.apache.hadoop.hive.ql.security.authorization.HiveAuthorizationProviderBase;
import org.apache.hadoop.hive.ql.security.authorization.Privilege;
import org.apache.hadoop.hive.shims.HadoopShims;
import org.apache.hadoop.hive.shims.ShimLoader;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
/**
* An AuthorizationProvider, which checks against the data access level permissions on HDFS.
* It makes sense to eventually move this class to Hive, so that all hive users can
* use this authorization model.
*/
public class HdfsAuthorizationProvider extends HiveAuthorizationProviderBase {
protected Warehouse wh;
//Config variables : create an enum to store them if we have more
private static final String PROXY_USER_NAME = "proxy.user.name";
public HdfsAuthorizationProvider() {
super();
}
public HdfsAuthorizationProvider(Configuration conf) {
super();
setConf(conf);
}
@Override
public void setConf(Configuration conf) {
super.setConf(conf);
try {
this.wh = new Warehouse(conf);
} catch (MetaException ex) {
throw new RuntimeException(ex);
}
}
protected FsAction getFsAction(Privilege priv, Path path) {
switch (priv.getPriv()) {
case ALL:
throw new AuthorizationException("no matching Action for Privilege.All");
case ALTER_DATA:
return FsAction.WRITE;
case ALTER_METADATA:
return FsAction.WRITE;
case CREATE:
return FsAction.WRITE;
case DROP:
return FsAction.WRITE;
case INDEX:
return FsAction.WRITE;
case LOCK:
return FsAction.WRITE;
case SELECT:
return FsAction.READ;
case SHOW_DATABASE:
return FsAction.READ;
case UNKNOWN:
default:
throw new AuthorizationException("Unknown privilege");
}
}
protected EnumSet<FsAction> getFsActions(Privilege[] privs, Path path) {
EnumSet<FsAction> actions = EnumSet.noneOf(FsAction.class);
if (privs == null) {
return actions;
}
for (Privilege priv : privs) {
actions.add(getFsAction(priv, path));
}
return actions;
}
private static final String DATABASE_WAREHOUSE_SUFFIX = ".db";
private Path getDefaultDatabasePath(String dbName) throws MetaException {
if (dbName.equalsIgnoreCase(DEFAULT_DATABASE_NAME)) {
return wh.getWhRoot();
}
return new Path(wh.getWhRoot(), dbName.toLowerCase() + DATABASE_WAREHOUSE_SUFFIX);
}
protected Path getDbLocation(Database db) throws HiveException {
try {
String location = db.getLocationUri();
if (location == null) {
return getDefaultDatabasePath(db.getName());
} else {
return wh.getDnsPath(wh.getDatabasePath(db));
}
} catch (MetaException ex) {
throw new HiveException(ex.getMessage());
}
}
@Override
public void authorize(Privilege[] readRequiredPriv, Privilege[] writeRequiredPriv)
throws HiveException, AuthorizationException {
//Authorize for global level permissions at the warehouse dir
Path root;
try {
root = wh.getWhRoot();
authorize(root, readRequiredPriv, writeRequiredPriv);
} catch (MetaException ex) {
throw new HiveException(ex);
}
}
@Override
public void authorize(Database db, Privilege[] readRequiredPriv, Privilege[] writeRequiredPriv)
throws HiveException, AuthorizationException {
if (db == null) {
return;
}
Path path = getDbLocation(db);
authorize(path, readRequiredPriv, writeRequiredPriv);
}
@Override
public void authorize(Table table, Privilege[] readRequiredPriv, Privilege[] writeRequiredPriv)
throws HiveException, AuthorizationException {
if (table == null) {
return;
}
//unlike Hive's model, this can be called at CREATE TABLE as well, since we should authorize
//against the table's declared location
Path path = null;
try {
if (table.getTTable().getSd().getLocation() == null
|| table.getTTable().getSd().getLocation().isEmpty()) {
path = wh.getTablePath(hive_db.getDatabase(table.getDbName()), table.getTableName());
} else {
path = table.getPath();
}
} catch (MetaException ex) {
throw new HiveException(ex);
}
authorize(path, readRequiredPriv, writeRequiredPriv);
}
//TODO: HiveAuthorizationProvider should expose this interface instead of #authorize(Partition, Privilege[], Privilege[])
public void authorize(Table table, Partition part, Privilege[] readRequiredPriv, Privilege[] writeRequiredPriv)
throws HiveException, AuthorizationException {
if (part == null || part.getLocation() == null) {
authorize(table, readRequiredPriv, writeRequiredPriv);
} else {
authorize(part.getPartitionPath(), readRequiredPriv, writeRequiredPriv);
}
}
@Override
public void authorize(Partition part, Privilege[] readRequiredPriv, Privilege[] writeRequiredPriv)
throws HiveException, AuthorizationException {
if (part == null) {
return;
}
authorize(part.getTable(), part, readRequiredPriv, writeRequiredPriv);
}
@Override
public void authorize(Table table, Partition part, List<String> columns,
Privilege[] readRequiredPriv, Privilege[] writeRequiredPriv) throws HiveException,
AuthorizationException {
//columns cannot live in different files, just check for partition level permissions
authorize(table, part, readRequiredPriv, writeRequiredPriv);
}
/**
* Authorization privileges against a path.
* @param path a filesystem path
* @param readRequiredPriv a list of privileges needed for inputs.
* @param writeRequiredPriv a list of privileges needed for outputs.
*/
public void authorize(Path path, Privilege[] readRequiredPriv, Privilege[] writeRequiredPriv)
throws HiveException, AuthorizationException {
try {
EnumSet<FsAction> actions = getFsActions(readRequiredPriv, path);
actions.addAll(getFsActions(writeRequiredPriv, path));
if (actions.isEmpty()) {
return;
}
checkPermissions(getConf(), path, actions);
} catch (AccessControlException ex) {
throw new AuthorizationException(ex);
} catch (LoginException ex) {
throw new AuthorizationException(ex);
} catch (IOException ex) {
throw new HiveException(ex);
}
}
/**
* Checks the permissions for the given path and current user on Hadoop FS. If the given path
* does not exists, it checks for it's parent folder.
*/
protected static void checkPermissions(final Configuration conf, final Path path,
final EnumSet<FsAction> actions) throws IOException, LoginException {
if (path == null) {
throw new IllegalArgumentException("path is null");
}
HadoopShims shims = ShimLoader.getHadoopShims();
final UserGroupInformation ugi;
if (conf.get(PROXY_USER_NAME) != null) {
ugi = UserGroupInformation.createRemoteUser(conf.get(PROXY_USER_NAME));
} else {
ugi = shims.getUGIForConf(conf);
}
final String user = shims.getShortUserName(ugi);
final FileSystem fs = path.getFileSystem(conf);
if (fs.exists(path)) {
checkPermissions(fs, path, actions, user, ugi.getGroupNames());
} else if (path.getParent() != null) {
// find the ancestor which exists to check it's permissions
Path par = path.getParent();
while (par != null) {
if (fs.exists(par)) {
break;
}
par = par.getParent();
}
checkPermissions(fs, par, actions, user, ugi.getGroupNames());
}
}
/**
* Checks the permissions for the given path and current user on Hadoop FS. If the given path
* does not exists, it returns.
*/
@SuppressWarnings("deprecation")
protected static void checkPermissions(final FileSystem fs, final Path path,
final EnumSet<FsAction> actions, String user, String[] groups) throws IOException,
AccessControlException {
final FileStatus stat;
try {
stat = fs.getFileStatus(path);
} catch (FileNotFoundException fnfe) {
// File named by path doesn't exist; nothing to validate.
return;
} catch (org.apache.hadoop.fs.permission.AccessControlException ace) {
// Older hadoop version will throw this @deprecated Exception.
throw new AccessControlException(ace.getMessage());
}
final FsPermission dirPerms = stat.getPermission();
final String grp = stat.getGroup();
for (FsAction action : actions) {
if (user.equals(stat.getOwner())) {
if (dirPerms.getUserAction().implies(action)) {
continue;
}
}
if (ArrayUtils.contains(groups, grp)) {
if (dirPerms.getGroupAction().implies(action)) {
continue;
}
}
if (dirPerms.getOtherAction().implies(action)) {
continue;
}
throw new AccessControlException("action " + action + " not permitted on path "
+ path + " for user " + user);
}
}
}