Contents

Vault

Vault

参考链接:https://thiscute.world/posts/experience-of-vault/#%E4%B8%80vault-%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5

介绍

Vault 是 hashicorp 推出的 secrets 管理、加密即服务与权限管理工具。它的功能简介如下:

1)secrets 管理:支持保存各种自定义信息、自动生成各类密钥,vault 自动生成的密钥还能自动轮转(rotate)

2)认证方式:支持接入各大云厂商的账号体系(比如阿里云RAM子账号体系)或者 LDAP 等进行身份验证,不需要创建额外的账号体系。

3)权限管理:通过 policy,可以设定非常细致的 ACL 权限。

4)密钥引擎:也支持接管各大云厂商的账号体系(比如阿里云RAM子账号体系),实现 API Key 的自动轮转。

5)支持接入 kubernetes rbac 认证体系,通过 serviceaccount+role 为每个 Pod 单独配置认证角色。

6)支持通过 sidecar/init-container 将 secrets 注入到 pod 中,或者通过 k8s operator 将 vault 数据同步到 k8s secrets 中

一、Vault 基础概念

​ 几乎所有的 Vault 组件都被统称为「屏障(Barrier)」。Vault 可以简单地被划分为存储后端(Storage Backend)、屏障(Barrier)和 HTTP/S API 三个部分。

​ Vault,翻译成中文就是金库。类比银行金库,「屏障」就是用于保护金库的合金大门钢筋混凝土,存储后端和客户端之间的所有数据流动都需要经过它。「屏障」确保只有加密数据会被写入存储后端,加密数据在经过「屏障」被读出的过程中被验证与解密。和银行金库的大门非常类似,「屏障」也必须先解封,才能解密存储后端中的数据。

1、数据存储及加密解密

存储后端(Storage Backend): Vault 自身不存储数据,因此需要为它配置一个存储后端。 存储后端是不受信任的,只用于存储加密数据。

初始化(Initialization): Vault 在首次启动时需要初始化,这一步生成一个加密密钥(Encryption Key)用于加密数据,加密完成的数据才能被保存到存储后端

解封(Unseal): Vault 启动后,因为不知道加密密钥所以无法解密数据,这种状态被形象得称作已封印(Sealed)。在解封前 Vault 无法进行任何操作。

加密密钥主密钥(Master Key)保护,我们必须提供主密钥才能解密出 Vault 的加密密钥,从而完成解封操作。

默认情况下,Vault 使用沙米尔密钥分割算法主密钥分割成五个分割密钥(Key Shares),必须要提供其中任意三个分割密钥才能重建出主密钥,完成解封操作。

分割密钥的总数,以及重建主密钥最少需要的分割密钥数量,都是可以调整的。 沙米尔密钥分割算法也可以关闭,这样主密钥将被直接提供给管理员,管理员可直接使用它进行解封操作。

2. 认证系统及权限系统

在解封完成后,Vault 就可以开始处理请求了。

HTTP 请求进入后的整个处理流程都由 vault core 管理,core 会强制进行 ACL 检查,并确保审计日志(audit logging)完成记录。

客户端首次连接 vault 时,需要先完成身份认证,vault 的 auth methods 模块有很多身份认证方法可选:

1)用户友好的认证方法,适合管理员使用:username/password、云服务商、ldap,在创建 user 的时候,需要为 user 绑定 policy,给予合适的权限。

2)应用友好的方法,适合应用程序使用:public/private keys、tokens、kubernetes、jwt身份验证请求流经 core 并进入 auth methodsauth methods 确定请求是否有效并返回「关联策略(policies)」的列表。

ACL 策略policy store 负责管理与存储,由 core 进行 ACL 检查。 ACL 的默认行为是拒绝,这意味着除非明确配置 policy 允许某项操作,否则该操作将被拒绝。在通过 auth methods 完成了身份认证,并且返回的关联策略也没毛病之后,token store 将会生成并管理一个新的凭证(token), 这个 token 会被返回给客户端,用于进行后续请求。类似 web 网站的 cookie,token 也都存在一个租期(lease)或者说有效期,这加强了安全性。token 关联了相关的策略 policies,这些策略将被用于验证请求的权限。

请求经过验证后,将被路由到 secret engine。如果 secret engine 返回了一个 secret(由 vault 自动生成的 secret), core 会将其注册到 expiration manager,并给它附加一个 lease ID。lease ID 被客户端用于更新(renew)或吊销(revoke)它得到的 secret.

