1 /* 2 * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.jmx.remote.security; 27 28 import java.io.IOException; 29 import java.security.AccessController; 30 import java.security.Principal; 31 import java.security.PrivilegedAction; 32 import java.security.PrivilegedActionException; 33 import java.security.PrivilegedExceptionAction; 34 import java.util.Collections; 35 import java.util.HashMap; 36 import java.util.Map; 37 import java.util.Properties; 38 import javax.management.remote.JMXPrincipal; 39 import javax.management.remote.JMXAuthenticator; 40 import javax.security.auth.AuthPermission; 41 import javax.security.auth.Subject; 42 import javax.security.auth.callback.*; 43 import javax.security.auth.login.AppConfigurationEntry; 44 import javax.security.auth.login.Configuration; 45 import javax.security.auth.login.LoginContext; 46 import javax.security.auth.login.LoginException; 47 import javax.security.auth.spi.LoginModule; 48 import com.sun.jmx.remote.util.ClassLogger; 49 import com.sun.jmx.remote.util.EnvHelp; 50 51 /** 52 * <p>This class represents a 53 * <a href="{@docRoot}/../guide/security/jaas/JAASRefGuide.html">JAAS</a> 54 * based implementation of the {@link JMXAuthenticator} interface.</p> 55 * 56 * <p>Authentication is performed by passing the supplied user's credentials 57 * to one or more authentication mechanisms ({@link LoginModule}) for 58 * verification. An authentication mechanism acquires the user's credentials 59 * by calling {@link NameCallback} and/or {@link PasswordCallback}. 60 * If authentication is successful then an authenticated {@link Subject} 61 * filled in with a {@link Principal} is returned. Authorization checks 62 * will then be performed based on this <code>Subject</code>.</p> 63 * 64 * <p>By default, a single file-based authentication mechanism 65 * {@link FileLoginModule} is configured (<code>FileLoginConfig</code>).</p> 66 * 67 * <p>To override the default configuration use the 68 * <code>com.sun.management.jmxremote.login.config</code> management property 69 * described in the JRE/conf/management/management.properties file. 70 * Set this property to the name of a JAAS configuration entry and ensure that 71 * the entry is loaded by the installed {@link Configuration}. In addition, 72 * ensure that the authentication mechanisms specified in the entry acquire 73 * the user's credentials by calling {@link NameCallback} and 74 * {@link PasswordCallback} and that they return a {@link Subject} filled-in 75 * with a {@link Principal}, for those users that are successfully 76 * authenticated.</p> 77 */ 78 public final class JMXPluggableAuthenticator implements JMXAuthenticator { 79 80 /** 81 * Creates an instance of <code>JMXPluggableAuthenticator</code> 82 * and initializes it with a {@link LoginContext}. 83 * 84 * @param env the environment containing configuration properties for the 85 * authenticator. Can be null, which is equivalent to an empty 86 * Map. 87 * @exception SecurityException if the authentication mechanism cannot be 88 * initialized. 89 */ 90 public JMXPluggableAuthenticator(Map<?, ?> env) { 91 92 String loginConfigName = null; 93 String passwordFile = null; 94 95 if (env != null) { 96 loginConfigName = (String) env.get(LOGIN_CONFIG_PROP); 97 passwordFile = (String) env.get(PASSWORD_FILE_PROP); 98 } 99 100 try { 101 102 if (loginConfigName != null) { 103 // use the supplied JAAS login configuration 104 loginContext = 105 new LoginContext(loginConfigName, new JMXCallbackHandler()); 106 107 } else { 108 // use the default JAAS login configuration (file-based) 109 SecurityManager sm = System.getSecurityManager(); 110 if (sm != null) { 111 sm.checkPermission( 112 new AuthPermission("createLoginContext." + 113 LOGIN_CONFIG_NAME)); 114 } 115 116 final String pf = passwordFile; 117 try { 118 loginContext = AccessController.doPrivileged( 119 new PrivilegedExceptionAction<LoginContext>() { 120 public LoginContext run() throws LoginException { 121 return new LoginContext( 122 LOGIN_CONFIG_NAME, 123 null, 124 new JMXCallbackHandler(), 125 new FileLoginConfig(pf)); 126 } 127 }); 128 } catch (PrivilegedActionException pae) { 129 throw (LoginException) pae.getException(); 130 } 131 } 132 133 } catch (LoginException le) { 134 authenticationFailure("authenticate", le); 135 136 } catch (SecurityException se) { 137 authenticationFailure("authenticate", se); 138 } 139 } 140 141 /** 142 * Authenticate the <code>MBeanServerConnection</code> client 143 * with the given client credentials. 144 * 145 * @param credentials the user-defined credentials to be passed in 146 * to the server in order to authenticate the user before creating 147 * the <code>MBeanServerConnection</code>. This parameter must 148 * be a two-element <code>String[]</code> containing the client's 149 * username and password in that order. 150 * 151 * @return the authenticated subject containing a 152 * <code>JMXPrincipal(username)</code>. 153 * 154 * @exception SecurityException if the server cannot authenticate the user 155 * with the provided credentials. 156 */ 157 public Subject authenticate(Object credentials) { 158 // Verify that credentials is of type String[]. 159 // 160 if (!(credentials instanceof String[])) { 161 // Special case for null so we get a more informative message 162 if (credentials == null) 163 authenticationFailure("authenticate", "Credentials required"); 164 165 final String message = 166 "Credentials should be String[] instead of " + 167 credentials.getClass().getName(); 168 authenticationFailure("authenticate", message); 169 } 170 // Verify that the array contains two elements. 171 // 172 final String[] aCredentials = (String[]) credentials; 173 if (aCredentials.length != 2) { 174 final String message = 175 "Credentials should have 2 elements not " + 176 aCredentials.length; 177 authenticationFailure("authenticate", message); 178 } 179 // Verify that username exists and the associated 180 // password matches the one supplied by the client. 181 // 182 username = aCredentials[0]; 183 password = aCredentials[1]; 184 if (username == null || password == null) { 185 final String message = "Username or password is null"; 186 authenticationFailure("authenticate", message); 187 } 188 189 // Perform authentication 190 try { 191 loginContext.login(); 192 final Subject subject = loginContext.getSubject(); 193 AccessController.doPrivileged(new PrivilegedAction<Void>() { 194 public Void run() { 195 subject.setReadOnly(); 196 return null; 197 } 198 }); 199 200 return subject; 201 202 } catch (LoginException le) { 203 authenticationFailure("authenticate", le); 204 } 205 return null; 206 } 207 208 private static void authenticationFailure(String method, String message) 209 throws SecurityException { 210 final String msg = "Authentication failed! " + message; 211 final SecurityException e = new SecurityException(msg); 212 logException(method, msg, e); 213 throw e; 214 } 215 216 private static void authenticationFailure(String method, 217 Exception exception) 218 throws SecurityException { 219 String msg; 220 SecurityException se; 221 if (exception instanceof SecurityException) { 222 msg = exception.getMessage(); 223 se = (SecurityException) exception; 224 } else { 225 msg = "Authentication failed! " + exception.getMessage(); 226 final SecurityException e = new SecurityException(msg); 227 EnvHelp.initCause(e, exception); 228 se = e; 229 } 230 logException(method, msg, se); 231 throw se; 232 } 233 234 private static void logException(String method, 235 String message, 236 Exception e) { 237 if (logger.traceOn()) { 238 logger.trace(method, message); 239 } 240 if (logger.debugOn()) { 241 logger.debug(method, e); 242 } 243 } 244 245 private LoginContext loginContext; 246 private String username; 247 private String password; 248 private static final String LOGIN_CONFIG_PROP = 249 "jmx.remote.x.login.config"; 250 private static final String LOGIN_CONFIG_NAME = "JMXPluggableAuthenticator"; 251 private static final String PASSWORD_FILE_PROP = 252 "jmx.remote.x.password.file"; 253 private static final ClassLogger logger = 254 new ClassLogger("javax.management.remote.misc", LOGIN_CONFIG_NAME); 255 256 /** 257 * This callback handler supplies the username and password (which was 258 * originally supplied by the JMX user) to the JAAS login module performing 259 * the authentication. No interactive user prompting is required because the 260 * credentials are already available to this class (via its enclosing class). 261 */ 262 private final class JMXCallbackHandler implements CallbackHandler { 263 264 /** 265 * Sets the username and password in the appropriate Callback object. 266 */ 267 public void handle(Callback[] callbacks) 268 throws IOException, UnsupportedCallbackException { 269 270 for (int i = 0; i < callbacks.length; i++) { 271 if (callbacks[i] instanceof NameCallback) { 272 ((NameCallback)callbacks[i]).setName(username); 273 274 } else if (callbacks[i] instanceof PasswordCallback) { 275 ((PasswordCallback)callbacks[i]) 276 .setPassword(password.toCharArray()); 277 278 } else { 279 throw new UnsupportedCallbackException 280 (callbacks[i], "Unrecognized Callback"); 281 } 282 } 283 } 284 } 285 286 /** 287 * This class defines the JAAS configuration for file-based authentication. 288 * It is equivalent to the following textual configuration entry: 289 * <pre> 290 * JMXPluggableAuthenticator { 291 * com.sun.jmx.remote.security.FileLoginModule required; 292 * }; 293 * </pre> 294 */ 295 private static class FileLoginConfig extends Configuration { 296 297 // The JAAS configuration for file-based authentication 298 private AppConfigurationEntry[] entries; 299 300 // The classname of the login module for file-based authentication 301 private static final String FILE_LOGIN_MODULE = 302 FileLoginModule.class.getName(); 303 304 // The option that identifies the password file to use 305 private static final String PASSWORD_FILE_OPTION = "passwordFile"; 306 307 /** 308 * Creates an instance of <code>FileLoginConfig</code> 309 * 310 * @param passwordFile A filepath that identifies the password file to use. 311 * If null then the default password file is used. 312 */ 313 public FileLoginConfig(String passwordFile) { 314 315 Map<String, String> options; 316 if (passwordFile != null) { 317 options = new HashMap<String, String>(1); 318 options.put(PASSWORD_FILE_OPTION, passwordFile); 319 } else { 320 options = Collections.emptyMap(); 321 } 322 323 entries = new AppConfigurationEntry[] { 324 new AppConfigurationEntry(FILE_LOGIN_MODULE, 325 AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, 326 options) 327 }; 328 } 329 330 /** 331 * Gets the JAAS configuration for file-based authentication 332 */ 333 public AppConfigurationEntry[] getAppConfigurationEntry(String name) { 334 335 return name.equals(LOGIN_CONFIG_NAME) ? entries : null; 336 } 337 338 /** 339 * Refreshes the configuration. 340 */ 341 public void refresh() { 342 // the configuration is fixed 343 } 344 } 345 346 }