作为一个从事运维安全的从业人员你是不是也有相同的痛点:
1、github提交敏感信息,云平台鉴权信息泄露,一夜起来全变成矿机。
2、内网就安全了么?攻入内网,gitlab中敏感信息被利用。
3、人员离职和变动,不知道他手上掌握哪些敏感信息,需要删除或重置。
4、想修改或删除敏感信息,但又不知道有没有人在用,谁在用,影响面有多大。
如果有上述痛点,那花点时间了解下Vault,它对敏感信息的保护和管理提出了一个大胆的构思。
该部分首先定义了什么是敏感信息,并以插件的形式将对敏感信息的操作进行标准化封装。敏感信息包括但不限于数据库鉴权信息、云平台登录鉴权信息、敏感键值对、敏感文本、密钥对等,Vault目前支持的Secret Engine有:AWS、Alicloud、Azure、Consul、Cassandra、MySql、Oracle、k8s、influxdb、key/value、k8s等。Vault简单的将这些资源封装成read和write两个操作,write用户set初始鉴权信息,read用于获取鉴权信息。像数据库、云平台这些资源又支持Dynamic Secret,随着read的调用同步产生一定时效的新认证,保证每次read到的信息不一样。

被Vault保护的Secret不能随便被调用,由Authentication对client端进行身份校验。Client端有可能是两种形式:机器和人。机器鉴权推荐使用AppRole,在Vault中预先配置了role_id和secret_id,机器请求时需要提供这些信息换取token,所以AppRole的鉴权数据是由Vault本身来维护的。人的鉴权推荐使用LDAP、github,Vault直接对接了外部的Authentication,这些鉴权数据不是由Vault来维护的。当然你也可以使用username/password方式交给Vault来维护人的鉴权信息,但该方式会引入新的认证,不推荐。

Vault作为一个平台来使用就必须要考虑多租户的权限隔离,所以需要解决不同用户登陆后的授权问题,Vault是通过policy的方式来解决的。任何的资源(Secret、Authentication)都是以路径的方式维护在Vault中的,例如/secret/aws代表所有aws平台的内容,/secret/aws/my-account代表my-account这个账号的aws内容,通过policy描述文件可以指定该policy具备的权限,例如:
- vault policy write my-policy - << EOF
- # Dev servers have version 2 of KV secrets engine mounted by default, so will
- # need these paths to grant permissions:
- path "secret/data/*" {
- capabilities = ["create", "update"]
- }
- path "secret/data/foo" {
- capabilities = ["read"]
- }
- EOF
这样只要在Vault中指定Authentication具备的Policy就可以限定client端的权限。
该部分的作用很容易理解,Vault中数据的存储总是需要固存起来的,而做为敏感信息管理平台的存储除开高可用和备份机制外,存储的敏感内容也都进行了加密,这样即便是直接访问数据库也看不懂Secret Engines的内容。
以上4部分是Vault的核心服务,提供了敏感信息管理的基础能力,下面再介绍两个增强模块。
审计设备本质就是一个访问日志,它记录了对Vault的所有请求和响应的详细日志。该功能一方面可以帮助我们知道到底谁在调用哪些敏感资源,甚至可以接入HIDS等加强安全管控。另一方面也为客户端和服务端调用的问题排查提供数据依据。一次调用可以支持记录多份审计日志,例如本地文件记录一份,Syslog也记录一份,但在Vault的设计理念中安全比性能更重要,所以在至少完成一份审计日志记录之前是会block内容返回的。
Vault搭建完成,有了这么好的平台我们在使用它的时候犯了难,因为对我们的业务代码侵入性太高了,我们必须完成加解密、登录、交换token、获得敏感内容等一系列编码后才能拿到想要的内容。
交互过程是这样的:

代码长得是这样的:
- package main
-
- import (
- ...snip...
- vault "github.com/hashicorp/vault/api"
- )
-
- // Fetches a key-value secret (kv-v2) after authenticating via AppRole
- func getSecretWithAppRole() (string, error) {
- config := vault.DefaultConfig()
-
- client := vault.NewClient(config)
- wrappingToken := ioutil.ReadFile("path/to/wrapping-token")
- unwrappedToken := client.Logical().Unwrap(strings.TrimSuffix(string(wrappingToken), "\n"))
-
- secretID := unwrappedToken.Data["secret_id"]
- roleID := os.Getenv("APPROLE_ROLE_ID")
-
- params := map[string]interface{}{
- "role_id": roleID,
- "secret_id": secretID,
- }
- resp := client.Logical().Write("auth/approle/login", params)
- client.SetToken(resp.Auth.ClientToken)
-
- secret, err := client.Logical().Read("kv-v2/data/creds")
- if err != nil {
- return "", fmt.Errorf("unable to read secret: %w", err)
- }
-
- data := secret.Data["data"].(map[string]interface{})
-
- ...snip...
- }
万一你公司的开发语言又有Java、Go、Python等多种语言,恭喜你,对于Vault的推广可以提前结束了。所以我在以前的很多文章,包括现实工作中一直在强调云原生的概念,你设计开发一套东西一定得是原生化的,最好做到对业务层的零侵入,否则你设计出来的东西再好也不会有人用。
Vault深知原生化的重要性,所以推出了神器Vault Agent来解决这一问题。
Agent以边车模式与业务代码运行在一起,如图:

Agent的config文件大体包含Agent做为服务端地址、客户端的鉴权、期望在服务端读取的Secret内容、同步写入本地的文件位置等内容,业务代码只需要以本地文件方式就可以获取到敏感内容,对于整个Vault体系是无感知的。
Agent除了帮我们自动鉴权和获取敏感内容,同时还兼顾了缓存功能,这样可以大大减小server端的压力。客户端缓存对于Vault来说是非常重要的,因为Vault的Server端虽然可以通过自主选举的方式做到高可用,但它做不到动态扩展。
Vault对所有的模块都是以插件化的方式提供好了接口和规则,并内置了一些常用插件的实现,这种设计跟Terraform和Packer一样,很符合HashiCorp的气质。从产品的生命周期和灵活度来考虑,个人觉得是个很有潜力的产品。