使用 HashiCorp Vault 验证和读取 secrets

本教程演示了如何使用来自 GitLab CI/CD 的 HashiCorp 的 Vault 进行身份验证、配置和读取 secrets。

note专业版支持对 HashiCorp Vault 的读取访问,并使您能够在 CI 作业中使用 Vault secrets。

要求

本教程假设您熟悉 GitLab CI/CD 和 Vault。

您必须具备:

  • 极狐GitLab 上的一个帐户。
  • 访问正在运行的 Vault 服务器(至少 v1.2.0)的权限,配置身份验证并创建角色和策略。对于 HashiCorp Vaults,这可以是开源或企业版本。
note您必须将下面的 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 Unique identifier for this token
iss Always Issuer, the domain of your GitLab instance
iat Always Issued at
nbf Always Not valid before
exp Always Expires at
sub Always Subject (job ID)
namespace_id Always Use this to scope to group or user level namespace by ID
namespace_path Always Use this to scope to group or user level namespace by path
project_id Always Use this to scope to project by ID
project_path Always Use this to scope to project by path
user_id Always ID of the user executing the job
user_login Always Username of the user executing the job
user_email Always Email of the user executing the job
pipeline_id Always ID of this pipeline
pipeline_source Always Pipeline source
job_id Always ID of this job
ref Always Git ref for this job
ref_type Always Git ref type, either branch or tag
ref_protected Always true if this Git ref is protected, false otherwise
environment Job is creating a deployment Environment this job deploys to
environment_protected Job is creating a deployment true if deployed environment is protected, false otherwise

示例 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 或其他客户端)。

示例

cautionJWT 是凭证,可以授予对资源的访问权限。小心您粘贴它们的地方!

假设您将 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。

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 的 创建角色文档

caution始终使用提供的声明之一(例如,project_idnamespace_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://www.vaultproject.io/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)"
note如果您使用 HashiCorp Cloud Platform 提供的 Vault 实例,则需要导出 VAULT_NAMESPACE 变量。它的默认值为 admin

read_secrets staging

以下作业能够使用 myproject-production 角色进行身份验证并读取 /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://www.vaultproject.io/api/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

read_secrets production

限制令牌对 Vault secrets 的访问

您可以使用 Vault 保护和极狐GitLab 功能控制对 Vault 机密的 CI_JOB_JWT 访问。例如,通过以下方式限制令牌:

  • 对使用 group_claim 的特定组使用 Vault bound_claims
  • 基于特定用户的 user_loginuser_email 的 Vault bound claims 的硬编码值。
  • 根据 token_explicit_max_ttl 中指定的令牌 TTL 设置 Vault 时间限制,其中令牌在身份验证后过期。
  • 将 JWT 范围限定为仅限项目用户子集的受保护分支
  • 将 JWT 范围限定为仅限项目用户子集的受保护标签