spring boot版本为:2.5.6
spring cloud版本为:2020.0.4
spring cloud config版本为:3.0.5
spring cloud config server
加密和解密
要使用加密和解密特性,您需要在JVM中安装全功能JCE(默认情况下不包括它)。您可以从Oracle下载“Java Cryptography Extension (JCE) Unlimited Strength管辖权策略文件”,并按照安装说明操作(实际上,您需要用下载的策略文件替换JRE lib/security目录中的两个策略文件)。
oracle下载地址
如果远程属性源包含加密的内容(以{cipher}
开头的值),它们在通过HTTP发送给客户端之前会被解密。这种设置的主要优点是,当属性值处于“静止状态”(例如,在git存储库中)时,它们不需要以纯文本形式显示。如果一个值不能被解密,它将从属性源中删除,并使用相同的键添加一个附加属性,但前缀为invalid
和表示“不适用”(通常为<n/a>
)的值。这在很大程度上是为了防止密文被用作密码而意外泄露。
如果您为config client应用程序设置远程配置存储库,那么它可能包含一个application.yml,类似以下内容:
application.yml
spring:
datasource:
username: dbuser
password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'
应用程序中的加密值。在application.properties文件中不能用引号括起来。否则,该值不被解密。下面的示例显示了可以工作的值:
application.properties
spring.datasource.username: dbuser
spring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ
您可以安全地将这个纯文本推送到共享的git存储库,并且保护密码。
服务器还公开/encrypt和/decrypt端点(假设这些端点将被保护并且只能由授权代理访问)。如果您正在编辑远程配置文件,可以使用Config Server通过POST到/encrypt端点来加密值,例如:
$ curl localhost:8888/encrypt -s -d mysecret
682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
如果您使用curl进行测试,那么使用
——data-urlencode(
而不是-d
),并在值的前面加上=
(curl需要这样做)或设置显式的Content-Type: text/plain
,以确保curl在存在特殊字符('+'特别棘手)时正确编码数据。
请确保加密值中不包含任何curl命令统计信息,这就是为什么示例使用-s选项来关闭它们。将该值输出到文件可以帮助避免这个问题。
反向操作也可以通过/decrypt
(如果服务器配置了对称密钥或完整密钥对),如下面的示例所示:
$ curl localhost:8888/decrypt -s -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
mysecret
在将加密值放入YAML或属性文件以及提交并将其推入远程(可能不安全)存储之前,获取加密值并添加{cipher}
前缀。
/encrypt
和/decrypt
端点也都接受/*/{application}/{profiles}
形式的路径,当客户端调用主环境资源时,可以使用该路径在每个应用程序(名称)和每个配置文件的基础上控制加密。
要以这种细粒度方式控制加密,还必须提供
TextEncryptorLocator
类型的@Bean
,该@Bean为每个名称和配置文件创建不同的加密器。默认情况下提供的加密不会这样做(所有加密都使用相同的密钥)。
spring命令行客户端(安装了spring Cloud CLI扩展)也可以用来加密和解密,如下面的示例所示:
$ spring encrypt mysecret --key foo
682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
$ spring decrypt --key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
mysecret
如果要在文件中使用密钥(如用于加密的RSA公钥),需要在密钥值前面加上“@”,并提供文件路径,示例如下:
$ spring encrypt mysecret --key @${HOME}/.ssh/id_rsa.pub
AQAjPgt3eFZQXwt8tsHAVv/QHiY5sI2dRcR+...
——key
参数是强制性的(尽管有——
前缀)。
密钥管理
配置服务器可以使用对称(共享)密钥或非对称(RSA密钥对)密钥。非对称选择在安全性方面更优越,但使用对称密钥通常更方便,因为它在bootstrap.properties
文件中采用单独的属性值来配置
要配置对称密钥,需要设置encrypt.key
到一个秘密字符串(或者使用ENCRYPT_KEY
环境变量使其不在纯文本配置文件中)。
不能使用
encrypt.key
配置非对称密钥
要配置非对称密钥,请使用keystore(例如,由JDK自带的keytool工具创建)。密钥存储库属性是encrypt.keyStore.*
,*
等于:
属性 | 描述 |
---|---|
encrypt.keyStore.location | 包含资源位置 |
encrypt.keyStore.password | 保存解锁密钥存储库的密码 |
encrypt.keyStore.alias | 标识要使用存储中的哪个键 |
encrypt.keyStore.type | 要创建的KeyStore的类型。默认为jks |
加密使用公钥,解密需要私钥。因此,原则上,如果您只想加密(并且准备在本地使用私钥对值进行解密),则只能在服务器中配置公钥。在实践中,您可能不希望在本地进行解密,因为它将密钥管理过程分散到所有客户机,而不是将其集中在服务器上。另一方面,如果配置服务器相对不安全,并且只有少数客户端需要加密属性,那么它可能是一个有用的选项。
创建用于测试的密钥库
要创建用于测试的密钥存储库,可以使用如下命令:
$ keytool -genkeypair -alias mytestkey -keyalg RSA \
-dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" \
-keypass changeme -keystore server.jks -storepass letmein
当使用JDK 11或以上版本时,您可能会在使用上面的命令时收到以下警告。在这种情况下,您可能希望确保keypass和storepass值匹配。
Warning: Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -keypass value.
将server.jks
文件放在类路径中,然后在配置服务器的bootstrap.yml中,创建以下设置:
encrypt:
keyStore:
location: classpath:/server.jks
password: letmein
alias: mytestkey
secret: changeme
在当前版本中,经测试,需要放到
application.yml
文件中,不知道是不是官方文档没有及时维护,所以看文档时,还请加以辩证
使用多个键和键旋转
除了加密属性值中的{cipher}
前缀外,配置服务器还会在(Base64编码的)密文开始之前查找0个或多个{name:value}
前缀。密钥被传递给一个TextEncryptorLocator
,它可以执行任何需要的逻辑来为密码定位一个TextEncryptor
。如果您已经配置了密钥存储库(encrypt.keystore.location
),默认定位器将查找由密钥前缀提供的别名的密钥,并使用类似如下的密文:
foo:
bar: `{cipher}{key:testkey}...`
定位器寻找一个名为“testkey”
的键。也可以在前缀中使用{secret:…}
值来提供secret
。但是,如果没有提供密钥库,则默认使用密钥库密码(这是在构建密钥库且不指定密钥时获得的密码)。如果您确实提供了一个secret
,您还应该使用自定义的SecretLocator
加密这个secret
。
当密钥只用于加密配置数据的几个字节时(也就是说,它们不在其他地方使用),从加密的角度来看,密钥旋转几乎是没有必要的。但是,您可能偶尔需要更改密钥(例如,在出现安全漏洞的情况下)。在这种情况下,所有客户端都需要更改他们的源配置文件(例如,在git中),并在所有密码中使用一个新的{key:…}
前缀。请注意,客户端需要首先检查Config Server密钥存储库中是否有密钥别名。
如果你想让配置服务器处理所有加密和解密,
{name:value}
前缀也可以作为纯文本添加到/encrypt
端点。(将{name:value}
放入请求体中)
提供加密属性
有时您希望客户机在本地解密配置,而不是在服务器中进行。在这种情况下,如果您提供encrypt.*
配置来定位密钥,你仍然可以使用/encrypt
和/decrypt
端点,但是你需要通过在bootstrap.[yml|properties]
放置spring.cloud.config.server.encrypt=false
显式地关闭对传出属性的解密。如果您不关心端点,那么如果您不配置密钥或启用标志,它应该可以工作。
在当前版本中,经测试,放在
bootstrap.yml
无效,需要放在application.yml
中
提供纯文本
您的应用程序可能需要通用的纯文本配置文件,而不是使用Environment抽象(或使用YAML或属性格式的另一种表示形式)。配置服务器通过/{application}/{profile}/{label}/{path}
的附加端点提供这些,其中application、profile和label与常规环境端点具有相同的含义,但是path是一个文件名(例如log.xml)的路径。这个端点的源文件的定位方式与环境端点的定位方式相同。属性和YAML文件使用相同的搜索路径。但是,不是聚合所有匹配的资源,而是只返回第一个匹配的资源。
定位资源后,使用提供的应用程序名称、概要文件和标签的有效Environment解析正常格式的占位符(${…})。通过这种方式,资源端点与环境端点紧密集成。
与用于环境配置的源文件一样,
profile
用于解析文件名。因此,如果您想要一个特定于概要文件的文件,/*/development/*/logback.xml
可以通过一个名为logback-development.xml
的文件来解析(而不是logback.xml)
。
如果不想提供标签并让服务器使用默认标签,可以提供
useDefaultLabel
请求参数。因此,前面的默认配置文件示例可以是/sample/default/nginx.conf?useDefaultLabel
。
目前,Spring Cloud Config可以为git、SVN、本地后端和AWS S3提供明文服务。对git、SVN和本地后端的支持是相同的。AWS S3的工作方式略有不同。下面的小节展示了它们的工作原理
Git、SVN和本地后端
考虑以下GIT或SVN存储库或本地后端示例:
application.yml
nginx.conf
其中nginx.conf看起来像这样:
server {
listen 80;
server_name ${nginx.server.name};
}
application.yml看起来像这样:
nginx:
server:
name: example.com
---
spring:
profiles: development
nginx:
server:
name: develop.com
/sample/default/master/nginx.conf资源如下所示:
server {
listen 80;
server_name example.com;
}
/sample/development/master/nginx.conf如下所示:
server {
listen 80;
server_name develop.com;
}
AWS S3
要为AWS s3提供纯文本服务,配置服务器应用程序需要包含对Spring Cloud AWS的依赖。关于如何设置该依赖项的详细信息,请参见Spring Cloud AWS参考指南。然后,您需要配置Spring Cloud AWS,请参见Spring Cloud AWS参考指南
解密纯文本
缺省情况下,明文文件中的加密值不被解密。为了启用对纯文本文件的解密,在bootstrap.[yml|properties]
中设置spring.cloud.config.server.encrypt.enabled=true
和spring.cloud.config.server.encrypt.plainTextEncrypt=true
。
解密纯文本文件只支持YAML、JSON和属性文件扩展名。
如果启用此特性,并且请求不受支持的文件扩展名,则将不会解密文件中的任何加密值。
嵌入配置服务器
配置服务器最好作为独立应用程序运行,但如果需要,可以将其嵌入到另一个应用程序中。只需使用@EnableConfigServer
注释。在这种情况下可以使用的可选属性是spring.cloud.config.server.bootstrap
,它是一个标志,表示服务器应该从自己的远程存储库配置自身。该标志默认关闭,因为它可能会延迟启动,但是当嵌入在另一个应用程序中时,以与其他应用程序相同的方式初始化是有意义的。当将spring.cloud.config.server.bootstrap
设置为true时,还必须使用复合环境存储库配置。例如:
spring:
application:
name: configserver
profiles:
active: composite
cloud:
config:
server:
composite:
- type: native
search-locations: ${HOME}/Desktop/config
bootstrap: true
如果使用bootstrap标志,配置服务器需要在bootstrap.yml中配置其名称和存储库URI。
要更改服务器端点的位置,您可以(可选)设置spring.cloud.config.server.prefix
,例如“/ config”,以提供前缀下的资源。前缀应该开始但不以“/”结尾。它应用于Config Server中的@RequestMappings(即Spring Boot前缀server.servletPath和server.contextPath)之下。
如果您想直接从后端存储库(而不是从配置服务器)读取应用程序的配置,这基本上是一个没有端点的嵌入式配置服务器。如果不使用@EnableConfigServer
注释(只设置spring.cloud.config.server.bootstrap=true
),则可以完全关闭端点。
推送通知和Spring Cloud Bus
许多源代码存储库提供商(如Github, Gitlab, Gitea, Gitee, Gogs,或Bitbucket)通过webhook
通知你存储库中的变化。您可以通过提供商的用户界面将webhook
配置为一个URL和一组您感兴趣的事件。例如,Github对webhook
使用POST, JSON体包含一个提交列表和头(X-Github-Event)设置为push。如果添加一个spring-cloud-config-monitor
依赖项,并在配置服务器中激活Spring Cloud Bus
,那么将启用/monitor
端点。
当webhook
被激活时,配置服务器发送一个RefreshRemoteApplicationEvent
,目标是它认为可能已经改变的应用程序。可以对变更检测进行策略化。然而,默认情况下,它查找与应用程序名称匹配的文件中的更改(例如,foo.properties
针对foo
应用程序,而application.properties
针对所有应用程序)。当您希望覆盖该行为时,要使用的策略是PropertyPathNotificationExtractor
,它接受请求头和请求体作为参数,并返回更改后的文件路径列表。
Github, Gitlab, Gitea, Gitee, Gogs或Bitbucket都是默认配置。除了来自Github、Gitlab、Gitee或Bitbucket的JSON通知,你还可以通过使用表单编码的体参数path={application}的模式post到/monitor来触发更改通知。这样做会广播到匹配{application}模式(可以包含通配符)的应用程序。
只有在配置服务器和客户端应用程序中激活spring cloud bus时,才会传输
RefreshRemoteApplicationEvent
。
默认配置还检测本地git存储库中的文件系统更改。在这种情况下,webhook不会被使用。但是,一旦您编辑配置文件,就会广播刷新。
欢迎关注我的公众号:程序员L札记