Spring Cloud Config Server
Spring Cloud Config Server为外部配置提供基于HTTP资源的API(名称—值对或等效的YAML内容),通过使用@EnableConfigServer
注解,服务器可嵌入Spring Boot应用程序中,因此,以下应用程序是配置服务器:
ConfigServer.java
@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}
与所有Spring Boot应用程序一样,它默认在端口8080上运行,但你可以通过各种方式将其切换到更传统的端口8888。最简单的,也是设置默认配置存储库,是通过spring.config.name=configserver
启动它(Config Server jar中有一个configserver.yml
),另一种方法是使用你自己的application.properties
,如以下示例所示:
application.properties
server.port: 8888
spring.cloud.config.server.git.uri: file://${user.home}/config-repo
其中${user.home}/config-repo
是一个包含YAML和属性文件的git存储库。
在Windows上,如果文件URL是绝对的驱动器前缀,则需要额外的“/”(例如,file:///${user.home}/config-repo
)。
以下清单显示了在前面的示例中创建git存储库的步骤:
$ cd $HOME
$ mkdir config-repo
$ cd config-repo
$ git init .
$ echo info.foo: bar > application.properties
$ git add -A .
$ git commit -m "Add application.properties"
使用git存储库的本地文件系统仅用于测试,生产中你应该使用服务器托管配置存储库。
如果只保留文本文件,则配置存储库的初始克隆可以快速有效,如果存储二进制文件(尤其是大型文件),则第一次请求配置可能会出现延迟或服务器中遇到内存不足错误。
环境存储库
应该在哪里存储配置服务器的配置数据?管理此行为的策略是EnvironmentRepository
,为Environment
对象提供服务,此Environment
是Spring Environment
中域的浅拷贝(包括propertySources
作为主要功能),Environment
资源由三个变量参数化:
-
{application}
,它映射到客户端的spring.application.name
。 -
{profile}
,它映射到客户端(逗号分隔列表)的spring.profiles.active
。 -
{label}
,这是标记配置文件集“版本化”的服务器端特性。
存储库实现通常表现得像Spring Boot应用程序,从spring.config.name
等于{application}
参数,spring.profiles.active
等于{profiles}
参数加载配置文件。配置文件的优先规则也与常规Spring Boot应用程序中的规则相同:活动配置文件优先于默认配置文件,如果有多个配置文件,则最后一个配置文件获胜(类似于向Map
添加条目)。
以下示例客户端应用程序具有此bootstrap配置:
bootstrap.yml
spring:
application:
name: foo
profiles:
active: dev,mysql
像通常一样,Spring Boot应用程序也可以通过环境变量或命令行参数来设置这些属性。
如果存储库是基于文件的,则服务器从application.yml
(在所有客户端之间共享)和foo.yml
(以foo.yml
优先)创建Environment
。如果YAML文件中包含指向Spring配置文件的文档,那么这些文档将以更高的优先级应用(按列出的配置文件的顺序)。如果存在特定配置文件的YAML(或属性)文件,则这些文件的优先级也高于默认值,较高的优先级转换为Environment
中先前列出的PropertySource
(这些相同的规则适用于独立的Spring Boot应用程序)。
你可以将spring.cloud.config.server.accept-empty
设置为false
,以便如果应用程序找不到,则Server返回HTTP 404状态,默认情况下,此标志设置为true
。
健康指示器
Config Server附带一个健康指示器,用于检查配置的EnvironmentRepository
是否正常工作,默认情况下,它会向EnvironmentRepository
请求名为app
的应用程序、default
配置文件以及EnvironmentRepository
实现提供的默认标签。
你可以配置健康指示器以检查更多应用程序以及自定义配置文件和自定义标签,如以下示例所示:
spring:
cloud:
config:
server:
health:
repositories:
myservice:
label: mylabel
myservice-dev:
name: myservice
profiles: development
你可以通过设置spring.cloud.config.server.health.enabled=false
来禁用监控指示器。
安全性
你可以以对你有意义的任何方式保护你的Config Server(从物理网络安全到OAuth2承载令牌),因为Spring Security和Spring Boot为许多安全安排提供支持。
要使用默认的Spring Boot配置的HTTP Basic安全性,请在类路径中包含Spring Security(例如,通过spring-boot-starter-security
),默认值为user
的用户名和随机生成的密码,随机密码在实践中没有用,因此建议你配置密码(通过设置spring.security.user.password
)并对其进行加密(有关如何执行此操作的说明,请参阅下文)。
加密和解密
要使用加密和解密特性,你需要在JVM中安装完整的JCE(默认情况下不包括),你可以从Oracle下载“Java Cryptography Extension(JCE)Unlimited Strength Jurisdiction Policy Files”并按照安装说明进行操作(实际上,你需要将JRE lib/security
目录中的两个策略文件替换为你下载的策略文件)。
如果远程属性源包含加密内容(以{cipher}
开头的值),则在通过HTTP发送到客户端之前对它们进行解密,此设置的主要优点是,属性值在“静止”时不必是纯文本格式(例如,在git存储库中)。如果某个值无法解密,则会从属性源中删除该值,并添加一个附加属性,该属性具有相同的键但前缀为invalid
,且值为“不适用”(通常为<n/a>
),这主要是为了防止密文被用作密码并意外泄露。
如果为配置客户端应用程序设置远程配置存储库,则它可能包含类似于以下内容的application.yml
:
spring:
datasource:
username: dbuser
password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'
.properties
文件中的加密值不能用引号括起来,否则,该值不会被解密,以下示例显示了有效的值:
application.properties
spring.datasource.username: dbuser
spring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ
你可以安全地将此纯文本推送到共享的git存储库,并且密码仍然受到保护。
服务器还公开/encrypt
和/decrypt
端点(假设这些端点是安全的并且只能由授权代理访问),如果编辑远程配置文件,则可以使用Config Server通过POST到/encrypt
端点来加密值,如以下示例所示:
$ curl localhost:8888/encrypt -d mysecret
682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
如果你加密的值中包含需要进行URL编码的字符,则应使用
--data-urlencode
选项进行
curl
以确保它们已正确编码。
请确保不要在加密值中包含任何
curl
命令统计信息,将值输出到文件可以帮助避免此问题。
通过/decrypt
也可以使用反向操作(前提是服务器配置了对称密钥或完整密钥对),如以下示例所示:
$ curl localhost:8888/decrypt -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
mysecret
如果你用
curl
测试,那么使用
--data-urlencode
(替代
-d
)或设置一个显式的
Content-Type: text/plain
来确保
curl
在有特殊字符时正确编码数据(’+’特别棘手)。
获取加密值并添加{cipher}
前缀,然后再将其放入YAML或属性文件中,然后再提交并将其推送到远程(可能不安全)存储。
/encrypt
和/decrypt
端点也接受/*/{name}/{profiles}
形式的路径,当客户端调用主环境资源时,可用于在每个应用程序(名称)和每个配置文件的基础上控制加密。
要以这种精细的方式控制加密,你还必须提供类型为
TextEncryptorLocator
的
@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
参数是必需的(尽管有
--
前缀)。
密钥管理
Config Server可以使用对称(共享)密钥或非对称密钥(RSA密钥对),非对称选择在安全性方面更优越,但使用对称密钥通常更方便,因为它是在bootstrap.properties
中配置的单个属性值。
要配置对称密钥,需要将encrypt.key
设置为秘密字符串(或使用ENCRYPT_KEY
环境变量将其排除在纯文本配置文件之外)。
无法使用
encrypt.key
配置非对称密钥。
要配置非对称密钥,请使用密钥库(例如,由JDK附带的keytool
实用工具创建),密钥库属性是encrypt.keyStore.*
,*
等于:
属性 | 描述 |
---|---|
encrypt.keyStore.location | 包含Resource 的位置 |
encrypt.keyStore.password | 保存解锁密钥库的密码 |
encrypt.keyStore.alias | 标识要使用存储中的哪个密钥 |
加密是使用公钥完成的,并且需要私钥进行解密,因此,原则上,如果只想加密(并准备使用私钥本地解密值),则只配置服务器中的公钥。实际上,你可能不希望在本地进行解密,因为它会围绕所有客户端传播密钥管理过程,而不是将其集中在服务器中,另一方面,如果你的配置服务器相对不安全且只有少数客户端需要加密属性,那么它可能是一个有用的选项。
创建用于测试的密钥库
要创建用于测试的密钥库,可以使用类似于以下内容的命令:
$ 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
将server.jks
文件放在类路径中(例如),然后在bootstrap.yml
中为Config Server创建以下设置:
encrypt:
keyStore:
location: classpath:/server.jks
password: letmein
alias: mytestkey
secret: changeme
使用多个密钥和密钥轮换
除了加密属性值中的{cipher}
前缀之外,Config Server还会在(Base64编码)密文开头之前查找零个或多个{name:value}
前缀,密钥传递给TextEncryptorLocator
,它可以执行为密文定位TextEncryptor
所需的任何逻辑。如果已配置密钥库(encrypt.keystore.location
),则默认定位器将查找具有key
前缀提供的别名的密钥,密文类似于以下内容:
foo:
bar: `{cipher}{key:testkey}...`
定位器查找名为“testkey”的密钥,也可以通过在前缀中使用{secret:…}
值来提供秘密,但是,如果未提供,则默认使用密钥库密码(这是你在构建密钥库时未指定秘密),如果你提供秘密,你还应该使用自定义SecretLocator
加密秘密。
当密钥仅用于加密几个字节的配置数据时(也就是说,它们没有在其他地方使用),在加密方面几乎不需要密钥轮换。但是,你可能偶尔需要更改密钥(例如,在发生安全漏洞时),在这种情况下,所有客户端都需要更改其源配置文件(例如,在git中)并在所有密文中使用新的{key:…}
前缀,请注意,客户端需要首先检查Config Server密钥库中的密钥别名是否可用。
如果你想让Config Server处理所有加密和解密,
{name:value}
前缀也可以作为纯文本添加发布到
/encrypt
端点。
提供加密属性
有时你希望客户端在本地解密配置,而不是在服务器中执行此操作。在这种情况下,如果你提供encrypt.*
配置来定位密钥,你仍然可以拥有/encrypt
和/decrypt
端点,但是你需要通过在bootstrap.[yml|properties]
中放置spring.cloud.config.server.encrypt.enabled=false
来明确地关闭输出属性的解密,如果你不关心端点,那么如果你不配置密钥或启用标志,它应该可以工作。
提供选择性的格式
环境端点的默认JSON格式非常适合Spring应用程序使用,因为它直接映射到Environment
抽象,如果你愿意,可以通过向资源路径添加后缀(“.yml”,“.yaml”或“.properties”)来使用与YAML或Java属性相同的数据,对于不关心JSON端点结构或它们提供的额外元数据的应用程序来说,这可能很有用(例如,不使用Spring的应用程序可能会受益于此方法的简单性)。
YAML和属性表示有一个额外的标志(作为名为resolvePlaceholders
的布尔查询参数提供),表示源文档中的占位符(在标准的Spring ${…}
形式)应该在渲染之前在输出中解析(在可能的情况),对于不了解Spring占位符约定的消费者而言,这是一个有用的特性。
使用YAML或属性格式存在限制,主要与元数据丢失有关。例如,JSON为属性源的有序列表结构,其名称与源相关,YAML和属性形式合并为单个映射,即使值的来源有多个源,并且原始源文件的名称丢失。此外,YAML表示不一定是支持存储库中YAML源的可靠表示,它由一个平面属性源列表构成,必须对键的形式进行假设。