blob: bf58fdbc93180e28f72e74392c9622055f710ecd [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.accumulo.testing.randomwalk.security;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import org.apache.accumulo.core.client.AccumuloClient;
import org.apache.accumulo.core.client.AccumuloException;
import org.apache.accumulo.core.client.AccumuloSecurityException;
import org.apache.accumulo.core.client.BatchWriter;
import org.apache.accumulo.core.client.BatchWriterConfig;
import org.apache.accumulo.core.client.MutationsRejectedException;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.client.admin.SecurityOperations;
import org.apache.accumulo.core.client.admin.TableOperations;
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.data.Key;
import org.apache.accumulo.core.data.Mutation;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.core.security.ColumnVisibility;
import org.apache.accumulo.core.security.TablePermission;
import org.apache.accumulo.testing.randomwalk.RandWalkEnv;
import org.apache.accumulo.testing.randomwalk.State;
import org.apache.accumulo.testing.randomwalk.Test;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
public class TableOp extends Test {
@Override
public void visit(State state, RandWalkEnv env, Properties props) throws Exception {
String tablePrincipal = WalkingSecurity.get(state, env).getTabUserName();
try (AccumuloClient client = env.createClient(tablePrincipal,
WalkingSecurity.get(state, env).getTabToken())) {
TableOperations tableOps = client.tableOperations();
SecurityOperations secOps = client.securityOperations();
String action = props.getProperty("action", "_random");
TablePermission tp;
if ("_random".equalsIgnoreCase(action)) {
Random r = new Random();
tp = TablePermission.values()[r.nextInt(TablePermission.values().length)];
} else {
tp = TablePermission.valueOf(action);
}
boolean tableExists = WalkingSecurity.get(state, env).getTableExists();
String tableName = WalkingSecurity.get(state, env).getTableName();
switch (tp) {
case READ: {
boolean canRead;
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;
}
Authorizations auths = secOps.getUserAuthorizations(tablePrincipal);
boolean ambiguousZone = WalkingSecurity.get(state, env).inAmbiguousZone(client.whoami(),
tp);
boolean ambiguousAuths = WalkingSecurity.get(state, env)
.ambiguousAuthorizations(client.whoami());
Scanner scan = null;
try {
scan = client.createScanner(tableName, secOps.getUserAuthorizations(client.whoami()));
int seen = 0;
Iterator<Entry<Key,Value>> iter = scan.iterator();
while (iter.hasNext()) {
Entry<Key,Value> entry = iter.next();
Key k = entry.getKey();
seen++;
if (!auths.contains(k.getColumnVisibilityData()) && !ambiguousAuths)
throw new AccumuloException(
"Got data I should not be capable of seeing: " + k + " table " + tableName);
}
if (!canRead && !ambiguousZone)
throw new AccumuloException(
"Was able to read when I shouldn't have had the perm with connection user "
+ client.whoami() + " table " + tableName);
for (Entry<String,Integer> entry : WalkingSecurity.get(state, env).getAuthsMap()
.entrySet()) {
if (auths.contains(entry.getKey().getBytes(UTF_8)))
seen = seen - entry.getValue();
}
if (seen != 0 && !ambiguousAuths)
throw new AccumuloException("Got mismatched amounts of data");
} catch (TableNotFoundException tnfe) {
if (tableExists)
throw new AccumuloException("Accumulo and test suite out of sync: table " + tableName,
tnfe);
return;
} catch (AccumuloSecurityException ae) {
if (ae.getSecurityErrorCode().equals(SecurityErrorCode.PERMISSION_DENIED)) {
if (canRead && !ambiguousZone)
throw new AccumuloException(
"Table read permission out of sync with Accumulo: table " + tableName, ae);
else
return;
}
if (ae.getSecurityErrorCode().equals(SecurityErrorCode.BAD_AUTHORIZATIONS)) {
if (ambiguousAuths)
return;
else
throw new AccumuloException("Mismatched authorizations! ", ae);
}
throw new AccumuloException("Unexpected exception!", ae);
} catch (RuntimeException re) {
if (re.getCause() instanceof AccumuloSecurityException
&& ((AccumuloSecurityException) re.getCause()).getSecurityErrorCode()
.equals(SecurityErrorCode.PERMISSION_DENIED)) {
if (canRead && !ambiguousZone)
throw new AccumuloException(
"Table read permission out of sync with Accumulo: table " + tableName,
re.getCause());
else
return;
}
if (re.getCause() instanceof AccumuloSecurityException
&& ((AccumuloSecurityException) re.getCause()).getSecurityErrorCode()
.equals(SecurityErrorCode.BAD_AUTHORIZATIONS)) {
if (ambiguousAuths)
return;
else
throw new AccumuloException("Mismatched authorizations! ", re.getCause());
}
throw new AccumuloException("Unexpected exception!", re);
} finally {
if (scan != null) {
scan.close();
scan = null;
}
}
break;
}
case WRITE:
boolean canWrite;
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;
}
String key = WalkingSecurity.get(state, env).getLastKey() + "1";
Mutation m = new Mutation(new Text(key));
for (String s : WalkingSecurity.get(state, env).getAuthsArray()) {
m.put(new Text(), new Text(), new ColumnVisibility(s),
new Value("value".getBytes(UTF_8)));
}
BatchWriter writer = null;
try {
try {
writer = client.createBatchWriter(tableName,
new BatchWriterConfig().setMaxMemory(9000l).setMaxWriteThreads(1));
} catch (TableNotFoundException tnfe) {
if (tableExists)
throw new AccumuloException("Table didn't exist when it should have: " + tableName);
return;
}
boolean works = true;
try {
writer.addMutation(m);
writer.close();
} catch (MutationsRejectedException mre) {
if (mre.getSecurityErrorCodes().size() == 1) {
// TabletServerBatchWriter will log the error automatically so make sure its the
// error we expect
SecurityErrorCode errorCode = mre.getSecurityErrorCodes().entrySet().iterator()
.next().getValue().iterator().next();
if (errorCode.equals(SecurityErrorCode.PERMISSION_DENIED) && !canWrite) {
log.info("Caught MutationsRejectedException({}) in TableOp.WRITE as expected.",
errorCode);
return;
}
}
throw new AccumuloException("Unexpected MutationsRejectedException in TableOp.WRITE",
mre);
}
if (works)
for (String s : WalkingSecurity.get(state, env).getAuthsArray())
WalkingSecurity.get(state, env).increaseAuthMap(s, 1);
} finally {
if (writer != null) {
writer.close();
writer = null;
}
}
break;
case BULK_IMPORT:
key = WalkingSecurity.get(state, env).getLastKey() + "1";
SortedSet<Key> keys = new TreeSet<>();
for (String s : WalkingSecurity.get(state, env).getAuthsArray()) {
Key k = new Key(key, "", "", s);
keys.add(k);
}
Path dir = new Path("/tmp", "bulk_" + UUID.randomUUID().toString());
Path fail = new Path(dir.toString() + "_fail");
FileSystem fs = WalkingSecurity.get(state, env).getFs();
RFileWriter rFileWriter = RFile.newWriter().to(dir + "/securityBulk.rf")
.withFileSystem(fs).build();
rFileWriter.startDefaultLocalityGroup();
fs.mkdirs(fail);
for (Key k : keys)
rFileWriter.append(k, new Value("Value".getBytes(UTF_8)));
rFileWriter.close();
try {
tableOps.importDirectory(dir.toString()).to(tableName).tableTime(true).load();
} catch (TableNotFoundException tnfe) {
if (tableExists)
throw new AccumuloException("Table didn't exist when it should have: " + tableName);
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);
return;
} else if (ae.getSecurityErrorCode().equals(SecurityErrorCode.BAD_CREDENTIALS)) {
if (WalkingSecurity.get(state, env).userPassTransient(client.whoami()))
return;
}
throw new AccumuloException("Unexpected exception!", 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);
break;
case ALTER_TABLE:
boolean tablePerm;
try {
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;
}
AlterTable.renameTable(client, state, env, tableName, tableName + "plus", tablePerm,
tableExists);
break;
case GRANT:
props.setProperty("task", "grant");
props.setProperty("perm", "random");
props.setProperty("source", "table");
props.setProperty("target", "system");
AlterTablePerm.alter(state, env, props);
break;
case DROP_TABLE:
props.setProperty("source", "table");
DropTable.dropTable(state, env, props);
break;
case GET_SUMMARIES:
throw new UnsupportedOperationException("GET_SUMMARIES not implemented");
}
}
}
}