Skip to content

Commit

Permalink
[RESTEASY-2257] MP Rest Client SSL support (#2047)
Browse files Browse the repository at this point in the history
  • Loading branch information
michalszynkiewicz authored and asoldano committed May 27, 2019
1 parent e7965fa commit 05a6b56
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import org.jboss.resteasy.microprofile.client.header.ClientHeadersRequestFilter;
import org.jboss.resteasy.specimpl.ResteasyUriBuilder;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.ws.rs.BeanParam;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
Expand All @@ -50,6 +52,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -124,6 +127,32 @@ public RestClientBuilder readTimeout(long time, TimeUnit timeUnit) {
return this;
}


@Override
public RestClientBuilder sslContext(SSLContext sslContext) {
this.sslContext = sslContext;
return this;
}

@Override
public RestClientBuilder trustStore(KeyStore trustStore) {
this.trustStore = trustStore;
return this;
}

@Override
public RestClientBuilder keyStore(KeyStore keyStore, String keystorePassword) {
this.keyStore = keyStore;
this.keystorePassword = keystorePassword;
return this;
}

@Override
public RestClientBuilder hostnameVerifier(HostnameVerifier hostnameVerifier) {
this.hostnameVerifier = hostnameVerifier;
return this;
}

@Override
public RestClientBuilder executorService(ExecutorService executor) {
if (executor == null) {
Expand Down Expand Up @@ -186,6 +215,12 @@ public <T> T build(Class<T> aClass) throws IllegalStateException, RestClientDefi
resteasyClientBuilder.register(DEFAULT_MEDIA_TYPE_FILTER);
resteasyClientBuilder.register(METHOD_INJECTION_FILTER);
resteasyClientBuilder.register(HEADERS_REQUEST_FILTER);
resteasyClientBuilder.sslContext(sslContext);
resteasyClientBuilder.trustStore(trustStore);
resteasyClientBuilder.keyStore(keyStore, keystorePassword);

resteasyClientBuilder.hostnameVerifier(hostnameVerifier);
resteasyClientBuilder.setIsTrustSelfSignedCertificates(false);

if (readTimeout != null) {
resteasyClientBuilder.readTimeout(readTimeout, readTimeoutUnit);
Expand Down Expand Up @@ -537,6 +572,13 @@ ResteasyClientBuilder getBuilderDelegate() {
private Long readTimeout;
private TimeUnit readTimeoutUnit;

private SSLContext sslContext;
private KeyStore trustStore;
private KeyStore keyStore;
private String keystorePassword;
private HostnameVerifier hostnameVerifier;


private Set<Object> localProviderInstances = new HashSet<>();

private final List<AsyncInvocationInterceptorFactory> asyncInterceptorFactories = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,22 @@
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.PassivationCapable;
import javax.enterprise.util.AnnotationLiteral;
import javax.net.ssl.HostnameVerifier;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -60,6 +70,21 @@ public class RestClientDelegateBean implements Bean<Object>, PassivationCapable

public static final String REST_PROVIDERS = "%s/mp-rest/providers";

public static final String TRUST_STORE = "%s/mp-rest/trustStore";

public static final String TRUST_STORE_PASSWORD = "%s/mp-rest/trustStorePassword";

public static final String TRUST_STORE_TYPE = "%s/mp-rest/trustStoreType";

public static final String KEY_STORE = "%s/mp-rest/keyStore";

public static final String KEY_STORE_PASSWORD = "%s/mp-rest/keyStorePassword";

public static final String KEY_STORE_TYPE = "%s/mp-rest/keyStoreType";

public static final String HOSTNAME_VERIFIER = "%s/mp-rest/hostnameVerifier";


private static final String PROPERTY_PREFIX = "%s/property/";

private final Class<?> proxyType;
Expand Down Expand Up @@ -110,10 +135,101 @@ public Object create(CreationalContext<Object> creationalContext) {

configureProviders(builder);

configureSsl(builder);

getConfigProperties().forEach(builder::property);
return builder.build(proxyType);
}

private void configureSsl(RestClientBuilder builder) {
Optional<String> maybeTrustStore = getOptionalProperty(TRUST_STORE, String.class);

maybeTrustStore.ifPresent(trustStore -> registerTrustStore(trustStore, builder));

Optional<String> maybeKeyStore = getOptionalProperty(KEY_STORE, String.class);
maybeKeyStore.ifPresent(keyStore -> registerKeyStore(keyStore, builder));

Optional<String> maybeHostnameVerifier = getOptionalProperty(HOSTNAME_VERIFIER, String.class);
maybeHostnameVerifier.ifPresent(verifier -> registerHostnameVerifier(verifier, builder));

}

private void registerHostnameVerifier(String verifier, RestClientBuilder builder) {
try {
Class<?> verifierClass = Class.forName(verifier, true, Thread.currentThread().getContextClassLoader());
builder.hostnameVerifier((HostnameVerifier) verifierClass.newInstance());
} catch (ClassNotFoundException e) {
throw new RuntimeException("Could not find hostname verifier class" + verifier, e);
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Failed to instantiate hostname verifier class. Make sure it has a public, no-argument constructor", e);
} catch (ClassCastException e) {
throw new RuntimeException("The provided hostname verifier " + verifier + " is not an instance of HostnameVerifier", e);
}
}

private void registerKeyStore(String keyStorePath, RestClientBuilder builder) {
Optional<String> keyStorePassword = getOptionalProperty(KEY_STORE_PASSWORD, String.class);
Optional<String> keyStoreType = getOptionalProperty(KEY_STORE_TYPE, String.class);

try {
KeyStore keyStore = KeyStore.getInstance(keyStoreType.orElse("JKS"));
String password = keyStorePassword.orElseThrow(() -> new IllegalArgumentException("No password provided for keystore"));

try (InputStream input = locateStream(keyStorePath)) {
keyStore.load(input, password.toCharArray());
} catch (IOException | CertificateException | NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Failed to initialize trust store from classpath resource " + keyStorePath, e);
}

builder.keyStore(keyStore, password);
} catch (KeyStoreException e) {
throw new IllegalArgumentException("Failed to initialize trust store from " + keyStorePath, e);
}
}

private void registerTrustStore(String trustStorePath, RestClientBuilder builder) {
Optional<String> maybeTrustStorePassword = getOptionalProperty(TRUST_STORE_PASSWORD, String.class);
Optional<String> maybeTrustStoreType = getOptionalProperty(TRUST_STORE_TYPE, String.class);

try {
KeyStore trustStore = KeyStore.getInstance(maybeTrustStoreType.orElse("JKS"));
String password = maybeTrustStorePassword.orElseThrow(() -> new IllegalArgumentException("No password provided for truststore"));

try (InputStream input = locateStream(trustStorePath)) {
trustStore.load(input, password.toCharArray());
} catch (IOException | CertificateException | NoSuchAlgorithmException e) {
throw new IllegalArgumentException("Failed to initialize trust store from classpath resource " + trustStorePath, e);
}

builder.trustStore(trustStore);
} catch (KeyStoreException e) {
throw new IllegalArgumentException("Failed to initialize trust store from " + trustStorePath, e);
}
}

private InputStream locateStream(String path) throws FileNotFoundException {
if (path.startsWith("classpath:")) {
path = path.replaceFirst("classpath:", "");
InputStream resultStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
if (resultStream == null) {
resultStream = getClass().getResourceAsStream(path);
}
if (resultStream == null) {
throw new IllegalArgumentException("Classpath resource " + path + " not found for MicroProfile Rest Client SSL configuration");
}
return resultStream;
} else {
if (path.startsWith("file:")) {
path = path.replaceFirst("file:", "");
}
File certificateFile = new File(path);
if (!certificateFile.isFile()) {
throw new IllegalArgumentException("Certificate file: " + path + " not found for MicroProfile Rest Client SSL configuration");
}
return new FileInputStream(certificateFile);
}
}

private void configureProviders(RestClientBuilder builder) {
Optional<String> maybeProviders = getOptionalProperty(REST_PROVIDERS, String.class);
maybeProviders.ifPresent(providers -> registerProviders(builder, providers));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@

import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
import org.jboss.resteasy.microprofile.client.utils.ClientRequestContextUtils;
import org.jboss.resteasy.spi.ResteasyProviderFactory;

import javax.annotation.Priority;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -92,8 +90,6 @@ public void filter(ClientRequestContext requestContext) {
.forEach(
(key, values) -> requestContext.getHeaders().put(key, castToListOfObjects(values))
);

ResteasyProviderFactory.getContextDataMap().put(HttpHeaders.class, new HttpHeadersContextProvider(requestContext));
}

private MultivaluedMap<String, String> updateHeaders(MultivaluedMap<String, String> headers, ClientHeadersFactory factory) {
Expand Down
8 changes: 7 additions & 1 deletion resteasy-dependencies-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
<version.org.jboss.spec.javax.xml.bind-api_2.3_spec>1.0.1.Final</version.org.jboss.spec.javax.xml.bind-api_2.3_spec>
<version.org.jboss.spec.javax.servlet.jboss-servlet-api_4.0_spec>1.0.0.Final</version.org.jboss.spec.javax.servlet.jboss-servlet-api_4.0_spec>
<version.org.jboss.shrinkwrap.resolver>2.2.4</version.org.jboss.shrinkwrap.resolver>
<version.microprofile.restclient>1.2.1</version.microprofile.restclient>
<version.microprofile.restclient>1.3.1</version.microprofile.restclient>
<version.microprofile.config>1.3</version.microprofile.config>
<version.org.slf4j>1.7.25</version.org.slf4j>
<version.org.wildfly.core.wildfly-cli>7.0.0.Final</version.org.wildfly.core.wildfly-cli>
Expand Down Expand Up @@ -712,6 +712,12 @@
<artifactId>jetty-server</artifactId>
<version>${version.org.eclipse.jetty}</version>
</dependency>
<!-- required to align the Jetty Servlet version for MicroProfile Rest Client tests-->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<version>${version.org.eclipse.jetty}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
Expand Down
22 changes: 22 additions & 0 deletions testsuite/microprofile-tck/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@
<version>${project.version}</version>
</dependency>

<!-- required to align the Jetty Servlets version to the versions of other Jetty components -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlets</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
Expand Down Expand Up @@ -143,6 +150,21 @@
<dependency>org.eclipse.microprofile.rest.client:microprofile-rest-client-tck</dependency>
</dependenciesToScan>
<excludes>
<!-- TODO: remove exclusions when the required features are implemented -->
<exclude>org.eclipse.microprofile.rest.client.tck.CloseTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.DefaultMIMETypeTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.cditests.CDIInvokeWithRegisteredProvidersTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.cditests.ConfigKeyForMultipleInterfacesTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.cditests.ConfigKeyTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.cditests.HasAppScopeTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.cditests.HasConversationScopeTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.cditests.HasRequestScopeTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.cditests.HasSessionScopeTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.cditests.HasSingletonScopeTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.timeout.TimeoutBuilderIndependentOfMPConfigTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.timeout.TimeoutTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.timeout.TimeoutViaMPConfigTest</exclude>
<exclude>org.eclipse.microprofile.rest.client.tck.timeout.TimeoutViaMPConfigWithConfigKeyTest</exclude>
</excludes>
</configuration>
</execution>
Expand Down

0 comments on commit 05a6b56

Please sign in to comment.