引言
在构建云原生CI/CD流水线时,我们经常遇到的第一个挑战就是"授权"。一个经典的场景是:运行在AWS EC2上的GitLab Runner需要将构建好的Docker镜像推送到AWS ECR(弹性容器镜像服务)。最直接的方案,莫过于为EC2实例配置一个拥有ECR推送权限的IAM Instance Profile。这套方案在纯AWS环境中优雅且高效,因为它免去了管理静态密钥的烦恼。但当我们的Runner需要运行在本地、数据中心或其他云平台时,这套"AWS专属"的授权模式便会立刻失灵。接下来将探讨这一困境,并引入一种更现代化、更具普适性的解决方案------利用OIDC(OpenID Connect)实现GitLab与AWS之间的安全、无缝认证,让CI/CD流水线彻底摆脱底层基础设施的束缚。
症结所在:Instance Profile 的"甜蜜陷阱"
Instance Profile 是一种将IAM角色关联到EC2实例的机制。实例上运行的应用程序(比如GitLab Runner)可以通过EC2的元数据服务,自动获取临时的AWS访问凭证。
它的优点显而易见:
- 无需硬编码密钥 :代码库和CI/CD变量中都不需要存储敏感的
AWS_ACCESS_KEY_ID
和AWS_SECRET_ACCESS_KEY
。 - 自动轮换:凭证是临时的,由AWS自动管理和轮换,安全性较高。
然而,这种便利性也带来了它的"陷阱"------基础设施强绑定。一旦GitLab Runner的执行环境离开了AWS EC2,整个授权链路就会中断。这在以下场景中会成为巨大的障碍:
- 混合云部署:一部分Runner在AWS,另一部分在本地物理机或VMware环境。
- 多云战略:业务可能同时使用AWS、Azure、GCP,Runner需要具备在不同云环境中执行的能力。
- 本地开发与调试:开发者在本地机器上无法模拟Instance Profile,导致本地环境与CI环境不一致,增加了调试难度。
因此,我们需要一种与运行环境无关的"身份认证"机制,让GitLab CI的作业(Job)本身成为一个可信的身份,无论它在哪里运行。
破局之道:OIDC,跨平台的信任桥梁
OIDC(OpenID Connect)是一个基于OAuth 2.0协议的简单身份层。它允许客户端(如GitLab CI Job)基于授权服务器(如GitLab本身)的认证,来验证用户的身份,并以一种可交互操作的、类似REST的方式,获取关于最终用户的基本信息。
在我们的场景中,这个流程可以被通俗地理解为:
- GitLab作为身份提供商 (IdP):当一个CI/CD作业运行时,GitLab可以为这个作业生成一个唯一的、短期的、包含特定信息(如项目路径、分支、标签等)的身份令牌(JWT - JSON Web Token)。
- AWS作为服务提供商 (SP):我们可以配置AWS IAM,使其信任来自特定GitLab实例的JWT。
- 建立信任关系:GitLab作业将自己的JWT令牌出示给AWS STS(Security Token Service),AWS验证令牌的签名和内容后,确认"此作业可信",然后颁发一个临时的AWS访问凭证。
整个过程就像GitLab给每个作业发了一张有时效的、防伪的"数字身份证",AWS的安全服务在校验证件无误后,授予其临时访问权限。
下面是这个授权流程的序列图:
实战演练:三步配置 GitLab 与 AWS 的 OIDC 信任
接下来,我们通过三个核心步骤,完成这套优雅的授权体系。
第一步:在 AWS IAM 中配置身份提供程序和角色
-
创建OIDC身份提供程序:
- 登录AWS IAM控制台,进入"身份提供商"页面,点击"添加提供商"。
- 选择"OpenID Connect"。
- 对于"提供商URL",填入你的GitLab实例地址,例如
https://gitlab.com
或自建实例的域名。 - 点击"获取指纹"来验证。
- 对于"受众",填入
https://gitlab.com
或你的实例域名。 - 完成创建。
-
创建IAM角色:
-
创建一个新角色,选择"Web身份"作为可信实体类型。
-
选择刚刚创建的OIDC提供商。
-
在"受众"中选择你的GitLab实例。
-
关键步骤:添加信任策略条件 。为了安全,我们需要精确控制哪个GitLab项目、哪个分支可以代入此角色。点击"编辑信任关系",添加
Condition
,限制sub
(Subject)字段。例如,只允许my-group/my-project
项目在main
分支上的作业使用此角色:json{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/gitlab.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "gitlab.com:sub": "project_path:my-group/my-project:ref_type:branch:ref:main" } } } ] }
-
为该角色附加权限策略,比如
AmazonEC2ContainerRegistryPowerUser
,以允许它对ECR进行读写操作。 -
记下这个角色的ARN,例如
arn:aws:iam::123456789012:role/gitlab-ecr-pusher
。
-
第二步:改造 .gitlab-ci.yml
文件
现在,我们来修改CI/CD的作业定义,让它去申请JWT并代入角色。
yaml
build-and-push:
stage: build
image:
name: amazon/aws-cli:2.13.0
entrypoint: [""]
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.com # 与AWS OIDC提供商中配置的受众一致
variables:
ROLE_ARN: "arn:aws:iam::123456789012:role/gitlab-ecr-pusher"
AWS_REGION: "us-east-1"
ECR_REGISTRY: "123456789012.dkr.ecr.us-east-1.amazonaws.com"
IMAGE_TAG: $CI_COMMIT_SHORT_SHA
script:
- echo "Authenticating with AWS using OIDC..."
# 1. 使用OIDC令牌换取临时AWS凭证
- |
aws sts assume-role-with-web-identity \
--role-arn ${ROLE_ARN} \
--role-session-name "GitLabRunner-${CI_PIPELINE_ID}" \
--web-identity-token ${GITLAB_OIDC_TOKEN} \
--duration-seconds 3600 \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
--output text
CREDS=$(aws sts assume-role-with-web-identity \
--role-arn ${ROLE_ARN} \
--role-session-name "GitLabRunner-${CI_PIPELINE_ID}" \
--web-identity-token ${GITLAB_OIDC_TOKEN} \
--duration-seconds 3600 \
--query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
--output text)
- export AWS_ACCESS_KEY_ID=$(echo "${CREDS}" | awk '{print $1}')
- export AWS_SECRET_ACCESS_KEY=$(echo "${CREDS}" | awk '{print $2}')
- export AWS_SESSION_TOKEN=$(echo "${CREDS}" | awk '{print $3}')
- echo "Logging in to Amazon ECR..."
# 2. 使用临时凭证登录ECR
- aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY}
- echo "Building and pushing Docker image..."
# 3. 正常执行docker build和push
# - docker build -t ${ECR_REGISTRY}/my-app:${IMAGE_TAG} .
# - docker push ${ECR_REGISTRY}/my-app:${IMAGE_TAG}
- echo "Push complete!"
在这个脚本中:
id_tokens
关键字指示GitLab为作业生成一个OIDC令牌,并将其存储在GITLAB_OIDC_TOKEN
环境变量中。aws sts assume-role-with-web-identity
命令是核心,它用JWT换取了临时的AWS凭证。- 后续的
docker login
和docker push
命令就和常规操作完全一样了。
结论:拥抱解耦与安全的未来
通过从Instance Profile迁移到OIDC联邦认证,我们不仅解决了跨平台部署的兼容性问题,更在安全性和灵活性上迈出了一大步。这种模式将授权的信任基础从"物理位置"(运行在哪台机器上)转移到了"逻辑身份"(是哪个项目的哪个作业),这正是现代DevSecOps所倡导的核心理念。
现在,无论是运行在AWS EC2、本地数据中心,还是竞争对手的云平台上,我们的GitLab CI流水线都能以统一、安全的方式与AWS ECR进行交互。这不仅提升了系统的可移植性,也为未来的架构演进铺平了道路。对于追求构建健壮、灵活CI/CD体系的团队而言,掌握并实践OIDC无疑是一项高价值的投资。