使用 HashiCorp Vault 验证和读取 secrets
本教程演示了如何使用来自 GitLab CI/CD 的 HashiCorp 的 Vault 进行身份验证、配置和读取 secrets。
要求
本教程假设您熟悉 GitLab CI/CD 和 Vault。
您必须具备:
- 极狐GitLab 上的一个账户。
- 访问正在运行的 Vault 服务器(至少 v1.2.0)的权限,配置身份验证并创建角色和策略。对于 HashiCorp Vaults,这可以是开源或企业版本。
vault.example.com
URL 替换为 Vault 服务器的 URL,并将 gitlab.example.com
替换为极狐GitLab 实例的 URL。工作原理
每个作业都有 JSON Web Token (JWT) 作为 CI/CD 变量提供,名为 CI_JOB_JWT
。此 JWT 可用于使用 JWT Auth 方法对 Vault 进行身份验证。
JWT 中包含以下字段:
字段 | When | 描述 |
---|---|---|
jti |
Always | 此令牌的唯一标识符 |
iss |
Always | 发行者,您的极狐GitLab 实例的域名 |
iat |
Always | 发行时间 |
nbf |
Always | 生效时间 |
exp |
Always | 到期时间 |
sub |
Always | 主题(作业 ID) |
namespace_id |
Always | 使用它来按 ID 将范围限定为群组或用户级别的命名空间 |
namespace_path |
Always | 使用它来按路径将范围限定为群组或用户级别的命名空间 |
project_id |
Always | 使用它来按 ID 确定项目范围 |
project_path |
Always | 使用它来按路径确定项目范围 |
user_id |
Always | 执行作业的用户 ID |
user_login |
Always | 执行作业的用户的用户名 |
user_email |
Always | 执行作业的用户的电子邮件 |
pipeline_id |
Always | 此流水线的 ID |
pipeline_source |
Always | 流水线源 |
job_id |
Always | 作业的 ID |
ref |
Always | 此作业的 Git ref |
ref_type |
Always | Git ref 类型,branch 或 tag
|
ref_protected |
Always | 如果 Git ref 受保护,则为 true ,否则为 false
|
environment |
作业指定环境 | 此作业指定的环境(引入于 13.9 版本) |
environment_protected |
作业指定环境 | 如果指定环境受保护,则为 true ,否则为 false (引入于 13.9 版本) |
deployment_tier |
作业指定环境 | 此作业指定的环境的部署级别(引入于 15.2 版本) |
示例 JWT 有效负载:
{
"jti": "c82eeb0c-5c6f-4a33-abf5-4c474b92b558",
"iss": "gitlab.example.com",
"iat": 1585710286,
"nbf": 1585798372,
"exp": 1585713886,
"sub": "job_1212",
"namespace_id": "1",
"namespace_path": "mygroup",
"project_id": "22",
"project_path": "mygroup/myproject",
"user_id": "42",
"user_login": "myuser",
"user_email": "myuser@example.com",
"pipeline_id": "1212",
"pipeline_source": "web",
"job_id": "1212",
"ref": "auto-deploy-2020-04-01",
"ref_type": "branch",
"ref_protected": "true",
"environment": "production",
"environment_protected": "true"
}
JWT 使用 RS256 编码并使用专用私钥签名。令牌的过期时间设置为作业的超时(如果指定),否则设置为 5 分钟。用于签署此令牌的密钥可能会更改,恕不另行通知。在这种情况下,重试作业会使用当前签名密钥生成新的 JWT。
您可以使用此 JWT 和您的实例的 JWKS 端点 (https://gitlab.example.com/-/jwks
),向配置为允许 JWT 身份验证方法进行身份验证的 Vault 服务器进行身份验证。
在 Vault 中配置角色时,您可以使用 bound_claims 来匹配 JWT 的声明并限制每个 CI 作业可以访问的 secret。
要与 Vault 通信,您可以使用其 CLI 客户端或执行 API 请求(使用 curl
或其他客户端)。
示例
假设您将 staging 和生产数据库的密码存储在运行在 http://vault.example.com:8200
上的 Vault 服务器中。您的 staging 密码是 pa$$w0rd
,您的生产密码是 real-pa$$w0rd
。
$ vault kv get -field=password secret/myproject/staging/db
pa$$w0rd
$ vault kv get -field=password secret/myproject/production/db
real-pa$$w0rd
要配置您的 Vault 服务器,首先启用 JWT Auth 方法:
$ vault auth enable jwt
Success! Enabled jwt auth method at: jwt/
然后创建允许您读取这些 secrets 的策略(每个 secret 一个):
$ vault policy write myproject-staging - <<EOF
# Policy name: myproject-staging
#
# Read-only permission on 'secret/myproject/staging/*' path
path "secret/myproject/staging/*" {
capabilities = [ "read" ]
}
EOF
Success! Uploaded policy: myproject-staging
$ vault policy write myproject-production - <<EOF
# Policy name: myproject-production
#
# Read-only permission on 'secret/myproject/production/*' path
path "secret/myproject/production/*" {
capabilities = [ "read" ]
}
EOF
Success! Uploaded policy: myproject-production
您还需要将 JWT 与这些策略相关联的角色。
一个用于名为 myproject-staging
的 staging:
$ vault write auth/jwt/role/myproject-staging - <<EOF
{
"role_type": "jwt",
"policies": ["myproject-staging"],
"token_explicit_max_ttl": 60,
"user_claim": "user_email",
"bound_claims": {
"project_id": "22",
"ref": "master",
"ref_type": "branch"
}
}
EOF
还有一个用于 myproject-production
的生产:
$ vault write auth/jwt/role/myproject-production - <<EOF
{
"role_type": "jwt",
"policies": ["myproject-production"],
"token_explicit_max_ttl": 60,
"user_claim": "user_email",
"bound_claims_type": "glob",
"bound_claims": {
"project_id": "22",
"ref_protected": "true",
"ref_type": "branch",
"ref": "auto-deploy-*"
}
}
EOF
此示例使用 bound_claims 指定仅允许与指定声明匹配值的 JWT 进行身份验证。
结合受保护的分支,您可以限制谁能够验证和读取 secret。
要对项目列表使用相同的策略,请使用 namespace_id
:
"bound_claims": {
"namespace_id": ["12", "22", "37"]
}
JWT 中包含的任何声明都可以与绑定声明中的值列表进行匹配。例如:
"bound_claims": {
"user_login": ["alice", "bob", "mallory"]
}
"bound_claims": {
"ref": ["main", "develop", "test"]
}
"bound_claims": {
"project_id": ["12", "22", "37"]
}
token_explicit_max_ttl
指定 Vault 颁发的令牌在成功验证后具有 60 秒的硬性生命周期限制。
user_claim
指定 Vault 在成功登录时创建的身份别名的名称。
bound_claims_type
配置 bound_claims
值的解释。 如果设置为 glob
,这些值将被解释为 glob,其中 *
匹配任意数量的字符。
上表中列出的声明字段也可以使用 Vault 中 JWT auth 的访问者名称,访问 Vault 的策略路径模板 。mount accessor name(以下示例中的ACCESSOR_NAME
)可以通过运行 Vault auth list
检索。
使用名为 project_path
的命名元数据字段的策略模板示例:
path "secret/data/{{identity.entity.aliases.ACCESSOR_NAME.metadata.project_path}}/staging/*" {
capabilities = [ "read" ]
}
支持上述模板化策略的角色示例,通过使用 claim_mappings
配置将声明字段 project_path
映射为元数据字段 :
{
"role_type": "jwt",
...
"claim_mappings": {
"project_path": "project_path"
}
}
有关选项的完整列表,请参阅 Vault 的 创建角色文档。
project_id
或 namespace_id
)将您的角色限制为项目或命名空间。否则,此实例生成的任何 JWT 都可能被允许使用此角色进行身份验证。现在,配置 JWT 身份验证方法:
$ vault write auth/jwt/config \
jwks_url="https://gitlab.example.com/-/jwks" \
bound_issuer="gitlab.example.com"
bound_issuer
指定只有当 Issuer(即 iss
声明)的 JWT 设置为 gitlab.example.com
,才可以使用此方法进行身份验证,并且应该使用 JWKS 端点(https://gitlab.example.com/-/jwks
)来验证令牌。
有关可用配置选项的完整列表,请参阅 Vault 的 API 文档。
以下作业在为默认分支运行时,能够读取 secret/myproject/staging/
下的 secret,但不能读取 secret/myproject/production/
下的 secret:
read_secrets:
script:
# Check job's ref name
- echo $CI_COMMIT_REF_NAME
# and is this ref protected
- echo $CI_COMMIT_REF_PROTECTED
# Vault's address can be provided here or as CI/CD variable
- export VAULT_ADDR=http://vault.example.com:8200
# Authenticate and get token. Token expiry time and other properties can be configured
# when configuring JWT Auth - https://developer.hashicorp.com/vault/api/auth/jwt#parameters-1
- export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-staging jwt=$CI_JOB_JWT)"
# Now use the VAULT_TOKEN to read the secret and store it in an environment variable
- export PASSWORD="$(vault kv get -field=password secret/myproject/staging/db)"
# Use the secret
- echo $PASSWORD
# This will fail because the role myproject-staging can not read secrets from secret/myproject/production/*
- export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)"
VAULT_NAMESPACE
变量。它的默认值为 admin
。以下作业能够使用 myproject-production
角色进行身份验证并读取 /secret/myproject/production/
下的 secret:
read_secrets:
image: vault:latest
script:
# Check job's ref name
- echo $CI_COMMIT_REF_NAME
# and is this ref protected
- echo $CI_COMMIT_REF_PROTECTED
# Vault's address can be provided here or as CI/CD variable
- export VAULT_ADDR=http://vault.example.com:8200
# Authenticate and get token. Token expiry time and other properties can be configured
# when configuring JWT Auth - https://developer.hashicorp.com/vault/api-docs/auth/jwt#parameters-1
- export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-production jwt=$CI_JOB_JWT)"
# Now use the VAULT_TOKEN to read the secret and store it in environment variable
- export PASSWORD="$(vault kv get -field=password secret/myproject/production/db)"
# Use the secret
- echo $PASSWORD
限制令牌对 Vault secrets 的访问
您可以使用 Vault 保护和极狐GitLab 功能控制对 Vault 机密的 CI_JOB_JWT
访问。例如,通过以下方式限制令牌:
- 对使用
group_claim
的特定组使用 Vault bound_claims。 - 基于特定用户的
user_login
和user_email
的 Vault bound claims 的硬编码值。 - 根据
token_explicit_max_ttl
中指定的令牌 TTL 设置 Vault 时间限制,其中令牌在身份验证后过期。 - 将 JWT 范围限定为仅限项目用户子集的受保护分支。
- 将 JWT 范围限定为仅限项目用户子集的受保护标签。