summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Godfrey <rgodfrey@apache.org>2014-01-13 16:57:08 +0000
committerRobert Godfrey <rgodfrey@apache.org>2014-01-13 16:57:08 +0000
commit82700edf3062785e05b3cb6eebe1b8137128c824 (patch)
tree0ed12885969b546f4e99b1a4ce1b5f79d2ffac9e
parente24fe7cc76a6ac23417d7d8fb83829ca69e156dc (diff)
downloadqpid-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
-rw-r--r--qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SubjectCreator.java17
-rw-r--r--qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java75
-rw-r--r--qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementUtil.java38
-rw-r--r--qpid/java/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/SaslServlet.java7
-rw-r--r--qpid/java/broker-plugins/management-http/src/main/java/resources/js/qpid/management/addPort.js11
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/Asserts.java2
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/QpidRestTestCase.java2
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/RestTestHelper.java37
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/rest/SaslRestTest.java7
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