Secure Discovery with Spring Cloud Netflix Eureka

Secure Discovery with Spring Cloud Netflix Eureka

Building a standard, not secure discovery mechanism with Spring Cloud Netflix Eureka is rather an easy thing to do. The same solution built over secure SSL communication between discovery client and server maybe a slightly more advanced challenge. I haven’t found any complete example of such an application on the web. Let’s try to implement it beginning from the server-side application.

1. Generate certificates

If you develop Java applications for some years you have probably heard about keytool. This tool is available in your ${JAVA_HOME}\bin directory and is designed for managing keys and certificates. We begin by generating a keystore for the server-side Spring Boot application. Here’s the appropriate keytool command that generates a certficate stored inside JKS keystore file named eureka.jks.

2. Setting up a secure Spring Eureka server

Since the Eureka server is embedded to the Spring Boot application, we need to secure it using standard Spring Boot properties. I placed generated keystore file eureka.jks on the application’s classpath. Now, the only thing that has to be done is to prepare some configuration settings inside application.yml that point to keystore file location, type, and access password.

server:
  port: 8761
  ssl:
    enabled: true
    key-store: classpath:eureka.jks
    key-store-password: 123456
    trust-store: classpath:eureka.jks
    trust-store-password: 123456
    key-alias: eureka

3. Setting up two-way SSL authentication

We will complicate our example a little. A standard SSL configuration assumes that only the client verifies the server certificate. We will force client’s certificate authentication on the server-side. It can be achieved by setting the property server.ssl.client-auth to need.

server:
  ssl:
    client-auth: need

It’s not all, because we also have to add client’s certficate to the list of trusted certificates on the server-side. So, first let’s generate client’s keystore using the same keytool command as for server’s keystore.

Now, we need to export certficates from generated keystores for both client and server sides.

Finally, we import the client’s certificate to the server’s keystore and the server’s certificate to the client’s keystore.

4. Running secure Spring Eureka server

