项目中敏感配置信息通常须要进行加密处理,好比数据库密码,Spring Boot内置不提供加密支持,不能加密配置文件信息,在官方文档中提供了自定义Environment和Spring Cloud Vault两种解决方案。使用jasypt-spring-boot是另外一种方案。html
Spring Cloud Vault为HashiCorp Vault的客户端,支持访问HashiCorp Vault内存储的数据,避免了在Spring Boot程序中存储敏感数据。java
本文详细介绍了如何使用jasypt-spring-boot、Spring Cloud Vault和HashiCorp Vault,如何使用Vault的AWS Secret、Database Secret、AWS EC2认证和AWS IAM认证。mysql
spring: datasource: password: a3Ehaf0f/S1Rt6JfOGfQ+w== jwt: secret: a3Ehaf0f/S1Rt6JfOGfQ+w==
package org.itrunner.heroes.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import java.io.IOException; import java.util.Map; public class DecryptedEnvironmentPostProcessor implements EnvironmentPostProcessor { private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader(); @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { Resource path = new ClassPathResource("config.yml"); PropertySource<Map<String, Object>> propertySource = loadYaml(path); environment.getPropertySources().addLast(propertySource); } private PropertySource<Map<String, Object>> loadYaml(Resource path) { if (!path.exists()) { throw new IllegalArgumentException("Resource " + path + " does not exist"); } try { OriginTrackedMapPropertySource propertySource = (OriginTrackedMapPropertySource) loader.load("custom-resource", path).get(0); return new DecryptedMapPropertySource(propertySource); } catch (IOException ex) { throw new IllegalStateException("Failed to load yaml configuration from " + path, ex); } } private static class DecryptedMapPropertySource extends MapPropertySource { public DecryptedMapPropertySource(OriginTrackedMapPropertySource propertySource) { super(propertySource.getName(), propertySource.getSource()); } @Override public Object getProperty(String name) { Object value = super.getProperty(name); if (value instanceof CharSequence) { // 执行解密,返回明文 return "DecryptedValue"; } return value; } } }
自定义的EnvironmentPostProcessor需在META-INF/spring.factories内注册:linux
org.springframework.boot.env.EnvironmentPostProcessor=org.itrunner.heroes.config.DecryptedEnvironmentPostProcessor
有三种方式集成jasypt-spring-boot:git
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency>
<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot</artifactId> <version>2.1.0</version> </dependency>
@Configuration @EnableEncryptableProperties public class MyApplication { ... }
@Configuration @EncryptablePropertySource(name = "EncryptedProperties", value = "classpath:encrypted.properties") public class MyApplication { ... }
或者使用@EncryptablePropertySources:github
@Configuration @EncryptablePropertySources({@EncryptablePropertySource("classpath:encrypted.properties"), @EncryptablePropertySource("file:/path/to/encrypted2.properties")}) public class MyApplication { .... }
Key | Required | Default Value |
---|---|---|
jasypt.encryptor.password | True | - |
jasypt.encryptor.algorithm | False | PBEWithMD5AndDES |
jasypt.encryptor.bean | False | jasyptStringEncryptor |
jasypt.encryptor.keyObtentionIterations | False | 1000 |
jasypt.encryptor.poolSize | False | 1 |
jasypt.encryptor.providerName | False | null |
jasypt.encryptor.saltGeneratorClassname | False | org.jasypt.salt.RandomSaltGenerator |
jasypt.encryptor.stringOutputType | False | base64 |
jasypt.encryptor.proxyPropertySources | False | false |
jasypt.encryptor.property.prefix | False | ENC( |
jasypt.encryptor.property.suffix | False | ) |
默认,加密算法为PBEWithMD5AndDES,加解密bean name为jasyptStringEncryptor,加密的数据需使用ENC()包裹。
全部这些属性均可在配置文件中声明,但加密密码不该存储在配置文件中,而应使用系统属性、命令行参数传入,只要名称为jasypt.encryptor.password便可:算法
java -jar jasypt-spring-boot-demo.jar --jasypt.encryptor.password=password 或 java -Djasypt.encryptor.password=password -jar jasypt-spring-boot-demo.jar
也可在application.properties 或 application.yml中使用环境变量:spring
jasypt.encryptor.password=${JASYPT_ENCRYPTOR_PASSWORD:}
配置文件示例:sql
spring: jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect hibernate: ddl-auto: update properties: hibernate: default_schema: heroes format_sql: true jdbc: lob: non_contextual_creation: true show-sql: true datasource: platform: postgresql driver-class-name: org.postgresql.Driver url: jdbc:postgresql://localhost:5432/postgres username: hero password: ENC(a3Ehaf0f/S1Rt6JfOGfQ+w==) initialization-mode: never jasypt: encryptor: algorithm: PBEWithMD5AndDES password: 1qefhQH7mRR4LADVettR stringOutputType: base64 property: prefix: ENC( suffix: )
使用CLI工具JasyptPBEStringEncryptionCLI生成加密数据,以下:数据库
java -cp jasypt-1.9.2.jar org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI input="inputdata" password=secretkey algorithm=PBEWithMD5AndDES
执行后,输出以下:
----ENVIRONMENT----------------- Runtime: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 25.191-b12 ----ARGUMENTS------------------- algorithm: PBEWithMD5AndDES input: hero password: 1qefhQH7mRR4LADVettR ----OUTPUT---------------------- a3Ehaf0f/S1Rt6JfOGfQ+w==
生成后,使用ENC(密文)替换明文数据便可。
HashiCorp Vault提供集中管理机密(Secret)和保护敏感数据的服务,可经过UI、CLI或HTTP API访问。HashiCorp Vault使用GO语言编写。
根据您的系统下载HashiCorp Vault,而后解压zip包,其中为一可执行文件。
以linux系统为例:
$ unzip vault_1.0.2_linux_amd64.zip $ sudo chown root:root vault $ sudo chmod 755 vault $ sudo mv vault /usr/local/bin/ $ vault --version
帮助
直接运行vault可查看支持的命令:
$ vault Usage: vault <command> [args] Common commands: read Read data and retrieves secrets write Write data, configuration, and secrets delete Delete secrets and configuration list List data or secrets login Authenticate locally agent Start a Vault agent server Start a Vault server status Print seal and HA status unwrap Unwrap a wrapped secret Other commands: audit Interact with audit devices auth Interact with auth methods kv Interact with Vault's Key-Value storage lease Interact with leases namespace Interact with namespaces operator Perform operator-specific tasks path-help Retrieve API help for paths plugin Interact with Vault plugins and catalog policy Interact with policies secrets Interact with secrets engines ssh Initiate an SSH session token Interact with tokens
运行 vault [command] [subcommand] -h可查看命令支持的参数。
path-help 查看系统、Secret引擎、认证方法等路径支持的配置,在实际应用中常常用到。好比:
$ vault path-help sys/ $ vault path-help database/ $ vault path-help database/roles $ vault path-help aws/ $ vault path-help auth/token/ $ vault path-help auth/aws/
说明:要启用相应功能才能查看路径。
自动完成
linux下,Vault支持命令自动完成功能,安装后输入vault [tab]会显示命令提示,需执行如下命令安装:
$ vault -autocomplete-install $ exec $SHELL
安装后将在~/.bashrc内添加以下内容:
complete -C /usr/local/bin/vault vault
dev模式启动Vault
以dev模式启动不需任何配置,数据保存在内存中。
$ vault server -dev
控制台输出以下内容:
==> Vault server configuration: Api Address: http://127.0.0.1:8200 Cgo: disabled Cluster Address: https://127.0.0.1:8201 Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", max_request_duration: "1m30s", max_request_size: "33554432", tls: "disabled") Log Level: (not set) Mlock: supported: true, enabled: false Storage: inmem Version: Vault v1.0.1 Version Sha: 08df121c8b9adcc2b8fd55fc8506c3f9714c7e61 WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory and starts unsealed with a single unseal key. The root token is already authenticated to the CLI, so you can immediately begin using Vault. You may need to set the following environment variable: $ export VAULT_ADDR='http://127.0.0.1:8200' The unseal key and root token are displayed below in case you want to seal/unseal the Vault or re-authenticate. Unseal Key: xSahEjtRQMMwbyBW6+rIzE2RRJ4d8X7BmAyPsSk63yE= Root Token: s.5bnclu8POKx2WCxETB4u8RqF Development mode should NOT be used in production installations!
其中,Unseal Key、Root Token要保存下来。以dev模式启动Vault其状态是unseal的,不须要使用Unseal Key解封服务器。访问Vault须要使用Root Token。建议将Vault服务器地址保存到环境变量VAULT_ADDR中,不然使用命令行访问vault时须要指定-address参数。
查看Vault Server状态:
$ vault status -address=http://127.0.0.1:8200
说明:-address默认为https://127.0.0.1:8200
从浏览器登陆Vault,在地址栏输入http://localhost:8200 :
在Token文本框内输入“Root Token”,进入Vault主界面:
从命令行登陆Vault:
$ vault login -method=token -address=http://127.0.0.1:8200 Token (will be hidden): Success! You are now authenticated. The token information displayed below is already stored in the token helper. You do NOT need to run "vault login" again. Future Vault requests will automatically use this token. Key Value --- ----- token s.1Pv48heTmZhXjm0bBd84Muef token_accessor 3gfMlTXFPHX3ehMQzkJUrk3o token_duration ∞ token_renewable false token_policies ["root"] identity_policies [] policies ["root"]
Vault支持多种登陆认证方式,默认启用了token方式。
从命令行查看启用的认证方法:
$ vault auth list Path Type Accessor Description ---- ---- -------- ----------- token/ token auth_token_cd421269 token based credentials
Vault支持多种Secret引擎,一些引擎只是存储和读取数据,如kv;一些引擎链接到其余服务并根据须要生成动态凭据,如AWS、database;一些引擎提供加密服务(如transit)、证书生成(如pki)等。默认启用了kv(Key-Value)和cubbyhole引擎。
从命令行查看启用的Secret引擎:
$ vault secrets list Path Type Accessor Description ---- ---- -------- ----------- cubbyhole/ cubbyhole cubbyhole_835f8a75 per-token private secret storage identity/ identity identity_0ba84c63 identity store secret/ kv kv_9558dfb7 key/value secret storage sys/ system system_5f7114e7 system endpoints used for control, policy and debugging
咱们在kv引擎secret下建立一secret供后面测试使用,以下:
也可使用命令行:
$ vault kv put secret/heroes-api hello=coco
查询secret:
$ vault kv get secret/heroes-api
以前使用dev模式启动Vault,接下来讲明真实环境如何配置。
以非dev模式启动Vault必须提供至少一个配置文件,下面建立配置文件vault.hcl:
$ sudo mkdir --parents /etc/vault.d $ sudo touch /etc/vault.d/vault.hcl $ sudo chown --recursive ec2-user:ec2-user /etc/vault.d $ sudo chmod 640 /etc/vault.d/vault.hcl
配置文件支持HCL (HashiCorp Configuration Language)和JSON格式,vault.hcl内容以下:
ui = true storage "file" { path = "/usr/vault/data" } listener "tcp" { address = "0.0.0.0:8200" tls_cert_file = "/etc/vault.d/cert.pem" tls_key_file = "/etc/vault.d/privkey.pem" } api_addr = "https://10.188.12.119:8200"
参数:
生成自签名证书:
$ openssl genrsa -out privkey.pem $ openssl req -x509 -new -key privkey.pem -out cert.pem -days 365 -subj /C=CN/ST=Beijing/L=Beijing/CN=vault.itrunner.org/OU=itrunner/O=itrunner/emailAddress=sjc-925@163.com
使用自签名证书时须要配置环境变量VAULT_CACERT:
$ export VAULT_CACERT='/etc/vault.d/cert.pem'
Spring Cloud Vault经过HTTPS协议访问Vault时需配置客户端证书,执行如下命令将cert.pem导入到keystore中:
$ keytool -importcert -keystore keystore.jks -file cert.pem -noprompt -storepass changeit -alias heroes
启动前先受权vault使用mlock syscall:
$ sudo setcap cap_ipc_lock=+ep /usr/local/bin/vault
不然会显示以下错误:
Error initializing core: Failed to lock memory: cannot allocate memory This usually means that the mlock syscall is not available. Vault uses mlock to prevent memory from being swapped to disk. This requires root privileges as well as a machine that supports mlock. Please enable mlock on your system or disable Vault from using it. To disable Vault from using it, set the `disable_mlock` configuration option in your configuration file.
启动Vault:
$ vault server -config=/etc/vault.d/vault.hcl
kill掉上面的vault进程,配置vault为系统服务。
建立vault.service:
$ sudo touch /etc/systemd/system/vault.service
内容以下:
[Unit] Description="HashiCorp Vault - A tool for managing secrets" Documentation=https://www.vaultproject.io/docs/ Requires=network-online.target After=network-online.target ConditionFileNotEmpty=/etc/vault.d/vault.hcl [Service] User=ec2-user Group=ec2-user SecureBits=keep-caps AmbientCapabilities=CAP_IPC_LOCK Capabilities=CAP_IPC_LOCK+ep CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK ExecStart=/usr/local/bin/vault server -config=/etc/vault.d/vault.hcl ExecReload=/bin/kill --signal HUP $MAINPID KillMode=process KillSignal=SIGINT Restart=on-failure RestartSec=5 TimeoutStopSec=30 StartLimitIntervalSec=60 StartLimitBurst=3 [Install] WantedBy=multi-user.target
启动Vault:
$ sudo systemctl enable vault $ sudo systemctl start vault $ sudo systemctl status vault
首次启动vault后须要执行初始化操做。
$ vault operator init
初始化后生成加密key、unseal key、Initial Root Token,这些数据要保存到安全的地方。
Unseal Key 1: 1OlGbwCZ/y4IeULDGWdi1x3I4weOil8sWanlZ5M3gUN8 Unseal Key 2: LwILr0IuyKLwpooN8d7C6mQPr/AuzqzMq20RhKQlw8gR Unseal Key 3: OMr0B1n4ugZErUWzwsoA3rFZw3v3nsJM5oQWocgr9SYo Unseal Key 4: a1m2Wbz+tlv1e7cTsidXKa1Yt/DTbzaFJlza2s/khUau Unseal Key 5: ZuL66Av5SOH9gYLii2VHec6CcWUktXk99qabWfcSAF9H Initial Root Token: s.1Pv48heTmZhXjm0bBd84Muef Vault initialized with 5 key shares and a key threshold of 3. Please securely distribute the key shares printed above. When the Vault is re-sealed, restarted, or stopped, you must supply at least 3 of these keys to unseal it before it can start servicing requests. Vault does not store the generated master key. Without at least 3 key to reconstruct the master key, Vault will remain permanently sealed! It is possible to generate new unseal keys, provided you have a quorum of existing unseal keys shares. See "vault operator rekey" for more information.
初始化后Vault Server处于封印状态,因不知如何解密存储的数据,因此不能读取。初始化输出的内容中“Vault initialized with 5 key shares and a key threshold of 3”,意味着为了解封须要5个key中的3个,执行解封命令以下:
$ vault operator unseal
选取3个key,执行3次上面的命令直到Sealed状态为false:
Unseal Key (will be hidden): Key Value --- ----- Seal Type shamir Initialized true Sealed false Total Shares 5 Threshold 3 Version 1.0.1 Cluster Name vault-cluster-654a8704 Cluster ID 91e5ea90-1a78-45c8-36f6-99a0ba7b5eec HA Enabled false
使用Initial Root Token登陆Vault:
$ vault login s.1Pv48heTmZhXjm0bBd84Muef
登陆成功后,输出以下结果:
Success! You are now authenticated. The token information displayed below is already stored in the token helper. You do NOT need to run "vault login" again. Future Vault requests will automatically use this token. Key Value --- ----- token s.1Pv48heTmZhXjm0bBd84Muef token_accessor 3gfMlTXFPHX3ehMQzkJUrk3o token_duration ∞ token_renewable false token_policies ["root"] identity_policies [] policies ["root"]
root用户能够从新封印Vault:
$ vault operator seal
Vault支持集群部署,更多内容请查阅官方文档。
Root Token具备最高权限,最佳实践不该存储Root Token,仅在必要时使用vault operator generate-root命令生成,用毕撤销token。
撤销token
$ vault token revoke -self
生成Root Token
$ vault operator generate-root -init A One-Time-Password has been generated for you and is shown in the OTP field. You will need this value to decode the resulting root token, so keep it safe. Nonce 94e81220-dc59-16c5-1f08-180551cfa158 Started true Progress 0/3 Complete false OTP kVpqIjLf7BZQgNUbEBAuQPikRk OTP Length 26
$ vault operator generate-root Operation nonce: 94e81220-dc59-16c5-1f08-180551cfa158 Unseal Key (will be hidden): Nonce 94e81220-dc59-16c5-1f08-180551cfa158 Started true Progress 1/3 Complete false
须要输入3次Unseal Key,成功后将输出Encoded Token:
Encoded Token GHhHHBovfg9dEQAiASNhFiEFMT0DOjw+Gx4
$ vault operator generate-root -decode=GHhHHBovfg9dEQAiASNhFiEFMT0DOjw+Gx4 -otp=kVpqIjLf7BZQgNUbEBAuQPikRk
建立Token,设定有效时间,不指定policy
$ vault token create -ttl 10m Key Value --- ----- token s.8DibgV8wlTJq3ygtcfK4ne2K token_accessor NuElYtSnxF51JXli3LC6XKHM token_duration 10m token_renewable true token_policies ["root"] identity_policies [] policies ["root"]
新token为当前使用token的子token,权限继承自当前使用的token。
过时后可renew token:
vault token renew s.8DibgV8wlTJq3ygtcfK4ne2K
建立Token,指定Policy
Policy有以下几种权限:
# This section grants all access on "secret/*". Further restrictions can be # applied to this broad policy, as shown below. path "secret/*" { capabilities = ["create", "read", "update", "delete", "list"] } # Even though we allowed secret/*, this line explicitly denies # secret/super-secret. This takes precedence. path "secret/super-secret" { capabilities = ["deny"] }
建立策略文件,仅容许读取路径secret/heroes-api:
$ vi heroes-policy.hcl
内容以下:
path "secret/heroes-api" { capabilities = ["read"] }
上传策略:
$ vault policy write heroes heroes-policy.hcl
使用新策略建立Token:
$ vault token create -policy=heroes Key Value --- ----- token s.1bJDHR7VuSaHfquqmoQREioA token_accessor FGufmiTSqWcEaiZAg9nuLkvx token_duration 768h token_renewable true token_policies ["default" "heroes"] identity_policies [] policies ["default" "heroes"]
默认duration为768h,policy为"default" "heroes"。
使用新token登陆,查看secret:
$ vault login s.1bJDHR7VuSaHfquqmoQREioA $ vault kv get secret/heroes-api
使用AWS Secret引擎,每次访问建立新的AWS用户和登陆凭证(访问密钥),Vault并不存储凭证。
$ vault secrets enable aws Success! Enabled the aws secrets engine at: aws/
$ vault write aws/config/root access_key=VKIAJBRHKH6EVTTNXDHA secret_key=vCtSM8ZUEQ3mOFVlYPBQkf2sO6F/W7a5TVzrl3Oj region=cn-north-1 Success! Data written to: aws/config/root
说明,可以使用vault path-help命令查看路径配置:
$ vault path-help aws/
配置Vault建立的AWS用户的角色:
$ vault write aws/roles/my-role \ credential_type=iam_user \ policy_document=-<<EOF { "Version": "2012-10-17", "Statement": [ { "Sid": "Stmt1426528957000", "Effect": "Allow", "Action": [ "ec2:*" ], "Resource": [ "*" ] } ] } EOF Success! Data written to: aws/roles/my-role
$ vault read aws/creds/my-role Key Value --- ----- lease_id aws/creds/my-role/0bce0782-32aa-25ec-f61d-c026ff22106e lease_duration 768h lease_renewable true access_key VKIAJBRHKH6EVTTNXDHA secret_key vCtSM8ZUEQ3mOFVlYPBQkf2sO6F/W7a5TVzrl3Oj security_token <nil>
成功执行以上命令后可经过AWS IAM控制台查看新建立的用户。
可以使用lease_id来执行renew、revoke操做,执行revoke后将删除AWS用户:
$ vault lease revoke aws/creds/my-role/0bce0782-32aa-25ec-f61d-c026ff22106
使用Database Secret引擎可动态建立数据库用户并受权。目前支持的数据库有Cassandra、HANA、MongoDB、MSSQL、MySQL/MariaDB、PostgreSQL、Oracle。
$ vault secrets enable database
$ vault write database/config/my-postgresql-database plugin_name=postgresql-database-plugin allowed_roles="my-role" \ connection_url="postgresql://{{username}}:{{password}}@localhost:5432?sslmode=disable" \ username="postgres" password="postgres"
参数:
plugin_name 插件名称,使用postgresql数据库
allowed_roles 容许使用的vault角色
用户名、密码要使用模板。
可执行如下命令查看参数说明:
$ vault path-help database/config/my-postgresql-database
角色用来定义建立数据库用户和受权的脚本:
$ vault write database/roles/my-role db_name=my-postgresql-database \ creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \ GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \ default_ttl="1h" max_ttl="24h" Success! Data written to: database/roles/my-role
{{name}}和{{password}}将动态生成。
$ vault read database/creds/my-role Key Value --- ----- lease_id database/creds/my-role/789xpa9Rg3vVosLDMaTJKDnT lease_duration 1h lease_renewable true password A1a-PLaakX5RdWS5Wb7t username v-root-my-role-4sXjeClqaYXQF10lms8F-1547715152
AWS认证方法提供了一种自动索取Vault token的机制,支持EC2和IAM两种方式。
EC2认证,AWS被视为受信任的第三方,使用惟一表明每一个ec2实例的加密签名动态元数据信息进行身份验证,仅适用于EC2实例。
IAM认证,使用AWS IAM凭据签名的AWS请求进行身份验证,IAM凭据可来自IAM User、IAM Role,适用于EC2实例、Lambda函数及其余提供AWS凭据的环境。推荐使用IAM认证。
$ vault auth enable aws
根据您使用的AWS区域选择终端节点,AWS区域和终端节点。
$ vault write auth/aws/config/client access_key=VKIAJBRHKH6EVTTNXDHA secret_key=vCtSM8ZUEQ3mOFVlYPBQkf2sO6F/W7a5TVzrl3Oj \ endpoint=https://ec2.cn-north-1.amazonaws.com.cn iam_endpoint=https://iam.cn-north-1.amazonaws.com.cn \ sts_endpoint=https://sts.cn-north-1.amazonaws.com.cn \ iam_server_id_header_value=vault.itrunner.org
角色配置中至少含有一种约束条件。
EC2认证
$ vault write auth/aws/role/dev-role auth_type=ec2 bound_ami_id=ami-04f306762a9e9a056 policies=heroes max_ttl=768h
IAM User认证
$ vault write auth/aws/role/dev-role-iam-user auth_type=iam bound_iam_principal_arn=arn:aws-cn:iam::123456789012:user/test policies=heroes max_ttl=768h
登陆时仅需提供User访问密钥。
IAM Role认证
下例适用于EC2实例,inferred_entity_type为ec2_instance,认证的EC2须要授予IAM角色并符合约束条件。
$ vault write auth/aws/role/dev-role-iam auth_type=iam inferred_entity_type=ec2_instance inferred_aws_region=cn-north-1 bound_ami_id=ami-04f306762a9e9a056 bound_iam_principal_arn=arn:aws-cn:iam::123456789012:role/MyRole policies=heroes max_ttl=768h
EC2认证须要验证签名,须要配置region的公有证书。
中国(北京)地区的 AWS 公有证书以下:
-----BEGIN CERTIFICATE----- MIIDNjCCAh4CCQD3yZ1w1AVkTzANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJV UzEZMBcGA1UECBMQV2FzaGluZ3RvbiBTdGF0ZTEQMA4GA1UEBxMHU2VhdHRsZTEg MB4GA1UEChMXQW1hem9uIFdlYiBTZXJ2aWNlcyBMTEMwIBcNMTUwNTEzMDk1OTE1 WhgPMjE5NDEwMTYwOTU5MTVaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNo aW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24g V2ViIFNlcnZpY2VzIExMQzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB AMWk9vyppSmDU3AxZ2Cy2bvKeK3F1UqNpMuyeriizi+NTsZ8tQqtNloaQcqhto/l gsw9+QSnEJeYWnmivJWOBdn9CyDpN7cpHVmeGgNJL2fvImWyWe2f2Kq/BL9l7N7C P2ZT52/sH9orlck1n2zO8xPi7MItgPHQwu3OxsGQsAdWucdxjHGtdchulpo1uJ31 jsTAPKZ3p1/sxPXBBAgBMatPHhRBqhwHO/Twm4J3GmTLWN7oVDds4W3bPKQfnw3r vtBj/SM4/IgQ3xJslFcl90TZbQbgxIi88R/gWTbs7GsyT2PzstU30yLdJhKfdZKz /aIzraHvoDTWFaOdy0+OOaECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAdSzN2+0E V1BfR3DPWJHWRf1b7zl+1X/ZseW2hYE5r6YxrLv+1VPf/L5I6kB7GEtqhZUqteY7 zAceoLrVu/7OynRyfQetJVGichaaxLNM3lcr6kcxOowb+WQQ84cwrB3keykH4gRX KHB2rlWSxta+2panSEO1JX2q5jhcFP90rDOtZjlpYv57N/Z9iQ+dvQPJnChdq3BK 5pZlnIDnVVxqRike7BFy8tKyPj7HzoPEF5mh9Kfnn1YoSVu+61lMVv/qRjnyKfS9 c96nE98sYFj0ZVBzXw8Sq4Gh8FiVmFHbQp1peGC19idOUqxPxWsasWxQXO0azYsP 9RyWLHKxH1dMuA== -----END CERTIFICATE-----
将其保存在文件AWSpubkey中,而后执行下面命令导入证书:
$ vault write auth/aws/config/certificate/cn-cert aws_public_cert="$(cat AWSpubkey)"
说明:cn-cert为证书名称。
首次EC2认证时执行以下命令:
$ vault write auth/aws/login role=dev-role \ pkcs7="$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/pkcs7 | tr -d '\n')"
输出以下结果:
Key Value --- ----- token s.zVo29eIEkbdtpitFc3r5bjc3 token_accessor v1ZFeHEQyWidnCiLNLHs5lA7 token_duration 768h token_renewable true token_policies ["default" "heroes"] identity_policies [] policies ["default" "heroes"] token_meta_account_id 123456789012 token_meta_ami_id ami-03dc01372eae510e2 token_meta_instance_id i-015f7488c627dff71 token_meta_nonce dba47cd8-06ad-9de0-7fee-34b977409bc4 token_meta_region cn-north-1 token_meta_role dev-role token_meta_role_tag_max_ttl 0s
其中包含token、token_meta_nonce等,token权限为["default" "heroes"]。
再次登陆时须要提供nonce:
$ vault write auth/aws/login role=dev-role \ pkcs7="$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/pkcs7 | tr -d '\n')" \ nonce=dba47cd8-06ad-9de0-7fee-34b977409bc4
IAM认证,Vault使用了AWS GO SDK,须要指定AWS Region:
$ export AWS_REGION=cn-north-1
也能够设置环境变量AWS_SDK_LOAD_CONFIG为true,这样能够从.aws文件夹读取配置:
$ export AWS_SDK_LOAD_CONFIG=true
执行IAM User认证:
$ vault login -method=aws header_value=vault.itrunner.org role=dev-role-iam-user aws_access_key_id=ASIAIOSFODNN7EXAMPLE aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
访问密钥能够在命令行提供,也能够从.aws文件夹读取。输出以下结果:
Success! You are now authenticated. The token information displayed below is already stored in the token helper. You do NOT need to run "vault login" again. Future Vault requests will automatically use this token. Key Value --- ----- token s.zVo29eIEkbdtpitFc3r5bjc3 token_accessor v1ZFeHEQyWidnCiLNLHs5lA7 token_duration 768h token_renewable true token_policies ["default" "heroes"] identity_policies [] policies ["default" "heroes"] token_meta_inferred_aws_region n/a token_meta_inferred_entity_id n/a token_meta_inferred_entity_type n/a token_meta_account_id 123456789012 token_meta_auth_type iam token_meta_canonical_arn arn:aws-cn:iam::123456789012:user/test token_meta_client_arn arn:aws-cn:iam::123456789012:user/test token_meta_client_user_id AROAPQVNYAPQTLNZVYUL9
执行IAM Role认证:
$ vault login -method=aws header_value=vault.itrunner.org role=dev-role-iam
输出以下结果:
Success! You are now authenticated. The token information displayed below is already stored in the token helper. You do NOT need to run "vault login" again. Future Vault requests will automatically use this token. Key Value --- ----- token s.zVo29eIEkbdtpitFc3r5bjc3 token_accessor v1ZFeHEQyWidnCiLNLHs5lA7 token_duration 768h token_renewable true token_policies ["default" "heroes"] identity_policies [] policies ["default" "heroes"] token_meta_inferred_aws_region cn-north-1 token_meta_inferred_entity_id i-0744e18eb21c22cc1 token_meta_inferred_entity_type ec2_instance token_meta_account_id 123456789012 token_meta_auth_type iam token_meta_canonical_arn arn:aws-cn:iam::123456789012:role/MyRole token_meta_client_arn arn:aws-cn:sts::123456789012:assumed-role/MyRole/i-0744e18eb21c22cc1 token_meta_client_user_id AROAPQVNYAPQTLNZVYUL9
若未指定AWS Region,会输出以下错误:
Error authenticating: Error making API request. URL: PUT https://vault.itrunner.org:8200/v1/auth/aws/login Code: 400. Errors: * error making upstream request: received error code 403 from STS: <ErrorResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/"> <Error> <Type>Sender</Type> <Code>SignatureDoesNotMatch</Code> <Message>Credential should be scoped to a valid region, not 'us-east-1'. </Message> </Error> <RequestId>82c67acf-2e8e-11e9-a251-03d652cb82bc</RequestId> </ErrorResponse>
在POM中配置Spring Cloud Vault依赖:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-vault-dependencies</artifactId> <version>2.1.0.RELEASE</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-vault-config</artifactId> </dependency> </dependencies>
Spring Cloud Vault配置支持bootstrap.properties、bootstrap.yml两种格式。在项目resources目录下建立bootstrap.yml文件,内容以下:
spring: application: name: heroes-api cloud: vault: application-name: heroes-api host: vault.itrunner.org port: 8200 scheme: https authentication: TOKEN token: s.1Rkb4yNR5WYawHLcdpYxzrox connection-timeout: 5000 read-timeout: 15000 config: order: -10 ssl: trust-store: classpath:keystore.jks trust-store-password: changeit kv: enabled: true backend: secret profile-separator: / default-context: application application-name: heroes-api
参数含义:
authentication: TOKEN 设置认证方式,Spring Cloud Vault支持的认证方式有:TOKEN, APPID, APPROLE, AWS_EC2, AWS_IAM, AZURE_MSI, CERT, CUBBYHOLE, GCP_GCE, GCP_IAM, KUBERNETES
kv.enabled: true 访问kv引擎数据
kv.backend: secret 设置secret的路径
Spring Cloud Vault从如下路径查找secret属性数据:
/secret/{application}/{profile} /secret/{application} /secret/{default-context}/{profile} /secret/{default-context}
application名字由如下配置属性决定:
spring.cloud.vault.kv.application-name spring.cloud.vault.application-name spring.application.name
使用https协议时需配置trust-store。
访问方式与读取Spring Boot配置文件属性相同,如下代码则会读取/secret/heroes-api/hello值:
@Value("${hello}") String name;
从HashiCorp Vault AWS Secret引擎获取凭证,需增长spring-cloud-vault-config-aws依赖:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-vault-config-aws</artifactId> <version>2.1.0.RELEASE</version> </dependency> </dependencies>
增长以下配置启用AWS后端:
spring.cloud.vault: aws: enabled: true role: my-role backend: aws access-key-property: cloud.aws.credentials.accessKey secret-key-property: cloud.aws.credentials.secretKey
参数:
role AWS Secret引擎中的角色名称
access-key-property 存储AWS access key的属性名称
secret-key-property 存储AWS secret key的属性名称
代码中读取生成用户凭证:
@Value("${cloud.aws.credentials.accessKey}") String accessKey; @Value("${cloud.aws.credentials.secretKey}") String secretKey;
每次读取建立AWS用户和访问密钥。
为利用Vault Database Secret引擎动态建立数据库凭证需添加spring-cloud-vault-config-databases依赖:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-vault-config-databases</artifactId> <version>2.1.0.RELEASE</version> </dependency> </dependencies>
增长以下配置启用Database后端:
spring.cloud.vault: database: enabled: true role: my-role backend: database username-property: spring.datasource.username password-property: spring.datasource.password
参数:
role Database Secret引擎中的角色名称
username-property 存储数据库用户名的属性名称
password-property 存储数据库密码的属性名称
用户名密码保存在spring.datasource.username和spring.datasource.password内,datasource中无需再配置。
首先修改heroes-policy.hcl,以下:
path "secret/heroes-api" { capabilities = ["read"] } path "secret/data/heroes-api" { capabilities = ["create", "read", "update", "delete", "list"] } path "secret/data/application" { capabilities = ["create", "read", "update", "delete", "list"] } path "secret/application" { capabilities = ["read"] }
从新写入policy:
$ vault policy write heroes heroes-policy.hcl
配置bootstrap.yml:
spring: application: name: heroes-api cloud: vault: application-name: heroes-api host: vault.itrunner.org port: 8200 scheme: https authentication: AWS_EC2 connection-timeout: 5000 read-timeout: 15000 config: order: 10 ssl: trust-store: classpath:keystore.jks trust-store-password: changeit kv: enabled: true backend: secret profile-separator: / default-context: application application-name: heroes-api aws-ec2: role: dev-role aws-ec2-path: aws identity-document: http://169.254.169.254/latest/dynamic/instance-identity/pkcs7 nonce: 0bcf5e01-9c32-168e-49a0-5cb717e60a3f
参数说明:
authentication: AWS_EC2 启用AWS_EC2认证
aws-ec2.aws-ec2-path Spring Cloud Vault默认路径为aws-ec2,HashiCorp Vault默认路径为aws,二者要一致
AWS IAM认证,Spring Vault在生成签名的请求时须要使用aws-java-sdk-core, 增长依赖:
<dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-java-sdk-core</artifactId> <version>1.11.513</version> </dependency>
下面咱们来看一下生成签名请求的过程。
Spring Vault org.springframework.vault.authentication.AwsIamAuthentication部分源代码
protected static Map<String, String> createRequestBody(AwsIamAuthenticationOptions options) { Map<String, String> login = new HashMap<>(); login.put("iam_http_request_method", "POST"); login.put("iam_request_url", Base64Utils.encodeToString(options.getEndpointUri().toString().getBytes())); login.put("iam_request_body", REQUEST_BODY_BASE64_ENCODED); String headerJson = getSignedHeaders(options); login.put("iam_request_headers", Base64Utils.encodeToString(headerJson.getBytes())); if (!StringUtils.isEmpty(options.getRole())) { login.put("role", options.getRole()); } return login; } ... private static String getSignedHeaders(AwsIamAuthenticationOptions options) { Map<String, String> headers = createIamRequestHeaders(options); AWS4Signer signer = new AWS4Signer(); DefaultRequest<String> request = new DefaultRequest<>("sts"); request.setContent(new ByteArrayInputStream(REQUEST_BODY.getBytes())); request.setHeaders(headers); request.setHttpMethod(HttpMethodName.POST); request.setEndpoint(options.getEndpointUri()); signer.setServiceName(request.getServiceName()); signer.sign(request, options.getCredentialsProvider().getCredentials()); Map<String, Object> map = new LinkedHashMap<>(); for (Entry<String, String> entry : request.getHeaders().entrySet()) { map.put(entry.getKey(), Collections.singletonList(entry.getValue())); } try { return OBJECT_MAPPER.writeValueAsString(map); } catch (JsonProcessingException e) { throw new IllegalStateException("Cannot serialize headers to JSON", e); } }
在org.springframework.vault.authentication.AwsIamAuthenticationOptions .AwsIamAuthenticationOptionsBuilder中硬编码了sts URI为“sts.amazonaws.com”,所以不能从地址推断出region(推断方法请看com.amazonaws.util.AwsHostNameUtils.parseStandardRegionName()):
private URI endpointUri = URI.create("https://sts.amazonaws.com/");
继续阅读com.amazonaws.auth.AWS4Signer源码,看如何生成签名:
com.amazonaws.auth.AWS4Signer部分源码
public void sign(SignableRequest<?> request, AWSCredentials credentials) { // anonymous credentials, don't sign if (isAnonymous(credentials)) { return; } AWSCredentials sanitizedCredentials = sanitizeCredentials(credentials); if (sanitizedCredentials instanceof AWSSessionCredentials) { addSessionCredentials(request, (AWSSessionCredentials) sanitizedCredentials); } final AWS4SignerRequestParams signerParams = new AWS4SignerRequestParams(request, overriddenDate, regionName, serviceName, AWS4_SIGNING_ALGORITHM, endpointPrefix); addHostHeader(request); request.addHeader(X_AMZ_DATE, signerParams.getFormattedSigningDateTime()); String contentSha256 = calculateContentHash(request); if ("required".equals(request.getHeaders().get(X_AMZ_CONTENT_SHA256))) { request.addHeader(X_AMZ_CONTENT_SHA256, contentSha256); } final String canonicalRequest = createCanonicalRequest(request, contentSha256); final String stringToSign = createStringToSign(canonicalRequest, signerParams); final byte[] signingKey = deriveSigningKey(sanitizedCredentials, signerParams); final byte[] signature = computeSignature(stringToSign, signingKey, signerParams); request.addHeader(AUTHORIZATION, buildAuthorizationHeader(request, signature, sanitizedCredentials, signerParams)); proce***equestPayload(request, signature, signingKey, signerParams); }
在生成的签名请求中必须包含region参数,如未增长配置,最终获得的region将是"us-east-1"。有兴趣的同窗能够继续跟踪源码com.amazonaws.util.AwsHostNameUtils.parseRegion() -> parseRegionNameByInternalConfig() -> InternalConfig.Factory.getInternalConfig() -> InternalConfig load(),其中会读取自定义配置文件"awssdk_config_override.json"。下面咱们使用这个文件来定义region(放在resources目录便可,若有更好方法请留言),内容以下:
配置Region
{ "hostRegexToRegionMappings" : [ { "hostNameRegex" : "(.+\\.)?sts\\.amazonaws\\.com", "regionName" : "cn-north-1" }] }
配置bootstrap.yml
spring.cloud.vault: authentication: AWS_IAM aws-iam: role: dev-role-iam-user aws-path: aws server-name: vault.itrunner.org
2019港百女子前十
HashiCorp Vault
Learn about secrets management and data protection with HashiCorp Vault
Vault Documentation
GitHub - HashiCorp Vault
Spring Cloud Vault
spring-cloud-vault-config-samples
AWS Instance Metadata and User Data
AWS SDK for Go Developer Guide