Repository: cassandra Updated Branches: refs/heads/trunk f46762eec -> 9944d9e24
Adding more test coverage for authenticated user login audit activity Patch by Vinay Chella; reviewed by marcuse for CASSANDRA-14901 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/9944d9e2 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/9944d9e2 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/9944d9e2 Branch: refs/heads/trunk Commit: 9944d9e24b09ce4ed51c5082771e1b948fe1e698 Parents: f46762e Author: Vinay Chella <vinaykumar...@gmail.com> Authored: Mon Nov 19 02:09:24 2018 -0800 Committer: Marcus Eriksson <marc...@apache.org> Committed: Tue Nov 20 06:05:42 2018 +0100 ---------------------------------------------------------------------- .../cassandra/audit/AuditLoggerAuthTest.java | 291 +++++++++++++++++++ .../apache/cassandra/audit/AuditLoggerTest.java | 9 + 2 files changed, 300 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/9944d9e2/test/unit/org/apache/cassandra/audit/AuditLoggerAuthTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/audit/AuditLoggerAuthTest.java b/test/unit/org/apache/cassandra/audit/AuditLoggerAuthTest.java new file mode 100644 index 0000000..1105770 --- /dev/null +++ b/test/unit/org/apache/cassandra/audit/AuditLoggerAuthTest.java @@ -0,0 +1,291 @@ +/* + * 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.cassandra.audit; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Queue; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.datastax.driver.core.Cluster; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.exceptions.AuthenticationException; +import com.datastax.driver.core.exceptions.UnauthorizedException; +import org.apache.cassandra.OrderedJUnit4ClassRunner; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.OverrideConfigurationLoader; +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.service.EmbeddedCassandraService; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * AuditLoggerAuthTest class is responsible for covering test cases for Authenticated user (LOGIN) audits. + * Non authenticated tests are covered in {@link AuditLoggerTest} + */ + +@RunWith(OrderedJUnit4ClassRunner.class) +public class AuditLoggerAuthTest +{ + private static EmbeddedCassandraService embedded; + + private static final String TEST_USER = "testuser"; + private static final String TEST_ROLE = "testrole"; + private static final String TEST_PW = "testpassword"; + private static final String CASS_USER = "cassandra"; + private static final String CASS_PW = "cassandra"; + + @BeforeClass + public static void setup() throws Exception + { + OverrideConfigurationLoader.override((config) -> { + config.authenticator = "PasswordAuthenticator"; + config.role_manager = "CassandraRoleManager"; + config.authorizer = "CassandraAuthorizer"; + config.audit_logging_options.enabled = true; + config.audit_logging_options.logger = "InMemoryAuditLogger"; + }); + CQLTester.prepareServer(); + + System.setProperty("cassandra.superuser_setup_delay_ms", "0"); + embedded = new EmbeddedCassandraService(); + embedded.start(); + + executeWithCredentials( + Arrays.asList(getCreateRoleCql(TEST_USER, true, false), + getCreateRoleCql("testuser_nologin", false, false), + "CREATE KEYSPACE testks WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", + "CREATE TABLE testks.table1 (key text PRIMARY KEY, col1 int, col2 int)"), + "cassandra", "cassandra", null); + } + + @AfterClass + public static void shutdown() + { + embedded.stop(); + } + + @Before + public void clearInMemoryLogger() + { + getInMemAuditLogger().clear(); + } + + @Test + public void testCqlLoginAuditing() throws Throwable + { + executeWithCredentials(Collections.emptyList(), TEST_USER, "wrongpassword", + AuditLogEntryType.LOGIN_ERROR); + assertEquals(0, getInMemAuditLogger().size()); + clearInMemoryLogger(); + + executeWithCredentials(Collections.emptyList(), "wronguser", TEST_PW, AuditLogEntryType.LOGIN_ERROR); + assertEquals(0, getInMemAuditLogger().size()); + clearInMemoryLogger(); + + executeWithCredentials(Collections.emptyList(), "testuser_nologin", + TEST_PW, AuditLogEntryType.LOGIN_ERROR); + assertEquals(0, getInMemAuditLogger().size()); + clearInMemoryLogger(); + + executeWithCredentials(Collections.emptyList(), TEST_USER, TEST_PW, AuditLogEntryType.LOGIN_SUCCESS); + assertEquals(0, getInMemAuditLogger().size()); + clearInMemoryLogger(); + } + + @Test + public void testCqlCreateRoleAuditing() + { + createTestRole(); + } + + @Test + public void testCqlALTERRoleAuditing() + { + createTestRole(); + String cql = "ALTER ROLE " + TEST_ROLE + " WITH PASSWORD = 'foo_bar'"; + executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS); + assertTrue(getInMemAuditLogger().size() > 0); + AuditLogEntry logEntry = getInMemAuditLogger().poll(); + assertLogEntry(logEntry, AuditLogEntryType.ALTER_ROLE, cql, CASS_USER); + assertEquals(0, getInMemAuditLogger().size()); + } + + @Test + public void testCqlDROPRoleAuditing() + { + createTestRole(); + String cql = "DROP ROLE " + TEST_ROLE; + executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS); + assertTrue(getInMemAuditLogger().size() > 0); + AuditLogEntry logEntry = getInMemAuditLogger().poll(); + assertLogEntry(logEntry, AuditLogEntryType.DROP_ROLE, cql, CASS_USER); + assertEquals(0, getInMemAuditLogger().size()); + } + + @Test + public void testCqlLISTROLESAuditing() + { + String cql = "LIST ROLES"; + executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS); + assertTrue(getInMemAuditLogger().size() > 0); + AuditLogEntry logEntry = getInMemAuditLogger().poll(); + assertLogEntry(logEntry, AuditLogEntryType.LIST_ROLES, cql, CASS_USER); + } + + @Test + public void testCqlLISTPERMISSIONSAuditing() + { + String cql = "LIST ALL"; + executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS); + assertTrue(getInMemAuditLogger().size() > 0); + AuditLogEntry logEntry = getInMemAuditLogger().poll(); + assertLogEntry(logEntry, AuditLogEntryType.LIST_PERMISSIONS, cql, CASS_USER); + } + + @Test + public void testCqlGRANTAuditing() + { + createTestRole(); + String cql = "GRANT SELECT ON ALL KEYSPACES TO " + TEST_ROLE; + executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS); + assertTrue(getInMemAuditLogger().size() > 0); + AuditLogEntry logEntry = getInMemAuditLogger().poll(); + assertLogEntry(logEntry, AuditLogEntryType.GRANT, cql, CASS_USER); + } + + @Test + public void testCqlREVOKEAuditing() + { + createTestRole(); + String cql = "REVOKE ALTER ON ALL KEYSPACES FROM " + TEST_ROLE; + executeWithCredentials(Arrays.asList(cql), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS); + assertTrue(getInMemAuditLogger().size() > 0); + AuditLogEntry logEntry = getInMemAuditLogger().poll(); + assertLogEntry(logEntry, AuditLogEntryType.REVOKE, cql, CASS_USER); + } + + @Test + public void testUNAUTHORIZED_ATTEMPTAuditing() + { + createTestRole(); + String cql = "ALTER ROLE " + TEST_ROLE + " WITH superuser = true"; + executeWithCredentials(Arrays.asList(cql), TEST_USER, TEST_PW, AuditLogEntryType.LOGIN_SUCCESS); + assertTrue(getInMemAuditLogger().size() > 0); + AuditLogEntry logEntry = getInMemAuditLogger().poll(); + assertLogEntry(logEntry, AuditLogEntryType.UNAUTHORIZED_ATTEMPT, cql, TEST_USER); + assertEquals(0, getInMemAuditLogger().size()); + } + + /** + * Helper methods + */ + + private static void executeWithCredentials(List<String> queries, String username, String password, + AuditLogEntryType expectedType) + { + boolean authFailed = false; + try (Cluster cluster = Cluster.builder().addContactPoints(InetAddress.getLoopbackAddress()) + .withoutJMXReporting() + .withCredentials(username, password) + .withPort(DatabaseDescriptor.getNativeTransportPort()).build()) + { + try (Session session = cluster.connect()) + { + for (String query : queries) + session.execute(query); + } + catch (AuthenticationException e) + { + authFailed = true; + } + catch (UnauthorizedException ue) + { + //no-op, taken care by caller + } + } + + if (expectedType != null) + { + assertTrue(getInMemAuditLogger().size() > 0); + AuditLogEntry logEntry = getInMemAuditLogger().poll(); + + assertEquals(expectedType, logEntry.getType()); + assertTrue(!authFailed || logEntry.getType() == AuditLogEntryType.LOGIN_ERROR); + assertSource(logEntry, username); + + // drain all remaining login related events, as there's no specification how connections and login attempts + // should be handled by the driver, so we can't assert a fixed number of login events + getInMemAuditLogger() + .removeIf(auditLogEntry -> auditLogEntry.getType() == AuditLogEntryType.LOGIN_ERROR + || auditLogEntry.getType() == AuditLogEntryType.LOGIN_SUCCESS); + } + } + + private static Queue<AuditLogEntry> getInMemAuditLogger() + { + return ((InMemoryAuditLogger) AuditLogManager.getInstance().getLogger()).inMemQueue; + } + + private static void assertLogEntry(AuditLogEntry logEntry, AuditLogEntryType type, String cql, String username) + { + assertSource(logEntry, username); + assertNotEquals(0, logEntry.getTimestamp()); + assertEquals(type, logEntry.getType()); + if (null != cql && !cql.isEmpty()) + { + assertThat(logEntry.getOperation(), containsString(cql)); + } + } + + private static void assertSource(AuditLogEntry logEntry, String username) + { + assertEquals(InetAddressAndPort.getLoopbackAddress().address, logEntry.getSource().address); + assertTrue(logEntry.getSource().port > 0); + if (logEntry.getType() != AuditLogEntryType.LOGIN_ERROR) + assertEquals(username, logEntry.getUser()); + } + + private static String getCreateRoleCql(String role, boolean login, boolean superUser) + { + return String.format("CREATE ROLE IF NOT EXISTS %s WITH LOGIN = %s AND SUPERUSER = %s AND PASSWORD = '%s'", + role, login, superUser, TEST_PW); + } + + private static void createTestRole() + { + String createTestRoleCQL = getCreateRoleCql(TEST_ROLE, true, false); + executeWithCredentials(Arrays.asList(createTestRoleCQL), CASS_USER, CASS_PW, AuditLogEntryType.LOGIN_SUCCESS); + assertTrue(getInMemAuditLogger().size() > 0); + AuditLogEntry logEntry = getInMemAuditLogger().poll(); + assertLogEntry(logEntry, AuditLogEntryType.CREATE_ROLE, createTestRoleCQL, CASS_USER); + assertEquals(0, getInMemAuditLogger().size()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cassandra/blob/9944d9e2/test/unit/org/apache/cassandra/audit/AuditLoggerTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/audit/AuditLoggerTest.java b/test/unit/org/apache/cassandra/audit/AuditLoggerTest.java index ac73504..b0299dc 100644 --- a/test/unit/org/apache/cassandra/audit/AuditLoggerTest.java +++ b/test/unit/org/apache/cassandra/audit/AuditLoggerTest.java @@ -38,7 +38,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +/** + * AuditLoggerTest is responsible for covering the test cases for Audit Logging CASSANDRA-12151 functionality. + * Authenticated user audit (LOGIN) tests are segregated from unauthenticated user audit tests. + */ public class AuditLoggerTest extends CQLTester { @BeforeClass @@ -290,6 +295,10 @@ public class AuditLoggerTest extends CQLTester assertEquals(5, ((InMemoryAuditLogger) AuditLogManager.getInstance().getLogger()).inMemQueue.size()); logEntry = ((InMemoryAuditLogger) AuditLogManager.getInstance().getLogger()).inMemQueue.poll(); + assertEquals(AuditLogEntryType.BATCH, logEntry.getType()); + assertTrue(logEntry.getOperation().contains("BatchId")); + assertNotEquals(0, logEntry.getTimestamp()); + logEntry = ((InMemoryAuditLogger) AuditLogManager.getInstance().getLogger()).inMemQueue.poll(); assertLogEntry(cqlInsert, AuditLogEntryType.UPDATE, logEntry, false); --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org