Secure Spring Cloud Config
source link: https://piotrminkowski.wordpress.com/2019/12/03/secure-spring-cloud-config/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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 pattern 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 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 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 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 need to have keystore that allows to decrypt the password. The architecture is visualized on the following picture.
Here’s the required configuration on the client side. Like I have mentioned before, account-service need to use key for decryption. It has to be set usingencrypt.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 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 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 on the client side
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 need to 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 account-service application. It establishes 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 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 a 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.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK