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 6cf4c8a6d5..71dcfde0ab 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,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 transactions = new ConcurrentHashMap<>(); + private final ConcurrentMap 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; diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java index 666abb1fa9..4940819d64 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/ProtonProtocolManager.java @@ -88,7 +88,7 @@ public class ProtonProtocolManager extends AbstractProtocolManager manager, Connection connection, + RemotingConnection remotingConnection) { + return new AnonymousServerSASL(); + } + + @Override + public int getPrecedence() { + return Integer.MIN_VALUE; + } + + @Override + public boolean isDefaultPermitted() { + return true; + } + +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java new file mode 100644 index 0000000000..e9087bec9d --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ExternalServerSASLFactory.java @@ -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 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; + } + +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java new file mode 100644 index 0000000000..e31632a607 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/GSSAPIServerSASLFactory.java @@ -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 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; + } + +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/MechanismFinder.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/MechanismFinder.java index fd24a5d754..62551f006c 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/MechanismFinder.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/MechanismFinder.java @@ -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 FACTORY_MAP = new HashMap<>(); + private static final Comparator 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 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); } } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java new file mode 100644 index 0000000000..5a88a82a9d --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/PlainServerSASLFactory.java @@ -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 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; + } + +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java new file mode 100644 index 0000000000..9831652a61 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/sasl/ServerSASLFactory.java @@ -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 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 true if this mechanism should be part of the servers default permitted + * protocols or false if it must be explicitly configured + */ + boolean isDefaultPermitted(); +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/resources/META-INF/services/org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory b/artemis-protocols/artemis-amqp-protocol/src/main/resources/META-INF/services/org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory new file mode 100644 index 0000000000..eb2c1127c5 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/main/resources/META-INF/services/org.apache.activemq.artemis.protocol.amqp.sasl.ServerSASLFactory @@ -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 \ No newline at end of file