diff --git a/README.adoc b/README.adoc
index 7893863..7599197 100644
--- a/README.adoc
+++ b/README.adoc
@@ -26,7 +26,8 @@ There are five implementation modules:
* cassandra-3.0 - builds against version 3.0.28
* cassandra-3.11 - builds against version 3.11.14
* cassandra-4.0 - builds against version 4.0.7
-* cassandra-4.1 - builds aganst version 4.1.0
+* cassandra-4.1 - builds aganist version 4.1.0
+* cassandra-5.0 - builds aganist version 5.0.0
Project is built as:
diff --git a/cassandra-5.0/pom.xml b/cassandra-5.0/pom.xml
new file mode 100644
index 0000000..f42dc57
--- /dev/null
+++ b/cassandra-5.0/pom.xml
@@ -0,0 +1,181 @@
+
+
+ 4.0.0
+
+
+ com.instaclustr
+ cassandra-ldap-parent
+ 1.1.2
+ ../pom.xml
+
+
+ cassandra-ldap-5.0.0
+ 1.0.0
+
+ Cassandra LDAP Authenticator for Cassandra 5.0
+ Pluggable LDAP authentication implementation for Apache Cassandra 5.0
+
+
+ 5.0.0
+
+ 1.2.6
+ 3.1.3
+
+ 4.0.1
+ 3.11.0
+ 6.14.3
+ 4.0.3
+ 1.15.3
+
+
+
+
+
+ org.jboss.shrinkwrap
+ shrinkwrap-bom
+ ${version.shrinkwrap.bom}
+ pom
+ import
+
+
+
+
+
+
+ org.apache.cassandra
+ cassandra-all
+ ${version.cassandra50}
+ provided
+
+
+
+ com.instaclustr
+ cassandra-ldap-base
+ 1.1.2
+
+
+ org.apache.cassandra
+ cassandra-all
+
+
+
+
+
+
+
+ org.jboss.shrinkwrap
+ shrinkwrap-depchain
+ pom
+ test
+
+
+
+ org.jboss.shrinkwrap.resolver
+ shrinkwrap-resolver-depchain
+ ${version.shrinkwrap.resolvers}
+ test
+ pom
+
+
+
+ com.github.nosan
+ embedded-cassandra
+ ${version.embedded.cassandra}
+ test
+
+
+ com.datastax.oss
+ java-driver-core
+
+
+
+
+
+ com.datastax.cassandra
+ cassandra-driver-core
+ ${version.cassandra.driver}
+
+
+ com.google.guava
+ guava
+
+
+ io.netty
+ netty-handler
+
+
+ io.netty
+ netty-buffer
+
+
+ io.netty
+ netty-codec
+
+
+
+
+
+ org.testng
+ testng
+ ${version.testng}
+ test
+
+
+
+ org.awaitility
+ awaitility
+ ${version.awaitility}
+ test
+
+
+
+ org.testcontainers
+ testcontainers
+ ${version.testcontainers}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${maven.shade.plugin.version}
+
+ cassandra-ldap-${version.cassandra41}-${project.version}
+
+
+
+ org.vafer
+ jdeb
+ ${version.jdeb}
+
+
+ de.dentrassi.maven
+ rpm
+ ${version.rpm}
+
+
+ package
+
+ rpm
+
+
+
+
+ cassandra
+ 4.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cassandra-5.0/src/deb/control/control b/cassandra-5.0/src/deb/control/control
new file mode 100644
index 0000000..bf616c9
--- /dev/null
+++ b/cassandra-5.0/src/deb/control/control
@@ -0,0 +1,7 @@
+Package: [[name]]
+Version: [[version]]
+Section: misc
+Priority: optional
+Architecture: all
+Depends: cassandra (>= 5.0)
+Maintainer: [[maintainer]]
diff --git a/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/auth/Cassandra50SystemAuthRoles.java b/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/auth/Cassandra50SystemAuthRoles.java
new file mode 100644
index 0000000..9399e5b
--- /dev/null
+++ b/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/auth/Cassandra50SystemAuthRoles.java
@@ -0,0 +1,192 @@
+/*
+ * 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 com.instaclustr.cassandra.ldap.auth;
+
+import static java.lang.String.format;
+import static java.util.Collections.singletonList;
+import static org.apache.cassandra.db.ConsistencyLevel.LOCAL_ONE;
+
+import java.util.Collections;
+
+import com.google.common.base.Function;
+import org.apache.cassandra.auth.AuthKeyspace;
+import org.apache.cassandra.auth.LDAPCassandraRoleManager.Role;
+import org.apache.cassandra.cql3.CQLStatement;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.cql3.UntypedResultSet.Row;
+import org.apache.cassandra.cql3.statements.CreateRoleStatement;
+import org.apache.cassandra.cql3.statements.SelectStatement;
+import org.apache.cassandra.cql3.statements.GrantRoleStatement;
+import org.apache.cassandra.db.ConsistencyLevel;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.exceptions.RequestExecutionException;
+import org.apache.cassandra.exceptions.RequestValidationException;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.QueryState;
+import org.apache.cassandra.transport.Dispatcher;
+import org.apache.cassandra.transport.messages.ResultMessage;
+import org.apache.cassandra.utils.ByteBufferUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Cassandra50SystemAuthRoles implements SystemAuthRoles
+{
+
+ private static final Logger logger = LoggerFactory.getLogger(SystemAuthRoles.class);
+
+ public static final String SELECT_ROLE_STATEMENT = "SELECT role FROM %s.%s where role = ?";
+
+ public static final String CREATE_ROLE_STATEMENT_WITH_LOGIN = "CREATE ROLE IF NOT EXISTS \"%s\" WITH LOGIN = true AND SUPERUSER = %s";
+
+ public static final String GRANT_ROLE_STATEMENT = "GRANT '%s' TO '%s'";
+
+ private ClientState clientState;
+
+ public void setClientState(ClientState clientState)
+ {
+ this.clientState = clientState;
+ }
+
+ public ClientState getClientState()
+ {
+ return clientState;
+ }
+
+ public boolean hasAdminRole(String role) throws RequestExecutionException
+ {
+ // Try looking up the 'cassandra' default role first, to avoid the range query if possible.
+ String defaultSUQuery = "SELECT * FROM system_auth.roles WHERE role = '" + role + "'";
+ String allUsersQuery = "SELECT * FROM system_auth.roles LIMIT 1";
+ return !QueryProcessor.process(defaultSUQuery, ConsistencyLevel.ONE).isEmpty()
+ || !QueryProcessor.process(defaultSUQuery, ConsistencyLevel.QUORUM).isEmpty()
+ || !QueryProcessor.process(allUsersQuery, ConsistencyLevel.QUORUM).isEmpty();
+ }
+
+
+ public boolean hasAdminRole() throws RequestExecutionException
+ {
+ return hasAdminRole("cassandra");
+ }
+
+ public CQLStatement prepare(String template, String keyspace, String table) {
+ try {
+ return QueryProcessor.parseStatement(String.format(template, keyspace, table)).prepare(ClientState.forInternalCalls());
+ } catch (RequestValidationException e) {
+ throw new AssertionError(e); // not supposed to happen
+ }
+ }
+
+ protected ConsistencyLevel getConsistencyForRole(String defaultSuperUserName, String role, ConsistencyLevel roleConsistencyLevel) {
+ ConsistencyLevel cl = role.equals(defaultSuperUserName) ? ConsistencyLevel.QUORUM : roleConsistencyLevel;
+
+ logger.debug(String.format("Resolved consistency level for role %s: %s", role, cl));
+
+ return cl;
+ }
+
+ // NullObject returned when a supplied role name not found in AuthKeyspace.ROLES
+ protected static final Role NULL_ROLE = new Role(null, false, false, Collections.emptySet());
+
+ protected static final Function ROW_TO_ROLE = new Function() {
+ public Role apply(UntypedResultSet.Row row) {
+ try {
+ return new Role(row.getString("role"),
+ row.getBoolean("is_superuser"),
+ row.getBoolean("can_login"),
+ row.has("member_of") ? row.getSet("member_of", UTF8Type.instance)
+ : Collections.emptySet());
+ }
+ // Failing to deserialize a boolean in is_superuser or can_login will throw an NPE
+ catch (NullPointerException e) {
+ logger.warn("An invalid value has been detected in the {} table for role {}. If you are " +
+ "unable to login, you may need to disable authentication and confirm " +
+ "that values in that table are accurate", AuthKeyspace.ROLES, row.getString("role"));
+ throw new RuntimeException(String.format("Invalid metadata has been detected for role %s", row.getString("role")), e);
+ }
+
+ }
+ };
+
+
+ public boolean roleMissing(String dn) {
+ assert getClientState() != null;
+
+ final SelectStatement selStmt = (SelectStatement) QueryProcessor.getStatement(format(SELECT_ROLE_STATEMENT,
+ "system_auth",
+ AuthKeyspace.ROLES),
+ getClientState());
+
+ final ResultMessage.Rows rows = selStmt.execute(new QueryState(getClientState()),
+ QueryOptions.forInternalCalls(singletonList(ByteBufferUtil.bytes(dn))),
+ Dispatcher.RequestTime.forImmediateExecution());
+
+ return rows.result.isEmpty();
+ }
+
+ public void createRole(String roleName, boolean superUser, String defaultRoleMembership)
+ {
+ final CreateRoleStatement createStmt = (CreateRoleStatement) QueryProcessor.getStatement(format(CREATE_ROLE_STATEMENT_WITH_LOGIN,
+ roleName,
+ superUser),
+ getClientState());
+
+ createStmt.execute(new QueryState(getClientState()),
+ QueryOptions.forInternalCalls(LOCAL_ONE, singletonList(ByteBufferUtil.bytes(roleName))),
+ Dispatcher.RequestTime.forImmediateExecution());
+
+ if (defaultRoleMembership != null)
+ {
+ if (roleMissing(defaultRoleMembership))
+ {
+ logger.warn("Unable to add user to default role {} because it doesn't exist.", defaultRoleMembership);
+ }
+ else
+ {
+ logger.debug("Adding user {} to default role {}", roleName, defaultRoleMembership);
+ final GrantRoleStatement grantRoleStmt = (GrantRoleStatement) QueryProcessor.getStatement(format(GRANT_ROLE_STATEMENT,
+ defaultRoleMembership,
+ roleName),
+ getClientState());
+
+ grantRoleStmt.execute(new QueryState(getClientState()),
+ QueryOptions.forInternalCalls(LOCAL_ONE, singletonList(ByteBufferUtil.bytes(roleName))),
+ Dispatcher.RequestTime.forImmediateExecution());
+ }
+ }
+ }
+
+ @Override
+ public Role getRole(String name, ConsistencyLevel roleConsistencyLevel)
+ throws RequestExecutionException, RequestValidationException {
+
+ SelectStatement loadRoleStatement = (SelectStatement) prepare("SELECT * from %s.%s WHERE role = ?", "system_auth", "roles");
+
+ ResultMessage.Rows rows = loadRoleStatement.execute(QueryState.forInternalCalls(),
+ QueryOptions.forInternalCalls(getConsistencyForRole("cassandra", name, roleConsistencyLevel),
+ Collections.singletonList(ByteBufferUtil.bytes(name))),
+ Dispatcher.RequestTime.forImmediateExecution());
+
+ if (rows.result.isEmpty()) {
+ return NULL_ROLE;
+ }
+
+ return ROW_TO_ROLE.apply(UntypedResultSet.create(rows.result).one());
+ }
+}
diff --git a/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/auth/Cassandra50UserRetriever.java b/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/auth/Cassandra50UserRetriever.java
new file mode 100644
index 0000000..f48176a
--- /dev/null
+++ b/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/auth/Cassandra50UserRetriever.java
@@ -0,0 +1,95 @@
+/*
+ * 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 com.instaclustr.cassandra.ldap.auth;
+
+import static java.util.Collections.singletonList;
+
+import com.instaclustr.cassandra.ldap.User;
+import org.apache.cassandra.cql3.QueryOptions;
+import org.apache.cassandra.cql3.QueryProcessor;
+import org.apache.cassandra.cql3.statements.SelectStatement;
+import org.apache.cassandra.schema.Schema;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.QueryState;
+import org.apache.cassandra.transport.Dispatcher;
+import org.apache.cassandra.transport.messages.ResultMessage.Rows;
+import org.apache.cassandra.utils.ByteBufferUtil;
+
+public class Cassandra50UserRetriever extends AbstractCassandraUserRetriever
+{
+
+ @Override
+ public void init(ClientState clientState)
+ {
+ this.clientState = clientState;
+ authenticateStatement = (SelectStatement) QueryProcessor.getStatement("SELECT salted_hash FROM system_auth.roles WHERE role = ?", clientState);
+
+ legacyTableExists = legacyCredentialsTableExists();
+
+ if (legacyTableExists)
+ {
+ prepareLegacyAuthenticateStatementInternal(clientState);
+ }
+ }
+
+ @Override
+ public Rows getRows(User user)
+ {
+ return authenticationStatement(clientState, legacyTableExists).execute(QueryState.forInternalCalls(),
+ QueryOptions.forInternalCalls(consistencyForRole(user.getUsername()),
+ singletonList(ByteBufferUtil.bytes(user.getUsername()))),
+ Dispatcher.RequestTime.forImmediateExecution());
+ }
+
+ @Override
+ protected void prepareLegacyAuthenticateStatementInternal(final ClientState clientState)
+ {
+ String query = String.format("SELECT salted_hash from %s.%s WHERE username = ?",
+ AUTH_KEYSPACE,
+ LEGACY_CREDENTIALS_TABLE);
+ legacyAuthenticateStatement = (SelectStatement) QueryProcessor.getStatement(query, clientState);
+ }
+
+ /**
+ * If the legacy users table exists try to verify credentials there. This is to handle the case
+ * where the cluster is being upgraded and so is running with mixed versions of the auth tables
+ */
+ protected SelectStatement authenticationStatement(final ClientState clientState,
+ final boolean legacyTableExists)
+ {
+ if (!legacyTableExists)
+ {
+ return (SelectStatement) QueryProcessor.getStatement("SELECT salted_hash FROM system_auth.roles WHERE role = ?", clientState);
+ } else
+ {
+ // the statement got prepared, we to try preparing it again.
+ // If the credentials was initialised only after statement got prepared, re-prepare (CASSANDRA-12813).
+ if (legacyAuthenticateStatement == null)
+ {
+ prepareLegacyAuthenticateStatementInternal(clientState);
+ }
+ return legacyAuthenticateStatement;
+ }
+ }
+
+ @Override
+ protected boolean legacyCredentialsTableExists()
+ {
+ return Schema.instance.getTableMetadata(AUTH_KEYSPACE, LEGACY_CREDENTIALS_TABLE) != null;
+ }
+}
diff --git a/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/cache/Cassandra50CacheDelegate.java b/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/cache/Cassandra50CacheDelegate.java
new file mode 100644
index 0000000..cf0bdb8
--- /dev/null
+++ b/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/cache/Cassandra50CacheDelegate.java
@@ -0,0 +1,106 @@
+package com.instaclustr.cassandra.ldap.cache;
+
+import java.util.HashMap;
+import java.util.function.Function;
+
+import com.instaclustr.cassandra.ldap.User;
+import org.apache.cassandra.auth.AuthCache;
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Cassandra50CacheDelegate implements CacheDelegate
+{
+
+ private static final Logger logger = LoggerFactory.getLogger(Cassandra50CacheDelegate.class);
+
+ private AuthCache cassandraCache;
+ private AuthCache ldapCache;
+
+ @Override
+ public void invalidate(final User user)
+ {
+ assert cassandraCache != null;
+ this.cassandraCache.invalidate(user);
+ this.ldapCache.invalidate(user);
+ }
+
+ @Override
+ public User get(final User user)
+ {
+ assert cassandraCache != null;
+ assert ldapCache != null;
+
+ try
+ {
+ try
+ {
+ User cassandraUser = this.cassandraCache.get(user);
+
+ if (cassandraUser != null)
+ {
+ logger.info("Fetching user from Cassandra: " + user.toString());
+ return cassandraUser;
+ }
+ } catch (final Exception ex)
+ {
+ logger.info("{} not found in Cassandra", user);
+ }
+
+ User ldapUser = this.ldapCache.get(user);
+
+ logger.debug("{} fetched user from LDAP", ldapUser);
+
+ return ldapUser;
+ } catch (final Exception ex)
+ {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public void init(final Function cassandraLoadingFunction,
+ final Function ldapLoadingFunction,
+ final boolean enableCache)
+ {
+ if (this.cassandraCache != null && this.ldapCache != null)
+ {
+ return;
+ }
+
+ this.cassandraCache = new CredentialsCache(cassandraLoadingFunction, "CredentialsCache", enableCache);
+ this.ldapCache = new CredentialsCache(ldapLoadingFunction, "LdapCredentialsCache", enableCache);
+ }
+
+ private static class CredentialsCache extends AuthCache implements CredentialsCacheMBean
+ {
+
+ private static final Logger logger = LoggerFactory.getLogger(CredentialsCache.class);
+
+ public CredentialsCache(Function loadingFunction, String cacheName, boolean enableCache)
+ {
+ super(cacheName,
+ DatabaseDescriptor::setCredentialsValidity,
+ DatabaseDescriptor::getCredentialsValidity,
+ DatabaseDescriptor::setCredentialsUpdateInterval,
+ DatabaseDescriptor::getCredentialsUpdateInterval,
+ DatabaseDescriptor::setCredentialsCacheMaxEntries,
+ DatabaseDescriptor::getCredentialsCacheMaxEntries,
+ DatabaseDescriptor::setCredentialsCacheActiveUpdate,
+ DatabaseDescriptor::getCredentialsCacheActiveUpdate,
+ loadingFunction,
+ HashMap::new,
+ () ->
+ {
+ logger.info(String.format("Using cache %s, enabled: %s", cacheName, enableCache));
+
+ return enableCache;
+ });
+ }
+
+ public void invalidateCredentials(String username)
+ {
+ invalidate(new User(username));
+ }
+ }
+}
diff --git a/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/cache/CredentialsCacheMBean.java b/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/cache/CredentialsCacheMBean.java
new file mode 100644
index 0000000..32b5fec
--- /dev/null
+++ b/cassandra-5.0/src/main/java/com/instaclustr/cassandra/ldap/cache/CredentialsCacheMBean.java
@@ -0,0 +1,9 @@
+package com.instaclustr.cassandra.ldap.cache;
+
+import org.apache.cassandra.auth.AuthCacheMBean;
+
+public interface CredentialsCacheMBean extends AuthCacheMBean
+{
+
+ void invalidateCredentials(String username);
+}
diff --git a/cassandra-5.0/src/main/resources/META-INF/services/com.instaclustr.cassandra.ldap.auth.CassandraUserRetriever b/cassandra-5.0/src/main/resources/META-INF/services/com.instaclustr.cassandra.ldap.auth.CassandraUserRetriever
new file mode 100644
index 0000000..bbdd8cd
--- /dev/null
+++ b/cassandra-5.0/src/main/resources/META-INF/services/com.instaclustr.cassandra.ldap.auth.CassandraUserRetriever
@@ -0,0 +1 @@
+com.instaclustr.cassandra.ldap.auth.Cassandra50UserRetriever
\ No newline at end of file
diff --git a/cassandra-5.0/src/main/resources/META-INF/services/com.instaclustr.cassandra.ldap.auth.SystemAuthRoles b/cassandra-5.0/src/main/resources/META-INF/services/com.instaclustr.cassandra.ldap.auth.SystemAuthRoles
new file mode 100644
index 0000000..5712a42
--- /dev/null
+++ b/cassandra-5.0/src/main/resources/META-INF/services/com.instaclustr.cassandra.ldap.auth.SystemAuthRoles
@@ -0,0 +1 @@
+com.instaclustr.cassandra.ldap.auth.Cassandra50SystemAuthRoles
\ No newline at end of file
diff --git a/cassandra-5.0/src/main/resources/META-INF/services/com.instaclustr.cassandra.ldap.cache.CacheDelegate b/cassandra-5.0/src/main/resources/META-INF/services/com.instaclustr.cassandra.ldap.cache.CacheDelegate
new file mode 100644
index 0000000..1d5a26f
--- /dev/null
+++ b/cassandra-5.0/src/main/resources/META-INF/services/com.instaclustr.cassandra.ldap.cache.CacheDelegate
@@ -0,0 +1 @@
+com.instaclustr.cassandra.ldap.cache.Cassandra50CacheDelegate
\ No newline at end of file
diff --git a/cassandra-5.0/src/test/java/com/instaclustr/cassandra/ldap/auth/AbstractLDAPTest.java b/cassandra-5.0/src/test/java/com/instaclustr/cassandra/ldap/auth/AbstractLDAPTest.java
new file mode 100644
index 0000000..9e32fc4
--- /dev/null
+++ b/cassandra-5.0/src/test/java/com/instaclustr/cassandra/ldap/auth/AbstractLDAPTest.java
@@ -0,0 +1,348 @@
+/*
+ * 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 com.instaclustr.cassandra.ldap.auth;
+
+import java.io.BufferedInputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.datastax.driver.core.Cluster;
+import com.datastax.driver.core.PlainTextAuthProvider;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.policies.DCAwareRoundRobinPolicy;
+import com.github.nosan.embedded.cassandra.Cassandra;
+import com.github.nosan.embedded.cassandra.CassandraBuilder;
+import com.github.nosan.embedded.cassandra.WorkingDirectoryCustomizer;
+import com.github.nosan.embedded.cassandra.WorkingDirectoryDestroyer;
+import com.github.nosan.embedded.cassandra.commons.ClassPathResource;
+import com.github.nosan.embedded.cassandra.commons.FileSystemResource;
+import org.testcontainers.containers.Container;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+import static com.github.nosan.embedded.cassandra.WorkingDirectoryCustomizer.addResource;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.toList;
+import static org.awaitility.Awaitility.await;
+import static org.awaitility.Durations.FIVE_MINUTES;
+import static org.jboss.shrinkwrap.resolver.api.maven.Maven.resolver;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public abstract class AbstractLDAPTest {
+
+ private static final Logger logger = LoggerFactory.getLogger(AbstractLDAPTest.class);
+ private static final String cassandraAdminUser = "cassandra";
+ private static final String cassandraAdminPassword = "cassandra";
+ private static final String cassandraDataCenter1 = "datacenter1";
+ private static final String testUserName = "bill";
+ private static final String testUserPassword = "test";
+ private static final String testUserDn = "cn=bill,dc=example,dc=org";
+ private static final String defaultRoleName = "default_role";
+ private static final String basicQuery = "SELECT * FROM system.local;";
+
+ protected void testLDAPinternal() throws Exception {
+ try (final GenericContainer ldapContainer = prepareLdapContainer();
+ final CassandraClusterContext context = getClusterContext(true, ldapContainer.getMappedPort(389))) {
+
+ context.start();
+
+ context.execute(context.firstNode,
+ cassandraAdminUser,
+ cassandraAdminPassword,
+ "ALTER KEYSPACE system_auth WITH replication = {'class': 'NetworkTopologyStrategy', 'datacenter1': 1, 'datacenter2':1};", cassandraDataCenter1, false);
+
+ logger.info("[first node]: login via cassandra");
+ context.execute(context.firstNode, cassandraAdminUser, cassandraAdminPassword, "select * from system_auth.roles", cassandraDataCenter1, true);
+ logger.info("[first node]: login bill");
+ context.execute(context.firstNode, testUserName, testUserPassword, basicQuery, cassandraDataCenter1, true);
+
+ logger.info("[second node]: login bill");
+ context.execute(context.secondNode, testUserName, testUserPassword, basicQuery, "datacenter2", true);
+
+ testDefaultRoleMembership(ldapContainer, context);
+ } catch (final Exception ex) {
+ fail("Exception occurred!", ex);
+ }
+ }
+
+ protected void testDefaultRoleMembership(GenericContainer ldapContainer, CassandraClusterContext context) throws Exception
+ {
+ // Create the default role
+ context.simpleExecute(
+ context.firstNode,
+ cassandraAdminUser,
+ cassandraAdminPassword,
+ String.format("CREATE ROLE '%s';", defaultRoleName),
+ cassandraDataCenter1
+ );
+
+ // Delete the user if it already exists
+ context.simpleExecute(
+ context.firstNode,
+ cassandraAdminUser,
+ cassandraAdminPassword,
+ String.format("DROP ROLE IF EXISTS '%s';", testUserDn),
+ cassandraDataCenter1
+ );
+
+ // Simulate a Logon as the user
+ context.simpleExecute(
+ context.firstNode,
+ testUserName,
+ testUserPassword,
+ basicQuery,
+ cassandraDataCenter1
+ );
+
+ // Check that the default role has been added to the user
+ assertTrue(
+ context.simpleExecute(
+ context.firstNode,
+ cassandraAdminUser,
+ cassandraAdminPassword,
+ String.format("LIST ROLES OF '%s';", testUserDn),
+ cassandraDataCenter1
+ ).all().stream().anyMatch(row -> row.getString("role").equals(defaultRoleName))
+ );
+ }
+
+ public abstract String getCassandraVersion();
+
+ public abstract String getImplementationGAV();
+
+ private CassandraClusterContext getClusterContext(boolean ldapEnabled, int ldapPort) {
+ CassandraClusterContext cassandraClusterContext = new CassandraClusterContext();
+ cassandraClusterContext.firstNode = configure(ldapEnabled, "first", ldapPort).build();
+ cassandraClusterContext.secondNode = configure(ldapEnabled, "second", ldapPort).build();
+ return cassandraClusterContext;
+ }
+
+ private static class CassandraClusterContext implements Closeable {
+
+ public Cassandra firstNode;
+ public Cassandra secondNode;
+
+ public void start() {
+ firstNode.start();
+ waitForOpenPort("127.0.0.1", 9042);
+ secondNode.start();
+ waitForOpenPort("127.0.0.2", 9042);
+ }
+
+ @Override
+ public void close() {
+ if (firstNode != null) {
+ firstNode.stop();
+ waitForClosedPort("127.0.0.1", 9042);
+ firstNode = null;
+ }
+
+ if (secondNode != null) {
+ secondNode.stop();
+ waitForClosedPort("127.0.0.2", 9042);
+ secondNode = null;
+ }
+ }
+
+ public synchronized void execute(Cassandra node,
+ String username,
+ String password,
+ String query,
+ String dc,
+ boolean check) {
+ execute(node.getSettings().getAddress(), username, password, query, dc, check);
+ }
+
+ public synchronized void execute(InetAddress point,
+ String username,
+ String password,
+ String query,
+ String dc,
+ boolean check) {
+ try (final Session session = Cluster.builder()
+ .addContactPoint(point.getHostAddress())
+ .withLoadBalancingPolicy(new DCAwareRoundRobinPolicy.Builder().withLocalDc(dc).build())
+ .withAuthProvider(new PlainTextAuthProvider(username, password))
+ .build().connect()) {
+ ResultSet execute = session.execute(query);
+
+ if (check) {
+ assertNotNull(execute);
+ assertFalse(execute.all().isEmpty());
+ assertTrue(execute.isFullyFetched());
+ }
+ } catch (final Exception ex) {
+ fail("Failed to execute a request!", ex);
+ }
+ }
+
+ public synchronized ResultSet simpleExecute(Cassandra node,
+ String username,
+ String password,
+ String query,
+ String dc)
+ {
+ try (final Session session = Cluster.builder()
+ .addContactPoint(node.getSettings().getAddress().getHostAddress())
+ .withLoadBalancingPolicy(new DCAwareRoundRobinPolicy.Builder().withLocalDc(dc).build())
+ .withAuthProvider(new PlainTextAuthProvider(username, password))
+ .build().connect()) {
+ return session.execute(query);
+ }
+ }
+
+ public void waitForClosedPort(String hostname, int port) {
+ await().timeout(FIVE_MINUTES).until(() ->
+ {
+ try {
+ (new Socket(hostname, port)).close();
+ return false;
+ } catch (SocketException e) {
+ return true;
+ }
+ });
+ }
+
+ public void waitForOpenPort(String hostname, int port) {
+ await().timeout(FIVE_MINUTES).until(() ->
+ {
+ try {
+ (new Socket(hostname, port)).close();
+ return true;
+ } catch (SocketException e) {
+ return false;
+ }
+ });
+ }
+ }
+
+ protected CassandraBuilder configure(final boolean ldap, final String node, final int ldapPort) {
+ final List pluginJars = stream(resolver()
+ .loadPomFromFile("pom.xml")
+ .resolve(getImplementationGAV())
+ .withTransitivity()
+ .asFile()).map(file -> file.toPath().toAbsolutePath()).collect(toList());
+
+ final File ldapPropertiesFile = getLdapPropertiesFile(ldapPort);
+
+ return new CassandraBuilder()
+ .version(getCassandraVersion())
+ .addJvmOptions("-Xmx1g", "-Xms1g")
+ .addSystemProperties(new HashMap() {{
+ put("cassandra.jmx.local.port", node.equals("first") ? "7199" : "7200");
+ put("cassandra.ring_delay_ms", "1000");
+ put("cassandra.ldap.properties.file", ldapPropertiesFile.toPath().toAbsolutePath().toString());
+ }})
+ .workingDirectory(() -> Files.createTempDirectory(null))
+ .addWorkingDirectoryCustomizers(new ArrayList() {{
+ if (ldap) {
+ add(addResource(new ClassPathResource(node + "-ldap.yaml"), "conf/cassandra.yaml"));
+ } else {
+ add(addResource(new ClassPathResource(node + ".yaml"), "conf/cassandra.yaml"));
+ }
+ add(addResource(new ClassPathResource(node + "-rackdc.properties"), "conf/cassandra-rackdc.properties"));
+ for (Path pluginJar : pluginJars) {
+ add(addResource(new FileSystemResource(pluginJar), "lib/" + pluginJar.getFileName().toString()));
+ }
+ }}.toArray(new WorkingDirectoryCustomizer[0]))
+ .workingDirectoryDestroyer(WorkingDirectoryDestroyer.doNothing());
+ }
+
+
+ protected GenericContainer prepareLdapContainer() throws Exception {
+ GenericContainer ldapContainer = new GenericContainer(DockerImageName.parse("osixia/openldap:latest"))
+ .withCopyFileToContainer(MountableFile.forHostPath("../conf/new-user.ldif"), "/new-user.ldif")
+ .withEnv("LDAP_ADMIN_PASSWORD", "admin")
+ .withExposedPorts(389)
+ .waitingFor(new HostPortWaitStrategy());
+
+ ldapContainer.start();
+
+ Container.ExecResult result = addLdapUser(ldapContainer);
+
+ while (result.getExitCode() != 0) {
+ logger.error(result.getStderr());
+ if (result.getStderr().contains("Already exists")) {
+ break;
+ }
+ Thread.sleep(5000);
+ result = addLdapUser(ldapContainer);
+ }
+
+ logger.info(result.getStdout());
+
+ return ldapContainer;
+ }
+
+ private Container.ExecResult addLdapUser(GenericContainer ldapContainer) throws Exception {
+ return ldapContainer.execInContainer(
+ "ldapadd",
+ "-x",
+ "-D",
+ "cn=admin,dc=example,dc=org",
+ "-w",
+ "admin",
+ "-f",
+ "/new-user.ldif",
+ "-H",
+ "ldap://127.0.0.1:389");
+ }
+
+ protected File getLdapPropertiesFile(int ldapPort) {
+ try {
+ File ldapPropertiesFile = Paths.get("../conf/ldap.properties").toFile();
+ Properties ldapProperties = new Properties();
+
+ try (InputStream is = new BufferedInputStream(new FileInputStream(ldapPropertiesFile))) {
+ ldapProperties.load(is);
+ } catch (Exception ex) {
+ throw new IllegalStateException("Unable to read content of ldap.properties!");
+ }
+
+ ldapProperties.setProperty("ldap_uri", "ldap://127.0.0.1:" + ldapPort + "/dc=example,dc=org");
+
+ File tempFile = Files.createTempFile("ldap-test", ".properties").toFile();
+ ldapProperties.store(new FileWriter(tempFile, true), "comments");
+ return tempFile;
+ } catch (Exception ex) {
+ throw new IllegalStateException("Unable to create ldap properties file for test.", ex);
+ }
+ }
+}
diff --git a/cassandra-5.0/src/test/java/com/instaclustr/cassandra/ldap/auth/Cassandra50LDAPIntegrationTest.java b/cassandra-5.0/src/test/java/com/instaclustr/cassandra/ldap/auth/Cassandra50LDAPIntegrationTest.java
new file mode 100644
index 0000000..2003e63
--- /dev/null
+++ b/cassandra-5.0/src/test/java/com/instaclustr/cassandra/ldap/auth/Cassandra50LDAPIntegrationTest.java
@@ -0,0 +1,20 @@
+package com.instaclustr.cassandra.ldap.auth;
+
+import org.testng.annotations.Test;
+
+public class Cassandra50LDAPIntegrationTest extends AbstractLDAPTest {
+ @Override
+ public String getCassandraVersion() {
+ return System.getProperty("version.cassandra50", "5.0.0");
+ }
+
+ @Override
+ public String getImplementationGAV() {
+ return "com.instaclustr:cassandra-ldap-" + getCassandraVersion() + ":1.0.0";
+ }
+
+ @Test
+ public void ldapTest() throws Exception {
+ super.testLDAPinternal();
+ }
+}
diff --git a/cassandra-5.0/src/test/resources/first-ldap.yaml b/cassandra-5.0/src/test/resources/first-ldap.yaml
new file mode 100644
index 0000000..566178f
--- /dev/null
+++ b/cassandra-5.0/src/test/resources/first-ldap.yaml
@@ -0,0 +1,104 @@
+cluster_name: Test Cluster
+num_tokens: 256
+hinted_handoff_enabled: true
+max_hint_window_in_ms: 10800000
+hinted_handoff_throttle_in_kb: 1024
+max_hints_delivery_threads: 2
+hints_flush_period_in_ms: 10000
+max_hints_file_size_in_mb: 128
+batchlog_replay_throttle_in_kb: 1024
+authenticator: LDAPAuthenticator
+authorizer: CassandraAuthorizer
+role_manager: LDAPCassandraRoleManager
+roles_validity_in_ms: 2000
+permissions_validity_in_ms: 2000
+credentials_validity_in_ms: 2000
+partitioner: org.apache.cassandra.dht.Murmur3Partitioner
+cdc_enabled: false
+disk_failure_policy: stop
+commit_failure_policy: stop
+prepared_statements_cache_size_mb: null
+key_cache_size_in_mb: null
+key_cache_save_period: 14400
+row_cache_size_in_mb: 0
+row_cache_save_period: 0
+counter_cache_size_in_mb: null
+counter_cache_save_period: 7200
+commitlog_sync: periodic
+commitlog_sync_period_in_ms: 10000
+commitlog_segment_size_in_mb: 32
+seed_provider:
+ - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ - {seeds: 127.0.0.1}
+concurrent_reads: 32
+concurrent_writes: 32
+concurrent_counter_writes: 32
+concurrent_materialized_view_writes: 32
+memtable_allocation_type: heap_buffers
+index_summary_capacity_in_mb: null
+index_summary_resize_interval_in_minutes: 60
+trickle_fsync: false
+trickle_fsync_interval_in_kb: 10240
+storage_port: 7000
+ssl_storage_port: 7001
+listen_address: localhost
+start_native_transport: true
+native_transport_port: 9042
+rpc_address: 127.0.0.1
+rpc_keepalive: true
+incremental_backups: false
+snapshot_before_compaction: false
+auto_snapshot: true
+column_index_size_in_kb: 64
+column_index_cache_size_in_kb: 2
+compaction_throughput_mb_per_sec: 16
+sstable_preemptive_open_interval_in_mb: 50
+read_request_timeout_in_ms: 5000
+range_request_timeout_in_ms: 10000
+write_request_timeout_in_ms: 2000
+counter_write_request_timeout_in_ms: 5000
+cas_contention_timeout_in_ms: 1000
+truncate_request_timeout_in_ms: 60000
+request_timeout_in_ms: 10000
+slow_query_log_timeout_in_ms: 500
+cross_node_timeout: false
+endpoint_snitch: GossipingPropertyFileSnitch
+dynamic_snitch_update_interval_in_ms: 100
+dynamic_snitch_reset_interval_in_ms: 600000
+dynamic_snitch_badness_threshold: 0.1
+server_encryption_options: {internode_encryption: none, keystore: conf/.keystore,
+ keystore_password: cassandra, truststore: conf/.truststore, truststore_password: cassandra}
+client_encryption_options: {enabled: false, optional: false, keystore: conf/.keystore,
+ keystore_password: cassandra}
+internode_compression: dc
+inter_dc_tcp_nodelay: false
+tracetype_query_ttl: 86400
+tracetype_repair_ttl: 604800
+enable_user_defined_functions: false
+enable_scripted_user_defined_functions: false
+windows_timer_interval: 1
+transparent_data_encryption_options:
+ enabled: false
+ chunk_length_kb: 64
+ cipher: AES/CBC/PKCS5Padding
+ key_alias: testing:1
+ key_provider:
+ - class_name: org.apache.cassandra.security.JKSKeyProvider
+ parameters:
+ - {keystore: conf/.keystore, keystore_password: cassandra, store_type: JCEKS,
+ key_password: cassandra}
+tombstone_warn_threshold: 1000
+tombstone_failure_threshold: 100000
+batch_size_warn_threshold_in_kb: 5
+batch_size_fail_threshold_in_kb: 50
+unlogged_batch_across_partitions_warn_threshold: 10
+compaction_large_partition_warning_threshold_mb: 100
+gc_warn_threshold_in_ms: 1000
+back_pressure_enabled: false
+back_pressure_strategy:
+ - class_name: org.apache.cassandra.net.RateBasedBackPressure
+ parameters:
+ - {high_ratio: 0.9, factor: 5, flow: FAST}
+enable_materialized_views: true
+enable_sasi_indexes: true
diff --git a/cassandra-5.0/src/test/resources/first-rackdc.properties b/cassandra-5.0/src/test/resources/first-rackdc.properties
new file mode 100644
index 0000000..b606d75
--- /dev/null
+++ b/cassandra-5.0/src/test/resources/first-rackdc.properties
@@ -0,0 +1,2 @@
+dc=datacenter1
+rack=rack1
\ No newline at end of file
diff --git a/cassandra-5.0/src/test/resources/first.yaml b/cassandra-5.0/src/test/resources/first.yaml
new file mode 100644
index 0000000..342d270
--- /dev/null
+++ b/cassandra-5.0/src/test/resources/first.yaml
@@ -0,0 +1,104 @@
+cluster_name: Test Cluster
+num_tokens: 256
+hinted_handoff_enabled: true
+max_hint_window_in_ms: 10800000
+hinted_handoff_throttle_in_kb: 1024
+max_hints_delivery_threads: 2
+hints_flush_period_in_ms: 10000
+max_hints_file_size_in_mb: 128
+batchlog_replay_throttle_in_kb: 1024
+authenticator: AllowAllAuthenticator
+authorizer: AllowAllAuthorizer
+role_manager: CassandraRoleManager
+roles_validity_in_ms: 2000
+permissions_validity_in_ms: 2000
+credentials_validity_in_ms: 2000
+partitioner: org.apache.cassandra.dht.Murmur3Partitioner
+cdc_enabled: false
+disk_failure_policy: stop
+commit_failure_policy: stop
+prepared_statements_cache_size_mb: null
+key_cache_size_in_mb: null
+key_cache_save_period: 14400
+row_cache_size_in_mb: 0
+row_cache_save_period: 0
+counter_cache_size_in_mb: null
+counter_cache_save_period: 7200
+commitlog_sync: periodic
+commitlog_sync_period_in_ms: 10000
+commitlog_segment_size_in_mb: 32
+seed_provider:
+- class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ - {seeds: 127.0.0.1}
+concurrent_reads: 32
+concurrent_writes: 32
+concurrent_counter_writes: 32
+concurrent_materialized_view_writes: 32
+memtable_allocation_type: heap_buffers
+index_summary_capacity_in_mb: null
+index_summary_resize_interval_in_minutes: 60
+trickle_fsync: false
+trickle_fsync_interval_in_kb: 10240
+storage_port: 7000
+ssl_storage_port: 7001
+listen_address: localhost
+start_native_transport: true
+native_transport_port: 9042
+rpc_address: 127.0.0.1
+rpc_keepalive: true
+incremental_backups: false
+snapshot_before_compaction: false
+auto_snapshot: true
+column_index_size_in_kb: 64
+column_index_cache_size_in_kb: 2
+compaction_throughput_mb_per_sec: 16
+sstable_preemptive_open_interval_in_mb: 50
+read_request_timeout_in_ms: 5000
+range_request_timeout_in_ms: 10000
+write_request_timeout_in_ms: 2000
+counter_write_request_timeout_in_ms: 5000
+cas_contention_timeout_in_ms: 1000
+truncate_request_timeout_in_ms: 60000
+request_timeout_in_ms: 10000
+slow_query_log_timeout_in_ms: 500
+cross_node_timeout: false
+endpoint_snitch: SimpleSnitch
+dynamic_snitch_update_interval_in_ms: 100
+dynamic_snitch_reset_interval_in_ms: 600000
+dynamic_snitch_badness_threshold: 0.1
+server_encryption_options: {internode_encryption: none, keystore: conf/.keystore,
+ keystore_password: cassandra, truststore: conf/.truststore, truststore_password: cassandra}
+client_encryption_options: {enabled: false, optional: false, keystore: conf/.keystore,
+ keystore_password: cassandra}
+internode_compression: dc
+inter_dc_tcp_nodelay: false
+tracetype_query_ttl: 86400
+tracetype_repair_ttl: 604800
+enable_user_defined_functions: false
+enable_scripted_user_defined_functions: false
+windows_timer_interval: 1
+transparent_data_encryption_options:
+ enabled: false
+ chunk_length_kb: 64
+ cipher: AES/CBC/PKCS5Padding
+ key_alias: testing:1
+ key_provider:
+ - class_name: org.apache.cassandra.security.JKSKeyProvider
+ parameters:
+ - {keystore: conf/.keystore, keystore_password: cassandra, store_type: JCEKS,
+ key_password: cassandra}
+tombstone_warn_threshold: 1000
+tombstone_failure_threshold: 100000
+batch_size_warn_threshold_in_kb: 5
+batch_size_fail_threshold_in_kb: 50
+unlogged_batch_across_partitions_warn_threshold: 10
+compaction_large_partition_warning_threshold_mb: 100
+gc_warn_threshold_in_ms: 1000
+back_pressure_enabled: false
+back_pressure_strategy:
+- class_name: org.apache.cassandra.net.RateBasedBackPressure
+ parameters:
+ - {high_ratio: 0.9, factor: 5, flow: FAST}
+enable_materialized_views: true
+enable_sasi_indexes: true
diff --git a/cassandra-5.0/src/test/resources/logback.xml b/cassandra-5.0/src/test/resources/logback.xml
new file mode 100644
index 0000000..8c6a9b2
--- /dev/null
+++ b/cassandra-5.0/src/test/resources/logback.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ %d{HH:mm:ss.SSS} %-5level %logger - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cassandra-5.0/src/test/resources/second-ldap.yaml b/cassandra-5.0/src/test/resources/second-ldap.yaml
new file mode 100644
index 0000000..fb60603
--- /dev/null
+++ b/cassandra-5.0/src/test/resources/second-ldap.yaml
@@ -0,0 +1,104 @@
+cluster_name: Test Cluster
+num_tokens: 256
+hinted_handoff_enabled: true
+max_hint_window_in_ms: 10800000
+hinted_handoff_throttle_in_kb: 1024
+max_hints_delivery_threads: 2
+hints_flush_period_in_ms: 10000
+max_hints_file_size_in_mb: 128
+batchlog_replay_throttle_in_kb: 1024
+authenticator: LDAPAuthenticator
+authorizer: CassandraAuthorizer
+role_manager: LDAPCassandraRoleManager
+roles_validity_in_ms: 2000
+permissions_validity_in_ms: 2000
+credentials_validity_in_ms: 2000
+partitioner: org.apache.cassandra.dht.Murmur3Partitioner
+cdc_enabled: false
+disk_failure_policy: stop
+commit_failure_policy: stop
+prepared_statements_cache_size_mb: null
+key_cache_size_in_mb: null
+key_cache_save_period: 14400
+row_cache_size_in_mb: 0
+row_cache_save_period: 0
+counter_cache_size_in_mb: null
+counter_cache_save_period: 7200
+commitlog_sync: periodic
+commitlog_sync_period_in_ms: 10000
+commitlog_segment_size_in_mb: 32
+seed_provider:
+ - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ - {seeds: 127.0.0.1}
+concurrent_reads: 32
+concurrent_writes: 32
+concurrent_counter_writes: 32
+concurrent_materialized_view_writes: 32
+memtable_allocation_type: heap_buffers
+index_summary_capacity_in_mb: null
+index_summary_resize_interval_in_minutes: 60
+trickle_fsync: false
+trickle_fsync_interval_in_kb: 10240
+storage_port: 7000
+ssl_storage_port: 7001
+listen_address: 127.0.0.2
+start_native_transport: true
+native_transport_port: 9042
+rpc_address: 127.0.0.2
+rpc_keepalive: true
+incremental_backups: false
+snapshot_before_compaction: false
+auto_snapshot: true
+column_index_size_in_kb: 64
+column_index_cache_size_in_kb: 2
+compaction_throughput_mb_per_sec: 16
+sstable_preemptive_open_interval_in_mb: 50
+read_request_timeout_in_ms: 5000
+range_request_timeout_in_ms: 10000
+write_request_timeout_in_ms: 2000
+counter_write_request_timeout_in_ms: 5000
+cas_contention_timeout_in_ms: 1000
+truncate_request_timeout_in_ms: 60000
+request_timeout_in_ms: 10000
+slow_query_log_timeout_in_ms: 500
+cross_node_timeout: false
+endpoint_snitch: GossipingPropertyFileSnitch
+dynamic_snitch_update_interval_in_ms: 100
+dynamic_snitch_reset_interval_in_ms: 600000
+dynamic_snitch_badness_threshold: 0.1
+server_encryption_options: {internode_encryption: none, keystore: conf/.keystore,
+ keystore_password: cassandra, truststore: conf/.truststore, truststore_password: cassandra}
+client_encryption_options: {enabled: false, optional: false, keystore: conf/.keystore,
+ keystore_password: cassandra}
+internode_compression: dc
+inter_dc_tcp_nodelay: false
+tracetype_query_ttl: 86400
+tracetype_repair_ttl: 604800
+enable_user_defined_functions: false
+enable_scripted_user_defined_functions: false
+windows_timer_interval: 1
+transparent_data_encryption_options:
+ enabled: false
+ chunk_length_kb: 64
+ cipher: AES/CBC/PKCS5Padding
+ key_alias: testing:1
+ key_provider:
+ - class_name: org.apache.cassandra.security.JKSKeyProvider
+ parameters:
+ - {keystore: conf/.keystore, keystore_password: cassandra, store_type: JCEKS,
+ key_password: cassandra}
+tombstone_warn_threshold: 1000
+tombstone_failure_threshold: 100000
+batch_size_warn_threshold_in_kb: 5
+batch_size_fail_threshold_in_kb: 50
+unlogged_batch_across_partitions_warn_threshold: 10
+compaction_large_partition_warning_threshold_mb: 100
+gc_warn_threshold_in_ms: 1000
+back_pressure_enabled: false
+back_pressure_strategy:
+ - class_name: org.apache.cassandra.net.RateBasedBackPressure
+ parameters:
+ - {high_ratio: 0.9, factor: 5, flow: FAST}
+enable_materialized_views: true
+enable_sasi_indexes: true
diff --git a/cassandra-5.0/src/test/resources/second-rackdc.properties b/cassandra-5.0/src/test/resources/second-rackdc.properties
new file mode 100644
index 0000000..fd79ad0
--- /dev/null
+++ b/cassandra-5.0/src/test/resources/second-rackdc.properties
@@ -0,0 +1,2 @@
+dc=datacenter2
+rack=rack1
\ No newline at end of file
diff --git a/cassandra-5.0/src/test/resources/second.yaml b/cassandra-5.0/src/test/resources/second.yaml
new file mode 100644
index 0000000..8f736e3
--- /dev/null
+++ b/cassandra-5.0/src/test/resources/second.yaml
@@ -0,0 +1,104 @@
+cluster_name: Test Cluster
+num_tokens: 256
+hinted_handoff_enabled: true
+max_hint_window_in_ms: 10800000
+hinted_handoff_throttle_in_kb: 1024
+max_hints_delivery_threads: 2
+hints_flush_period_in_ms: 10000
+max_hints_file_size_in_mb: 128
+batchlog_replay_throttle_in_kb: 1024
+authenticator: AllowAllAuthenticator
+authorizer: AllowAllAuthorizer
+role_manager: CassandraRoleManager
+roles_validity_in_ms: 2000
+permissions_validity_in_ms: 2000
+credentials_validity_in_ms: 2000
+partitioner: org.apache.cassandra.dht.Murmur3Partitioner
+cdc_enabled: false
+disk_failure_policy: stop
+commit_failure_policy: stop
+prepared_statements_cache_size_mb: null
+key_cache_size_in_mb: null
+key_cache_save_period: 14400
+row_cache_size_in_mb: 0
+row_cache_save_period: 0
+counter_cache_size_in_mb: null
+counter_cache_save_period: 7200
+commitlog_sync: periodic
+commitlog_sync_period_in_ms: 10000
+commitlog_segment_size_in_mb: 32
+seed_provider:
+ - class_name: org.apache.cassandra.locator.SimpleSeedProvider
+ parameters:
+ - {seeds: 127.0.0.1}
+concurrent_reads: 32
+concurrent_writes: 32
+concurrent_counter_writes: 32
+concurrent_materialized_view_writes: 32
+memtable_allocation_type: heap_buffers
+index_summary_capacity_in_mb: null
+index_summary_resize_interval_in_minutes: 60
+trickle_fsync: false
+trickle_fsync_interval_in_kb: 10240
+storage_port: 7000
+ssl_storage_port: 7001
+listen_address: 127.0.0.2
+start_native_transport: true
+native_transport_port: 9042
+rpc_address: 127.0.0.2
+rpc_keepalive: true
+incremental_backups: false
+snapshot_before_compaction: false
+auto_snapshot: true
+column_index_size_in_kb: 64
+column_index_cache_size_in_kb: 2
+compaction_throughput_mb_per_sec: 16
+sstable_preemptive_open_interval_in_mb: 50
+read_request_timeout_in_ms: 5000
+range_request_timeout_in_ms: 10000
+write_request_timeout_in_ms: 2000
+counter_write_request_timeout_in_ms: 5000
+cas_contention_timeout_in_ms: 1000
+truncate_request_timeout_in_ms: 60000
+request_timeout_in_ms: 10000
+slow_query_log_timeout_in_ms: 500
+cross_node_timeout: false
+endpoint_snitch: SimpleSnitch
+dynamic_snitch_update_interval_in_ms: 100
+dynamic_snitch_reset_interval_in_ms: 600000
+dynamic_snitch_badness_threshold: 0.1
+server_encryption_options: {internode_encryption: none, keystore: conf/.keystore,
+ keystore_password: cassandra, truststore: conf/.truststore, truststore_password: cassandra}
+client_encryption_options: {enabled: false, optional: false, keystore: conf/.keystore,
+ keystore_password: cassandra}
+internode_compression: dc
+inter_dc_tcp_nodelay: false
+tracetype_query_ttl: 86400
+tracetype_repair_ttl: 604800
+enable_user_defined_functions: false
+enable_scripted_user_defined_functions: false
+windows_timer_interval: 1
+transparent_data_encryption_options:
+ enabled: false
+ chunk_length_kb: 64
+ cipher: AES/CBC/PKCS5Padding
+ key_alias: testing:1
+ key_provider:
+ - class_name: org.apache.cassandra.security.JKSKeyProvider
+ parameters:
+ - {keystore: conf/.keystore, keystore_password: cassandra, store_type: JCEKS,
+ key_password: cassandra}
+tombstone_warn_threshold: 1000
+tombstone_failure_threshold: 100000
+batch_size_warn_threshold_in_kb: 5
+batch_size_fail_threshold_in_kb: 50
+unlogged_batch_across_partitions_warn_threshold: 10
+compaction_large_partition_warning_threshold_mb: 100
+gc_warn_threshold_in_ms: 1000
+back_pressure_enabled: false
+back_pressure_strategy:
+ - class_name: org.apache.cassandra.net.RateBasedBackPressure
+ parameters:
+ - {high_ratio: 0.9, factor: 5, flow: FAST}
+enable_materialized_views: true
+enable_sasi_indexes: true
diff --git a/pom.xml b/pom.xml
index b907524..48ea28d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,7 @@
cassandra-3.11
cassandra-4.0
cassandra-4.1
+ cassandra-5.0
Cassandra LDAP Authenticator parent