前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >开源KMS之vault part9

开源KMS之vault part9

原创
作者头像
保持热爱奔赴山海
发布2024-06-03 10:37:57
960
发布2024-06-03 10:37:57

加密即服务【非常常用】

transit引擎:

https://developer.hashicorp.com/vault/docs/secrets/transit

https://developer.hashicorp.com/vault/api-docs/secret/transit 【重要,参数自定义都在这篇文档上】

腾讯云这个案例 https://cloud.tencent.com/document/product/573/8790

我们有时会编写一些与敏感信息(PII,Personally Identifiable Information)打交道的服务,例如存储用户的姓名、联系方式、住址等隐私信息。为防止由于意外数据库泄漏,或是减少内鬼违规查阅、下载数据的可能性,将PII数据加密后存储到数据库是一个比较好的实践。

transit 主要用途是帮助加密应用程序发来的数据,然后应用程序在其主要数据存储中保存经过加密的数据。该机密引擎将正确加解密数据的重担从应用程序的开发者身上转移到了 Vault 的管理员这里。

一般我们会使用对称加密算法来加密该类数据,一个对称加密算法的关键是加密密钥。拥有加密密钥的人可以解密对应的密文数据。

如果我们在应用程序代码中调用加密库加解密信息,那么不可避免地就要与密钥打交道,密钥的管理、访问密钥的权限、密钥的轮替和紧急状态下吊销密钥这些工作就都落在应用程序开发者身上了。所以Vault提供了名为transit的机密引擎,对外提供了“加密即服务”功能(Encryption as a Service)。

大白话就是: vault transit引擎提供一个密钥,应用程序将明文数据plaintext发给vault,vault用密钥对plaintext进行加密,然后把加密后的内容返回给应用程序。

启用transit引擎

代码语言:txt
复制
$ export VAULT_ADDR='http://192.168.31.181:8200' 
$ vault login hvs.BwHLss1Dmh9Be30uKVHAAlD5
$ vault secrets enable transit

Vault模拟了一个文件系统,各个机密引擎的启用是被挂载到相应文件路径下,默认挂载路径是引擎名称,所以我们启用的transit引擎被挂载到了默认的transit/路径下。

创建一个密钥环

创建一个密钥环orders,才能开始使用加密服务。

代码语言:txt
复制
$ 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:1717216826]
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

vault的web ui 也可以看到新加的秘钥环:

添加策略

到目前为止我们都是使用Root用户进行的操作,下面我们要模拟一个普通的应用程序如何访问Vault来执行加解密操作。首先让我们为应用程序配置访问密钥环的权限。

我们新增了一个名为app-oerders的Policy,赋予了对transit引擎下名为orders的密钥环加密与解密的操作权限。

编写策略文件 app-orders-policy.hcl ,内容如下:

代码语言:txt
复制
path "transit/encrypt/orders" {
 capabilities = [ "update" ]
}
path "transit/decrypt/orders" {
 capabilities = [ "update" ]
}

将policy发布到vault

代码语言:txt
复制
$ vault policy write app-orders ./app-orders-policy.hcl
Success! Uploaded policy: app-orders

然后我们为应用程序创建一个Vault Token

然后我们为应用程序创建一个vault Token,后续应用程序登录vault靠的就是这个token。

代码语言:txt
复制
$ vault token create -policy=app-orders
Key Value
---  -----
token  hvs.CAESIGsmgky0rU1VYZtU-0Xj3A2a99bfZJYrMw-NdlUPqm04Gh4KHGh2cy5BdW8xNUQzb3o4WmphTzZtNTZ6SVZmVUU
token_accessor J6h5CqAuDBwhFswsQGVON7Xz
token_duration 768h
token_renewable true
token_policies ["app-orders" "default"]
identity_policies []
policies ["app-orders" "default"]

可以看到,这个新Token拥有两个Policy:app-orders与default。default是默认所有用户都拥有的策略。

使用上面生成的token登录到vault

代码语言:txt
复制
$ vault login hvs.CAESIGsmgky0rU1VYZtU-0Xj3A2a99bfZJYrMw-NdlUPqm04Gh4KHGh2cy5BdW8xNUQzb3o4WmphTzZtNTZ6SVZmVUU
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.CAESIGsmgky0rU1VYZtU-0Xj3A2a99bfZJYrMw-NdlUPqm04Gh4KHGh2cy5BdW8xNUQzb3o4WmphTzZtNTZ6SVZmVUU
token_accessor J6h5CqAuDBwhFswsQGVON7Xz
token_duration 767h54m12s
token_renewable true
token_policies ["app-orders" "default"]
identity_policies []
policies ["app-orders" "default"]

