Skip to content

CASSANDRA-21546: Support pluggable default role initialization - #4990

Open
aparna0522 wants to merge 9 commits into
apache:trunkfrom
aparna0522:anaik-hardcoded-creds-fix
Open

CASSANDRA-21546: Support pluggable default role initialization#4990
aparna0522 wants to merge 9 commits into
apache:trunkfrom
aparna0522:anaik-hardcoded-creds-fix

Conversation

@aparna0522

Copy link
Copy Markdown
Contributor

Description
Cassandra bootstraps every new cluster with a hardcoded cassandra superuser whose password is also cassandra, so credential operators must remember to rotate.

This PR adds IDefaultRoleInitializer, a pluggable strategy for bootstrapping the initial role, configured via a new default_role_initializer option in cassandra.yaml:

  • PasswordDefaultRoleInitializer (default): same behavior as today, role/password now configurable.
  • MutualTlsDefaultRoleInitializer: creates the superuser with no password, mapping a client cert identity instead. Rejected at startup if the authenticator doesn't support MTLS.

patch by Aparna Naik; reviewed by for CASSANDRA-21546

@aparna0522
aparna0522 force-pushed the anaik-hardcoded-creds-fix branch from 8759820 to 96a5f44 Compare August 1, 2026 00:25
@aparna0522 aparna0522 changed the title Add MutualTLS strategy for Cluster Startup CASSANDRA-21546: Add MutualTLS strategy for Cluster Startup Aug 1, 2026
@smiklosovic smiklosovic changed the title CASSANDRA-21546: Add MutualTLS strategy for Cluster Startup CASSANDRA-21546: Support pluggable default role initialization Aug 2, 2026
@smiklosovic
smiklosovic self-requested a review August 2, 2026 21:43
@aparna0522
aparna0522 force-pushed the anaik-hardcoded-creds-fix branch from 96a5f44 to 16cc98f Compare August 5, 2026 18:34
Comment thread conf/cassandra.yaml
# password: cassandra
# # password_hash: "$2a$04$wsvzFamDJPDrTwMjgfcgpO.mKc.CMEuHBFZSjhGz2Ts6.v8PUO2rC"
#
# default_role_initializer:

@smiklosovic smiklosovic Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will add two sections into doc/modules/cassandra/pages/managing/configuration/cass_yaml_file.adoc (and wrongly formatted) when docs are generated, you should make it one configuration block instead.

}

private static <T> T authInstantiate(ParameterizedClass authCls, Class<T> expectedType, Class<? extends T> defaultCls) {
public static <T> T authInstantiate(ParameterizedClass authCls, Class<T> expectedType, Class<? extends T> defaultCls)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can go back to be private

return ParameterizedClass.newInstance(authCls, List.of("", authPackage), expectedType);
}

if (defaultCls == null)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure we need this anymore? This patch does not call this (changed) method.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually this can stay, it is a bug if we dont check for nullity because it would NPE next on defaultCls.newInstance().


IRoleManager roleManager = authInstantiate(conf.role_manager, IRoleManager.class, CassandraRoleManager.class);

if (authenticator instanceof PasswordAuthenticator && !(roleManager instanceof CassandraRoleManager))

@smiklosovic smiklosovic Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should not we add something similar for IDefaultRoleInitializer? Something like ...

    if ((defaultRoleInitializer instanceof PasswordDefaultRoleInitializer
         || defaultRoleInitializer instanceof MutualTlsDefaultRoleInitializer))
    {
        if (!(roleManager instanceof CassandraRoleManager))
            throw new ConfigurationException(...)
    }

This is what I was trying to avoid by having it embedded, now we are basically in a situation when we somehow need to be sure that it integrates together.

Maybe having additional method on IDefaultRoleInitializer returning the supported IRoleManager's would be OK? That means an implementator of IDefaultRoleInitializer would need to declare what kind of IRoleManagers it can service.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are wiring PasswordDefaultRoleInitializer with CassandraRoleManager as we call consistencyForRoleWrite from there. So if we use PasswordDefaultRoleInitializer with something else from CassandraRoleManager the results are unpredictable, basically. So I would make it necessary for the implementator to explicitly enumerate what role managers it is supposed to work with.

throw new OverloadedException(failure);
}

private static String hashpw(String password)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better if these helper methods were moved to AuthUtils if they are to be reused in various classes (hashpw, escape).

*
* @throws ConfigurationException when there is a configuration error.
*/
default void validateConfiguration() throws ConfigurationException

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not make this method default. IRoleManager has validateConfiguration method which is not default either. I think it is a good practice to force people to implement their validations explicitly and keep it empty on purpose if nothing to validate.

* gives the role the default password so PasswordAuthenticator can be used to log in (if
* configured)
*/
default void setupDefaultRole()

@smiklosovic smiklosovic Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a hard time to distinguish between "createDefaultRole" and "setupDefaultRole". If this method was not implemented already I would not know what to put into it. Maybe coverting IDefaultRoleInitializer to an abstract class (or making intermediate abstract class to implement this) and making this method public void would be better? Do we ever have a need to actually override this method?

default void setupDefaultRole()
{
if (ClusterMetadata.current().tokenMap.tokens().isEmpty())
throw new IllegalStateException("CassandraRoleManager skipped default role setup: no known tokens in ring");

@smiklosovic smiklosovic Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the log message is wrong, change the log message so it does not reference CassandraRoleManager:

throw new IllegalStateException(IDefaultRoleInitializer.this.getClass().getName() + " skipped default role setup: no known tokens in ring");

}
catch (RequestExecutionException e)
{
logger.warn("CassandraRoleManager skipped default role setup: some nodes were not ready");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same


public PasswordDefaultRoleInitializer(Map<String, String> parameters)
{
for (String param : parameters.keySet())

@smiklosovic smiklosovic Aug 6, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is going to happen all over again in other implementations too, we might put this logic to interface / abstract class and just call it from here (static method) with SUPPORTED_PARAMS and parameters as arguments or similar.

* {@link MutualTlsDefaultRoleInitializer} which gives no password and instead
* maps a client certificate identity to itself.
*/
public interface IDefaultRoleInitializer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after my latest refactor this interface is "implementation free"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants