ARTEMIS-261 cert-based auth

This commit is contained in:
jbertram 2015-10-28 15:43:33 -05:00 committed by Clebert Suconic
parent e1fa9bcd7b
commit 0c407922a8
32 changed files with 642 additions and 157 deletions

View File

@ -0,0 +1,48 @@
/**
* 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.activemq.artemis.utils;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.security.cert.X509Certificate;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.handler.ssl.SslHandler;
public class CertificateUtil {
public static X509Certificate[] getCertsFromChannel(Channel channel) {
X509Certificate[] certificates = null;
ChannelHandler channelHandler = channel
.pipeline()
.get("ssl");
if (channelHandler != null && channelHandler instanceof SslHandler) {
SslHandler sslHandler = (SslHandler) channelHandler;
try {
certificates = sslHandler
.engine()
.getSession()
.getPeerCertificateChain();
}
catch (SSLPeerUnverifiedException e) {
// ignore
}
}
return certificates;
}
}

View File

@ -16,11 +16,13 @@
*/ */
package org.apache.activemq.artemis.core.security; package org.apache.activemq.artemis.core.security;
import javax.security.cert.X509Certificate;
import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.SimpleString;
public interface SecurityStore { public interface SecurityStore {
void authenticate(String user, String password) throws Exception; void authenticate(String user, String password, X509Certificate[] certificates) throws Exception;
void check(SimpleString address, CheckType checkType, SecurityAuth session) throws Exception; void check(SimpleString address, CheckType checkType, SecurityAuth session) throws Exception;

View File

@ -16,6 +16,7 @@
*/ */
package org.apache.activemq.artemis.core.security.impl; package org.apache.activemq.artemis.core.security.impl;
import javax.security.cert.X509Certificate;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -101,7 +102,7 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
securityRepository.unRegisterListener(this); securityRepository.unRegisterListener(this);
} }
public void authenticate(final String user, final String password) throws Exception { public void authenticate(final String user, final String password, X509Certificate[] certificates) throws Exception {
if (securityEnabled) { if (securityEnabled) {
if (managementClusterUser.equals(user)) { if (managementClusterUser.equals(user)) {
@ -121,18 +122,25 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
} }
} }
if (!securityManager.validateUser(user, password)) { boolean userIsValid = false;
if (securityManager instanceof ActiveMQSecurityManager2) {
userIsValid = ((ActiveMQSecurityManager2)securityManager).validateUser(user, password, certificates);
}
else {
userIsValid = securityManager.validateUser(user, password);
}
if (!userIsValid) {
if (notificationService != null) { if (notificationService != null) {
TypedProperties props = new TypedProperties(); TypedProperties props = new TypedProperties();
props.putSimpleStringProperty(ManagementHelper.HDR_USER, SimpleString.toSimpleString(user));
Notification notification = new Notification(null, CoreNotificationType.SECURITY_AUTHENTICATION_VIOLATION, props); Notification notification = new Notification(null, CoreNotificationType.SECURITY_AUTHENTICATION_VIOLATION, props);
notificationService.sendNotification(notification); notificationService.sendNotification(notification);
} }
throw ActiveMQMessageBundle.BUNDLE.unableToValidateUser(user); throw ActiveMQMessageBundle.BUNDLE.unableToValidateUser();
} }
} }
} }

View File

