Skip to content

Commit 6316494

Browse files
authored
Add reload listener to SslProfile (#135244)
This change allows components and plugins that are dependent on a `SslProfile` to be notified when that profile is reloaded. This is useful if they have objects (e.g. http clients) that have been built around the profile's runtime objects (trust manager, etc) and they need to cascade the reload downwards.
1 parent 768fc6e commit 6316494

File tree

9 files changed

+79
-12
lines changed

9 files changed

+79
-12
lines changed

docs/changelog/135244.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 135244
2+
summary: Add reload listener to `SslProfile`
3+
area: TLS
4+
type: enhancement
5+
issues: []

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLService.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import java.util.Optional;
5858
import java.util.Set;
5959
import java.util.concurrent.atomic.AtomicInteger;
60+
import java.util.function.Consumer;
6061
import java.util.function.Function;
6162
import java.util.function.Predicate;
6263
import java.util.function.Supplier;
@@ -772,7 +773,7 @@ final class SSLContextHolder implements SslProfile {
772773
private final SslKeyConfig keyConfig;
773774
private final SslTrustConfig trustConfig;
774775
private final SslConfiguration sslConfiguration;
775-
private final List<Runnable> reloadListeners;
776+
private final List<Consumer<? super SSLContextHolder>> reloadListeners;
776777

777778
SSLContextHolder(SSLContext context, SslConfiguration sslConfiguration) {
778779
this.context = context;
@@ -799,7 +800,7 @@ public SSLSocketFactory socketFactory() {
799800
sslConfiguration.supportedProtocols().toArray(Strings.EMPTY_ARRAY),
800801
supportedCiphers(socketFactory.getSupportedCipherSuites(), sslConfiguration.getCipherSuites(), false)
801802
);
802-
this.addReloadListener(securitySSLSocketFactory::reload);
803+
this.addReloadListener(profile -> securitySSLSocketFactory.reload());
803804
return securitySSLSocketFactory;
804805
}
805806

@@ -850,11 +851,16 @@ public SSLEngine engine(String host, int port) {
850851
return sslEngine;
851852
}
852853

854+
@Override
855+
public void addReloadListener(Consumer<SslProfile> listener) {
856+
this.reloadListeners.add(listener);
857+
}
858+
853859
synchronized void reload() {
854860
invalidateSessions(context.getClientSessionContext());
855861
invalidateSessions(context.getServerSessionContext());
856862
reloadSslContext();
857-
this.reloadListeners.forEach(Runnable::run);
863+
this.reloadListeners.forEach(l -> l.accept(this));
858864
}
859865

860866
private void reloadSslContext() {
@@ -875,10 +881,6 @@ private void reloadSslContext() {
875881
throw new ElasticsearchException("failed to initialize the SSLContext", e);
876882
}
877883
}
878-
879-
public void addReloadListener(Runnable listener) {
880-
this.reloadListeners.add(listener);
881-
}
882884
}
883885

884886
/**

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SslProfile.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
1313
import org.elasticsearch.common.ssl.SslConfiguration;
1414

15+
import java.util.function.Consumer;
16+
1517
import javax.net.ssl.HostnameVerifier;
1618
import javax.net.ssl.SSLContext;
1719
import javax.net.ssl.SSLEngine;
@@ -49,4 +51,10 @@ public interface SslProfile {
4951
TlsStrategy clientTlsStrategy();
5052

5153
SSLEngine engine(String host, int port);
54+
55+
/**
56+
* Add a listener that is called when this profile is reloaded (for example, because one of the {@link #configuration() configuration's}
57+
* {@link SslConfiguration#getDependentFiles() dependent files} is modified.
58+
*/
59+
void addReloadListener(Consumer<SslProfile> listener);
5260
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationReloaderTests.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import java.util.concurrent.CountDownLatch;
7474
import java.util.concurrent.CyclicBarrier;
7575
import java.util.concurrent.TimeUnit;
76+
import java.util.concurrent.atomic.AtomicBoolean;
7677
import java.util.concurrent.atomic.AtomicReference;
7778
import java.util.function.Consumer;
7879

@@ -84,6 +85,7 @@
8485
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
8586
import static org.hamcrest.Matchers.containsInAnyOrder;
8687
import static org.hamcrest.Matchers.containsString;
88+
import static org.hamcrest.Matchers.is;
8789
import static org.hamcrest.Matchers.sameInstance;
8890

8991
/**
@@ -629,7 +631,12 @@ private void validateSSLConfigurationIsReloaded(
629631
) throws Exception {
630632
final CyclicBarrier reloadBarrier = new CyclicBarrier(2);
631633
final SSLService sslService = new SSLService(env);
632-
final SslConfiguration config = sslService.getSSLConfiguration("xpack.security.transport.ssl");
634+
final SslProfile profile = sslService.profile("xpack.security.transport.ssl");
635+
final AtomicBoolean profileReloaded = new AtomicBoolean(false);
636+
profile.addReloadListener(p -> {
637+
assertThat(p, sameInstance(profile));
638+
profileReloaded.set(true);
639+
});
633640
final Consumer<SslConfiguration> reloadConsumer = sslConfiguration -> {
634641
try {
635642
sslService.reloadSSLContext(sslConfiguration);
@@ -643,7 +650,7 @@ private void validateSSLConfigurationIsReloaded(
643650
};
644651
new SSLConfigurationReloader(reloadConsumer, resourceWatcherService, SSLService.getSSLConfigurations(env, List.of()));
645652
// Baseline checks
646-
preChecks.accept(sslService.sslContextHolder(config).sslContext());
653+
preChecks.accept(sslService.sslContextHolder(profile.configuration()).sslContext());
647654

648655
assertEquals("nothing should have called reload", 0, reloadBarrier.getNumberWaiting());
649656

@@ -655,7 +662,8 @@ private void validateSSLConfigurationIsReloaded(
655662
}
656663

657664
// checks after reload
658-
postChecks.accept(sslService.sslContextHolder(config).sslContext());
665+
postChecks.accept(sslService.sslContextHolder(profile.configuration()).sslContext());
666+
assertThat(profileReloaded.get(), is(true));
659667
}
660668

661669
private static void atomicMoveIfPossible(Path source, Path target) throws IOException {

x-pack/plugin/security/qa/ssl-extension/src/javaRestTest/java/org/elasticsearch/xpack/core/ssl/extension/SslProfileExtensionIT.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,20 @@
99

1010
import org.elasticsearch.client.Request;
1111
import org.elasticsearch.client.Response;
12+
import org.elasticsearch.common.io.Streams;
1213
import org.elasticsearch.common.settings.SecureString;
1314
import org.elasticsearch.common.settings.Settings;
1415
import org.elasticsearch.common.util.concurrent.ThreadContext;
1516
import org.elasticsearch.test.cluster.ElasticsearchCluster;
17+
import org.elasticsearch.test.cluster.LogType;
1618
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
19+
import org.elasticsearch.test.cluster.util.resource.MutableResource;
1720
import org.elasticsearch.test.cluster.util.resource.Resource;
1821
import org.elasticsearch.test.rest.ESRestTestCase;
1922
import org.hamcrest.Matchers;
2023
import org.junit.ClassRule;
2124

25+
import java.io.InputStream;
2226
import java.util.List;
2327
import java.util.Map;
2428

@@ -28,11 +32,13 @@
2832

2933
public class SslProfileExtensionIT extends ESRestTestCase {
3034

35+
private static final MutableResource caFile = MutableResource.from(Resource.fromClasspath("ca1.crt"));
36+
3137
@ClassRule
3238
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
3339
.distribution(DistributionType.INTEG_TEST)
3440
.plugin("test-ssl-extension")
35-
.configFile("test.ssl.ca.crt", Resource.fromClasspath("ca.crt"))
41+
.configFile("test.ssl.ca.crt", caFile)
3642
.setting("test.ssl.certificate_authorities", "test.ssl.ca.crt")
3743
.setting("xpack.security.enabled", "true")
3844
.user("admin", "pass/word")
@@ -57,4 +63,14 @@ public void testCertificateIsLoaded() throws Exception {
5763
assertThat(certs, hasItem(hasEntry("path", "test.ssl.ca.crt")));
5864
}
5965

66+
public void testCertificateReload() throws Exception {
67+
caFile.update(Resource.fromClasspath("ca2.crt"));
68+
assertBusy(() -> {
69+
try (InputStream log = cluster.getNodeLog(0, LogType.SERVER_JSON)) {
70+
final List<String> logLines = Streams.readAllLines(log);
71+
assertThat(logLines, hasItem(Matchers.containsString("TEST SSL PROFILE RELOADED [test.ssl]")));
72+
}
73+
});
74+
}
75+
6076
}
File renamed without changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDqzCCApOgAwIBAgIUIxeD5zWFBndCDsdJCxCUJlf4bGMwDQYJKoZIhvcNAQEL
3+
BQAwXTETMBEGCgmSJomT8ixkARkWA29yZzEdMBsGCgmSJomT8ixkARkWDWVsYXN0
4+
aWNzZWFyY2gxDTALBgNVBAsTBHRlc3QxGDAWBgNVBAMTD3NzbC1wcm9maWxlLWFs
5+
dDAeFw0yNTA5MjMwMjE5MThaFw0yODA5MjIwMjE5MThaMF0xEzARBgoJkiaJk/Is
6+
ZAEZFgNvcmcxHTAbBgoJkiaJk/IsZAEZFg1lbGFzdGljc2VhcmNoMQ0wCwYDVQQL
7+
EwR0ZXN0MRgwFgYDVQQDEw9zc2wtcHJvZmlsZS1hbHQwggEiMA0GCSqGSIb3DQEB
8+
AQUAA4IBDwAwggEKAoIBAQCkbUzJoaxX9BbKfPiZsY2CyjPcI+A3FbP5qhSFC3hO
9+
HdEamn+GWCbzN3PUupWy/i0bEFA8WTOfPmO4/Td8V0RrNg0ONC/TkqShz9/39Vcp
10+
TnMlPuaIAQDjSZ0a8KcJMCfde3sutjK7pD+DHYaQasJ2wXZlhWB/BSrwzAdBzNBz
11+
Ca7+aKeYoXNk/QlA3bm3isFWjI8IVtX3L6o1XwHM4haJxgaUkdZBVe8So3OSQbft
12+
GXhaxKZkj3kWGwSY3Bp8/GuYYEisDGK6JNc2Mla9oYKJsp3sTmJoxMZZ0a9uEsWf
13+
JodUxGyZr7TUbZ6focnXcN2ikFbO/kfKq9uDvCAvCyZzAgMBAAGjYzBhMB0GA1Ud
14+
DgQWBBQytaNb2xfEtJaew29FP73J2WiYrzAfBgNVHSMEGDAWgBQytaNb2xfEtJae
15+
w29FP73J2WiYrzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkq
16+
hkiG9w0BAQsFAAOCAQEACrsvGa+fgXlQ5NbFv2AYKwAlPgssZpIJaoL0A1F7S42K
17+
p8KZ0TYIwtFEVhNmjwyR9pVVfk00Wjmiab9jouPGIq8Lg3VP6w3DQoYYthXymdGW
18+
4f+mZ0OP4Djq4ReKf05SUdmd4BndKPc1rVNk+qyrRNzIJEuHyINYVyiXxmyuFcP5
19+
5VRh1KLDe8Xn2TeZw6S0ZHPgwReg3JaGrLauJo8LOk3X8y9/f/u7c4XZkAHTjHyE
20+
L5JSzNFiJ2bzOz+t6WRE196QBZGcxI0TZP/MebGlC5q4nCha48Akp35IkKJQMo9L
21+
ko97anazKRarty3blQhONWQXA/6gxTJHDwXxBydYZQ==
22+
-----END CERTIFICATE-----

x-pack/plugin/security/qa/ssl-extension/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module org.elasticsearch.internal.ssl {
55
requires org.elasticsearch.server;
66
requires org.elasticsearch.xcore;
7+
requires org.elasticsearch.logging;
78

89
provides SslProfileExtension with TestSslProfile;
910
}

x-pack/plugin/security/qa/ssl-extension/src/main/java/org/elasticsearch/test/xpack/core/ssl/extension/TestSslProfile.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,24 @@
77

88
package org.elasticsearch.test.xpack.core.ssl.extension;
99

10+
import org.elasticsearch.logging.LogManager;
11+
import org.elasticsearch.logging.Logger;
1012
import org.elasticsearch.xpack.core.ssl.SslProfile;
1113
import org.elasticsearch.xpack.core.ssl.extension.SslProfileExtension;
1214

1315
import java.util.Set;
1416

1517
public class TestSslProfile implements SslProfileExtension {
18+
19+
private final Logger logger = LogManager.getLogger(getClass());
20+
1621
@Override
1722
public Set<String> getSettingPrefixes() {
1823
return Set.of("test.ssl");
1924
}
2025

2126
@Override
2227
public void applyProfile(String prefix, SslProfile profile) {
23-
// no-op
28+
profile.addReloadListener(p -> logger.info("TEST SSL PROFILE RELOADED [{}] [{}]", prefix, p));
2429
}
2530
}

0 commit comments

Comments
 (0)