com.datastax.driver.dse.auth.DseGSSAPIAuthProvider Maven / Gradle / Ivy
/*
* Copyright DataStax, Inc.
*
* This software can be used solely with DataStax Enterprise. Please consult the license at
* http://www.datastax.com/terms/datastax-dse-driver-license-terms
*/
package com.datastax.driver.dse.auth;
import com.datastax.driver.core.AuthProvider;
import com.datastax.driver.core.Authenticator;
import com.datastax.driver.core.exceptions.AuthenticationException;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import java.net.InetSocketAddress;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
/**
* {@link AuthProvider} that provides GSSAPI authenticator instances for clients to connect to DSE
* clusters secured with {@code DseAuthenticator}.
*
* To create a cluster using this auth provider, declare the following:
*
*
{@code
* Cluster cluster = Cluster.builder()
* .addContactPoint(hostname)
* .withAuthProvider(DseGSSAPIAuthProvider.builder().build())
* .build();
* }
*
* Kerberos Authentication
*
* Keytab and ticket cache settings are specified using a standard JAAS configuration file. The
* location of the file can be set using the java.security.auth.login.config
system
* property or by adding a login.config.url.n
entry in the java.security
* properties file.
*
* Alternatively a {@link Configuration} object can be provided using {@link
* #DseGSSAPIAuthProvider(Configuration)} to set the JAAS configuration programmatically.
*
*
See the following documents for further details:
*
*
* - JAAS
* Login Configuration File;
*
- JAAS
* Authentication Tutorial for more on JAAS in general.
*
*
* Authentication using ticket cache
*
* Run kinit
to obtain a ticket and populate the cache before connecting. JAAS config:
*
*
* DseClient {
* com.sun.security.auth.module.Krb5LoginModule required
* useTicketCache=true
* renewTGT=true;
* };
*
*
* Authentication using a keytab file
*
* To enable authentication using a keytab file, specify its location on disk. If your keytab
* contains more than one principal key, you should also specify which one to select.
*
*
* DseClient {
* com.sun.security.auth.module.Krb5LoginModule required
* useKeyTab=true
* keyTab="/path/to/file.keytab"
* principal="[email protected]";
* };
*
*
* Specifying SASL protocol name
*
* The SASL protocol name used by this auth provider defaults to "
* {@value #DEFAULT_SASL_PROTOCOL_NAME}
".
*
* Important: the SASL protocol name should match the username of the Kerberos
* service principal used by the DSE server. This information is specified in the dse.yaml file by
* the {@code service_principal} option under the kerberos_options
* section, and may vary from one DSE installation to another – especially if you installed
* DSE with an automated package installer.
*
*
For example, if your dse.yaml file contains the following:
*
*
{@code
* kerberos_options:
* ...
* service_principal: cassandra/[email protected]
* }
*
* The correct SASL protocol name to use when authenticating against this DSE server is "{@code
* cassandra}".
*
* Should you need to change the SASL protocol name, use one of the methods below:
*
*
* - Specify the protocol name via one of the following constructors: {@link
* #DseGSSAPIAuthProvider(String)} or {@link #DseGSSAPIAuthProvider(Configuration, String)};
*
- Specify the protocol name with the {@code dse.sasl.protocol} system property when starting
* your application, e.g. {@code -Ddse.sasl.protocol=cassandra}.
*
*
* If a non-null SASL protocol name is provided to the aforementioned constructors, that name takes
* precedence over the contents of the {@code dse.sasl.protocol} system property.
*
* @see Authenticating
* a DSE cluster with Kerberos
*/
public class DseGSSAPIAuthProvider implements AuthProvider {
/** The default SASL protocol name used by this auth provider. */
public static final String DEFAULT_SASL_PROTOCOL_NAME = "dse";
/** The name of the system property to use to specify the SASL protocol name. */
public static final String SASL_PROTOCOL_NAME_PROPERTY = "dse.sasl.protocol";
/**
* The default SASL properties:
*
*
* javax.security.sasl.server.authentication = true
* javax.security.sasl.qop = auth
*
*/
public static final Map DEFAULT_SASL_PROPERTIES =
ImmutableMap.builder()
.put(Sasl.SERVER_AUTH, "true")
.put(Sasl.QOP, "auth")
.build();
private final Configuration loginConfiguration;
private final String saslProtocol;
private final String authorizationId;
private final Subject subject;
private final Map properties;
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Configuration loginConfiguration;
private String saslProtocol;
private String authorizationId;
private Subject subject;
private Map properties = new HashMap(DEFAULT_SASL_PROPERTIES);
private Builder() {}
/**
* @param loginConfiguration The login configuration to use to create a {@link LoginContext}. If
* {@link #withSubject} is also used, this input is not used.
*/
public Builder withLoginConfiguration(Configuration loginConfiguration) {
this.loginConfiguration = loginConfiguration;
return this;
}
/**
* @param saslProtocol The SASL protocol name to use; should match the username of the Kerberos
* service principal used by the DSE server.
*/
public Builder withSaslProtocol(String saslProtocol) {
this.saslProtocol = saslProtocol;
return this;
}
/** @param authorizationId The authorization ID (allows proxy authentication). */
public Builder withAuthorizationId(String authorizationId) {
this.authorizationId = authorizationId;
return this;
}
/**
* @param subject A previously authenticated subject to reuse. If provided, any calls to {@link
* #withLoginConfiguration} are ignored.
*/
public Builder withSubject(Subject subject) {
this.subject = subject;
return this;
}
/**
* Add a SASL property to use when creating the SASL client.
*
* @param name the property name.
* @param value the property value.
* @see Sasl
*/
public Builder addSaslProperty(String name, String value) {
this.properties.put(name, value);
return this;
}
public DseGSSAPIAuthProvider build() {
return new DseGSSAPIAuthProvider(
loginConfiguration, subject, saslProtocol, authorizationId, properties);
}
}
/**
* Creates an instance of {@code DseGSSAPIAuthProvider} with default login configuration options
* and default SASL protocol name ({@value #DEFAULT_SASL_PROTOCOL_NAME}).
*
* @deprecated Use {@link Builder} to create {@link DseGSSAPIAuthProvider} instead.
*/
@Deprecated
@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
public DseGSSAPIAuthProvider() {
this(null, null, null, null, DEFAULT_SASL_PROPERTIES);
}
/**
* Creates an instance of {@code DseGSSAPIAuthProvider} with the given login configuration and
* default SASL protocol name ({@value #DEFAULT_SASL_PROTOCOL_NAME}).
*
* @param loginConfiguration The login configuration to use to create a {@link LoginContext}.
* @deprecated Use {@link Builder} to create {@link DseGSSAPIAuthProvider} instead.
*/
@Deprecated
@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
public DseGSSAPIAuthProvider(Configuration loginConfiguration) {
this(loginConfiguration, null, null, null, DEFAULT_SASL_PROPERTIES);
}
/**
* Creates an instance of {@code DseGSSAPIAuthProvider} with default login configuration and the
* given SASL protocol name.
*
* @param saslProtocol The SASL protocol name to use; should match the username of the Kerberos
* service principal used by the DSE server.
* @deprecated Use {@link Builder} to create {@link DseGSSAPIAuthProvider} instead.
*/
@Deprecated
@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
public DseGSSAPIAuthProvider(String saslProtocol) {
this(null, null, saslProtocol, null, DEFAULT_SASL_PROPERTIES);
}
/**
* Creates an instance of {@code DseGSSAPIAuthProvider} with the given login configuration and the
* given SASL protocol name.
*
* @param loginConfiguration The login configuration to use to create a {@link LoginContext}.
* @param saslProtocol The SASL protocol name to use; should match the username of the Kerberos
* service principal used by the DSE server.
* @deprecated Use {@link Builder} to create {@link DseGSSAPIAuthProvider} instead.
*/
@Deprecated
@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"})
public DseGSSAPIAuthProvider(Configuration loginConfiguration, String saslProtocol) {
this(loginConfiguration, null, saslProtocol, null, DEFAULT_SASL_PROPERTIES);
}
private DseGSSAPIAuthProvider(
Configuration loginConfiguration,
Subject subject,
String saslProtocol,
String authorizationId,
Map properties) {
this.loginConfiguration = loginConfiguration;
this.subject = subject;
this.saslProtocol = saslProtocol;
this.authorizationId = authorizationId;
this.properties = ImmutableMap.copyOf(properties);
}
@Override
public Authenticator newAuthenticator(InetSocketAddress host, String authenticator)
throws AuthenticationException {
if (subject != null) {
return new GSSAPIAuthenticator(
authenticator, authorizationId, host, subject, saslProtocol, properties);
} else {
return new GSSAPIAuthenticator(
authenticator, authorizationId, host, loginConfiguration, saslProtocol, properties);
}
}
private static class GSSAPIAuthenticator extends BaseDseAuthenticator {
private static final String JAAS_CONFIG_ENTRY = "DseClient";
private static final String[] SUPPORTED_MECHANISMS = new String[] {"GSSAPI"};
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
private static final byte[] MECHANISM = "GSSAPI".getBytes(Charsets.UTF_8);
private static final byte[] SERVER_INITIAL_CHALLENGE = "GSSAPI-START".getBytes(Charsets.UTF_8);
private final Subject subject;
private final SaslClient saslClient;
private GSSAPIAuthenticator(
String authenticator,
String authorizationId,
InetSocketAddress host,
Configuration loginConfiguration,
String saslProtocol,
Map properties) {
super(authenticator);
try {
String protocol = saslProtocol;
if (protocol == null) {
protocol = System.getProperty(SASL_PROTOCOL_NAME_PROPERTY, DEFAULT_SASL_PROTOCOL_NAME);
}
LoginContext login = new LoginContext(JAAS_CONFIG_ENTRY, null, null, loginConfiguration);
login.login();
subject = login.getSubject();
saslClient =
Sasl.createSaslClient(
SUPPORTED_MECHANISMS,
authorizationId,
protocol,
host.getAddress().getCanonicalHostName(),
properties,
null);
} catch (LoginException e) {
throw new RuntimeException(e);
} catch (SaslException e) {
throw new RuntimeException(e);
}
}
private GSSAPIAuthenticator(
String authenticator,
String authorizationId,
InetSocketAddress host,
Subject subject,
String saslProtocol,
Map properties) {
super(authenticator);
try {
String protocol = saslProtocol;
if (protocol == null) {
protocol = System.getProperty(SASL_PROTOCOL_NAME_PROPERTY, DEFAULT_SASL_PROTOCOL_NAME);
}
this.subject = subject;
saslClient =
Sasl.createSaslClient(
SUPPORTED_MECHANISMS,
authorizationId,
protocol,
host.getAddress().getCanonicalHostName(),
properties,
null);
} catch (SaslException e) {
throw new RuntimeException(e);
}
}
@Override
public byte[] getMechanism() {
return MECHANISM.clone();
}
@Override
public byte[] getInitialServerChallenge() {
return SERVER_INITIAL_CHALLENGE.clone();
}
@Override
public byte[] evaluateChallenge(byte[] challenge) {
if (Arrays.equals(SERVER_INITIAL_CHALLENGE, challenge)) {
if (!saslClient.hasInitialResponse()) {
return EMPTY_BYTE_ARRAY;
}
challenge = EMPTY_BYTE_ARRAY;
}
final byte[] internalChallenge = challenge;
try {
return Subject.doAs(
subject,
new PrivilegedExceptionAction() {
@Override
public byte[] run() throws SaslException {
return saslClient.evaluateChallenge(internalChallenge);
}
});
} catch (PrivilegedActionException e) {
throw new RuntimeException(e.getException());
}
}
}
}