How can I authenticate a Ribbon load balancer and Zuul proxy using a certificate? - spring

I have a Spring application, that acts as an authentication proxy for two backend servers. A user will access the Spring application and be forwarded to the backend once he is successfully authenticated. To prevent unwanted access without prior authentication the backend servers require a certificate as authentication.
My Spring application uses Netflix-Ribbon as a load balancer and Netflix-Zuul as a Proxy for the users requests. How can I configure them to use the client certificate that is required for the authentication on the backend servers?

Ok, I figured it out. You can configure your own CloasableHttpClient as a #Bean and create a custom SSL context. You can provide a certificate to a server through .loadKeyMaterial(). Zuul will then use these settings.
#Configuration
public class HttpClientConfig {
#Bean
public CloseableHttpClient httpClient() throws Throwable {
String keyPassphrase = "yourCertificatePassword";
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("Path/to/your/clientCert.pfx"), keyPassphrase.toCharArray());
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(keyStore, keyPassphrase.toCharArray())
.build();
return HttpClients.custom()
.setSSLContext(sslContext)
.build();
}
}

Related

Spring Gateway - multiple httpclients in Spring Gateway?

I have many filters that I use to manipulate different requests.
I'm overriding the default netty httpClient provided by Spring Gateway so I can set programatically some sslContext - mTLS in my case. This is fine, but I will also need to have a second netty httpClient so I can connect to other server with no ssl at all, or just regular tls.
I was wondering if it's possible either to set sslContext on specific filters or to decide what httpClient to use based on the filter or some other extension point that spring gateway provides.
Do you think it is possible to have multiple httpClients and decide which one to use?
I tried to create a new bean which configures a httpClient and set some sslContext like this:
#Bean
public reactor.netty.http.client.HttpClient httpClient() {
final PrivateKey privateKey = getPrivateKey();
final X509Certificate x509Certificate = getX509Certificate();
final SslContext sslContext =
SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.keyManager(privateKey, x509Certificate)
.build();
return reactor.netty.http.client.HttpClient.create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
}
While I use this httpClient to connect via mTLS it works fine, but I'd like to decide what httpClient to use based on specific filters.

What type of certificate spring-boot application needs to have HTTPS connection?

I would like to make clients require HTTPS protocol to connect to my spring-boot backend application but I have no idea what kind of certificate I need for this.
Any certificate is good for this? Or I need to "turn on" some configurations?
Well, best you can do is to generate self-signed certificate.
There are many examples.
Also, you have to configure your backend.
Add dependency:
implementation 'org.apache.httpcomponents:httpclient:4.5'
Provide RestTemplate bean:
#Bean
private RestTemplate restTemplate() {
SSLContext sslContext = buildSslContext();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
HttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
private SSLContext buildSslContext() {
try {
char[] keyStorePassword = sslProperties.getKeyStorePassword();
return new SSLContextBuilder()
.loadKeyMaterial(
KeyStore.getInstance(new File(sslProperties.getKeyStore()), keyStorePassword),
keyStorePassword
).build();
} catch (Exception ex) {
throw new IllegalStateException("Unable to instantiate SSL context", ex);
} finally {
sslProperties.setKeyStorePassword(null);
sslProperties.setTrustStorePassword(null);
}
}
Provide required SSL properties in your application.properties or application.yaml file:
server:
ssl:
enabled: true
key-store: /path/to/key.keystore
key-store-password: password
key-alias: alias
trust-store: /path/to/truststore
trust-store-password: password
That's it. Now you can see your Tomcat is starting on 8080 (or another port) (https).
Alternatively, you can use my spring boot starter
1, You need a certificate for the application
there is two kind of certificates: self-signed certificate and ca-issued certificate
self-signed certificate
you can sign a certificate yourself use keytool
self-signed certificate has some disadvantage, browers will show a red alert page when access self-sign certificate site, and client need import the corresponding self-sign ca root certificate to communicate with server
ca-issued certificate
you can get a ca-issued certificate from let's encrypt
ca-issued certificate do not have all those disadvantage mentioned above ,it just work
2, You also need setting some spring boot configurations, see Spring Boot Docs - How to configure ssl

Certificate Based authentication, client share certificate to server

I have a requirement from a server application to share a SSL certificate. step i did:
I generated a self signed certificate against the Domain IP address(don't have domain name) where my application is deployed.
i shared the certificate to the server. they will keep the certificate in their trust store.
server is validating the request for the IP address. if the request are not coming from the IP address they are stopping them.
My question:
i have a spring boot application. do i need to make any change in my code for the certificate i have generated. if yes then what is the change.
Yes you need to make changes in your code. You need to load your keystore(with keypair) and if required also load your truststore into your http client. Most of the http clients require a SSLContext, so this would be sufficient for you:
KeyStore keyStore = ...;
TrustStore trustStore = ...;
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
// Spring provides by default RestTemplate as HTTP Client, this client is an Apache HTTP Client wrapper
// The setup would be:
HttpClient httpClient = HttpClient.newBuilder();
.sslContext(sslFactory.getSslContext());
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory)

Does calling https service by Resttemplate work for all services whose certificate has been imported in the trustore of my client service?

I have a restemplate which can make call to multiple external systems over https.
I configured the resttemplate like so :
SSLContext sslContext = SSLContextBuilder
.create()
.loadTrustMaterial(key, keyPassword)
.build();
HttpClient client = HttpClients
.custom()
.setSSLContext(sslContext)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.build();
HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
client);
httpComponentsClientHttpRequestFactory.setConnectTimeout(5000);
httpComponentsClientHttpRequestFactory.setReadTimeout(30000);
return new RestTemplate(httpComponentsClientHttpRequestFactory);
I have setup 2 mock services on 2 separate VMs with ssl enabled and I am testing this restemplate by calling those services over https. And it works.
What I want to confirm is that configuring the restemplate as shown in the above code and importing the certificates of the different services to be called in by the client truststore should work without any additional configurations right?
I am confused because in the code in this example here : https://github.com/jonashackt/spring-boot-rest-clientcertificates-docker-compose the author has used 2 different factories i.e. serverTomClientHttpRequestFactory & serverAliceClientHttpRequestFactory, however mine works with a single restemplate. can someone please shed some light on this topic?

Disable TLS certificate check for WebTestClient

I am using Spring WebFlux WebTestClient in integration tests. I create client using WebTestClient.bindToServer(). Tested server serves resources with HTTPS. I have following code:
WebTestClient webTestClient = WebTestClient.bindToServer()
.baseUrl("https://secured.example.com")
.build();
webTestClient.get().uri("/index.html")
.exchange()
.expectStatus().isOk();
There is no need to use valid certificate in tests. How can I configure WebTestClient to trust all certificates?
P.S. I can configure WebClient to trust everyone. But WebTestClient is more suitable for writing tests.
You can configure that with a custom ClientHttpConnector that you can provide to the WebTestClient.
// create a custom connector and customize TLS configuration there.
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(options -> options...);
WebTestClient client = WebTestClient.bindToServer(connector);
This has been fixed recently in SPR-16168.

Resources