尝试加密一段数据

假设Abcd@1234 是某个明文密码

代码语言:txt
复制
$ vault write transit/encrypt/orders plaintext=$(base64 <<< "Abcd@1234")
Key Value
--- -----
ciphertext vault:v1:Kz38wR3kWN+xHcW9MHEKT7FQbRTNuV/QpHHP06xCJcVfjDn3RRs=
key_version 1

可以看到,给出明文Abcd@1234,我们得到的了密文vault:v1:Kz38wR3kWN+xHcW9MHEKT7FQbRTNuV/QpHHP06xCJcVfjDn3RRs=

在这里我们还可以看到,输出结果中key_version为1,并且密文头部也有vault:v1:的前缀。这是因为transit引擎支持多版本密钥管理以及密钥轮替操作。

还可以多执行几次,可以看到每次获得的ciphertext都是不一样的。

代码语言:txt
复制
$ vault write transit/encrypt/orders plaintext=$(base64 <<< "Abcd@1234")
Key Value
--- -----
ciphertext vault:v1:bXbCItJZWucx+xjKcVACdaTbmbTOZBrbWIAZ/JaC4nuuKqTjUs8=
key_version 1


$ vault write transit/encrypt/orders plaintext=$(base64 <<< "Abcd@1234")
Key Value
--- -----
ciphertext vault:v1:DSwrID5mjj2onmX7o35xMj8A6elIE9DSdirtXb7k0MyqLcC1gEE=
key_version 1

试下解密接口

对上面生成的3个ciphertext 都执行下解密,可以看到得出的结果都是Abcd@1234

代码语言:txt
复制
$ vault write transit/decrypt/orders ciphertext="vault:v1:Kz38wR3kWN+xHcW9MHEKT7FQbRTNuV/QpHHP06xCJcVfjDn3RRs="| tail -1 | awk '{print $NF}'| base64 -d
Abcd@1234


$ vault write transit/decrypt/orders ciphertext="vault:v1:bXbCItJZWucx+xjKcVACdaTbmbTOZBrbWIAZ/JaC4nuuKqTjUs8="| tail -1 | awk '{print $NF}'| base64 -d
Abcd@1234


$ vault write transit/decrypt/orders ciphertext="vault:v1:DSwrID5mjj2onmX7o35xMj8A6elIE9DSdirtXb7k0MyqLcC1gEE="| tail -1 | awk '{print $NF}'| base64 -d
Abcd@1234

密钥版本

transit允许同时保存一个密钥环的多个版本的密钥。我们使用Root Token登录,再查看一下当前密钥环的信息:

代码语言:txt
复制
$ vault login hvs.BwHLss1Dmh9Be30uKVHAAlD5

查看秘钥的版本

代码语言:txt
复制
$ vault read 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:1717216826]
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

可以看到,transit/keys/orders密钥环采用的是aes256-gcm96算法,当前最新密钥版本是1,保存的最久远的密钥版本min_decryption_version也是1。

轮转秘钥

执行rotate轮转orders这个密钥的命令,这将生成一个新密钥并将其添加至命名密钥的密钥环中。

代码语言:txt
复制
$ vault write -f transit/keys/orders/rotate
Key Value
--- -----
allow_plaintext_backup false
auto_rotate_period 0s
deletion_allowed false
derived false
exportable false
imported_key false
keys  map[1:1717216826 2:1717228595]
latest_version 2
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

再次查看秘钥版本

代码语言:txt
复制
$ vault read 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:1717216826 2:1717228595]
latest_version 2 # 可以看到这里版本已经变为2了
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

可以看到,最新密钥版本变成了2,而min_decryption_version还是1,这代表轮替后将默认使用版本号为2的密钥加密数据,但使用版本号为1的密钥加密的数据仍然可以正常解密。让我们验证一下现在我们是否还可以解密刚才的密文:

再试下,之前的ciphertext能否解密出来

代码语言:txt
复制
$ vault write transit/decrypt/orders ciphertext="vault:v1:Kz38wR3kWN+xHcW9MHEKT7FQbRTNuV/QpHHP06xCJcVfjDn3RRs=" | tail -1 | awk '{print $NF}'| base64 -d
Abcd@1234


$ vault write transit/decrypt/orders ciphertext="vault:v1:bXbCItJZWucx+xjKcVACdaTbmbTOZBrbWIAZ/JaC4nuuKqTjUs8=" | tail -1 | awk '{print $NF}'| base64 -d
Abcd@1234


