blob: d5a73a24644bc5ff24c3ada238f0f421d9c6e451 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2004-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package com.sun.jmx.remote.security;
27
28import java.io.IOException;
29import java.security.AccessController;
30import java.security.Principal;
31import java.security.PrivilegedAction;
32import java.security.PrivilegedActionException;
33import java.security.PrivilegedExceptionAction;
34import java.util.Collections;
35import java.util.HashMap;
36import java.util.Map;
37import java.util.Properties;
38import javax.management.remote.JMXPrincipal;
39import javax.management.remote.JMXAuthenticator;
40import javax.security.auth.AuthPermission;
41import javax.security.auth.Subject;
42import javax.security.auth.callback.*;
43import javax.security.auth.login.AppConfigurationEntry;
44import javax.security.auth.login.Configuration;
45import javax.security.auth.login.LoginContext;
46import javax.security.auth.login.LoginException;
47import javax.security.auth.spi.LoginModule;
48import com.sun.jmx.remote.util.ClassLogger;
49import 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/lib/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 */
78public 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 */
262private 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 */
295private static class FileLoginConfig extends Configuration {
296
297 // The JAAS configuration for file-based authentication
298 private static 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}