[ARTEMIS-1758] support SASL EXTERNAL with TextCertLoginModule
- rework proton handler to use saslListener
This commit is contained in:
parent
92a73e2cb6
commit
72ec6c8e0b
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
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));
|
||||
}
|
||||
processPending(sasl);
|
||||
|
||||
byte[] response = null;
|
||||
if (received != -1) {
|
||||
response = chosenMechanism.processSASL(dataSASL);
|
||||
}
|
||||
if (response != null) {
|
||||
sasl.send(response, 0, response.length);
|
||||
}
|
||||
saslResult = chosenMechanism.result();
|
||||
|
||||
if (saslResult != null) {
|
||||
if (saslResult.isSuccess()) {
|
||||
saslComplete(Sasl.SaslOutcome.PN_SASL_OK);
|
||||
} else {
|
||||
saslComplete(Sasl.SaslOutcome.PN_SASL_AUTH);
|
||||
}
|
||||
}
|
||||
} 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());
|
||||
// no auth available, system error
|
||||
saslComplete(sasl, Sasl.SaslOutcome.PN_SASL_SYS);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
private void processPending(Sasl sasl) {
|
||||
byte[] dataSASL = new byte[sasl.pending()];
|
||||
|
||||
@Override
|
||||
public Subject getSubject() {
|
||||
return null;
|
||||
}
|
||||
int received = sasl.recv(dataSASL, 0, dataSASL.length);
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Working on sasl, length:" + received);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
sasl = null;
|
||||
byte[] response = chosenMechanism.processSASL(received != -1 ? dataSASL : null);
|
||||
if (response != null) {
|
||||
sasl.send(response, 0, response.length);
|
||||
}
|
||||
|
||||
dispatchAuthSuccess();
|
||||
break;
|
||||
case PN_SASL_CONF:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
saslResult = chosenMechanism.result();
|
||||
if (saslResult != null) {
|
||||
if (saslResult.isSuccess()) {
|
||||
saslComplete(sasl, Sasl.SaslOutcome.PN_SASL_OK);
|
||||
} else {
|
||||
saslComplete(sasl, Sasl.SaslOutcome.PN_SASL_AUTH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saslComplete(Sasl.SaslOutcome saslOutcome) {
|
||||
@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);
|
||||
}
|
||||
|
||||
saslResult = new SASLResult() {
|
||||
@Override
|
||||
public String getUser() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subject getSubject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
dispatchAuthSuccess();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -19,14 +19,14 @@ package org.apache.activemq.artemis.protocol.amqp.sasl;
|
|||
import javax.security.auth.Subject;
|
||||
import java.security.Principal;
|
||||
|
||||
public class GSSAPISASLResult implements SASLResult {
|
||||
public class PrincipalSASLResult implements SASLResult {
|
||||
|
||||
private final boolean success;
|
||||
private final Subject identity = new Subject();
|
||||
private String user;
|
||||
|
||||
|
||||
public GSSAPISASLResult(boolean success, Principal peer) {
|
||||
public PrincipalSASLResult(boolean success, Principal peer) {
|
||||
this.success = success;
|
||||
if (success) {
|
||||
this.identity.getPrivateCredentials().add(peer);
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,3 +16,4 @@
|
|||
#
|
||||
|
||||
programmers=first
|
||||
widgets=second
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue