diff options
author | Robert Godfrey <rgodfrey@apache.org> | 2014-01-13 16:57:08 +0000 |
---|---|---|
committer | Robert Godfrey <rgodfrey@apache.org> | 2014-01-13 16:57:08 +0000 |
commit | 82700edf3062785e05b3cb6eebe1b8137128c824 (patch) | |
tree | 0ed12885969b546f4e99b1a4ce1b5f79d2ffac9e | |
parent | e24fe7cc76a6ac23417d7d8fb83829ca69e156dc (diff) | |
download | qpid-python-82700edf3062785e05b3cb6eebe1b8137128c824.tar.gz |
QPID-5475 : [Java Broker] add ability to use ssl client auth to REST api and HTTP management
git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1557770 13f79535-47bb-0310-9956-ffa450edef68
9 files changed, 177 insertions, 19 deletions
diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SubjectCreator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SubjectCreator.java index 244ab0dd94..066c05b898 100644 --- a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SubjectCreator.java +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SubjectCreator.java @@ -37,6 +37,7 @@ import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationS import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManager; import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManager; /** * Creates a {@link Subject} formed by the {@link Principal}'s returned from: @@ -129,6 +130,17 @@ public class SubjectCreator } } + public Subject createSubjectWithGroups(Principal principal) + { + Subject authenticationSubject = new Subject(); + + authenticationSubject.getPrincipals().add(principal); + authenticationSubject.getPrincipals().addAll(getGroupPrincipals(principal.getName())); + authenticationSubject.setReadOnly(); + + return authenticationSubject; + } + public Subject createSubjectWithGroups(String username) { Subject authenticationSubject = new Subject(); @@ -159,4 +171,9 @@ public class SubjectCreator { return _authenticationManager instanceof AnonymousAuthenticationManager; } + + public boolean isExternalAuthenticationAllowed() + { + return _authenticationManager instanceof ExternalAuthenticationManager; + } } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java index 63d8a78822..039114056f 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java @@ -23,15 +23,13 @@ package org.apache.qpid.server.management.plugin; import java.lang.reflect.Type; import java.net.SocketAddress; import java.security.GeneralSecurityException; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.UUID; +import java.util.*; +import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.IllegalConfigurationException; import org.apache.qpid.server.logging.actors.CurrentActor; @@ -77,6 +75,7 @@ import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.model.adapter.AbstractPluginAdapter; import org.apache.qpid.server.plugin.PluginFactory; import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.transport.network.security.ssl.QpidMultipleTrustManager; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.DispatcherType; import org.eclipse.jetty.server.Server; @@ -239,22 +238,82 @@ public class HttpManagement extends AbstractPluginAdapter implements HttpManagem else if (transports.contains(Transport.SSL)) { KeyStore keyStore = port.getKeyStore(); + Collection<TrustStore> trustStores = port.getTrustStores(); if (keyStore == null) { throw new IllegalConfigurationException("Key store is not configured. Cannot start management on HTTPS port without keystore"); } SslContextFactory factory = new SslContextFactory(); + final boolean needClientAuth = Boolean.valueOf(String.valueOf(port.getAttribute(Port.NEED_CLIENT_AUTH))); + final boolean wantClientAuth = Boolean.valueOf(String.valueOf(port.getAttribute(Port.WANT_CLIENT_AUTH))); + boolean needClientCert = needClientAuth || wantClientAuth; + if (needClientCert && trustStores.isEmpty()) + { + throw new IllegalConfigurationException("Client certificate authentication is enabled on AMQP port '" + + this.getName() + "' but no trust store defined"); + } + try { SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyStore.getKeyManagers(), null, null); + KeyManager[] keyManagers = keyStore.getKeyManagers(); + + TrustManager[] trustManagers; + if(trustStores == null || trustStores.isEmpty()) + { + trustManagers = null; + } + else if(trustStores.size() == 1) + { + trustManagers = trustStores.iterator().next().getTrustManagers(); + } + else + { + Collection<TrustManager> trustManagerList = new ArrayList<TrustManager>(); + final QpidMultipleTrustManager mulTrustManager = new QpidMultipleTrustManager(); + + for(TrustStore ts : trustStores) + { + TrustManager[] managers = ts.getTrustManagers(); + if(managers != null) + { + for(TrustManager manager : managers) + { + if(manager instanceof X509TrustManager) + { + mulTrustManager.addTrustManager((X509TrustManager)manager); + } + else + { + trustManagerList.add(manager); + } + } + } + } + if(!mulTrustManager.isEmpty()) + { + trustManagerList.add(mulTrustManager); + } + trustManagers = trustManagerList.toArray(new TrustManager[trustManagerList.size()]); + } + sslContext.init(keyManagers, trustManagers, null); + factory.setSslContext(sslContext); + if(needClientAuth) + { + factory.setNeedClientAuth(true); + } + else if(wantClientAuth) + { + factory.setWantClientAuth(true); + } } catch (GeneralSecurityException e) { throw new RuntimeException("Cannot configure port " + port.getName() + " for transport " + Transport.SSL, e); } connector = new SslSocketConnector(factory); + } else { diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementUtil.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementUtil.java index 990ff1c53b..f6674b5152 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementUtil.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementUtil.java @@ -23,10 +23,14 @@ package org.apache.qpid.server.management.plugin; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.security.AccessControlException; +import java.security.Principal; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.security.cert.X509Certificate; +import java.util.Collections; import javax.security.auth.Subject; +import javax.security.auth.x500.X500Principal; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -36,11 +40,17 @@ import org.apache.qpid.server.logging.LogActor; import org.apache.qpid.server.logging.actors.CurrentActor; import org.apache.qpid.server.logging.actors.HttpManagementActor; import org.apache.qpid.server.management.plugin.session.LoginLogoutReporter; +import org.apache.qpid.server.model.AuthenticationProvider; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.security.SecurityManager; import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManager; +import org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManagerFactory; +import org.apache.qpid.transport.network.security.ssl.SSLUtil; public class HttpManagementUtil { @@ -164,17 +174,41 @@ public class HttpManagementUtil session.setAttribute(ATTR_LOGIN_LOGOUT_REPORTER, new LoginLogoutReporter(logActor, subject)); } - private static Subject tryToAuthenticate(HttpServletRequest request, HttpManagementConfiguration managementConfig) + public static Subject tryToAuthenticate(HttpServletRequest request, HttpManagementConfiguration managementConfig) { Subject subject = null; SocketAddress localAddress = getSocketAddress(request); - SubjectCreator subjectCreator = managementConfig.getAuthenticationProvider(localAddress).getSubjectCreator(); + final AuthenticationProvider authenticationProvider = managementConfig.getAuthenticationProvider(localAddress); + SubjectCreator subjectCreator = authenticationProvider.getSubjectCreator(); String remoteUser = request.getRemoteUser(); if (remoteUser != null || subjectCreator.isAnonymousAuthenticationAllowed()) { subject = authenticateUser(subjectCreator, remoteUser, null); } + else if(subjectCreator.isExternalAuthenticationAllowed() + && Collections.list(request.getAttributeNames()).contains("javax.servlet.request.X509Certificate")) + { + Principal principal = null; + X509Certificate[] certificates = + (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate"); + if(certificates != null && certificates.length != 0) + { + principal = certificates[0].getSubjectX500Principal(); + + if(!Boolean.valueOf(String.valueOf(authenticationProvider.getAttribute(ExternalAuthenticationManagerFactory.ATTRIBUTE_USE_FULL_DN)))) + { + String username; + String dn = ((X500Principal) principal).getName(X500Principal.RFC2253); + + + username = SSLUtil.getIdFromSubjectDN(dn); + principal = new UsernamePrincipal(username); + } + + subject = subjectCreator.createSubjectWithGroups(new AuthenticatedPrincipal(principal)); + } + } else { String header = request.getHeader("Authorization"); diff --git a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java index 2b035fed8f..9ad52007ab 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java +++ b/qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java @@ -284,6 +284,11 @@ public class SaslServlet extends AbstractServlet @Override protected Subject getAuthorisedSubject(HttpServletRequest request) { - return HttpManagementUtil.getAuthorisedSubject(request.getSession()); + Subject subject = HttpManagementUtil.getAuthorisedSubject(request.getSession()); + if(subject == null) + { + subject = HttpManagementUtil.tryToAuthenticate(request, HttpManagementUtil.getManagementConfiguration(getServletContext())); + } + return subject; } } diff --git a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPort.js b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPort.js index 8f2b9cbee3..86ded8f059 100644 --- a/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPort.js +++ b/qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPort.js @@ -110,7 +110,7 @@ define(["dojo/_base/xhr", } var type = dijit.byId("formAddPort.type").value; - if (type == "AMQP") + if (type == "AMQP" || type == "HTTP") { var transportWidget = registry.byId("formAddPort.transports"); var needClientAuth = dijit.byId("formAddPort.needClientAuth"); @@ -154,7 +154,7 @@ define(["dojo/_base/xhr", { var clientAuthPanel = dojo.byId("formAddPort:fieldsClientAuth"); var display = clientAuthPanel.style.display; - if (transportType == "SSL" && protocolType == "AMQP") + if (transportType == "SSL" && (protocolType == "AMQP" || protocolType == "HTTP")) { clientAuthPanel.style.display = "block"; registry.byId("formAddPort.needClientAuth").set("disabled", false); @@ -212,8 +212,11 @@ define(["dojo/_base/xhr", }); var isAMQP = ("AMQP" == newValue); - registry.byId("formAddPort.needClientAuth").set("enabled", isAMQP); - registry.byId("formAddPort.wantClientAuth").set("enabled", isAMQP); + + var isHTTP = ("HTTP" == newValue); + + registry.byId("formAddPort.needClientAuth").set("enabled", isAMQP || isHTTP); + registry.byId("formAddPort.wantClientAuth").set("enabled", isAMQP || isHTTP); registry.byId("formAddPort:fields" + newValue).domNode.style.display = "block"; var defaultsAMQPProtocols = registry.byId("formAddPort.protocolsDefault"); diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java index b65ddbb2d0..56aec08d09 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java @@ -126,7 +126,7 @@ public class Asserts Queue.DISCARDS_TTL_MESSAGES, Queue.STATE_CHANGED); } - public static void assertAttributesPresent(Map<String, Object> data, String[] attributes) + public static void assertAttributesPresent(Map<String, Object> data, String... attributes) { for (String name : attributes) { diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java index 57398ea929..ce501adeb6 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java @@ -35,6 +35,8 @@ import org.apache.qpid.test.utils.QpidBrokerTestCase; public class QpidRestTestCase extends QpidBrokerTestCase { public static final String ANONYMOUS_AUTHENTICATION_PROVIDER = "testAnonymous"; + public static final String EXTERNAL_AUTHENTICATION_PROVIDER = "testExternal"; + public static final String TEST1_VIRTUALHOST = "test"; public static final String TEST2_VIRTUALHOST = "test2"; public static final String TEST3_VIRTUALHOST = "test3"; diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java index 7d99b30049..810b70a2ba 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java @@ -18,6 +18,8 @@ */ package org.apache.qpid.systest.rest; +import static org.apache.qpid.test.utils.TestSSLConstants.KEYSTORE; +import static org.apache.qpid.test.utils.TestSSLConstants.KEYSTORE_PASSWORD; import static org.apache.qpid.test.utils.TestSSLConstants.TRUSTSTORE; import static org.apache.qpid.test.utils.TestSSLConstants.TRUSTSTORE_PASSWORD; @@ -41,6 +43,7 @@ import java.util.List; import java.util.Map; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManagerFactory; @@ -65,16 +68,19 @@ import org.codehaus.jackson.type.TypeReference; public class RestTestHelper { private static final Logger LOGGER = Logger.getLogger(RestTestHelper.class); + private static final String CERT_ALIAS_APP1 = "app1"; private int _httpPort; private boolean _useSsl; + private String _username; private String _password; private File _passwdFile; + private boolean _useSslAuth; public RestTestHelper(int httpPort) { @@ -110,7 +116,30 @@ public class RestTestHelper { URL url = getManagementURL(path); HttpURLConnection httpCon = (HttpURLConnection) url.openConnection(); - if(_useSsl) + + if(_useSslAuth) + { + try + { + // We have to use a SSLSocketFactory from a new SSLContext so that we don't re-use + // the JVM's defaults that may have been initialised in previous tests. + + SSLContext sslContext = SSLContextFactory.buildClientContext( + TRUSTSTORE, TRUSTSTORE_PASSWORD, + KeyStore.getDefaultType(), + TrustManagerFactory.getDefaultAlgorithm(), + KEYSTORE, KEYSTORE_PASSWORD, KeyStore.getDefaultType(), KeyManagerFactory.getDefaultAlgorithm(), CERT_ALIAS_APP1); + + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + + ((HttpsURLConnection) httpCon).setSSLSocketFactory(sslSocketFactory); + } + catch (GeneralSecurityException e) + { + throw new RuntimeException(e); + } + } + else if(_useSsl) { try { @@ -475,4 +504,10 @@ public class RestTestHelper connection.connect(); return readConnectionInputStream(connection); } + + public void setUseSslAuth(final boolean useSslAuth) + { + _useSslAuth = useSslAuth; + _useSsl = true; + } } diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/SaslRestTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/SaslRestTest.java index 1c05f17e25..61f4a1a8e2 100644 --- a/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/SaslRestTest.java +++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/SaslRestTest.java @@ -55,6 +55,8 @@ public class SaslRestTest extends QpidRestTestCase public void startBrokerNow() throws Exception { super.startBroker(); + + getRestTestHelper().setUsernameAndPassword(null,null); } public void testGetMechanismsWithBrokerPlainPasswordPrincipalDatabase() throws Exception @@ -71,7 +73,7 @@ public class SaslRestTest extends QpidRestTestCase { assertTrue("Mechanism " + mechanism + " is not found", mechanisms.contains(mechanism)); } - assertNull("Unexpected user was returned", saslData.get("user")); + assertNull("Unexpected user was returned: " + saslData.get("user"), saslData.get("user")); } public void testGetMechanismsWithBrokerBase64MD5FilePrincipalDatabase() throws Exception @@ -89,7 +91,8 @@ public class SaslRestTest extends QpidRestTestCase { assertTrue("Mechanism " + mechanism + " is not found", mechanisms.contains(mechanism)); } - assertNull("Unexpected user was returned", saslData.get("user")); + + assertNull("Unexpected user was returned: " + saslData.get("user"), saslData.get("user")); } public void testPlainSaslAuthenticationForValidCredentials() throws Exception |