/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.auth.jmx;
import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.management.remote.JMXAuthenticator;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.exceptions.ConfigurationException;
An alternative to the JAAS based implementation of JMXAuthenticator provided
by the JDK (JMXPluggableAuthenticator).
Authentication is performed via delegation to a LoginModule. The JAAS login
config is specified by passing its identifier in a custom system property:
cassandra.jmx.remote.login.config
The location of the JAAS configuration file containing that config is
specified in the standard way, using the java.security.auth.login.config
system property.
If authentication is successful then a Subject containing one or more
Principals is returned. This Subject may then be used during authorization
if a JMX authorization is enabled.
/**
* An alternative to the JAAS based implementation of JMXAuthenticator provided
* by the JDK (JMXPluggableAuthenticator).
*
* Authentication is performed via delegation to a LoginModule. The JAAS login
* config is specified by passing its identifier in a custom system property:
* cassandra.jmx.remote.login.config
*
* The location of the JAAS configuration file containing that config is
* specified in the standard way, using the java.security.auth.login.config
* system property.
*
* If authentication is successful then a Subject containing one or more
* Principals is returned. This Subject may then be used during authorization
* if a JMX authorization is enabled.
*/
public final class AuthenticationProxy implements JMXAuthenticator
{
private static Logger logger = LoggerFactory.getLogger(AuthenticationProxy.class);
// Identifier of JAAS configuration to be used for subject authentication
private final String loginConfigName;
Creates an instance of JMXPluggableAuthenticator
and initializes it with a LoginContext
. Params: - loginConfigName – name of the specifig JAAS login configuration to
use when authenticating JMX connections
Throws: - SecurityException – if the authentication mechanism cannot be
initialized.
/**
* Creates an instance of <code>JMXPluggableAuthenticator</code>
* and initializes it with a {@link LoginContext}.
*
* @param loginConfigName name of the specifig JAAS login configuration to
* use when authenticating JMX connections
* @throws SecurityException if the authentication mechanism cannot be
* initialized.
*/
public AuthenticationProxy(String loginConfigName)
{
if (loginConfigName == null)
throw new ConfigurationException("JAAS login configuration missing for JMX authenticator setup");
this.loginConfigName = loginConfigName;
}
Perform authentication of the client opening the
MBeanServerConnection
Params: - credentials – optionally these credentials may be supplied by the JMX user. Out of the box, the JDK's
RMIServerImpl
is capable of supplying a two element String[], containing username and password. If present, these credentials will be made available to configured
LoginModule
s via
JMXCallbackHandler
.
Throws: - SecurityException – if the server cannot authenticate the user
with the provided credentials.
Returns: the authenticated subject containing any
Principal
s added by the
LoginModule
s
/**
* Perform authentication of the client opening the {@code}MBeanServerConnection{@code}
*
* @param credentials optionally these credentials may be supplied by the JMX user.
* Out of the box, the JDK's {@code}RMIServerImpl{@code} is capable
* of supplying a two element String[], containing username and password.
* If present, these credentials will be made available to configured
* {@code}LoginModule{@code}s via {@code}JMXCallbackHandler{@code}.
*
* @return the authenticated subject containing any {@code}Principal{@code}s added by
*the {@code}LoginModule{@code}s
*
* @throws SecurityException if the server cannot authenticate the user
* with the provided credentials.
*/
public Subject authenticate(Object credentials)
{
// The credentials object is expected to be a string array holding the subject's
// username & password. Those values are made accessible to LoginModules via the
// JMXCallbackHandler.
JMXCallbackHandler callbackHandler = new JMXCallbackHandler(credentials);
try
{
LoginContext loginContext = new LoginContext(loginConfigName, callbackHandler);
loginContext.login();
final Subject subject = loginContext.getSubject();
if (!subject.isReadOnly())
{
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
subject.setReadOnly();
return null;
});
}
return subject;
}
catch (LoginException e)
{
logger.trace("Authentication exception", e);
throw new SecurityException("Authentication error", e);
}
}
This callback handler supplies the username and password (which was
optionally supplied by the JMX user) to the JAAS login module performing
the authentication, should it require them . No interactive user
prompting is necessary because the credentials are already available to
this class (via its enclosing class).
/**
* This callback handler supplies the username and password (which was
* optionally supplied by the JMX user) to the JAAS login module performing
* the authentication, should it require them . No interactive user
* prompting is necessary because the credentials are already available to
* this class (via its enclosing class).
*/
private static final class JMXCallbackHandler implements CallbackHandler
{
private char[] username;
private char[] password;
private JMXCallbackHandler(Object credentials)
{
// if username/password credentials were supplied, store them in
// the relevant variables to make them accessible to LoginModules
// via JMXCallbackHandler
if (credentials instanceof String[])
{
String[] strings = (String[]) credentials;
if (strings[0] != null)
username = strings[0].toCharArray();
if (strings[1] != null)
password = strings[1].toCharArray();
}
}
public void handle(Callback[] callbacks) throws UnsupportedCallbackException
{
for (int i = 0; i < callbacks.length; i++)
{
if (callbacks[i] instanceof NameCallback)
((NameCallback)callbacks[i]).setName(username == null ? null : new String(username));
else if (callbacks[i] instanceof PasswordCallback)
((PasswordCallback)callbacks[i]).setPassword(password == null ? null : password);
else
throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback: " + callbacks[i].getClass().getName());
}
}
}
}