$ vault write transit/decrypt/orders ciphertext="vault:v1:DSwrID5mjj2onmX7o35xMj8A6elIE9DSdirtXb7k0MyqLcC1gEE=" | tail -1 | awk '{print $NF}'| base64 -d
Abcd@1234

可以看到,用轮转前加密好的密文,现在还是可以被正常解密出来的,rotate操作并不会造成老的密文的不可解密。

再试下用当前的轮转过的秘钥来进行加密和解密

代码语言:txt
复制
# 加密
$ vault write transit/encrypt/orders plaintext=$(base64 <<< "Abcd@1234")
Key Value
--- -----
ciphertext vault:v2:8gPj7LCEPOQp26DP8NYYHJAQ7w1XWpI7nDQCLcU9ImDByxJEIXs=
key_version 2


# 解密
$ vault write transit/decrypt/orders ciphertext="vault:v2:8gPj7LCEPOQp26DP8NYYHJAQ7w1XWpI7nDQCLcU9ImDByxJEIXs=" | tail -1 | awk '{print $NF}'| base64 -d
Abcd@1234

可以看到,轮转后的秘钥的加密和解密也都是可以正常工作的。

我们在vault的web ui,可以看到当前是有2个版本

tips: 可以在web ui调整auto_rotate_period时常,最短为1h, 也可以通过http接口方式调整这些参数,

下面演示下http方式

vim transit_payload.json 内容如下:

代码语言:txt
复制
{
 "auto_rotate_period": "86400"
}

查看当前配置

代码语言:txt
复制
curl --header "X-Vault-Token: hvs.BwHLss1Dmh9Be30uKVHAAlD5" http://192.168.31.181:8200/v1/transit/keys/orders

修改配置

代码语言:txt
复制
curl --header "X-Vault-Token: hvs.BwHLss1Dmh9Be30uKVHAAlD5" --request POST --data @transit_payload.json http://192.168.31.181:8200/v1/transit/keys/orders/config

更新密文版本

如果我们数据库中目前存储的是v1版本的密文,在轮替密钥后,我们希望把旧版本密文全部更新成新版本,可以使用Vault的rewrap来完成。

例如,上面最早有3个密文

代码语言:txt
复制
vault:v1:Kz38wR3kWN+xHcW9MHEKT7FQbRTNuV/QpHHP06xCJcVfjDn3RRs=
vault:v1:bXbCItJZWucx+xjKcVACdaTbmbTOZBrbWIAZ/JaC4nuuKqTjUs8=
vault:v1:DSwrID5mjj2onmX7o35xMj8A6elIE9DSdirtXb7k0MyqLcC1gEE=

执行下面的rewrap命令即可

代码语言:txt
复制
$ vault write transit/rewrap/orders ciphertext="vault:v1:Kz38wR3kWN+xHcW9MHEKT7FQbRTNuV/QpHHP06xCJcVfjDn3RRs="
Key Value
--- -----
ciphertext vault:v2:1eXZxme2mygIGkZ1qv2Lhkc7y5NyfBwpCU/F+TYFGiON1cLmImY=
key_version 2


$ vault write transit/rewrap/orders ciphertext="vault:v1:bXbCItJZWucx+xjKcVACdaTbmbTOZBrbWIAZ/JaC4nuuKqTjUs8="
Key Value
--- -----
ciphertext vault:v2:huUsofv/7AHCpktTXo2eiRt8Nb45Z4swBGQEfkybE2lMdlRfNpg=
key_version 2


$ vault write transit/rewrap/orders ciphertext="vault:v1:DSwrID5mjj2onmX7o35xMj8A6elIE9DSdirtXb7k0MyqLcC1gEE="
Key Value
--- -----
ciphertext vault:v2:h8XE2J2MOsiYA2ZqSiZLW6skBu6V7EiI4yOF92Oy7RRg0WzJjTA=
key_version 2

通过rewrap,我们在不知晓明文的前提下,将密文的密钥版本从v1更新到了v2。

假设,业务代码用之前token登录,执行解密操作:

代码语言:txt
复制
$ vault login hvs.CAESIGsmgky0rU1VYZtU-0Xj3A2a99bfZJYrMw-NdlUPqm04Gh4KHGh2cy5BdW8xNUQzb3o4WmphTzZtNTZ6SVZmVUU

