近来在做spring cloud config相关的一些开发,由是重新学习了下,才发现spring cloud config还有很多以前没注意到的功能,比如:加密。

用途

众所周知,spring cloud config作为服务,包含两部分,一个是server本身,一个是配置数据,对于后台接vault或者数据库的,其安全性已有保障,此处不作赘述,而以文件形式存在的配置文件,无论是存在于远端git/svn,还是本地文件,从某种程度来说,它们仍然存在一定安全隐患。

尽管Spring Cloud Config通常不会发布到互联网上,但作为集中管理配置的服务,数据库密码等credentials信息,如果在文件中以明文形式出现,还是有泄露的风险,而encryption功能就是解决这一问题的。

效果

简而言之,你肯定不希望你的配置文件是这样:

1
2
3
4
spring:
datasource:
username: dbuser
password: aaabbb #明文存储的密码

如果可以用不对称加密如RSA等,以这样存储是不是更好?

1
2
3
4
spring:
datasource:
username: dbuser
password: '{cipher}AQCAC+Qi9TcNhNH0E0jnG0pYp6mO9PdBBdi+Tkl5m4049AHY3YTNHlL7gPrgrKgeqLNj5Cn39v3SqfK48B6BqiNXXTquxYfBDWbDk97U9Sa2RFB9sS+ZBUseYIyn4hgv/KxRnFaSIRWimDqzyh7ldc4ZMFgPn3WtFV7Vy4OQyfu8SsGMVLck9eNF6Sc9TjXj5kcPrteufAEbN7eWSSWgDUBtGcYqlG8rXhVrnn3O+fK6VKQT/lMmkuT2lTpl9jwNTY9kYgd2h7piXCdIPt8LFjYB3/cS70a1KbJIx926imuRBc8O5RyN/iME+pcz3jzeKQu2Ckn0zlf87Y5HHnMCRVsziIlxItesDq8h5II9eISW1Ln+Qu04yVNvnmgo1+UzeBw='

所以,第一个功能就是:加密。

Encryption & Decryption

有了加密,消费端就需要解密,即:decryption功能。

spring cloud config提供了两个接口,/encrypt 和 /decrypt ,用于加解密,开发人员可以用 /encrypt 对数据库密码、各种密码做加密后,保存在相应服务的application.yml文件中。

启动config-server后,执行:

1
curl http://localhost:8888/encrypt -d aaabbb

得到:

1
AQCAC+Qi9TcNhNH0E0jnG0pYp6mO9PdBBdi+Tkl5m4049AHY3YTNHlL7gPrgrKgeqLNj5Cn39v3SqfK48B6BqiNXXTquxYfBDWbDk97U9Sa2RFB9sS+ZBUseYIyn4hgv/KxRnFaSIRWimDqzyh7ldc4ZMFgPn3WtFV7Vy4OQyfu8SsGMVLck9eNF6Sc9TjXj5kcPrteufAEbN7eWSSWgDUBtGcYqlG8rXhVrnn3O+fK6VKQT/lMmkuT2lTpl9jwNTY9kYgd2h7piXCdIPt8LFjYB3/cS70a1KbJIx926imuRBc8O5RyN/iME+pcz3jzeKQu2Ckn0zlf87Y5HHnMCRVsziIlxItesDq8h5II9eISW1Ln+Qu04yVNvnmgo1+UzeBw=

解密(/decrypt)同理,不做赘述。不过,相对于我们去访问/decrypt接口获得原始密码,消费端在获得密码时,解码为明文,才是真正重要的。

spring cloud config提供两种解密方式:

  • 在服务端解密
    从config server通过接口对外提供的密码就是原文
  • 在消费端解密
    消费端获得的密码仍然是密文,接收到以后进行解密

具体实现

下面我们就来说一说,具体如何实现。

首先,我们需要先生成一个keystore。

什么是Keystore?

