Skip to content

Commit 02588c4

Browse files
committed
Set client auth flags on start
1 parent eec3005 commit 02588c4

9 files changed

Lines changed: 118 additions & 12 deletions

File tree

junit5/src/main/java/io/specto/hoverfly/junit5/HoverflyExtensionUtils.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ static io.specto.hoverfly.junit.core.HoverflyConfig getHoverflyConfigs(HoverflyC
3131
configs = localConfigs()
3232
.caCert(config.caCertPath(), config.caCertKeyPath())
3333
.clientAuth(config.clientCertPath(), config.clientKeyPath(), config.clientAuthDestination())
34+
.clientAuthCaCertPath(config.clientCaCertPath())
3435
.upstreamProxy(config.upstreamProxy())
3536
.logLevel(config.logLevel());
3637
if (config.plainHttpTunneling()) {

junit5/src/main/java/io/specto/hoverfly/junit5/api/HoverflyConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,9 @@
125125
* Destination filter to what target urls to enable mutual TLS authentication.
126126
*/
127127
String[] clientAuthDestination() default {};
128+
129+
/**
130+
* Client CA certificate file in classpath. Must be a PEM encoded certificate, with .crt or .pem extensions
131+
*/
132+
String clientCaCertPath() default "";
128133
}

src/main/java/io/specto/hoverfly/junit/core/Hoverfly.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@
7070
/**
7171
* A wrapper class for the Hoverfly binary. Manage the lifecycle of the processes, and then manage Hoverfly itself by using it's API endpoints.
7272
*/
73-
// TODO extract interface and create LocalHoverfly and RemoteHoverfly
7473
public class Hoverfly implements AutoCloseable {
7574

7675
private static final Logger LOGGER = LoggerFactory.getLogger(Hoverfly.class);
@@ -195,6 +194,27 @@ private void startHoverflyProcess() {
195194
commands.add("-key");
196195
commands.add("ca.key");
197196
}
197+
198+
if (StringUtils.isNotBlank(hoverflyConfig.getClientCertPath())) {
199+
tempFileManager.copyClassPathResource(hoverflyConfig.getClientCertPath(), "client-auth.crt");
200+
commands.add("-client-authentication-client-cert");
201+
commands.add("client-auth.crt");
202+
}
203+
if (StringUtils.isNotBlank(hoverflyConfig.getClientKeyPath())) {
204+
tempFileManager.copyClassPathResource(hoverflyConfig.getClientKeyPath(), "client-auth.key");
205+
commands.add("-client-authentication-client-key");
206+
commands.add("client-auth.key");
207+
}
208+
if (StringUtils.isNotBlank(hoverflyConfig.getClientAuthDestination())) {
209+
commands.add("-client-authentication-destination");
210+
commands.add(hoverflyConfig.getClientAuthDestination());
211+
}
212+
if (StringUtils.isNotBlank(hoverflyConfig.getClientCaCertPath())) {
213+
tempFileManager.copyClassPathResource(hoverflyConfig.getClientCaCertPath(), "client-ca.crt");
214+
commands.add("-client-authentication-ca-cert");
215+
commands.add("client-ca.crt");
216+
}
217+
198218
if (hoverflyConfig.isPlainHttpTunneling()) {
199219
commands.add("-plain-http-tunneling");
200220
}

src/main/java/io/specto/hoverfly/junit/core/SslConfigurer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ void setDefaultSslContext() {
6565
}
6666

6767
/**
68-
* Sets the JVM trust store so Hoverfly's SSL certificate is trusted
68+
* Sets the JVM trust store so Hoverfly's CA certificate is trusted
6969
*/
7070
void setDefaultSslContext(String pemFilename) {
7171
setDefaultSslContext(findResourceOnClasspath(pemFilename));
@@ -82,7 +82,7 @@ private void setDefaultSslContext(URL pemFile) {
8282
SSLContext.setDefault(sslContext);
8383
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
8484
} catch (Exception e) {
85-
throw new IllegalStateException("Failed to set SSLContext from hoverfly certificate " + pemFile.toString(), e);
85+
throw new IllegalStateException("Failed to import Hoverfly certificate '" + pemFile.toString() + "' into keystore", e);
8686
}
8787
}
8888

src/main/java/io/specto/hoverfly/junit/core/config/HoverflyConfiguration.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class HoverflyConfiguration {
4444
private String clientCertPath;
4545
private String clientKeyPath;
4646
private String clientAuthDestination;
47+
private String clientCaCertPath;
4748

4849
/**
4950
* Create configurations for external hoverfly
@@ -322,4 +323,12 @@ public String getClientAuthDestination() {
322323
public void setClientAuthDestination(String clientAuthDestination) {
323324
this.clientAuthDestination = clientAuthDestination;
324325
}
326+
327+
public String getClientCaCertPath() {
328+
return clientCaCertPath;
329+
}
330+
331+
public void setClientCaCertPath(String clientCaCertPath) {
332+
this.clientCaCertPath = clientCaCertPath;
333+
}
325334
}

src/main/java/io/specto/hoverfly/junit/core/config/LocalHoverflyConfig.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@
1414

1515
import io.specto.hoverfly.junit.core.Hoverfly;
1616
import io.specto.hoverfly.junit.core.HoverflyConfig;
17-
import org.slf4j.Logger;
18-
import org.slf4j.LoggerFactory;
19-
2017
import java.net.InetSocketAddress;
2118
import java.util.Arrays;
2219
import java.util.LinkedList;
2320
import java.util.List;
21+
import org.slf4j.Logger;
22+
import org.slf4j.LoggerFactory;
2423

2524
/**
2625
* Config builder interface for settings specific to {@link Hoverfly} managed internally
@@ -40,6 +39,7 @@ public class LocalHoverflyConfig extends HoverflyConfig {
4039
private String clientCertPath;
4140
private String clientKeyPath;
4241
private String clientAuthDestination;
42+
private String clientCaCertPath;
4343

4444
/**
4545
* Sets the certificate file to override the default Hoverfly's CA cert
@@ -177,14 +177,26 @@ public HoverflyConfig binaryLocation(String binaryLocation) {
177177
public LocalHoverflyConfig clientAuth(String clientCertPath, String clientKeyPath, String... destinations) {
178178
this.clientCertPath = clientCertPath;
179179
this.clientKeyPath = clientKeyPath;
180-
if (destinations.length == 0) {
181-
this.clientAuthDestination = ".";
182-
} else {
183-
this.clientAuthDestination = String.join("|", destinations);
180+
if (destinations != null) {
181+
if (destinations.length == 0) {
182+
this.clientAuthDestination = ".";
183+
} else {
184+
this.clientAuthDestination = String.join("|", destinations);
185+
}
184186
}
185187
return this;
186188
}
187189

190+
/**
191+
* Set client CA certificate for mutual TLS authentication
192+
* @param clientCaCertPath CA certificate file in classpath. Must be any PEM encoded certificate, with .crt or .pem extensions
193+
* @return the {@link LocalHoverflyConfig} for further customizations
194+
*/
195+
public LocalHoverflyConfig clientAuthCaCertPath(String clientCaCertPath) {
196+
this.clientCaCertPath = clientCaCertPath;
197+
return this;
198+
}
199+
188200
@Override
189201
public HoverflyConfiguration build() {
190202
HoverflyConfiguration configs = new HoverflyConfiguration(proxyPort, adminPort, proxyLocalHost, destination,
@@ -200,6 +212,7 @@ public HoverflyConfiguration build() {
200212
configs.setClientCertPath(clientCertPath);
201213
configs.setClientKeyPath(clientKeyPath);
202214
configs.setClientAuthDestination(clientAuthDestination);
215+
configs.setClientCaCertPath(clientCaCertPath);
203216
HoverflyConfigValidator validator = new HoverflyConfigValidator();
204217
return validator.validate(configs);
205218
}

src/test/java/io/specto/hoverfly/junit/core/HoverflyConfigTest.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public void shouldHaveDefaultSettings() {
4646
assertThat(configs.getClientCertPath()).isNull();
4747
assertThat(configs.getClientKeyPath()).isNull();
4848
assertThat(configs.getClientAuthDestination()).isNull();
49+
assertThat(configs.getClientCaCertPath()).isNull();
4950
}
5051

5152
@Test
@@ -212,7 +213,7 @@ public void shouldSetClientAuth() {
212213
.build();
213214

214215
assertThat(configs.getClientCertPath()).isEqualTo("ssl/cert.pem");
215-
assertThat(configs.getClientKeyPath()).isEqualTo("ssl/key.pem");
216+
assertThat(configs.getClientCertPath()).isEqualTo("ssl/cert.pem");
216217
assertThat(configs.getClientAuthDestination()).isEqualTo(".");
217218
}
218219

@@ -227,4 +228,13 @@ public void shouldSetClientAuthWithDestinationFilter() {
227228
assertThat(configs.getClientAuthDestination()).isEqualTo("foo.com|bar.com");
228229
}
229230

231+
@Test
232+
public void shouldSetClientAuthCaCert() {
233+
HoverflyConfiguration configs = localConfigs()
234+
.clientAuthCaCertPath("ssl/ca.pem")
235+
.build();
236+
237+
assertThat(configs.getClientCaCertPath()).isEqualTo("ssl/ca.pem");
238+
}
239+
230240
}

src/test/java/io/specto/hoverfly/junit/core/HoverflyTest.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.specto.hoverfly.junit.core.model.RequestFieldMatcher;
1919
import io.specto.hoverfly.junit.core.model.RequestResponsePair;
2020
import io.specto.hoverfly.junit.core.model.Simulation;
21+
import java.util.Optional;
2122
import org.apache.http.HttpResponse;
2223
import org.apache.http.client.HttpClient;
2324
import org.apache.http.client.methods.HttpGet;
@@ -345,7 +346,7 @@ public void shouldSetTrustStoreWhenStartingHoverfly() {
345346
}
346347

347348
@Test
348-
public void shouldNotSetJVMTrustStoreIfSslCertificatePathExists() {
349+
public void shouldConfigSslContextWithCustomCaCert() {
349350
// Given
350351
hoverfly = new Hoverfly(localConfigs().caCert("ssl/ca.crt", "ssl/ca.key"), SIMULATE);
351352
SslConfigurer sslConfigurer = mock(SslConfigurer.class);
@@ -356,6 +357,7 @@ public void shouldNotSetJVMTrustStoreIfSslCertificatePathExists() {
356357

357358
// Then
358359
verify(sslConfigurer, never()).setDefaultSslContext();
360+
verify(sslConfigurer).setDefaultSslContext("ssl/ca.crt");
359361
}
360362

361363
@Test
@@ -421,6 +423,25 @@ public void shouldCopySslCertAndKeyToTempFolderIfPresent () {
421423
verify(tempFileManager).copyClassPathResource("ssl/ca.key", "ca.key");
422424
}
423425

426+
@Test
427+
public void shouldCopyClientCertAndKeyToTempFolderIfPresent () {
428+
// Given
429+
hoverfly = new Hoverfly(localConfigs()
430+
.clientAuth("ssl/ca.crt", "ssl/ca.key")
431+
.clientAuthCaCertPath("ssl/client-ca.crt")
432+
, SIMULATE);
433+
TempFileManager tempFileManager = spy(TempFileManager.class);
434+
Whitebox.setInternalState(hoverfly, "tempFileManager", tempFileManager);
435+
436+
// When
437+
hoverfly.start();
438+
439+
// Then
440+
verify(tempFileManager).copyClassPathResource("ssl/ca.crt", "client-auth.crt");
441+
verify(tempFileManager).copyClassPathResource("ssl/ca.key", "client-auth.key");
442+
verify(tempFileManager).copyClassPathResource("ssl/client-ca.crt", "client-ca.crt");
443+
}
444+
424445
@Test
425446
public void shouldCopyMiddlewareScriptToTempFolderIfLocalMiddlewareEnabled () {
426447
String rawFilename = "middleware.py";
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEnjCCA4agAwIBAgIJAPvHolsYpVwPMA0GCSqGSIb3DQEBBQUAMIGQMQswCQYD
3+
VQQGEwJHQjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xEzARBgNV
4+
BAoTClNwZWN0b0xhYnMxETAPBgNVBAsTCGhvdmVyZmx5MRIwEAYDVQQDEwlzcGVj
5+
dG8uaW8xIzAhBgkqhkiG9w0BCQEWFHRvbW15LnNpdHVAc3BlY3RvLmlvMB4XDTE3
6+
MDIxNzExMzIwMVoXDTE4MDIxNzExMzIwMVowgZAxCzAJBgNVBAYTAkdCMQ8wDQYD
7+
VQQIEwZMb25kb24xDzANBgNVBAcTBkxvbmRvbjETMBEGA1UEChMKU3BlY3RvTGFi
8+
czERMA8GA1UECxMIaG92ZXJmbHkxEjAQBgNVBAMTCXNwZWN0by5pbzEjMCEGCSqG
9+
SIb3DQEJARYUdG9tbXkuc2l0dUBzcGVjdG8uaW8wggEiMA0GCSqGSIb3DQEBAQUA
10+
A4IBDwAwggEKAoIBAQDPiXcHyFBOQawvsRwet9WVa4pjvywsCFFa2e2tGPLCSxvv
11+
P4e2Z4M7sDna9/MP8zwKVmWf9W8BZG2XcyVvHRxDbsFvMZa5xLmXMv1rk5zZpub9
12+
2eRNtj6v4kOFFbak5hKHcaGtW8m/MjyPMWkEhsqSeqInXE+pE4dK7tJ9J9SecoCI
13+
tnPk6jsj5r5wdG1ub4tWsTAS0TOHGZ6ArnQ7NXrxT+RvrQvqT2Uw1BCIgkBUfH+8
14+
gHsg46klR0y4X7ooUf4/91eaOnBdVe5w7kAu+dS+B2mBN/mS0nYvWw0RW0F7TJhH
15+
4FFuqXDiZC+dv862ywrYqyrvsCmkSnyinDUPddUHAgMBAAGjgfgwgfUwHQYDVR0O
16+
BBYEFEn/byeOo+ZOdKTdspEjVlNqf8xZMIHFBgNVHSMEgb0wgbqAFEn/byeOo+ZO
17+
dKTdspEjVlNqf8xZoYGWpIGTMIGQMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9u
18+
ZG9uMQ8wDQYDVQQHEwZMb25kb24xEzARBgNVBAoTClNwZWN0b0xhYnMxETAPBgNV
19+
BAsTCGhvdmVyZmx5MRIwEAYDVQQDEwlzcGVjdG8uaW8xIzAhBgkqhkiG9w0BCQEW
20+
FHRvbW15LnNpdHVAc3BlY3RvLmlvggkA+8eiWxilXA8wDAYDVR0TBAUwAwEB/zAN
21+
BgkqhkiG9w0BAQUFAAOCAQEAMVMQMws5q3HC/KGwVu7kNUKdqHOslklWpMTym5ii
22+
v2Z/1rReXvHszhoywwkP1pYt96XrzCYO2Th2JLMLpTihTJN7wrzBGeZZoGSxEWVW
23+
bop2y/qljb3KT3hN2NpTHd5gnO1udNp3OjxzHYKyxiTCV7fnoPAC1Lmn1r5QWLxz
24+
jfSroSb7h14dNjIgvq2H6rYopivdgAqea6CuElwvQioLIHaF2r0YKE0s4FXuiIBd
25+
wpAeGmwAQALiwuGUZqe1+piBdqGf3a1sqrdgGdQTTEPc6c04tMYFEL0SWATfQZQ4
26+
JosAQYC3eb6ltfPtZWGVdtCcpQtNEjjNBXOOniEQvcbBcQ==
27+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)