From 93750785b2101409fac61365026fc7ff7b9d819c Mon Sep 17 00:00:00 2001 From: Tigran Mkrtchyan Date: Wed, 17 Apr 2024 12:38:25 +0200 Subject: [PATCH] rpc: update OncRpcSvcBuilder to support SSLContext supplier Motivation: SSLEngineConfigurator creates the SSLContext only once, thus it host certificate is updated, we need to restart the service to reload the certificate. Modification: Update OncRpcSvc to use custom versions of SSLEngineConfigurator, that will always call SSLContextConfigurator#createSSLContext to get a context. Create a custom version of SSLContextConfigurator, that uses a suppler to get a SSLContext. Update OncRpcSvcBuilder to accept SSLContext supplier. Result: More flexible SSLContext creation Acked-by: Lea Morschel Target: master --- .../dcache/oncrpc4j/grizzly/GrizzlyUtils.java | 67 ++++++++++++++++++- .../org/dcache/oncrpc4j/rpc/OncRpcSvc.java | 28 ++++++-- .../dcache/oncrpc4j/rpc/OncRpcSvcBuilder.java | 15 +++++ .../oncrpc4j/rpc/OncRpcSvcBuilderTest.java | 14 +++- 4 files changed, 115 insertions(+), 9 deletions(-) diff --git a/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/grizzly/GrizzlyUtils.java b/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/grizzly/GrizzlyUtils.java index d7d9696..ca59aed 100644 --- a/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/grizzly/GrizzlyUtils.java +++ b/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/grizzly/GrizzlyUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 - 2018 Deutsches Elektronen-Synchroton, + * Copyright (c) 2009 - 2024 Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY * * This library is free software; you can redistribute it and/or modify @@ -33,13 +33,21 @@ import org.glassfish.grizzly.memory.PooledMemoryManager; import org.glassfish.grizzly.nio.transport.TCPNIOTransport; import org.glassfish.grizzly.nio.transport.UDPNIOTransport; +import org.glassfish.grizzly.ssl.SSLContextConfigurator; +import org.glassfish.grizzly.ssl.SSLEngineConfigurator; import org.glassfish.grizzly.strategies.LeaderFollowerNIOStrategy; import org.glassfish.grizzly.strategies.SameThreadIOStrategy; import org.glassfish.grizzly.threadpool.ThreadPoolConfig; import static com.google.common.base.Preconditions.checkArgument; + import static org.dcache.oncrpc4j.rpc.IoStrategy.WORKER_THREAD; +import javax.net.ssl.SSLContext; + +import java.util.concurrent.Callable; +import javax.net.ssl.SSLEngine; + /** * Class with utility methods for Grizzly */ @@ -165,4 +173,61 @@ public static IOStrategy getNIOStrategy(IoStrategy ioStrategy) { return SameThreadIOStrategy.getInstance(); } } + + /** + * Create a SSLContextConfigurator that uses the specified SSLContext supplier. + * @param supplier a supplier that will be called to obtain the SSLContext instance. + * @return a SSLContextConfigurator that uses the specified SSLContext supplier. + */ + public static SSLContextConfigurator asContextConfigurator(Callable supplier) { + return new SupplierBasedSSLContextConfigurator(supplier); + } + + /** + * A version of {@link SSLContextConfigurator} that uses a supplier to obtain the SSLContext. + */ + private static class SupplierBasedSSLContextConfigurator extends SSLContextConfigurator { + + private final Callable contextSupplier; + + public SupplierBasedSSLContextConfigurator(Callable contextSupplier) { + super(false); + this.contextSupplier = contextSupplier; + } + + @Override + public SSLContext createSSLContext(boolean throwException) { + try { + return contextSupplier.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * A SSLEngineConfigurator that always uses supplied sslContextConfigurator to create + * a new {@link SSLContext} instance. + */ + public static class ReloadableSSLEngineConfigurator extends SSLEngineConfigurator { + + public ReloadableSSLEngineConfigurator(SSLContextConfigurator sslContextConfigurator, boolean clientMode, + boolean needClientAuth, boolean wantClientAuth) { + super(sslContextConfigurator, clientMode, needClientAuth, wantClientAuth); + } + + @Override + public SSLContext getSslContext() { + return sslContextConfiguration.createSSLContext(true); + } + + @Override + public SSLEngine createSSLEngine(String peerHost, int peerPort) { + var ctx = getSslContext(); + final SSLEngine sslEngine = ctx.createSSLEngine(peerHost, peerPort); + configure(sslEngine); + + return sslEngine; + } + } } diff --git a/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvc.java b/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvc.java index 992bb4b..b3012e2 100644 --- a/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvc.java +++ b/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 - 2022 Deutsches Elektronen-Synchroton, + * Copyright (c) 2009 - 2024 Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY * * This library is free software; you can redistribute it and/or modify @@ -20,6 +20,7 @@ package org.dcache.oncrpc4j.rpc; import org.dcache.oncrpc4j.grizzly.GrizzlyRpcTransport; +import org.dcache.oncrpc4j.grizzly.GrizzlyUtils; import org.dcache.oncrpc4j.grizzly.StartTlsFilter; import org.dcache.oncrpc4j.portmap.GenericPortmapClient; import org.dcache.oncrpc4j.portmap.OncPortmapClient; @@ -45,6 +46,7 @@ import org.glassfish.grizzly.nio.transport.TCPNIOTransportBuilder; import org.glassfish.grizzly.nio.transport.UDPNIOTransport; import org.glassfish.grizzly.nio.transport.UDPNIOTransportBuilder; +import org.glassfish.grizzly.ssl.SSLContextConfigurator; import org.glassfish.grizzly.ssl.SSLEngineConfigurator; import org.glassfish.grizzly.ssl.SSLFilter; import org.glassfish.grizzly.threadpool.ThreadPoolConfig; @@ -64,6 +66,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -107,7 +110,7 @@ public class OncRpcSvc { /** * SSL context to use, if configured. */ - private final SSLContext _sslContext; + private final Callable _sslContextProvider; /** * SSL parameters that should be applied to SSL engine. @@ -194,8 +197,14 @@ public class OncRpcSvc { _gssSessionManager = builder.getGssSessionManager(); _programs.putAll(builder.getRpcServices()); _withSubjectPropagation = builder.getSubjectPropagation(); - _svcName = builder.getServiceName(); - _sslContext = builder.getSSLContext(); + _svcName = builder.getServiceName(); + + if (builder.getSSLContext() != null) { + final SSLContext sslContext = builder.getSSLContext(); + _sslContextProvider = () -> sslContext; + } else { + _sslContextProvider = builder.getSSLContextProvider(); + } _startTLS = builder.isStartTLS(); _sslParams = builder.getSSLParameters(); _callInterceptor = builder.getCallInterceptor(); @@ -323,12 +332,17 @@ public void start() throws IOException { FilterChainBuilder filterChain = FilterChainBuilder.stateless(); filterChain.add(new TransportFilter()); - if (_sslContext != null) { + if (_sslContextProvider != null) { + + SSLContextConfigurator sslContextConfigurator = GrizzlyUtils.asContextConfigurator(_sslContextProvider); + + // Grizzly original SSLEngineConfigurator reuses the context. We need to use the one + // that sslContextConfigurator#createSSLContext returns to support certificate reloading. SSLEngineConfigurator serverSSLEngineConfigurator = - new SSLEngineConfigurator(_sslContext, false, false, false); + new GrizzlyUtils.ReloadableSSLEngineConfigurator(sslContextConfigurator, false, false, false); SSLEngineConfigurator clientSSLEngineConfigurator = - new SSLEngineConfigurator(_sslContext, true, false, false); + new GrizzlyUtils.ReloadableSSLEngineConfigurator(sslContextConfigurator, true, false, false); if (_sslParams != null) { String[] cipherSuites = _sslParams.getCipherSuites(); diff --git a/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvcBuilder.java b/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvcBuilder.java index af8bd3b..a00bd45 100644 --- a/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvcBuilder.java +++ b/oncrpc4j-core/src/main/java/org/dcache/oncrpc4j/rpc/OncRpcSvcBuilder.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; @@ -81,6 +82,7 @@ public class OncRpcSvcBuilder { private int _workerThreadPoolSize = 0; private boolean _subjectPropagation = false; private SSLContext _sslContext = null; + private Callable _sslContextProvider = null; private boolean _startTLS = false; private SSLParameters _sslParams; private MemoryAllocator _allocator = MemoryAllocator.DEFAULT; @@ -236,6 +238,15 @@ public OncRpcSvcBuilder withMemoryAllocator(MemoryAllocator allocator) { return this; } + public OncRpcSvcBuilder withSSLContextProvider(Callable sslContextProvider) { + _sslContextProvider = sslContextProvider; + return this; + } + + public Callable getSSLContextProvider() { + return _sslContextProvider; + } + public boolean getSubjectPropagation() { return _subjectPropagation; } @@ -359,6 +370,10 @@ public OncRpcSvc build() { throw new IllegalArgumentException("Can't set worker thread pool size with external execution service"); } + if (_sslContext != null && _sslContextProvider != null) { + throw new IllegalArgumentException("Can't set both SSLContext and SSLContextProvider"); + } + return new OncRpcSvc(this); } } diff --git a/oncrpc4j-core/src/test/java/org/dcache/oncrpc4j/rpc/OncRpcSvcBuilderTest.java b/oncrpc4j-core/src/test/java/org/dcache/oncrpc4j/rpc/OncRpcSvcBuilderTest.java index 3d20578..f2c4763 100644 --- a/oncrpc4j-core/src/test/java/org/dcache/oncrpc4j/rpc/OncRpcSvcBuilderTest.java +++ b/oncrpc4j-core/src/test/java/org/dcache/oncrpc4j/rpc/OncRpcSvcBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009 - 2018 Deutsches Elektronen-Synchroton, + * Copyright (c) 2009 - 2024 Deutsches Elektronen-Synchroton, * Member of the Helmholtz Association, (DESY), HAMBURG, GERMANY * * This library is free software; you can redistribute it and/or modify @@ -21,6 +21,8 @@ import org.dcache.oncrpc4j.rpc.OncRpcSvc; import org.dcache.oncrpc4j.rpc.OncRpcSvcBuilder; + +import java.security.NoSuchAlgorithmException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.Test; @@ -28,6 +30,8 @@ import static org.mockito.Mockito.mock; import static org.junit.Assert.*; +import javax.net.ssl.SSLContext; + /** * */ @@ -105,4 +109,12 @@ public void shouldThrowExceptionDefinedWorkerThreadPoolWithExtern() { .build(); } + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionIfSSLContextAndProvidedDefined() throws NoSuchAlgorithmException { + new OncRpcSvcBuilder() + .withTCP() + .withSSLContext(SSLContext.getDefault()) + .withSSLContextProvider(() -> SSLContext.getDefault()) + .build(); + } }