From f078c02cb58bddd735490b07548f7352f0eb09aa Mon Sep 17 00:00:00 2001 From: jkonisa Date: Mon, 22 May 2023 22:15:22 -0700 Subject: [PATCH] Adding Mutual TLS authenticators for client & internode connections Patch by Jyothsna Konisa & Dinesh Joshi; reviewed by Yifan Cai, Jon Meredith, Yuki Morishita & Dinesh Joshi for CASSANDRA-18554 Co-Authored-By: Dinesh Joshi --- conf/cassandra.yaml | 22 +- .../pages/developing/cql/cql_singlefile.adoc | 51 ++++ .../getting-started/mtlsauthenticators.adoc | 133 +++++++++++ src/antlr/Lexer.g | 1 + src/antlr/Parser.g | 31 +++ .../cassandra/audit/AuditLogEntryType.java | 2 + .../org/apache/cassandra/auth/AuthConfig.java | 20 +- .../apache/cassandra/auth/AuthKeyspace.java | 14 +- .../cassandra/auth/CassandraRoleManager.java | 114 +++++++++ .../auth/IInternodeAuthenticator.java | 2 +- .../apache/cassandra/auth/IRoleManager.java | 56 +++++ .../auth/MutualTlsAuthenticator.java | 208 ++++++++++++++++ .../auth/MutualTlsCertificateValidator.java | 63 +++++ .../auth/MutualTlsInternodeAuthenticator.java | 226 ++++++++++++++++++ ...lTlsWithPasswordFallbackAuthenticator.java | 53 ++++ .../cassandra/auth/PasswordAuthenticator.java | 3 +- .../auth/SpiffeCertificateValidator.java | 94 ++++++++ .../org/apache/cassandra/config/Config.java | 4 +- .../cassandra/config/DatabaseDescriptor.java | 3 +- .../cassandra/config/ParameterizedClass.java | 49 ++++ .../cql3/statements/AddIdentityStatement.java | 86 +++++++ .../statements/DropIdentityStatement.java | 79 ++++++ .../cassandra/service/StorageService.java | 2 +- ...cassandra-mtls-backward-compatibility.yaml | 76 ++++++ test/conf/cassandra-mtls.yaml | 89 +++++++ test/conf/cassandra_ssl_test.truststore | Bin 5295 -> 7638 bytes .../conf/cassandra_ssl_test_outbound.keystore | Bin 2286 -> 2375 bytes .../auth/SampleInvalidCertificate.pem | 21 ++ .../auth/SampleMtlsClientCertificate.pem | 29 +++ ...ampleUnauthorizedMtlsClientCertificate.pem | 29 +++ .../cassandra/audit/AuditLoggerAuthTest.java | 2 +- .../apache/cassandra/auth/AuthConfigTest.java | 100 ++++++++ .../apache/cassandra/auth/AuthTestUtils.java | 46 ++++ .../auth/MutualTlsAuthenticatorTest.java | 181 ++++++++++++++ .../MutualTlsInternodeAuthenticatorTest.java | 188 +++++++++++++++ ...WithPasswordFallbackAuthenticatorTest.java | 93 +++++++ .../auth/SpiffeCertificateValidatorTest.java | 58 +++++ .../config/ConfigCompatabilityTest.java | 17 +- .../config/YamlConfigurationLoaderTest.java | 39 ++- .../statements/AddIdentityStatementTest.java | 198 +++++++++++++++ .../statements/DropIdentityStatementTest.java | 157 ++++++++++++ .../cassandra/transport/CQLUserAuditTest.java | 2 +- 42 files changed, 2618 insertions(+), 23 deletions(-) create mode 100644 doc/modules/cassandra/pages/getting-started/mtlsauthenticators.adoc create mode 100644 src/java/org/apache/cassandra/auth/MutualTlsAuthenticator.java create mode 100644 src/java/org/apache/cassandra/auth/MutualTlsCertificateValidator.java create mode 100644 src/java/org/apache/cassandra/auth/MutualTlsInternodeAuthenticator.java create mode 100644 src/java/org/apache/cassandra/auth/MutualTlsWithPasswordFallbackAuthenticator.java create mode 100644 src/java/org/apache/cassandra/auth/SpiffeCertificateValidator.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/AddIdentityStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/DropIdentityStatement.java create mode 100644 test/conf/cassandra-mtls-backward-compatibility.yaml create mode 100644 test/conf/cassandra-mtls.yaml create mode 100644 test/resources/auth/SampleInvalidCertificate.pem create mode 100644 test/resources/auth/SampleMtlsClientCertificate.pem create mode 100644 test/resources/auth/SampleUnauthorizedMtlsClientCertificate.pem create mode 100644 test/unit/org/apache/cassandra/auth/AuthConfigTest.java create mode 100644 test/unit/org/apache/cassandra/auth/MutualTlsAuthenticatorTest.java create mode 100644 test/unit/org/apache/cassandra/auth/MutualTlsInternodeAuthenticatorTest.java create mode 100644 test/unit/org/apache/cassandra/auth/MutualTlsWithPasswordFallbackAuthenticatorTest.java create mode 100644 test/unit/org/apache/cassandra/auth/SpiffeCertificateValidatorTest.java create mode 100644 test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java create mode 100644 test/unit/org/apache/cassandra/cql3/statements/DropIdentityStatementTest.java diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index 6414fab956..8783f53252 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -150,7 +150,14 @@ batchlog_replay_throttle: 1024KiB # users. It keeps usernames and hashed passwords in system_auth.roles table. # Please increase system_auth keyspace replication factor if you use this authenticator. # If using PasswordAuthenticator, CassandraRoleManager must also be used (see below) -authenticator: AllowAllAuthenticator +authenticator: + class_name : org.apache.cassandra.auth.AllowAllAuthenticator +# MutualTlsAuthenticator can be configured using the following configuration. One can add their own validator +# which implements MutualTlsCertificateValidator class and provide logic for extracting identity out of certificates +# and validating certificates. +# class_name : org.apache.cassandra.auth.MutualTlsAuthenticator +# parameters : +# validator_class_name: org.apache.cassandra.auth.SpiffeCertificateValidator # Authorization backend, implementing IAuthorizer; used to limit access/provide permissions # Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer, @@ -820,8 +827,17 @@ listen_address: localhost # Internode authentication backend, implementing IInternodeAuthenticator; # used to allow/disallow connections from peer nodes. -# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator - +#internode_authenticator: +# class_name : org.apache.cassandra.auth.AllowAllInternodeAuthenticator +# parameters : +# MutualTlsInternodeAuthenticator can be configured using the following configuration.One can add their own validator +# which implements MutualTlsCertificateValidator class and provide logic for extracting identity out of certificates +# and validating certificates. +# class_name : org.apache.cassandra.auth.MutualTlsInternodeAuthenticator +# parameters : +# validator_class_name: org.apache.cassandra.auth.SpiffeCertificateValidator +# trusted_peer_identities: "spiffe1,spiffe2" +# node_identity: "spiffe1" # Whether to start the native transport server. # The address on which the native transport is bound is defined by rpc_address. start_native_transport: true diff --git a/doc/modules/cassandra/pages/developing/cql/cql_singlefile.adoc b/doc/modules/cassandra/pages/developing/cql/cql_singlefile.adoc index 64e71b051c..286a9923c2 100644 --- a/doc/modules/cassandra/pages/developing/cql/cql_singlefile.adoc +++ b/doc/modules/cassandra/pages/developing/cql/cql_singlefile.adoc @@ -2246,6 +2246,57 @@ LIST ROLES; but only roles with the `LOGIN` privilege are included in the output. +[[databaseIdentity]] +=== Database Identities + +[[AddIdentityStmt]] +==== ADD IDENTITY + +_Syntax:_ + +bc(syntax).. + +::= ADD IDENTITY ( IF NOT EXISTS )? TO ROLE ? + +_Sample:_ + +bc(sample). + +ADD IDENTITY 'id1' TO ROLE 'role1'; + +Only a user with privileges to add roles can add identities + +Role names & Identity names should be quoted if they contain non-alphanumeric characters. + +[[addIdentityConditional]] +===== Adding an identity conditionally + +Attempting to add an existing identity results in an invalid query +condition unless the `IF NOT EXISTS` option is used. If the option is +used and the identity exists, the statement is a no-op. + +bc(sample). + +ADD IDENTITY IF NOT EXISTS 'id1' TO ROLE 'role1'; + +[[dropIdentityStmt]] +==== DROP IDENTITY + +_Syntax:_ + +bc(syntax).. + +::= DROP IDENTITY ( IF EXISTS )? + +p. + +_Sample:_ + +bc(sample). + +DROP IDENTITY 'testIdentity'; + +DROP IDENTITY IF EXISTS 'testIdentity'; + +Only a user with privileges to drop roles can remove identities + +Attempting to drop an Identity which does not exist results in an invalid +query condition unless the `IF EXISTS` option is used. If the option is +used and the identity does not exist the statement is a no-op. + [[dataControl]] === Data Control diff --git a/doc/modules/cassandra/pages/getting-started/mtlsauthenticators.adoc b/doc/modules/cassandra/pages/getting-started/mtlsauthenticators.adoc new file mode 100644 index 0000000000..e3cd6ef79f --- /dev/null +++ b/doc/modules/cassandra/pages/getting-started/mtlsauthenticators.adoc @@ -0,0 +1,133 @@ += Getting started with mTLS authenticators + +When a certificate based authentication protocol like TLS is used for client and +Internode connections, `MutualTlsAuthenticator` & `MutualTlsInternodeAuthenticator` +can be used for the authentication by leveraging the client certificates from the +SSL handshake. + +After SSL handshake, identity from the client certificates is extracted and only +authorized users will be granted access. + +== What is an Identity + +Operators can define their own identity for certificates by extracting some fields or +information from the certificates. Implementing the interface `MutualTlsCertificateValidator` +supports validating & extracting identities from the certificates that can be used by +`MutualTlsAuthenticator` and `MutualTlsInternodeAuthenticator` to customize for the +certificate conventions used in the deployment environment. + +There is a default implementation of `MutualTlsCertificateValidator` with +https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/[SPIFFE] as the identity +of the certificates.This requires spiffe to be present in the SAN of the certificate. + +Instead of using `SPIFFE` based validator, a custom `CN` based validator that implements `MutualTlsCertificateValidator` +could be configured by the operator if required. + +== Configuring mTLS authenticator for client connections + +Note that the following steps uses SPIFFE identity as an example, If you are using +a custom validator, use appropriate identity in place of `spiffe://testdomain.com/testIdentifier/testValue`. + +*STEP 1: Add authorized users to system_auth.identity_to_roles table* + +Note that only users with permissions to create/modify roles can add/remove identities. +Client certificates with the identities in this table will be trusted by C*. +[source, plaintext] +---- +ADD IDENTITY 'spiffe://testdomain.com/testIdentifier/testValue' TO ROLE 'read_only_user' +---- + +*STEP 2: Configure Cassandra.yaml with right properties* + +`client_encryption_options` configuration for mTLS connections +[source, plaintext] +---- +client_encryption_options: + enabled: true + optional: false + keystore: conf/.keystore + keystore_password: cassandra + truststore: conf/.truststore + truststore_password: cassandra + require_client_auth: true // to enable mTLS +---- +Configure mTLS authenticator and the validator for client connections . If you are +implementing a custom validator, use that instead of Spiffe validator +[source, plaintext] +---- +authenticator: + class_name : org.apache.cassandra.auth.MutualTlsAuthenticator + parameters : + validator_class_name: org.apache.cassandra.auth.SpiffeCertificateValidator +---- + +*STEP 3: Bounce the cluster* + +After the bounce, C* will accept mTLS connections from the clients and if their +identity is present in the `identity_to_roles` table, access will be granted. + +== Configuring mTLS authenticator for Internode connections + +Internode authenticator trusts certificates whose identities are present in +`internode_authenticator.parameters.trusted_peer_identities` if configured. + +Otherwise, it trusts connections which have the same identity as the node. +When a node is making an outbound connection to another node, it uses the +certificate configured in `server_encryption_options.outbound_keystore`. +During the start of the node, identity is extracted from the outbound keystore and +connections from other nodes who have the same identity will be trusted if +`trusted_peer_identities` is not configured. + +For example, if a node has `testIdentity` embedded in the certificate in +outbound keystore, It trusts connections from other nodes when their certificates +have `testIdentity` embedded in them. + +There is an optional configuration `node_identity` that can be used to verify identity +extracted from the keystore to avoid any configuration errors. + +*STEP 1: Configure server_encryption_options in cassandra.yaml* + +[source, plaintext] +---- +server_encryption_options: + internode_encryption: all + optional: true + keystore: conf/.keystore + keystore_password: cassandra + outbound_keystore: conf/.outbound_keystore + outbound_keystore_password: cassandra + require_client_auth: true // for enabling mTLS + truststore: conf/.truststore + truststore_password: cassandra +---- + +*STEP 2: Configure Internode Authenticator and Validator* + +Configure mTLS Internode authenticator and validator. If you are +implementing a custom validator, use that instead of Spiffe validator +[source, plaintext] +---- +internode_authenticator: + class_name : org.apache.cassandra.auth.MutualTlsInternodeAuthenticator + parameters : + validator_class_name: org.apache.cassandra.auth.SpiffeCertificateValidator + trusted_peer_identities : "spiffe1,spiffe2" +---- + +*STEP 3: Bounce the cluster* +Once all nodes in the cluster are restarted, all internode communications will be authenticated by mTLS. + +== Migration from existing password based authentication +* For client connections, since the migration will not happen overnight, +the operators can run cassandra in optional mTLS mode and use +`MutualTlsWithPasswordFallbackAuthenticator` which will accept both mTLS & password +based connections, based on the type of connection client is making. These settings +can be configured in `cassandra.yaml`. Once all the clients migrate to using mTLS, +turn off optional mode and set the authenticator to be `MutualTlsAuthenticator`. From +that point only mTLS client connections will be accepted. + +* For Internode connections, while doing rolling upgrades from non-mTLS based configuration +to mTLS based configuration, set `server_encryption_options.optional:true` for the new nodes to +be able to connect to old nodes which are still using non-mTLS based configuration during upgrade. +After this, change the internode authenticator to be `MutualTlsInternodeAuthenticator` and turn off the optional +mode by setting `server_encryption_options.optional:false`. \ No newline at end of file diff --git a/src/antlr/Lexer.g b/src/antlr/Lexer.g index a4f8ea715f..c75523ed74 100644 --- a/src/antlr/Lexer.g +++ b/src/antlr/Lexer.g @@ -156,6 +156,7 @@ K_NOLOGIN: N O L O G I N; K_OPTIONS: O P T I O N S; K_ACCESS: A C C E S S; K_DATACENTERS: D A T A C E N T E R S; +K_IDENTITY: I D E N T I T Y; K_CLUSTERING: C L U S T E R I N G; K_ASCII: A S C I I; diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g index 65ed92a397..0d2150481d 100644 --- a/src/antlr/Parser.g +++ b/src/antlr/Parser.g @@ -247,6 +247,8 @@ cqlStatement returns [CQLStatement.Raw stmt] | st39=dropMaterializedViewStatement { $stmt = st39; } | st40=alterMaterializedViewStatement { $stmt = st40; } | st41=describeStatement { $stmt = st41; } + | st42=addIdentityStatement { $stmt = st42; } + | st43=dropIdentityStatement { $stmt = st43; } ; /* @@ -1237,6 +1239,28 @@ dropUserStatement returns [DropRoleStatement stmt] } : K_DROP K_USER (K_IF K_EXISTS { ifExists = true; })? u=username { name.setName($u.text, true); $stmt = new DropRoleStatement(name, ifExists); } ; +/** + * ADD IDENTITY [IF NOT EXISTS] TO ROLE + */ +addIdentityStatement returns [AddIdentityStatement stmt] + @init { + String identity = null; + String role = null; + boolean ifNotExists = false; + } + : K_ADD K_IDENTITY (K_IF K_NOT K_EXISTS { ifNotExists = true; })? u=identity { identity= $u.text; } K_TO K_ROLE r=identity { role=$r.text; $stmt = new AddIdentityStatement(identity, role, ifNotExists); } + ; + +/** + * DROP IDENTITY [IF EXISTS] + */ + dropIdentityStatement returns [DropIdentityStatement stmt] + @init { + boolean ifExists = false; + String identity = null; + } + : K_DROP K_IDENTITY (K_IF K_EXISTS { ifExists = true; })? u=identity { identity= $u.text; $stmt = new DropIdentityStatement(identity, ifExists);} + ; /** * LIST USERS @@ -1878,6 +1902,12 @@ username | QUOTED_NAME { addRecognitionError("Quoted strings are are not supported for user names and USER is deprecated, please use ROLE");} ; +identity + : IDENT + | STRING_LITERAL + | QUOTED_NAME { addRecognitionError("Quoted strings are are not supported for identity");} + ; + mbean : STRING_LITERAL ; @@ -1923,6 +1953,7 @@ basic_unreserved_keyword returns [String str] | K_USERS | K_ROLE | K_ROLES + | K_IDENTITY | K_SUPERUSER | K_NOSUPERUSER | K_LOGIN diff --git a/src/java/org/apache/cassandra/audit/AuditLogEntryType.java b/src/java/org/apache/cassandra/audit/AuditLogEntryType.java index ccf0169ff8..1055f875e0 100644 --- a/src/java/org/apache/cassandra/audit/AuditLogEntryType.java +++ b/src/java/org/apache/cassandra/audit/AuditLogEntryType.java @@ -58,6 +58,8 @@ public enum AuditLogEntryType LIST_PERMISSIONS(AuditLogEntryCategory.DCL), ALTER_TYPE(AuditLogEntryCategory.DDL), CREATE_ROLE(AuditLogEntryCategory.DCL), + CREATE_IDENTITY(AuditLogEntryCategory.DCL), + DROP_IDENTITY(AuditLogEntryCategory.DCL), USE_KEYSPACE(AuditLogEntryCategory.OTHER), DESCRIBE(AuditLogEntryCategory.OTHER), diff --git a/src/java/org/apache/cassandra/auth/AuthConfig.java b/src/java/org/apache/cassandra/auth/AuthConfig.java index 9c5fceb6dc..5cecd6f130 100644 --- a/src/java/org/apache/cassandra/auth/AuthConfig.java +++ b/src/java/org/apache/cassandra/auth/AuthConfig.java @@ -18,11 +18,14 @@ package org.apache.cassandra.auth; +import java.util.Arrays; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.config.Config; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.config.ParameterizedClass; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.utils.FBUtilities; @@ -50,12 +53,15 @@ public final class AuthConfig /* Authentication, authorization and role management backend, implementing IAuthenticator, IAuthorizer & IRoleMapper*/ if (conf.authenticator != null) - authenticator = FBUtilities.newAuthenticator(conf.authenticator); + { + authenticator = ParameterizedClass.newInstance(conf.authenticator, + Arrays.asList("", AuthConfig.class.getPackage().getName())); + } // the configuration options regarding credentials caching are only guaranteed to // work with PasswordAuthenticator, so log a message if some other authenticator // is in use and non-default values are detected - if (!(authenticator instanceof PasswordAuthenticator) + if (!(authenticator instanceof PasswordAuthenticator || authenticator instanceof MutualTlsAuthenticator) && (conf.credentials_update_interval != null || conf.credentials_validity.toMilliseconds() != 2000 || conf.credentials_cache_max_entries != 1000)) @@ -75,7 +81,7 @@ public final class AuthConfig authorizer = FBUtilities.newAuthorizer(conf.authorizer); if (!authenticator.requireAuthentication() && authorizer.requireAuthorization()) - throw new ConfigurationException(conf.authenticator + " can't be used with " + conf.authorizer, false); + throw new ConfigurationException(conf.authenticator.class_name + " can't be used with " + conf.authorizer, false); DatabaseDescriptor.setAuthorizer(authorizer); @@ -95,14 +101,18 @@ public final class AuthConfig // authenticator if (conf.internode_authenticator != null) - DatabaseDescriptor.setInternodeAuthenticator(FBUtilities.construct(conf.internode_authenticator, "internode_authenticator")); + { + DatabaseDescriptor.setInternodeAuthenticator(ParameterizedClass.newInstance(conf.internode_authenticator, + Arrays.asList("", AuthConfig.class.getPackage().getName()))); + } + // network authorizer INetworkAuthorizer networkAuthorizer = FBUtilities.newNetworkAuthorizer(conf.network_authorizer); DatabaseDescriptor.setNetworkAuthorizer(networkAuthorizer); if (networkAuthorizer.requireAuthorization() && !authenticator.requireAuthentication()) { - throw new ConfigurationException(conf.network_authorizer + " can't be used with " + conf.authenticator, false); + throw new ConfigurationException(conf.network_authorizer + " can't be used with " + conf.authenticator.class_name, false); } // Validate at last to have authenticator, authorizer, role-manager and internode-auth setup diff --git a/src/java/org/apache/cassandra/auth/AuthKeyspace.java b/src/java/org/apache/cassandra/auth/AuthKeyspace.java index 75d9871d03..b1616e07fa 100644 --- a/src/java/org/apache/cassandra/auth/AuthKeyspace.java +++ b/src/java/org/apache/cassandra/auth/AuthKeyspace.java @@ -58,7 +58,8 @@ public final class AuthKeyspace public static final String ROLE_PERMISSIONS = "role_permissions"; public static final String RESOURCE_ROLE_INDEX = "resource_role_permissons_index"; public static final String NETWORK_PERMISSIONS = "network_permissions"; - public static final Set TABLE_NAMES = ImmutableSet.of(ROLES, ROLE_MEMBERS, ROLE_PERMISSIONS, RESOURCE_ROLE_INDEX, NETWORK_PERMISSIONS); + public static final String IDENTITY_TO_ROLES = "identity_to_role"; + public static final Set TABLE_NAMES = ImmutableSet.of(ROLES, ROLE_MEMBERS, ROLE_PERMISSIONS, RESOURCE_ROLE_INDEX, NETWORK_PERMISSIONS, IDENTITY_TO_ROLES); public static final long SUPERUSER_SETUP_DELAY = SUPERUSER_SETUP_DELAY_MS.getLong(); @@ -73,6 +74,15 @@ public final class AuthKeyspace + "member_of set," + "PRIMARY KEY(role))"); + private static final TableMetadata IdentityToRoles = + parse(IDENTITY_TO_ROLES, + "mtls authorized identities lookup table", + "CREATE TABLE %s (" + + "identity text," // opaque identity string for use by role authenticators + + "role text," + + "PRIMARY KEY(identity))" + ); + private static final TableMetadata RoleMembers = parse(ROLE_MEMBERS, "role memberships lookup table", @@ -119,6 +129,6 @@ public final class AuthKeyspace { return KeyspaceMetadata.create(SchemaConstants.AUTH_KEYSPACE_NAME, KeyspaceParams.simple(Math.max(DEFAULT_RF, DatabaseDescriptor.getDefaultKeyspaceRF())), - Tables.of(Roles, RoleMembers, RolePermissions, ResourceRoleIndex, NetworkPermissions)); + Tables.of(Roles, RoleMembers, RolePermissions, ResourceRoleIndex, NetworkPermissions, IdentityToRoles)); } } diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java index 37bda4eac4..3221c85184 100644 --- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java +++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java @@ -17,6 +17,7 @@ */ package org.apache.cassandra.auth; +import java.nio.ByteBuffer; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; @@ -46,6 +47,7 @@ import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.NoSpamLogger; import org.mindrot.jbcrypt.BCrypt; import static org.apache.cassandra.config.CassandraRelevantProperties.AUTH_BCRYPT_GENSALT_LOG2_ROUNDS; @@ -80,6 +82,7 @@ import static org.apache.cassandra.utils.Clock.Global.nanoTime; public class CassandraRoleManager implements IRoleManager { private static final Logger logger = LoggerFactory.getLogger(CassandraRoleManager.class); + private static final NoSpamLogger nospamLogger = NoSpamLogger.getLogger(logger, 1L, TimeUnit.MINUTES); public static final String DEFAULT_SUPERUSER_NAME = "cassandra"; public static final String DEFAULT_SUPERUSER_PASSWORD = "cassandra"; @@ -125,6 +128,7 @@ public class CassandraRoleManager implements IRoleManager } private SelectStatement loadRoleStatement; + private SelectStatement loadIdentityStatement; private final Set