{{< details >}}
- Tier: 专业版, 旗舰版
- Offering: JihuLab.com, 私有化部署
{{< /details >}}
{{< alert type=”warning” >}}
使用 CI_JOB_JWT
认证在极狐GitLab 15.9 中被弃用而且在极狐GitLab 17.0 中被移除。使用 HashiCorp Vault ID 令牌来认证取而代之。
{{< /alert >}}
{{< alert type=”note” >}}
从 Vault 1.17 开始,当 JWT 包含 aud
声明时,JWT 认证登录需要在角色上绑定受众。aud
声明可以是单个字符串或字符串列表。
{{< /alert >}}
这篇教程展示了如何通过极狐GitLab CI/CD 从 HashiCorp 的 Vault 进行身份验证、配置和读取密钥。
前提条件
这篇教程假设你已经熟悉极狐GitLab CI/CD 和 Vault。
为了跟进,你必须拥有:
- 一个极狐GitLab 账号。
- 访问运行的 Vault 服务器(至少 v1.2.0),以配置身份验证并创建角色和策略。 对于 HashiCorp Vault,可以是开源版或企业版。
{{< alert type=”note” >}}
你必须将下面的 vault.example.com
URL 替换为你的 Vault 服务器的 URL,
并将 gitlab.example.com
替换为你的极狐GitLab 实例的 URL。
{{< /alert >}}
工作原理
ID 令牌是用于与第三方服务进行 OIDC 身份验证的 JSON Web Tokens (JWTs)。
如果一个任务至少定义了一个 ID 令牌,secrets
关键字会自动使用该令牌与 Vault 进行身份验证。
JWT 中包含以下字段:
字段 | 何时 | 描述 |
---|---|---|
jti |
始终 | 此令牌的唯一标识符 |
iss |
始终 | 发行者,你的极狐GitLab 实例的域名 |
iat |
始终 | 签发时间 |
nbf |
始终 | 不在此之前有效 |
exp |
始终 | 到期时间 |
sub |
始终 | 主题(任务 ID) |
namespace_id |
始终 | 使用此 ID 将范围限定为群组或用户级别命名空间 |
namespace_path |
始终 | 使用此路径将范围限定为群组或用户级别命名空间 |
project_id |
始终 | 使用此 ID 将范围限定为项目 |
project_path |
始终 | 使用此路径将范围限定为项目 |
user_id |
始终 | 执行任务的用户的 ID |
user_login |
始终 | 执行任务的用户的用户名 |
user_email |
始终 | 执行任务的用户的电子邮件 |
pipeline_id |
始终 | 此流水线的 ID |
pipeline_source |
始终 | 流水线来源 |
job_id |
始终 | 此任务的 ID |
ref |
始终 | 此任务的 Git ref |
ref_type |
始终 | Git ref 类型,branch 或 tag
|
ref_path |
始终 | 此任务的完全限定 ref,例如 refs/heads/main 。引入于极狐GitLab 16.0。 |
ref_protected |
始终 | 如果此 Git ref 是受保护的,则为 true ,否则为 false
|
environment |
任务指定环境时 | 任务指定的环境 |
groups_direct |
用户是 0 到 200 个群组的直接成员时 | 用户直接成员群组的路径。如果用户是超过 200 个群组的直接成员,则省略。(引入于极狐GitLab 16.11)。 |
environment_protected |
任务指定环境时 | 如果指定的环境是受保护的,则为 true ,否则为 false
|
deployment_tier |
任务指定环境时 | 任务指定的环境的部署层级(引入于极狐GitLab 15.2) |
environment_action |
任务指定环境时 | 任务中指定的环境操作(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 分钟。用于签署此令牌的密钥可能会在没有任何通知的情况下更改。在这种情况下,重试任务会使用当前签名密钥生成新的 JWT。
你可以使用此 JWT 与配置为允许 JWT 身份验证方法的 Vault 服务器进行身份验证。将你的极狐GitLab 实例的基本 URL(例如 https://gitlab.example.com
)提供给你的 Vault 服务器作为 oidc_discovery_url
。服务器然后可以从你的实例检索密钥以验证令牌。
在 Vault 中配置角色时,你可以使用绑定声明来匹配 JWT 声明,并限制每个 CI/CD 任务可以访问的密钥。
与 Vault 通信时,你可以使用其 CLI 客户端或执行 API 请求(使用 curl
或其他客户端)。
示例
{{< alert type=”warning” >}}
JWT 是凭证,可以授予资源访问权限。注意你粘贴它们的地方!
{{< /alert >}}
假设你的 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/
然后创建允许你读取这些密钥的策略(每个密钥一个):
$ 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 角色。绑定声明 配置为只允许策略用于项目 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 包含的声明 中的任何声明字段,以限制每个 CI/CD 任务可以访问的密钥。例如:
"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
指定 Vault 在成功认证后发放的令牌具有 60 秒的硬生命时限。
user_claim
指定 Vault 在成功登录后创建的身份别名的名称。
bound_claims_type
配置 bound_claims
值的解释。如果设置为 glob
,则将这些值解释为 globs,其中 *
匹配任意数量的字符。
通过使用 Vault 中 JWT 身份验证的访问者名称,声明字段列表中表中的字段也可以用于 Vault 的策略路径模板目的。可以通过运行 vault auth list
来检索 mount 访问者名称(下面示例中的 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 的创建角色文档。
{{< alert type=”warning” >}}
始终使用提供的声明(例如 project_id
或 namespace_id
)将角色限制为项目或命名空间。否则任何由此实例生成的 JWT 都可能被允许使用此角色进行身份验证。
{{< /alert >}}
现在,配置 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 JWT Auth 角色名称。在本教程中,我们已经创建了两个名称为myproject-staging
和myproject-production
的角色。如果没有指定角色,Vault 使用默认角色,在配置身份验证方法时指定。 -
VAULT_AUTH_PATH
- 可选。身份验证方法挂载的路径。默认为jwt
。 -
VAULT_NAMESPACE
- 可选。用于读取密钥和身份验证的 Vault 企业命名空间。如果没有指定命名空间,Vault 使用根 (/
) 命名空间。Vault 开源版忽略该设置。
使用 Hashicorp Vault 的自动 ID 令牌身份验证
以下任务在默认分支运行时,可以读取 secret/myproject/staging/
下的密钥,但不能读取 secret/myproject/production/
下的密钥:
job_with_secrets:
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.com
secrets:
STAGING_DB_PASSWORD:
vault: myproject/staging/db/password@secret # translates to a path of 'secret/myproject/staging/db' and field 'password'. Authenticates using $VAULT_ID_TOKEN.
script:
- access-staging-db.sh --token $STAGING_DB_PASSWORD
在这个例子中:
-
id_tokens
- 用于 OIDC 身份验证的 JSON Web Token (JWT)。aud
声明设置为匹配用于 Vault JWT 身份验证方法的role
的bound_audiences
参数。 -
@secret
- 启用了你的密钥引擎的 vault 名称。 -
myproject/staging/db
- Vault 中密钥的路径位置。 -
password
- 要获取的引用密钥中的字段。
如果定义了多个 ID 令牌,请使用 token
关键字指定应该使用哪个令牌。例如:
job_with_secrets:
id_tokens:
FIRST_ID_TOKEN:
aud: https://first.service.com
SECOND_ID_TOKEN:
aud: https://second.service.com
secrets:
FIRST_DB_PASSWORD:
vault: first/db/password
token: $FIRST_ID_TOKEN
SECOND_DB_PASSWORD:
vault: second/db/password
token: $SECOND_ID_TOKEN
script:
- access-first-db.sh --token $FIRST_DB_PASSWORD
- access-second-db.sh --token $SECOND_DB_PASSWORD
手动 ID 令牌身份验证
你可以使用 ID 令牌手动进行 HashiCorp Vault 身份验证。例如:
manual_authentication:
variables:
VAULT_ADDR: http://vault.example.com:8200
image: vault:latest
id_tokens:
VAULT_ID_TOKEN:
aud: http://vault.example.com
script:
- export VAULT_TOKEN="$(vault write -field=token auth/jwt/login role=myproject-example jwt=$VAULT_ID_TOKEN)"
- export PASSWORD="$(vault kv get -field=password secret/myproject/example/db)"
- my-authentication-script.sh $VAULT_TOKEN $PASSWORD
限制令牌对 Vault 密钥的访问
你可以通过使用 Vault 保护和极狐GitLab 功能来控制 ID 令牌访问 Vault 密钥。例如,通过以下方式限制令牌:
- 使用 Vault 绑定受众为特定 ID 令牌
aud
声明。 - 使用 Vault 绑定声明为特定群组使用
group_claim
。 - 根据特定用户的
user_login
和user_email
硬编码 Vault 绑定声明的值。 - 在
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
api error: status code 400: missing role
错误
你可能会在尝试启动配置为访问 HashiCorp Vault 的任务时收到 missing role
错误。错误可能是因为未定义 VAULT_AUTH_ROLE
变量,因此任务无法与 Vault 服务器进行身份验证。
audience claim does not match any expected audience
错误
如果 YAML 文件中指定的 ID 令牌 aud:
声明值与用于 JWT 身份验证的 role
的 bound_audiences
参数之间的值不匹配,则可能会收到此错误:
invalid audience (aud) claim: audience claim does not match any expected audience
确保这些值是相同的。