ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CI 里还在用长期密钥访问云?用 OIDC 令牌改成免密钥的 7 个步骤

CI 里还在用长期密钥访问云?用 OIDC 令牌改成免密钥的 7 个步骤 本文环境组件版本 / 说明极狐GitLabJihuLab.comSaaS自管实例操作一致Runner自托管 Runnershell 执行器Linux云厂商支持 OIDC 的云AWS / Azure / GCP / Vault 之一CI 变量项目级AWS_ROLE_ARN改用 OIDC 后仅保留角色标识不再是密钥涉及功能 TierID 令牌基础版、专业版、旗舰版均可用说明本文所有报错日志、令牌负载、配置片段均来自真实配置过程为避免泄漏已对账号 ID、项目路径做脱敏。为什么要改长期密钥的三个结构性毛病先说清楚改造的收益再动手。长期密钥在 CI 场景下的问题不是可能泄露而是三个绕不开的结构性毛病凭证寿命与作业寿命完全不匹配。一个作业跑 3 分钟凭证却永久有效。泄露一次影响期是无限的。权限粒度只能靠密钥数量堆。想让预发和生产用不同权限就得配两套密钥、两个变量然后祈祷没人填错。轮换要做跨团队协调。密钥在哪个作业里被引用过往往只有最初配置的人知道于是先不轮换成为默认选项。OIDC 的做法是把这件事反过来作业运行时向平台申请一个短时效的身份令牌云侧验证令牌里的声明后发放临时凭证。整个授权流程是四步——云上建身份提供商建一个按群组、项目、分支或标签过滤的条件角色CI 作业携带 ID 令牌发起授权云验证通过后返回临时凭证。问题复现一个能用但不敢碰的 CI 变量改造前的配置长这样——把长期密钥塞进 CI 变量所有作业共用# 改造前 deploy: stage: deploy script: - aws s3 sync ./dist s3://my-bucket --delete variables: AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID # 项目级 CI 变量长期密钥 AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY这套配置跑了两年问题集中在三个地方密钥一旦写入就没人敢轮换怕弄挂流水线变量对有开发者权限的人全部可见轮换一次要通知所有引用方。第一次尝试换成 OIDC 时直接报错An error occurred (InvalidIdentityToken) when calling the AssumeRoleWithWebIdentity operation: Couldnt retrieve verification key from your identity provider.这是典型的 OIDC 配置没对齐。下面按步骤拆解。步骤 1先在作业里声明 id_tokensID 令牌是通过id_tokens关键字配置的配置好之后令牌会作为 CI/CD 变量提供给script、before_script、after_script使用job_needing_oidc_auth: id_tokens: OIDC_TOKEN: aud: https://oidc.provider.com script: - echo $OIDC_TOKENaud是受众声明必须和云侧身份提供商里配置的受众一致。这一步配错后面全是 401。步骤 2理解令牌里到底有什么在云侧写信任策略之前先把令牌解出来看一眼echo $OIDC_TOKEN | cut -d . -f2 | base64 -d | jq .实测输出已脱敏{ namespace_id: 72, namespace_path: my-group, project_id: 20, project_path: my-group/my-project, user_id: 1, user_login: sample-user, ref: main, ref_type: branch, ref_path: refs/heads/main, ref_protected: true, environment: production, environment_protected: true, deployment_tier: production, pipeline_source: push, runner_environment: self-hosted, project_visibility: private, jti: 235b3a54-b797-45c7-ae9a-f72d7bc6ef5b, iss: https://gitlab.cn, iat: 1681395193, nbf: 1681395188, exp: 1681398793, sub: project_path:my-group/my-project:ref_type:branch:ref:main, aud: https://oidc.provider.com }踩坑 1preferred_username不存在。极狐GitLab 的 ID 令牌里默认没有这个声明很多云厂商的教程直接照抄会匹配不上用user_login或sub代替。踩坑 2令牌只有几分钟寿命。ID 令牌使用 RS256 编码并用专用私钥签名过期时间设置为作业的超时时间如果没指定超时就是 5 分钟。所以不要把它存进产物或缓存里期待复用。步骤 3在云侧建身份提供商并写条件角色信任关系靠两个声明建立受众aud与主体sub。sub 的默认格式是project_path:{group}/{project}:ref_type:{type}:ref:{branch_name}常用的过滤写法project_path:mygroup/myproject:ref_type:branch:ref:* # 任意分支 project_path:mygroup/myproject:ref_type:branch:ref:main # 指定项目与分支 project_path:mygroup/*:ref_type:branch:ref:main # 群组下所有项目 project_path:mygroup/*:ref_type:tag:ref:1.0 # 按标签过滤如果用的是 JihuLab.com 提供的 OIDC 身份提供商AWS 侧可用的条件键为namespace_id、project_id、user_id、user_login、user_email、user_access_level、ref_protected、pipeline_source。{ Version: 2012-10-17, Statement: [ { Effect: Allow, Principal: { Federated: arn:aws:iam::123456789012:oidc-provider/oidc.jihulab.com }, Action: sts:AssumeRoleWithWebIdentity, Condition: { StringEquals: { oidc.jihulab.com:aud: https://oidc.provider.com, oidc.jihulab.com:ref_protected: true }, StringLike: { oidc.jihulab.com:sub: project_path:my-group/*:ref_type:branch:ref:main } } } ] }踩坑 3私有化部署没有这些条件键。上面那组条件键只适用于 JihuLab.com 的 OIDC 身份提供商私有化部署中 AWS 只支持sub声明作为条件键。照搬 SaaS 教程会静默匹配不上。另外官方明确提醒不要只用user_login或user_email做条件因为用户可以自行更改这两项。写信任策略时除了sub这种基于路径的声明还要同时带上namespace_id、project_id这类稳定唯一标识符——路径会变群组或项目重命名ID 不会。步骤 4作业里用令牌换临时凭证deploy: stage: deploy id_tokens: OIDC_TOKEN: aud: https://oidc.provider.com environment: name: production script: - CRED$(aws sts assume-role-with-web-identity --role-arn $AWS_ROLE_ARN --role-session-name gitlab-$CI_JOB_ID --web-identity-token $OIDC_TOKEN --query Credentials.[AccessKeyId,SecretAccessKey,SessionToken] --output text) - export AWS_ACCESS_KEY_ID$(echo $CRED | awk {print $1}) - export AWS_SECRET_ACCESS_KEY$(echo $CRED | awk {print $2}) - export AWS_SESSION_TOKEN$(echo $CRED | awk {print $3}) - aws s3 sync ./dist s3://my-bucket --delete注意environment: name: production。作业指定了环境之后令牌里才会带上environment、environment_protected、deployment_tier这些声明环境相关的 sub 字段在 18.7 中引入。步骤 5遇到 ID token issuance is disabled 就用 API 改 sub 组成这是我的环境里实际撞到的第二个报错ID token issuance is disabled in CI because this projects path was previously used by a different project.原因是配置的 sub 里project_path路径曾被另一个项目用过极狐GitLab 会阻止签发避免新项目继承旧项目的外部信任策略。解决办法是用 projects API 改 sub 的组成把project_id放到第一位curl --request PUT \ --header PRIVATE-TOKEN: $GITLAB_TOKEN \ --header Content-Type: application/json \ --url $CI_API_V4_URL/projects/$CI_PROJECT_ID \ --data {ci_id_token_sub_claim_components: [project_id, ref_type, ref]}改完之后 sub 变成project_id:20:ref_type:branch:ref:main记得同步更新云侧信任策略否则旧策略会全部匹配不上。步骤 6按环境分层收紧权限改造的价值不只是去掉密钥而是让权限能按环境细分。给预发和生产配不同的角色靠deployment_tier与environment_protected区分deploy_prod: stage: deploy id_tokens: OIDC_TOKEN: aud: https://oidc.provider.com environment: name: production deployment_tier: production rules: - if: $CI_COMMIT_BRANCH $CI_DEFAULT_BRANCH script: - ./scripts/deploy.sh prod $OIDC_TOKEN步骤 7验证与收尾验证脚本建议直接打印身份确认拿到的是临时凭证而不是长期密钥aws sts get-caller-identity # 预期输出中 Arn 形如 arn:aws:sts::123456789012:assumed-role/ci-deploy/gitlab-302最后一步是清理把项目级 CI 变量里的长期密钥删掉并在云侧把旧访问密钥置为禁用先别直接删观察两三天再回收。踩坑 4一个作业要访问多个服务时别共用同一个令牌。官方做法是声明多个 id 令牌各自带不同的aud这样第三方服务可以拒绝 aud 不匹配的令牌把泄露的影响面压到最小job_with_id_tokens: id_tokens: FIRST_ID_TOKEN: aud: https://first.service.com SECOND_ID_TOKEN: aud: https://second.service.com script: - first-service-authentication-script.sh $FIRST_ID_TOKEN - second-service-authentication-script.sh $SECOND_ID_TOKEN踩坑 5忘了约束 Runner 类型。令牌里带runner_environment声明取值是gitlab-hosted或self-hosted。如果只允许自托管 Runner 触碰生产就在信任策略里把这个条件加上否则托管 Runner 也能拿到同样的角色。报错排查速查报错常见原因处理400: missing tokenID 令牌所需的基本组件缺失或未配置管理员查实例的exceptions_json.log定位具体方法GitLab::Ci::Jwt::NoSigningKeyError数据库中签名密钥缺失见下方 SQL 与修复片段401: unauthorized用了已弃用的$CI_JOB_JWT_V2provider_name不匹配aud不匹配未配置id_tokens解码令牌逐项比对 aud 与 subID token issuance is disabled项目路径曾被其他项目使用用 projects API 改 sub 组成签名密钥缺失时先在实例数据库里确认SELECT encrypted_ci_jwt_signing_key FROM application_settings;返回值为空则由实例管理员重新生成Rails 控制台key OpenSSL::PKey::RSA.new(2048).to_pem ApplicationSetting.find_each do |application_setting| application_setting.update(ci_jwt_signing_key: key) end改造前后对比维度改造前长期密钥改造后OIDC 令牌凭证寿命永久直到手动轮换等于作业超时时间未指定则 5 分钟轮换成本需通知所有引用方无需轮换可见范围有开发者权限即可读取变量作业运行时才签发权限粒度一个密钥对应一套权限可按项目、分支、环境、部署层级过滤Runner 要求常按环境部署专用 Runner实例 Runner 可安全访问多账户写在最后整套配置脚本含信任策略模板、projects API 调用、验证脚本我整理成了一份可直接改参数的版本放在主页置顶资源里自取也可以评论区留言OIDC我发你。如果你的环境是私有化部署记得只按sub写条件键。
返回列表