云托管版本,名为 HCP Vault(HashiCorp Cloud Platform Vault),与自托管 Vault 具有相同的二进制文件,在保证一致用户体验的同时,允许组织快速使用 Vault。
是什么
HashiCorp Vault 是一个用于管理密码、密钥和证书等秘密的系统,同时还可提供有身份认证和授权保护的加密服务。利用 Vault 提供的UI、CLI或HTTP API,可以在严格的控制和审计下安全地存储和管理敏感数据。
为什么
在现代系统中,需要诸如数据库访问凭证、外部API访问密钥、服务间调用凭证等多种大量秘密信息,这些信息分散存储在纯文本、配置文件、源代码或其他位置。如果没有专门的解决方案,仅靠散落在各平台自身的机制,很难弄清楚谁在访问哪些密钥,很难做好安全存储、密钥轮换和安全审计等工作。
Vault 通过将所有这些凭据集中起来,在一个地方定义,减少了不必要的暴露,并提供了访问的安全性和审计的方便性。
工作原理
Vault 主要通过与安全策略关联的令牌来控制客户端对秘密的访问。安全策略由一组描述路径及其操作可访问性的安全规则组成。令牌可以手动创建并授予客户端,也可以由客户端通过登录自行获取。。
Vault 的核心工作流为:
- 客户端提供身份识别信息
- Vault 通过 LDAP、GitHub、AppRole 等可信第三方验证客户端
- Vault 将定义好的安全策略关联到令牌,并授予客户端
- 客户端通过令牌访问秘密
功能特性
Vault 在对秘密数据持久存储之前会对其进行加密,因此其原始存储也是安全的。
Vault 可为 AWS、SQL 数据库等系统提供临时访问凭据,并在到期后作废。
Vault 也可以根据安全团队定义的加密参数提供公共的数据的加密、解密服务,而不必存储加解密数据。
Vault 使用租期管理存储其中的各种秘密,到期前没有被续租的秘密将废弃。
Vault 允许主动废弃单个、相关、特定类型的秘密,实现密钥轮换或锁定系统,以防范入侵。
版本
Vault 共有开源版、云托管版和企业版3种版本。
开源版需要自托管,提供动态秘密管理、加密和数据保护等基本功能特性,需要通过社区获得支持。
云托管版本,名为 HCP Vault(HashiCorp Cloud Platform Vault),与自托管 Vault 具有相同的二进制文件,在保证一致用户体验的同时,允许组织快速使用 Vault。
企业版需要自托管,比开源版多了扩展、容灾等企业特性,并由HashiCorp提供服务支持。
快速体验
安装
# 安装 yum-config-manager 来管理存储库。
sudo yum install -y yum-utils
# 使用 yum-config-manager 添加官方的 HashiCorp Linux 存储库。
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
# 安装
sudo yum -y install vault
# 或者安装企业版
sudo yum -y install vault-enterprise
# 验证
vault
启动
# 查看启动帮助
vault server -help
# 以开发模式启动,数据仍然加密,但保存在内存中,使用http侦听,仅用于快速体验,不能用于生产
vault server -dev
# 记录日志中输出的VAULT_ADDR、Unseal Key、Root Token
# 在一个新终端中进行如下操作
# 留存Unseal Key到某个地方
echo "把这段文字用Unseal Key替换" > unseal.key
# 根据server启动日志中的信息设置环境变量VAULT_ADDR,表示server的访问地址
export VAULT_ADDR='http://127.0.0.1:8200'
# 根据server启动日志中的信息设置环境变量VAULT_TOKEN,表示root访问令牌
export VAULT_TOKEN="用Root Token替换这段文字"
# 查看服务状态
vault status
处理键值对secret
# 查看处理键值对的命令帮助
vault kv -help
# 查看put键值对命令帮助
vault kv put -help
# 创建一个名为hell、包含foo、excited两个Key的键值对secret。
# 每针对同名secret put一次,其元数据中的version就会递增
vault kv put -mount=secret hello foo=world excited=yes
# 读取名为hello的键值对secret,响应中会显示其元数据和键值数据
vault kv get -mount=secret hello
# 读取kv中excited键的值
vault kv get -mount=secret -field=excited hello
# 以json格式输出,并使用jq命令提取excited这个key的数据
vault kv get -mount=secret -format=json hello | jq -r .data.data.excited
# 删除名为hello的键值对secret
vault kv delete -mount=secret hello
# 恢复无意删除的名为hello的secret到第2个版本
vault kv undelete -mount=secret -versions=2 hello
引擎
秘密引擎是 Vault 用于储存、生成和加解密的插件化的组件,键值只是其中一种,其它存储引擎还有数据库、Transit(加解密服务引擎)、SSH、Time-baseed OTP、AWS、Consul等,也可以自定义。
# 秘密引擎需要启用后才能使用。
# 同一种存储引擎可以在不同的路径(path)上启用。
# 开发模式预设启用了键值引擎。
vault secrets enable -path=kv kv
# 启用秘密引擎的路径默认为秘密引擎的名称
vault secrets enable kv
# 以下命令用于列出所有秘密引擎
vault secrets list
# 以下命令使用 path/秘密名称 的形式设置名为hello的键值数据
vault kv put kv/hello target=world
# 以下命令可禁用路径为kv下的秘密引擎,并删除关联的秘密和配置
vault secrets disable kv/
生产模式安装
建立配置文件
Vault使用HCL文件进行配置,首先创建一个HCL配置文件,可以命名为config.hcl。
storage "raft" {
path = "./vault/data"
node_id = "node1"
}
listener "tcp" {
address = "[::]:8200"
cluster_address = "[::]:8201"
tls_cert_file = "/etc/certs/vault.crt"
tls_key_file = "/etc/certs/vault.key"
}
api_addr = "https://127.0.0.1:8200"
cluster_addr = "https://127.0.0.1:8201"
ui = true
storage 指示后端存储方式,raft 是一种适合生产模式的后端存储。
listener 用于配置侦听 API 请求的地址,生产环境应启用 TLS 。
api_addr 用于备用服务器向主服务器重定向客户端的地址。
cluster_addr 为同一集群下各node互相通信的地址,用于备用服务器forward客户端请求到主服务器,必须使用TLS。
ui用于启用默认不启用的Web UI。
不支持内存锁定时,需要添加配置:disable_mlock = true
建立raft工作目录
启动服务器
vault server -config=config.hcl
初始化
启动一个新的终端,并执行如下命令以初始化服务器。
# 设置服务API地址
export VAULT_ADDR='http://127.0.0.1:8200'
# 初始化服务
vault operator init
初始化过程会输出5个解封密钥和1个root初始Token,需要安全的分发密钥,避免一个人持有所有密钥,以便当Vault密封时,至少需要3个密钥才能解封。
解封
Vault在初始化后,以及每次重启后,都需要解封才能读取秘密数据。需要在不同的计算机上,分别执行如下命令,并各提供一个不同的密钥,才能完成解封。
登录
使用初始化后获得的Root令牌登录Vault服务。
其它运维命令
任一运维人员可通过执行 vault operator seal 命令再次密封Vault,可以通过执行 pgrep -f vault | xargs kill 命令终止 Vault 进程,通过执行 rm -r ./vault/data 命令清除 Vault 数据。
使用Transit引擎进行加解密
以下演示使用Vault Transit引擎进行数据加解密(不存储)的过程,涉及管理员和客户端两个角色。
# (管理员) 以开发模式及root token启动 Vault
# 注:生产环境中,应使用具有某些安全策略的其它Token执行本任务
vault server -dev -dev-root-token-id root
# (管理员) 使用环境变量声明Vault服务的地址,方便后续操作
export VAULT_ADDR=$VAULT_ADDR
# (管理员) 使用环境变量声明要使用的令牌,方便后续操作
export VAULT_TOKEN=root
# (管理员) 启用transit引擎
vault secrets enable transit
# (管理员) 启用名为orders的加密环
vault write -f transit/keys/orders
# (管理员) 为客户端创建名为app-orders的安全策略
vault policy write app-orders -<<EOF
path "transit/encrypt/orders" {
capabilities = [ "update" ]
}
path "transit/decrypt/orders" {
capabilities = [ "update" ]
}
EOF
# (管理员)为app-orders安全策略创建一个令牌
vault token create -policy=app-orders
# (客户端)使用环境变量存储管理员分配的令牌
export APP_ORDER_TOKEN="hvs.CAESIOVYYt5Cq5E0zZX3QVHEv5EYj94pGkGipUX48rI_f2wFGh4KHGh2cy5kdlhPSEhyR2pLa0hlODQwRDVkMzVuMWE"
# (客户端)对明文base64编码后,再调用Vault服务进行加密
VAULT_TOKEN=$APP_ORDER_TOKEN vault write transit/encrypt/orders \
plaintext=$(base64 <<< "4111 1111 1111 1111")
# (客户端)调用Vault服务对密文进行解密
VAULT_TOKEN=$APP_ORDER_TOKEN vault write transit/decrypt/orders \
ciphertext="vault:v1:cZNHVx+sxdMErXRSuDa1q/pz49fXTn1PScKfhf+PIZPvy8xKfkytpwKcbC0fF2U="
# (客户端)对解密后的文本进行base64解码
base64 --decode <<< "NDExMSAxMTExIDExMTEgMTExMQo="
# (管理员)执行如下命令进行密钥轮换
vault write -f transit/keys/orders/rotate
# (客户端)再次调用加密服务,此时密文开头会变为vault:v2
vault write transit/encrypt/orders \
plaintext=$(base64 <<< "4111 1111 1111 1111")
# (客户端)使用新密钥包装旧密文。Vault会自行先解密再用新密钥加密。
vault write transit/rewrap/orders \
ciphertext="vault:v1:cZNHVx+sxdMErXRSuDa1q/pz49fXTn1PScKfhf+PIZPvy8xKfkytpwKcbC0fF2U="
# (管理员)启用密钥的24小时自动轮换
vault write transit/keys/orders/config \
auto_rotate_period=24h
# (管理员) 查看密钥信息,其中 auto_rotate_period 为轮换设置,默认值0s表示禁用。
vault read transit/keys/orders
# (管理员) 设置最小可解密密钥版本,默认从1开始所有版本的密钥均可以解密
vault write transit/keys/orders/config \
min_decryption_version=5
# (管理员)导出密钥(包括明文和密文),密文可以保存到Vault的键值引擎,
# 用于在Vault外对大文件进行加解密
vault write -f transit/datakey/plaintext/orders
# (管理员)导入一个已有的外部密钥
vault read -field=public_key transit/wrapping_key
使用数据库引擎获得动态凭证
以下演示使用Vault的数据库引擎来管理数据库访问凭证,涉及管理员和应用程序两个角色。
# 首先做一些准备工作,本例中使用docker方式启动一个postgres数据库实例
# 拉取postgres的docker镜像
docker pull postgres:latest
# 启动 postgres 容器
docker run \
--detach \
--name learn-postgres \
-e POSTGRES_USER=root \
-e POSTGRES_PASSWORD=rootpassword \
-p 5432:5432 \
--rm \
postgres
# 通过docker在名为learn-postgres的容器中执行psql命令,
# 来创建一个名为 ro 的角色,用于完成示例
docker exec -i \
learn-postgres \
psql -U root -c "CREATE ROLE \"ro\" NOINHERIT;"
# 为 ro 角色授予一些权限
docker exec -i \
learn-postgres \
psql -U root -c "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"ro\";"
#--------以下进入正式环节-----
# (管理员)以dev模式启动一个Vault服务,方便演示
vault server -dev -dev-root-token-id root
# (管理员)设置环境变量,方便后续操作
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=root
export POSTGRES_URL=127.0.0.1:5432
# (管理员)在Vault中启用数据库引擎
vault secrets enable database
# (管理员) 配置 postgreql-database-plugin,包括连接信息
# connection_url支持以,分割的多个实例,插件将依次尝试连接
vault write database/config/postgresql \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@$POSTGRES_URL/postgres?sslmode=disable" \
allowed_roles=readonly \
username="root" \
password="rootpassword"
# (管理员)创建一个可在postgres中建立角色的sql模板
tee readonly.sql <<EOF
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' INHERIT;
GRANT ro TO "{{name}}";
EOF
# (管理员)创建名为readonly的数据库角色,创建语句将使用上面的sql模板
vault write database/roles/readonly \
db_name=postgresql \
creation_statements=@readonly.sql \
default_ttl=1h \
max_ttl=24h
# (应用程序) 从Vault获取一个只读角色
# 返回结果中包括username、password,以及租约id和租期等信息
vault read database/creds/readonly
# 可使用如下命令验证获得的角色是否存在
docker exec -i \
learn-postgres \
psql -U root -c "SELECT usename, valuntil FROM pg_user;"
# (管理员)列出所有租约
vault list sys/leases/lookup/database/creds/readonly
#(管理员)使用环境变量保存第一份租约的id
LEASE_ID=$(vault list -format=json sys/leases/lookup/database/creds/readonly | jq -r ".[0]")
# (管理员)续约
vault lease renew database/creds/readonly/$LEASE_ID
# (管理员)在租约到期前主动撤销
vault lease revoke database/creds/readonly/$LEASE_ID
# (管理员)按照前缀撤销所有符合条件的租约
vault lease revoke -prefix database/creds/readonly
Vault还提供数据库用户名模板、密码策略等功能。