diff --git a/test/src/main/java/org/apache/accumulo/test/AuditMessageIT.java b/test/src/main/java/org/apache/accumulo/test/AuditMessageIT.java index 69e294bdc1d..3a931c178a5 100644 --- a/test/src/main/java/org/apache/accumulo/test/AuditMessageIT.java +++ b/test/src/main/java/org/apache/accumulo/test/AuditMessageIT.java @@ -482,6 +482,10 @@ public void testDeniedAudits() throws AccumuloSecurityException, AccumuloExcepti auditConnector.tableOperations().deleteRows(OLD_TEST_TABLE_NAME, new Text("myRow"), new Text("myRow~")); } catch (AccumuloSecurityException ex) {} + try { + auditConnector.tableOperations().flush(OLD_TEST_TABLE_NAME, new Text("myRow"), + new Text("myRow~"), false); + } catch (AccumuloSecurityException ex) {} // ... that will do for now. // End of testing activities @@ -519,6 +523,11 @@ public void testDeniedAudits() throws AccumuloSecurityException, AccumuloExcepti "operation: denied;.*" + String.format(AuditedSecurityOperation.CAN_DELETE_RANGE_AUDIT_TEMPLATE, OLD_TEST_TABLE_NAME, "myRow", "myRow~")).size()); + assertEquals(1, + findAuditMessage(auditMessages, + "operation: denied;.*" + String + .format(AuditedSecurityOperation.CAN_FLUSH_TABLE_AUDIT_TEMPLATE, "1", "\\+default")) + .size()); } @Test diff --git a/test/src/main/java/org/apache/accumulo/test/functional/ManagerApiIT.java b/test/src/main/java/org/apache/accumulo/test/functional/ManagerApiIT.java new file mode 100644 index 00000000000..ad007c75ef3 --- /dev/null +++ b/test/src/main/java/org/apache/accumulo/test/functional/ManagerApiIT.java @@ -0,0 +1,216 @@ +/* + * 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.accumulo.test.functional; + +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.admin.SecurityOperations; +import org.apache.accumulo.core.client.impl.ClientContext; +import org.apache.accumulo.core.client.impl.ClientExec; +import org.apache.accumulo.core.client.impl.Credentials; +import org.apache.accumulo.core.client.impl.MasterClient; +import org.apache.accumulo.core.client.security.SecurityErrorCode; +import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.core.conf.Property; +import org.apache.accumulo.core.master.thrift.MasterClientService; +import org.apache.accumulo.core.master.thrift.MasterGoalState; +import org.apache.accumulo.core.security.SystemPermission; +import org.apache.accumulo.core.security.TablePermission; +import org.apache.accumulo.core.security.thrift.TCredentials; +import org.apache.accumulo.core.util.TextUtil; +import org.apache.accumulo.harness.SharedMiniClusterBase; +import org.apache.hadoop.io.Text; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +// the shutdown test should sort last, so other tests don't break +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class ManagerApiIT extends SharedMiniClusterBase { + + @Override + public int defaultTimeoutSeconds() { + return 60; + } + + private static Credentials rootUser; + private static Credentials regularUser; + private static Credentials privilegedUser; + + @BeforeClass + public static void setup() throws Exception { + SharedMiniClusterBase.startMiniCluster(); + rootUser = new Credentials(getPrincipal(), getToken()); + regularUser = new Credentials("regularUser", new PasswordToken("regularUser")); + privilegedUser = new Credentials("privilegedUser", new PasswordToken("privilegedUser")); + SecurityOperations rootSecOps = getConnector().securityOperations(); + for (Credentials user : Arrays.asList(regularUser, privilegedUser)) + rootSecOps.createLocalUser(user.getPrincipal(), (PasswordToken) user.getToken()); + rootSecOps.grantSystemPermission(privilegedUser.getPrincipal(), SystemPermission.SYSTEM); + } + + @AfterClass + public static void teardown() throws Exception { + SharedMiniClusterBase.stopMiniCluster(); + } + + private Function> op; + + @Test + public void testPermissions_setMasterGoalState() throws Exception { + // To setMasterGoalState, user needs SystemPermission.SYSTEM + op = user -> client -> client.setMasterGoalState(null, user, MasterGoalState.NORMAL); + expectPermissionDenied(op, regularUser); + expectPermissionSuccess(op, rootUser); + expectPermissionSuccess(op, privilegedUser); + } + + @Test + public void testPermissions_initiateFlush() throws Exception { + // To initiateFlush, user needs TablePermission.WRITE or TablePermission.ALTER_TABLE + String[] uniqNames = getUniqueNames(3); + String tableName = uniqNames[0]; + Credentials regUserWithWrite = new Credentials(uniqNames[1], new PasswordToken(uniqNames[1])); + Credentials regUserWithAlter = new Credentials(uniqNames[2], new PasswordToken(uniqNames[2])); + SecurityOperations rootSecOps = getConnector().securityOperations(); + rootSecOps.createLocalUser(regUserWithWrite.getPrincipal(), + (PasswordToken) regUserWithWrite.getToken()); + rootSecOps.createLocalUser(regUserWithAlter.getPrincipal(), + (PasswordToken) regUserWithAlter.getToken()); + getConnector().tableOperations().create(tableName); + rootSecOps.grantTablePermission(regUserWithWrite.getPrincipal(), tableName, + TablePermission.WRITE); + rootSecOps.grantTablePermission(regUserWithAlter.getPrincipal(), tableName, + TablePermission.ALTER_TABLE); + String tableId = getConnector().tableOperations().tableIdMap().get(tableName); + op = user -> client -> client.initiateFlush(null, user, tableId); + expectPermissionDenied(op, regularUser); + // privileged users can grant themselves permission, but it's not default + expectPermissionDenied(op, privilegedUser); + expectPermissionSuccess(op, regUserWithWrite); + expectPermissionSuccess(op, regUserWithAlter); + // root user can because they created the table + expectPermissionSuccess(op, rootUser); + } + + @Test + public void testPermissions_waitForFlush() throws Exception { + // To waitForFlush, user needs TablePermission.WRITE or TablePermission.ALTER_TABLE + String[] uniqNames = getUniqueNames(3); + String tableName = uniqNames[0]; + Credentials regUserWithWrite = new Credentials(uniqNames[1], new PasswordToken(uniqNames[1])); + Credentials regUserWithAlter = new Credentials(uniqNames[2], new PasswordToken(uniqNames[2])); + SecurityOperations rootSecOps = getConnector().securityOperations(); + rootSecOps.createLocalUser(regUserWithWrite.getPrincipal(), + (PasswordToken) regUserWithWrite.getToken()); + rootSecOps.createLocalUser(regUserWithAlter.getPrincipal(), + (PasswordToken) regUserWithAlter.getToken()); + getConnector().tableOperations().create(tableName); + rootSecOps.grantTablePermission(regUserWithWrite.getPrincipal(), tableName, + TablePermission.WRITE); + rootSecOps.grantTablePermission(regUserWithAlter.getPrincipal(), tableName, + TablePermission.ALTER_TABLE); + String tableId = getConnector().tableOperations().tableIdMap().get(tableName); + AtomicLong flushId = new AtomicLong(); + // initiateFlush as the root user to get the flushId, then test waitForFlush with other users + op = user -> client -> flushId.set(client.initiateFlush(null, user, tableId)); + expectPermissionSuccess(op, rootUser); + op = user -> client -> client.waitForFlush(null, user, tableId, + TextUtil.getByteBuffer(new Text("myrow")), TextUtil.getByteBuffer(new Text("myrow~")), + flushId.get(), 1); + expectPermissionDenied(op, regularUser); + // privileged users can grant themselves permission, but it's not default + expectPermissionDenied(op, privilegedUser); + expectPermissionSuccess(op, regUserWithWrite); + expectPermissionSuccess(op, regUserWithAlter); + // root user can because they created the table + expectPermissionSuccess(op, rootUser); + } + + @Test + public void testPermissions_setSystemProperty() throws Exception { + // To setSystemProperty, user needs SystemPermission.SYSTEM + String propKey = Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey(); + op = user -> client -> client.setSystemProperty(null, user, propKey, "10000"); + expectPermissionDenied(op, regularUser); + expectPermissionSuccess(op, rootUser); + expectPermissionSuccess(op, privilegedUser); + getConnector().instanceOperations().removeProperty(propKey); // clean up property + } + + @Test + public void testPermissions_removeSystemProperty() throws Exception { + // To removeSystemProperty, user needs SystemPermission.SYSTEM + String propKey1 = Property.GC_CYCLE_DELAY.getKey(); + String propKey2 = Property.GC_CYCLE_START.getKey(); + getConnector().instanceOperations().setProperty(propKey1, "10000"); // ensure it exists + getConnector().instanceOperations().setProperty(propKey2, "10000"); // ensure it exists + op = user -> client -> client.removeSystemProperty(null, user, propKey1); + expectPermissionDenied(op, regularUser); + expectPermissionSuccess(op, rootUser); + op = user -> client -> client.removeSystemProperty(null, user, propKey2); + expectPermissionSuccess(op, privilegedUser); + } + + @Test + public void testPermissions_shutdownTabletServer() throws Exception { + // To shutdownTabletServer, user needs SystemPermission.SYSTEM + // this server won't exist, so shutting it down is a NOOP on success + String fakeHostAndPort = getUniqueNames(1)[0] + ":0"; + op = user -> client -> client.shutdownTabletServer(null, user, fakeHostAndPort, false); + expectPermissionDenied(op, regularUser); + expectPermissionSuccess(op, rootUser); + expectPermissionSuccess(op, privilegedUser); + } + + // this test should go last, because it shuts things down; + // see the junit annotation to control test ordering at the top of this class + @Test + public void z99_testPermissions_shutdown() throws Exception { + // To shutdown, user needs SystemPermission.SYSTEM + op = user -> client -> client.shutdown(null, user, false); + expectPermissionDenied(op, regularUser); + // We should be able to do both of the following RPC calls before it actually shuts down + expectPermissionSuccess(op, rootUser); + expectPermissionSuccess(op, privilegedUser); + } + + private static void expectPermissionSuccess( + Function> op, Credentials user) + throws Exception { + ClientContext context = + new ClientContext(getConnector().getInstance(), user, getCluster().getClientConfig()); + MasterClient.executeVoid(context, op.apply(context.rpcCreds())); + } + + private static void expectPermissionDenied( + Function> op, Credentials user) + throws Exception { + AccumuloSecurityException e = + assertThrows(AccumuloSecurityException.class, () -> expectPermissionSuccess(op, user)); + assertSame(SecurityErrorCode.PERMISSION_DENIED, e.getSecurityErrorCode()); + } + +} diff --git a/test/src/main/java/org/apache/accumulo/test/functional/PermissionsIT.java b/test/src/main/java/org/apache/accumulo/test/functional/PermissionsIT.java index 2876ef27599..33308e222a2 100644 --- a/test/src/main/java/org/apache/accumulo/test/functional/PermissionsIT.java +++ b/test/src/main/java/org/apache/accumulo/test/functional/PermissionsIT.java @@ -271,7 +271,35 @@ private void testMissingSystemPermission(String tableNamePrefix, Connector root_ } break; case SYSTEM: - // test for system permission would go here + try { + // Test setProperty + loginAs(testUser); + test_user_conn.instanceOperations() + .setProperty(Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey(), "10000"); + throw new IllegalStateException("Should NOT be able to set System Property"); + } catch (AccumuloSecurityException e) { + loginAs(rootUser); + if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED + || root_conn.instanceOperations().getSystemConfiguration() + .get(Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey()).equals("10000")) + throw e; + } + // Test removal of property + loginAs(rootUser); + root_conn.instanceOperations().setProperty(Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey(), + "10000"); + try { + loginAs(testUser); + test_user_conn.instanceOperations() + .removeProperty(Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey()); + throw new IllegalStateException("Should NOT be able to remove Sysem Property"); + } catch (AccumuloSecurityException e) { + loginAs(rootUser); + if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED + || !root_conn.instanceOperations().getSystemConfiguration() + .get(Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey()).equals("10000")) + throw e; + } break; case CREATE_NAMESPACE: namespace = "__CREATE_NAMESPACE_WITHOUT_PERM_TEST__"; @@ -458,7 +486,22 @@ private void testGrantedSystemPermission(String tableNamePrefix, Connector root_ throw new IllegalStateException("Should be able to alter a user"); break; case SYSTEM: - // test for system permission would go here + // Test setProperty + loginAs(testUser); + test_user_conn.instanceOperations() + .setProperty(Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey(), "10000"); + loginAs(rootUser); + if (!root_conn.instanceOperations().getSystemConfiguration() + .get(Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey()).equals("10000")) + throw new IllegalStateException("Should be able to set system property"); + // Test removal of property + loginAs(testUser); + test_user_conn.instanceOperations() + .removeProperty(Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey()); + loginAs(rootUser); + if (root_conn.instanceOperations().getSystemConfiguration() + .get(Property.TSERV_TOTAL_MUTATION_QUEUE_MAX.getKey()).equals("10000")) + throw new IllegalStateException("Should be able remove systemproperty"); break; case CREATE_NAMESPACE: namespace = "__CREATE_NAMESPACE_WITH_PERM_TEST__"; @@ -660,6 +703,15 @@ private void testMissingTablePermission(Connector test_user_conn, ClusterUser te if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED) throw e; } + // Now see if we can flush + try { + test_user_conn.tableOperations().flush(tableName, new Text("myrow"), new Text("myrow~"), + false); + throw new IllegalStateException("Should NOT be able to flsuh a table"); + } catch (AccumuloSecurityException e) { + if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED) + throw e; + } break; case BULK_IMPORT: // test for bulk import permission would go here @@ -674,6 +726,14 @@ private void testMissingTablePermission(Connector test_user_conn, ClusterUser te if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED) throw e; } + try { + test_user_conn.tableOperations().flush(tableName, new Text("myrow"), new Text("myrow~"), + false); + throw new IllegalStateException("Should NOT be able to flsuh a table"); + } catch (AccumuloSecurityException e) { + if (e.getSecurityErrorCode() != SecurityErrorCode.PERMISSION_DENIED) + throw e; + } break; case DROP_TABLE: try { @@ -717,6 +777,8 @@ private void testGrantedTablePermission(Connector test_user_conn, ClusterUser no iter.next(); break; case WRITE: + test_user_conn.tableOperations().flush(tableName, new Text("myrow"), new Text("myrow~"), + false); writer = test_user_conn.createBatchWriter(tableName, new BatchWriterConfig()); m = new Mutation(new Text("row")); m.put(new Text("a"), new Text("b"), new Value("c".getBytes())); @@ -727,6 +789,8 @@ private void testGrantedTablePermission(Connector test_user_conn, ClusterUser no // test for bulk import permission would go here break; case ALTER_TABLE: + test_user_conn.tableOperations().flush(tableName, new Text("myrow"), new Text("myrow~"), + false); Map> groups = new HashMap<>(); groups.put("tgroup", new HashSet<>(Arrays.asList(new Text("t1"), new Text("t2")))); break;