在极狐GitLab CI/CD 中使用 Hashicorp vault 专业版
CI_JOB_JWT
进行认证在极狐GitLab 15.9 中被弃用并在极狐GitLab 17.0 中被移除。您可以使用ID 令牌来认证 HashiCorp Vault。此教程演示如何从极狐GitLab CI/CD 中进行身份验证、配置和读取 HashiCorp 的 Vault 中的密钥。
先决条件
此教程假设你熟悉极狐GitLab CI/CD 和 Vault。
您必须具有:
- 极狐GitLab 账号。
- 正在运行的 Vault 服务器(至少 v1.2.0)以配置身份验证并创建角色和策略。对于 HashiCorp Vault,这可以是开源版本或企业版本。
vault.example.com
URL 替换为您自己 vault 服务器的 URL。工作原理
ID 令牌是 JWT,用于使用第三方服务进行 OIDC 验证。如果作业至少定一个了以 ID 令牌,secrets
关键字会自动使用该令牌进行身份验证。
JWT 中包含如下字段:
字段 | 合适 | 描述 |
---|---|---|
jti |
Always | 此令牌的唯一身份识别 |
iss |
Always | 极狐GitLab 实例域名的颁发者 |
iat |
Always | 签发于 |
nbf |
Always | 在xx之间无效 |
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_path |
Always | 作业的完全限定引用。比如 refs/heads/main .引入于极狐GitLab 16.0 |
ref_protected |
Always |
true 如果 Git ref 是受保护的,则为 true ,否则为 false
|
environment |
Job specifies an environment | 此作业指定的环境 |
groups_direct |
User is a direct member of 0 to 200 groups | 用户的直接成员关系群组路径。如果用户是超过 200 个组的直接成员,则省略该内容。引入于极狐GitLab 16.11 |
environment_protected |
Job specifies an environment | 如果指定的环境是受保护的,则为 true ,否则为 false
|
deployment_tier |
Job specifies an environment | 此作业指定的环境的部署层级,引入于GitLab 15.2 |
environment_action |
Job specifies an environment | 作业中指定的环境操作 (environment:action ),引入于极狐GitLab 16.5 |
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_path": "refs/heads/auto-deploy-2020-04-01",
"ref_protected": "true",
"groups_direct": ["mygroup/mysubgroup", "myothergroup/myothersubgroup"],
"environment": "production",
"environment_protected": "true",
"environment_action": "start"
}
JWT 使用 RS256 进行了编码,使用专用私钥进行签名。如果指定了作业超时,则令牌的过期时间为作业超时,否则为 5 分钟。签名此令牌的 key 可能在毫无通知的情况下发生变更。在这种情况下,重试作业将使用当前签名密钥生成新的 JWT。
您可以使用此 JWT 进行身份验证,该 Vault 服务器已配置为允许 JWT 身份验证方法。将您的极狐GitLab 实例的基 URL(例如 https://gitlab.example.com
)提供给您的 Vault 服务器作为 oidc_discovery_url
。然后,服务器可以从您的实例中检索用于验证令牌的密钥。
当在 Vault 中配置角色时,您可以使用绑定声明来匹配 JWT 声明并限制每个 CI/CD 作业可以访问哪些秘密。
为了和 Vault 通信,您可以使用其 CLI 客户端或执行 API 请求(使用 curl
或其他客户端)。
示例
假设您在运行于 http://vault.example.com:8200
的 Vault 服务器上存储了您的 staging 和生产数据库的密码。您的 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/
然后创建允许您读取这些密钥的策略(每个密钥一个策略):
$ 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 和这些策略的角色。
比如,一个用于 staging 的角色,名为 myproject-staging
。绑定声明被配置为仅允许在项目 ID 为 22
的项目中使用 main
分支的策略:
$ vault write auth/jwt/role/myproject-staging - <<EOF
{
"role_type": "jwt",
"policies": ["myproject-staging"],
"token_explicit_max_ttl": 60,
"user_claim": "user_email",
"bound_audiences": "https://vault.example.com",
"bound_claims": {
"project_id": "22",
"ref": "main",
"ref_type": "branch"
}
}
EOF
另外一个用于生产环境的角色,名为 myproject-production
。这个角色的 bound_claims
部分只允许匹配 auto-deploy-*
模式的受保护分支访问这些秘密。
$ vault write auth/jwt/role/myproject-production - <<EOF
{
"role_type": "jwt",
"policies": ["myproject-production"],
"token_explicit_max_ttl": 60,
"user_claim": "user_email",
"bound_audiences": "https://vault.example.com",
"bound_claims_type": "glob",
"bound_claims": {
"project_id": "22",
"ref_protected": "true",
"ref_type": "branch",
"ref": "auto-deploy-*"
}
}
EOF
和受保护分支结合使用,您可以限制谁能够进行身份验证并读取这些密钥。
包含在 JWT中的任何声明都可以与绑定声明中的值列表进行匹配。例如:
"bound_claims": {
"user_login": ["alice", "bob", "mallory"]
}
"bound_claims": {
"ref": ["main", "develop", "test"]
}
"bound_claims": {
"namespace_id": ["10", "20", "30"]
}
"bound_claims": {
"project_id": ["12", "22", "37"]
}
- 如果仅使用
namespace_id
,则允许命名空间中的所有项目。嵌套项目不包括在内,因此如果需要,必须将它们的命名空间 ID 也添加到列表中。 - 如果同时使用
namespace_id
和project_id
,Vault 首先检查项目的命名空间是否在namespace_id
中,然后检查项目是否在project_id
中。
token_explicit_max_ttl
指定了 Vualt 发布的令牌,在成功登录后,具有 60 秒的硬生命周期限制。
user_claim
指定了 Vault 在成功登录时创建的身份别名的名称。
bound_claims_type
配置了 bound_claims
值的解释。如果设置为 glob
,则值将被解释为 globs,其中 *
匹配任意数量的字符。
在上表中列出的声明字段也可以通过使用 Vault 中 JWT 身份验证的访问器名称来访问,用于 Vault 的策略路径模板化目的。可以通过运行 vault auth list
来获取挂载访问器名称(下面示例中的 ACCESSOR_NAME
)
使用名为 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 \
oidc_discovery_url="https://gitlab.example.com" \
bound_issuer="https://gitlab.example.com"
bound_issuer
指明了只有 iss
声明设置为 gitlab.example.com
的 JWT 才能使用此方法进行身份验证,并且 oidc_discovery_url
(https://gitlab.example.com
) 应用于验证令牌。
可用配置选项的详细列表,可以查看 Vault 的 API 文档。
在极狐GitLab 中,创建以下 CI/CD 变量以提供有关您的 Vault 服务器的详细信息:
-
VAULT_SERVER_URL
- 您 Vault 服务器的 URL,比如https://vault.example.com:8200
。 -
VAULT_AUTH_ROLE
- 可选。尝试进行身份验证时要使用的角色。如果没有指定角色,Vault 将使用在配置身份验证方法时指定的默认角色。 -
VAULT_AUTH_PATH
- 可选。身份验证方法安装的路径。默认为jwt
。 -
VAULT_NAMESPACE
- 可选。用于读取秘密和身份验证的 Vault Enterprise 命名空间。如果没有指定命名空间,Vault 将使用根 (/
) 命名空间。该设置被 Vault Open Source 忽略。
如下作业,当为默认分支运行时,可以读取 secret/myproject/staging/
下的密钥,但不能读取 secret/myproject/production/
下的密钥:
job_with_secrets:
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.com
secrets:
STAGING_DB_PASSWORD:
vault: secret/myproject/staging/db/password@secrets # authenticates using $VAULT_ID_TOKEN
script:
- access-staging-db.sh --token $STAGING_DB_PASSWORD
在此示例中:
-
id_tokens
- 用于 OIDC 身份验证的 JSON Web 令牌 (JWT)。aud
声明设置为匹配 Vault JWT 身份验证方法的bound_audiences
参数。 -
@secrets
- Vault 名称,其中启用了 Secret Engines。 -
secret/myproject/staging/db
- Vault 中密钥的路径位置。 -
password
在引用的密钥中要提取的字段。
限制令牌来访问 Vault 密钥
您可以通过 Vault 保护和极狐GitLab 功能来控制 ID 令牌对 Vault 密钥的访问。比如,通过如下方法限制令牌:
- 为指定的 ID 令牌
aud
声明,使用 Vault bound audiences。 - 为指定组使用
group_claim
,使用 Vault bound claims。 - 为指定用户硬编码 Vault bound claims 值,基于
user_login
和user_email
。 - 为 Vault token 设置时间限制,如
token_explicit_max_ttl
, - 将 JWT 作用域限制到极狐GitLab 的受保护分支,
- 将 JWT 作用域限制到极狐GitLab 的受保护标签。
故障排查
The secrets provider can not be found. Check your CI/CD variables and try again.
消息
当尝试启动配置了访问 Hashicorp Vault 的作业时,您可能会接收到如下错误:
The secrets provider can not be found. Check your CI/CD variables and try again.
作业无法被创建,因为所需的变量未定义。
VAULT_SERVER_URL