Skip to content

Commit

Permalink
fix(#508): Support SSL keystore in secure Http client steps
Browse files Browse the repository at this point in the history
  • Loading branch information
christophd committed Jun 13, 2024
1 parent 4c7d25f commit 0bb71ed
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;

import io.cucumber.datatable.DataTable;
import io.cucumber.java.Before;
Expand All @@ -33,12 +34,14 @@
import io.cucumber.java.en.When;
import org.apache.hc.client5.http.auth.AuthScope;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.ssl.SSLContexts;
import org.citrusframework.Citrus;
import org.citrusframework.CitrusSettings;
Expand All @@ -55,12 +58,13 @@
import org.citrusframework.http.client.HttpClientBuilder;
import org.citrusframework.http.message.HttpMessage;
import org.citrusframework.util.FileUtils;
import org.citrusframework.util.StringUtils;
import org.citrusframework.variable.dictionary.DataDictionary;
import org.citrusframework.yaks.YaksSettings;
import org.citrusframework.yaks.util.ResourceUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import static org.citrusframework.TestActionBuilder.logger;
Expand Down Expand Up @@ -110,6 +114,10 @@ public class HttpClientSteps implements HttpSteps {
private String authUser = HttpSettings.getClientAuthUser();
private String authPassword = HttpSettings.getClientAuthPassword();

private boolean useSslKeyStore = HttpSettings.isUseSslKeyStore();
private String sslKeyStorePath = HttpSettings.getSslKeyStorePath();
private String sslKeyStorePassword = HttpSettings.getSslKeyStorePassword();

@Before
public void before(Scenario scenario) {
if (httpClient == null) {
Expand Down Expand Up @@ -156,6 +164,22 @@ public void setUrl(String url) {
this.requestUrl = resolvedUrl;
}

@Given("^HTTP client (enable|disable) SSL keystore$")
public void setSecureKeyStore(String mode) {
this.useSslKeyStore = "enable".equals(mode);
}

@Given("^HTTP client SSL keystore path ([^\\s]+)$")
public void setSslKeyStorePath(String sslKeyStorePath) {
this.sslKeyStorePath = sslKeyStorePath;
this.useSslKeyStore = true;
}

@Given("^HTTP client SSL keystore password ([^\\s]+)$")
public void setSslKeyStorePassword(String sslKeyStorePassword) {
this.sslKeyStorePassword = sslKeyStorePassword;
}

@Given("^HTTP client (enable|disable) basic auth$")
public void setBasicAuth(String mode) {
if ("enable".equals(mode)) {
Expand Down Expand Up @@ -438,13 +462,17 @@ private HttpComponentsClientHttpRequestFactory sslRequestFactory() {
*/
private org.apache.hc.client5.http.classic.HttpClient sslClient() {
try {
SSLContext sslcontext = SSLContexts
SSLContextBuilder sslContextBuilder = SSLContexts
.custom()
.loadTrustMaterial(TrustAllStrategy.INSTANCE)
.build();
.loadTrustMaterial(TrustAllStrategy.INSTANCE);

if (useSslKeyStore && StringUtils.hasText(sslKeyStorePath)) {
sslContextBuilder.loadKeyMaterial(ResourceUtils.resolve(sslKeyStorePath, context).getURL(),
sslKeyStorePassword.toCharArray(), sslKeyStorePassword.toCharArray());
}

SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
sslcontext, NoopHostnameVerifier.INSTANCE);
sslContextBuilder.build(), NoopHostnameVerifier.INSTANCE);

PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
.setSSLSocketFactory(sslSocketFactory)
Expand All @@ -453,7 +481,7 @@ private org.apache.hc.client5.http.classic.HttpClient sslClient() {
return HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException | IOException | UnrecoverableKeyException | CertificateException e) {
throw new CitrusRuntimeException("Failed to create http client for ssl connection", e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.citrusframework.http.server.HttpServer;
import org.citrusframework.http.server.HttpServerBuilder;
import org.citrusframework.util.FileUtils;
import org.citrusframework.util.StringUtils;
import org.citrusframework.variable.dictionary.DataDictionary;
import org.citrusframework.yaks.util.ResourceUtils;
import org.eclipse.jetty.http.HttpVersion;
Expand Down Expand Up @@ -83,8 +84,6 @@ public class HttpServerSteps implements HttpSteps {

private HttpServer httpServer;

private ServerConnector sslConnector;

private Map<String, String> requestHeaders = new HashMap<>();
private Map<String, String> responseHeaders = new HashMap<>();
private Map<String, String> requestParams = new HashMap<>();
Expand All @@ -106,6 +105,9 @@ public class HttpServerSteps implements HttpSteps {
private int serverPort = HttpSettings.getServerPort();
private String serverName = HttpSettings.getServerName();

private boolean useSslConnector = HttpSettings.isUseSslConnector();
private boolean useSslKeyStore = HttpSettings.isUseSslKeyStore();

private String sslKeyStorePath = HttpSettings.getSslKeyStorePath();
private String sslKeyStorePassword = HttpSettings.getSslKeyStorePassword();

Expand Down Expand Up @@ -142,7 +144,6 @@ public void before(Scenario scenario) {
bodyValidationExpressions = new HashMap<>();
outboundDictionary = null;
inboundDictionary = null;
sslConnector = null;
}

@Given("^HTTP server \"([^\"\\s]+)\"$")
Expand Down Expand Up @@ -227,16 +228,21 @@ public void setAuthPassword(String authPassword) {

@Given("^HTTP server (enable|disable) SSL$")
public void setSecureConnector(String mode) {
if ("enable".equals(mode)) {
this.sslConnector = sslConnector();
} else {
this.sslConnector = null;
useSslConnector = "enable".equals(mode);
if (useSslConnector) {
this.useSslKeyStore = true;
}
}

@Given("^HTTP server (enable|disable) SSL keystore$")
public void setSecureKeyStore(String mode) {
this.useSslKeyStore = "enable".equals(mode);
}

@Given("^HTTP server SSL keystore path ([^\\s]+)$")
public void setSslKeyStorePath(String sslKeyStorePath) {
this.sslKeyStorePath = sslKeyStorePath;
this.useSslKeyStore = true;
}

@Given("^HTTP server SSL keystore password ([^\\s]+)$")
Expand Down Expand Up @@ -454,8 +460,8 @@ public HttpServer getOrCreateHttpServer() {
.name(serverName)
.build();

if (sslConnector != null) {
httpServer.setConnector(sslConnector);
if (useSslConnector) {
httpServer.setConnector(sslConnector());
}

if ("basic".equals(authMethod)) {
Expand Down Expand Up @@ -555,8 +561,10 @@ private HttpConfiguration httpConfiguration() {
private SslContextFactory.Server sslContextFactory() {
try {
SslContextFactory.Server contextFactory = new SslContextFactory.Server();
contextFactory.setKeyStorePath(getKeyStorePathPath());
contextFactory.setKeyStorePassword(context.replaceDynamicContentInString(sslKeyStorePassword));
if (useSslKeyStore && StringUtils.hasText(sslKeyStorePath)) {
contextFactory.setKeyStorePath(getKeyStorePathPath());
contextFactory.setKeyStorePassword(context.replaceDynamicContentInString(sslKeyStorePassword));
}
return contextFactory;
} catch (IOException e) {
throw new CitrusRuntimeException("Failed to read keystore file in path: " + sslKeyStorePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ public class HttpSettings {
private static final String SECURE_KEYSTORE_PASSWORD_ENV = HTTP_ENV_PREFIX + "SECURE_KEYSTORE_PASSWORD";
private static final String SECURE_KEYSTORE_PASSWORD_DEFAULT = "secret";

private static final String USE_SECURE_CONNECTOR_PROPERTY = HTTP_PROPERTY_PREFIX + "use.secure.connector";
private static final String USE_SECURE_CONNECTOR_ENV = HTTP_ENV_PREFIX + "USE_SECURE_CONNECTOR";
private static final String USE_SECURE_CONNECTOR_DEFAULT = "false";

private static final String USE_SECURE_KEYSTORE_PROPERTY = HTTP_PROPERTY_PREFIX + "use.secure.keystore";
private static final String USE_SECURE_KEYSTORE_ENV = HTTP_ENV_PREFIX + "USE_SECURE_KEYSTORE";
private static final String USE_SECURE_KEYSTORE_DEFAULT = "false";

private static final String HEADER_NAME_IGNORE_CASE_PROPERTY = HTTP_PROPERTY_PREFIX + "header.name.ignore.case";
private static final String HEADER_NAME_IGNORE_CASE_ENV = HTTP_ENV_PREFIX + "HEADER_NAME_IGNORE_CASE";
private static final String HEADER_NAME_IGNORE_CASE_DEFAULT = "false";
Expand Down Expand Up @@ -143,6 +151,18 @@ public static int getSecurePort() {
System.getenv(SECURE_PORT_ENV) != null ? System.getenv(SECURE_PORT_ENV) : SECURE_PORT_DEFAULT));
}

public static boolean isUseSslConnector() {
return Boolean.parseBoolean(System.getProperty(USE_SECURE_CONNECTOR_PROPERTY,
System.getenv(USE_SECURE_CONNECTOR_ENV) != null ? System.getenv(USE_SECURE_CONNECTOR_ENV) :
USE_SECURE_CONNECTOR_DEFAULT));
}

public static boolean isUseSslKeyStore() {
return Boolean.parseBoolean(System.getProperty(USE_SECURE_KEYSTORE_PROPERTY,
System.getenv(USE_SECURE_KEYSTORE_ENV) != null ? System.getenv(USE_SECURE_KEYSTORE_ENV) :
USE_SECURE_KEYSTORE_DEFAULT));
}

/**
* SSL key store path.
* @return
Expand Down

0 comments on commit 0bb71ed

Please sign in to comment.