Secure Spring Cloud Config

Secure Spring Cloud Config

If you are building microservices architecture on top of Spring Boot and Spring Cloud I’m almost sure that one of projects you are using is Spring Cloud Config. Spring Cloud Config is responsible for implementing one of the most popular microservices patterns called distributed configuration. It provides server-side (Spring Cloud Config Server) and client-side (Spring Cloud Config Server) support for externalized configuration in a distributed system. In this article I focus on security aspects related to that project. If you are interested in some basics please refer to my previous article about it Microservices Configuration With Spring Cloud Config.
The topics covered in this article are:

  • Encryption and decryption of sensitive data
  • Setting up SSL configuration on the server side
  • SSL connection on the client side

1. Encryption and decryption

If you use JDK 8 or lower, you first need to download and install the Java Cryptography Extension (JCE) provided by Oracle. It consists of two JAR files (local_policy.jar and US_export_policy.jar), which need to override the existing policy files in the JRE lib/security directory. Since our sample applications use JDK 11 we don’t have to install any additional libraries. JDK 9 and later use by default the unlimited policy files. Sample applications source code is available in my GitHub repository sample-spring-cloud-security in the branch secure_config: https://github.com/piomin/sample-spring-cloud-security/tree/secure_config.
If the remote property sources stored on the config server contain encrypted data, their values should be prefixed with {cipher} and wrapped in quotes to designate it as a YAML file. Wrapping in quotes is not necessary for .properties files. If such a value cannot be decrypted, it is replaced by an additional value (usually <n a=""></n>) under the same key prefixed with invalid.
Now, let’s consider the case where we are securing our API with Basic Auth mechanism. To enable it for our sample Spring Boot application (for example account-service) we first need to include spring-boot-starter-security artifact except standard Web and Cloud Config Client starters:

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>   

When adding Security starter Basic Auth is enabled by default. We can override username and password. First, let’s encrypt a sample password using encrypt endpoint exposed by Spring Cloud Config Server. This endpoint is responsible for encrypting a given parameter using the key stored on the server. Our test password is demotest. Let’s encrypt it:

encrypt

There is also an opposite endpoint that allows to decrypt already encrypted parameter. Let’s use it just for check:

secure-spring-cloud-config-decrypt

Now, we will prepare the following YAML properties file for account-service, that is stored on the config server.

spring:  
  application:
    name: account-service
  security:
    user:
      password: '{cipher}AQDDqhpwfKGBPf1KZ0lPuHpRHb1mGTdRt3VMioFhTFyNaVJB5Sn+3zguMJUUntXq3Sib/2zg8n6RG5DHKApXCd5DeVicrOkarqrWVRFc2VO10Yg6anGvHRfqWKD0D7T8xXGrDx0i06UR2sj5ZyNRiGT/Lzy+2pWMxZrSepP9caiVKElLt1a/oTwyiTECA2A/5KulHG39+bJPEeZqycLffmjdwWAy3DXxDmDbEqsxsTnCD9wE9Dc4etl1eWhtHG1UR8QkPmUti+eqj2rDzAqr59p4FHlbc6VnGgUmL2fz2MQFqzghdGaBhWLuC5v/TdpqJsyred2o7n653opDD5NeEZRy1/Uipwu9hf563mM9wCLyKj6lx2ieuNag6YSOiKTHVCU='

The sample application account-service is retrieving properties from config server. Before using encrypted values it has to decrypt it. Therefore, it needs to have a keystore that allows to decrypt the password. The architecture is visualized in the following picture.

secure-spring-cloud-config-architecture
Here’s the required configuration on the client side. Like I have mentioned before, account-service need to use a key for decryption. It has to be set using encrypt.keyStore.* properties.

encrypt:
  keyStore:
    location: classpath:/account.jks
    password: 123456
    alias: account
    secret: 123456

After startup application is available on port 8091. If we try to call any endpoint without Authorization header we should receive HTTP 401.

secure-spring-cloud-config-401

In the request visible below we set Authorization header. The default username is user. The password has been already set to demotest.

secure-spring-cloud-config-communication

2. Configure SSL on Spring Cloud Config Server

You have probably noticed that I used parameter --insecure for curl examples in the previous section. I didn’t pass any key and the server does not reject my requests. Now, we will force Spring Cloud Config Server to verify client SSL certificate during connection attempt. The appropriate configuration is visible below. We are forcing client authentication by setting property server.ssl.client-auth to true. We also should set truststore and keystore locations.

server:  
  port: ${PORT:8888}
  ssl:
    enabled: true
    client-auth: need
    key-store: classpath:config.jks
    key-store-password: 123456
    trust-store: classpath:config.jks
    trust-store-password: 123456
    key-alias: discovery

