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 <[email protected]> Authored: Tue Mar 13 17:21:06 2018 +0000 Committer: Timothy Bish <[email protected]> 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
