GitHub Actions 与 AWS OIDC 集成:零密钥安全授权实战指南

GitHub Actions 与 AWS OIDC 集成:零密钥安全授权实战指南 1. 项目概述为什么用 OIDC 连接 GitHub Actions 和 AWS 不再是“高级玩法”而是默认安全实践最近三个月我帮六家不同规模的团队重构 CI/CD 权限体系其中五家都卡在同一个问题上AWS 访问密钥Access Key硬编码进 GitHub Secrets导致每次轮换密钥都要改 workflow、重发通知、手动验证权限、半夜被告警叫醒——最夸张的一次某 SaaS 公司因密钥泄露被扫描出未授权 S3 列表操作安全团队直接叫停所有部署流水线 48 小时。而真正让我下定决心写这篇实操笔记的是上周一位刚转岗 DevOps 的朋友发来截图他把 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY 直接写进 .github/workflows/deploy.yml 的 env 字段里还加了注释“临时用一下下周就改”。这根本不是“临时”这是把生产环境的门钥匙焊死在玻璃门上还贴了张“欢迎光临”的纸条。这就是为什么今天我们要聊Connect GitHub Actions and AWS using OIDC——它不是又一个炫技的新功能而是解决密钥管理顽疾的外科手术刀。OIDCOpenID Connect在这里扮演的是“可信身份中介”GitHub Actions 运行器不再需要长期有效的 AWS 凭据而是在每次 job 启动时向 GitHub 的 OIDC 提供商申请一个短时效、作用域精确、可审计的 JWTJSON Web Token再把这个 token 拿去 AWS STSSecurity Token Service兑换成临时凭证。整个过程不经过任何人工环节不落地密钥不依赖 Secrets 管理策略的执行力度甚至不需要 AWS IAM 用户——只靠角色IAM Role和信任策略Trust Policy就能完成授权闭环。核心关键词“GitHub Actions”“AWS”“OIDC”不是并列关系而是因果链因为 GitHub Actions 原生支持 OIDC 身份令牌2021 年底上线所以 AWS 能通过sts:AssumeRoleWithWebIdentity接收并校验该令牌因为 AWS 支持基于 OIDC 提供商的动态角色假设所以 GitHub 工作流能按需获取最小权限凭证最终结果是你删掉仓库里所有 AWS_* 类 SecretsCI 流水线依然能推镜像、更新 Lambda、同步 CloudFormation 堆栈且每次凭证有效期默认 15 分钟可配过期即废无法复用。适合谁所有正在用 GitHub Actions 部署 AWS 资源的团队无论你是用 Terraform 管 IaC、用 CDK 写基础设施、还是用 shell 脚本调 AWS CLI——只要你的 workflow 中出现过aws configure或export AWS_ACCESS_KEY_ID这篇就是为你写的。它不教你怎么写 YAML而是帮你把权限这根绷紧的弦换成一根有弹性的橡皮筋。2. 整体设计思路与方案选型为什么不用 IAM 用户 Secrets为什么非得走 OIDC 这条“绕路”很多人第一反应是“我用 Secrets 存密钥定期轮换加个审批流程不也挺安全”——这话在 2018 年成立但在 2024 年它暴露的是对现代云原生权限模型的理解断层。我们来拆解三种主流方案的底层逻辑和真实代价2.1 方案对比IAM 用户密钥 vs GitHub OIDC IAM 角色 vs 自建 OIDC 中继维度IAM 用户密钥传统方式GitHub OIDC IAM 角色本文方案自建 OIDC 中继如 HashiCorp Vault OIDC凭证生命周期长期有效默认无过期人为轮换每次 job 动态生成JWT 默认 15 分钟STS 临时凭证默认 1 小时可控但需额外维护中继服务健康度与证书续期权限粒度绑定到 IAM 用户难以按 workflow/job 细分可为每个仓库、分支、甚至 job 名称设置独立 IAM 角色信任策略中用sub字段精准匹配粒度取决于中继配置通常不如 GitHub 原生细泄露影响面单密钥泄露 该用户所有权限永久暴露JWT 泄露仅影响单次 job15 分钟内STS 凭证泄露仅影响单次 assume1 小时内且无法跨 job 复用影响面取决于中继策略但引入新攻击面中继本身运维复杂度表面简单实则轮换成本高改 Secrets → 改 workflow → 测试 → 通知 → 回滚预案初始配置稍重需建角色、写信任策略、配 workflow后续零维护需部署、监控、升级、备份中继服务DevOps 成本翻倍审计能力CloudTrail 日志中显示为固定 IAM 用户无法区分哪个 workflow 触发CloudTrail 明确记录AssumeRoleWithWebIdentity调用subject字段含repo:org/repo:ref:refs/heads/main可直接关联到具体代码提交审计日志分散在中继服务与 AWS 两端关联分析成本高提示别被“OIDC 听起来很重”吓退。GitHub 的 OIDC 是开箱即用的——你不需要部署 Keycloak不需要配置 OAuth2 流程不需要管理证书。GitHub 就是那个现成的、受信的 OIDC 提供商Issuer它的https://token.actions.githubusercontent.com地址和 JWKS 端点https://token.actions.githubusercontent.com/.well-known/jwks已预置在 AWS STS 白名单中。你唯一要做的是告诉 AWS“我信任这个 Issuer并允许它代表特定 repo 去扮演我的某个角色”。2.2 为什么必须用 IAM 角色而非 IAM 用户关键在于sts:AssumeRoleWithWebIdentity这个 API 的设计哲学。它要求调用方提供三个要素OIDC Token由 GitHub 签发含iss、sub、aud等声明Role ARN你要扮演的 AWS 角色Role Session Name会话名用于 CloudTrail 标识。AWS STS 收到请求后会做三件事用 GitHub 的公钥从 JWKS 获取验证 token 签名是否有效检查 token 的iss是否为https://token.actions.githubusercontent.com硬编码白名单核对角色的信任策略Trust Policy中是否明确允许该iss和sub组合。而 IAM 用户没有“被扮演”的概念——它本身就是凭证主体。你无法让一个 IAM 用户去“假设”另一个 IAM 用户的身份这违反了 AWS 的权限隔离原则。角色才是 AWS 授权模型的基石它不拥有长期凭证只定义“谁能以什么条件来扮演我”。把 GitHub Actions 当作一个动态的、一次性的“服务主体”让它通过 OIDC 证明自己身份再由角色授予最小权限这才是云原生权限的正解。2.3 信任策略Trust Policy的设计逻辑sub字段是权限的“DNA”很多人卡在信任策略写不对根本原因是没理解subSubject字段的生成规则。它不是随便写的字符串而是 GitHub 动态拼接的、不可伪造的标识符。格式为repo:ORG_NAME/REPO_NAME:ref:refs/heads/BRANCH_NAME例如你的仓库是acme-inc/frontend-appworkflow 在main分支触发则sub为repo:acme-inc/frontend-app:ref:refs/heads/main注意ORG_NAME是 GitHub 组织名或用户名区分大小写GitHub API 返回全小写但策略中必须严格匹配REPO_NAME同理不能写成Frontend-Appref:refs/heads/main中的refs/heads/是固定前缀不能省略或改成branch:main如果你想让 PR 构建也能用同一角色需额外添加repo:acme-inc/frontend-app:pull_request若想限制只允许main和staging分支策略中要写两条StringLike条件而非用通配符*AWS 不支持sub的通配符匹配只能精确或StringLike。这就是为什么我在实操中坚持手写策略而非用 AWS 控制台向导——向导会帮你填sub为*看似省事实则开了个天窗任何人的任意仓库只要知道你的角色 ARN都能伪造一个sub: *的 token虽然技术上不可能但策略层面等于没设防。3. 核心细节解析与实操要点从 AWS 角色创建到 GitHub workflow 配置的每一步深挖现在进入真正的“抄作业”环节。我会把每个步骤背后的原理、常见错误、以及我踩过的坑掰开揉碎讲清楚。这不是配置清单而是带你理解每一行代码在做什么。3.1 AWS 侧创建 IAM 角色并配置信任策略Trust Policy第一步永远是 AWS 控制台或 CLI 创建角色。重点不是“怎么点”而是为什么这样点。操作路径控制台IAM 控制台 → 角色 → 创建角色 → “Web 身份” → “提供商”选https://token.actions.githubusercontent.com→ “受众”填sts.amazonaws.com这是 AWS STS 的标准 audience别写错→ 下一步 → 附加权限策略如AmazonEC2FullAccess先给足权限测试后续再收紧→ 角色名填github-actions-deploy-role→ 创建。注意很多教程跳过“受众Audience”这步直接下一步。这是大忌。aud是 JWT 的核心声明之一AWS STS 会严格校验传入的 token 中aud字段是否等于你在此处填写的值。如果填错比如填成https://github.comSTS 会直接返回InvalidIdentityToken错误且 CloudTrail 日志里只显示“拒绝”不提示具体原因。关键动作编辑信任策略Trust Policy创建完角色后立刻点击“编辑信任策略”。默认策略长这样别直接用{ Version: 2012-10-17, Statement: [ { Effect: Allow, Principal: { Federated: arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com }, Action: sts:AssumeRoleWithWebIdentity, Condition: { StringEquals: { token.actions.githubusercontent.com:aud: sts.amazonaws.com } } } ] }你需要追加Condition块精准锁定仓库和分支。完整策略如下以acme-inc/frontend-app的main分支为例{ Version: 2012-10-17, Statement: [ { Effect: Allow, Principal: { Federated: arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com }, Action: sts:AssumeRoleWithWebIdentity, Condition: { StringEquals: { token.actions.githubusercontent.com:aud: sts.amazonaws.com }, StringLike: { token.actions.githubusercontent.com:sub: repo:acme-inc/frontend-app:ref:refs/heads/main } } } ] }为什么用StringLike而非StringEquals因为sub字段在实际 token 中可能包含额外声明如job_id、run_idAWS 的 OIDC 实现允许StringLike进行前缀匹配。StringEquals要求完全一致一旦 GitHub 更新 token 结构你的策略就失效。StringLike更健壮且符合 AWS 官方文档推荐写法。实操心得我第一次配置时把sub写成repo:acme-inc/frontend-app:main漏了ref:refs/heads/结果 STS 返回Not authorized to perform sts:AssumeRoleWithWebIdentity。CloudTrail 日志里errorMessage字段是空的只能靠反复比对文档。如果你用 Terraform 管理 IAM务必用dynamic块生成多分支策略。例如同时支持main和stagingdynamic statement { for_each [main, staging] content { effect Allow principals { type Federated identifiers [arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com] } actions [sts:AssumeRoleWithWebIdentity] condition { test StringLike variable token.actions.githubusercontent.com:sub values [repo:acme-inc/frontend-app:ref:refs/heads/${statement.value}] } condition { test StringEquals variable token.actions.githubusercontent.com:aud values [sts.amazonaws.com] } } }3.2 GitHub 侧在仓库中启用 OIDC 并配置 workflowGitHub 侧配置极简但有两个隐藏开关必须打开。第一步确认组织/仓库级 OIDC 设置组织管理员需进入Settings → Security → Code security and analysis → GitHub Actions → General确保 “Allow GitHub Actions to create OIDC tokens for AWS” 已勾选默认开启但有些老组织可能关闭。仓库 Maintainer 需进入Settings → Actions → General → Workflow permissions将权限从 “Read and write permissions” 改为 “Read repository contents permission” “Allow specified actions”更安全并务必勾选 “Allow GitHub Actions to create OIDC tokens”。这是最关键的一步如果没勾workflow 中id-token: write会静默失败job 日志里只显示Error: Unable to get ID token, make sure you have the required permissions没有任何上下文。第二步编写 workflow 文件.github/workflows/deploy.yml核心是三处改动在permissions中显式声明id-token: write在env中注入AWS_ROLE_ARN和AWS_WEB_IDENTITY_TOKEN_FILE使用aws-actions/configure-aws-credentialsv2Action 加载凭证。完整示例name: Deploy to AWS on: push: branches: [main] # 关键必须声明 id-token 权限 permissions: id-token: write contents: read jobs: deploy: runs-on: ubuntu-latest steps: # Step 1: Checkout code - uses: actions/checkoutv4 # Step 2: Configure AWS credentials using OIDC - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentialsv2 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-role aws-region: us-east-1 # Step 3: Now you can use AWS CLI freely - name: List S3 buckets run: aws s3 ls # Step 4: Deploy CloudFormation stack - name: Deploy Stack run: | aws cloudformation deploy \ --template-file template.yaml \ --stack-name frontend-app-prod \ --capabilities CAPABILITY_IAM为什么permissions必须单独写GitHub Actions 的权限模型是“最小化默认”。id-token: write不是继承自contents: read它是一个独立的、高危的权限因为 token 可用于扮演角色必须显式声明。如果你省略这一行configure-aws-credentialsAction 会因拿不到 token 而报错且错误信息极其模糊。configure-aws-credentialsv2的工作原理这个 Action 并非黑盒。它内部做了三件事读取 GitHub 提供的环境变量ACTIONS_ID_TOKEN_REQUEST_URL和ACTIONS_ID_TOKEN_REQUEST_TOKEN向ACTIONS_ID_TOKEN_REQUEST_URL发起 GET 请求获取 JWT存入/tmp/...临时文件设置环境变量AWS_WEB_IDENTITY_TOKEN_FILE/tmp/...和AWS_ROLE_ARN...并调用aws sts assume-role-with-web-identity换取临时凭证最后将凭证写入~/.aws/credentials。这意味着你不需要在 workflow 中手动curl或jq解析 tokenconfigure-aws-credentials已封装全部逻辑。但你必须确保它运行在ubuntu-latest或其他支持aws-cli的 runner上且aws-cli版本 ≥ 2.0v1 不支持 web identity。3.3 权限收紧实战从“FullAccess”到“JustEnough”初始配置用AmazonEC2FullAccess是为了快速验证流程但上线前必须收紧。这里分享我总结的“最小权限四步法”第一步跑一次完整 workflow收集 CloudTrail 日志在 AWS CloudTrail 控制台筛选Event source sts.amazonaws.com且Event name AssumeRoleWithWebIdentity的事件找到你的角色调用记录。点击详情展开requestParameters你会看到roleSessionName通常是 GitHub job ID。再用这个 session name 去查所有eventSource ec2.amazonaws.com的事件导出 CSV。第二步用 AWS IAM Access Analyzer 分析权限将导出的 CloudTrail 日志上传到 S3然后在 IAM 控制台 → Access Analyzer → “Analyze activity” → 选择 S3 日志桶 → 运行分析。Analyzer 会生成一份报告列出所有被调用的 EC2 API如RunInstances,CreateTags,DescribeImages并标注哪些权限是“未使用”的。第三步手写最小策略不要用“Policy Generator”根据 Analyzer 报告编写只包含必要 API 的策略。例如如果你只启动 EC2 实例并打标签策略应为{ Version: 2012-10-17, Statement: [ { Effect: Allow, Action: [ ec2:RunInstances, ec2:CreateTags, ec2:DescribeImages, ec2:DescribeSubnets, ec2:DescribeSecurityGroups ], Resource: * } ] }注意Resource设为*是因为RunInstances需要创建新实例ARN 未知但CreateTags必须限制到特定实例前缀可用Condition实现Condition: { StringLike: { ec2:ResourceTag/Project: frontend-app-* } }第四步用iam:SimulatePrincipalPolicy验证在 IAM 控制台 → 角色 → “模拟角色策略”输入你要测试的 API如ec2:RunInstances和资源 ARN如arn:aws:ec2:us-east-1:123456789012:instance/*运行模拟。绿色 ✅ 才算通过。我曾因漏掉DescribeSubnets导致 VPC 部署失败错误日志只显示InvalidSubnetID.NotFound根本看不出是权限问题。4. 实操过程与核心环节实现一次完整的端到端部署演示含参数计算与现场记录现在我们用一个真实场景走一遍将一个 Node.js 应用打包成 Docker 镜像推送到 ECR再用 ECS Fargate 启动任务。全程不碰任何 AWS 密钥。4.1 环境准备账号、仓库、基础架构AWS 账号主账号123456789012区域us-east-1GitHub 仓库acme-inc/frontend-app已启用 OIDC见 3.2ECS 集群已存在frontend-clusterVPC 和子网已就绪ECR 仓库已创建frontend-app策略已允许ecr:GetAuthorizationToken此权限需在角色策略中显式添加因为configure-aws-credentials不自动处理 ECR 登录。提示ecr:GetAuthorizationToken是 ECR 的特殊 API它返回的 token 用于docker login且该 token 本身有 12 小时有效期。但它不依赖 OIDC而是由configure-aws-credentials换取的临时凭证直接调用。所以你只需在角色策略中加上{ Effect: Allow, Action: ecr:GetAuthorizationToken, Resource: * }4.2 workflow 编写分阶段详解每行命令意图name: Build and Deploy to ECS on: push: branches: [main] paths: - src/** - Dockerfile - ecs-task-definition.json permissions: id-token: write contents: read jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 # Step 1: Configure AWS credentials (OIDC flow) - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentialsv2 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecs-deploy-role aws-region: us-east-1 # Step 2: Login to Amazon ECR - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-loginv1 # Step 3: Extract metadata (sha, tags) for Docker - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-actionv5 with: images: 123456789012.dkr.ecr.us-east-1.amazonaws.com/frontend-app # Step 4: Build, tag, and push Docker image to ECR - name: Build and push uses: docker/build-push-actionv4 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} # Step 5: Deploy to ECS - name: Deploy to ECS uses: aws-actions/amazon-ecs-deploy-task-definitionv1 with: task-definition: ecs-task-definition.json service: frontend-service cluster: frontend-cluster wait-for-service-stability: trueStep-by-step 意图解析Step 1Configure AWS Credentials触发 OIDC 流程获取临时凭证。此时~/.aws/credentials已写入后续所有aws命令均可直接使用。Step 2Login to Amazon ECRamazon-ecr-loginv1Action 内部调用aws ecr get-login-password并将输出传给docker login。它依赖 Step 1 的凭证所以顺序不能错。Step 3Extract metadatadocker/metadata-action自动生成镜像标签如sha-abc123、latest。它不接触 AWS纯本地计算。Step 4Build and pushdocker/build-push-action使用 Step 2 的docker login凭据将镜像推送到 ECR。注意push: true表示推送到远程 registry而非仅构建。Step 5Deploy to ECSamazon-ecs-deploy-task-definitionv1读取ecs-task-definition.json更新其中的image字段为新推送的 ECR URI然后调用ecs register-task-definition和ecs update-service。它同样依赖 Step 1 的凭证。参数计算实录ECR URI 格式ACCOUNT_ID.dkr.ecr.REGION.amazonaws.com/REPO_NAME。我第一次写成REGION.ecr.amazonaws.com/REPO_NAME导致docker push报错unauthorized: authentication required。ecs-task-definition.json中的image字段必须是占位符如image: 123456789012.dkr.ecr.us-east-1.amazonaws.com/frontend-app:${IMAGE_TAG}amazon-ecs-deploy-task-definition会自动替换${IMAGE_TAG}为实际 tag。wait-for-service-stability: true是关键开关。它会让 Action 轮询describe-services直到所有任务处于RUNNING状态且健康检查通过。否则job 可能提前结束而 ECS 任务还在启动中。4.3 一次失败的调试记录如何从 CloudTrail 日志定位AssumeRoleWithWebIdentity失败上周一个客户的 workflow 卡在Configure AWS Credentials步骤日志只显示Error: Error in AssumeRoleWithWebIdentity call: Not authorized to perform sts:AssumeRoleWithWebIdentity我立刻登录 AWS CloudTrail筛选Event source:sts.amazonaws.comEvent name:AssumeRoleWithWebIdentityTime range: 最近 15 分钟找到失败事件点击“View event”。关键字段errorCode:AccessDeniederrorMessage: 空requestParameters:{ roleArn: arn:aws:iam::123456789012:role/github-actions-ecs-deploy-role, roleSessionName: github-workflow-1234567890, webIdentityToken: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... }userIdentity:{ type: FederatedUser, principalId: https://token.actions.githubusercontent.com:repo:acme-inc/frontend-app:ref:refs/heads/main }问题来了principalId显示的是https://token.actions.githubusercontent.com:...但信任策略中Principal.Federated是arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com。两者不匹配根因客户在创建 OIDC 提供商时ARN 写成了arn:aws:iam::123456789012:oidc-provider/https://token.actions.githubusercontent.com多了https://。AWS 要求 ARN 中的 provider 名称必须是域名部分即token.actions.githubusercontent.com不能带协议头。修复删除旧 OIDC 提供商重新创建ARN 填token.actions.githubusercontent.com。5 分钟后workflow 恢复正常。实操心得CloudTrail 是你的第一道防线。当 OIDC 报错时永远先看AssumeRoleWithWebIdentity事件而不是在 GitHub 日志里猜。principalId字段会告诉你 GitHub 实际声称的身份requestParameters.roleArn告诉你它想扮演谁两者对比就能定位信任策略问题。5. 常见问题与排查技巧实录整理成速查表附独家避坑技巧以下是我在 6 个客户现场、32 次 OIDC 配置中高频遇到的 12 个问题及解决方案。按发生概率排序附真实错误日志和修复命令。5.1 常见问题速查表问题现象错误日志片段根本原因修复方案验证方法1.Unable to get ID tokenError: Unable to get ID token, make sure you have the required permissionsGitHub 仓库未开启id-token: write权限进入Settings → Actions → General → Workflow permissions勾选 “Allow GitHub Actions to create OIDC tokens”在 workflow 中加 steprun: echo $ACTIONS_ID_TOKEN_REQUEST_URL应输出非空 URL2.Not authorized to perform sts:AssumeRoleWithWebIdentityError: Error in AssumeRoleWithWebIdentity call: Not authorized to perform sts:AssumeRoleWithWebIdentityAWS 角色信任策略中Principal.FederatedARN 错误如多写了https://或sub字段不匹配检查 OIDC 提供商 ARN 是否为token.actions.githubusercontent.com用jq -r .sub (echo $TOKEN)解析实际sub值对比策略CloudTrail 中principalId字段应与策略中sub完全匹配3.InvalidIdentityTokenError: InvalidIdentityToken: The web identity token that was passed could not be validated.aud字段不匹配GitHub token 的aud是sts.amazonaws.com但策略中写了其他值编辑信任策略确保Condition.StringEquals.token.actions.githubusercontent.com:aud等于sts.amazonaws.com用jwt.io解析 token查看aud声明4.AccessDeniedon ECR pushdenied: User: arn:aws:sts::123456789012:assumed-role/... is not authorized to perform: ecr:GetAuthorizationToken角色策略未包含ecr:GetAuthorizationToken权限在角色策略中添加Action: ecr:GetAuthorizationToken, Resource: *运行aws ecr get-login-password --region us-east-1应返回密码字符串5.InvalidSubnetID.NotFoundduring ECS deployAn error occurred (InvalidSubnetID.NotFound) when calling the RegisterTaskDefinition operation角色缺少ec2:DescribeSubnets权限添加ec2:DescribeSubnets到角色策略运行aws ec2 describe-subnets --region us-east-1 --filters Namevpc-id,Valuesvpc-xxx6.NoCredentialProvidersin custom scripterror: NoCredentialProviders: no valid providers in chain自定义脚本未使用~/.aws/credentials或AWS_PROFILE未设置在脚本开头加export AWS_PROFILEdefault或直接调用aws --profile default ...aws sts get-caller-identity --profile default应返回角色 ARN5.2 独家避坑技巧来自血泪教训技巧 1用aws sts get-caller-identity在 workflow 中实时验证凭证在Configure AWS Credentials步骤后立即加一个 debug step- name: Debug - Check assumed role run: | echo Current identity: aws sts get-caller-identity --query Arn --output text echo Available regions: aws ec2 describe-regions --query Regions[].RegionName --output table这能第一时间确认凭证是否成功加载get-caller-identity不报错角色是否正确扮演ARN 应为arn:aws:sts::ACCOUNT:assumed-role/ROLE_NAME/SESSION_NAME区域是否配置正确describe-regions列出的 region 应包含你的目标 region。技巧 2为不同环境创建独立角色用sub字段天然隔离不要用一个角色服务所有分支。为main、staging、feature/*创建三个角色信任策略中sub分别为main:repo:acme-inc/frontend-app:ref:refs/heads/mainstaging:repo:acme-inc/frontend-app:ref:refs/heads/stagingfeature/*:repo:acme-inc/frontend-app:ref:refs/heads/feature/*注意StringLike支持*通配符这样feature分支的 workflow 即使被恶意篡改也无法部署到 production 环境因为它的sub不匹配main角色的信任策略。技巧 3用aws-actions/configure-aws-credentialsv2的role-duration-seconds参数控制凭证有效期默认 STS 凭证有效期是 1 小时但你的 workflow 可能 5 分钟就结束了。缩短有效期能进一步降低风险- name: Configure AWS Credentials uses: aws-actions/configure-aws-credentialsv2 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: us-east-1 role-duration-seconds: 900 # 15 minutesrole-duration-seconds必须 ≤ 角色的MaxSessionDuration默认