The next step is to generate keys and certificates. We will use the command-line tool provided under JRE — keytool. Let’s begin with a well-known command for generating a keystore file with a key pair. One keystore is generated for a config
server, while the second for the client application, in this particular case, for account-service:

$ keytool -genkey -alias account -store type JKS -keyalg RSA -keysize 2048 -
$ keystore account.jks -validity 3650
$ keytool -genkey -alias config -storetype JKS -keyalg RSA -keysize 2048 -
$ keystore config.jks -validity 3650

Then, the self-signed certificate has to be exported from a keystore to a file — for example, with a .cer or .crt extension. You will then be prompted for the password you provided during the keystore generation:

$ keytool -exportcert -alias account -keystore account.jks -file account.cer
$ keytool -exportcert -alias config -keystore config.jks -file config.cer

The certificate corresponding to the public key has been extracted from the keystore, so now it can be distributed to all interested parties. The public certificate from account-service should be included in the discovery server’s trust store and vice-versa:

$ keytool -importcert -alias config -keystore account.jks -file config.cer
$ keytool -importcert -alias account -keystore config.jks -file account.cer

There is also some additional configuration on the server-side. We have to enable native profile and encryption.

spring:
  application:
    name: config-service
  cloud:
    config:
      server:
        encrypt:
          enabled: false
  profiles:
    active: native

3. Configure SSL for Spring Cloud Config Client

After enabling SSL on the server side we may proceed to the client side implementation. We will have to use HttpClient to implement secure connection with a server. So, first let’s include the following artifact to the Maven dependencies:

<dependency>
   <groupId>org.apache.httpcomponents</groupId>
   <artifactId>httpclient</artifactId>
</dependency>

We use 2.2.1.RELEASE version of Spring Boot and Hoxton.RELEASE Release Train of Spring Cloud.

<properties>
   <java.version>11</java.version>
</properties>

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.2.1.RELEASE</version>
</parent>

<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-dependencies</artifactId>
         <version>Hoxton.RELEASE</version>
         <type>pom</type>
         <scope>import</scope>
      </dependency>
   </dependencies>
</dependencyManagement>

Spring Cloud Config Client uses RestTemplate for HTTP communication with a config server. This communication is performed in the bootstrap phase. That’s very important information, because we have to override RestTemplate bean properties before retrieving configuration sources from a config server. First, we need to implement secure connection using HttpClient and ConfigServicePropertySourceLocator bean. Here’s the implementation of custom ConfigServicePropertySourceLocator bean inside @Configuration class.

@Configuration
public class SSLConfigServiceBootstrapConfiguration {

   @Autowired
   ConfigClientProperties properties;

   @Bean
   public ConfigServicePropertySourceLocator configServicePropertySourceLocator() throws Exception {
      final char[] password = "123456".toCharArray();
      final ClassPathResource resource = new ClassPathResource("config.jks");

      SSLContext sslContext = SSLContexts.custom()
         .loadKeyMaterial(resource.getFile(), password, password)
         .loadTrustMaterial(resource.getFile(), password, new TrustSelfSignedStrategy()).build();
      CloseableHttpClient httpClient = HttpClients.custom()
         .setSSLContext(sslContext)
         .setSSLHostnameVerifier((s, sslSession) -> true)
         .build();
      HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
      ConfigServicePropertySourceLocator configServicePropertySourceLocator = new ConfigServicePropertySourceLocator(properties);
      configServicePropertySourceLocator.setRestTemplate(new RestTemplate(requestFactory));
      return configServicePropertySourceLocator;
   }

}

The configuration class defined above needs to be loaded on a bootstrap phase. To do that we have to create file spring.factories in the src/main/resources/META-INF directory:

org.springframework.cloud.bootstrap.BootstrapConfiguration = pl.piomin.services.account.SSLConfigServiceBootstrapConfiguration

Finally we may start the account-service application. It establishes a secure SSL connection with Spring cloud config Server using two-way (mutual) authentication. Here’s the fragment of logs during application startup.

logs

Conclusion

Our scenario may be extended with a secure SSL discovery connection based on Spring Cloud Netflix Eureka server. An example of secure discovery between Spring microservice and Eureka has been described in the following article: Secure Discovery with Spring Cloud Netflix Eureka. In this article, I show you the most interesting aspects related to distributed configuration security with Spring Cloud Config. For the working example of application you should refer to my GitHub repository https://github.com/piomin/sample-spring-cloud-security/tree/secure_config.

2 COMMENTS

comments user
Delight

This article saved me! Until something went wrong… It was running fine up until a few days ago and now I’m constantly getting “java.io.IOException:BC JKS store is read-only and only supports certificate entries”

I’m out of ideas!

    comments user
    piotr.minkowski

    Ok, but when do you receive such an exception. I run config-service, then discovery-service, and then account-service, and everything works perfectly fine on my local machine of course.

Leave a Reply