diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java b/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java index d25973fa4..ec25f6538 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java @@ -398,7 +398,7 @@ private SslContext bootSslContext(NetworkListenerConfiguration listener, SSLCert try { // Try to find certificate data on db byte[] keystoreContent = parent.getDynamicCertificatesManager().getCertificateForDomain(certificate.getId()); - KeyStore keystore; + final KeyStore keystore; if (keystoreContent != null) { LOG.debug("start SSL with dynamic certificate id {}, on listener {}:{}", certificate.getId(), listener.getHost(), port); keystore = loadKeyStoreData(keystoreContent, certificate.getPassword()); diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/certificates/DynamicCertificatesManager.java b/carapace-server/src/main/java/org/carapaceproxy/server/certificates/DynamicCertificatesManager.java index ee0c938ac..0d165e91b 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/certificates/DynamicCertificatesManager.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/certificates/DynamicCertificatesManager.java @@ -606,7 +606,7 @@ private RuntimeServerConfiguration getConfig() { * @return PKCS12 Keystore content */ public byte[] getCertificateForDomain(String domain) { - CertificateData cert = certificates.get(domain); // certs always retrived from cache + CertificateData cert = certificates.get(domain); // certs always retrieved from cache if (cert == null || cert.getKeystoreData() == null || cert.getKeystoreData().length == 0) { LOG.error("No dynamic certificate available for domain {}", domain); return null; diff --git a/carapace-server/src/test/java/org/carapaceproxy/ApplyConfigurationTest.java b/carapace-server/src/test/java/org/carapaceproxy/ApplyConfigurationTest.java index 40f9af3ae..e49458a39 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/ApplyConfigurationTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/ApplyConfigurationTest.java @@ -23,18 +23,35 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.anEmptyMap; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import com.github.tomakehurst.wiremock.junit.WireMockRule; import java.io.IOException; import java.net.URI; -import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.Map; import java.util.Properties; -import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; import org.carapaceproxy.configstore.PropertiesConfigurationStore; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.server.config.ConfigurationChangeInProgressException; @@ -46,6 +63,7 @@ import org.carapaceproxy.user.UserRealm; import org.carapaceproxy.utils.TestEndpointMapper; import org.carapaceproxy.utils.TestUserRealm; +import org.carapaceproxy.utils.TestUtils; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; @@ -53,13 +71,14 @@ import org.junit.rules.TemporaryFolder; /** - * * @author enrico.olivelli */ public class ApplyConfigurationTest { @ClassRule public static WireMockRule wireMockRule = new WireMockRule(0); + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); @BeforeClass public static void setupWireMock() { @@ -73,236 +92,197 @@ public static void setupWireMock() { } - @Rule - public TemporaryFolder tmpDir = new TemporaryFolder(); - - /** - * Static mapper, so that it can be references by configuration - */ - public static final class StaticEndpointMapper extends TestEndpointMapper { - - public StaticEndpointMapper(final HttpProxyServer ignoredServer) { - this(); // required for reflective construction - } - - public StaticEndpointMapper() { - super("localhost", wireMockRule.port()); - } + private static CloseableHttpClient createHttpClientWithDisabledSSLValidation() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + return HttpClients.custom() + .setSSLContext(SSLContextBuilder.create() + .loadTrustMaterial((chain, authType) -> true) // Trust all certificates + .build()) + .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) // Disable hostname verification + .build(); } @Test public void testChangeListenersConfig() throws Exception { - try (HttpProxyServer server = new HttpProxyServer(StandardEndpointMapper::new, tmpDir.newFolder());) { - - { - Properties configuration = new Properties(); - configuration.put("mapper.class", StaticEndpointMapper.class.getName()); - configuration.put("aws.accesskey", "accesskey"); - configuration.put("aws.secretkey", "secretkey"); - server.configureAtBoot(new PropertiesConfigurationStore(configuration)); - } + server.configureAtBoot(new PropertiesConfigurationStore(propsWithMapper(Map.of( + "aws.accesskey", "accesskey", + "aws.secretkey", "secretkey" + )))); // start without listeners server.start(); // start two listeners - { - Properties configuration = new Properties(); - configuration.put("mapper.class", StaticEndpointMapper.class.getName()); - configuration.put("listener.1.host", "localhost"); - configuration.put("listener.1.port", "1423"); - configuration.put("listener.2.host", "localhost"); - configuration.put("listener.2.port", "1426"); - reloadConfiguration(configuration, server); - } + reloadConfiguration(server, propsWithMapper(Map.of( + "listener.1.host", "localhost", + "listener.1.port", "1423", + "listener.2.host", "localhost", + "listener.2.port", "1426" + ))); testIt(1423, true); testIt(1426, true); // restart listener 1 - { - Properties configuration = new Properties(); - configuration.put("mapper.class", StaticEndpointMapper.class.getName()); - configuration.put("listener.1.host", "localhost"); - configuration.put("listener.1.port", "1425"); - configuration.put("listener.2.host", "localhost"); - configuration.put("listener.2.port", "1426"); - reloadConfiguration(configuration, server); - } + reloadConfiguration(server, propsWithMapper(Map.of( + "listener.1.host", "localhost", + "listener.1.port", "1425", + "listener.2.host", "localhost", + "listener.2.port", "1426" + ))); testIt(1425, true); testIt(1426, true); // stop listener 2 - { - Properties configuration = new Properties(); - configuration.put("mapper.class", StaticEndpointMapper.class.getName()); - configuration.put("listener.1.host", "localhost"); - configuration.put("listener.1.port", "1425"); - reloadConfiguration(configuration, server); - } + reloadConfiguration(server, propsWithMapper(Map.of( + "listener.1.host", "localhost", + "listener.1.port", "1425" + ))); testIt(1425, true); testIt(1426, false); - // restart listerer 2 - { - Properties configuration = new Properties(); - configuration.put("mapper.class", StaticEndpointMapper.class.getName()); - configuration.put("listener.1.host", "localhost"); - configuration.put("listener.1.port", "1425"); - configuration.put("listener.2.host", "localhost"); - configuration.put("listener.2.port", "1426"); - reloadConfiguration(configuration, server); - } + // restart listener 2 + reloadConfiguration(server, propsWithMapper(Map.of( + "listener.1.host", "localhost", + "listener.1.port", "1425", + "listener.2.host", "localhost", + "listener.2.port", "1426" + ))); testIt(1425, true); testIt(1426, true); // no more listeners - { - Properties configuration = new Properties(); - configuration.put("mapper.class", StaticEndpointMapper.class.getName()); - reloadConfiguration(configuration, server); - } + reloadConfiguration(server, propsWithMapper(Map.of())); testIt(1425, false); testIt(1426, false); // listener with correct tls version - { - Properties configuration = new Properties(); - configuration.put("mapper.class", StaticEndpointMapper.class.getName()); - configuration.put("certificate.1.hostname", "*"); - configuration.put("certificate.1.mode", "manual"); - - configuration.put("listener.1.host", "localhost"); - configuration.put("listener.1.port", "1423"); - configuration.put("listener.1.ssl", "true"); - configuration.put("listener.1.sslprotocols", "TLSv1.2"); - - configuration.put("listener.2.host", "localhost"); - configuration.put("listener.2.port", "1426"); - configuration.put("listener.2.ssl", "true"); - configuration.put("listener.2.sslprotocols", "TLSv1.2,TLSv1.3"); - reloadConfiguration(configuration, server); - } + String defaultCertificate = TestUtils.deployResource("ia.p12", tmpDir.getRoot()); + reloadConfiguration(server, propsWithMapperAndCertificate(defaultCertificate, Map.of( + "listener.1.host", "localhost", + "listener.1.port", "1423", + "listener.1.ssl", "true", + "listener.1.sslprotocols", "TLSv1.2", + "listener.2.host", "localhost", + "listener.2.port", "1426", + "listener.2.ssl", "true", + "listener.2.sslprotocols", "TLSv1.2,TLSv1.3" + ))); + + // Test HTTPS for listener 1 + testIt(1423, true, true); // Expecting valid HTTPS connection + // Test HTTPS for listener 2 + testIt(1426, true, true); // Expecting valid HTTPS connection + // listener with default tls version - { - Properties configuration = new Properties(); - configuration.put("mapper.class", StaticEndpointMapper.class.getName()); - configuration.put("certificate.1.hostname", "*"); - configuration.put("certificate.1.mode", "manual"); - configuration.put("listener.1.host", "localhost"); - configuration.put("listener.1.port", "1423"); - configuration.put("listener.1.ssl", "true"); - reloadConfiguration(configuration, server); - } + reloadConfiguration(server, propsWithMapperAndCertificate(defaultCertificate, Map.of( + "listener.1.host", "localhost", + "listener.1.port", "1423", + "listener.1.ssl", "true" + ))); + // Test HTTPS for listener 1 + testIt(1423, true, true); // Expecting valid HTTPS connection + // listener with wrong tls version - try { - Properties configuration = new Properties(); - configuration.put("mapper.class", StaticEndpointMapper.class.getName()); - configuration.put("certificate.1.hostname", "*"); - configuration.put("certificate.1.mode", "manual"); - configuration.put("listener.1.host", "localhost"); - configuration.put("listener.1.port", "1423"); - configuration.put("listener.1.ssl", "true"); - configuration.put("listener.1.sslprotocols", "TLSUNKNOWN"); - reloadConfiguration(configuration, server); - } catch (IllegalStateException e) { - Throwable cause = e.getCause(); - assertTrue(cause instanceof ConfigurationNotValidException && cause.getMessage().contains("Unsupported SSL Protocols")); - } + final IllegalStateException e = assertThrows(IllegalStateException.class, () -> + reloadConfiguration(server, propsWithMapperAndCertificate(defaultCertificate, Map.of( + "listener.1.host", "localhost", + "listener.1.port", "1423", + "listener.1.ssl", "true", + "listener.1.sslprotocols", "TLSUNKNOWN" + )))); + Throwable cause = e.getCause(); + assertThat(cause, instanceOf(ConfigurationNotValidException.class)); + assertThat(cause.getMessage(), containsString("Unsupported SSL Protocols")); } } @Test public void testReloadMapper() throws Exception { - try (HttpProxyServer server = new HttpProxyServer(StandardEndpointMapper::new, tmpDir.newFolder());) { - - { - Properties configuration = new Properties(); - server.configureAtBoot(new PropertiesConfigurationStore(configuration)); - } + server.configureAtBoot(new PropertiesConfigurationStore(new Properties())); server.start(); - { - StandardEndpointMapper mapper = (StandardEndpointMapper) server.getMapper(); - assertEquals(0, mapper.getBackends().size()); - } + assertThat(server.getMapper(), instanceOf(StandardEndpointMapper.class)); + assertThat(server.getMapper().getBackends(), is(anEmptyMap())); // add backend - { - Properties configuration = new Properties(); - configuration.put("backend.1.id", "foo"); - configuration.put("backend.1.host", "my-host1"); - configuration.put("backend.1.port", "4213"); - configuration.put("backend.1.enabled", "true"); - reloadConfiguration(configuration, server); - - StandardEndpointMapper mapper = (StandardEndpointMapper) server.getMapper(); - assertEquals(1, mapper.getBackends().size()); - System.out.println("backends:" + mapper.getBackends()); - assertNotNull(mapper.getBackends().get("foo")); - } + reloadConfiguration(server, props(Map.of( + "backend.1.id", "foo", + "backend.1.host", "my-host1", + "backend.1.port", "4213", + "backend.1.enabled", "true" + ))); + assertThat(server.getMapper(), instanceOf(StandardEndpointMapper.class)); + assertThat(server.getMapper().getBackends(), allOf( + is(aMapWithSize(1)), + hasKey("foo") + )); // add second backend - { - Properties configuration = new Properties(); - configuration.put("backend.1.id", "foo"); - configuration.put("backend.1.host", "my-host1"); - configuration.put("backend.1.port", "4213"); - configuration.put("backend.1.enabled", "true"); - - configuration.put("backend.2.id", "bar"); - configuration.put("backend.2.host", "my-host2"); - configuration.put("backend.2.port", "4213"); - configuration.put("backend.2.enabled", "true"); - reloadConfiguration(configuration, server); - - StandardEndpointMapper mapper = (StandardEndpointMapper) server.getMapper(); - - assertEquals(2, mapper.getBackends().size()); - assertNotNull(mapper.getBackends().get("foo")); - assertNotNull(mapper.getBackends().get("bar")); - } + reloadConfiguration(server, props(Map.of( + "backend.1.id", "foo", + "backend.1.host", "my-host1", + "backend.1.port", "4213", + "backend.1.enabled", "true", + "backend.2.id", "bar", + "backend.2.host", "my-host2", + "backend.2.port", "4213", + "backend.2.enabled", "true" + ))); + + assertThat(server.getMapper(), instanceOf(StandardEndpointMapper.class)); + assertThat(server.getMapper().getBackends(), allOf( + is(aMapWithSize(2)), + hasKey("foo"), + hasKey("bar") + )); // remove first backend - { - Properties configuration = new Properties(); - - configuration.put("backend.2.id", "bar"); - configuration.put("backend.2.host", "my-host2"); - configuration.put("backend.2.port", "4213"); - configuration.put("backend.2.enabled", "true"); - reloadConfiguration(configuration, server); - - StandardEndpointMapper mapper = (StandardEndpointMapper) server.getMapper(); - assertEquals(1, mapper.getBackends().size()); - assertNull(mapper.getBackends().get("foo")); - assertNotNull(mapper.getBackends().get("bar")); - } - + reloadConfiguration(server, props(Map.of( + "backend.2.id", "bar", + "backend.2.host", "my-host2", + "backend.2.port", "4213", + "backend.2.enabled", "true" + ))); + + assertThat(server.getMapper(), instanceOf(StandardEndpointMapper.class)); + assertThat(server.getMapper().getBackends(), allOf( + is(aMapWithSize(1)), + hasKey("bar") + )); } } + private void reloadConfiguration(final HttpProxyServer server, final Properties configuration) throws ConfigurationChangeInProgressException, InterruptedException { + PropertiesConfigurationStore config = new PropertiesConfigurationStore(configuration); + server.applyDynamicConfigurationFromAPI(config); + } + + private Properties props(final Map props) { + final var configuration = new Properties(props.size() + 1); + configuration.putAll(props); + return configuration; + } + @Test public void testUserRealm() throws Exception { // Default UserRealm try (HttpProxyServer server = new HttpProxyServer(StandardEndpointMapper::new, tmpDir.newFolder())) { - Properties configuration = new Properties(); - server.configureAtBoot(new PropertiesConfigurationStore(configuration)); + server.configureAtBoot(new PropertiesConfigurationStore(new Properties())); server.start(); UserRealm realm = server.getRealm(); - assertTrue(realm instanceof SimpleUserRealm); + assertThat(realm, is(instanceOf(SimpleUserRealm.class))); // default user with auth always valid SimpleUserRealm userRealm = (SimpleUserRealm) server.getRealm(); - assertEquals(1, userRealm.listUsers().size()); + assertThat(userRealm.listUsers(), hasSize(1)); assertNotNull(userRealm.login("test_0", "anypass0")); assertNotNull(userRealm.login("test_1", "anypass1")); @@ -311,26 +291,32 @@ public void testUserRealm() throws Exception { // TestUserRealm try (HttpProxyServer server = new HttpProxyServer(StandardEndpointMapper::new, tmpDir.newFolder())) { - Properties configuration = new Properties(); - configuration.put("userrealm.class", "org.carapaceproxy.utils.TestUserRealm"); - configuration.put("user.test1", "pass1"); - configuration.put("user.test2", "pass2"); - server.configureAtBoot(new PropertiesConfigurationStore(configuration)); + server.configureAtBoot(new PropertiesConfigurationStore(props(Map.of( + "userrealm.class", "org.carapaceproxy.utils.TestUserRealm", + "user.test1", "pass1", + "user.test2", "pass2" + )))); server.start(); UserRealm realm = server.getRealm(); - assertTrue(realm instanceof TestUserRealm); + assertThat(realm, is(instanceOf(TestUserRealm.class))); + TestUserRealm userRealm = (TestUserRealm) server.getRealm(); - assertEquals(2, userRealm.listUsers().size()); + assertThat(userRealm.listUsers(), hasSize(2)); assertNotNull(userRealm.login("test1", "pass1")); assertNotNull(userRealm.login("test2", "pass2")); assertNull(userRealm.login("test1", "pass3")); // wrong pass // Add new user - configuration.put("user.test3", "pass3"); - reloadConfiguration(configuration, server); + reloadConfiguration(server, props(Map.of( + "userrealm.class", "org.carapaceproxy.utils.TestUserRealm", + "user.test1", "pass1", + "user.test2", "pass2", + "user.test3", "pass3" + ))); + userRealm = (TestUserRealm) server.getRealm(); // realm re-created at each configuration reload - assertEquals(3, userRealm.listUsers().size()); + assertThat(userRealm.listUsers(), hasSize(3)); assertNotNull(userRealm.login("test3", "pass3")); } } @@ -338,81 +324,68 @@ public void testUserRealm() throws Exception { @SuppressWarnings("deprecation") @Test public void testChangeFiltersConfiguration() throws Exception { - try (HttpProxyServer server = new HttpProxyServer(StandardEndpointMapper::new, tmpDir.newFolder());) { - - { - Properties configuration = new Properties(); - configuration.put("filter.1.type", "add-x-forwarded-for"); - server.configureAtBoot(new PropertiesConfigurationStore(configuration)); - } + server.configureAtBoot(new PropertiesConfigurationStore(props("filter.1.type", "add-x-forwarded-for"))); server.start(); - assertEquals(1, server.getFilters().size()); - assertTrue(server.getFilters().get(0) instanceof XForwardedForRequestFilter); + assertThat(server.getFilters(), hasSize(1)); + assertThat(server.getFilters().get(0), instanceOf(XForwardedForRequestFilter.class)); // add a filter - { - Properties configuration = new Properties(); - configuration.put("filter.1.type", "add-x-forwarded-for"); - configuration.put("filter.2.type", "match-user-regexp"); - reloadConfiguration(configuration, server); - - assertEquals(2, server.getFilters().size()); - assertTrue(server.getFilters().get(0) instanceof XForwardedForRequestFilter); - assertTrue(server.getFilters().get(1) instanceof RegexpMapUserIdFilter); - } + reloadConfiguration(server, props(Map.of( + "filter.1.type", "add-x-forwarded-for", + "filter.2.type", "match-user-regexp" + ))); - // remove a filter - { - Properties configuration = new Properties(); - configuration.put("filter.2.type", "match-user-regexp"); - reloadConfiguration(configuration, server); - - assertEquals(1, server.getFilters().size()); - assertTrue(server.getFilters().get(0) instanceof RegexpMapUserIdFilter); - } + assertThat(server.getFilters(), hasSize(2)); + assertThat(server.getFilters().get(0), is(instanceOf(XForwardedForRequestFilter.class))); + assertThat(server.getFilters().get(1), is(instanceOf(RegexpMapUserIdFilter.class))); + // remove a filter + reloadConfiguration(server, props("filter.2.type", "match-user-regexp")); + assertThat(server.getFilters(), hasSize(1)); + assertThat(server.getFilters().get(0), is(instanceOf(RegexpMapUserIdFilter.class))); } } + private Properties props(final String key, final String value) { + return props(Map.of(key, value)); + } + @Test public void testChangeBackendHealthManagerConfiguration() throws Exception { - try (HttpProxyServer server = new HttpProxyServer(StandardEndpointMapper::new, tmpDir.newFolder());) { - - { - Properties configuration = new Properties(); - configuration.put("healthmanager.connecttimeout", "9479"); - server.configureAtBoot(new PropertiesConfigurationStore(configuration)); - } + server.configureAtBoot(new PropertiesConfigurationStore(props("healthmanager.connecttimeout", "9479"))); server.start(); assertEquals(9479, server.getBackendHealthManager().getConnectTimeout()); // change configuration - { - Properties configuration = new Properties(); - configuration.put("healthmanager.connecttimeout", "9233"); - reloadConfiguration(configuration, server); - - assertEquals(9233, server.getBackendHealthManager().getConnectTimeout()); - } - + reloadConfiguration(server, props("healthmanager.connecttimeout", "9233")); + assertEquals(9233, server.getBackendHealthManager().getConnectTimeout()); } } - private void reloadConfiguration(Properties configuration, final HttpProxyServer server) throws ConfigurationNotValidException, ConfigurationChangeInProgressException, InterruptedException { - PropertiesConfigurationStore config = new PropertiesConfigurationStore(configuration); - server.applyDynamicConfigurationFromAPI(config); + private void testIt(int port, boolean ok) throws Exception { + testIt(port, false, ok); } - private void testIt(int port, boolean ok) throws URISyntaxException, IOException { - try { - String url = "http://localhost:" + port + "/index.html?redir"; - String s = IOUtils.toString(URI.create(url), StandardCharsets.UTF_8); - System.out.println("RES FOR: " + url + " -> " + s); - assertEquals("it works !!", s); - if (!ok) { - fail("Expecting an error for port " + port); + private void testIt(int port, final boolean https, boolean ok) throws Exception { + try (CloseableHttpClient client = createHttpClientWithDisabledSSLValidation()) { + final String protocol = https ? "https" : "http"; + String url = protocol + "://localhost:" + port + "/index.html?redir"; + + HttpGet request = new HttpGet(new URI(url)); + try (CloseableHttpResponse response = client.execute(request)) { + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = new String(response.getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8); + + System.out.println("RES FOR: " + url + " -> " + responseBody); + + // Check that the response body matches what we expect + assertEquals("it works !!", responseBody); + + if (!ok && statusCode == 200) { + fail("Expecting an error for port " + port); + } } } catch (IOException err) { if (ok) { @@ -421,4 +394,34 @@ private void testIt(int port, boolean ok) throws URISyntaxException, IOException } } + private Properties propsWithMapper(final Map props) { + final var configuration = new Properties(props.size()); + configuration.put("mapper.class", StaticEndpointMapper.class.getName()); + configuration.putAll(props); + return configuration; + } + + private Properties propsWithMapperAndCertificate(final String defaultCertificate, final Map props) { + final var configuration = new Properties(props.size()); + configuration.put("mapper.class", StaticEndpointMapper.class.getName()); + configuration.put("certificate.1.hostname", "*"); + configuration.put("certificate.1.file", defaultCertificate); + configuration.put("certificate.1.password", "changeit"); + configuration.putAll(props); + return configuration; + } + + /** + * Static mapper, so that it can be references by configuration + */ + public static final class StaticEndpointMapper extends TestEndpointMapper { + + public StaticEndpointMapper(final HttpProxyServer ignoredServer) { + this(); // required for reflective construction + } + + public StaticEndpointMapper() { + super("localhost", wireMockRule.port()); + } + } }