Skip to content

Commit

Permalink
feat(tls): ssl default options (#128)
Browse files Browse the repository at this point in the history
* feat(tls): use default sslcontext when ssl is enabled, and allow ssl mapping from properties in rabbitmq
  • Loading branch information
juancgalvis authored Dec 27, 2024
1 parent 62107bb commit d67f86d
Showing 1 changed file with 126 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.reactivecommons.async.rabbit.config.ConnectionFactoryProvider;
import org.reactivecommons.async.rabbit.config.RabbitProperties;
import org.reactivecommons.async.rabbit.config.props.AsyncProps;
import org.reactivecommons.async.rabbit.config.spring.RabbitPropertiesBase;
import org.springframework.boot.context.properties.PropertyMapper;
import reactor.core.publisher.Mono;
import reactor.rabbitmq.ChannelPool;
Expand All @@ -29,14 +30,50 @@
import reactor.rabbitmq.Utils;
import reactor.util.retry.Retry;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.time.Duration;
import java.util.Arrays;
import java.util.logging.Level;

@Log
@UtilityClass
public class RabbitMQSetupUtils {
private static final String LISTENER_TYPE = "listener";
private static final String SENDER_TYPE = "sender";
private static final String DEFAULT_PROTOCOL;
public static final int START_INTERVAL = 300;
public static final int MAX_BACKOFF_INTERVAL = 3000;

static {
String protocol = "TLSv1.1";
try {
String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols();
for (String prot : protocols) {
if ("TLSv1.2".equals(prot)) {
protocol = "TLSv1.2";
break;
}
}
} catch (NoSuchAlgorithmException e) {
// nothing
}
DEFAULT_PROTOCOL = protocol;
}

@SneakyThrows
public static ConnectionFactoryProvider connectionFactoryProvider(RabbitProperties properties) {
Expand All @@ -48,9 +85,7 @@ public static ConnectionFactoryProvider connectionFactoryProvider(RabbitProperti
map.from(properties::determinePassword).whenNonNull().to(factory::setPassword);
map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost);
factory.useNio();
if (properties.getSsl() != null && properties.getSsl().isEnabled()) {
factory.useSslProtocol();
}
setUpSSL(factory, properties);
return () -> factory;
}

Expand Down Expand Up @@ -118,9 +153,95 @@ private static Mono<Connection> createConnectionMono(ConnectionFactory factory,
log.log(Level.SEVERE, "Error creating connection to RabbitMQ Broker in host" +
factory.getHost() + ". Starting retry process...", err)
)
.retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(300))
.maxBackoff(Duration.ofMillis(3000)))
.retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofMillis(START_INTERVAL))
.maxBackoff(Duration.ofMillis(MAX_BACKOFF_INTERVAL)))
.cache();
}

// SSL based on RabbitConnectionFactoryBean
// https://github.com/spring-projects/spring-amqp/blob/main/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/connection/RabbitConnectionFactoryBean.java

private static void setUpSSL(ConnectionFactory factory, RabbitProperties properties)
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException,
CertificateException, IOException {
var ssl = properties.getSsl();
if (ssl != null && ssl.isEnabled()) {
var keyManagers = configureKeyManagers(ssl);
var trustManagers = configureTrustManagers(ssl);
var secureRandom = SecureRandom.getInstanceStrong();

if (log.isLoggable(Level.FINE)) {
log.fine("Initializing SSLContext with KM: " + Arrays.toString(keyManagers) +
", TM: " + Arrays.toString(trustManagers) + ", random: " + secureRandom);
}
var context = createSSLContext(ssl);
context.init(keyManagers, trustManagers, secureRandom);
factory.useSslProtocol(context);

logDetails(trustManagers);

if (ssl.getVerifyHostname()) {
factory.enableHostnameVerification();
}
}
}

private static KeyManager[] configureKeyManagers(RabbitPropertiesBase.Ssl ssl) throws KeyStoreException,
IOException,
NoSuchAlgorithmException,
CertificateException, UnrecoverableKeyException {
KeyManager[] keyManagers = null;
if (ssl.getKeyStore() != null) {
var ks = KeyStore.getInstance(ssl.getKeyStoreType());
char[] keyPassphrase = null;
if (ssl.getKeyStorePassword() != null) {
keyPassphrase = ssl.getKeyStorePassword().toCharArray();
}
try (var inputStream = new FileInputStream(ssl.getKeyStore())) {
ks.load(inputStream, keyPassphrase);
}
var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyPassphrase);
keyManagers = kmf.getKeyManagers();
}
return keyManagers;
}

private static TrustManager[] configureTrustManagers(RabbitPropertiesBase.Ssl ssl)
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore tks = null;
if (ssl.getTrustStore() != null) {
tks = KeyStore.getInstance(ssl.getTrustStoreType());
char[] trustPassphrase = null;
if (ssl.getTrustStorePassword() != null) {
trustPassphrase = ssl.getTrustStorePassword().toCharArray();
}
try (InputStream inputStream = new FileInputStream(ssl.getTrustStore())) {
tks.load(inputStream, trustPassphrase);
}
}

var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(tks);
return tmf.getTrustManagers();
}

private static SSLContext createSSLContext(RabbitPropertiesBase.Ssl ssl) throws NoSuchAlgorithmException {
return SSLContext.getInstance(ssl.getAlgorithm() != null ? ssl.getAlgorithm() : DEFAULT_PROTOCOL);
}

private static void logDetails(TrustManager[] managers) {
var found = false;
for (var trustManager : managers) {
if (trustManager instanceof X509TrustManager) {
found = true;
var x509TrustManager = (X509TrustManager) trustManager;
log.info("Loaded " + x509TrustManager.getAcceptedIssuers().length + " accepted issuers for rabbitmq");
}
}
if (!found) {
log.warning("No X509TrustManager found in the truststore.");
}
}

}

0 comments on commit d67f86d

Please sign in to comment.