[ 
https://issues.apache.org/jira/browse/CASSANDRA-21546?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18101873#comment-18101873
 ] 

Francisco Guerrero edited comment on CASSANDRA-21546 at 8/4/26 9:30 PM:
------------------------------------------------------------------------

I think the crux of our disagreement is composition vs. inheritance, so let me 
make that explicit.

There are two independent axes here:

How roles are stored and managed -- {{IRoleManager}} ({{CassandraRoleManager}} 
writes to {{system_auth}} tables; a hypothetical backend might use LDAP or a 
REST service).

How the very first role is bootstrapped -- password ({{role}}/{{password}}), 
password hash, or a passwordless mTLS role mapped to a cert identity.

These vary independently. The bootstrap strategy is not a sub-type of the role 
store -- it's a behavior the role store uses.

The inheritance approach (collapsing the initializer into 
{{PasswordCassandraRoleManager}} / {{MTLSCassandraRoleManager}}) models this as 
"is-a" and encodes the cross-product of the two axes. It works for the 2 
combinations we ship today, but the axes multiply:

  {code}
                   password        mtls          <future bootstrap>
  CassandraRM      PasswordCRM     MtlsCRM        ...CRM
  <future store>   Password???RM   Mtls???RM      ...
  {code}

  Every new bootstrap mode or new storage backend forces a new subclass, most 
differing only in the one method that creates the default role. That's the 
rigid, deep "is-a" tree, with tight coupling: a change in 
{{CassandraRoleManager}} ripples into every subclass.

The composition approach models it as "has-a": a {{CassandraRoleManager}} has-a 
{{IDefaultRoleInitializer}}. The strategy is a small, focused, swappable part 
-- Lego blocks, not a family tree:

  - Loose coupling -- the role store and the bootstrap strategy evolve 
independently; adding an mTLS bootstrap does not touch {{CassandraRoleManager}} 
at all.
  - No cross-product -- N stores + M bootstrap modes stay N + M classes, not 
N×M.
  - Swappable in config -- you change the initializer, not the role-manager 
class, to switch how the first role is created.

On your fair point that the shipped initializers do {{INSERT INTO 
system_auth.roles}} and so are coupled to the {{CassandraRoleManager}} storage 
family: that's real, but it's exactly what composition handles gracefully -- a 
strategy may target a particular store family, and a different {{IRoleManager}} 
simply composes a compatible initializer (or its own). That's still "has-a", 
not "is-a": we don't inherit a whole role manager just to change one bootstrap 
step.

And to your discoverability concern -- that a custom {{IRoleManager}} author 
should know this seam exists -- I've kept {{defaultRoleInitializer()}} on the 
{{IRoleManager}} interface (as a {{default}} method it will be in the latest 
commit of my branch). So the capability is visible in the type an implementor 
actually reads, while the strategy itself stays composed and swappable rather 
than baked into a subclass hierarchy.


was (Author: frankgh):
I think the crux of our disagreement is composition vs. inheritance, so let me 
make that explicit.

There are two independent axes here:

How roles are stored and managed -- {{IRoleManager}} ({{CassandraRoleManager}} 
writes to {{system_auth}} tables; a hypothetical backend might use LDAP or a 
REST service).

How the very first role is bootstrapped -- password ({{role}}/{{password}}), 
password hash, or a passwordless mTLS role mapped to a cert identity.

These vary independently. The bootstrap strategy is not a sub-type of the role 
store -- it's a behavior the role store uses.

The inheritance approach (collapsing the initializer into 
{{PasswordCassandraRoleManager}} / {{MTLSCassandraRoleManager}}) models this as 
"is-a" and encodes the cross-product of the two axes. It works for the 2 
combinations we ship today, but the axes multiply:

  {code}
                   password        mtls          <future bootstrap>
  CassandraRM      PasswordCRM     MtlsCRM        ...CRM
  <future store>   Password???RM   Mtls???RM      ...
  {code}

  Every new bootstrap mode or new storage backend forces a new subclass, most 
differing only in the one method that creates the default role. That's the 
rigid, deep "is-a" tree, with tight coupling: a change in 
{{CassandraRoleManager}} ripples into every subclass.

The composition approach models it as "has-a": a {{CassandraRoleManager}} has-a 
{{IDefaultRoleInitializer}}. The strategy is a small, focused, swappable part 
-- Lego blocks, not a family tree:

  - Loose coupling -- the role store and the bootstrap strategy evolve 
independently; adding an mTLS bootstrap does not touch {{CassandraRoleManager}} 
at all.
  - No cross-product -- N stores + M bootstrap modes stay N + M classes, not 
N×M.
  - Swappable in config -- you change the initializer, not the role-manager 
class, to switch how the first role is created.

On your fair point that the shipped initializers do {{INSERT INTO 
system_auth.roles}} and so are coupled to the {{CassandraRoleManager}} storage 
family: that's real, but it's exactly what composition handles gracefully -- a 
strategy may target a particular store family, and a different {{IRoleManager}} 
simply composes a compatible initializer (or its own). That's still "has-a", 
not "is-a": we don't inherit a whole role manager just to change one bootstrap 
step.

And to your discoverability concern -- that a custom {{IRoleManager}} author 
should know this seam exists -- I've kept {{defaultRoleInitializer()}} on the 
{{IRoleManager}} interface (as a {{default}} method it will be in the latest 
commit). So the capability is visible in the type an implementor actually 
reads, while the strategy itself stays composed and swappable rather than baked 
into a subclass hierarchy.

> Support pluggable default role initialization (avoid hardcoded superuser 
> password)
> ----------------------------------------------------------------------------------
>
>                 Key: CASSANDRA-21546
>                 URL: https://issues.apache.org/jira/browse/CASSANDRA-21546
>             Project: Apache Cassandra
>          Issue Type: Bug
>            Reporter: Aparna Naik
>            Assignee: Aparna Naik
>            Priority: Normal
>
> Cassandra's first-boot bootstrap hardcodes the creation of a cassandra 
> superuser role with a default password (cassandra). Every new cluster starts 
> with this guessable credential exposed until an operator manually rotates or 
> drops it, and deployments that already use mutual TLS have no way to 
> bootstrap a superuser identity without also creating this password-based one. 
> This ticket will make the default role bootstrap pluggable via a new 
> IDefaultRoleInitializer interface and default_role_initializer config option. 
> The existing password-based behavior will be the default implementation for 
> backward compatibility, and it will add a MutualTlsDefaultRoleInitializer 
> that instead maps a client certificate identity to the superuser role, so no 
> password credential needs to exist at all.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to