Java tutorial
/* * 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; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.accumulo.core.Constants; import org.apache.accumulo.core.client.AccumuloException; import org.apache.accumulo.core.client.AccumuloSecurityException; import org.apache.accumulo.core.client.BatchScanner; import org.apache.accumulo.core.client.BatchWriter; import org.apache.accumulo.core.client.BatchWriterConfig; import org.apache.accumulo.core.client.Connector; import org.apache.accumulo.core.client.Scanner; import org.apache.accumulo.core.client.TableExistsException; import org.apache.accumulo.core.client.TableNotFoundException; import org.apache.accumulo.core.client.security.tokens.PasswordToken; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.Mutation; import org.apache.accumulo.core.data.Range; import org.apache.accumulo.core.data.Value; import org.apache.accumulo.core.security.Authorizations; import org.apache.accumulo.core.security.SystemPermission; import org.apache.accumulo.core.security.TablePermission; import org.apache.accumulo.minicluster.MiniAccumuloCluster; import org.apache.accumulo.server.security.AuditedSecurityOperation; import org.apache.commons.io.FileUtils; import org.apache.commons.io.LineIterator; import org.apache.hadoop.io.Text; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.rules.TemporaryFolder; /** * Tests that Accumulo is outputting audit messages as expected. Since this is using MiniAccumuloCluster, it could take a while if we test everything in * isolation. We test blocks of related operations, run the whole test in one MiniAccumulo instance, trying to clean up objects between each test. The * MiniAccumuloClusterTest sets up the log4j stuff differently to an installed instance, instead piping everything through stdout and writing to a set location * so we have to find the logs and grep the bits we need out. */ public class AuditMessageTest { private static MiniAccumuloCluster accumulo; private static File logDir; private static List<MiniAccumuloCluster.LogWriter> logWriters; private static final String AUDIT_USER_1 = "AuditUser1"; private static final String AUDIT_USER_2 = "AuditUser2"; private static final String PASSWORD = "password"; private static final String OLD_TEST_TABLE_NAME = "apples"; private static final String NEW_TEST_TABLE_NAME = "oranges"; private static final String THIRD_TEST_TABLE_NAME = "pears"; private static final Authorizations auths = new Authorizations("private", "public"); private static TemporaryFolder folder = new TemporaryFolder( new File(System.getProperty("user.dir") + "/target")); // Must be static to survive Junit re-initialising the class every time. private static String lastAuditTimestamp; private Connector auditConnector; private Connector conn; private static ArrayList<String> findAuditMessage(ArrayList<String> input, String pattern) { ArrayList<String> result = new ArrayList<String>(); for (String s : input) { if (s.matches(".*" + pattern + ".*")) result.add(s); } return result; } @BeforeClass public static void setupMiniCluster() throws Exception { folder.create(); Logger.getLogger("org.apache.zookeeper").setLevel(Level.ERROR); accumulo = new MiniAccumuloCluster(folder.getRoot(), "superSecret"); accumulo.start(); logDir = accumulo.getConfig().getLogDir(); logWriters = accumulo.getLogWriters(); } /** * Returns a List of Audit messages that have been grep'd out of the MiniAccumuloCluster output. * * @param stepName * A unique name for the test being executed, to identify the System.out messages. * @return A List of the Audit messages, sorted (so in chronological order). */ private ArrayList<String> getAuditMessages(String stepName) throws IOException { for (MiniAccumuloCluster.LogWriter lw : logWriters) { lw.flush(); } // Grab the audit messages System.out.println("Start of captured audit messages for step " + stepName); ArrayList<String> result = new ArrayList<String>(); for (File file : logDir.listFiles()) { // We want to grab the files called .out if (file.getName().contains(".out") && file.isFile() && file.canRead()) { LineIterator it = FileUtils.lineIterator(file, Constants.UTF8.name()); try { while (it.hasNext()) { String line = it.nextLine(); if (line.matches(".* \\[" + AuditedSecurityOperation.AUDITLOG + "\\s*\\].*")) { // Only include the message if startTimestamp is null. or the message occurred after the startTimestamp value if ((lastAuditTimestamp == null) || (line.substring(0, 23).compareTo(lastAuditTimestamp) > 0)) result.add(line); } } } finally { LineIterator.closeQuietly(it); } } } Collections.sort(result); for (String s : result) { System.out.println(s); } System.out.println("End of captured audit messages for step " + stepName); if (result.size() > 0) lastAuditTimestamp = (result.get(result.size() - 1)).substring(0, 23); return result; } private void grantEverySystemPriv(Connector conn, String user) throws AccumuloSecurityException, AccumuloException { SystemPermission[] arrayOfP = new SystemPermission[] { SystemPermission.SYSTEM, SystemPermission.ALTER_TABLE, SystemPermission.ALTER_USER, SystemPermission.CREATE_TABLE, SystemPermission.CREATE_USER, SystemPermission.DROP_TABLE, SystemPermission.DROP_USER }; for (SystemPermission p : arrayOfP) { conn.securityOperations().grantSystemPermission(user, p); } } @Before public void setup() throws AccumuloException, AccumuloSecurityException, TableNotFoundException, IOException { conn = accumulo.getConnector("root", "superSecret"); // I don't want to recreate the instance for every test since it will take ages. // If we run every test as non-root users, I can drop these users every test which should effectively // reset the environment. if (conn.securityOperations().listLocalUsers().contains(AUDIT_USER_1)) conn.securityOperations().dropLocalUser(AUDIT_USER_1); if (conn.securityOperations().listLocalUsers().contains(AUDIT_USER_2)) conn.securityOperations().dropLocalUser(AUDIT_USER_2); if (conn.securityOperations().listLocalUsers().contains(AUDIT_USER_2)) conn.securityOperations().dropLocalUser(THIRD_TEST_TABLE_NAME); if (conn.tableOperations().exists(NEW_TEST_TABLE_NAME)) conn.tableOperations().delete(NEW_TEST_TABLE_NAME); if (conn.tableOperations().exists(OLD_TEST_TABLE_NAME)) conn.tableOperations().delete(OLD_TEST_TABLE_NAME); // This will set the lastAuditTimestamp for the first test getAuditMessages("setup"); } @Test(timeout = 60 * 1000) @SuppressWarnings("unchecked") public void testTableOperationsAudits() throws AccumuloException, AccumuloSecurityException, TableExistsException, TableNotFoundException, IOException, InterruptedException { conn.securityOperations().createLocalUser(AUDIT_USER_1, new PasswordToken(PASSWORD)); conn.securityOperations().grantSystemPermission(AUDIT_USER_1, SystemPermission.SYSTEM); conn.securityOperations().grantSystemPermission(AUDIT_USER_1, SystemPermission.CREATE_TABLE); // Connect as Audit User and do a bunch of stuff. // Testing activity begins here auditConnector = accumulo.getConnector(AUDIT_USER_1, PASSWORD); auditConnector.tableOperations().create(OLD_TEST_TABLE_NAME); auditConnector.tableOperations().rename(OLD_TEST_TABLE_NAME, NEW_TEST_TABLE_NAME); auditConnector.tableOperations().clone(NEW_TEST_TABLE_NAME, OLD_TEST_TABLE_NAME, true, Collections.EMPTY_MAP, Collections.EMPTY_SET); auditConnector.tableOperations().delete(OLD_TEST_TABLE_NAME); auditConnector.tableOperations().offline(NEW_TEST_TABLE_NAME); auditConnector.tableOperations().delete(NEW_TEST_TABLE_NAME); // Testing activity ends here ArrayList<String> auditMessages = getAuditMessages("testTableOperationsAudits"); assertEquals(1, findAuditMessage(auditMessages, "action: createTable; targetTable: " + OLD_TEST_TABLE_NAME).size()); assertEquals(1, findAuditMessage(auditMessages, "action: renameTable; targetTable: " + OLD_TEST_TABLE_NAME).size()); assertEquals(1, findAuditMessage(auditMessages, "action: cloneTable; targetTable: " + NEW_TEST_TABLE_NAME).size()); assertEquals(1, findAuditMessage(auditMessages, "action: deleteTable; targetTable: " + OLD_TEST_TABLE_NAME).size()); assertEquals(1, findAuditMessage(auditMessages, "action: offlineTable; targetTable: " + NEW_TEST_TABLE_NAME) .size()); assertEquals(1, findAuditMessage(auditMessages, "action: deleteTable; targetTable: " + NEW_TEST_TABLE_NAME).size()); } @Test(timeout = 60 * 1000) public void testUserOperationsAudits() throws AccumuloSecurityException, AccumuloException, TableExistsException, InterruptedException, IOException { conn.securityOperations().createLocalUser(AUDIT_USER_1, new PasswordToken(PASSWORD)); conn.securityOperations().grantSystemPermission(AUDIT_USER_1, SystemPermission.SYSTEM); conn.securityOperations().grantSystemPermission(AUDIT_USER_1, SystemPermission.CREATE_USER); grantEverySystemPriv(conn, AUDIT_USER_1); // Connect as Audit User and do a bunch of stuff. // Start testing activities here auditConnector = accumulo.getConnector(AUDIT_USER_1, PASSWORD); auditConnector.securityOperations().createLocalUser(AUDIT_USER_2, new PasswordToken(PASSWORD)); // It seems only root can grant stuff. conn.securityOperations().grantSystemPermission(AUDIT_USER_2, SystemPermission.ALTER_TABLE); conn.securityOperations().revokeSystemPermission(AUDIT_USER_2, SystemPermission.ALTER_TABLE); auditConnector.tableOperations().create(NEW_TEST_TABLE_NAME); conn.securityOperations().grantTablePermission(AUDIT_USER_2, NEW_TEST_TABLE_NAME, TablePermission.READ); conn.securityOperations().revokeTablePermission(AUDIT_USER_2, NEW_TEST_TABLE_NAME, TablePermission.READ); auditConnector.securityOperations().changeLocalUserPassword(AUDIT_USER_2, new PasswordToken("anything")); auditConnector.securityOperations().changeUserAuthorizations(AUDIT_USER_2, auths); auditConnector.securityOperations().dropLocalUser(AUDIT_USER_2); // Stop testing activities here ArrayList<String> auditMessages = getAuditMessages("testUserOperationsAudits"); assertEquals(1, findAuditMessage(auditMessages, "action: createUser; targetUser: " + AUDIT_USER_2).size()); assertEquals(1, findAuditMessage(auditMessages, "action: grantSystemPermission; permission: " + SystemPermission.ALTER_TABLE.toString() + "; targetUser: " + AUDIT_USER_2).size()); assertEquals(1, findAuditMessage(auditMessages, "action: revokeSystemPermission; permission: " + SystemPermission.ALTER_TABLE.toString() + "; targetUser: " + AUDIT_USER_2).size()); assertEquals(1, findAuditMessage(auditMessages, "action: grantTablePermission; permission: " + TablePermission.READ.toString() + "; targetTable: " + NEW_TEST_TABLE_NAME).size()); assertEquals(1, findAuditMessage(auditMessages, "action: revokeTablePermission; permission: " + TablePermission.READ.toString() + "; targetTable: " + NEW_TEST_TABLE_NAME).size()); assertEquals(1, findAuditMessage(auditMessages, "action: changePassword; targetUser: " + AUDIT_USER_2 + "").size()); assertEquals(1, findAuditMessage(auditMessages, "action: changeAuthorizations; targetUser: " + AUDIT_USER_2 + "; authorizations: " + auths.toString()).size()); assertEquals(1, findAuditMessage(auditMessages, "action: dropUser; targetUser: " + AUDIT_USER_2).size()); } @Test(timeout = 60 * 1000) public void testImportExportOperationsAudits() throws AccumuloSecurityException, AccumuloException, TableExistsException, TableNotFoundException, IOException, InterruptedException { conn.securityOperations().createLocalUser(AUDIT_USER_1, new PasswordToken(PASSWORD)); conn.securityOperations().grantSystemPermission(AUDIT_USER_1, SystemPermission.SYSTEM); conn.securityOperations().changeUserAuthorizations(AUDIT_USER_1, auths); grantEverySystemPriv(conn, AUDIT_USER_1); // Connect as Audit User and do a bunch of stuff. // Start testing activities here auditConnector = accumulo.getConnector(AUDIT_USER_1, PASSWORD); auditConnector.tableOperations().create(OLD_TEST_TABLE_NAME); // Insert some play data BatchWriter bw = auditConnector.createBatchWriter(OLD_TEST_TABLE_NAME, new BatchWriterConfig()); Mutation m = new Mutation("myRow"); m.put("cf1", "cq1", "v1"); m.put("cf1", "cq2", "v3"); bw.addMutation(m); bw.close(); // Prepare to export the table File exportDir = new File(accumulo.getConfig().getDir().toString() + "/export"); auditConnector.tableOperations().offline(OLD_TEST_TABLE_NAME); auditConnector.tableOperations().exportTable(OLD_TEST_TABLE_NAME, exportDir.toString()); // We've exported the table metadata to the MiniAccumuloCluster root dir. Grab the .rf file path to re-import it File distCpTxt = new File(exportDir.toString() + "/distcp.txt"); File importFile = null; LineIterator it = FileUtils.lineIterator(distCpTxt, Constants.UTF8.name()); // Just grab the first rf file, it will do for now. String filePrefix = "file:"; try { while (it.hasNext() && importFile == null) { String line = it.nextLine(); if (line.matches(".*\\.rf")) { importFile = new File(line.replaceFirst(filePrefix, "")); } } } finally { LineIterator.closeQuietly(it); } FileUtils.copyFileToDirectory(importFile, exportDir); auditConnector.tableOperations().importTable(NEW_TEST_TABLE_NAME, exportDir.toString()); // Now do a Directory (bulk) import of the same data. auditConnector.tableOperations().create(THIRD_TEST_TABLE_NAME); File failDir = new File(exportDir + "/tmp"); failDir.mkdirs(); auditConnector.tableOperations().importDirectory(THIRD_TEST_TABLE_NAME, exportDir.toString(), failDir.toString(), false); auditConnector.tableOperations().online(OLD_TEST_TABLE_NAME); // Stop testing activities here ArrayList<String> auditMessages = getAuditMessages("testImportExportOperationsAudits"); assertEquals(1, findAuditMessage(auditMessages, String .format(AuditedSecurityOperation.CAN_CREATE_TABLE_AUDIT_TEMPLATE, OLD_TEST_TABLE_NAME)) .size()); assertEquals(1, findAuditMessage(auditMessages, String.format(AuditedSecurityOperation.CAN_ONLINE_OFFLINE_TABLE_AUDIT_TEMPLATE, "offlineTable", OLD_TEST_TABLE_NAME)).size()); assertEquals(1, findAuditMessage(auditMessages, String.format(AuditedSecurityOperation.CAN_EXPORT_AUDIT_TEMPLATE, OLD_TEST_TABLE_NAME, exportDir.toString())).size()); assertEquals(1, findAuditMessage(auditMessages, String.format(AuditedSecurityOperation.CAN_IMPORT_AUDIT_TEMPLATE, NEW_TEST_TABLE_NAME, exportDir.toString())).size()); assertEquals(1, findAuditMessage(auditMessages, String .format(AuditedSecurityOperation.CAN_CREATE_TABLE_AUDIT_TEMPLATE, THIRD_TEST_TABLE_NAME)) .size()); assertEquals(1, findAuditMessage(auditMessages, String.format(AuditedSecurityOperation.CAN_BULK_IMPORT_AUDIT_TEMPLATE, THIRD_TEST_TABLE_NAME, filePrefix + exportDir.toString(), filePrefix + failDir.toString())).size()); assertEquals(1, findAuditMessage(auditMessages, String.format(AuditedSecurityOperation.CAN_ONLINE_OFFLINE_TABLE_AUDIT_TEMPLATE, "onlineTable", OLD_TEST_TABLE_NAME)).size()); } @Test(timeout = 60 * 1000) public void testDataOperationsAudits() throws AccumuloSecurityException, AccumuloException, TableExistsException, TableNotFoundException, IOException, InterruptedException { conn.securityOperations().createLocalUser(AUDIT_USER_1, new PasswordToken(PASSWORD)); conn.securityOperations().grantSystemPermission(AUDIT_USER_1, SystemPermission.SYSTEM); conn.securityOperations().changeUserAuthorizations(AUDIT_USER_1, auths); grantEverySystemPriv(conn, AUDIT_USER_1); // Connect as Audit User and do a bunch of stuff. // Start testing activities here auditConnector = accumulo.getConnector(AUDIT_USER_1, PASSWORD); auditConnector.tableOperations().create(OLD_TEST_TABLE_NAME); // Insert some play data BatchWriter bw = auditConnector.createBatchWriter(OLD_TEST_TABLE_NAME, new BatchWriterConfig()); Mutation m = new Mutation("myRow"); m.put("cf1", "cq1", "v1"); m.put("cf1", "cq2", "v3"); bw.addMutation(m); bw.close(); // Start testing activities here // A regular scan Scanner scanner = auditConnector.createScanner(OLD_TEST_TABLE_NAME, auths); for (Map.Entry<Key, Value> entry : scanner) { System.out.println("Scanner row: " + entry.getKey() + " " + entry.getValue()); } scanner.close(); // A batch scan BatchScanner bs = auditConnector.createBatchScanner(OLD_TEST_TABLE_NAME, auths, 1); bs.fetchColumn(new Text("cf1"), new Text("cq1")); bs.setRanges(Arrays.asList(new Range("myRow", "myRow~"))); for (Map.Entry<Key, Value> entry : bs) { System.out.println("BatchScanner row: " + entry.getKey() + " " + entry.getValue()); } bs.close(); // Delete some data. auditConnector.tableOperations().deleteRows(OLD_TEST_TABLE_NAME, new Text("myRow"), new Text("myRow~")); // End of testing activities ArrayList<String> auditMessages = getAuditMessages("testDataOperationsAudits"); assertTrue( 1 <= findAuditMessage(auditMessages, "action: scan; targetTable: " + OLD_TEST_TABLE_NAME).size()); assertTrue( 1 <= findAuditMessage(auditMessages, "action: scan; targetTable: " + OLD_TEST_TABLE_NAME).size()); assertEquals(1, findAuditMessage(auditMessages, String.format(AuditedSecurityOperation.CAN_DELETE_RANGE_AUDIT_TEMPLATE, OLD_TEST_TABLE_NAME, "myRow", "myRow~")).size()); } @Test(timeout = 60 * 1000) public void testDeniedAudits() throws AccumuloSecurityException, AccumuloException, TableExistsException, TableNotFoundException, IOException, InterruptedException { // Create our user with no privs conn.securityOperations().createLocalUser(AUDIT_USER_1, new PasswordToken(PASSWORD)); conn.tableOperations().create(OLD_TEST_TABLE_NAME); auditConnector = accumulo.getConnector(AUDIT_USER_1, PASSWORD); // Start testing activities // We should get denied or / failed audit messages here. // We don't want the thrown exceptions to stop our tests, and we are not testing that the Exceptions are thrown. try { auditConnector.tableOperations().create(NEW_TEST_TABLE_NAME); } catch (AccumuloSecurityException ex) { } try { auditConnector.tableOperations().rename(OLD_TEST_TABLE_NAME, NEW_TEST_TABLE_NAME); } catch (AccumuloSecurityException ex) { } try { auditConnector.tableOperations().clone(OLD_TEST_TABLE_NAME, NEW_TEST_TABLE_NAME, true, Collections.<String, String>emptyMap(), Collections.<String>emptySet()); } catch (AccumuloSecurityException ex) { } try { auditConnector.tableOperations().delete(OLD_TEST_TABLE_NAME); } catch (AccumuloSecurityException ex) { } try { auditConnector.tableOperations().offline(OLD_TEST_TABLE_NAME); } catch (AccumuloSecurityException ex) { } try { Scanner scanner = auditConnector.createScanner(OLD_TEST_TABLE_NAME, auths); scanner.iterator().next().getKey(); } catch (RuntimeException ex) { } try { auditConnector.tableOperations().deleteRows(OLD_TEST_TABLE_NAME, new Text("myRow"), new Text("myRow~")); } catch (AccumuloSecurityException ex) { } // ... that will do for now. // End of testing activities ArrayList<String> auditMessages = getAuditMessages("testDeniedAudits"); assertEquals(1, findAuditMessage(auditMessages, "operation: denied;.*" + String.format( AuditedSecurityOperation.CAN_CREATE_TABLE_AUDIT_TEMPLATE, NEW_TEST_TABLE_NAME)) .size()); assertEquals(1, findAuditMessage(auditMessages, "operation: denied;.*" + String.format(AuditedSecurityOperation.CAN_RENAME_TABLE_AUDIT_TEMPLATE, OLD_TEST_TABLE_NAME, NEW_TEST_TABLE_NAME)).size()); assertEquals(1, findAuditMessage(auditMessages, "operation: denied;.*" + String.format(AuditedSecurityOperation.CAN_CLONE_TABLE_AUDIT_TEMPLATE, OLD_TEST_TABLE_NAME, NEW_TEST_TABLE_NAME)).size()); assertEquals(1, findAuditMessage(auditMessages, "operation: denied;.*" + String.format( AuditedSecurityOperation.CAN_DELETE_TABLE_AUDIT_TEMPLATE, OLD_TEST_TABLE_NAME)) .size()); assertEquals(1, findAuditMessage(auditMessages, "operation: denied;.*" + String.format(AuditedSecurityOperation.CAN_ONLINE_OFFLINE_TABLE_AUDIT_TEMPLATE, "offlineTable", OLD_TEST_TABLE_NAME)).size()); assertEquals(1, findAuditMessage(auditMessages, "operation: denied;.*" + "action: scan; targetTable: " + OLD_TEST_TABLE_NAME).size()); assertEquals(1, findAuditMessage(auditMessages, "operation: denied;.*" + String.format(AuditedSecurityOperation.CAN_DELETE_RANGE_AUDIT_TEMPLATE, OLD_TEST_TABLE_NAME, "myRow", "myRow~")).size()); } @Test(timeout = 60 * 1000) public void testFailedAudits() throws AccumuloSecurityException, AccumuloException, TableExistsException, TableNotFoundException, IOException, InterruptedException { // Start testing activities // Test that we get a few "failed" audit messages come through when we tell it to do dumb stuff // We don't want the thrown exceptions to stop our tests, and we are not testing that the Exceptions are thrown. try { conn.securityOperations().dropLocalUser(AUDIT_USER_2); } catch (AccumuloSecurityException ex) { } try { conn.securityOperations().revokeSystemPermission(AUDIT_USER_2, SystemPermission.ALTER_TABLE); } catch (AccumuloSecurityException ex) { } try { conn.securityOperations().createLocalUser("root", new PasswordToken("super secret")); } catch (AccumuloSecurityException ex) { } ArrayList<String> auditMessages = getAuditMessages("testFailedAudits"); // ... that will do for now. // End of testing activities assertEquals(1, findAuditMessage(auditMessages, String.format(AuditedSecurityOperation.DROP_USER_AUDIT_TEMPLATE, AUDIT_USER_2)).size()); assertEquals(1, findAuditMessage(auditMessages, String.format(AuditedSecurityOperation.REVOKE_SYSTEM_PERMISSION_AUDIT_TEMPLATE, SystemPermission.ALTER_TABLE, AUDIT_USER_2)).size()); assertEquals(1, findAuditMessage(auditMessages, String.format(AuditedSecurityOperation.CREATE_USER_AUDIT_TEMPLATE, "root", "")).size()); } @AfterClass public static void tearDownMiniCluster() throws Exception { accumulo.stop(); // Comment this out to have a look at the logs, they will be in /tmp/junit* folder.delete(); } }