diff --git a/src/main/java/org/apache/accumulo/testing/randomwalk/security/Authenticate.java b/src/main/java/org/apache/accumulo/testing/randomwalk/security/Authenticate.java index e6ef8b61..5d023c29 100644 --- a/src/main/java/org/apache/accumulo/testing/randomwalk/security/Authenticate.java +++ b/src/main/java/org/apache/accumulo/testing/randomwalk/security/Authenticate.java @@ -44,19 +44,30 @@ public static void authenticate(String principal, AuthenticationToken token, Sta String targetProp = props.getProperty("target"); boolean success = Boolean.parseBoolean(props.getProperty("valid")); - try (AccumuloClient client = env.createClient(principal, token)) { + String target; - String target; + if (targetProp.equals("table")) { + target = WalkingSecurity.get(state, env).getTabUserName(); + } else { + target = WalkingSecurity.get(state, env).getSysUserName(); + } + boolean exists = WalkingSecurity.get(state, env).userExists(target); + // Copy so if failed it doesn't mess with the password stored in state + byte[] password = Arrays.copyOf(WalkingSecurity.get(state, env).getUserPassword(target), + WalkingSecurity.get(state, env).getUserPassword(target).length); - if (targetProp.equals("table")) { - target = WalkingSecurity.get(state, env).getTabUserName(); - } else { - target = WalkingSecurity.get(state, env).getSysUserName(); + String actor = principal; + AuthenticationToken actorToken = token; + if (principal.equals(target) && !success) { + String rootUser = WalkingSecurity.get(state, env).getRootUserName(); + AuthenticationToken rootToken = WalkingSecurity.get(state, env).getRootToken(); + if (rootUser != null && rootToken != null) { + actor = rootUser; + actorToken = rootToken; } - boolean exists = WalkingSecurity.get(state, env).userExists(target); - // Copy so if failed it doesn't mess with the password stored in state - byte[] password = Arrays.copyOf(WalkingSecurity.get(state, env).getUserPassword(target), - WalkingSecurity.get(state, env).getUserPassword(target).length); + } + + try (AccumuloClient client = env.createClient(actor, actorToken)) { boolean hasPermission = client.securityOperations().hasSystemPermission(principal, SystemPermission.SYSTEM) || principal.equals(target); diff --git a/src/main/java/org/apache/accumulo/testing/randomwalk/security/SecurityFixture.java b/src/main/java/org/apache/accumulo/testing/randomwalk/security/SecurityFixture.java index dcd955af..35251431 100644 --- a/src/main/java/org/apache/accumulo/testing/randomwalk/security/SecurityFixture.java +++ b/src/main/java/org/apache/accumulo/testing/randomwalk/security/SecurityFixture.java @@ -64,7 +64,7 @@ public void setUp(State state, RandWalkEnv env) throws Exception { WalkingSecurity.get(state, env).setTableName(secTableName); WalkingSecurity.get(state, env).setNamespaceName(secNamespaceName); - state.set("rootUserPass", env.getToken()); + WalkingSecurity.get(state, env).setRootUserCredentials(client.whoami(), env.getToken()); WalkingSecurity.get(state, env).setSysUserName(systemUserName); WalkingSecurity.get(state, env).createUser(systemUserName, sysUserPass); diff --git a/src/main/java/org/apache/accumulo/testing/randomwalk/security/TableOp.java b/src/main/java/org/apache/accumulo/testing/randomwalk/security/TableOp.java index cadfbaec..79b3c57b 100644 --- a/src/main/java/org/apache/accumulo/testing/randomwalk/security/TableOp.java +++ b/src/main/java/org/apache/accumulo/testing/randomwalk/security/TableOp.java @@ -21,6 +21,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.util.Iterator; +import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.SortedSet; @@ -40,6 +41,7 @@ import org.apache.accumulo.core.client.rfile.RFile; import org.apache.accumulo.core.client.rfile.RFileWriter; import org.apache.accumulo.core.client.security.SecurityErrorCode; +import org.apache.accumulo.core.client.summary.Summary; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.Mutation; import org.apache.accumulo.core.data.Value; @@ -80,10 +82,11 @@ public void visit(State state, RandWalkEnv env, Properties props) throws Excepti try { canRead = secOps.hasTablePermission(tablePrincipal, tableName, TablePermission.READ); } catch (AccumuloSecurityException ase) { - if (tableExists) - throw new AccumuloException("Table didn't exist when it should have: " + tableName, - ase); - return; + if (handlePermissionCheckException(ase, tableExists, tableName, state, env, + tablePrincipal)) { + return; + } + throw ase; } Authorizations auths = secOps.getUserAuthorizations(tablePrincipal); boolean ambiguousZone = @@ -171,10 +174,11 @@ public void visit(State state, RandWalkEnv env, Properties props) throws Excepti try { canWrite = secOps.hasTablePermission(tablePrincipal, tableName, TablePermission.WRITE); } catch (AccumuloSecurityException ase) { - if (tableExists) - throw new AccumuloException("Table didn't exist when it should have: " + tableName, - ase); - return; + if (handlePermissionCheckException(ase, tableExists, tableName, state, env, + tablePrincipal)) { + return; + } + throw ase; } String key = WalkingSecurity.get(state, env).getLastKey() + "1"; @@ -223,6 +227,17 @@ public void visit(State state, RandWalkEnv env, Properties props) throws Excepti } break; case BULK_IMPORT: + boolean canBulkImportBefore; + try { + canBulkImportBefore = + secOps.hasTablePermission(tablePrincipal, tableName, TablePermission.BULK_IMPORT); + } catch (AccumuloSecurityException ase) { + if (handlePermissionCheckException(ase, tableExists, tableName, state, env, + tablePrincipal)) { + return; + } + throw ase; + } key = WalkingSecurity.get(state, env).getLastKey() + "1"; SortedSet keys = new TreeSet<>(); for (String s : WalkingSecurity.get(state, env).getAuthsArray()) { @@ -247,24 +262,100 @@ public void visit(State state, RandWalkEnv env, Properties props) throws Excepti return; } catch (AccumuloSecurityException ae) { if (ae.getSecurityErrorCode().equals(SecurityErrorCode.PERMISSION_DENIED)) { - if (secOps.hasTablePermission(tablePrincipal, tableName, TablePermission.BULK_IMPORT)) - throw new AccumuloException( - "Bulk Import failed when it should have worked: " + tableName); + try { + boolean canBulkImportAfter = secOps.hasTablePermission(tablePrincipal, tableName, + TablePermission.BULK_IMPORT); + if (canBulkImportBefore && canBulkImportAfter) { + if (WalkingSecurity.get(state, env).inAmbiguousZone(tablePrincipal, + TablePermission.BULK_IMPORT)) { + log.info("Bulk import denied while permission propagating; ignoring."); + return; + } + throw new AccumuloException( + "Bulk Import failed when it should have worked: " + tableName, ae); + } + } catch (AccumuloSecurityException ase) { + if (handlePermissionCheckException(ase, tableExists, tableName, state, env, + tablePrincipal)) { + return; + } + throw ase; + } return; } else if (ae.getSecurityErrorCode().equals(SecurityErrorCode.BAD_CREDENTIALS)) { if (WalkingSecurity.get(state, env).userPassTransient(client.whoami())) return; } throw new AccumuloException("Unexpected exception!", ae); + } catch (AccumuloException ae) { + Throwable cause = ae.getCause(); + if (cause instanceof AccumuloSecurityException) { + AccumuloSecurityException ase = (AccumuloSecurityException) cause; + if (ase.getSecurityErrorCode().equals(SecurityErrorCode.PERMISSION_DENIED)) { + try { + boolean canBulkImportAfter = secOps.hasTablePermission(tablePrincipal, tableName, + TablePermission.BULK_IMPORT); + if (canBulkImportBefore && canBulkImportAfter) { + if (WalkingSecurity.get(state, env).inAmbiguousZone(tablePrincipal, + TablePermission.BULK_IMPORT)) { + log.info("Bulk import denied while permission propagating; ignoring."); + return; + } + throw new AccumuloException( + "Bulk Import failed when it should have worked: " + tableName, ase); + } + } catch (AccumuloSecurityException inner) { + if (handlePermissionCheckException(inner, tableExists, tableName, state, env, + tablePrincipal)) { + return; + } + throw inner; + } + return; + } else if (ase.getSecurityErrorCode().equals(SecurityErrorCode.BAD_CREDENTIALS)) { + if (WalkingSecurity.get(state, env).userPassTransient(client.whoami())) { + return; + } + } + } + throw ae; } for (String s : WalkingSecurity.get(state, env).getAuthsArray()) WalkingSecurity.get(state, env).increaseAuthMap(s, 1); fs.delete(dir, true); fs.delete(fail, true); - if (!secOps.hasTablePermission(tablePrincipal, tableName, TablePermission.BULK_IMPORT)) - throw new AccumuloException( - "Bulk Import succeeded when it should have failed: " + dir + " table " + tableName); + try { + boolean canBulkImportAfter = + secOps.hasTablePermission(tablePrincipal, tableName, TablePermission.BULK_IMPORT); + if (!canBulkImportAfter) { + if (!canBulkImportBefore) { + if (WalkingSecurity.get(state, env).inAmbiguousZone(tablePrincipal, + TablePermission.BULK_IMPORT)) { + log.info("Bulk import succeeded before permission fully propagated; ignoring."); + return; + } + throw new AccumuloException("Bulk Import succeeded when it should have failed: " + + dir + " table " + tableName); + } + return; + } + if (!canBulkImportBefore && canBulkImportAfter) { + if (WalkingSecurity.get(state, env).inAmbiguousZone(tablePrincipal, + TablePermission.BULK_IMPORT)) { + log.info("Bulk import succeeded during permission propagation; ignoring."); + return; + } + throw new AccumuloException("Bulk Import succeeded when it should have failed: " + dir + + " table " + tableName); + } + } catch (AccumuloSecurityException ase) { + if (handlePermissionCheckException(ase, tableExists, tableName, state, env, + tablePrincipal)) { + return; + } + throw ase; + } break; case ALTER_TABLE: boolean tablePerm; @@ -272,10 +363,11 @@ public void visit(State state, RandWalkEnv env, Properties props) throws Excepti tablePerm = secOps.hasTablePermission(tablePrincipal, tableName, TablePermission.ALTER_TABLE); } catch (AccumuloSecurityException ase) { - if (tableExists) - throw new AccumuloException("Table didn't exist when it should have: " + tableName, - ase); - return; + if (handlePermissionCheckException(ase, tableExists, tableName, state, env, + tablePrincipal)) { + return; + } + throw ase; } AlterTable.renameTable(client, state, env, tableName, tableName + "plus", tablePerm, tableExists); @@ -294,9 +386,68 @@ public void visit(State state, RandWalkEnv env, Properties props) throws Excepti DropTable.dropTable(state, env, props); break; - case GET_SUMMARIES: - throw new UnsupportedOperationException("GET_SUMMARIES not implemented"); + case GET_SUMMARIES: { + boolean canSummarize; + try { + canSummarize = + secOps.hasTablePermission(tablePrincipal, tableName, TablePermission.GET_SUMMARIES); + } catch (AccumuloSecurityException ase) { + if (handlePermissionCheckException(ase, tableExists, tableName, state, env, + tablePrincipal)) { + return; + } + throw ase; + } + try { + List summaries = tableOps.summaries(tableName).retrieve(); + if (!canSummarize) { + throw new AccumuloException( + "Was able to retrieve summaries when permission should be denied: " + tableName); + } + if (summaries == null) { + throw new AccumuloException( + "Summary list was unexpectedly null for table " + tableName); + } + } catch (TableNotFoundException tnfe) { + if (tableExists) + throw new AccumuloException("Table didn't exist when it should have: " + tableName, + tnfe); + return; + } catch (AccumuloSecurityException ae) { + if (ae.getSecurityErrorCode().equals(SecurityErrorCode.PERMISSION_DENIED)) { + if (canSummarize) + throw new AccumuloException( + "Get summaries failed when it should have succeeded: " + tableName, ae); + return; + } else if (ae.getSecurityErrorCode().equals(SecurityErrorCode.BAD_CREDENTIALS)) { + if (WalkingSecurity.get(state, env).userPassTransient(client.whoami())) + return; + } + throw new AccumuloException("Unexpected security exception retrieving summaries", ae); + } + break; + } } } } + + private boolean handlePermissionCheckException(AccumuloSecurityException ase, boolean tableExists, + String tableName, State state, RandWalkEnv env, String principal) + throws AccumuloException, AccumuloSecurityException { + SecurityErrorCode code = ase.getSecurityErrorCode(); + switch (code) { + case TABLE_DOESNT_EXIST: + case NAMESPACE_DOESNT_EXIST: + if (tableExists) + throw new AccumuloException("Table didn't exist when it should have: " + tableName, ase); + return true; + case BAD_CREDENTIALS: + if (WalkingSecurity.get(state, env).userPassTransient(principal)) + return true; + break; + default: + break; + } + throw ase; + } } diff --git a/src/main/java/org/apache/accumulo/testing/randomwalk/security/WalkingSecurity.java b/src/main/java/org/apache/accumulo/testing/randomwalk/security/WalkingSecurity.java index fc87203b..652aad74 100644 --- a/src/main/java/org/apache/accumulo/testing/randomwalk/security/WalkingSecurity.java +++ b/src/main/java/org/apache/accumulo/testing/randomwalk/security/WalkingSecurity.java @@ -47,6 +47,7 @@ public class WalkingSecurity { private static final String tableName = "SecurityTableName"; private static final String namespaceName = "SecurityNamespaceName"; private static final String userName = "UserName"; + private static final String rootUserName = "RootUserName"; private static final String userPass = "UserPass"; private static final String userExists = "UserExists"; @@ -152,8 +153,7 @@ private void setTabPerm(State state, String userName, TablePermission tp, String log.debug((value ? "Gave" : "Took") + " the table permission " + tp.name() + (value ? " to" : " from") + " user " + userName); state.set("Tab-" + userName + '-' + tp.name(), Boolean.toString(value)); - if (tp.equals(TablePermission.READ) || tp.equals(TablePermission.WRITE)) - state.set("Tab-" + userName + '-' + tp.name() + '-' + "time", System.currentTimeMillis()); + state.set("Tab-" + userName + '-' + tp.name() + '-' + "time", System.currentTimeMillis()); } public void revokeTablePermission(String user, String table, TablePermission permission) @@ -200,6 +200,21 @@ public void setSysUserName(String name) { state.set("system" + userName, name); } + public void setRootUserCredentials(String name, AuthenticationToken token) { + state.set(rootUserName, name); + state.set("rootUserPass", token); + state.set(name + userPass + "time", System.currentTimeMillis()); + } + + public String getRootUserName() { + return state.getString(rootUserName); + } + + public AuthenticationToken getRootToken() { + Object token = state.getOkIfAbsent("rootUserPass"); + return token instanceof AuthenticationToken ? (AuthenticationToken) token : null; + } + public String getTableName() { return state.getString(tableName); } @@ -271,13 +286,9 @@ public String[] getAuthsArray() { } public boolean inAmbiguousZone(String userName, TablePermission tp) { - if (tp.equals(TablePermission.READ) || tp.equals(TablePermission.WRITE)) { - Long setTime = state.getLong("Tab-" + userName + '-' + tp.name() + '-' + "time"); - if (setTime == null) - throw new RuntimeException("Tab-" + userName + '-' + tp.name() + '-' + "time is null"); - if (System.currentTimeMillis() < (setTime + 1000)) - return true; - } + Long setTime = state.getLong("Tab-" + userName + '-' + tp.name() + '-' + "time"); + if (setTime != null && System.currentTimeMillis() < (setTime + 1000)) + return true; return false; }