ARTEMIS-33 Generic integration with SASL Frameworks

This adds the opportunity to register new SASL schemes via the default
java service-loader mechanism.
Implementors have to provide an implementation of the ServerSASLFactory
that is responsible for providing instances of the actual scheme.
This commit is contained in:
Christoph Läubrich 2021-02-05 06:16:01 +01:00 committed by Clebert Suconic
parent eb273d1073
commit 0bedb3048a
9 changed files with 325 additions and 40 deletions

View File

@ -17,7 +17,6 @@
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;
@ -29,7 +28,6 @@ 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;
@ -44,11 +42,10 @@ 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.MechanismFinder;
import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
import org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASL;
import org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.spi.core.remoting.ReadyListener;
import org.apache.activemq.artemis.utils.UUIDGenerator;
@ -63,7 +60,7 @@ public class AMQPConnectionCallback implements FailureListener, CloseListener {
private static final Logger logger = Logger.getLogger(AMQPConnectionCallback.class);
private ConcurrentMap<Binary, Transaction> transactions = new ConcurrentHashMap<>();
private final ConcurrentMap<Binary, Transaction> transactions = new ConcurrentHashMap<>();
private final ProtonProtocolManager manager;
@ -105,36 +102,11 @@ public class AMQPConnectionCallback implements FailureListener, CloseListener {
public ServerSASL getServerSASL(final String mechanism) {
ServerSASL result = null;
if (isPermittedMechanism(mechanism)) {
switch (mechanism) {
case PlainSASL.NAME:
result = new PlainSASL(server.getSecurityStore(), manager.getSecurityDomain(), connection.getProtocolConnection());
break;
case AnonymousServerSASL.NAME:
result = new AnonymousServerSASL();
break;
case GSSAPIServerSASL.NAME:
GSSAPIServerSASL gssapiServerSASL = new GSSAPIServerSASL();
gssapiServerSASL.setLoginConfigScope(manager.getSaslLoginConfigScope());
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;
ServerSASLFactory factory = MechanismFinder.getFactory(mechanism);
if (factory != null) {
result = factory.create(server, manager, connection, protonConnectionDelegate);
} else {
logger.debug("Mo matching mechanism found for: " + mechanism);
}
}
return result;

View File

@ -88,7 +88,7 @@ public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage,
private int initialRemoteMaxFrameSize = 4 * 1024;
private String[] saslMechanisms = MechanismFinder.getKnownMechanisms();
private String[] saslMechanisms = MechanismFinder.getDefaultMechanisms();
private String saslLoginConfigScope = "amqp-sasl-gssapi";

View File

@ -0,0 +1,48 @@
/*
* 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 org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
public class AnonymousServerSASLFactory implements ServerSASLFactory {
@Override
public String getMechanism() {
return AnonymousServerSASL.NAME;
}
@Override
public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
RemotingConnection remotingConnection) {
return new AnonymousServerSASL();
}
@Override
public int getPrecedence() {
return Integer.MIN_VALUE;
}
@Override
public boolean isDefaultPermitted() {
return true;
}
}

View File

@ -0,0 +1,65 @@
/*
* 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;
import org.apache.activemq.artemis.core.remoting.CertificateUtil;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.jboss.logging.Logger;
/**
*
*/
public class ExternalServerSASLFactory implements ServerSASLFactory {
private static final Logger logger = Logger.getLogger(ExternalServerSASLFactory.class);
@Override
public String getMechanism() {
return ExternalServerSASL.NAME;
}
@Override
public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
RemotingConnection remotingConnection) {
// validate ssl cert present
Principal principal = CertificateUtil.getPeerPrincipalFromConnection(remotingConnection);
if (principal != null) {
ExternalServerSASL externalServerSASL = new ExternalServerSASL();
externalServerSASL.setPrincipal(principal);
return externalServerSASL;
}
logger.debug("SASL EXTERNAL mechanism requires a TLS peer principal");
return null;
}
@Override
public int getPrecedence() {
return 0;
}
@Override
public boolean isDefaultPermitted() {
return false;
}
}

View File

@ -0,0 +1,58 @@
/*
* 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 org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManager;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.jboss.logging.Logger;
public class GSSAPIServerSASLFactory implements ServerSASLFactory {
private static final Logger logger = Logger.getLogger(GSSAPIServerSASLFactory.class);
@Override
public String getMechanism() {
return GSSAPIServerSASL.NAME;
}
@Override
public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
RemotingConnection remotingConnection) {
if (manager instanceof ProtonProtocolManager) {
GSSAPIServerSASL gssapiServerSASL = new GSSAPIServerSASL();
gssapiServerSASL.setLoginConfigScope(((ProtonProtocolManager) manager).getSaslLoginConfigScope());
return gssapiServerSASL;
}
logger.debug("SASL GSSAPI requires ProtonProtocolManager");
return null;
}
@Override
public int getPrecedence() {
return 0;
}
@Override
public boolean isDefaultPermitted() {
return false;
}
}

View File

@ -17,11 +17,43 @@
package org.apache.activemq.artemis.protocol.amqp.sasl;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
public class MechanismFinder {
public static String[] KNOWN_MECHANISMS = new String[]{PlainSASL.NAME, AnonymousServerSASL.NAME};
private static final Map<String, ServerSASLFactory> FACTORY_MAP = new HashMap<>();
private static final Comparator<? super ServerSASLFactory> PRECEDENCE_COMPARATOR =
(f1, f2) -> Integer.compare(f1.getPrecedence(), f2.getPrecedence());
private static final String[] DEFAULT_MECHANISMS;
public static String[] getKnownMechanisms() {
return KNOWN_MECHANISMS;
static {
ServiceLoader<ServerSASLFactory> serviceLoader =
ServiceLoader.load(ServerSASLFactory.class, MechanismFinder.class.getClassLoader());
for (ServerSASLFactory factory : serviceLoader) {
FACTORY_MAP.merge(factory.getMechanism(), factory, (f1, f2) -> {
if (f2.getPrecedence() > f1.getPrecedence()) {
return f2;
} else {
return f1;
}
});
}
DEFAULT_MECHANISMS = FACTORY_MAP.values()
.stream()
.filter(ServerSASLFactory::isDefaultPermitted)
.sorted(PRECEDENCE_COMPARATOR.reversed())
.map(ServerSASLFactory::getMechanism)
.toArray(String[]::new);
}
public static String[] getDefaultMechanisms() {
return DEFAULT_MECHANISMS;
}
public static ServerSASLFactory getFactory(String mechanism) {
return FACTORY_MAP.get(mechanism);
}
}

View File

@ -0,0 +1,48 @@
/*
* 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 org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
public class PlainServerSASLFactory implements ServerSASLFactory {
@Override
public String getMechanism() {
return ServerSASLPlain.NAME;
}
@Override
public ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
RemotingConnection remotingConnection) {
return new PlainSASL(server.getSecurityStore(), manager.getSecurityDomain(), connection.getProtocolConnection());
}
@Override
public int getPrecedence() {
return 0;
}
@Override
public boolean isDefaultPermitted() {
return true;
}
}

View File

@ -0,0 +1,58 @@
/*
* 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 org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.protocol.amqp.broker.AmqpInterceptor;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
/**
* A {@link ServerSASLFactory} is responsible for instantiating a given SASL mechanism
*/
public interface ServerSASLFactory {
/**
* @return the name of the scheme to offer
*/
String getMechanism();
/**
* creates a new {@link ServerSASL} for the provided context
* @param server
* @param manager
* @param connection
* @param remotingConnection
* @return a new instance of {@link ServerSASL} that implements the provided mechanism
*/
ServerSASL create(ActiveMQServer server, ProtocolManager<AmqpInterceptor> manager, Connection connection,
RemotingConnection remotingConnection);
/**
* returns the precedence of the given SASL mechanism, the default precedence is zero, where
* higher means better
* @return the precedence of this mechanism
*/
int getPrecedence();
/**
* @return <code>true</code> if this mechanism should be part of the servers default permitted
* protocols or <code>false</code> if it must be explicitly configured
*/
boolean isDefaultPermitted();
}

View File

@ -0,0 +1,4 @@
org.apache.activemq.artemis.protocol.amqp.sasl.AnonymousServerSASLFactory
org.apache.activemq.artemis.protocol.amqp.sasl.PlainServerSASLFactory
org.apache.activemq.artemis.protocol.amqp.sasl.GSSAPIServerSASLFactory
org.apache.activemq.artemis.protocol.amqp.sasl.ExternalServerSASLFactory