The sample applications are available on GitHub in repository sample-secure-eureka-discovery (https://github.com/piomin/sample-secure-eureka-discovery.git). After running discovery-service application, Eureka is available under address https://localhost:8761. If you try to visit its web dashboard you get the following exception in your web browser. It means Eureka server is secured.

Well, Eureka dashboard is sometimes an useful tool, so let’s import client’s keystore to our web browser to be able to access it. We have to convert client’s keystore from JKS to PKCS12 format. Here’s the command that performs mentioned operation.

$ keytool -importkeystore -srckeystore client.jks -destkeystore client.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass 123456 -deststorepass 123456 -srcalias client -destalias client -srckeypass 123456 -destkeypass 123456 -noprompt

5. Client’s secure application configuration

When implementing a secure connection on the client-side, we generally need to do the same as in the previous step – import a keystore. However, it is not a very simple thing to do, because Spring Cloud does not provide any configuration property that allows you to pass the location of the SSL keystore to a discovery client. What’s worth mentioning Eureka client leverages the Jersey client to communicate with the server-side application. It may be surprising a little it is not Spring RestTemplate, but we should remember that Spring Cloud Eureka is built on top of Netflix OSS Eureka client, which does not use Spring libraries.
HTTP basic authentication is automatically added to your eureka client if you include security credentials to connection URL, for example http://piotrm:12345@localhost:8761/eureka. For more advanced configuration, like passing SSL keystore to HTTP client we need to provide @Bean of type DiscoveryClientOptionalArgs.
The following fragment of code shows how to enable SSL connection for discovery client. First, we set location of keystore and truststore files using javax.net.ssl.* Java system property. Then, we provide custom implementation of Jersey client based on Java SSL settings, and set it for DiscoveryClientOptionalArgs bean.

@Bean
public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() throws NoSuchAlgorithmException {
   DiscoveryClient.DiscoveryClientOptionalArgs args = new DiscoveryClient.DiscoveryClientOptionalArgs();
   System.setProperty("javax.net.ssl.keyStore", "src/main/resources/client.jks");
   System.setProperty("javax.net.ssl.keyStorePassword", "123456");
   System.setProperty("javax.net.ssl.trustStore", "src/main/resources/client.jks");
   System.setProperty("javax.net.ssl.trustStorePassword", "123456");
   EurekaJerseyClientBuilder builder = new EurekaJerseyClientBuilder();
   builder.withClientName("account-client");
   builder.withSystemSSLConfiguration();
   builder.withMaxTotalConnections(10);
   builder.withMaxConnectionsPerHost(10);
   args.setEurekaJerseyClient(builder.build());
   return args;
}

6. Enabling HTTPS on the client side

The configuration provided in the previous step applies only to communication between the discovery client and the Eureka server. What if we also would like to secure HTTP endpoints exposed by the client-side application? The first step is pretty the same as for the discovery server: we need to generate keystore and set it using Spring Boot properties inside application.yml.

server:
  port: ${PORT:8090}
  ssl:
    enabled: true
    key-store: classpath:client.jks
    key-store-password: 123456
    key-alias: client

During registration we need to “inform” Eureka server that our application’s endpoints are secured. To achieve it we should set property eureka.instance.securePortEnabled to true, and also disable non secure port, which is enabled by default.with nonSecurePortEnabled property.

eureka:
  instance:
    nonSecurePortEnabled: false
    securePortEnabled: true
    securePort: ${server.port}
    statusPageUrl: https://localhost:${server.port}/info
    healthCheckUrl: https://localhost:${server.port}/health
    homePageUrl: https://localhost:${server.port}
  client:
    securePortEnabled: true
    serviceUrl:
      defaultZone: https://localhost:8761/eureka/

7. Running secure Spring client’s application

Finally, we can run client-side application. After launching the application should be visible in Eureka Dashboard.

All the client application’s endpoints are registred in Eureka under HTTPS protocol. I have also override default implementation of actuator endpoint /info, as shown on the code fragment below.

@Component
public class SecureInfoContributor implements InfoContributor {

   @Override
   public void contribute(Builder builder) {
      builder.withDetail("hello", "I'm secure app!");
   }

}

Now, we can try to visit /info endpoint one more time. You should see the same information as below.

Alternatively, if you try to set on the client-side the certificate, which is not trusted by server-side, you will see the following exception while starting your client application.

Conclusion

Securing connection between microservices and Eureka server is only the first step of securing the whole system. We need to thing about secure connection between microservices and config server, and also between all microservices during inter-service communication with @LoadBalanced RestTemplate or OpenFeign client. You can find the examples of such implementations and many more in my book “Mastering Spring Cloud” (https://www.packtpub.com/application-development/mastering-spring-cloud).

9 COMMENTS

Stanislav

I checked out the sample project and started eureka and opened it with https://localhost:8761 but got a ERR_SSL_VERSION_OR_CIPHER_MISMATCH error. Any idea why?

    Piotr Mińkowski

    Hi,
    How did you run it?

      Sadun Samintha

      I got same issue too. I import complete application as one project and run eureka server first of all. I couldn’t log into eureka dashboard because of above error. Then run client but client couldn’t register with eureka. Could you please help me to resolve this?

        Piotr Mińkowski

        You mean ERR_SSL_VERSION_OR_CIPHER_MISMATCH error?

      hossein mahdavi

      hi piotr
      i have this problem too .
      every tings are fine . discovery and client connected on https mode
      but when i want to explore it on the chrome , mismatch error is showing
      can you help us friend ?

Rob Faber

Piotr,
Nice article! I see an issue with your eureka.instance configuration on the client (Chapter 6).
You explicitly reference ‘localhost’ in the statusPageUrl, healthCheckUrl and homePageUrl. Although this works locally, how would you set this up in a real environment? There you cannot reference to localhost because then the links from eureka to this service will not be correct.

Diolix

It’s a good course but too old 🙁

Can’t you update it ?

Thanks

    piotr.minkowski

    Hello,
    Can you point out what has changed there?

Diolix

Thanks so much for reply 🙂

The version of spring boot is 2.7.0
the cloud version is 2021.0.3

When I enable the server or the client, it’s done in the main class by adding respectively the annotation @EnableEurekaServer or @EnableEurekaClient

So most of your code doesn’t work anymore 🙁

So fare as I did I could make the server works, and there is no big difference form you course. And on the client side nothing work and it’s difficult to make it works. (I’m sure because I’m a nooby in it 😀 )

If you want I can live the github link, if you want to take a look at it.

Exit mobile version