com.datastax.driver.dse.auth.DseGSSAPIAuthProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dse-java-driver-core Show documentation
Show all versions of dse-java-driver-core Show documentation
A driver for DataStax Enterprise (DSE)
and Apache Cassandra 1.2+ clusters that works exclusively with the
Cassandra Query Language version 3 (CQL3) and Cassandra's binary protocol,
supporting DSE-specific features such as geospatial types, DSE Graph and DSE authentication.
/*
* 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.util.HashMap;
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;
import java.net.InetSocketAddress;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.Map;
/**
* {@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());
}
}
}
}