$ vault write transit/decrypt/orders ciphertext="vault:v1:Kz38wR3kWN+xHcW9MHEKT7FQbRTNuV/QpHHP06xCJcVfjDn3RRs=" | tail -1 | awk '{print $NF}'| base64 -d  # 这里的ciphertext是用的老版本的秘钥加密的结果
Abcd@1234

$ vault write transit/decrypt/orders ciphertext="vault:v2:h8XE2J2MOsiYA2ZqSiZLW6skBu6V7EiI4yOF92Oy7RRg0WzJjTA=" | tail -1 | awk '{print $NF}'| base64 -d # 这里的ciphertext是用的老版本被rewrap后的秘钥加密的结果
Abcd@1234

可以看到,都能正确的解密出原始数据。

最佳实践

假设我们在数据库中存放了大量密文数据,一种比较好的实践是定期用rotate命令轮替生成新的密钥,并且编写定时任务,通过rewrap命令定时将密文全部更新到最新密钥版本,这样即使密文意外泄漏,存放在Vault中的密钥可能也已经被抛弃了。

抛弃旧的秘钥

在生产环境中我们定期轮替生成新密钥,但老密钥还是被保存在Vault中,长此以往会在Vault中留下大量的无用密钥(旧密文已被定期更新到新密钥版本)。我们可以设置密钥环的最低解密密钥版本。

如下图,version 1 就是旧的秘钥。

这里需要使用root token登录, 然后执行抛弃旧的秘钥的操作(也就是限制min_decryption_version版本)

代码语言:txt
复制
$ vault login hvs.BwHLss1Dmh9Be30uKVHAAlD5
$ vault write transit/keys/orders/config min_decryption_version=2 # 这里限制最低的秘钥版本为2
Key Value
--- -----
allow_plaintext_backup false
auto_rotate_period 0s
deletion_allowed false
derived false
exportable false
imported_key false
keys map[2:1717228595]
latest_version 2
min_available_version 0
min_decryption_version 2
min_encryption_version 0
name orders
supports_decryption true
supports_derivation true
supports_encryption true
supports_signing false
type aes256-gcm96

如此,当前的min_decryption_version就被设置为2。

从vault web ui,可以看到刚才的 version 1 已经不见了。

我们现在试试解密v1版本的密文:

# 使用做过了policy的普通token登录

代码语言:txt
复制
$ vault login hvs.CAESIGsmgky0rU1VYZtU-0Xj3A2a99bfZJYrMw-NdlUPqm04Gh4KHGh2cy5BdW8xNUQzb3o4WmphTzZtNTZ6SVZmVUU

# 执行解密v1版本的密文的操作

代码语言:txt
复制
$ vault write transit/decrypt/orders ciphertext="vault:v1:Kz38wR3kWN+xHcW9MHEKT7FQbRTNuV/QpHHP06xCJcVfjDn3RRs=" | tail -1 | awk '{print $NF}'| base64 -d
Error writing data to transit/decrypt/orders: Error making API request.
URL: PUT http://192.168.31.181:8200/v1/transit/decrypt/orders
Code: 400. Errors:
* ciphertext or signature version is disallowed by policy (too old)

Vault拒绝解密v1版本的密文,原因是too old。v1版本的密钥已经被Vault删除。

需要注意的是,抛弃密钥前必须确保所有旧版本密文都已被更新到新密钥版本,否则抛弃旧密钥等同于删除旧版本密文。

也就意味着: 在做抛弃旧的秘钥的操作前,请确保所有代码中写死老的旧版本密文的地方都必须更新好,否则到时候会直接报错,可能阻断业务流程!!。

如果解密v2版本的密文,可以看到解密命令还是可以正常工作的

代码语言:txt
复制
$ vault write transit/decrypt/orders ciphertext="vault:v2:h8XE2J2MOsiYA2ZqSiZLW6skBu6V7EiI4yOF92Oy7RRg0WzJjTA=" | tail -1 | awk '{print $NF}'| base64 -d
Abcd@1234

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 加密即服务【非常常用】
    • 启用transit引擎
      • 创建一个密钥环
        • 添加策略
          • 然后我们为应用程序创建一个Vault Token
            • 密钥版本
              • 轮转秘钥
                • 更新密文版本
                  • 最佳实践
                    • 抛弃旧的秘钥
                    相关产品与服务
                    密钥管理系统
                    密钥管理系统(Key Management Service,KMS)是一款安全管理类服务,可以让您轻松创建和管理密钥,保护密钥的保密性、完整性和可用性,满足用户多应用多业务的密钥管理需求,符合合规要求。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档