[ARTEMIS-1758] support SASL EXTERNAL with TextCertLoginModule

- rework proton handler to use saslListener
This commit is contained in:
gtully 2018-03-13 17:21:06 +00:00 committed by Timothy Bish
parent 92a73e2cb6
commit 72ec6c8e0b
11 changed files with 444 additions and 111 deletions

View File

@ -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;
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -16,3 +16,4 @@
#
programmers=first
widgets=second

View File

@ -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