Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/java/org/apache/cassandra/auth/AuthConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.util.List;

import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -38,6 +39,15 @@ public final class AuthConfig

private static boolean initialized;

/**
* Resets the initialized flag, enabling AuthConfig to be reconfigured multiple times within a single
* test case.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Specifically, see AuthConfigTest in this same PR.

*/
@VisibleForTesting
static void reset() {
initialized = false;
}

public static void applyAuth()
{
// some tests need this
Expand Down
57 changes: 29 additions & 28 deletions src/java/org/apache/cassandra/auth/CassandraRoleManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -78,29 +79,16 @@
import static org.apache.cassandra.service.QueryState.forInternalCalls;

/**
* Responsible for the creation, maintenance and deletion of roles
* for the purposes of authentication and authorization.
* Role data is stored internally, using the roles and role_members tables
* in the system_auth keyspace.
* Responsible for the creation, maintenance and deletion of roles for the purposes of authentication and
* authorization. Role data is stored internally, using the roles and role_members tables in the system_auth
* keyspace.
*
* Additionally, if org.apache.cassandra.auth.PasswordAuthenticator is used,
* encrypted passwords are also stored in the system_auth.roles table. This
* coupling between the IAuthenticator and IRoleManager implementations exists
* because setting a role's password via CQL is done with a CREATE ROLE or
* ALTER ROLE statement, the processing of which is handled by IRoleManager.
* As IAuthenticator is concerned only with credentials checking and has no
* means to modify passwords, PasswordAuthenticator depends on
* CassandraRoleManager for those functions.
*
* Alternative IAuthenticator implementations may be used in conjunction with
* CassandraRoleManager, but WITH PASSWORD = 'password' will not be supported
* in CREATE/ALTER ROLE statements.
*
* Such a configuration could be implemented using a custom IRoleManager that
* extends CassandraRoleManager and which includes Option.PASSWORD in the {@code Set<Option>}
* returned from supportedOptions/alterableOptions. Any additional processing
* of the password itself (such as storing it in an alternative location) would
* be added in overridden createRole and alterRole implementations.
* Authenticators (implementations of {@link IAuthenticator}) can specify additional attributes to be stored.
* For example, {@link org.apache.cassandra.auth.PasswordAuthenticator}, stores encrypted passwords in the
* system_auth.roles table. This coupling between the IAuthenticator and IRoleManager implementations exists because
* setting a role's password via CQL is done with a CREATE ROLE or ALTER ROLE statement, the processing of which is
* handled by IRoleManager. Authenticators depend on CassandraRoleManager for those functions because IAuthenticator
* is concerned only with credentials checking and has no means to directly modify passwords.
*/
public class CassandraRoleManager implements IRoleManager, CassandraRoleManagerMBean
{
Expand All @@ -110,8 +98,24 @@ public class CassandraRoleManager implements IRoleManager, CassandraRoleManagerM
public static final String DEFAULT_SUPERUSER_NAME = "cassandra";
public static final String DEFAULT_SUPERUSER_PASSWORD = "cassandra";

/**
* Role options which are supported for all authentication mechanisms. IAuthenticator implementations can declare
* additional supported role options via {@link IAuthenticator#getSupportedRoleOptions()}.
*/
@VisibleForTesting
static final Set<Option> DEFAULT_SUPPORTED_ROLE_OPTIONS = ImmutableSet.of(Option.LOGIN, Option.SUPERUSER);

/**
* User-alterable role options which are supported for all authentication mechanisms. IAuthenticator
* implementations can declare additional alterable role options via
* {@link IAuthenticator#getAlterableRoleOptions()}.
*/
@VisibleForTesting
static final Set<Option> DEFAULT_ALTERABLE_ROLE_OPTIONS = ImmutableSet.of();

@VisibleForTesting
static final String PARAM_INVALID_ROLE_DISCONNECT_TASK_PERIOD = "invalid_role_disconnect_task_period";

@VisibleForTesting
static final String PARAM_INVALID_ROLE_DISCONNECT_TASK_MAX_JITTER = "invalid_role_disconnect_task_max_jitter";

Expand Down Expand Up @@ -164,12 +168,9 @@ public CassandraRoleManager()

public CassandraRoleManager(Map<String, String> parameters)
{
supportedOptions = DatabaseDescriptor.getAuthenticator() instanceof PasswordAuthenticator
? ImmutableSet.of(Option.LOGIN, Option.SUPERUSER, Option.PASSWORD, Option.HASHED_PASSWORD, Option.GENERATED_PASSWORD)
: ImmutableSet.of(Option.LOGIN, Option.SUPERUSER);
alterableOptions = DatabaseDescriptor.getAuthenticator() instanceof PasswordAuthenticator
? ImmutableSet.of(Option.PASSWORD, Option.HASHED_PASSWORD, Option.GENERATED_PASSWORD)
: ImmutableSet.<Option>of();
supportedOptions = ImmutableSet.copyOf(Sets.union(DEFAULT_SUPPORTED_ROLE_OPTIONS, DatabaseDescriptor.getAuthenticator().getSupportedRoleOptions()));

alterableOptions = ImmutableSet.copyOf(Sets.union(DEFAULT_ALTERABLE_ROLE_OPTIONS, DatabaseDescriptor.getAuthenticator().getAlterableRoleOptions()));

// Inherit parsing and validation from existing config parser
invalidClientDisconnectPeriodMillis = new DurationSpec.LongMillisecondsBound(parameters.getOrDefault(PARAM_INVALID_ROLE_DISCONNECT_TASK_PERIOD, "0h")).toMilliseconds();
Expand Down
23 changes: 23 additions & 0 deletions src/java/org/apache/cassandra/auth/IAuthenticator.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import javax.annotation.Nonnull;

import com.google.common.collect.ImmutableSet;
import org.apache.cassandra.exceptions.AuthenticationException;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.ClientState;
Expand Down Expand Up @@ -66,6 +67,28 @@ default boolean supportsEarlyAuthentication()
*/
Set<? extends IResource> protectedResources();

/**
* Set of IRoleManager.Options used by this authenticator and supported by CREATE ROLE and ALTER ROLE statements.
*
* @return A set of IRoleManager.Options that this authenticator requires support for.
*/
default Set<IRoleManager.Option> getSupportedRoleOptions()
{
return ImmutableSet.of();
}

/**
* Set of IRoleManager.Options used by this authenticator that users are allowed to alter via
* ALTER ROLE statements. Alterable role options must also be supported role options.
*
* @return A set of supported role options that users are allowed to alter.
*/
default Set<IRoleManager.Option> getAlterableRoleOptions()
{
return ImmutableSet.of();
}


/**
* Validates configuration of IAuthenticator implementation (if configurable).
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public NetworkPermissionsCache(INetworkAuthorizer authorizer)
DatabaseDescriptor::getRolesCacheActiveUpdate,
authorizer::authorize,
authorizer.bulkLoader(),
() -> DatabaseDescriptor.getAuthenticator().requireAuthentication());
DatabaseDescriptor::isAuthenticationRequired);

MBeanWrapper.instance.registerMBean(this, MBEAN_NAME_BASE + DEPRECATED_CACHE_NAME);
}
Expand Down
31 changes: 31 additions & 0 deletions src/java/org/apache/cassandra/auth/PasswordAuthenticator.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ public class PasswordAuthenticator implements IAuthenticator, AuthCache.BulkLoad
public static final String PASSWORD_KEY = "password";
private static final Set<AuthenticationMode> AUTHENTICATION_MODES = Collections.singleton(AuthenticationMode.PASSWORD);

@VisibleForTesting
static final Set<IRoleManager.Option> SUPPORTED_ROLE_OPTIONS =
ImmutableSet.of(IRoleManager.Option.PASSWORD,
IRoleManager.Option.HASHED_PASSWORD,
IRoleManager.Option.GENERATED_PASSWORD);

@VisibleForTesting
static final Set<IRoleManager.Option> ALTERABLE_ROLE_OPTIONS =
ImmutableSet.of(IRoleManager.Option.PASSWORD,
IRoleManager.Option.HASHED_PASSWORD,
IRoleManager.Option.GENERATED_PASSWORD);

static final byte NUL = 0;
private SelectStatement authenticateStatement;

Expand All @@ -86,7 +98,26 @@ public PasswordAuthenticator()
AuthCacheService.instance.register(cache);
}

/**
* {@inheritDoc}
*/
@Override
public Set<IRoleManager.Option> getSupportedRoleOptions()
{
return SUPPORTED_ROLE_OPTIONS;
}

/**
* {@inheritDoc}
*/
@Override
public Set<IRoleManager.Option> getAlterableRoleOptions()
{
return ALTERABLE_ROLE_OPTIONS;
}

// No anonymous access.
@Override
public boolean requireAuthentication()
{
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/java/org/apache/cassandra/auth/Roles.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class Roles

private static final Role NO_ROLE = new Role("", false, false, Collections.emptyMap(), Collections.emptySet());

public static final RolesCache cache = new RolesCache(DatabaseDescriptor.getRoleManager(), () -> DatabaseDescriptor.getAuthenticator().requireAuthentication());
public static final RolesCache cache = new RolesCache(DatabaseDescriptor.getRoleManager(), DatabaseDescriptor::isAuthenticationRequired);

/** Use {@link AuthCacheService#initializeAndRegisterCaches} rather than calling this directly */
public static void init()
Expand Down
36 changes: 36 additions & 0 deletions src/java/org/apache/cassandra/config/DatabaseDescriptor.java
Original file line number Diff line number Diff line change
Expand Up @@ -1959,16 +1959,52 @@ public static void setCryptoProvider(AbstractCryptoProvider cryptoProvider)
DatabaseDescriptor.cryptoProvider = cryptoProvider;
}

/**
* Returns the authenticator configured for this node.
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the future this will become getDefaultAuthenticator(). Additional methods will be added to enable/disable negotiation and provide access to the node's supported authenticators.

public static IAuthenticator getAuthenticator()
{
return authenticator;
}

/**
* Returns an authenticator configured for this node, if it is of the requested type.
* @param clazz The class of the requested authenticator: e.g. PasswordAuthenticator.class.
* @return An Optional of the configured authenticator, if it is of the requested type; otherwise
* returns an empty Optional.
*/
public static <T extends IAuthenticator> Optional<T> getAuthenticator(Class<T> clazz)
{
return hasAuthenticator(clazz) ? Optional.of(clazz.cast(authenticator)) : Optional.empty();
}

/**
* Sets the authenticator used by this node to authenticate clients.
*/
public static void setAuthenticator(IAuthenticator authenticator)
{
DatabaseDescriptor.authenticator = authenticator;
}

/**
* Indicates if this node uses an authenticator that requires authentication.
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the future, the semantics will change from "'The' authenticator requires authentication" to "Any supported authenticator requires authentication." Existing callers to this method should be unaffected by that change.

public static boolean isAuthenticationRequired()
{
return DatabaseDescriptor.getAuthenticator().requireAuthentication();
}

/**
* Indicates if this node is configured with an authenticator of the specified type.
* @param clazz The class of the authenticator.
* @return True if this node has an authenticator of the specified type, false otherwise.
*/
private static boolean hasAuthenticator(Class<? extends IAuthenticator> clazz)
{
return clazz.isAssignableFrom(authenticator.getClass());
}


public static IAuthorizer getAuthorizer()
{
return authorizer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import java.util.Optional;

import org.apache.cassandra.auth.IAuthenticator;
import org.apache.cassandra.auth.PasswordAuthenticator;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.marshal.UTF8Type;
Expand All @@ -42,11 +41,7 @@ final class CredentialsCacheKeysTable extends AbstractMutableVirtualTable
.addPartitionKeyColumn(ROLE, UTF8Type.instance)
.build());

IAuthenticator authenticator = DatabaseDescriptor.getAuthenticator();
if (authenticator instanceof PasswordAuthenticator)
this.passwordAuthenticatorOptional = Optional.of((PasswordAuthenticator) authenticator);
else
this.passwordAuthenticatorOptional = Optional.empty();
this.passwordAuthenticatorOptional = DatabaseDescriptor.getAuthenticator(PasswordAuthenticator.class);
}

public DataSet data()
Expand Down
2 changes: 1 addition & 1 deletion src/java/org/apache/cassandra/service/CassandraDaemon.java
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ public void validateTransportsCanStart()
{
if (StorageService.instance.isSurveyMode())
{
if (!StorageService.instance.readyToFinishJoiningRing() || DatabaseDescriptor.getAuthenticator().requireAuthentication())
if (!StorageService.instance.readyToFinishJoiningRing() || DatabaseDescriptor.isAuthenticationRequired())
{
throw new IllegalStateException("Not starting client transports in write_survey mode as it's bootstrapping or " +
"auth is enabled");
Expand Down
4 changes: 2 additions & 2 deletions src/java/org/apache/cassandra/service/ClientState.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ protected ClientState(InetSocketAddress remoteAddress)
{
this.isInternal = false;
this.remoteAddress = remoteAddress;
if (!DatabaseDescriptor.getAuthenticator().requireAuthentication())
if (!DatabaseDescriptor.isAuthenticationRequired())
this.user = AuthenticatedUser.ANONYMOUS_USER;
}

Expand Down Expand Up @@ -623,7 +623,7 @@ public boolean isOrdinaryUser()
*/
public boolean isSuper()
{
return !DatabaseDescriptor.getAuthenticator().requireAuthentication() || (user != null && user.isSuper());
return !DatabaseDescriptor.isAuthenticationRequired() || (user != null && user.isSuper());
}

/**
Expand Down
Loading