/********************************* Master related hooks **********************************/ @Override public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException { // Need to create the new system table for labels here MasterServices master = ctx.getEnvironment().getMasterServices(); if (!MetaTableAccessor.tableExists(master.getConnection(), LABELS_TABLE_NAME)) { HTableDescriptor labelsTable = new HTableDescriptor(LABELS_TABLE_NAME); HColumnDescriptor labelsColumn = new HColumnDescriptor(LABELS_TABLE_FAMILY); labelsColumn.setBloomFilterType(BloomType.NONE); labelsColumn.setBlockCacheEnabled(false); // We will cache all the labels. No need of normal // table block cache. labelsTable.addFamily(labelsColumn); // Let the "labels" table having only one region always. We are not expecting too many labels in // the system. labelsTable.setValue(HTableDescriptor.SPLIT_POLICY, DisabledRegionSplitPolicy.class.getName()); labelsTable.setValue(Bytes.toBytes(HConstants.DISALLOW_WRITES_IN_RECOVERING), Bytes.toBytes(true)); master.createTable(labelsTable, null, HConstants.NO_NONCE, HConstants.NO_NONCE); } }
@Override public void start(CoprocessorEnvironment env) throws IOException { this.conf = env.getConfiguration(); authorizationEnabled = isAuthorizationSupported(conf); if (!authorizationEnabled) { LOG.warn("The VisibilityController has been loaded with authorization checks disabled."); } if (HFile.getFormatVersion(conf) < HFile.MIN_FORMAT_VERSION_WITH_TAGS) { throw new RuntimeException("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS + " is required to persist visibility labels. Consider setting " + HFile.FORMAT_VERSION_KEY + " accordingly."); } if (env instanceof RegionServerCoprocessorEnvironment) { throw new RuntimeException("Visibility controller should not be configured as " + "'hbase.coprocessor.regionserver.classes'."); } // Do not create for master CPs if (!(env instanceof MasterCoprocessorEnvironment)) { visibilityLabelService = VisibilityLabelServiceManager.getInstance() .getVisibilityLabelService(this.conf); } }
@Override public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> c, final TableName tableName) throws IOException { requirePermission("truncateTable", tableName, null, null, Action.ADMIN, Action.CREATE); final Configuration conf = c.getEnvironment().getConfiguration(); User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { @Override public Void run() throws Exception { List<UserPermission> acls = AccessControlLists.getUserTablePermissions(conf, tableName); if (acls != null) { tableAcls.put(tableName, acls); } return null; } }); }
@Override public void postTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx, final TableName tableName) throws IOException { final Configuration conf = ctx.getEnvironment().getConfiguration(); User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { @Override public Void run() throws Exception { List<UserPermission> perms = tableAcls.get(tableName); if (perms != null) { for (UserPermission perm : perms) { AccessControlLists.addUserPermission(conf, perm); } } tableAcls.remove(tableName); return null; } }); }
@Override public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName, final HTableDescriptor htd) throws IOException { final Configuration conf = c.getEnvironment().getConfiguration(); // default the table owner to current user, if not specified. final String owner = (htd.getOwnerString() != null) ? htd.getOwnerString() : getActiveUser().getShortName(); User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { @Override public Void run() throws Exception { UserPermission userperm = new UserPermission(Bytes.toBytes(owner), htd.getTableName(), null, Action.values()); AccessControlLists.addUserPermission(conf, userperm); return null; } }); }
@Override public void postListProcedures( ObserverContext<MasterCoprocessorEnvironment> ctx, List<ProcedureInfo> procInfoList) throws IOException { if (procInfoList.isEmpty()) { return; } // Retains only those which passes authorization checks, as the checks weren't done as part // of preListProcedures. Iterator<ProcedureInfo> itr = procInfoList.iterator(); User user = getActiveUser(); while (itr.hasNext()) { ProcedureInfo procInfo = itr.next(); try { if (!ProcedureInfo.isProcedureOwner(procInfo, user)) { // If the user is not the procedure owner, then we should further probe whether // he can see the procedure. requirePermission("listProcedures", Action.ADMIN); } } catch (AccessDeniedException e) { itr.remove(); } } }
@Override public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx, List<TableName> tableNamesList, List<HTableDescriptor> descriptors, String regex) throws IOException { // We are delegating the authorization check to postGetTableDescriptors as we don't have // any concrete set of table names when a regex is present or the full list is requested. if (regex == null && tableNamesList != null && !tableNamesList.isEmpty()) { // Otherwise, if the requestor has ADMIN or CREATE privs for all listed tables, the // request can be granted. MasterServices masterServices = ctx.getEnvironment().getMasterServices(); for (TableName tableName: tableNamesList) { // Skip checks for a table that does not exist if (masterServices.getTableDescriptors().get(tableName) == null) { continue; } requirePermission("getTableDescriptors", tableName, null, null, Action.ADMIN, Action.CREATE); } } }
@Override public void postGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx, List<TableName> tableNamesList, List<HTableDescriptor> descriptors, String regex) throws IOException { // Skipping as checks in this case are already done by preGetTableDescriptors. if (regex == null && tableNamesList != null && !tableNamesList.isEmpty()) { return; } // Retains only those which passes authorization checks, as the checks weren't done as part // of preGetTableDescriptors. Iterator<HTableDescriptor> itr = descriptors.iterator(); while (itr.hasNext()) { HTableDescriptor htd = itr.next(); try { requirePermission("getTableDescriptors", htd.getTableName(), null, null, Action.ADMIN, Action.CREATE); } catch (AccessDeniedException e) { itr.remove(); } } }
@Override public void start(CoprocessorEnvironment env) throws IOException { this.conf = env.getConfiguration(); if (HFile.getFormatVersion(conf) < HFile.MIN_FORMAT_VERSION_WITH_TAGS) { throw new RuntimeException("A minimum HFile version of " + HFile.MIN_FORMAT_VERSION_WITH_TAGS + " is required to persist visibility labels. Consider setting " + HFile.FORMAT_VERSION_KEY + " accordingly."); } if (env instanceof RegionServerCoprocessorEnvironment) { throw new RuntimeException("Visibility controller should not be configured as " + "'hbase.coprocessor.regionserver.classes'."); } // Do not create for master CPs if (!(env instanceof MasterCoprocessorEnvironment)) { visibilityLabelService = VisibilityLabelServiceManager.getInstance() .getVisibilityLabelService(this.conf); } Pair<List<String>, List<String>> superUsersAndGroups = VisibilityUtils.getSystemAndSuperUsers(this.conf); this.superUsers = superUsersAndGroups.getFirst(); this.superGroups = superUsersAndGroups.getSecond(); }
/********************************* Master related hooks **********************************/ @Override public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException { // Need to create the new system table for labels here MasterServices master = ctx.getEnvironment().getMasterServices(); if (!MetaTableAccessor.tableExists(master.getConnection(), LABELS_TABLE_NAME)) { HTableDescriptor labelsTable = new HTableDescriptor(LABELS_TABLE_NAME); HColumnDescriptor labelsColumn = new HColumnDescriptor(LABELS_TABLE_FAMILY); labelsColumn.setBloomFilterType(BloomType.NONE); labelsColumn.setBlockCacheEnabled(false); // We will cache all the labels. No need of normal // table block cache. labelsTable.addFamily(labelsColumn); // Let the "labels" table having only one region always. We are not expecting too many labels in // the system. labelsTable.setValue(HTableDescriptor.SPLIT_POLICY, DisabledRegionSplitPolicy.class.getName()); labelsTable.setValue(Bytes.toBytes(HConstants.DISALLOW_WRITES_IN_RECOVERING), Bytes.toBytes(true)); master.createTable(labelsTable, null); } }
/********************************* Master related hooks **********************************/ @Override public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException { // Need to create the new system table for labels here MasterServices master = ctx.getEnvironment().getMasterServices(); if (!MetaReader.tableExists(master.getCatalogTracker(), LABELS_TABLE_NAME)) { HTableDescriptor labelsTable = new HTableDescriptor(LABELS_TABLE_NAME); HColumnDescriptor labelsColumn = new HColumnDescriptor(LABELS_TABLE_FAMILY); labelsColumn.setBloomFilterType(BloomType.NONE); labelsColumn.setBlockCacheEnabled(false); // We will cache all the labels. No need of normal // table block cache. labelsTable.addFamily(labelsColumn); // Let the "labels" table having only one region always. We are not expecting too many labels in // the system. labelsTable.setValue(HTableDescriptor.SPLIT_POLICY, DisabledRegionSplitPolicy.class.getName()); labelsTable.setValue(Bytes.toBytes(HConstants.DISALLOW_WRITES_IN_RECOVERING), Bytes.toBytes(true)); master.createTable(labelsTable, null); } }
@Override public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName, HTableDescriptor htd) throws IOException { if (!authorizationEnabled) { return; } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME); } }
@Override public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName, HColumnDescriptor column) throws IOException { if (!authorizationEnabled) { return; } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME); } }
@Override public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName, HColumnDescriptor descriptor) throws IOException { if (!authorizationEnabled) { return; } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME); } }
@Override public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName, byte[] c) throws IOException { if (!authorizationEnabled) { return; } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot alter " + LABELS_TABLE_NAME); } }
@Override public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName) throws IOException { if (!authorizationEnabled) { return; } if (LABELS_TABLE_NAME.equals(tableName)) { throw new ConstraintException("Cannot disable " + LABELS_TABLE_NAME); } }
@Override public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> c, HTableDescriptor desc, HRegionInfo[] regions) throws IOException { Set<byte[]> families = desc.getFamiliesKeys(); Map<byte[], Set<byte[]>> familyMap = new TreeMap<byte[], Set<byte[]>>(Bytes.BYTES_COMPARATOR); for (byte[] family: families) { familyMap.put(family, null); } requireNamespacePermission("createTable", desc.getTableName().getNamespaceAsString(), desc.getTableName(), familyMap, Action.CREATE); }
@Override public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> c, final TableName tableName) throws IOException { final Configuration conf = c.getEnvironment().getConfiguration(); User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { @Override public Void run() throws Exception { AccessControlLists.removeTablePermissions(conf, tableName); return null; } }); this.authManager.getZKPermissionWatcher().deleteTableACLNode(tableName); }
@Override public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> c, final TableName tableName, final byte[] col) throws IOException { final Configuration conf = c.getEnvironment().getConfiguration(); User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { @Override public Void run() throws Exception { AccessControlLists.removeTablePermissions(conf, tableName, col); return null; } }); }
@Override public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> c, TableName tableName) throws IOException { if (Bytes.equals(tableName.getName(), AccessControlLists.ACL_GLOBAL_NAME)) { // We have to unconditionally disallow disable of the ACL table when we are installed, // even if not enforcing authorizations. We are still allowing grants and revocations, // checking permissions and logging audit messages, etc. If the ACL table is not // available we will fail random actions all over the place. throw new AccessDeniedException("Not allowed to disable " + AccessControlLists.ACL_TABLE_NAME + " table with AccessController installed"); } requirePermission("disableTable", tableName, null, null, Action.ADMIN, Action.CREATE); }
@Override public void preAbortProcedure( ObserverContext<MasterCoprocessorEnvironment> ctx, final ProcedureExecutor<MasterProcedureEnv> procEnv, final long procId) throws IOException { if (!procEnv.isProcedureOwner(procId, getActiveUser())) { // If the user is not the procedure owner, then we should further probe whether // he can abort the procedure. requirePermission("abortProcedure", Action.ADMIN); } }
@Override public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException { if (!MetaTableAccessor.tableExists(ctx.getEnvironment().getMasterServices() .getConnection(), AccessControlLists.ACL_TABLE_NAME)) { // initialize the ACL storage table AccessControlLists.createACLTable(ctx.getEnvironment().getMasterServices()); } else { aclTabAvailable = true; } }
@Override public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx, final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor) throws IOException { requirePermission("snapshot", hTableDescriptor.getTableName(), null, null, Permission.Action.ADMIN); }
@Override public void preListSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx, final SnapshotDescription snapshot) throws IOException { if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, getActiveUser())) { // list it, if user is the owner of snapshot } else { requirePermission("listSnapshot", Action.ADMIN); } }
@Override public void preRestoreSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx, final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor) throws IOException { if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, getActiveUser())) { requirePermission("restoreSnapshot", hTableDescriptor.getTableName(), null, null, Permission.Action.ADMIN); } else { requirePermission("restore", Action.ADMIN); } }
@Override public void preDeleteSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx, final SnapshotDescription snapshot) throws IOException { if (SnapshotDescriptionUtils.isSnapshotOwner(snapshot, getActiveUser())) { // Snapshot owner is allowed to delete the snapshot // TODO: We are not logging this for audit } else { requirePermission("deleteSnapshot", Action.ADMIN); } }
@Override public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, final String namespace) throws IOException { final Configuration conf = ctx.getEnvironment().getConfiguration(); User.runAsLoginUser(new PrivilegedExceptionAction<Void>() { @Override public Void run() throws Exception { AccessControlLists.removeNamespacePermissions(conf, namespace); return null; } }); this.authManager.getZKPermissionWatcher().deleteNamespaceACLNode(namespace); LOG.info(namespace + " entry deleted in "+AccessControlLists.ACL_TABLE_NAME+" table."); }
@Override public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx, NamespaceDescriptor ns) throws IOException { // We require only global permission so that // a user with NS admin cannot altering namespace configurations. i.e. namespace quota requireGlobalPermission("modifyNamespace", Action.ADMIN, ns.getName()); }
@Override public void postListNamespaceDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx, List<NamespaceDescriptor> descriptors) throws IOException { // Retains only those which passes authorization checks, as the checks weren't done as part // of preGetTableDescriptors. Iterator<NamespaceDescriptor> itr = descriptors.iterator(); while (itr.hasNext()) { NamespaceDescriptor desc = itr.next(); try { requireNamespacePermission("listNamespaces", desc.getName(), Action.ADMIN); } catch (AccessDeniedException e) { itr.remove(); } } }
@Override public void postGetTableNames(ObserverContext<MasterCoprocessorEnvironment> ctx, List<HTableDescriptor> descriptors, String regex) throws IOException { // Retains only those which passes authorization checks. Iterator<HTableDescriptor> itr = descriptors.iterator(); while (itr.hasNext()) { HTableDescriptor htd = itr.next(); try { requireAccess("getTableNames", htd.getTableName(), Action.values()); } catch (AccessDeniedException e) { itr.remove(); } } }
@Override public void postCreateTableHandler( final ObserverContext<MasterCoprocessorEnvironment> ctx, HTableDescriptor desc, HRegionInfo[] regions) throws IOException { // the AccessController test, some times calls only and directly the postCreateTableHandler() if (tableCreationLatch != null) { tableCreationLatch.countDown(); } }
@Override public void postDeleteTableHandler( final ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName) throws IOException { // the AccessController test, some times calls only and directly the postDeleteTableHandler() if (tableDeletionLatch != null) { tableDeletionLatch.countDown(); } }
@Test (timeout=180000) public void testCoprocessorLoading() throws Exception { MasterCoprocessorHost cpHost = TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterCoprocessorHost(); cpHost.load(MyAccessController.class, Coprocessor.PRIORITY_HIGHEST, conf); AccessController ACCESS_CONTROLLER = (AccessController) cpHost.findCoprocessor( MyAccessController.class.getName()); MasterCoprocessorEnvironment CP_ENV = cpHost.createEnvironment( MyAccessController.class, ACCESS_CONTROLLER, Coprocessor.PRIORITY_HIGHEST, 1, conf); RegionServerCoprocessorHost rsHost = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0) .getRegionServerCoprocessorHost(); RegionServerCoprocessorEnvironment RSCP_ENV = rsHost.createEnvironment( MyAccessController.class, ACCESS_CONTROLLER, Coprocessor.PRIORITY_HIGHEST, 1, conf); }
@Override public void postAddColumnHandler(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName, HColumnDescriptor column) throws IOException { Threads.sleep(6000); try { ctx.getEnvironment().getMasterServices().checkTableModifiable(tableName); } catch(TableNotDisabledException expected) { //pass return; } catch(IOException ex) { } fail("was expecting the table to be enabled"); }
@Override public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName) throws IOException { try { LOG.debug("Waiting for addColumn to be processed first"); //wait for addColumn to be processed first addColumn.await(); LOG.debug("addColumn started, we can continue"); } catch (InterruptedException ex) { LOG.warn("Sleep interrupted while waiting for addColumn countdown"); } }
@Override public void postCreateTableHandler(final ObserverContext<MasterCoprocessorEnvironment> ctx, HTableDescriptor desc, HRegionInfo[] regions) throws IOException { // the AccessController test, some times calls only and directly the postCreateTableHandler() if (tableCreationLatch != null) { tableCreationLatch.countDown(); } }
@Override public void postDeleteTableHandler(final ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName) throws IOException { // the AccessController test, some times calls only and directly the postDeleteTableHandler() if (tableDeletionLatch != null) { tableDeletionLatch.countDown(); } }
@Override public void preSnapshot(final ObserverContext<MasterCoprocessorEnvironment> ctx, final SnapshotDescription snapshot, final HTableDescriptor hTableDescriptor) throws IOException { if (snapshotCount != null) { snapshotCount.incrementAndGet(); } }