Skip to content

Commit de93989

Browse files
authored
Reintroduces SSL tests (#30947)
These tests were removed when backporting a PR to 6.x. The PR itself was regarding moving security tests from plugin/scr and did not originally touch these files.
1 parent f165a1b commit de93989

File tree

3 files changed

+572
-0
lines changed

3 files changed

+572
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.ssl;
7+
8+
import org.apache.http.conn.ssl.NoopHostnameVerifier;
9+
import org.apache.http.message.BasicHeader;
10+
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
11+
import org.apache.http.ssl.SSLContexts;
12+
import org.apache.http.util.EntityUtils;
13+
import org.elasticsearch.ElasticsearchException;
14+
import org.elasticsearch.ExceptionsHelper;
15+
import org.elasticsearch.client.Response;
16+
import org.elasticsearch.client.RestClient;
17+
import org.elasticsearch.client.transport.TransportClient;
18+
import org.elasticsearch.common.network.NetworkModule;
19+
import org.elasticsearch.common.settings.MockSecureSettings;
20+
import org.elasticsearch.common.settings.Settings;
21+
import org.elasticsearch.common.transport.TransportAddress;
22+
import org.elasticsearch.test.SecurityIntegTestCase;
23+
import org.elasticsearch.transport.Transport;
24+
import org.elasticsearch.xpack.core.TestXPackTransportClient;
25+
import org.elasticsearch.xpack.core.security.SecurityField;
26+
import org.elasticsearch.xpack.core.ssl.SSLClientAuth;
27+
import org.elasticsearch.xpack.security.LocalStateSecurity;
28+
29+
30+
import javax.net.ssl.KeyManagerFactory;
31+
import javax.net.ssl.SSLContext;
32+
import javax.net.ssl.SSLHandshakeException;
33+
import javax.net.ssl.TrustManagerFactory;
34+
import java.io.IOException;
35+
import java.io.InputStream;
36+
import java.nio.file.Files;
37+
import java.nio.file.Path;
38+
import java.security.KeyStore;
39+
import java.security.SecureRandom;
40+
import java.security.cert.CertPathBuilderException;
41+
42+
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
43+
import static org.hamcrest.Matchers.containsString;
44+
import static org.hamcrest.Matchers.equalTo;
45+
import static org.hamcrest.Matchers.instanceOf;
46+
47+
public class SSLClientAuthTests extends SecurityIntegTestCase {
48+
@Override
49+
protected Settings nodeSettings(int nodeOrdinal) {
50+
return Settings.builder()
51+
.put(super.nodeSettings(nodeOrdinal))
52+
// invert the require auth settings
53+
.put("xpack.ssl.client_authentication", SSLClientAuth.REQUIRED)
54+
.put("xpack.security.http.ssl.enabled", true)
55+
.put("xpack.security.http.ssl.client_authentication", SSLClientAuth.REQUIRED)
56+
.put("transport.profiles.default.xpack.security.ssl.client_authentication", SSLClientAuth.NONE)
57+
.put(NetworkModule.HTTP_ENABLED.getKey(), true)
58+
.build();
59+
}
60+
61+
@Override
62+
protected boolean transportSSLEnabled() {
63+
return true;
64+
}
65+
66+
public void testThatHttpFailsWithoutSslClientAuth() throws IOException {
67+
SSLIOSessionStrategy sessionStrategy = new SSLIOSessionStrategy(SSLContexts.createDefault(), NoopHostnameVerifier.INSTANCE);
68+
try (RestClient restClient = createRestClient(httpClientBuilder -> httpClientBuilder.setSSLStrategy(sessionStrategy), "https")) {
69+
restClient.performRequest("GET", "/");
70+
fail("Expected SSLHandshakeException");
71+
} catch (SSLHandshakeException e) {
72+
Throwable t = ExceptionsHelper.unwrap(e, CertPathBuilderException.class);
73+
assertThat(t, instanceOf(CertPathBuilderException.class));
74+
assertThat(t.getMessage(), containsString("unable to find valid certification path to requested target"));
75+
}
76+
}
77+
78+
public void testThatHttpWorksWithSslClientAuth() throws IOException {
79+
SSLIOSessionStrategy sessionStrategy = new SSLIOSessionStrategy(getSSLContext(), NoopHostnameVerifier.INSTANCE);
80+
try (RestClient restClient = createRestClient(httpClientBuilder -> httpClientBuilder.setSSLStrategy(sessionStrategy), "https")) {
81+
Response response = restClient.performRequest("GET", "/",
82+
new BasicHeader("Authorization", basicAuthHeaderValue(transportClientUsername(), transportClientPassword())));
83+
assertThat(response.getStatusLine().getStatusCode(), equalTo(200));
84+
assertThat(EntityUtils.toString(response.getEntity()), containsString("You Know, for Search"));
85+
}
86+
}
87+
88+
public void testThatTransportWorksWithoutSslClientAuth() throws IOException {
89+
// specify an arbitrary keystore, that does not include the certs needed to connect to the transport protocol
90+
Path store = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient-client-profile.jks");
91+
92+
if (Files.notExists(store)) {
93+
throw new ElasticsearchException("store path doesn't exist");
94+
}
95+
96+
MockSecureSettings secureSettings = new MockSecureSettings();
97+
secureSettings.setString("xpack.ssl.keystore.secure_password", "testclient-client-profile");
98+
Settings settings = Settings.builder()
99+
.put("xpack.security.transport.ssl.enabled", true)
100+
.put("xpack.ssl.client_authentication", SSLClientAuth.NONE)
101+
.put("xpack.ssl.keystore.path", store)
102+
.setSecureSettings(secureSettings)
103+
.put("cluster.name", internalCluster().getClusterName())
104+
.put(SecurityField.USER_SETTING.getKey(),
105+
transportClientUsername() + ":" + new String(transportClientPassword().getChars()))
106+
.build();
107+
try (TransportClient client = new TestXPackTransportClient(settings, LocalStateSecurity.class)) {
108+
Transport transport = internalCluster().getDataNodeInstance(Transport.class);
109+
TransportAddress transportAddress = transport.boundAddress().publishAddress();
110+
client.addTransportAddress(transportAddress);
111+
112+
assertGreenClusterState(client);
113+
}
114+
}
115+
116+
private SSLContext getSSLContext() {
117+
try (InputStream in =
118+
Files.newInputStream(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.jks"))) {
119+
KeyStore keyStore = KeyStore.getInstance("jks");
120+
keyStore.load(in, "testclient".toCharArray());
121+
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
122+
tmf.init(keyStore);
123+
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
124+
kmf.init(keyStore, "testclient".toCharArray());
125+
SSLContext context = SSLContext.getInstance("TLSv1.2");
126+
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
127+
return context;
128+
} catch (Exception e) {
129+
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
130+
}
131+
}
132+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.ssl;
7+
8+
import org.bouncycastle.asn1.x500.X500Name;
9+
import org.bouncycastle.asn1.x509.Extension;
10+
import org.bouncycastle.asn1.x509.Time;
11+
import org.bouncycastle.cert.X509CertificateHolder;
12+
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
13+
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
14+
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
15+
import org.bouncycastle.operator.ContentSigner;
16+
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
17+
import org.elasticsearch.ElasticsearchException;
18+
import org.elasticsearch.common.network.InetAddressHelper;
19+
import org.elasticsearch.common.settings.MockSecureSettings;
20+
import org.elasticsearch.common.settings.Settings;
21+
import org.elasticsearch.common.transport.TransportAddress;
22+
import org.elasticsearch.common.util.set.Sets;
23+
import org.elasticsearch.env.TestEnvironment;
24+
import org.elasticsearch.test.SecurityIntegTestCase;
25+
import org.elasticsearch.test.SecuritySettingsSource;
26+
import org.elasticsearch.test.SecuritySettingsSourceField;
27+
import org.elasticsearch.transport.Transport;
28+
import org.elasticsearch.xpack.core.ssl.CertUtils;
29+
import org.elasticsearch.xpack.core.ssl.SSLService;
30+
import org.joda.time.DateTime;
31+
import org.joda.time.DateTimeZone;
32+
33+
import javax.net.ssl.SSLHandshakeException;
34+
import javax.net.ssl.SSLSocket;
35+
import javax.net.ssl.SSLSocketFactory;
36+
import java.io.IOException;
37+
import java.io.InputStream;
38+
import java.io.OutputStream;
39+
import java.net.SocketException;
40+
import java.nio.file.AtomicMoveNotSupportedException;
41+
import java.nio.file.Files;
42+
import java.nio.file.Path;
43+
import java.nio.file.StandardCopyOption;
44+
import java.security.KeyPair;
45+
import java.security.KeyStore;
46+
import java.security.cert.Certificate;
47+
import java.security.cert.X509Certificate;
48+
import java.util.Locale;
49+
import java.util.concurrent.CountDownLatch;
50+
51+
import static org.hamcrest.Matchers.containsString;
52+
import static org.hamcrest.Matchers.is;
53+
54+
/**
55+
* Integration tests for SSL reloading support
56+
*/
57+
public class SSLReloadIntegTests extends SecurityIntegTestCase {
58+
59+
private Path nodeStorePath;
60+
61+
@Override
62+
public Settings nodeSettings(int nodeOrdinal) {
63+
if (nodeStorePath == null) {
64+
Path origPath = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks");
65+
Path tempDir = createTempDir();
66+
nodeStorePath = tempDir.resolve("testnode.jks");
67+
try {
68+
Files.copy(origPath, nodeStorePath);
69+
} catch (IOException e) {
70+
throw new ElasticsearchException("failed to copy keystore");
71+
}
72+
}
73+
Settings settings = super.nodeSettings(nodeOrdinal);
74+
Settings.Builder builder = Settings.builder()
75+
.put(settings.filter((s) -> s.startsWith("xpack.ssl.") == false));
76+
77+
78+
SecuritySettingsSource.addSSLSettingsForStore(builder,
79+
"/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks", "testnode");
80+
builder.put("resource.reload.interval.high", "1s")
81+
.put("xpack.ssl.keystore.path", nodeStorePath);
82+
83+
if (builder.get("xpack.ssl.truststore.path") != null) {
84+
builder.put("xpack.ssl.truststore.path", nodeStorePath);
85+
}
86+
87+
return builder.build();
88+
}
89+
90+
@Override
91+
protected boolean transportSSLEnabled() {
92+
return true;
93+
}
94+
95+
public void testThatSSLConfigurationReloadsOnModification() throws Exception {
96+
KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048));
97+
X509Certificate certificate = getCertificate(keyPair);
98+
KeyStore keyStore = KeyStore.getInstance("jks");
99+
keyStore.load(null, null);
100+
keyStore.setKeyEntry("key", keyPair.getPrivate(), SecuritySettingsSourceField.TEST_PASSWORD.toCharArray(),
101+
new Certificate[] { certificate });
102+
Path keystorePath = createTempDir().resolve("newcert.jks");
103+
try (OutputStream out = Files.newOutputStream(keystorePath)) {
104+
keyStore.store(out, SecuritySettingsSourceField.TEST_PASSWORD.toCharArray());
105+
}
106+
MockSecureSettings secureSettings = new MockSecureSettings();
107+
secureSettings.setString("xpack.ssl.keystore.secure_password", SecuritySettingsSourceField.TEST_PASSWORD);
108+
secureSettings.setString("xpack.ssl.truststore.secure_password", "testnode");
109+
Settings settings = Settings.builder()
110+
.put("path.home", createTempDir())
111+
.put("xpack.ssl.keystore.path", keystorePath)
112+
.put("xpack.ssl.truststore.path", nodeStorePath)
113+
.setSecureSettings(secureSettings)
114+
.build();
115+
String node = randomFrom(internalCluster().getNodeNames());
116+
SSLService sslService = new SSLService(settings, TestEnvironment.newEnvironment(settings));
117+
SSLSocketFactory sslSocketFactory = sslService.sslSocketFactory(settings);
118+
TransportAddress address = internalCluster()
119+
.getInstance(Transport.class, node).boundAddress().publishAddress();
120+
try (SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(address.getAddress(), address.getPort())) {
121+
assertThat(socket.isConnected(), is(true));
122+
socket.startHandshake();
123+
fail("handshake should not have been successful!");
124+
} catch (SSLHandshakeException | SocketException expected) {
125+
logger.trace("expected exception", expected);
126+
}
127+
128+
KeyStore nodeStore = KeyStore.getInstance("jks");
129+
try (InputStream in = Files.newInputStream(nodeStorePath)) {
130+
nodeStore.load(in, "testnode".toCharArray());
131+
}
132+
nodeStore.setCertificateEntry("newcert", certificate);
133+
Path path = nodeStorePath.getParent().resolve("updated.jks");
134+
try (OutputStream out = Files.newOutputStream(path)) {
135+
nodeStore.store(out, "testnode".toCharArray());
136+
}
137+
try {
138+
Files.move(path, nodeStorePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
139+
} catch (AtomicMoveNotSupportedException e) {
140+
Files.move(path, nodeStorePath, StandardCopyOption.REPLACE_EXISTING);
141+
}
142+
143+
CountDownLatch latch = new CountDownLatch(1);
144+
assertBusy(() -> {
145+
try (SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(address.getAddress(), address.getPort())) {
146+
logger.info("opened socket for reloading [{}]", socket);
147+
socket.addHandshakeCompletedListener(event -> {
148+
try {
149+
assertThat(event.getPeerPrincipal().getName(), containsString("Test Node"));
150+
logger.info("ssl handshake completed on port [{}]", event.getSocket().getLocalPort());
151+
latch.countDown();
152+
} catch (Exception e) {
153+
fail("caught exception in listener " + e.getMessage());
154+
}
155+
});
156+
socket.startHandshake();
157+
158+
} catch (Exception e) {
159+
fail("caught exception " + e.getMessage());
160+
}
161+
});
162+
latch.await();
163+
}
164+
165+
private X509Certificate getCertificate(KeyPair keyPair) throws Exception {
166+
final DateTime notBefore = new DateTime(DateTimeZone.UTC);
167+
final DateTime notAfter = notBefore.plusYears(1);
168+
X500Name subject = new X500Name("CN=random cert");
169+
JcaX509v3CertificateBuilder builder =
170+
new JcaX509v3CertificateBuilder(subject, CertUtils.getSerial(),
171+
new Time(notBefore.toDate(), Locale.ROOT), new Time(notAfter.toDate(), Locale.ROOT), subject, keyPair.getPublic());
172+
173+
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
174+
builder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(keyPair.getPublic()));
175+
builder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(keyPair.getPublic()));
176+
builder.addExtension(Extension.subjectAlternativeName, false,
177+
CertUtils.getSubjectAlternativeNames(true, Sets.newHashSet(InetAddressHelper.getAllAddresses())));
178+
179+
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate());
180+
X509CertificateHolder certificateHolder = builder.build(signer);
181+
return new JcaX509CertificateConverter().getCertificate(certificateHolder);
182+
}
183+
}

0 commit comments

Comments
 (0)