Java Keystore (JKS) 是安全证书的存储库,包含授权证书、公钥证书,以及相应的私钥,可用于SSL的加密。(原谅我的低水平直译)

在这里,我们使用它来对密码进行加解密,因此,我们需要使用keytool生成一个keystore,然后在config-server中配置keystore。

1. 升级JCE

在生成keystore之前,请先确认你已经升级了JCE,JDK中默认的JCE包不符合spring cloud config的位数要求,需从Oracle网站下载后,替换你的JDK下的两个文件。JCE-8官方下载地址

下载并解压后,按照说明文件将两个jar包拷贝到:$JAVA_HOME/jre/lib/security下,然后重启你的应用。

2. 生成 keystore文件

1
2
3
keytool -genkeypair -alias mykey -keyalg RSA -dname \
"CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=CN" \
-keypass mysecret -keystore my_keystore.jks -storepass mypassword

则在当前文件夹会看到一个名为 my_keystore.jks 的文件。

3. 给config-server配置keystore

bootstrap.yml:

1
2
3
4
5
6
7
encrypt:
failOnError: false
keyStore: # 与上面命令中的参数保持一致
location: my_keystore.jks
password: mypassword
alias: mykey
secret: mysecret

4. 指定解密方式

application.yml

1
2
3
4
5
6
spring:
cloud:
config:
server:
encrypt:
enabled: true

此处,spring.cloud.config.server.encrypt.enabled=true表示在服务端进行解密,即通过接口访问获得密码是明文,反之则为密文。

如果指定为true,即在服务端进行解密,执行:

1
curl -X "GET" http://localhost:8888/my-app/dev

后,会看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "my-app",
"profiles": [
"dev"
],
"label": "master",
"version": "a5c2c511ec2d9858e580cc3d2edfbae6c795aa8f",
"state": null,
"propertySources": [
{
"name": "git@gitlab.com:micro-services-sample/config-data.git/application-dev.yml",
"source": {
"spring.datasource.username": "dbuser",
"spring.datasource.password": "aaabbb",
}
}
]
}

而如果指定为false,则结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "my-app",
"profiles": [
"dev"
],
"label": "master",
"version": "a5c2c511ec2d9858e580cc3d2edfbae6c795aa8f",
"state": null,
"propertySources": [
{
"name": "git@gitlab.com:micro-services-sample/config-data.git/application-dev.yml",
"source": {
"spring.datasource.username": "dbuser",
"spring.datasource.password": "{cipher}AQBY3gLEjF+WShmQ2oYRbWJE1TPdI251QOS/mdYPE5rfOBEHhH0SghRK8yOsy0IdfKKSYDTXxLz7zNEk+wA4CNwUVIEcDPUXxy3pquxz/3Z6CMQhJKzOcCT2cUgECXLFOv9Q4GupFmFXSNepGoaZKuTwJDC+q6ajl/EjXcdIMu7ZhhM8898zlirNdL/sjMXY60NpCdXVgrE7ec9G3ID3itWnhNlethumsT1LSEk9o/HtWY3PqNOGMjV/EfIFSP5rHgzdbrEaIaOs2Q5gMWa1HLoag4j3/BD6P1OQqMLR7b5nUuC3CI2Rfv6FmUn4MDhrlMQmqR5B0naQ+CgIefMtdqd33zLQzytnDkFSlfmW7wUgCCY4sJ1QGOEI/k8RATbWN2E=",
}
}
]
}

两种方式对比

第一种,只需要config-server保存keystore即可,对于消费端没有任何影响,仅仅是存储在磁盘上的文件,始终是密文;
第二种,安全性更高,但需要消费端也通过同样的keystore对配置进行解密。

P.S. 常见错误:

  • invalid key size: 当前JCE生成的key size不符合Spring cloud config的要求,请前往oracle下载并升级JCE(地址文中有);
  • No key was installed for encryption service: 没有生成keystore文件。