@ -149,8 +149,8 @@ public interface ActiveMQMessageBundle {
@Message(id = 119030, value = "large-message not initialized on server") @Message(id = 119030, value = "large-message not initialized on server")
ActiveMQIllegalStateException largeMessageNotInitialised(); ActiveMQIllegalStateException largeMessageNotInitialised();
@Message(id = 119031, value = "Unable to validate user: {0}", format = Message.Format.MESSAGE_FORMAT) @Message(id = 119031, value = "Unable to validate user", format = Message.Format.MESSAGE_FORMAT)
ActiveMQSecurityException unableToValidateUser(String user); ActiveMQSecurityException unableToValidateUser();
@Message(id = 119032, value = "User: {0} does not have permission=''{1}'' on address {2}", format = Message.Format.MESSAGE_FORMAT) @Message(id = 119032, value = "User: {0} does not have permission=''{1}'' on address {2}", format = Message.Format.MESSAGE_FORMAT)
ActiveMQSecurityException userNoPermissions(String username, CheckType checkType, String saddress); ActiveMQSecurityException userNoPermissions(String username, CheckType checkType, String saddress);

View File

@ -17,6 +17,7 @@
package org.apache.activemq.artemis.core.server.impl; package org.apache.activemq.artemis.core.server.impl;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.security.cert.X509Certificate;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.PrintWriter; import java.io.PrintWriter;
@ -77,6 +78,7 @@ import org.apache.activemq.artemis.core.postoffice.QueueBinding;
import org.apache.activemq.artemis.core.postoffice.impl.DivertBinding; import org.apache.activemq.artemis.core.postoffice.impl.DivertBinding;
import org.apache.activemq.artemis.core.postoffice.impl.LocalQueueBinding; import org.apache.activemq.artemis.core.postoffice.impl.LocalQueueBinding;
import org.apache.activemq.artemis.core.postoffice.impl.PostOfficeImpl; import org.apache.activemq.artemis.core.postoffice.impl.PostOfficeImpl;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection;
import org.apache.activemq.artemis.core.remoting.server.RemotingService; import org.apache.activemq.artemis.core.remoting.server.RemotingService;
import org.apache.activemq.artemis.core.remoting.server.impl.RemotingServiceImpl; import org.apache.activemq.artemis.core.remoting.server.impl.RemotingServiceImpl;
import org.apache.activemq.artemis.core.replication.ReplicationManager; import org.apache.activemq.artemis.core.replication.ReplicationManager;
@ -125,6 +127,7 @@ import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.protocol.SessionCallback; import org.apache.activemq.artemis.spi.core.protocol.SessionCallback;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
import org.apache.activemq.artemis.utils.ActiveMQThreadFactory; import org.apache.activemq.artemis.utils.ActiveMQThreadFactory;
import org.apache.activemq.artemis.utils.CertificateUtil;
import org.apache.activemq.artemis.utils.ConcurrentHashSet; import org.apache.activemq.artemis.utils.ConcurrentHashSet;
import org.apache.activemq.artemis.utils.ExecutorFactory; import org.apache.activemq.artemis.utils.ExecutorFactory;
import org.apache.activemq.artemis.utils.OrderedExecutorFactory; import org.apache.activemq.artemis.utils.OrderedExecutorFactory;
@ -932,7 +935,11 @@ public class ActiveMQServerImpl implements ActiveMQServer {
final boolean autoCreateQueues) throws Exception { final boolean autoCreateQueues) throws Exception {
if (securityStore != null) { if (securityStore != null) {
securityStore.authenticate(username, password); X509Certificate[] certificates = null;
if (connection.getTransportConnection() instanceof NettyConnection) {
certificates = CertificateUtil.getCertsFromChannel(((NettyConnection)connection.getTransportConnection()).getChannel());
}
securityStore.authenticate(username, password, certificates);
} }
checkSessionLimit(username); checkSessionLimit(username);

View File

@ -19,16 +19,20 @@ package org.apache.activemq.artemis.spi.core.security;
import javax.security.auth.Subject; import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException; import javax.security.auth.login.LoginException;
import javax.security.cert.X509Certificate;
import java.security.Principal; import java.security.Principal;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Set; import java.util.Set;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection;
import org.apache.activemq.artemis.core.security.CheckType; import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role; import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCredentialCallbackHandler; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal; import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
import org.apache.activemq.artemis.utils.CertificateUtil;
/** /**
* This implementation delegates to the JAAS security interfaces. * This implementation delegates to the JAAS security interfaces.
@ -36,33 +40,51 @@ import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
* The {@link Subject} returned by the login context is expecting to have a set of {@link RolePrincipal} for each * The {@link Subject} returned by the login context is expecting to have a set of {@link RolePrincipal} for each
* role of the user. * role of the user.
*/ */
public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager { public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager2 {
private final boolean trace = ActiveMQServerLogger.LOGGER.isTraceEnabled(); private final boolean trace = ActiveMQServerLogger.LOGGER.isTraceEnabled();
private String configurationName; private String configurationName;
public boolean validateUser(final String user, final String password) { @Override
public boolean validateUser(String user, String password) {
throw new UnsupportedOperationException("Invoke validateUser(String, String, X509Certificate[]) instead");
}
@Override
public boolean validateUser(final String user, final String password, X509Certificate[] certificates) {
try { try {
getAuthenticatedSubject(user, password); getAuthenticatedSubject(user, password, certificates);
return true; return true;
} }
catch (LoginException e) { catch (LoginException e) {
ActiveMQServerLogger.LOGGER.debug("Couldn't validate user: " + user, e); ActiveMQServerLogger.LOGGER.debug("Couldn't validate user", e);
return false; return false;
} }
} }
@Override
public boolean validateUserAndRole(String user, String password, Set<Role> roles, CheckType checkType) {
throw new UnsupportedOperationException("Invoke validateUserAndRole(String, String, Set<Role>, CheckType, String, RemotingConnection) instead");
}
@Override
public boolean validateUserAndRole(final String user, public boolean validateUserAndRole(final String user,
final String password, final String password,
final Set<Role> roles, final Set<Role> roles,
final CheckType checkType) { final CheckType checkType,
final String address,
final RemotingConnection connection) {
X509Certificate[] certificates = null;
if (connection.getTransportConnection() instanceof NettyConnection) {
certificates = CertificateUtil.getCertsFromChannel(((NettyConnection) connection.getTransportConnection()).getChannel());
}
Subject localSubject; Subject localSubject;
try { try {
localSubject = getAuthenticatedSubject(user, password); localSubject = getAuthenticatedSubject(user, password, certificates);
} }
catch (LoginException e) { catch (LoginException e) {
ActiveMQServerLogger.LOGGER.debug("Couldn't validate user: " + user, e); ActiveMQServerLogger.LOGGER.debug("Couldn't validate user", e);
return false; return false;
} }
@ -85,15 +107,15 @@ public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager {
} }
if (trace) { if (trace) {
ActiveMQServerLogger.LOGGER.trace("user " + user + (authorized ? " is " : " is NOT ") + "authorized"); ActiveMQServerLogger.LOGGER.trace("user " + (authorized ? " is " : " is NOT ") + "authorized");
} }
} }
return authorized; return authorized;
} }
private Subject getAuthenticatedSubject(final String user, final String password) throws LoginException { private Subject getAuthenticatedSubject(final String user, final String password, final X509Certificate[] certificates) throws LoginException {
LoginContext lc = new LoginContext(configurationName, new JaasCredentialCallbackHandler(user, password)); LoginContext lc = new LoginContext(configurationName, new JaasCallbackHandler(user, password, certificates));
lc.login(); lc.login();
return lc.getSubject(); return lc.getSubject();
} }

View File

@ -16,6 +16,7 @@
*/ */
package org.apache.activemq.artemis.spi.core.security; package org.apache.activemq.artemis.spi.core.security;
import javax.security.cert.X509Certificate;
import java.util.Set; import java.util.Set;
import org.apache.activemq.artemis.core.security.CheckType; import org.apache.activemq.artemis.core.security.CheckType;
@ -32,12 +33,24 @@ import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
*/ */
public interface ActiveMQSecurityManager2 extends ActiveMQSecurityManager { public interface ActiveMQSecurityManager2 extends ActiveMQSecurityManager {
/**
* is this a valid user.
*
* This method is called instead of
* {@link ActiveMQSecurityManager#validateUser(String, String)}.
*
* @param user the user
* @param password the users password
* @return true if a valid user
*/
boolean validateUser(String user, String password, X509Certificate[] certificates);
/** /**
* Determine whether the given user is valid and whether they have * Determine whether the given user is valid and whether they have
* the correct role for the given destination address. * the correct role for the given destination address.
* *
* This method is called instead of * This method is called instead of
* {@link ActiveMQSecurityManager.validateUserAndRole}. * {@link ActiveMQSecurityManager#validateUserAndRole(String, String, Set, CheckType)}.
* *
* @param user the user * @param user the user
* @param password the user's password * @param password the user's password

View File

@ -17,7 +17,7 @@
package org.apache.activemq.artemis.spi.core.security.jaas; package org.apache.activemq.artemis.spi.core.security.jaas;
import javax.security.auth.callback.Callback; import javax.security.auth.callback.Callback;
import java.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
/** /**
* A Callback for SSL certificates. * A Callback for SSL certificates.

View File

@ -23,9 +23,9 @@ import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException; import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule; import javax.security.auth.spi.LoginModule;
import javax.security.cert.X509Certificate;
import java.io.IOException; import java.io.IOException;
import java.security.Principal; import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -35,7 +35,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
/** /**
* A LoginModule that allows for authentication based on SSL certificates. * A LoginModule that allows for authentication based on SSL certificates.
* Allows for subclasses to define methods used to verify user certificates and * Allows for subclasses to define methods used to verify user certificates and
* find user groups. Uses CertificateCallbacks to retrieve certificates. * find user roles. Uses CertificateCallbacks to retrieve certificates.
*/ */
public abstract class CertificateLoginModule implements LoginModule { public abstract class CertificateLoginModule implements LoginModule {
@ -44,7 +44,7 @@ public abstract class CertificateLoginModule implements LoginModule {
private X509Certificate[] certificates; private X509Certificate[] certificates;
private String username; private String username;
private Set<String> groups; private Set<String> roles;
private Set<Principal> principals = new HashSet<Principal>(); private Set<Principal> principals = new HashSet<Principal>();
private boolean debug; private boolean debug;
@ -87,7 +87,7 @@ public abstract class CertificateLoginModule implements LoginModule {
throw new FailedLoginException("No user for client certificate: " + getDistinguishedName(certificates)); throw new FailedLoginException("No user for client certificate: " + getDistinguishedName(certificates));
} }
groups = getUserGroups(username); roles = getUserRoles(username);
if (debug) { if (debug) {
ActiveMQServerLogger.LOGGER.debug("Certificate for user: " + username); ActiveMQServerLogger.LOGGER.debug("Certificate for user: " + username);
@ -102,8 +102,8 @@ public abstract class CertificateLoginModule implements LoginModule {
public boolean commit() throws LoginException { public boolean commit() throws LoginException {
principals.add(new UserPrincipal(username)); principals.add(new UserPrincipal(username));
for (String group : groups) { for (String role : roles) {
principals.add(new RolePrincipal(group)); principals.add(new RolePrincipal(role));
} }
subject.getPrincipals().addAll(principals); subject.getPrincipals().addAll(principals);
@ -147,13 +147,13 @@ public abstract class CertificateLoginModule implements LoginModule {
* Helper method. * Helper method.
*/ */
private void clear() { private void clear() {
groups.clear(); roles.clear();
certificates = null; certificates = null;
} }
/** /**
* Should return a unique name corresponding to the certificates given. The * Should return a unique name corresponding to the certificates given. The
* name returned will be used to look up access levels as well as group * name returned will be used to look up access levels as well as role
* associations. * associations.
* *
* @param certs The distinguished name. * @param certs The distinguished name.
@ -162,14 +162,14 @@ public abstract class CertificateLoginModule implements LoginModule {
protected abstract String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException; protected abstract String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException;
/** /**
* Should return a set of the groups this user belongs to. The groups * Should return a set of the roles this user belongs to. The roles
* returned will be added to the user's credentials. * returned will be added to the user's credentials.
* *
* @param username The username of the client. This is the same name that * @param username The username of the client. This is the same name that
* getUserNameForDn returned for the user's DN. * getUserNameForDn returned for the user's DN.
* @return A Set of the names of the groups this user belongs to. * @return A Set of the names of the roles this user belongs to.
*/ */
protected abstract Set<String> getUserGroups(final String username) throws LoginException; protected abstract Set<String> getUserRoles(final String username) throws LoginException;
protected String getDistinguishedName(final X509Certificate[] certs) { protected String getDistinguishedName(final X509Certificate[] certs) {
if (certs != null && certs.length > 0 && certs[0] != null) { if (certs != null && certs.length > 0 && certs[0] != null) {

View File

@ -21,19 +21,22 @@ import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.cert.X509Certificate;
import java.io.IOException; import java.io.IOException;
/** /**
* A JAAS username password CallbackHandler. * A JAAS username password CallbackHandler.
*/ */
public class JaasCredentialCallbackHandler implements CallbackHandler { public class JaasCallbackHandler implements CallbackHandler {
private final String username; private final String username;
private final String password; private final String password;
final X509Certificate[] certificates;
public JaasCredentialCallbackHandler(String username, String password) { public JaasCallbackHandler(String username, String password, X509Certificate[] certs) {
this.username = username; this.username = username;
this.password = password; this.password = password;
this.certificates = certs;
} }
@Override @Override
@ -58,6 +61,14 @@ public class JaasCredentialCallbackHandler implements CallbackHandler {
nameCallback.setName(username); nameCallback.setName(username);
} }
} }
else if (callback instanceof CertificateCallback) {
CertificateCallback certCallback = (CertificateCallback) callback;
certCallback.setCertificates(certificates);
}
else {
throw new UnsupportedCallbackException(callback);
}
} }
} }
} }

View File

@ -1,65 +0,0 @@
/*
* 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.activemq.artemis.spi.core.security.jaas;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;
import java.security.cert.X509Certificate;
/**
* A Standard JAAS callback handler for SSL certificate requests. Will only
* handle callbacks of type CertificateCallback.
*/
public class JaasCertificateCallbackHandler implements CallbackHandler {
final X509Certificate[] certificates;
/**
* Basic constructor.
*
* @param certs The certificate returned when calling back.
*/
public JaasCertificateCallbackHandler(X509Certificate[] certs) {
certificates = certs;
}
/**
* Overriding handle method to handle certificates.
*
* @param callbacks The callbacks requested.
* @throws IOException
* @throws UnsupportedCallbackException Thrown if an unknown Callback type is
* encountered.
*/
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
Callback callback = callbacks[i];
if (callback instanceof CertificateCallback) {
CertificateCallback certCallback = (CertificateCallback) callback;
certCallback.setCertificates(certificates);
}
else {
throw new UnsupportedCallbackException(callback);
}
}
}
}

View File

@ -19,9 +19,9 @@ package org.apache.activemq.artemis.spi.core.security.jaas;
import javax.security.auth.Subject; import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException; import javax.security.auth.login.LoginException;
import javax.security.cert.X509Certificate;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -32,21 +32,21 @@ import java.util.Set;
* A LoginModule allowing for SSL certificate based authentication based on * A LoginModule allowing for SSL certificate based authentication based on
* Distinguished Names (DN) stored in text files. The DNs are parsed using a * Distinguished Names (DN) stored in text files. The DNs are parsed using a
* Properties class where each line is <user_name>=<user_DN>. This class also * Properties class where each line is <user_name>=<user_DN>. This class also
* uses a group definition file where each line is <group_name>=<user_name_1>,<user_name_2>,etc. * uses a group definition file where each line is <role_name>=<user_name_1>,<user_name_2>,etc.
* The user and group files' locations must be specified in the * The user and role files' locations must be specified in the
* org.apache.activemq.jaas.textfiledn.user and * org.apache.activemq.jaas.textfiledn.user and
* org.apache.activemq.jaas.textfiledn.user properties respectively. NOTE: This * org.apache.activemq.jaas.textfiledn.role properties respectively. NOTE: This
* class will re-read user and group files for every authentication (i.e it does * class will re-read user and group files for every authentication (i.e it does
* live updates of allowed groups and users). * live updates of allowed groups and users).
*/ */
public class TextFileCertificateLoginModule extends CertificateLoginModule { public class TextFileCertificateLoginModule extends CertificateLoginModule {
private static final String USER_FILE = "org.apache.activemq.jaas.textfiledn.user"; private static final String USER_FILE = "org.apache.activemq.jaas.textfiledn.user";
private static final String GROUP_FILE = "org.apache.activemq.jaas.textfiledn.group"; private static final String ROLE_FILE = "org.apache.activemq.jaas.textfiledn.role";
private File baseDir; private File baseDir;
private String usersFilePathname; private String usersFilePathname;
private String groupsFilePathname; private String rolesFilePathname;
/** /**
* Performs initialization of file paths. A standard JAAS override. * Performs initialization of file paths. A standard JAAS override.
@ -61,8 +61,8 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
baseDir = new File("."); baseDir = new File(".");
} }
usersFilePathname = (String) options.get(USER_FILE) + ""; usersFilePathname = options.get(USER_FILE) + "";
groupsFilePathname = (String) options.get(GROUP_FILE) + ""; rolesFilePathname = options.get(ROLE_FILE) + "";
} }
/** /**
@ -98,7 +98,7 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
Enumeration<Object> keys = users.keys(); Enumeration<Object> keys = users.keys();
for (Enumeration<Object> vals = users.elements(); vals.hasMoreElements(); ) { for (Enumeration<Object> vals = users.elements(); vals.hasMoreElements(); ) {
if (((String) vals.nextElement()).equals(dn)) { if (vals.nextElement().equals(dn)) {
return (String) keys.nextElement(); return (String) keys.nextElement();
} }
else { else {
@ -110,38 +110,38 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
} }
/** /**
* Overriding to allow for group discovery based on text files. * Overriding to allow for role discovery based on text files.
* *
* @param username The name of the user being examined. This is the same * @param username The name of the user being examined. This is the same
* name returned by getUserNameForCertificates. * name returned by getUserNameForCertificates.
* @return A Set of name Strings for groups this user belongs to. * @return A Set of name Strings for roles this user belongs to.
* @throws LoginException Thrown if unable to find group definition file. * @throws LoginException Thrown if unable to find role definition file.
*/ */
@Override @Override
protected Set<String> getUserGroups(String username) throws LoginException { protected Set<String> getUserRoles(String username) throws LoginException {
File groupsFile = new File(baseDir, groupsFilePathname); File rolesFile = new File(baseDir, rolesFilePathname);
Properties groups = new Properties(); Properties roles = new Properties();
try { try {
java.io.FileInputStream in = new java.io.FileInputStream(groupsFile); java.io.FileInputStream in = new java.io.FileInputStream(rolesFile);
groups.load(in); roles.load(in);
in.close(); in.close();
} }
catch (IOException ioe) { catch (IOException ioe) {
throw new LoginException("Unable to load group properties file " + groupsFile); throw new LoginException("Unable to load role properties file " + rolesFile);
} }
Set<String> userGroups = new HashSet<String>(); Set<String> userRoles = new HashSet<String>();
for (Enumeration<Object> enumeration = groups.keys(); enumeration.hasMoreElements(); ) { for (Enumeration<Object> enumeration = roles.keys(); enumeration.hasMoreElements(); ) {
String groupName = (String) enumeration.nextElement(); String groupName = (String) enumeration.nextElement();
String[] userList = (groups.getProperty(groupName) + "").split(","); String[] userList = (roles.getProperty(groupName) + "").split(",");
for (int i = 0; i < userList.length; i++) { for (int i = 0; i < userList.length; i++) {
if (username.equals(userList[i])) { if (username.equals(userList[i])) {
userGroups.add(groupName); userRoles.add(groupName);
break; break;
} }
} }
} }
return userGroups; return userRoles;
} }
} }

View File

@ -27,7 +27,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.Vector; import java.util.Vector;
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCertificateCallbackHandler; import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal; import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal; import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
import org.junit.Assert; import org.junit.Assert;
@ -37,17 +37,17 @@ import org.junit.Test;
public class CertificateLoginModuleTest extends Assert { public class CertificateLoginModuleTest extends Assert {
private static final String USER_NAME = "testUser"; private static final String USER_NAME = "testUser";
private static final List<String> GROUP_NAMES = new Vector<String>(); private static final List<String> ROLE_NAMES = new Vector<String>();
private StubCertificateLoginModule loginModule; private StubCertificateLoginModule loginModule;
private Subject subject; private Subject subject;
public CertificateLoginModuleTest() { public CertificateLoginModuleTest() {
GROUP_NAMES.add("testGroup1"); ROLE_NAMES.add("testRole1");
GROUP_NAMES.add("testGroup2"); ROLE_NAMES.add("testRole2");
GROUP_NAMES.add("testGroup3"); ROLE_NAMES.add("testRole3");
GROUP_NAMES.add("testGroup4"); ROLE_NAMES.add("testRole4");
} }
@Before @Before
@ -55,9 +55,9 @@ public class CertificateLoginModuleTest extends Assert {
subject = new Subject(); subject = new Subject();
} }
private void loginWithCredentials(String userName, Set<String> groupNames) throws LoginException { private void loginWithCredentials(String userName, Set<String> rolesNames) throws LoginException {
loginModule = new StubCertificateLoginModule(userName, new HashSet<String>(groupNames)); loginModule = new StubCertificateLoginModule(userName, new HashSet<String>(rolesNames));
JaasCertificateCallbackHandler callbackHandler = new JaasCertificateCallbackHandler(null); JaasCallbackHandler callbackHandler = new JaasCallbackHandler(null, null, null);
loginModule.initialize(subject, callbackHandler, null, new HashMap()); loginModule.initialize(subject, callbackHandler, null, new HashMap());
@ -67,9 +67,9 @@ public class CertificateLoginModuleTest extends Assert {
private void checkPrincipalsMatch(Subject subject) { private void checkPrincipalsMatch(Subject subject) {
boolean nameFound = false; boolean nameFound = false;
boolean[] groupsFound = new boolean[GROUP_NAMES.size()]; boolean[] rolesFound = new boolean[ROLE_NAMES.size()];
for (int i = 0; i < groupsFound.length; ++i) { for (int i = 0; i < rolesFound.length; ++i) {
groupsFound[i] = false; rolesFound[i] = false;
} }
for (Iterator iter = subject.getPrincipals().iterator(); iter.hasNext(); ) { for (Iterator iter = subject.getPrincipals().iterator(); iter.hasNext(); ) {
@ -91,17 +91,17 @@ public class CertificateLoginModuleTest extends Assert {
} }
else if (currentPrincipal instanceof RolePrincipal) { else if (currentPrincipal instanceof RolePrincipal) {
int principalIdx = GROUP_NAMES.indexOf(((RolePrincipal) currentPrincipal).getName()); int principalIdx = ROLE_NAMES.indexOf(((RolePrincipal) currentPrincipal).getName());
if (principalIdx < 0) { if (principalIdx < 0) {
fail("Unknown GroupPrincipal found."); fail("Unknown RolePrincipal found.");
} }
if (!groupsFound[principalIdx]) { if (!rolesFound[principalIdx]) {
groupsFound[principalIdx] = true; rolesFound[principalIdx] = true;
} }
else { else {
fail("GroupPrincipal found twice."); fail("RolePrincipal found twice.");
} }
} }
else { else {
@ -113,7 +113,7 @@ public class CertificateLoginModuleTest extends Assert {
@Test @Test
public void testLoginSuccess() throws IOException { public void testLoginSuccess() throws IOException {
try { try {
loginWithCredentials(USER_NAME, new HashSet<String>(GROUP_NAMES)); loginWithCredentials(USER_NAME, new HashSet<String>(ROLE_NAMES));
} }
catch (Exception e) { catch (Exception e) {
fail("Unable to login: " + e.getMessage()); fail("Unable to login: " + e.getMessage());
@ -141,7 +141,7 @@ public class CertificateLoginModuleTest extends Assert {
@Test @Test
public void testLogOut() throws IOException { public void testLogOut() throws IOException {
try { try {
loginWithCredentials(USER_NAME, new HashSet<String>(GROUP_NAMES)); loginWithCredentials(USER_NAME, new HashSet<String>(ROLE_NAMES));
} }
catch (Exception e) { catch (Exception e) {
fail("Unable to login: " + e.getMessage()); fail("Unable to login: " + e.getMessage());

View File

@ -32,7 +32,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.spi.core.security.jaas.JaasCredentialCallbackHandler; import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule; import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -46,7 +46,7 @@ import static org.junit.Assert.assertTrue;
public class PropertiesLoginModuleRaceConditionTest { public class PropertiesLoginModuleRaceConditionTest {
private static final String GROUPS_FILE = "roles.properties"; private static final String ROLES_FILE = "roles.properties";
private static final String USERS_FILE = "users.properties"; private static final String USERS_FILE = "users.properties";
private static final String USERNAME = "first"; private static final String USERNAME = "first";
private static final String PASSWORD = "secret"; private static final String PASSWORD = "secret";
@ -114,12 +114,12 @@ public class PropertiesLoginModuleRaceConditionTest {
options.put("reload", "true"); // Used to simplify reproduction of the options.put("reload", "true"); // Used to simplify reproduction of the
// race condition // race condition
options.put("org.apache.activemq.jaas.properties.user", USERS_FILE); options.put("org.apache.activemq.jaas.properties.user", USERS_FILE);
options.put("org.apache.activemq.jaas.properties.role", GROUPS_FILE); options.put("org.apache.activemq.jaas.properties.role", ROLES_FILE);
options.put("baseDir", temp.getRoot().getAbsolutePath()); options.put("baseDir", temp.getRoot().getAbsolutePath());
errors = new ArrayBlockingQueue<Exception>(processorCount()); errors = new ArrayBlockingQueue<Exception>(processorCount());
pool = Executors.newFixedThreadPool(processorCount()); pool = Executors.newFixedThreadPool(processorCount());
callback = new JaasCredentialCallbackHandler(USERNAME, PASSWORD); callback = new JaasCallbackHandler(USERNAME, PASSWORD, null);
} }
@After @After
@ -182,7 +182,7 @@ public class PropertiesLoginModuleRaceConditionTest {
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
groups.put("group" + i, "first,second,third"); groups.put("group" + i, "first,second,third");
} }
store(groups, temp.newFile(GROUPS_FILE)); store(groups, temp.newFile(ROLES_FILE));
} }
private void createUsers() throws FileNotFoundException, IOException { private void createUsers() throws FileNotFoundException, IOException {

View File

@ -17,7 +17,7 @@
package org.apache.activemq.artemis.core.security.jaas; package org.apache.activemq.artemis.core.security.jaas;
import javax.security.auth.login.LoginException; import javax.security.auth.login.LoginException;
import java.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
import java.util.Set; import java.util.Set;
import org.apache.activemq.artemis.spi.core.security.jaas.CertificateLoginModule; import org.apache.activemq.artemis.spi.core.security.jaas.CertificateLoginModule;
@ -40,7 +40,7 @@ public class StubCertificateLoginModule extends CertificateLoginModule {
return userName; return userName;
} }
protected Set getUserGroups(String username) throws LoginException { protected Set getUserRoles(String username) throws LoginException {
lastUserName = username; lastUserName = username;
return this.groupNames; return this.groupNames;
} }

View File

@ -522,7 +522,7 @@ managed using the X.500 system. It is implemented by `org.apache.activemq.artemi
`javax.naming.directory.SearchControls.SUBTREE_SCOPE`). `javax.naming.directory.SearchControls.SUBTREE_SCOPE`).
- `debug` - boolean flag; if `true`, enable debugging; this is used only for testing or debugging; normally, it - `debug` - boolean flag; if `true`, enable debugging; this is used only for testing or debugging; normally, it
should be set to `false`, or omitted; default is `false` should be set to `false`, or omitted; default is `false`
Add user entries under the node specified by the `userBase` option. When creating a new user entry in the directory, Add user entries under the node specified by the `userBase` option. When creating a new user entry in the directory,
choose an object class that supports the `userPassword` attribute (for example, the `person` or `inetOrgPerson` object choose an object class that supports the `userPassword` attribute (for example, the `person` or `inetOrgPerson` object
@ -539,6 +539,109 @@ corresponding user (where the DN is specified either fully, `uid=jdoe,ou=User,ou
If you want to add roles to user entries, you would need to customize the directory schema, by adding a suitable If you want to add roles to user entries, you would need to customize the directory schema, by adding a suitable
attribute type to the user entry's object class. The chosen attribute type must be capable of handling multiple values. attribute type to the user entry's object class. The chosen attribute type must be capable of handling multiple values.
#### CertificateLoginModule
The JAAS certificate authentication login module must be used in combination with SSL and the clients must be configured
with their own certificate. In this scenario, authentication is actually performed during the SSL/TLS handshake, not
directly by the JAAS certificate authentication plug-in. The role of the plug-in is as follows:
- To further constrain the set of acceptable users, because only the user DNs explicitly listed in the relevant
properties file are eligible to be authenticated.
- To associate a list of groups with the received user identity, facilitating integration with the authorization feature.
- To require the presence of an incoming certificate (by default, the SSL/TLS layer is configured to treat the
presence of a client certificate as optional).
The JAAS certificate login module stores a collection of certificate DNs in a pair of flat files. The files associate a
username and a list of group IDs with each DN.
The certificate login module is implemented by the following class:
org.apache.activemq.artemis.spi.core.security.jaas.TextFileCertificateLoginModule
The following `CertLogin` login entry shows how to configure certificate login module in the login.config file:
CertLogin {
org.apache.activemq.artemis.spi.core.security.jaas.TextFileCertificateLoginModule
debug=true
org.apache.activemq.jaas.textfiledn.user="users.properties"
org.apache.activemq.jaas.textfiledn.role="roles.properties";
};
In the preceding example, the JAAS realm is configured to use a single `org.apache.activemq.artemis.spi.core.security.jaas.TextFileCertificateLoginModule`
login module. The options supported by this login module are as follows:
- `debug` - boolean flag; if true, enable debugging; this is used only for testing or debugging; normally,
it should be set to `false`, or omitted; default is `false`
- `org.apache.activemq.jaas.textfiledn.user` - specifies the location of the user properties file (relative to the
directory containing the login configuration file).
- `org.apache.activemq.jaas.textfiledn.role` - specifies the location of the role properties file (relative to the
directory containing the login configuration file).
In the context of the certificate login module, the `users.properties` file consists of a list of properties of the form,
`UserName=StringifiedSubjectDN`. For example, to define the users, system, user, and guest, you could create a file like
the following:
system=CN=system,O=Progress,C=US
user=CN=humble user,O=Progress,C=US
guest=CN=anon,O=Progress,C=DE
Each username is mapped to a subject DN, encoded as a string (where the string encoding is specified by RFC 2253). For
example, the system username is mapped to the `CN=system,O=Progress,C=US` subject DN. When performing authentication,
the plug-in extracts the subject DN from the received certificate, converts it to the standard string format, and
compares it with the subject DNs in the `users.properties` file by testing for string equality. Consequently, you must
be careful to ensure that the subject DNs appearing in the `users.properties` file are an exact match for the subject
DNs extracted from the user certificates.
Note: Technically, there is some residual ambiguity in the DN string format. For example, the `domainComponent` attribute
could be represented in a string either as the string, `DC`, or as the OID, `0.9.2342.19200300.100.1.25`. Normally, you do
not need to worry about this ambiguity. But it could potentially be a problem, if you changed the underlying
implementation of the Java security layer.
The easiest way to obtain the subject DNs from the user certificates is by invoking the `keytool` utility to print the
certificate contents. To print the contents of a certificate in a keystore, perform the following steps:
1. Export the certificate from the keystore file into a temporary file. For example, to export the certificate with
alias `broker-localhost` from the `broker.ks` keystore file, enter the following command:
keytool -export -file broker.export -alias broker-localhost -keystore broker.ks -storepass password
After running this command, the exported certificate is in the file, `broker.export`.
1. Print out the contents of the exported certificate. For example, to print out the contents of `broker.export`,
enter the following command:
keytool -printcert -file broker.export
Which should produce output similar to that shown here:
Owner: CN=localhost, OU=broker, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Issuer: CN=localhost, OU=broker, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Serial number: 4537c82e
Valid from: Thu Oct 19 19:47:10 BST 2006 until: Wed Jan 17 18:47:10 GMT 2007
Certificate fingerprints:
MD5: 3F:6C:0C:89:A8:80:29:CC:F5:2D:DA:5C:D7:3F:AB:37
SHA1: F0:79:0D:04:38:5A:46:CE:86:E1:8A:20:1F:7B:AB:3A:46:E4:34:5C
The string following `Owner:` gives the subject DN. The format used to enter the subject DN depends on your
platform. The `Owner:` string above could be represented as either `CN=localhost,\ OU=broker,\ O=Unknown,\ L=Unknown,\ ST=Unknown,\ C=Unknown`
or `CN=localhost,OU=broker,O=Unknown,L=Unknown,ST=Unknown,C=Unknown`.
The `roles.properties` file consists of a list of properties of the form, `Role=UserList`, where `UserList` is a
comma-separated list of users. For example, to define the roles `admins`, `users`, and `guests`, you could create a file
like the following:
admins=system
users=system,user
guests=guest
The simplest way to make the login configuration available to JAAS is to add the directory containing the file,
`login.config`, to your CLASSPATH.
## Changing the username/password for clustering ## Changing the username/password for clustering
In order for cluster connections to work correctly, each node in the In order for cluster connections to work correctly, each node in the

View File

@ -81,7 +81,6 @@ public class SecurityNotificationTest extends ActiveMQTestBase {
ClientMessage[] notifications = SecurityNotificationTest.consumeMessages(1, notifConsumer); ClientMessage[] notifications = SecurityNotificationTest.consumeMessages(1, notifConsumer);
Assert.assertEquals(SECURITY_AUTHENTICATION_VIOLATION.toString(), notifications[0].getObjectProperty(ManagementHelper.HDR_NOTIFICATION_TYPE).toString()); Assert.assertEquals(SECURITY_AUTHENTICATION_VIOLATION.toString(), notifications[0].getObjectProperty(ManagementHelper.HDR_NOTIFICATION_TYPE).toString());
Assert.assertEquals(unknownUser, notifications[0].getObjectProperty(ManagementHelper.HDR_USER).toString());
} }
@Test @Test

View File

@ -16,16 +16,22 @@
*/ */
package org.apache.activemq.artemis.tests.integration.security; package org.apache.activemq.artemis.tests.integration.security;
import javax.security.cert.X509Certificate;
import javax.transaction.xa.XAResource; import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid; import javax.transaction.xa.Xid;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.net.URL; import java.net.URL;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.activemq.artemis.api.core.ActiveMQException; import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException; import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.api.core.client.ClientConsumer; import org.apache.activemq.artemis.api.core.client.ClientConsumer;
import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.client.ClientMessage;
import org.apache.activemq.artemis.api.core.client.ClientProducer; import org.apache.activemq.artemis.api.core.client.ClientProducer;
@ -34,6 +40,7 @@ import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.api.core.client.ServerLocator; import org.apache.activemq.artemis.api.core.client.ServerLocator;
import org.apache.activemq.artemis.core.config.Configuration; import org.apache.activemq.artemis.core.config.Configuration;
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnection; import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnection;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.core.security.CheckType; import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.Role; import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.ActiveMQServer;
@ -42,10 +49,10 @@ import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl; import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl;
import org.apache.activemq.artemis.core.settings.HierarchicalRepository; import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2;
import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManagerImpl; import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManagerImpl;
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase; import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.tests.util.CreateMessage; import org.apache.activemq.artemis.tests.util.CreateMessage;
import org.junit.Assert; import org.junit.Assert;
@ -102,6 +109,43 @@ public class SecurityTest extends ActiveMQTestBase {
} }
} }
@Test
public void testJAASSecurityManagerAuthenticationWithCerts() throws Exception {
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
securityManager.setConfigurationName("CertLogin");
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
Map<String, Object> params = new HashMap<String, Object>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "server-side-keystore.jks");
params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "secureexample");
params.put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "server-side-truststore.jks");
params.put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "secureexample");
params.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
server.getConfiguration().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params));
server.start();
TransportConfiguration tc = new TransportConfiguration(NETTY_CONNECTOR_FACTORY);
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "client-side-truststore.jks");
tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "secureexample");
tc.getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "client-side-keystore.jks");
tc.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "secureexample");
ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc));
ClientSessionFactory cf = createSessionFactory(locator);
try {
ClientSession session = cf.createSession();
session.close();
}
catch (ActiveMQException e) {
e.printStackTrace();
Assert.fail("should not throw exception");
}
}
@Test @Test
public void testJAASSecurityManagerAuthenticationBadPassword() throws Exception { public void testJAASSecurityManagerAuthenticationBadPassword() throws Exception {
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(); ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
@ -119,6 +163,50 @@ public class SecurityTest extends ActiveMQTestBase {
} }
} }
/**
* This test requires a client-side certificate that will be trusted by the server but whose dname will be rejected
* by the CertLogin login module. I created this cert with the follow commands:
*
* keytool -genkey -keystore bad-client-side-keystore.jks -storepass secureexample -keypass secureexample -dname "CN=Bad Client, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ"
* keytool -export -keystore bad-client-side-keystore.jks -file activemq-jks.cer -storepass secureexample
* keytool -import -keystore server-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt -alias bad
*/
@Test
public void testJAASSecurityManagerAuthenticationWithBadClientCert() throws Exception {
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
securityManager.setConfigurationName("CertLogin");
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
Map<String, Object> params = new HashMap<String, Object>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "server-side-keystore.jks");
params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "secureexample");
params.put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "server-side-truststore.jks");
params.put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "secureexample");
params.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
server.getConfiguration().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params));
server.start();
TransportConfiguration tc = new TransportConfiguration(NETTY_CONNECTOR_FACTORY);
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "client-side-truststore.jks");
tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "secureexample");
tc.getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "bad-client-side-keystore.jks");
tc.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "secureexample");
ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc));
ClientSessionFactory cf = createSessionFactory(locator);
try {
cf.createSession();
fail("Creating session here should fail due to authentication error.");
}
catch (ActiveMQException e) {
assertTrue(e.getType() == ActiveMQExceptionType.SECURITY_EXCEPTION);
}
}
@Test @Test
public void testJAASSecurityManagerAuthenticationGuest() throws Exception { public void testJAASSecurityManagerAuthenticationGuest() throws Exception {
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(); ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
@ -222,6 +310,112 @@ public class SecurityTest extends ActiveMQTestBase {
} }
} }
@Test
public void testJAASSecurityManagerAuthorizationNegativeWithCerts() throws Exception {
final SimpleString ADDRESS = new SimpleString("address");
final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue");
final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue");
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
securityManager.setConfigurationName("CertLogin");
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
Map<String, Object> params = new HashMap<String, Object>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "server-side-keystore.jks");
params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "secureexample");
params.put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "server-side-truststore.jks");
params.put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "secureexample");
params.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
server.getConfiguration().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params));
Set<Role> roles = new HashSet<>();
roles.add(new Role("programmers", false, false, false, false, false, false, false));
server.getConfiguration().getSecurityRoles().put("#", roles);
server.start();
TransportConfiguration tc = new TransportConfiguration(NETTY_CONNECTOR_FACTORY);
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "client-side-truststore.jks");
tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "secureexample");
tc.getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "client-side-keystore.jks");
tc.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "secureexample");
ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc));
ClientSessionFactory cf = createSessionFactory(locator);
server.createQueue(ADDRESS, DURABLE_QUEUE, null, true, false);
server.createQueue(ADDRESS, NON_DURABLE_QUEUE, null, false, false);
ClientSession session = addClientSession(cf.createSession());
// CREATE_DURABLE_QUEUE
try {
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
Assert.fail("should throw exception here");
}
catch (ActiveMQException e) {
// ignore
}
// DELETE_DURABLE_QUEUE
try {
session.deleteQueue(DURABLE_QUEUE);
Assert.fail("should throw exception here");
}
catch (ActiveMQException e) {
// ignore
}
// CREATE_NON_DURABLE_QUEUE
try {
session.createQueue(ADDRESS, NON_DURABLE_QUEUE, false);
Assert.fail("should throw exception here");
}
catch (ActiveMQException e) {
// ignore
}
// DELETE_NON_DURABLE_QUEUE
try {
session.deleteQueue(NON_DURABLE_QUEUE);
Assert.fail("should throw exception here");
}
catch (ActiveMQException e) {
// ignore
}
// PRODUCE
try {
ClientProducer producer = session.createProducer(ADDRESS);
producer.send(session.createMessage(true));
Assert.fail("should throw exception here");
}
catch (ActiveMQException e) {
// ignore
}
// CONSUME
try {
ClientConsumer consumer = session.createConsumer(DURABLE_QUEUE);
Assert.fail("should throw exception here");
}
catch (ActiveMQException e) {
// ignore
}
// MANAGE
try {
ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
producer.send(session.createMessage(true));
Assert.fail("should throw exception here");
}
catch (ActiveMQException e) {
// ignore
}
}
@Test @Test
public void testJAASSecurityManagerAuthorizationPositive() throws Exception { public void testJAASSecurityManagerAuthorizationPositive() throws Exception {
final SimpleString ADDRESS = new SimpleString("address"); final SimpleString ADDRESS = new SimpleString("address");
@ -300,6 +494,102 @@ public class SecurityTest extends ActiveMQTestBase {
} }
} }
@Test
public void testJAASSecurityManagerAuthorizationPositiveWithCerts() throws Exception {
final SimpleString ADDRESS = new SimpleString("address");
final SimpleString DURABLE_QUEUE = new SimpleString("durableQueue");
final SimpleString NON_DURABLE_QUEUE = new SimpleString("nonDurableQueue");
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager();
securityManager.setConfigurationName("CertLogin");
ActiveMQServer server = addServer(ActiveMQServers.newActiveMQServer(createDefaultInVMConfig().setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
Map<String, Object> params = new HashMap<String, Object>();
params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "server-side-keystore.jks");
params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "secureexample");
params.put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "server-side-truststore.jks");
params.put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "secureexample");
params.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
server.getConfiguration().addAcceptorConfiguration(new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params));
Set<Role> roles = new HashSet<>();
roles.add(new Role("programmers", true, true, true, true, true, true, true));
server.getConfiguration().getSecurityRoles().put("#", roles);
server.start();
TransportConfiguration tc = new TransportConfiguration(NETTY_CONNECTOR_FACTORY);
tc.getParams().put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
tc.getParams().put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "client-side-truststore.jks");
tc.getParams().put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "secureexample");
tc.getParams().put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "client-side-keystore.jks");
tc.getParams().put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "secureexample");
ServerLocator locator = addServerLocator(ActiveMQClient.createServerLocatorWithoutHA(tc));
ClientSessionFactory cf = createSessionFactory(locator);
ClientSession session = addClientSession(cf.createSession());
// CREATE_DURABLE_QUEUE
try {
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
}
catch (ActiveMQException e) {
Assert.fail("should not throw exception here");
}
// DELETE_DURABLE_QUEUE
try {
session.deleteQueue(DURABLE_QUEUE);
}
catch (ActiveMQException e) {
Assert.fail("should not throw exception here");
}
// CREATE_NON_DURABLE_QUEUE
try {
session.createQueue(ADDRESS, NON_DURABLE_QUEUE, false);
}
catch (ActiveMQException e) {
Assert.fail("should not throw exception here");
}
// DELETE_NON_DURABLE_QUEUE
try {
session.deleteQueue(NON_DURABLE_QUEUE);
}
catch (ActiveMQException e) {
Assert.fail("should not throw exception here");
}
session.createQueue(ADDRESS, DURABLE_QUEUE, true);
// PRODUCE
try {
ClientProducer producer = session.createProducer(ADDRESS);
producer.send(session.createMessage(true));
}
catch (ActiveMQException e) {
Assert.fail("should not throw exception here");
}
// CONSUME
try {
session.createConsumer(DURABLE_QUEUE);
}
catch (ActiveMQException e) {
Assert.fail("should not throw exception here");
}
// MANAGE
try {
ClientProducer producer = session.createProducer(server.getConfiguration().getManagementAddress());
producer.send(session.createMessage(true));
}
catch (ActiveMQException e) {
Assert.fail("should not throw exception here");
}
}
@Test @Test
public void testJAASSecurityManagerAuthorizationPositiveGuest() throws Exception { public void testJAASSecurityManagerAuthorizationPositiveGuest() throws Exception {
final SimpleString ADDRESS = new SimpleString("address"); final SimpleString ADDRESS = new SimpleString("address");
@ -1454,6 +1744,10 @@ public class SecurityTest extends ActiveMQTestBase {
final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true); final Configuration configuration = createDefaultInVMConfig().setSecurityEnabled(true);
final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager2() { final ActiveMQSecurityManager customSecurityManager = new ActiveMQSecurityManager2() {
public boolean validateUser(final String username, final String password) { public boolean validateUser(final String username, final String password) {
fail("Unexpected call to overridden method");
return false;
}
public boolean validateUser(final String username, final String password, final X509Certificate[] certificates) {
return (username.equals("foo") || username.equals("bar") || username.equals("all")) && return (username.equals("foo") || username.equals("bar") || username.equals("all")) &&
password.equals("frobnicate"); password.equals("frobnicate");
} }

View File

@ -69,12 +69,12 @@ public class CoreClientOverOneWaySSLTest extends ActiveMQTestBase {
* These artifacts are required for testing 1-way SSL * These artifacts are required for testing 1-way SSL
* *
* Commands to create the JKS artifacts: * Commands to create the JKS artifacts:
* keytool -genkey -keystore server-side-keystore.jks -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis, OU=ActiveMQ Artemis, O=ActiveMQ Artemis, L=ActiveMQ Artemis, S=ActiveMQ Artemis, C=AMQ" * keytool -genkey -keystore server-side-keystore.jks -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ" -keyalg RSA
* keytool -export -keystore server-side-keystore.jks -file activemq-jks.cer -storepass secureexample * keytool -export -keystore server-side-keystore.jks -file activemq-jks.cer -storepass secureexample
* keytool -import -keystore client-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt * keytool -import -keystore client-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt
* *
* Commands to create the JCEKS artifacts: * Commands to create the JCEKS artifacts:
* keytool -genkey -keystore server-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis, OU=ActiveMQ Artemis, O=ActiveMQ Artemis, L=ActiveMQ Artemis, S=ActiveMQ Artemis, C=AMQ" * keytool -genkey -keystore server-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis Server, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ"
* keytool -export -keystore server-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample * keytool -export -keystore server-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample
* keytool -import -keystore client-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt * keytool -import -keystore client-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt
*/ */

View File

@ -70,15 +70,15 @@ public class CoreClientOverTwoWaySSLTest extends ActiveMQTestBase {
public static final SimpleString QUEUE = new SimpleString("QueueOverSSL"); public static final SimpleString QUEUE = new SimpleString("QueueOverSSL");
/** /**
* These artifacts are required for testing 2-way SSL * These artifacts are required for testing 2-way SSL in addition to the artifacts for 1-way SSL
* *
* Commands to create the JKS artifacts: * Commands to create the JKS artifacts:
* keytool -genkey -keystore client-side-keystore.jks -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis, OU=ActiveMQ Artemis, O=ActiveMQ Artemis, L=ActiveMQ Artemis, S=ActiveMQ Artemis, C=AMQ" * keytool -genkey -keystore client-side-keystore.jks -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ"
* keytool -export -keystore client-side-keystore.jks -file activemq-jks.cer -storepass secureexample * keytool -export -keystore client-side-keystore.jks -file activemq-jks.cer -storepass secureexample
* keytool -import -keystore server-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt * keytool -import -keystore server-side-truststore.jks -file activemq-jks.cer -storepass secureexample -keypass secureexample -noprompt
* *
* Commands to create the JCEKS artifacts: * Commands to create the JCEKS artifacts:
* keytool -genkey -keystore client-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis, OU=ActiveMQ Artemis, O=ActiveMQ Artemis, L=ActiveMQ Artemis, S=ActiveMQ Artemis, C=AMQ" * keytool -genkey -keystore client-side-keystore.jceks -storetype JCEKS -storepass secureexample -keypass secureexample -dname "CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ, L=AMQ, S=AMQ, C=AMQ"
* keytool -export -keystore client-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample * keytool -export -keystore client-side-keystore.jceks -file activemq-jceks.cer -storetype jceks -storepass secureexample
* keytool -import -keystore server-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt * keytool -import -keystore server-side-truststore.jceks -storetype JCEKS -file activemq-jceks.cer -storepass secureexample -keypass secureexample -noprompt
*/ */

View File

@ -0,0 +1,18 @@
#
# 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.
#
programmers=first

View File

@ -0,0 +1,18 @@
#
# 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.
#
first=CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ, L=AMQ, ST=AMQ, C=AMQ

View File

@ -116,3 +116,10 @@ OpenLdapConfiguration {
roleSearchSubtree=true roleSearchSubtree=true
; ;
}; };
CertLogin {
org.apache.activemq.artemis.spi.core.security.jaas.TextFileCertificateLoginModule required
debug=true
org.apache.activemq.jaas.textfiledn.user="cert-users.properties"
org.apache.activemq.jaas.textfiledn.role="cert-roles.properties";
};