/*
 * 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:
/** * 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 LoginModules via JMXCallbackHandler.
Throws:
  • SecurityException – if the server cannot authenticate the user with the provided credentials.
Returns:the authenticated subject containing any Principals added by the LoginModules
/** * 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()); } } } }