Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable certificate validation #122

Merged
merged 5 commits into from
Nov 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ You can an individual test class by:
Follow the instructions in [Integration Tests](integration-tests/README.md) to run integration tests.


## Security

For more information about how the certificates between APIML services are setup, see [TLS Certificates for localhost](keystore/README.md).


## Contributor guidelines

Follow the guidelines in [Contributing](CONTRIBUTING.md) to add new functionality.
Expand All @@ -71,7 +76,8 @@ Follow the guidelines in [Contributing](CONTRIBUTING.md) to add new functionalit

Follow the guidelines in [Local Configuration](docs/local-configuration.md) to set local environment properties for testing on your local machine include HTTPS setup.

Also if you use IntelliJ Idea [learn how to configure Run Dashboard](docs/idea-setup.md) to use these local configurations.
Also if you use IntelliJ IDEA, see [learn how to configure Run Dashboard](docs/idea-setup.md) to use these local configurations.


## Adding services that does not support API Mediation Layer natively

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public ConnectionSocketFactory createSslSocketFactory() {
if (config.isVerifySslCertificatesOfServices()) {
return createSecureSslSocketFactory();
} else {
log.warn("The gateway is not verifying the TLS/SSL certificates of the services");
log.warn("The service is not verifying the TLS/SSL certificates of the services");
return createIgnoringSslSocketFactory();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@
*/
package com.ca.mfaas.tomcat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.ca.mfaas.security.HttpsConfig;
import com.ca.mfaas.security.HttpsConfigError;
import com.ca.mfaas.security.HttpsConfigError.ErrorCode;
import com.ca.mfaas.security.HttpsFactory;

import lombok.extern.slf4j.Slf4j;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.apache.http.HttpResponse;
Expand All @@ -22,16 +33,13 @@

import javax.net.ssl.SSLHandshakeException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import com.ca.mfaas.security.HttpsConfig;
import com.ca.mfaas.security.HttpsConfigError;
import com.ca.mfaas.security.HttpsFactory;
import com.ca.mfaas.security.HttpsConfigError.ErrorCode;

@Slf4j
public class TomcatHttpsTest {
private static final String EXPECTED_SSL_HANDSHAKE_EXCEPTION_NOT_THROWN = "excepted SSLHandshakeException exception not thrown";
private static final String EXPECTED_HTTPS_CONFIG_ERROR_NOT_THROWN = "excepted HttpsConfigError exception not thrown";
private static final String UNABLE_TO_FIND_CERTIFICATION_PATH_MESSAGE = "unable to find valid certification path";
private static final String STORE_PASSWORD = "password"; // NOSONAR

@Test
public void correctConfigurationShouldWork() throws IOException, LifecycleException {
HttpsConfig httpsConfig = correctHttpsSettings().build();
Expand All @@ -43,9 +51,9 @@ public void noTrustStoreShouldFail() throws IOException, LifecycleException {
HttpsConfig httpsConfig = correctHttpsKeyStoreSettings().build();
try {
startTomcatAndDoHttpsRequest(httpsConfig);
fail("excepted SSLHandshakeException message not thrown");
} catch (SSLHandshakeException e) {
assertTrue(e.getMessage().contains("unable to find valid certification path"));
fail(EXPECTED_SSL_HANDSHAKE_EXCEPTION_NOT_THROWN);
} catch (SSLHandshakeException e) { // NOSONAR
assertTrue(e.getMessage().contains(UNABLE_TO_FIND_CERTIFICATION_PATH_MESSAGE));
}
}

Expand All @@ -55,20 +63,27 @@ public void trustStoreWithDifferentCertificateAuthorityShouldFail() throws IOExc
.trustStore(pathFromRepository("keystore/localhost/localhost2.truststore.p12")).build();
try {
startTomcatAndDoHttpsRequest(httpsConfig);
fail("excepted SSLHandshakeException message not thrown");
} catch (SSLHandshakeException e) {
assertTrue(e.getMessage().contains("unable to find valid certification path"));
fail(EXPECTED_SSL_HANDSHAKE_EXCEPTION_NOT_THROWN);
} catch (SSLHandshakeException e) { // NOSONAR
assertTrue(e.getMessage().contains(UNABLE_TO_FIND_CERTIFICATION_PATH_MESSAGE));
}
}

@Test
public void trustStoreWithDifferentCertificateAuthorityShouldNotFailWhenCertificateValidationIsDisabled() throws IOException, LifecycleException {
HttpsConfig httpsConfig = correctHttpsSettings().verifySslCertificatesOfServices(false)
.trustStore(pathFromRepository("keystore/localhost/localhost2.truststore.p12")).build();
startTomcatAndDoHttpsRequest(httpsConfig);
}

@Test
public void trustStoreInInvalidFormatShouldFail() throws IOException, LifecycleException {
HttpsConfig httpsConfig = correctHttpsSettings()
.trustStore(pathFromRepository("README.md")).build();
try {
startTomcatAndDoHttpsRequest(httpsConfig);
fail("excepted SSLHandshakeException message not thrown");
} catch (HttpsConfigError e) {
fail(EXPECTED_HTTPS_CONFIG_ERROR_NOT_THROWN);
} catch (HttpsConfigError e) { // NOSONAR
assertEquals(ErrorCode.HTTP_CLIENT_INITIALIZATION_FAILED, e.getCode());
}
}
Expand All @@ -78,25 +93,57 @@ public void wrongKeyAliasShouldFail() throws IOException, LifecycleException {
HttpsConfig httpsConfig = correctHttpsKeyStoreSettings().keyAlias("wrong").build();
try {
startTomcatAndDoHttpsRequest(httpsConfig);
fail("excepted message not thrown");
} catch (HttpsConfigError e) {
fail(EXPECTED_HTTPS_CONFIG_ERROR_NOT_THROWN);
} catch (HttpsConfigError e) { // NOSONAR
assertEquals(ErrorCode.WRONG_KEY_ALIAS, e.getCode());
}
}

@Test
public void correctConfigurationWithClientAuthenticationShouldWork() throws IOException, LifecycleException {
HttpsConfig httpsConfig = correctHttpsSettings().clientAuth(true).build();
startTomcatAndDoHttpsRequest(httpsConfig);
}

@Test
public void wrongClientCertificateShouldFail() throws IOException, LifecycleException {
HttpsConfig serverConfig = correctHttpsSettings().clientAuth(true).build();
HttpsConfig clientConfig = correctHttpsSettings().keyStore(pathFromRepository("keystore/localhost/localhost2.keystore.p12")).build();
try {
startTomcatAndDoHttpsRequest(serverConfig, clientConfig);
fail(EXPECTED_SSL_HANDSHAKE_EXCEPTION_NOT_THROWN);
} catch (SSLHandshakeException e) { // NOSONAR
assertTrue(e.getMessage().contains("bad_certificate"));
}
}

@Test
public void wrongClientCertificateShouldNotFailWhenCertificateValidationIsDisabled() throws IOException, LifecycleException {
HttpsConfig serverConfig = correctHttpsSettings().clientAuth(true).verifySslCertificatesOfServices(false).build();
HttpsConfig clientConfig = correctHttpsSettings().keyStore(pathFromRepository("keystore/localhost/localhost2.keystore.p12")).build();
startTomcatAndDoHttpsRequest(serverConfig, clientConfig);
}

private String pathFromRepository(String path) {
String newPath = "../" + path;
try {
return new File("../" + path).getCanonicalPath();
return new File(newPath).getCanonicalPath();
} catch (IOException e) {
throw new Error("Invalid repository path: " + path, e);
log.error("Error opening file {}", newPath, e);
fail("Invalid repository path: " + newPath);
return null;
}
}

private void startTomcatAndDoHttpsRequest(HttpsConfig httpsConfig) throws IOException, LifecycleException {
Tomcat tomcat = new TomcatServerFactory().startTomcat(httpsConfig);
private void startTomcatAndDoHttpsRequest(HttpsConfig config) throws IOException, LifecycleException {
startTomcatAndDoHttpsRequest(config, config);
}

private void startTomcatAndDoHttpsRequest(HttpsConfig serverConfig, HttpsConfig clientConfig) throws IOException, LifecycleException {
Tomcat tomcat = new TomcatServerFactory().startTomcat(serverConfig);
try {
HttpsFactory httpsFactory = new HttpsFactory(httpsConfig);
HttpClient client = httpsFactory.createSecureHttpClient();
HttpsFactory clientHttpsFactory = new HttpsFactory(clientConfig);
HttpClient client = clientHttpsFactory.createSecureHttpClient();

int port = TomcatServerFactory.getLocalPort(tomcat);
HttpGet get = new HttpGet(String.format("https://localhost:%d", port));
Expand All @@ -113,13 +160,13 @@ private void startTomcatAndDoHttpsRequest(HttpsConfig httpsConfig) throws IOExce

private HttpsConfig.HttpsConfigBuilder correctHttpsKeyStoreSettings() throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IOException is never thrown

return HttpsConfig.builder().protocol("TLSv1.2")
.keyStore(pathFromRepository("keystore/localhost/localhost.keystore.p12")).keyStorePassword("password")
.keyPassword("password");
.keyStore(pathFromRepository("keystore/localhost/localhost.keystore.p12"))
.keyStorePassword(STORE_PASSWORD).keyPassword(STORE_PASSWORD);
}

private HttpsConfig.HttpsConfigBuilder correctHttpsSettings() throws IOException {
return correctHttpsKeyStoreSettings()
.trustStore(pathFromRepository("keystore/localhost/localhost.truststore.p12"))
.trustStorePassword("password");
.trustStorePassword(STORE_PASSWORD);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@

@Slf4j
public class TomcatServerFactory {
private final static String SERVLET_NAME = "hello";
private static final String SERVLET_NAME = "hello";
private static final String STORE_PASSWORD = "password"; // NOSONAR

public Tomcat startTomcat(HttpsConfig httpsConfig) throws IOException {
Tomcat tomcat = new Tomcat();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ServletException is never thrown

Expand All @@ -67,7 +68,7 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
try {
tomcat.start();
} catch (LifecycleException e) {
throw new RuntimeException(e);
throw new RuntimeException(e); // NOSONAR
}
return tomcat;
}
Expand All @@ -77,9 +78,14 @@ private Connector createHttpsConnector(HttpsConfig httpsConfig) {
httpsConnector.setPort(0);
httpsConnector.setSecure(true);
httpsConnector.setScheme("https");
httpsConnector.setAttribute("clientAuth",
Boolean.toString(httpsConfig.isClientAuth() && httpsConfig.isVerifySslCertificatesOfServices()));
httpsConnector.setAttribute("keystoreFile", httpsConfig.getKeyStore());
httpsConnector.setAttribute("clientAuth", Boolean.toString(httpsConfig.isClientAuth()));
httpsConnector.setAttribute("keystorePass", httpsConfig.getKeyPassword());
httpsConnector.setAttribute("keystorePass", httpsConfig.getKeyStorePassword());
if (httpsConfig.isClientAuth()) {
httpsConnector.setAttribute("truststoreFile", httpsConfig.getTrustStore());
httpsConnector.setAttribute("truststorePass", httpsConfig.getTrustStorePassword());
}
httpsConnector.setAttribute("sslProtocol", httpsConfig.getProtocol());
httpsConnector.setAttribute("SSLEnabled", true);
return httpsConnector;
Expand All @@ -103,9 +109,9 @@ public static void main(String[] args) throws LifecycleException, ClientProtocol

HttpsConfig httpsConfig = HttpsConfig.builder()
.keyStore(new File("keystore/localhost/localhost.keystore.p12").getCanonicalPath())
.keyStorePassword("password").keyPassword("password")
.keyStorePassword(STORE_PASSWORD).keyPassword(STORE_PASSWORD)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClientProtocolException is never thrown because of more general exception

.trustStore(new File("keystore/localhost/localhost.truststore.p12").getCanonicalPath())
.trustStorePassword("password").protocol("TLSv1.2").build();
.trustStorePassword(STORE_PASSWORD).protocol("TLSv1.2").build();
HttpsFactory httpsFactory = new HttpsFactory(httpsConfig);

Tomcat tomcat = new TomcatServerFactory().startTomcat(httpsConfig);
Expand Down
38 changes: 0 additions & 38 deletions config/local/api-catalog-ui.yml

This file was deleted.

2 changes: 2 additions & 0 deletions config/local/discovery-service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ apiml:
port: 10011
discovery:
staticApiDefinitionsDirectory: config/local/api-defs
security:
verifySslCertificatesOfServices: true

spring:
output:
Expand Down
2 changes: 2 additions & 0 deletions config/local/gateway-service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ apiml:
ipAddress: 127.0.0.1
port: 10010
discoveryServiceUrls: https://localhost:10011/eureka/
security:
verifySslCertificatesOfServices: true

spring:
output:
Expand Down
Loading