注:本文总结自:lonegunmanb.github.io/essential-v…
现状分析
为何要使用秘钥管理?
面临的问题
- 代码或配置以明文形式记录敏感信息,开发者不小心把机密信息随着代码上传到公网的源码仓库造成泄密
- 敏感信息的生成、分发、保管、部署全流程经多人之手,缺乏有效的管控手段;
- 敏感信息生成之后长期有效,没有自动轮转机制,加大了泄露风险及影响程度。
- 执行密码轮换策略很痛苦
- 掌握机密的员工离职后可能泄密或是恶意报复
- 管理多个系统的机密非常麻烦
- 需要将机密信息安全地加密后存储,但又不想将密钥暴露给应用程序,以防止应用程序被入侵后连带密钥一起泄漏
- ....
机密蔓生
机密蔓生描述的是这样的问题,系统机密信息被零散地以不同形式保存在许多彼此不相关的地方,我们即不知道哪些系统保存了哪些机密,也不知道哪些机密被哪些系统哪些人获取了。即使我们发现某个机密信息流失在外,我们也无法回溯该机密信息是何人申请使用,何人泄漏,我们也无法确信立即从生产环境中吊销该机密不会导致生产环境的故障,因为我们不知道哪些系统正在使用这些机密。 机密蔓生引发了一连串的严重问题,导致我们既不能事前防备机密泄漏,也无法事后快速响应进行损害管制。
解决方式
- 建立机密的单一事实来源(Single Source Of Truth)
这个单一事实来源可以是目前公有云平台普遍提供的密钥管理服务(KMS),也可以是像 HashiCorp Vault 这样的开源、多云平台的软件 坚持所有的机密信息都是从单一事实来源获取,并且使用工具链确保单一事实来源中管理的机密发生变更时能够及时通知相关的合法应用更新机密信息,同时对谁有权使用什么样的机密、谁正在使用这些机密,某个机密是谁申请的这些信息进行集中管理,可以有效治理机密蔓生问题。 - 应用程序要与该唯一来源集成,从平台获取敏感信息,并完成续租和轮转等操作
- 部署工具要与该唯一来源集成,为应用程序注入登录平台所需的身份凭据
vault介绍
Vault 是一个强大的敏感信息管理工具,自带了多种认证引擎和密码引擎,并通过插件机制允许自定义引擎,可应用于多种常见的敏感信息保护场景,具体用法本文不做介绍,请参考Vault官方文档。至于部署发布工具与 Vault 的集成,与所使用的部署工具及发布流水线有关,可根据自身实际情况做相关的集成方案,本文不做讨论。
vault许可协议
Vault产品,采用商业源代码许可证(BSL 或 BUSL)v1.1
该许可大意是:您可以将许可作品用于生产用途,前提是您的使用不包括以托管或嵌入方式向第三方提供许可作品以与 HashiCorp 的许可作品的付费版本竞争。
协议文本描述:www.hashicorp.com/bsl
该协议的一些讨论:zhuanlan.zhihu.com/p/651264833
vault的架构
各个模块描述见:lonegunmanb.github.io/essential-v…
通过架构设计,我们可以对vault有一个初步的印象
Vault 的安全模型:
外部威胁:
- Vault 客户端和服务器之间没有默认的相互信任
- 集群内 Vault 实例之间的所有服务器之间的流量要使用TLS 来确保传输中数据的机密性和完整性
- Vault 使用的存储后端在设计上也是不受信任的
内部威胁:
如果攻击者已被允许对 Vault 进行某种级别的访问并且能够进行身份验证,则这是一种内部威胁
- 任何访问都基于最小权限的设计,都会有一个ACL访问策略对应
- 对令牌的认证方式都依据租约、续租、过期吊销的认证方式
- 封印的主秘钥:安装完成后,vault是处于封印状态的,需要使用使用秘钥分片,才能解封获取主秘钥
Unseal Key 1: K1pDKuDqxmnTPA9CMxxxxxbViLyn+GLy3FO/v
Unseal Key 2: I5TC4Ktte+xhmyxxxxxfffpWFP01tMfqZziyHm03Sc
Unseal Key 3: EKGr1sHXBz/xxxxxxhKUKv4AMGs7uyL2MDkXtopUj2
Unseal Key 4: on4XiIhocXDorrrrrrU6/FJXI0ootsr/RuDFfKXIqEG
Unseal Key 5: OgTZo1eYxZeFkkkllfdsadeereh8vx2Xph394lYSgFmAehorfx
Initial Root Token: hvs.IuGKDJwsfdSsaewSqghGHSWEQCy1B
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 root key. Without at least 3 keys to
reconstruct the root 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.
详见:lonegunmanb.github.io/essential-v…
vault的访问方式
- client客户端 待会操作就能看到命令行客户端
- 开放式API(restful方式) 根client客户端对应的restfule风格的请求方式
- web ui
本质上是restful的访问方式
下面通过ui介绍几种重要的概念:
- Secret:vault要保护的敏感信息。
- Access:Vault 支持多种身份验证方法,包括 GitHub、LDAP、AppRole 等。每个身份验证方法都有对应的使用场景;有些是面向人类用户的,而其他的则是面向验证机器身份的。完成后,返回Token令牌给用户
- Token:令牌是Vault中身份验证的核心方法;可以直接创建令牌,也可以根据外部身份动态生成令牌
- Policies:策略对应的是对某些secret的访问策略,有create/delete/read/update/list等,策略可以对应到Token,也可以对应到auth实体
其他概念见文档
机密管理的方法的介绍
下面,根据vault的文档,枚举出比较典型的,常用的机密管理方法,给大家介绍一下:
1. kv保存机密
- 查看kv 类型的secret
cyxinda@oldsix /cyxinda/vault vault secrets enable -path=mysql2 kv-v2
Success! Enabled the kv-v2 secrets engine at: mysql2/
cyxinda@oldsix /cyxinda/vault vault secrets list --detailed|grep "version:2"
bryan/ kv kv_924d6ede system system false replicated false false map[version:2] n/a b917c70e-9be4-785e-fe59-d0297678797b n/a v0.17.0+builtin n/a supported
devops/ kv kv_cee7c677 system system false replicated false false map[version:2] n/a ea3cb96a-a89b-8225-db6a-b0cfe20482a4 n/a v0.17.0+builtin n/a supported
mysql/ kv kv_dea680f1 system system false replicated false false map[version:2] n/a a96e604a-93e4-4a66-d07d-120bbca29781 n/a v0.17.0+builtin n/a supported
mysql2/ kv kv_2f89f8ad system system false replicated false false map[version:2] n/a 79ea1942-dc29-ced3-be7e-be70181783f0 n/a v0.17.0+builtin n/a supported
secret/ kv kv_2715109d system system false replicated false false map[version:2] n/a f3edc8d2-61e7-ad1a-0faa-2d39892d8edf n/a v0.17.0+builtin n/a supported
- 创建访问策略
cyxinda@oldsix /cyxinda/vault vault policy write mysql2-policy -<<EOF
path "mysql2/data/secret/*" {
capabilities = [ "create","delete","update","read" ]
}
EOF
Success! Uploaded policy: mysql2-policy
- 创建token
cyxinda@oldsix /cyxinda/vault vault token create -policy=mysql2-policy
Key Value
--- -----
token hvs.CAESICSEZ_64gncnccWHgsqJGKTCI__frdfu_UyPgE9zmipRGh4KHGh2cy5mTzdLVHJ4SU12bXNnY1pwSDJRMk1PRmI
token_accessor 8H3hSiuUVhUuCGRzRsE8fYPC
token_duration 768h
token_renewable true
token_policies ["default" "mysql2-policy"]
identity_policies []
policies ["default" "mysql2-policy"]
- 使用token登录
cyxinda@oldsix ~ vault login hvs.CAESICSEZ_64gncnccWHgsqJGKTCI__frdfu_UyPgE9zmipRGh4KHGh2cy5mTzdLVHJ4SU12bXNnY1pwSDJRMk1PRmI
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 hvs.CAESICSEZ_64gncnccWHgsqJGKTCI__frdfu_UyPgE9zmipRGh4KHGh2cy5mTzdLVHJ4SU12bXNnY1pwSDJRMk1PRmI
token_accessor 8H3hSiuUVhUuCGRzRsE8fYPC
token_duration 767h59m47s
token_renewable true
token_policies ["default" "mysql2-policy"]
identity_policies []
policies ["default" "mysql2-policy"]
- 查看当前登录的token
cyxinda@oldsix ~ vault token lookup
Key Value
--- -----
accessor 8H3hSiuUVhUuCGRzRsE8fYPC
creation_time 1715847979
creation_ttl 768h
display_name token
entity_id n/a
expire_time 2024-06-17T08:26:19.443195944Z
explicit_max_ttl 0s
id hvs.CAESICSEZ_64gncnccWHgsqJGKTCI__frdfu_UyPgE9zmipRGh4KHGh2cy5mTzdLVHJ4SU12bXNnY1pwSDJRMk1PRmI
issue_time 2024-05-16T08:26:19.443202741Z
meta <nil>
num_uses 0
orphan false
path auth/token/create
policies [default mysql2-policy]
renewable true
ttl 767h59m37s
type service
- 写入数据
cyxinda@oldsix ~ vault kv put -mount=mysql2 secret/db1 username=root bar=baz password=123456
===== Secret Path =====
mysql2/data/secret/db1
======= Metadata =======
Key Value
--- -----
created_time 2024-05-16T08:51:51.633033141Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 4
- 读取数据
cyxinda@oldsix ~ vault kv get -mount=mysql2 secret/db1
===== Secret Path =====
mysql2/data/secret/db1
======= Metadata =======
Key Value
--- -----
created_time 2024-05-16T08:51:51.633033141Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 4
====== Data ======
Key Value
--- -----
bar baz
password 123456
username root
2. 加密即服务
实验开始
- 启动transit
cyxinda@oldsix /cyxinda/vault vault secrets enable transit
Error enabling: Error making API request.
URL: POST https://vault.knowdee.com/v1/sys/mounts/transit
Code: 400. Errors:
* path is already in use at transit/
创建一个秘钥环
✘ cyxinda@oldsix /cyxinda/vault vault write -f transit/keys/orders
Key Value
--- -----
allow_plaintext_backup false
auto_rotate_period 0s
deletion_allowed false
derived false
exportable false
imported_key false
keys map[1:1715674518]
latest_version 1
min_available_version 0
min_decryption_version 1
min_encryption_version 0
name orders
supports_decryption true
supports_derivation true
supports_encryption true
supports_signing false
type aes256-gcm96
创建一个策略
cyxinda@oldsix /cyxinda/vault vault policy write app-orders -<<EOF
path "transit/encrypt/orders" {
capabilities = [ "update" ]
}
path "transit/decrypt/orders" {
capabilities = [ "update" ]
}
EOF
Success! Uploaded policy: app-orders
创建一个Token令牌
cyxinda@oldsix /cyxinda/vault vault token create -policy=app-orders
Key Value
--- -----
token hvs.CAESIMcLEau84wu_sXGaW6_RZgH-3Jac34WQhBkb0q1MSB1PGh4KHGh2cy5Ea0c1OXJtdmoyYm9ham1kUkEyYllWeFY
token_accessor OkNBDfWz6J1zenflSrRnbB7P
token_duration 768h
token_renewable true
token_policies ["app-orders" "default"]
identity_policies []
policies ["app-orders" "default"]
接下来,换一个session:
- 登录:
cyxinda@oldsix ~ vault login hvs.CAESIMcLEau84wu_sXGaW6_RZgH-3Jac34WQhBkb0q1MSB1PGh4KHGh2cy5Ea0c1OXJtdmoyYm9ham1kUkEyYllWeFY
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 hvs.CAESIMcLEau84wu_sXGaW6_RZgH-3Jac34WQhBkb0q1MSB1PGh4KHGh2cy5Ea0c1OXJtdmoyYm9ham1kUkEyYllWeFY
token_accessor OkNBDfWz6J1zenflSrRnbB7P
token_duration 767h59m36s
token_renewable true
token_policies ["app-orders" "default"]
identity_policies []
policies ["app-orders" "default"]
- 尝试加密一段数据
cyxinda@oldsix ~ vault write transit/encrypt/orders \
plaintext=$(base64 <<< "knowdee是一家人工智能的科技公司")
Key Value
--- -----
ciphertext vault:v1:ba8/VClN4/m6W89dmWg+Ea5vyaiGusaxYED4lAffozpJE4EYc2mCzbaXdth9cDYNeNQJT4mlvjTSkC3X548iF3ikQcPcdNBz
key_version 1
- 尝试解密一段数据
cyxinda@oldsix ~ vault write transit/decrypt/orders \
ciphertext="vault:v1:ba8/VClN4/m6W89dmWg+Ea5vyaiGusaxYED4lAffozpJE4EYc2mCzbaXdth9cDYNeNQJT4mlvjTSkC3X548iF3ikQcPcdNBz"
Key Value
--- -----
plaintext a25vd2RlZeaYr+S4gOWutuS6uuW3peaZuuiDveeahOenkeaKgOWFrOWPuAo=
cyxinda@oldsix ~ echo a25vd2RlZeaYr+S4gOWutuS6uuW3peaZuuiDveeahOenkeaKgOWFrOWPuAo= |base64 -d
knowdee是一家人工智能的科技公司
2. 数据库凭证管理
轮换数据库根凭证
生成动态数据库凭证
下面实验,使用root的token,省略在vault中,使用专用账号进行操作的步骤。
- 准备工作 在mysql中,创建一个超级用户
CREATE USER 'super_user'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON *.* TO 'super_user'@'%' WITH GRANT OPTION;
- 配置mysql的机密引擎
vault write database/config/my-mysql-database-test \
plugin_name=mysql-database-plugin \
connection_url="{{username}}:{{password}}@tcp(172.70.21.19:30071)/" \
allowed_roles="mysql-test-role" \
username="super_user" \
password="your_password"
- 在vault中,创建mysql角色
vault write database/roles/mysql-test-role \
db_name=my-mysql-database-test \
creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT SELECT ON *.* TO '{{name}}'@'%';" \
default_ttl="10m" \
max_ttl="24h"
- 轮换根凭据
cyxinda@oldsix /cyxinda/vault vault write -force database/rotate-root/my-mysql-database-test
Success! Data written to: database/rotate-root/my-mysql-database-test
现在,根凭证的秘钥目前只有vault知晓,所以禁止使用root账号充当根凭证 5. 获取新的数据库凭证
cyxinda@oldsix /cyxinda/vault vault read database/creds/mysql-test-role
Key Value
--- -----
lease_id database/creds/mysql-test-role/IANdxbYJjYWgD0WhiWdpiC1j
lease_duration 10m
lease_renewable true
password y9d-yafphHqQHb2LeZa1
username v-root-mysql-test-yzuNPIDO5s5qxK
可以在mysql库的user表中看到响应的用户,该用户租约是10分钟,十分钟后,用户将被删除
3. Cubbyhole(隔间)
零号乌龟问题
在《时间简史》的开篇,霍金讲述了一个故事。这个故事是关于一位科学家在做公开演讲时,他说:“我们生活的地球是球形的”。这时候,一个老妇人站出来说:“不,我是站在一只大乌龟身上的”。科学家并没有对老妇人的无知表现出不耐烦,反而问道,那么乌龟站在什么上面呢?老妇人说,它站在另一个乌龟上面,一层层地撂下来。
实际上,上面的两种保存秘钥的方式本质上是把秘钥的保存,转换成了获取机密的方式而已,为了保护一个机密,又维护了另一个机密。这就相当于刚才说的,一个乌龟站在另一个乌龟上面。
那么我们为了解决这个问题,就得知道0号乌龟,并且必须假设0号乌龟是不会被泄露的,或者泄露以后,能够被及时发现的。
概念
Cubbyhole 是美语,意为一个舒适的小房间,或是一个储物柜。每一个 Token 都会拥有一个独立的 Cubbyhole 存储空间,彼此相互隔离;每当一个 Token 被吊销或是过期时,Vault 会清空相应的 Cubbyhole 存储。
Cubbyhole 对外也是提供了一个 key-value 格式的存储形式,但与 Key-Value 机密引擎不同的是,任何 Token 无权访问其他 Token 的 Cubbyhole(即使是 Root 令牌),而 Key-Value 机密引擎提供的则是一个全局的 Key-Value 存储,凡是配置了相应路径读写 Policy 的用户都可以读写同一个路径下的机密数据。
响应封装:
这个概念是vault提出来的,这个概念如下三个特点:
- 响应封装,返回的是被加密以后的Token
- 被加密的Token只能被解密一次,如果被调用解密超过一次,会发出警报
- 响应封装具有租期,超过租期,则不能被解密
一个 Token 封装只能被解封一次,重复申请解封会被 Vault 拒绝。也就是说,假设传递给工具的 Token 封装被攻击者截获了,如果工具率先解封得到了 Token,那么攻击者随后试图解封将会失败;反过来如果攻击者率先成功解封得到了 Token,那么会造成工具解封失败,无法得到 Token,而这种类型的错误足以拉响运维系统的警报,督促管理员在第一时间吊销泄漏的 Token,并开始追查泄漏途径。
注:总结起来,Cubbyhole的概念就是,vault专门设置了一种单独的kv空间,用于存储wrap token与token的映射,只对客户端返回wrap token;
实验开始:
如下实验过程,省略单独为实验设置专有账号,使用root账号开始实验
- 创建一个策略,该策略运行读取kv引擎的特定路径
vault policy write apps -<<EOF
path "secret/data/dev" {
capabilities = [ "read" ]
}
EOF
Success! Uploaded policy: apps
- 查看kv的secret路径
cyxinda@oldsix /cyxinda/vault vault secrets list --detailed|grep secret
ath Plugin Accessor Default TTL Max TTL Force No Cache Replication Seal Wrap External Entropy Access Options Description UUID Version Running Version Running SHA256 Deprecation Status
---- ------ -------- ----------- ------- -------------- ----------- --------- ----------------------- ------- ----------- ---- ------- --------------- -------------- ------------------
cubbyhole/ cubbyhole cubbyhole_d54da78b n/a n/a false local false false map[] per-token private secret storage 1a087aa6-4614-5992-c4a0-be7db6e8d970 n/a v1.16.1+builtin.vault n/a n/a
secret/ kv kv_2715109d system system false replicated false false map[version:2] n/a f3edc8d2-61e7-ad1a-0faa-2d39892d8edf n/a v0.17.0+builtin n/a supported
- 写入两条数据到该路径下
cyxinda@oldsix /cyxinda/vault vault kv put secret/dev username="webapp" password="my-long-password"
= Secret Path =
secret/data/dev
======= Metadata =======
Key Value
--- -----
created_time 2024-05-16T09:41:12.139782724Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 2
- 创建一个token,并利用 Cubbyhole Response Wrap 机制将其封装
cyxinda@oldsix /cyxinda/vault vault token create -policy=apps -wrap-ttl=600
Key Value
--- -----
wrapping_token: hvs.CAESIGdggXn0LxFtLhlCWU762uNj7az9IGjF5vkH5Uvr3TylGh4KHGh2cy4xelJHdngwRVI2Sk56VkZrZVlDSlhodUc
wrapping_accessor: nIW8z5emWR7ZyXLzD6MJGT39
wrapping_token_ttl: 10m
wrapping_token_creation_time: 2024-05-16 09:52:54.065685899 +0000 UTC
wrapping_token_creation_path: auth/token/create
wrapped_accessor: B8NBmTrQcDpl2RKzCpUpz6Zu
- 创建一个只有登录权限的token:only-login
cyxinda@oldsix /cyxinda/vault vault token create -policy=default
Key Value
--- -----
token hvs.CAESINotJX7zibK7s9OF4tb6k55KxB96-imr9_gqhShN0BDSGh4KHGh2cy5sN2J4ZGpuMVh0UzZBMzJlY2hUT2dWd1g
token_accessor hY4tMpa2nZkz91GVBip6vENo
token_duration 768h
token_renewable true
token_policies ["default"]
identity_policies []
policies ["default"]
- 使用该token:only-login登录vault
该 Token 只拥有
default
策略,默认情况下没有任何权限,但足以让我们进行后续的交互。我们先用该 Token 登录,然后解封 Wrapping Token:
cyxinda@oldsix ~ vault login hvs.CAESINotJX7zibK7s9OF4tb6k55KxB96-imr9_gqhShN0BDSGh4KHGh2cy5sN2J4ZGpuMVh0UzZBMzJlY2hUT2dWd1g
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 hvs.CAESINotJX7zibK7s9OF4tb6k55KxB96-imr9_gqhShN0BDSGh4KHGh2cy5sN2J4ZGpuMVh0UzZBMzJlY2hUT2dWd1g
token_accessor hY4tMpa2nZkz91GVBip6vENo
token_duration 767h59m50s
token_renewable true
token_policies ["default"]
identity_policies []
policies ["default"]
- 解封 Wrapping Token
cyxinda@oldsix ~ VAULT_TOKEN=hvs.CAESIGdggXn0LxFtLhlCWU762uNj7az9IGjF5vkH5Uvr3TylGh4KHGh2cy4xelJHdngwRVI2Sk56VkZrZVlDSlhodUc vault unwrap
Key Value
--- -----
token hvs.CAESIOyzuej8e8eJAa2tEFPYt71jYDm0sQpDWLaZ9XnZbVFIGh4KHGh2cy42c3lQVHJ0dFcyZUIwcUhlaUd6VDF0SFA
token_accessor B8NBmTrQcDpl2RKzCpUpz6Zu
token_duration 768h
token_renewable true
token_policies ["apps" "default"]
identity_policies []
policies ["apps" "default"]
cyxinda@oldsix ~ VAULT_TOKEN=hvs.CAESIGdggXn0LxFtLhlCWU762uNj7az9IGjF5vkH5Uvr3TylGh4KHGh2cy4xelJHdngwRVI2Sk56VkZrZVlDSlhodUc vault unwrap
Error unwrapping: Error making API request.
URL: PUT https://vault.knowdee.com/v1/sys/wrapping/unwrap
Code: 400. Errors:
* wrapping token is not valid or does not exist
- 使用解封出来的Token登录,并读取数据
cyxinda@oldsix ~ vault login hvs.CAESIOyzuej8e8eJAa2tEFPYt71jYDm0sQpDWLaZ9XnZbVFIGh4KHGh2cy42c3lQVHJ0dFcyZUIwcUhlaUd6VDF0SFA
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 hvs.CAESIOyzuej8e8eJAa2tEFPYt71jYDm0sQpDWLaZ9XnZbVFIGh4KHGh2cy42c3lQVHJ0dFcyZUIwcUhlaUd6VDF0SFA
token_accessor B8NBmTrQcDpl2RKzCpUpz6Zu
token_duration 767h53m19s
token_renewable true
token_policies ["apps" "default"]
identity_policies []
policies ["apps" "default"]
读取数据
cyxinda@oldsix ~ vault kv get secret/dev
= Secret Path =
secret/data/dev
======= Metadata =======
Key Value
--- -----
created_time 2024-05-16T09:41:12.139782724Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 2
====== Data ======
Key Value
--- -----
password my-long-password
username webapp
再深入思考
回到0号乌龟,我们似乎是找到了它。但实际上,并没有。vault提供的0号乌龟,依然有泄露的风险。那么如何找到0号乌龟呢?这就涉及到另外一个概念零信任网络。kubernetes社区提供了key manager的解决方案,spiffe和spire