如果客户端允许租约(lease)到期,expiration manager 将自动吊销这个 secret.

core 还负责处理审核代理 audit broker的请求及响应日志,将请求发送到所有已配置的审核设备 audit devices. 不过默认情况下这个功能貌似是关闭的。

3. Secret Engine

Secret Engine 是保存、生成或者加密数据的组件,它非常灵活。

有的 Secret Engines 只是单纯地存储与读取数据,比如 kv 就可以看作一个加密的 Redis。 而其他的 Secret Engines 则连接到其他的服务并按需生成动态凭证。

还有些 Secret Engines 提供「加密即服务(encryption as a service)」的能力,如 transit、证书管理等。

常用的 engine 举例:

1)AliCloud Secrets Engine: 基于 RAM 策略动态生成 AliCloud Access Token,或基于 RAM 角色动态生成 AliCloud STS 凭据,Access Token 会自动更新(Renew),而 STS 凭据是临时使用的,过期后就失效了。

2)kv: 键值存储,可用于存储一些静态的配置。它一定程度上能替代掉携程的 Apollo 配置中心。

3)Transit Secrets Engine: 提供加密即服务的功能,它只负责加密和解密,不负责存储。主要应用场景是帮 app 加解密数据,但是数据仍旧存储在 MySQL 等数据库中。

二、部署

1、docker-compose

version: '3.3'
services:
  vault:
    image: vault:1.12.3
    container_name: vault
    ports:
      - "8200:8200"
    restart: always
    volumes:
      - ./logs:/vault/logs
      - ./file:/vault/date
      - ./config.hcl:/vault/config/config.hcl
      #- ./certs:/certs
    # vault 需要锁定内存以防止敏感值信息被交换(swapped)到磁盘中
    # 为此需要添加如下 capability
    cap_add:
      - IPC_LOCK
    entrypoint: vault server -config /vault/config/config.hcl

config.hcl 内容如下:

# 单机版
ui = true

listener "tcp" {
  tls_disable = 1
  address = "[::]:8200"
  cluster_address = "[::]:8201"
}
storage "file" {
  path = "/vault/data"
}
ui = true

// 使用文件做数据存储(单节点)
storage "raft" {
  path    = "/vault/date"
  node_id = "node1"
}

listener "tcp" {
  address = "[::]:8200"

  tls_disable = "true"

}

api_addr = "http://0.0.0.0/8200"
// ha 访问入口,不能写0.0.0.0
cluster_addr = "http://172.16.78.161:8201"

2、初始化

# 设置vault地址
[root@sugar vault]# export VAULT_ADDR='http://127.0.0.1:8200'

# 复制命令到宿主机
[root@sugar vault]# docker  cp vault:/bin/vault /usr/local/bin/vault
[root@sugar vault]# chmod +x /usr/local/bin/vault

[root@sugar vault]# vault operator init
vault operator init
Unseal Key 1: aTpDcs5FQ1GzklifRwYCKdLPHpCUvNm9EGhZ0NH8Ut0p
Unseal Key 2: 33V89ljeUr23AQpBgDANgFRzft+8f7E5KRXPDjsFpOG2
Unseal Key 3: OwfbdEA481baX8TZ0/kT49dBFg6ArXboVapBAd1y0fqk
Unseal Key 4: DD1Lp86WGqVq+18LwUdGPxb6cquTjSz/yAIc9fkYNzF9
Unseal Key 5: FrqRqQSlhb/sjj7h9Pcau5eqrB/rxtkFBbBJSfuv48IM

Initial Root Token: hvs.FTcALTSfrVuw9On8B2g2JQIl

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

3、解封

[root@sugar vault]# vault operator unseal aTpDcs5FQ1GzklifRwYCKdLPHpCUvNm9EGhZ0NH8Ut0p
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    1/3
Unseal Nonce       8d5a33c3-bff5-e407-eb13-15c1457bdb63
Version            1.12.3
Build Date         2023-02-02T09:07:27Z
Storage Type       raft
HA Enabled         true



[root@sugar vault]# vault operator unseal OwfbdEA481baX8TZ0/kT49dBFg6ArXboVapBAd1y0fqk
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    2/3
Unseal Nonce       8d5a33c3-bff5-e407-eb13-15c1457bdb63
Version            1.12.3
Build Date         2023-02-02T09:07:27Z
Storage Type       raft
HA Enabled         true
[root@sugar vault]# vault operator unseal 'FrqRqQSlhb/sjj7h9Pcau5eqrB/rxtkFBbBJSfuv48IM'
Key                     Value
---                     -----
Seal Type               shamir
Initialized             true
Sealed                  false
Total Shares            5
Threshold               3
Version                 1.12.3
Build Date              2023-02-02T09:07:27Z
Storage Type            raft
Cluster Name            vault-cluster-8257ffed
Cluster ID              2eccf238-400e-1ba5-f491-321c1388c826
HA Enabled              true
HA Cluster              n/a
HA Mode                 standby
Active Node Address     <none>
Raft Committed Index    31
Raft Applied Index      31
[root@sugar vault]# vault status
Key                     Value
---                     -----
Seal Type               shamir
Initialized             true
Sealed                  false
Total Shares            5
Threshold               3
Version                 1.12.3
Build Date              2023-02-02T09:07:27Z
Storage Type            raft
Cluster Name            vault-cluster-8257ffed
Cluster ID              2eccf238-400e-1ba5-f491-321c1388c826
HA Enabled              true
HA Cluster              https://192.168.10.161:8201
HA Mode                 active
Active Since            2023-03-07T12:11:38.884017872Z
Raft Committed Index    36
Raft Applied Index      36

4、登录测试

# export VAULT_TOKEN='hvs.FTcALTSfrVuw9On8B2g2JQIl'

[root@jenkins116 vault]# vault login 
Token (will be hidden): 
WARNING! The VAULT_TOKEN environment variable is set! The value of this
variable will take precedence; if this is unwanted please unset VAULT_TOKEN or
update its value accordingly.

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.FTcALTSfrVuw9On8B2g2JQIl
token_accessor       vkDaIsiPx3w9JEpUWv9NRnQm
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

三、策略与授权

  • 参考链接:https://zhuanlan.zhihu.com/p/370343645

​ Vault模拟了一个文件系统,Vault中所有的信息,包括机密、配置等,都是依照各自的路径来使用的。使用Vault策略,我们可以使用声明式的语法来赋予或者禁止对特定路径的特定操作。Vault的策略默认情况下是拒绝一切访问的,所以一个空的策略不会赋予对系统的任何访问权限。

1、策略语法

策略使用HCL或是JSON语法编写,描述了一个人类用户或是应用程序允许访问Vault中哪些路径。

一个简单的例子,赋予对secret/foo路径的读权限:

path "secret/foo" {
  capabilities = ["read"]
}

当这个策略被附加到一个令牌后,该令牌可以读取secret/foo,然而无法修改或删除secret/foo,因为没有授予其相关能力(Capability)。由于策略系统是默认拒绝的,所以令牌在Vault中没有其他权限。

另一个更加丰富的策略,包含注释:

# 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"]
}

# Policies can also specify allowed, disallowed, and required parameters. Here
# the key "secret/restricted" can only contain "foo" (any value) and "bar" (one
# of "zip" or "zap").
path "secret/restricted" {
  capabilities = ["create"]
  allowed_parameters = {
    "foo" = []
    "bar" = ["zip", "zap"]
  }
}

策略基于路径匹配来验证一个请求所需要的能力。一个策略路径可以精准匹配一个确切的路径,或者可以使用*模式指定前缀匹配:

# Permit reading only "secret/foo". An attached token cannot read "secret/food"
# or "secret/foo/bar".
path "secret/foo" {
  capabilities = ["read"]
}

# Permit reading everything under "secret/bar". An attached token could read
# "secret/bar/zip", "secret/bar/zip/zap", but not "secret/bars/zip".
path "secret/bar/*" {
  capabilities = ["read"]
}

# Permit reading everything prefixed with "zip-". An attached token could read
# "secret/zip-zap" or "secret/zip-zap/zong", but not "secret/zip/zap
path "secret/zip-*" {
  capabilities = ["read"]
}

另外,路径当中可以使用+代表路径中一个段内任意长度的字符(从Vault 1.1开始支持):

# Permit reading the "teamb" path under any top-level path under secret/
path "secret/+/teamb" {
  capabilities = ["read"]
}

# Permit reading secret/foo/bar/teamb, secret/bar/foo/teamb, etc.
path "secret/+/+/teamb" {
  capabilities = ["read"]
}

