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
) 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:
There is also an opposite endpoint that allows to decrypt already encrypted parameter. Let’s use it just for check:
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.
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
.
In the request visible below we set Authorization
header. The default username is user
. The password has been already set to demotest
.
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.
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