You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by ta...@apache.org on 2018/03/22 14:12:05 UTC
[1/2] activemq-artemis git commit: [ARTEMIS-1758] support SASL
EXTERNAL with TextCertLoginModule - rework proton handler to use saslListener
Repository: activemq-artemis
Updated Branches:
refs/heads/master 92a73e2cb -> 2f9d37393
[ARTEMIS-1758] support SASL EXTERNAL with TextCertLoginModule
- rework proton handler to use saslListener
Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/72ec6c8e
Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/72ec6c8e
Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/72ec6c8e
Branch: refs/heads/master
Commit: 72ec6c8e0b065acc07cc520172fd158ac2fabe3a
Parents: 92a73e2
Author: gtully <ga...@gmail.com>
Authored: Tue Mar 13 17:21:06 2018 +0000
Committer: Timothy Bish <ta...@gmail.com>
Committed: Thu Mar 22 10:09:58 2018 -0400
----------------------------------------------------------------------
.../amqp/broker/AMQPConnectionCallback.java | 16 ++
.../amqp/proton/AMQPConnectionContext.java | 1 -
.../amqp/proton/handler/ProtonHandler.java | 213 +++++++++--------
.../protocol/amqp/sasl/ExternalServerSASL.java | 62 +++++
.../protocol/amqp/sasl/GSSAPISASLResult.java | 51 ----
.../protocol/amqp/sasl/GSSAPIServerSASL.java | 4 +-
.../protocol/amqp/sasl/PrincipalSASLResult.java | 51 ++++
docs/user-manual/en/security.md | 5 +
.../integration/amqp/JMSSaslExternalTest.java | 235 +++++++++++++++++++
.../integration/amqp/JMSSaslGssapiTest.java | 11 +-
.../src/test/resources/cert-roles.properties | 1 +
.../src/test/resources/cert-users.properties | 1 +
12 files changed, 492 insertions(+), 159 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java
index 17cae8e..05b4f4f 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPConnectionCallback.java
@@ -17,6 +17,7 @@
package org.apache.activemq.artemis.protocol.amqp.broker;
import java.net.URI;
+import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -28,6 +29,7 @@ import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.core.buffers.impl.ChannelBufferWrapper;
import org.apache.activemq.artemis.core.client.impl.TopologyMemberImpl;
+import org.apache.activemq.artemis.core.remoting.CertificateUtil;
import org.apache.activemq.artemis.core.remoting.CloseListener;
import org.apache.activemq.artemis.core.remoting.FailureListener;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
@@ -42,6 +44,7 @@ import org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport;
import org.apache.activemq.artemis.protocol.amqp.proton.handler.ExtCapability;
import org.apache.activemq.artemis.protocol.amqp.proton.transaction.ProtonTransactionImpl;
import org.apache.activemq.artemis.protocol.amqp.sasl.AnonymousServerSASL;
+import org.apache.activemq.artemis.protocol.amqp.sasl.ExternalServerSASL;
import org.apache.activemq.artemis.protocol.amqp.sasl.GSSAPIServerSASL;
import org.apache.activemq.artemis.protocol.amqp.sasl.PlainSASL;
import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
@@ -113,7 +116,20 @@ public class AMQPConnectionCallback implements FailureListener, CloseListener {
result = gssapiServerSASL;
break;
+ case ExternalServerSASL.NAME:
+ // validate ssl cert present
+ Principal principal = CertificateUtil.getPeerPrincipalFromConnection(protonConnectionDelegate);
+ if (principal != null) {
+ ExternalServerSASL externalServerSASL = new ExternalServerSASL();
+ externalServerSASL.setPrincipal(principal);
+ result = externalServerSASL;
+ } else {
+ logger.debug("SASL EXTERNAL mechanism requires a TLS peer principal");
+ }
+ break;
+
default:
+ logger.debug("Mo matching mechanism found for: " + mechanism);
break;
}
}
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
index 5d376f1..efb438d 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/AMQPConnectionContext.java
@@ -332,7 +332,6 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
@Override
public void onAuthSuccess(final ProtonHandler protonHandler, final Connection connection) {
connection.open();
- flush();
}
@Override
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
index 58b4988..5bee66e 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/handler/ProtonHandler.java
@@ -32,7 +32,6 @@ import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASL;
import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
import org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASL;
import org.apache.activemq.artemis.spi.core.remoting.ReadyListener;
-import org.apache.activemq.artemis.utils.ByteUtil;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.transport.AmqpError;
@@ -42,6 +41,7 @@ import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.EndpointState;
import org.apache.qpid.proton.engine.Event;
import org.apache.qpid.proton.engine.Sasl;
+import org.apache.qpid.proton.engine.SaslListener;
import org.apache.qpid.proton.engine.Transport;
import org.apache.qpid.proton.engine.impl.TransportInternal;
import org.jboss.logging.Logger;
@@ -49,7 +49,7 @@ import org.jboss.logging.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
-public class ProtonHandler extends ProtonInitializable {
+public class ProtonHandler extends ProtonInitializable implements SaslListener {
private static final Logger log = Logger.getLogger(ProtonHandler.class);
@@ -65,8 +65,6 @@ public class ProtonHandler extends ProtonInitializable {
private List<EventHandler> handlers = new ArrayList<>();
- private Sasl sasl;
-
private ServerSASL chosenMechanism;
private ClientSASL clientSASLMechanism;
@@ -174,9 +172,10 @@ public class ProtonHandler extends ProtonInitializable {
}
public void createServerSASL(String[] mechanisms) {
- this.sasl = transport.sasl();
- this.sasl.server();
+ Sasl sasl = transport.sasl();
+ sasl.server();
sasl.setMechanisms(mechanisms);
+ sasl.setListener(this);
}
public void flushBytes() {
@@ -281,7 +280,6 @@ public class ProtonHandler extends ProtonInitializable {
lock.lock();
try {
transport.process();
- checkSASL();
} finally {
lock.unlock();
}
@@ -303,113 +301,127 @@ public class ProtonHandler extends ProtonInitializable {
flush();
}
- protected void checkSASL() {
- if (isServer) {
- if (sasl != null && sasl.getRemoteMechanisms().length > 0) {
+ // server side SASL Listener
+ @Override
+ public void onSaslInit(Sasl sasl, Transport transport) {
+ log.debug("onSaslInit: " + sasl);
+ dispatchRemoteMechanismChosen(sasl.getRemoteMechanisms()[0]);
- if (chosenMechanism == null) {
- if (log.isTraceEnabled()) {
- log.trace("SASL chosenMechanism: " + sasl.getRemoteMechanisms()[0]);
- }
- dispatchRemoteMechanismChosen(sasl.getRemoteMechanisms()[0]);
+ if (chosenMechanism != null) {
+
+ processPending(sasl);
+
+ } else {
+ // no auth available, system error
+ saslComplete(sasl, Sasl.SaslOutcome.PN_SASL_SYS);
+ }
+ }
+
+ private void processPending(Sasl sasl) {
+ byte[] dataSASL = new byte[sasl.pending()];
+
+ int received = sasl.recv(dataSASL, 0, dataSASL.length);
+ if (log.isTraceEnabled()) {
+ log.trace("Working on sasl, length:" + received);
+ }
+
+ byte[] response = chosenMechanism.processSASL(received != -1 ? dataSASL : null);
+ if (response != null) {
+ sasl.send(response, 0, response.length);
+ }
+
+ saslResult = chosenMechanism.result();
+ if (saslResult != null) {
+ if (saslResult.isSuccess()) {
+ saslComplete(sasl, Sasl.SaslOutcome.PN_SASL_OK);
+ } else {
+ saslComplete(sasl, Sasl.SaslOutcome.PN_SASL_AUTH);
+ }
+ }
+ }
+
+ @Override
+ public void onSaslResponse(Sasl sasl, Transport transport) {
+ log.debug("onSaslResponse: " + sasl);
+ processPending(sasl);
+ }
+
+ // client SASL Listener
+ @Override
+ public void onSaslMechanisms(Sasl sasl, Transport transport) {
+
+ dispatchMechanismsOffered(sasl.getRemoteMechanisms());
+
+ if (clientSASLMechanism == null) {
+ log.infof("Outbound connection failed - unknown mechanism, offered mechanisms: %s",
+ Arrays.asList(sasl.getRemoteMechanisms()));
+ dispatchAuthFailed();
+ } else {
+ sasl.setMechanisms(clientSASLMechanism.getName());
+ byte[] initialResponse = clientSASLMechanism.getInitialResponse();
+ if (initialResponse != null) {
+ sasl.send(initialResponse, 0, initialResponse.length);
+ }
+ }
+ }
+
+ @Override
+ public void onSaslChallenge(Sasl sasl, Transport transport) {
+ int challengeSize = sasl.pending();
+ byte[] challenge = new byte[challengeSize];
+ sasl.recv(challenge, 0, challengeSize);
+ byte[] response = clientSASLMechanism.getResponse(challenge);
+ sasl.send(response, 0, response.length);
+ }
+
+ @Override
+ public void onSaslOutcome(Sasl sasl, Transport transport) {
+ log.debug("onSaslOutcome: " + sasl);
+ switch (sasl.getState()) {
+ case PN_SASL_FAIL:
+ log.info("Outbound connection failed, authentication failure");
+ dispatchAuthFailed();
+ break;
+ case PN_SASL_PASS:
+ log.debug("Outbound connection succeeded");
+
+ if (sasl.pending() != 0) {
+ byte[] additionalData = new byte[sasl.pending()];
+ sasl.recv(additionalData, 0, additionalData.length);
+ clientSASLMechanism.getResponse(additionalData);
}
- if (chosenMechanism != null) {
- byte[] dataSASL = new byte[sasl.pending()];
- int received = sasl.recv(dataSASL, 0, dataSASL.length);
- if (log.isTraceEnabled()) {
- log.trace("Working on sasl ::" + (received > 0 ? ByteUtil.bytesToHex(dataSASL, 2) : "recv:" + received));
+ saslResult = new SASLResult() {
+ @Override
+ public String getUser() {
+ return null;
}
- byte[] response = null;
- if (received != -1) {
- response = chosenMechanism.processSASL(dataSASL);
- }
- if (response != null) {
- sasl.send(response, 0, response.length);
+ @Override
+ public Subject getSubject() {
+ return null;
}
- saslResult = chosenMechanism.result();
- if (saslResult != null) {
- if (saslResult.isSuccess()) {
- saslComplete(Sasl.SaslOutcome.PN_SASL_OK);
- } else {
- saslComplete(Sasl.SaslOutcome.PN_SASL_AUTH);
- }
+ @Override
+ public boolean isSuccess() {
+ return true;
}
- } else {
- // no auth available, system error
- saslComplete(Sasl.SaslOutcome.PN_SASL_SYS);
- }
- }
- } else {
- if (sasl != null) {
- switch (sasl.getState()) {
- case PN_SASL_IDLE:
- if (sasl.getRemoteMechanisms().length != 0) {
- dispatchMechanismsOffered(sasl.getRemoteMechanisms());
-
- if (clientSASLMechanism == null) {
- log.infof("Outbound connection failed - unknown mechanism, offered mechanisms: %s",
- Arrays.asList(sasl.getRemoteMechanisms()));
- sasl = null;
- dispatchAuthFailed();
- } else {
- sasl.setMechanisms(clientSASLMechanism.getName());
- byte[] initialResponse = clientSASLMechanism.getInitialResponse();
- if (initialResponse != null) {
- sasl.send(initialResponse, 0, initialResponse.length);
- }
- }
- }
- break;
- case PN_SASL_STEP:
- int challengeSize = sasl.pending();
- byte[] challenge = new byte[challengeSize];
- sasl.recv(challenge, 0, challengeSize);
- byte[] response = clientSASLMechanism.getResponse(challenge);
- sasl.send(response, 0, response.length);
- break;
- case PN_SASL_FAIL:
- log.info("Outbound connection failed, authentication failure");
- sasl = null;
- dispatchAuthFailed();
- break;
- case PN_SASL_PASS:
- log.debug("Outbound connection succeeded");
- saslResult = new SASLResult() {
- @Override
- public String getUser() {
- return null;
- }
+ };
- @Override
- public Subject getSubject() {
- return null;
- }
+ dispatchAuthSuccess();
+ break;
- @Override
- public boolean isSuccess() {
- return true;
- }
- };
- sasl = null;
-
- dispatchAuthSuccess();
- break;
- case PN_SASL_CONF:
- // do nothing
- break;
- }
- }
+ default:
+ break;
}
}
- private void saslComplete(Sasl.SaslOutcome saslOutcome) {
+ private void saslComplete(Sasl sasl, Sasl.SaslOutcome saslOutcome) {
+ log.debug("saslComplete: " + sasl);
sasl.done(saslOutcome);
- sasl = null;
if (chosenMechanism != null) {
chosenMechanism.done();
+ chosenMechanism = null;
}
}
@@ -501,7 +513,8 @@ public class ProtonHandler extends ProtonInitializable {
}
public void createClientSASL() {
- this.sasl = transport.sasl();
- this.sasl.client();
+ Sasl sasl = transport.sasl();
+ sasl.client();
+ sasl.setListener(this);
}
}
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASL.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASL.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASL.java
new file mode 100644
index 0000000..3c1a012
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASL.java
@@ -0,0 +1,62 @@
+/*
+ * 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.protocol.amqp.sasl;
+
+import java.security.Principal;
+
+public class ExternalServerSASL implements ServerSASL {
+
+ public static final String NAME = "EXTERNAL";
+ private static final byte[] EMPTY = new byte[0];
+ private Principal principal;
+ private SASLResult result;
+
+ public ExternalServerSASL() {
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public byte[] processSASL(byte[] bytes) {
+ if (bytes != null) {
+ if (bytes.length == 0) {
+ result = new PrincipalSASLResult(true, principal);
+ } else {
+ // we don't accept any client identity
+ result = new PrincipalSASLResult(false, null);
+ }
+ }
+ return EMPTY;
+ }
+
+ @Override
+ public SASLResult result() {
+ return result;
+ }
+
+ @Override
+ public void done() {
+ }
+
+ public void setPrincipal(Principal principal) {
+ this.principal = principal;
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPISASLResult.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPISASLResult.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPISASLResult.java
deleted file mode 100644
index 0b6e378..0000000
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPISASLResult.java
+++ /dev/null
@@ -1,51 +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.protocol.amqp.sasl;
-
-import javax.security.auth.Subject;
-import java.security.Principal;
-
-public class GSSAPISASLResult implements SASLResult {
-
- private final boolean success;
- private final Subject identity = new Subject();
- private String user;
-
-
- public GSSAPISASLResult(boolean success, Principal peer) {
- this.success = success;
- if (success) {
- this.identity.getPrivateCredentials().add(peer);
- this.user = peer.getName();
- }
- }
-
- @Override
- public String getUser() {
- return user;
- }
-
- @Override
- public Subject getSubject() {
- return identity;
- }
-
- @Override
- public boolean isSuccess() {
- return success;
- }
-}
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java
index e89d548..fc601f0 100644
--- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASL.java
@@ -76,13 +76,13 @@ public class GSSAPIServerSASL implements ServerSASL {
byte[] challenge = Subject.doAs(jaasId, (PrivilegedExceptionAction<byte[]>) () -> saslServer.evaluateResponse(bytes));
if (saslServer.isComplete()) {
- result = new GSSAPISASLResult(true, new KerberosPrincipal(saslServer.getAuthorizationID()));
+ result = new PrincipalSASLResult(true, new KerberosPrincipal(saslServer.getAuthorizationID()));
}
return challenge;
} catch (Exception outOfHere) {
log.info("Error on sasl input: " + outOfHere.toString(), outOfHere);
- result = new GSSAPISASLResult(false, null);
+ result = new PrincipalSASLResult(false, null);
}
return null;
}
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PrincipalSASLResult.java
----------------------------------------------------------------------
diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PrincipalSASLResult.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PrincipalSASLResult.java
new file mode 100644
index 0000000..a44b0c8
--- /dev/null
+++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PrincipalSASLResult.java
@@ -0,0 +1,51 @@
+/*
+ * 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.protocol.amqp.sasl;
+
+import javax.security.auth.Subject;
+import java.security.Principal;
+
+public class PrincipalSASLResult implements SASLResult {
+
+ private final boolean success;
+ private final Subject identity = new Subject();
+ private String user;
+
+
+ public PrincipalSASLResult(boolean success, Principal peer) {
+ this.success = success;
+ if (success) {
+ this.identity.getPrivateCredentials().add(peer);
+ this.user = peer.getName();
+ }
+ }
+
+ @Override
+ public String getUser() {
+ return user;
+ }
+
+ @Override
+ public Subject getSubject() {
+ return identity;
+ }
+
+ @Override
+ public boolean isSuccess() {
+ return success;
+ }
+}
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/docs/user-manual/en/security.md
----------------------------------------------------------------------
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index 8bbcb38..ac0c76f 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -741,6 +741,11 @@ SASL GSSAPI. However, for clients that don't support SASL (core client), using T
over an *unsecure* channel.
+## SASL
+[AMQP](using-AMQP.md) supports SASL. The following mechanisms are supported; PLAIN, EXTERNAL, ANONYMOUS, GSSAPI.
+The published list can be constrained via the amqp acceptor `saslMechanisms` property.
+Note: EXTERNAL will only be chosen if a subject is available from the TLS client certificate.
+
## Changing the username/password for clustering
In order for cluster connections to work correctly, each node in the
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalTest.java
----------------------------------------------------------------------
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalTest.java
new file mode 100644
index 0000000..b9da886
--- /dev/null
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslExternalTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.tests.integration.amqp;
+
+import javax.jms.Connection;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import java.lang.management.ManagementFactory;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.activemq.artemis.api.core.TransportConfiguration;
+import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl;
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory;
+import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector;
+import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.server.ActiveMQServer;
+import org.apache.activemq.artemis.core.server.ActiveMQServers;
+import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManagerFactory;
+import org.apache.activemq.artemis.protocol.amqp.client.AMQPClientConnectionFactory;
+import org.apache.activemq.artemis.protocol.amqp.client.ProtonClientConnectionManager;
+import org.apache.activemq.artemis.protocol.amqp.client.ProtonClientProtocolManager;
+import org.apache.activemq.artemis.protocol.amqp.proton.handler.EventHandler;
+import org.apache.activemq.artemis.protocol.amqp.proton.handler.ProtonHandler;
+import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASL;
+import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASLFactory;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
+import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
+import org.apache.activemq.artemis.tests.util.Wait;
+import org.apache.activemq.artemis.utils.RandomUtil;
+import org.apache.qpid.jms.JmsConnectionFactory;
+import org.apache.qpid.jms.sasl.ExternalMechanism;
+import org.apache.qpid.proton.amqp.Symbol;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class JMSSaslExternalTest extends ActiveMQTestBase {
+
+ static {
+ String path = System.getProperty("java.security.auth.login.config");
+ if (path == null) {
+ URL resource = JMSSaslExternalTest.class.getClassLoader().getResource("login.config");
+ if (resource != null) {
+ path = resource.getFile();
+ System.setProperty("java.security.auth.login.config", path);
+ }
+ }
+ }
+
+ private ActiveMQServer server;
+ private final boolean debug = false;
+
+ @Before
+ public void setUpDebug() throws Exception {
+
+ if (debug) {
+ for (java.util.logging.Logger logger : new java.util.logging.Logger[] {java.util.logging.Logger.getLogger("javax.security.sasl"), java.util.logging.Logger.getLogger("org.apache.qpid.proton")}) {
+ logger.setLevel(java.util.logging.Level.FINEST);
+ logger.addHandler(new java.util.logging.ConsoleHandler());
+ for (java.util.logging.Handler handler : logger.getHandlers()) {
+ handler.setLevel(java.util.logging.Level.FINEST);
+ }
+ }
+ }
+ }
+
+ @Before
+ public void startServer() throws Exception {
+ ConfigurationImpl configuration = createBasicConfig(0).setJMXManagementEnabled(false);
+ ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager("CertLogin");
+ server = addServer(ActiveMQServers.newActiveMQServer(configuration.setSecurityEnabled(true), ManagementFactory.getPlatformMBeanServer(), securityManager, false));
+
+ Map<String, Object> params = new HashMap<>();
+ params.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+ params.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "keystore1.jks");
+ params.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "changeit");
+ params.put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "truststore.jks");
+ params.put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "changeit");
+ params.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
+
+ Map<String, Object> extraParams = new HashMap<>();
+ extraParams.put("saslMechanisms", "EXTERNAL");
+
+ server.getConfiguration().addAcceptorConfiguration(new TransportConfiguration(NettyAcceptorFactory.class.getCanonicalName(), params, "netty", extraParams));
+
+ // role mapping via CertLogin - TextFileCertificateLoginModule
+ final String roleName = "widgets";
+ Role role = new Role(roleName, true, true, true, true, true, true, true, true, true, true);
+ Set<Role> roles = new HashSet<>();
+ roles.add(role);
+ server.getSecurityRepository().addMatch("TEST", roles);
+
+ server.start();
+ }
+
+ @After
+ public void stopServer() throws Exception {
+ server.stop();
+ }
+
+ @Test(timeout = 600000)
+ public void testConnection() throws Exception {
+
+ final String keystore = this.getClass().getClassLoader().getResource("client_not_revoked.jks").getFile();
+ final String truststore = this.getClass().getClassLoader().getResource("truststore.jks").getFile();
+
+ String connOptions = "?amqp.saslMechanisms=EXTERNAL" + "&" +
+ "transport.trustStoreLocation=" + truststore + "&" +
+ "transport.trustStorePassword=changeit" + "&" +
+ "transport.keyStoreLocation=" + keystore + "&" +
+ "transport.keyStorePassword=changeit" + "&" +
+ "transport.verifyHost=false";
+
+ JmsConnectionFactory factory = new JmsConnectionFactory(new URI("amqps://localhost:" + 61616 + connOptions));
+ Connection connection = factory.createConnection("client", null);
+ connection.start();
+
+ try {
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ javax.jms.Queue queue = session.createQueue("TEST");
+ MessageConsumer consumer = session.createConsumer(queue);
+ MessageProducer producer = session.createProducer(queue);
+
+ final String text = RandomUtil.randomString();
+ producer.send(session.createTextMessage(text));
+
+ TextMessage m = (TextMessage) consumer.receive(1000);
+ assertNotNull(m);
+ assertEquals(text, m.getText());
+
+ } finally {
+ connection.close();
+ }
+ }
+
+ @Test
+ public void testOutbound() throws Exception {
+
+ final Map<String, Object> config = new LinkedHashMap<>(); config.put(TransportConstants.HOST_PROP_NAME, "localhost");
+ config.put(TransportConstants.PORT_PROP_NAME, String.valueOf(61616));
+ config.put(TransportConstants.KEYSTORE_PATH_PROP_NAME, "client_not_revoked.jks");
+ config.put(TransportConstants.KEYSTORE_PASSWORD_PROP_NAME, "changeit");
+ config.put(TransportConstants.TRUSTSTORE_PATH_PROP_NAME, "truststore.jks");
+ config.put(TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME, "changeit");
+ config.put(TransportConstants.NEED_CLIENT_AUTH_PROP_NAME, true);
+ config.put(TransportConstants.SSL_ENABLED_PROP_NAME, true);
+
+
+ final AtomicBoolean connectionOpened = new AtomicBoolean();
+ final AtomicBoolean authFailed = new AtomicBoolean();
+
+ EventHandler eventHandler = new EventHandler() {
+ @Override
+ public void onRemoteOpen(org.apache.qpid.proton.engine.Connection connection) throws Exception {
+ connectionOpened.set(true);
+ }
+
+ @Override
+ public void onAuthFailed(ProtonHandler protonHandler, org.apache.qpid.proton.engine.Connection connection) {
+ authFailed.set(true);
+ }
+ };
+
+ final ClientSASLFactory clientSASLFactory = new ClientSASLFactory() {
+ @Override
+ public ClientSASL chooseMechanism(String[] availableMechanims) {
+ ExternalMechanism externalMechanism = new ExternalMechanism();
+ return new ClientSASL() {
+ @Override
+ public String getName() {
+ return externalMechanism.getName();
+ }
+
+ @Override
+ public byte[] getInitialResponse() {
+ return externalMechanism.getInitialResponse();
+ }
+
+ @Override
+ public byte[] getResponse(byte[] challenge) {
+ return new byte[0];
+ }
+ };
+ }
+ };
+
+
+ ProtonClientConnectionManager lifeCycleListener = new ProtonClientConnectionManager(new AMQPClientConnectionFactory(server, "myid", Collections.singletonMap(Symbol.getSymbol("myprop"), "propvalue"), 5000), Optional.of(eventHandler), clientSASLFactory);
+ ProtonClientProtocolManager protocolManager = new ProtonClientProtocolManager(new ProtonProtocolManagerFactory(), server);
+ NettyConnector connector = new NettyConnector(config, lifeCycleListener, lifeCycleListener, server.getExecutorFactory().getExecutor(), server.getExecutorFactory().getExecutor(), server.getScheduledPool(), protocolManager);
+ connector.start();
+ connector.createConnection();
+
+ try {
+ Wait.assertEquals(1, server::getConnectionCount);
+ Wait.assertTrue(connectionOpened::get);
+ Wait.assertFalse(authFailed::get);
+
+ lifeCycleListener.stop();
+
+ Wait.assertEquals(0, server::getConnectionCount);
+ } finally {
+ lifeCycleListener.stop();
+ }
+
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java
----------------------------------------------------------------------
diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java
index 5a93154..a7af43d 100644
--- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java
+++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/JMSSaslGssapiTest.java
@@ -82,11 +82,12 @@ public class JMSSaslGssapiTest extends JMSClientTestSupport {
kdc.createPrincipal(userKeyTab, "client", "amqp/localhost");
if (debug) {
- java.util.logging.Logger logger = java.util.logging.Logger.getLogger("javax.security.sasl");
- logger.setLevel(java.util.logging.Level.FINEST);
- logger.addHandler(new java.util.logging.ConsoleHandler());
- for (java.util.logging.Handler handler : logger.getHandlers()) {
- handler.setLevel(java.util.logging.Level.FINEST);
+ for (java.util.logging.Logger logger : new java.util.logging.Logger[] {java.util.logging.Logger.getLogger("javax.security.sasl"), java.util.logging.Logger.getLogger("org.apache.qpid.proton")}) {
+ logger.setLevel(java.util.logging.Level.FINEST);
+ logger.addHandler(new java.util.logging.ConsoleHandler());
+ for (java.util.logging.Handler handler : logger.getHandlers()) {
+ handler.setLevel(java.util.logging.Level.FINEST);
+ }
}
}
}
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/tests/integration-tests/src/test/resources/cert-roles.properties
----------------------------------------------------------------------
diff --git a/tests/integration-tests/src/test/resources/cert-roles.properties b/tests/integration-tests/src/test/resources/cert-roles.properties
index 4f3ba3f..5f860fc 100644
--- a/tests/integration-tests/src/test/resources/cert-roles.properties
+++ b/tests/integration-tests/src/test/resources/cert-roles.properties
@@ -16,3 +16,4 @@
#
programmers=first
+widgets=second
http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/72ec6c8e/tests/integration-tests/src/test/resources/cert-users.properties
----------------------------------------------------------------------
diff --git a/tests/integration-tests/src/test/resources/cert-users.properties b/tests/integration-tests/src/test/resources/cert-users.properties
index 7bd65f0..4af10d5 100644
--- a/tests/integration-tests/src/test/resources/cert-users.properties
+++ b/tests/integration-tests/src/test/resources/cert-users.properties
@@ -16,3 +16,4 @@
#
first=CN=ActiveMQ Artemis Client, OU=Artemis, O=ActiveMQ, L=AMQ, ST=AMQ, C=AMQ
+second=O=Internet Widgits Pty Ltd, C=AU, ST=Some-State, CN=lakalkalaoioislkxn
[2/2] activemq-artemis git commit: This closes #1961
Posted by ta...@apache.org.
This closes #1961
Project: http://git-wip-us.apache.org/repos/asf/activemq-artemis/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq-artemis/commit/2f9d3739
Tree: http://git-wip-us.apache.org/repos/asf/activemq-artemis/tree/2f9d3739
Diff: http://git-wip-us.apache.org/repos/asf/activemq-artemis/diff/2f9d3739
Branch: refs/heads/master
Commit: 2f9d3739375ed1b2aea19d77eda8440c56101072
Parents: 92a73e2 72ec6c8
Author: Timothy Bish <ta...@gmail.com>
Authored: Thu Mar 22 10:10:50 2018 -0400
Committer: Timothy Bish <ta...@gmail.com>
Committed: Thu Mar 22 10:10:50 2018 -0400
----------------------------------------------------------------------
.../amqp/broker/AMQPConnectionCallback.java | 16 ++
.../amqp/proton/AMQPConnectionContext.java | 1 -
.../amqp/proton/handler/ProtonHandler.java | 213 +++++++++--------
.../protocol/amqp/sasl/ExternalServerSASL.java | 62 +++++
.../protocol/amqp/sasl/GSSAPISASLResult.java | 51 ----
.../protocol/amqp/sasl/GSSAPIServerSASL.java | 4 +-
.../protocol/amqp/sasl/PrincipalSASLResult.java | 51 ++++
docs/user-manual/en/security.md | 5 +
.../integration/amqp/JMSSaslExternalTest.java | 235 +++++++++++++++++++
.../integration/amqp/JMSSaslGssapiTest.java | 11 +-
.../src/test/resources/cert-roles.properties | 1 +
.../src/test/resources/cert-users.properties | 1 +
12 files changed, 492 insertions(+), 159 deletions(-)
----------------------------------------------------------------------