Vault模拟了一个文件系统,所有的操作都对应了一个路径,以及对应的能力,即使是Vault内部的核心配置信息也挂载于sys/路径下。策略可以定义一个令牌对这些路径和能力的访问权限。

Vault采用一组具有优先级的判定规则来决定最为具体的路径匹配。如果一个匹配模式被多个策略使用并能匹配上给定路径,Vault会取其能力的并集。如果一个路径能被多个策略定义的不同的匹配模式所匹配,那么只有最高优先级的匹配会被采用。

假设对给定路径P,存在两条策略都能够匹配,它们的路径匹配模式分别是P1P2,Vault采用如下优先级规则:

1)如果P1中第一个+或是*出现的位置早于P2,那么采用P2

2)如果P1*结尾,而P2不是,那么采用P2

3)如果P1包含更多的+段,那么采用P2

4)如果P1更短,那么采用P2

5)如果P1得到的字典序更小,那么采用P2

举个例子,给定两个路径:secret/*secret/+/+/foo/*,由于第一个通配符的位置相同(都在secret/之后),并且都以*结尾,而后者拥有更多的通配符段(多了两个+/段),所以结果是使用secret/*路径模式的策略。

需要注意的是,*与正则表达式中的同符号并不同义,Vault仅允许*出现在模式的末尾。

如果赋予了list能力,需要注意的是因为list操作总是作用于一个路径前缀上,所以策略定义的路径匹配模式必须使用前缀匹配(即以*结尾)。

能力(Capabilites)

除了路径匹配模式以外,每条规则都必须指定一个或多个能力来定义细颗粒度的允许-禁止规则。能力永远以一个字符串列表的形式定义,哪怕只有一个能力。

需要注意的是,我们在下面列出能力的同时,也会给出该能力相对应的HTTP动词。当编写策略时,可以先查阅相关HTTP API文档了解路径信息以及相关HTTP动词,然后映射到策略定义中的能力。虽然映射关系并不是严格的1:1,但它们通常非常相似地匹配。

  • create(POST/PUT)——允许在指定路径创建数据。只有很少的Vault部件会区分createupdate,所以大多数操作同时需要create以及update能力。需要区分二者的部分会在相关文档中说明。
  • read(GET)——允许读取指定路径的数据
  • update(POST/PUT)——允许修改指定路径的数据。对多数Vault部件来说,这隐含了在指定位置创建初始值的能力
  • delete(DELETE)——允许删除指定路径的数据
  • list(LIST)——允许罗列指定路径的所有值。要注意的是,经由list操作返回的键是未经策略过滤的。请勿在键名中编码敏感信息。不是所有后端都支持list操作

下面的能力并无对应的HTTP动词:

  • sudo——允许访问需要根权限保护的路径。除非拥有sudo能力,否则令牌被禁止与这些路径交互(对这种路径的操作可能同时需要其他能力,例如readdelete) 例如,修改审计日志后端配置就需要令牌具有sudo特权。
  • deny——不允许访问。该定义总是优先于其他能力定义,包括sudo,只要能力中存在deny,就不允许执行任何操作

需要注意的是,上述的能力映射到的是HTTP动词而非实际底层执行的操作,这通常会使人感到困惑。举个例子,通过Vault的数据库机密引擎创建一个数据库用户名密码,底层实际执行的操作是创建,但对应的HTTP动词却是GET,所以要在对应路径上配置read能力。

2、细颗粒度控制

除却以上标准的能力,Vault还提供了对指定路径的更细颗粒度的权限控制。与路径相关联的功能优先于对参数的控制。

1)参数限制

需要首先说明,Version 2kv机密引擎不支持参数限制,所以以下例子均假设secret/路径挂载的是Version 1kv机密引擎。

Vault中的数据以键值对的形式表达:key=value。Vault策略可以进一步限制对指定路径下特定键和值的访问。这些可选的细颗粒度控制项有:

  • required_parameters——一组必须指定的参数:
# This requires the user to create "secret/foo" with a parameter named
# "bar" and "baz".
path "secret/foo" {
  capabilities = ["create"]
  required_parameters = ["bar", "baz"]
}

上述例子中,用户想要在secret/foo下创建数据,必须包含名为barbaz的参数。

  • allowed_parameters——针对指定路径允许操作的键值对的白名单。值为空白列表则代表允许使用任意值:
# This allows the user to create "secret/foo" with a parameter named
# "bar". It cannot contain any other parameters, but "bar" can contain
# any value.
path "secret/foo" {
  capabilities = ["create"]
  allowed_parameters = {
    "bar" = []
  }
}

上述例子允许在secret/foo下创建键为bar,值为任意值的数据。

给定非空列表值则意味着只能使用列表中限定的值:

# This allows the user to create "secret/foo" with a parameter named
# "bar". It cannot contain any other parameters, and "bar" can only
# contain the values "zip" or "zap".
path "secret/foo" {
  capabilities = ["create"]
  allowed_parameters = {
    "bar" = ["zip", "zap"]
  }
}

上述例子允许在secret/foo下创建键为bar,值为zipzap的数据。

如果指定了任意的键,那么所有未被指定的键都会被拒绝,除非同时指定了*参数为空列表,这就允许修改其他任意键数据。被指定的键仍然受到给定的值列表的限制:

# This allows the user to create "secret/foo" with a parameter named
# "bar". The parameter "bar" can only contain the values "zip" or "zap",
# but any other parameters may be created with any value.
path "secret/foo" {
  capabilities = ["create"]
  allowed_parameters = {
    "bar" = ["zip", "zap"]
    "*"   = []
  }
}

上述例子中,只限制对bar的值必须是zipzap,对其他键的值则没有任何限制,可以创建任意键。

重要的一点是,使用*可能会造成意外的后果:

# This allows the user to create or update "secret/foo" with a parameter
# named "bar". The values passed to parameter "bar" must start with "baz/"
# so values like "baz/quux" are fine. However, values like
# "baz/quux,wibble,wobble,wubble" would also be accepted. The API that
# underlies "secret/foo" might allow comma delimited values for the "bar"
# parameter, and if it did, specifying a value like
# "baz/quux,wibble,wobble,wubble" would result in 4 different values getting
# passed along. Seeing values like "wibble" or "wobble" getting passed to
# "secret/foo" might surprise someone that expected the allowed_parameters
# constraint to only allow values starting with "baz/".
path "secret/foo" {
  capabilities = ["create", "update"]
  allowed_parameters = {
    "bar" = ["baz/*"]
  }
}

在上面的例子中,我们限制对secret/foo只能写入键为bar的数据,并且值必须以baz/为前缀。比如bar=baz/quux这样的数据就是合法的。问题是,我们也可以把值设置成baz/quux,wibble,wobble,wubble,Vault会接纳这种带有分隔符的值,而这样的值可能会被应用程序解析为长度为4的列表,这样的话我们可能会惊讶地发现即使我们限制了bar的值必须以baz/为前缀,仍然读取到了诸如wibble这样的值。

  • denied_parameters——键值对的黑名单,优先级高于allowed_parameters

设置值为空列表会导致拒绝对对应键的任意修改。

# This allows the user to create "secret/foo" with any parameters not
# named "bar".
path "secret/foo" {
  capabilities = ["create"]
  denied_parameters = {
    "bar" = []
  }
}

上面的例子禁止在secret/foo下创建键为bar的任意键值对。

如果对denied_parameters赋值一个非空列表,会导致禁止参数的值包含列表中的任意元素:

# This allows the user to create "secret/foo" with a parameter named
# "bar". It can contain any other parameters, but "bar" cannot contain
# the values "zip" or "zap".
path "secret/foo" {
  capabilities = ["create"]
  denied_parameters = {
    "bar" = ["zip", "zap"]
  }
}

上述例子禁止设置secret/foobar的值为zip或是zap

设置denied_parameters的键为*,则禁止操作任意键:

# This allows the user to create "secret/foo", but it cannot have any
# parameters.
path "secret/foo" {
  capabilities = ["create"]
  denied_parameters = {
    "*" = []
  }
}

如果denied_parameters配置了任意键,那么默认所有未被指定的键都是允许操作的,除非另有显式的allowed_parameters配置。

参数限制中的值也支持前缀与后缀表示:

path "secret/foo" {
  capabilities = ["create"]
  allowed_parameters = {
    "bar" = ["foo-*"]
  }
}

上面的例子规定对secret/foo,只能创建键为bar,值以foo-为前缀的键值对。

path "secret/foo" {
  capabilities = ["create"]
  allowed_parameters = {
    "bar" = ["*-foo"]
  }
}

而上面的例子则是限制值必须以-foo为后缀。

2)限制响应封装的有效期

Vault可以设置响应封装机制。我们可以在策略中使用相关参数限制客户端可以申请的响应封装的有效期时限,精确到秒。我们可以通过sm或是h后缀来代表秒、分钟和小时。

在实践中,为特定路径指定值为一秒的min_wrapping_ttl可以达到强制必须以响应封装的形式返回相应路径数据的目的。

  • min_wrapping_ttl——客户端可以指定的响应封装有效期的最小值,如果设置该值,则强制必须以响应封装的形式返回相应路径的数据
  • max_wrapping_ttl——允许设置的响应封装有效期的最大值。
# This effectively makes response wrapping mandatory for this path by setting min_wrapping_ttl to 1 second.
# This also sets this path's wrapped response maximum allowed TTL to 90 seconds.
path "auth/approle/role/my-role/secret-id" {
    capabilities = ["create", "update"]
    min_wrapping_ttl = "1s"
    max_wrapping_ttl = "90s"
}

上面的例子限制了用户只能以响应封装的形式返回auth/approle/role/my-role/secret-id的值,并且响应封装的有效期最大为90秒。

当两个参数同时被指定时,最小值必须小于最大值。另外如果当不同区块的路径合并时,最低值会被采纳,目的是使得令牌的有效期尽可能的短。

3、内建策略

Vault有两个内建策略:defaultroot。本节来讨论下这两个内建策略。

1)Default策略

default策略是一个无法删除的Vault内建策略。默认情况下,它被附加到所有令牌上,但可以通过使用身份验证方式创建令牌时显式排除之。

该策略包含了基础的功能,例如准许令牌查询有关自身的数据以及使用**Cubbyhole数据**。然而,Vault并不限制该策略的内容,你可以根据需要修改它。Vault永远不会覆盖你的设置。如果你想把default策略同步到最新的Vault版本的默认值,只要用新版Vault执行vault server -dev启动一个测试服务,读取它的default策略内容,然后写回到原来的Vault服务的default策略即可:

$ vault read sys/policy/default
Key      Value
---      -----
name     default
rules    # Allow tokens to look up their own properties
path "auth/token/lookup-self" {
    capabilities = ["read"]
}

# Allow tokens to renew themselves
path "auth/token/renew-self" {
    capabilities = ["update"]
}

# Allow tokens to revoke themselves
path "auth/token/revoke-self" {
    capabilities = ["update"]
}

# Allow a token to look up its own capabilities on a path
path "sys/capabilities-self" {
    capabilities = ["update"]
}

# Allow a token to look up its own entity by id or name
path "identity/entity/id/{{identity.entity.id}}" {
  capabilities = ["read"]
}
path "identity/entity/name/{{identity.entity.name}}" {
  capabilities = ["read"]
}


# Allow a token to look up its resultant ACL from all policies. This is useful
# for UIs. It is an internal path because the format may change at any time
# based on how the internal ACL features and capabilities change.
path "sys/internal/ui/resultant-acl" {
    capabilities = ["read"]
}

# Allow a token to renew a lease via lease_id in the request body; old path for
# old clients, new path for newer
path "sys/renew" {
    capabilities = ["update"]
}
path "sys/leases/renew" {
    capabilities = ["update"]
}

# Allow looking up lease properties. This requires knowing the lease ID ahead
# of time and does not divulge any sensitive information.
path "sys/leases/lookup" {
    capabilities = ["update"]
}

# Allow a token to manage its own cubbyhole
path "cubbyhole/*" {
    capabilities = ["create", "read", "update", "delete", "list"]
}

# Allow a token to wrap arbitrary values in a response-wrapping token
path "sys/wrapping/wrap" {
    capabilities = ["update"]
}

# Allow a token to look up the creation time and TTL of a given
# response-wrapping token
path "sys/wrapping/lookup" {
    capabilities = ["update"]
}

# Allow a token to unwrap a response-wrapping token. This is a convenience to
# avoid client token swapping since this is also part of the response wrapping
# policy.
path "sys/wrapping/unwrap" {
    capabilities = ["update"]
}

# Allow general purpose tools
path "sys/tools/hash" {
    capabilities = ["update"]
}
path "sys/tools/hash/*" {
    capabilities = ["update"]
}

# Allow checking the status of a Control Group request if the user has the
# accessor
path "sys/control-group/request" {
    capabilities = ["update"]
}

创建令牌时排除default策略:

$ vault token create -no-default-policy

或是调用API:

$ curl \
  --request POST \
  --header "X-Vault-Token: ..." \
  --data '{"no_default_policy": "true"}' \
  https://vault.hashicorp.rocks/v1/auth/token/create

根策略

root策略是一个无法删除也无法修改的Vault内建策略。任何关联了该策略的用户都将是根用户。根用户可以在Vault内执行任意操作,强烈建议在生产环境中使用Vault前首先吊销所有的根令牌

每当Vault服务被首次初始化时,都会创建一个根用户。这个根用户是用来执行Vault的初始化配置的。配置完成后,应创建并使用由细颗粒度策略约束的用户并启用身份认证方式,然后吊销根令牌。

要吊销根令牌可以使用命令行:

$ vault token revoke "<token>"

或是通过HTTP API:

$ curl \
  --request POST \
  --header "X-Vault-Token: ..." \
  --data '{"token": "<token>"}' \
  https://vault.hashicorp.rocks/v1/auth/token/revoke

四、vault cli使用

显示所有策略

$ vault read sys/policy

上传并创建策略

$ vault policy write policy-name policy-file.hcl

# http
$ curl \
  --request POST \
  --header "X-Vault-Token: ..." \
  --data '{"policy":"path \"...\" {...} "}' \
  https://vault.hashicorp.rocks/v1/sys/policy/policy-name
  

# 这两个例子里,策略的名称都是 policy-name。你可以把策略名理解成指向策略规则的指针。令牌通过策略名关联相关策略规则。

更新策略

$ vault write my-existing-policy updated-policy.json

# http
$ curl \
  --request POST \
  --header "X-Vault-Token: ..." \
  --data '{"policy":"path \"...\" {...} "}' \
  https://vault.hashicorp.rocks/v1/sys/policy/my-existing-policy

删除策略

$ vault delete sys/policy/policy-name

# http
$ curl \
  --request DELETE \
  --header "X-Vault-Token: ..." \
  https://vault.hashicorp.rocks/v1/sys/policy/policy-name


# 删除是一个幂等操作。删除一个不存在的策略不会导致Vault返回错误。

五、用户权限管理

1、关联策略

Vault可以通过身份认证方式登录时自动在令牌上关联一组策略,相关配置随具体的身份认证方式类型而不同。简单起见,我们演示一下Vault内建的userpass认证方式。

Vault管理员或是安全团队成员可以用如下命令行创建一个关联了一组策略的用户:

# 开启用户登录
vault auth enable userpass

# http 方式开启
$ curl \
  --request DELETE \
  --header "X-Vault-Token: ..." \
  https://vault.hashicorp.rocks/v1/sys/policy/policy-name

curl -XPOST -s -H "X-Vault-Token: xxxxx" "http://vault.local.com/v1/sys/auth/userpass" -d'{"type": "userpass"}'

# 创建sethvargo用户,并设置密码和策略
$ vault write auth/userpass/users/sethvargo \
    password="s3cr3t!" \
    policies="dev-readonly,logs"

用户可以用命令行执行身份认证,获取令牌:

$ vault login -method="userpass" username="sethvargo"
Password (will be hidden): ...

如果用户名密码正确,Vault会创建一个令牌,将预设的策略附加在令牌上,然后返回给用户。

2、创建令牌时附加策略

可以在通过命令行创建令牌时关联策略:

$ vault token create -policy=dev-readonly -policy=logs

子令牌可以关联一组父令牌拥有的策略的子集。根用户可以分派任意策略。

一旦令牌被签发,其关联的策略无法再被修改。必须吊销旧令牌并申请新令牌才能得到更新后的关联策略。

然而,令牌关联的策略内容是实时解析的,也就是说,如果更新了策略内容,附加此策略的令牌下次的请求就会按照新策略内容进行权限检查。

六、terraform 管理

terraform {
  required_providers {
    vault = {
      source = "hashicorp/vault"
    }
  }
}

provider "vault" {
  address = var.vault-init.url
  token   = var.vault-init.token

}


resource "vault_mount" "secret_path" {
  for_each = var.vault-init.secret_path

  path        = each.value.path
  type        = each.value.type
  description = each.value.description
  options = {
    version = "1"
  }
}


resource "vault_policy" "policy" {
  for_each = var.vault-init.policy
  name     = each.key

  policy = each.value.policy

}

resource "vault_auth_backend" "userpass" {
  type = "userpass"
}


resource "vault_generic_endpoint" "create_user" {
  for_each = var.vault-init.userlist

  path                 = "auth/userpass/users/${each.key}"
  ignore_absent_fields = true
  data_json            = jsonencode(each.value)

  depends_on = [
    vault_auth_backend.userpass,
    vault_policy.policy
  ]
}

resource "vault_generic_secret" "secret_value" {
  for_each = var.vault-init.secret_value

  path      = "${each.value.path}/${each.key}"
  data_json = jsonencode(each.value.data)
  depends_on = [
    vault_mount.secret_path,
    vault_policy.policy
  ]
}

variable "vault-init" {
  type = any
  default = {
    url          = null
    token        = null
    secret_path  = {}
    userlist     = {}
    policy       = {}
    secret_value = {}
  }
}

tfvars

vault-init = {

  url = "http://127.0.0.1:8200"

  token = "cccccccccc"


  secret_path = {
    p2 = {
      path        = "Monitor"
      type        = "kv"
      description = "Metrics/Tracing/Logs"
    },
    p5 = {
      path        = "APP"
      type        = "kv"
      description = "Gitea/Gitlab"
    }
  }

  policy = {
    admin = {
      policy = <<EOT
path "cubbyhole/*" {
  capabilities = ["deny"]
}
path "sys/auth" {
  capabilities = ["read"]
}
path "auth/userpass/users/*" {
  capabilities = ["list", "read", "update"]
}
path "Monitor/+" {
  capabilities = ["read", "list"]
}
path "APP/+" {
  capabilities = ["read", "list", "update", "patch"]
}
path "APP/+/+" {
  capabilities = ["read", "list"]
}

EOT
    },
    user = {
      policy = <<EOT
path "cubbyhole/*" {
  capabilities = ["deny"]
}
path "Hardware/+" {
  capabilities = ["deny"]
}
path "Monitor/+" {
  capabilities = ["read", "list"]
}
EOT
    }

  }

  userlist = {
    user = {
      username = "user",
      password = "hello_world",
      policies = ["user"]
    },
    admin = {
      username = "admin",
      password = "hello_world",
      policies = ["admin"]
    }
  }


  secret_value = {
    Grafana = {
      path = "Monitor"
      data = {
  "External Address"= "http://grafana.local.com",
  "Password"= "CvcTKkm4xxxxxxxxx",
  "Username"= "admin"
}


    }
  }
}

Terraform vault unseal

vault 官方支持的自动 unseal 依赖于第三方服务,除此以外可以使用 vault cli 和 http 方式解封。

基于 scottwinkler/shell,通过执行命令进行 vault 的初始化和 unseal

terraform {
  required_providers {
    helm = {
      source  = "hashicorp/helm"
    }
    shell = {
      source  = "scottwinkler/shell"
      version = "1.7.10"
    }
  }

}


module "vault" {
  count  = var.vault.enabled ? 1 : 0
  source = "xxxx"
  xxxxx
}

resource "shell_script" "vault_init" {
  count  = var.vault.enabled ? 1 : 0
  lifecycle_commands {
    create = <<EOF
      sleep 50
      kubectl -n ${module.vault[0].namespace} exec vault-0 -- vault operator init -key-shares=6  -key-threshold=3 -format=json
    EOF
    delete = ""
  }
  environment = {
    KUBECONFIG = var.kubeconfig
  }
  depends_on = [
    module.vault,
    ]
}

resource "shell_script" "vault_unseal" {
  count = var.vault.enabled ? 1 : 0
  lifecycle_commands {
    create = <<EOF
      kubectl -n ${module.vault[0].namespace} exec vault-0 -- vault operator unseal ${jsondecode(shell_script.vault_init[0].output.unseal_keys_b64)[0]}
      kubectl -n ${module.vault[0].namespace} exec vault-0 -- vault operator unseal ${jsondecode(shell_script.vault_init[0].output.unseal_keys_b64)[1]}
      kubectl -n ${module.vault[0].namespace} exec vault-0 -- vault operator unseal ${jsondecode(shell_script.vault_init[0].output.unseal_keys_b64)[2]}
    EOF
    delete = ""
  }
  environment = {
    KUBECONFIG = var.kubeconfig
  }
  depends_on = [module.vault]
}