| /** |
| * 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); |
| } |
| } |
| } |