如果你一直将AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY作为GitHub Secrets存储起来,以便后续用于部署到AWS上,那么你并不是个例。这种做法确实非常普遍,但同时也是CI/CD流程中存在最大安全风险的因素之一。

原因在于:这些静态凭据本身并不会自动过期。如果由于工作流配置错误、公共分支的泄露,或者仓库被入侵,攻击者就会持续拥有对你的AWS环境的访问权限,直到你手动更换这些凭据为止。而大多数团队并没有经常进行这样的更换操作。

OpenID Connect(OIDC)彻底解决了这个问题。GitHub Actions在每次运行工作流时,都会直接从AWS请求一个短期有效的令牌,而无需存储任何长期有效的凭据。这样一来,既不会导致凭据泄露,也不需要手动管理这些密钥。

在本教程中,你将学习如何从头开始配置GitHub Actions与AWS之间的OIDC认证机制。完成配置后,你的工作流将能够安全地访问AWS资源,而无需存储任何访问密钥。

目录

什么是OpenID Connect(OIDC)?

OpenID Connect是一种基于OAuth 2.0构建的身份认证协议。它允许系统通过令牌而非共享密钥来进行身份验证。

在GitHub Actions与AWS的协作场景中:

  • GitHub充当身份提供者的角色。每当有工作流运行时,GitHub都会生成一个经过签名的JWT(JSON Web Token)。

  • AWS则作为服务提供者。它会使用GitHub的公钥来验证这些令牌,并将其转换成临时性的AWS访问凭据。AWS返回的这些凭据是短期有效的(默认有效期为1小时),并且其权限范围仅限于你所定义的IAM角色。当工作流结束时,这些凭据就会失效。

这种模型被称为联合身份认证。当你在第三方网站上选择“使用Google登录”时,所使用的就是同样的原理。不同的是,在这种情况下,并不是用户自己进行登录操作,而是你的工作流程本身负责完成身份验证过程。

GitHub Actions与AWS之间的OIDC认证机制

在开始编写任何YAML代码之前,了解整个认证流程是非常有必要的。这是我个人在尝试使用新技术或新概念时采取的做法。每次你的工作流程被执行时,以下这些步骤都会依次发生:

展示GitHub Actions与AWS之间通过OpenID Connect进行身份验证的流程图

该流程图清楚地展示了GitHub Actions与AWS如何利用OpenID Connect(OIDC)来实现安全的身份验证机制,从而避免了在GitHub中存储长期有效的AWS凭证。具体步骤如下:

1. 初始认证请求

当你的GitHub Actions工作流程开始运行时,执行该流程的虚拟机会向位于https://token.actions.githubusercontent.com的GitHub OIDC提供者请求一个JSON Web Token(JWT)。

2>令牌的生成与签名

GitHub的OIDC提供者会生成一个包含有关你的工作流程的重要信息的JWT,并对该令牌进行签名处理。这些信息包括该工作流程是从哪个仓库中运行的、是由哪个分支触发的、在什么环境中运行等等,这些数据用于验证该工作流程的身份。

3>令牌的验证

GitHub Actions的执行器会将这个经过签名的JWT提交给AWS的安全令牌服务(STS)。AWS STS会使用GitHub公开提供的加密密钥来验证该JWT的签名,从而确保该令牌是真实有效的、没有被篡改过。

4>信任策略的检查

AWS STS还会检查你在IAM角色中配置的信任策略。这个信任策略规定了哪些GitHub仓库、分支或环境可以被授权使用这个角色。如果JWT中的信息与信任策略中的条件相匹配,那么认证过程就会成功完成。

5>临时凭证的发放

一旦验证通过,AWS STS会向GitHub Actions的执行器返回临时安全凭证。这些凭证包括访问密钥ID、秘密访问密钥以及会话令牌,它们的有效期都是有限的(默认为1小时,最长可设置为12小时)。

6>对AWS API的访问

GitHub Actions的执行器会使用这些临时凭证来调用AWS提供的各种API服务,比如将Docker镜像上传到ECR、更新ECS服务、向S3存储桶写入数据,或者触发Lambda函数等。

关键点在于:AWS永远不会看到你的GitHub凭证,而GitHub也同样不会看到你的AWS凭证。唯一被交换和使用的信息就是这个经过签名处理的JWT,而且它的有效期非常短。

先决条件

在开始之前,请确保您已经具备以下条件:

  • 一个拥有IAM权限的AWS账户,这些权限允许您创建身份提供者及角色

  • 一个用于运行工作流的GitHub仓库(无论是公共仓库还是私有仓库都行)

  • GitHub Actions有基本的了解,知道如何编写.yml格式的工作流文件

  • 对AWS的IAM角色、策略及权限有基本的了解

  • 已安装并配置了AWS CLI(虽然不是必需的,但有助于后续操作)。您不需要成为AWS专家。每一步都会明确指出需要在控制台中输入的路径以及所需的配置参数。

步骤1:在AWS中创建一个IAM OIDC身份提供者

首先,您需要让AWS认可GitHub作为身份提供者。这个设置对于每个AWS账户来说都是唯一的。

如何在AWS控制台中操作

  1. 打开AWS IAM控制台

  2. 在左侧边栏中,点击“身份提供者”选项

  3. 然后点击“添加提供者”

  4. 在“提供者类型”中选择“OpenID Connect”

  5. 在“提供者URL”字段中输入:

  6. https://token.actions.githubusercontent.com
    1. 在“受众”字段中输入:
    sts.amazonaws.com
    1. 最后点击“添加提供者”

    AWS IAM控制台中显示的为GitHub Actions OIDC配置的身份提供者添加界面

    如何使用AWS CLI操作

    如果您更喜欢在终端中操作,可以运行以下命令:

    aws iam create-open-id-connect-provider \
      --url https://token.actions.githubusercontent.com \
      --client-id-list sts.amazonaws.com \
    

    使用AWS CLI创建OIDC连接的成功界面

    创建完成后,您会在AWS IAM控制台的“身份提供者”列表中看到token.actions.github.com这一条目。在下一步中,您的IAM角色信任策略将会引用这个提供者。

    在AWS中验证OIDC连接的配置结果

    步骤2:创建具有信任策略的IAM角色

    现在,你需要为GitHub Actions工作流创建一个IAM角色。该角色所关联的信任策略会决定哪些仓库和分支可以请求访问凭证。

    如何在AWS控制台中创建IAM角色

    1. 打开AWS IAM控制台

    2. 在左侧导航栏中,点击 Roles

    3. 点击Create role

    4. Trusted entity type选项中,选择Web identity

    5. 对于Identity Provider,请选择你之前创建的token.actions.github.com

    6. 对于Audience,也选择sts.amazonaws.com

    7. 在“GitHub organisation”字段中,输入你的GitHub用户名或组织名称

    8. 在“GitHub repository”字段中,输入你的GitHub仓库地址

    9. 在“GitHub branch”字段中,输入你的分支名称(例如main)

    10. 依次点击“Next”按钮,然后为该角色指定一个名称,最后点击“Create role”即可完成创建

    通过AWS控制台为GitHub Actions创建IAM角色

    注意:使用这种方法创建IAM角色后,根据上述步骤4-9设定的信任策略,受信任实体的相关设置就已经完成了。你可以通过点击刚刚创建的角色,然后进入“Trust relationships”页面来验证这一设置。

    如何使用AWS CLI创建IAM角色

    首先,你需要在本地计算机上生成一份信任策略文件,可以将其命名为trust-policy.json

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Principal": {
            "Federated": "arn:aws:iam::YOUR_ACCOUNT_ID:oidc-provider/token.actions.github.com"
          },
          "Action": "sts:AssumeRoleWithWebIdentity",
          "Condition": {
            "StringEquals": {
              "token.actions/github.com:aud": "sts.amazonaws.com"
            },
            "StringLike": {
              "token.actions.github.com:sub": "repo:YOUR_GITHUB_ORG/YOUR_REPO_NAME:*"
            }
          }
        }
      ]
    }
    

    在保存文件之前,请将以下占位符替换为相应的实际值:

    占位符 应替换为
    YOUR_ACCOUNT_ID 你的12位AWS账户ID
    YOUR_GITHUB_ORG 你的GitHub用户名或组织名称
    YOUR_REPO_NAME 你的GitHub仓库名称

    如何理解sub条件字段

    JWT中的sub (subject)字段能够明确告知AWS请求的来源。值repo:your-org/your-repo:*表示该仓库中的任何分支都可以担任这一角色。

    根据实际需求,你可以进一步细化这些设置:

    
    # 仅允许主分支进行部署
    "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
    
    # 仅在特定的GitHub环境中进行部署
    "token.actions.github.com:sub": "repo:your-org/your-repo:environment:production"
    

    正确设置这一权限范围是这种配置方案中最重要的安全措施之一。以下是判断方法:

    • 如果只有主分支或生产环境分支需要部署到AWS,应使用ref:refs/heads/main。这是最严格且最安全的选项:因为其他分支无法意外地(或恶意地)触发部署或修改生产环境资源。

    • 如果你使用了带有保护规则的GitHub环境(例如需要特定审核人员参与或设置部署审批流程),可以使用environment:production。这样可以通过GitHub的审批流程来控制部署操作,同时仍能限制哪些工作流程能够访问AWS资源。

    • 只有当你确实需要让仓库中的任意分支都能进行部署时,才应使用repo:your-org/your-repo:*(通配符形式)。例如在开发环境中,每个功能分支都会被部署到独立的系统中,这种情况下可以使用这种设置。但绝对不要在生产环境中使用这种方式。

    运行以下命令,根据你的信任策略创建该角色:

    aws iam create-role \
      --role-name GitHubActionsOIDCRole \
      --assume-role-policy-document file://trust-policy.json \
      --description "Role assumed by GitHub Actions via OIDC"
    

    注意输出结果中的角色ARN,它的格式如下:

    arn:aws:iam::YOUR ACCOUNT_ID:role/GitHubActionsOIDCRole
    

    在步骤4中,你的工作流程配置文件中会需要用到这个ARN。

    AWS CLI create-role命令的输出结果,其中显示了返回的Role ARN

    步骤3:为IAM角色分配权限

    现在这个IAM角色已经具备了身份验证能力,但还没有任何权限。你需要为其附加一个策略,明确说明该角色在AWS中可以被允许执行哪些操作。

    如何应用最小权限原则

    只授予工作流程真正需要的权限。如果某个工作流程需要将数据存储到S3中,就给它S3的权限;如果它需要将镜像上传到ECR,就给它ECR的权限。绝对不要为CI/CD角色分配AdministratorAccess权限。

    选项1:快速配置AWS管理的策略

    aws iam attach-role-policy \
      --role-name GitHubActionsOIDCRole \
      --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
    

    这种做法在生产环境中被推荐使用,因为它能够限制安全事件的影响范围。如果你的工作流程凭据遭到泄露,那么针对特定桶定制的政策意味着攻击者只能影响那个特定的桶,而不会影响到你AWS账户中的其他所有S3桶。此外,这种方式还能防止工作流程中的配置错误影响到无关的资源。

    创建一个名为s3-deploy-policy.json的文件:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "s3:PutObject",
            "s3:DeleteObject",
            "s3:ListBucket"
          ],
          "Resource": [
            "arn:aws:s3:::your-bucket-name",
            "arn:aws:s3:::your-bucket-name/*"
          ]
        }
      ]
    }
    

    然后创建并应用该政策:

    aws iam create-policy \
      --policy-name GitHubActionsS3DeployPolicy \
      --policy-document file://s3-deploy-policy.json
     
    aws iam attach-role-policy \
      --role-name GitHubActionsOIDCRole \
      --policy-arn arn:aws:iam::YOUR_ACCOUNT_ID:policy/GitHubActionsS3DeployPolicy
    

    注意:你也可以通过控制台来执行步骤3

    参考:有关AWS IAM所有可用操作的完整列表,请参阅AWS IAM操作参考文档

    步骤4:将角色ARN存储为GitHub Actions变量

    在配置工作流程之前,你需要让该角色ARN能够被工作流程使用。你需要将其作为仓库变量存储在GitHub中,而不是作为秘密信息存储,因为ARN本身并不属于敏感数据。

    如何将变量添加到你的仓库中

    1. 打开你的GitHub仓库,然后点击设置:

    GitHub仓库顶部导航栏,其中“设置”选项被高亮显示

    1. 在左侧导航栏中,向下滚动到秘密和变量,然后点击操作:

    GitHub仓库设置页面,其中“秘密和变量”选项已被展开,并选中了“操作”选项

    1. 点击变量选项卡(而非“秘密”选项卡)。

    2. 点击新建仓库变量

    3. 你可以将名称设置为以下内容:

    AWS_ROLE_ARN
    1. 设置为步骤2中获得的角色ARN,例如:
    arn:aws:iam::YOUR_ACCOUNT_ID::role/GitHubActionsOIDCRole
    1. 点击添加变量

    GitHub仓库操作中的变量选项卡,显示AWS_ROLE_ARN变量已成功添加

    在下一步中,你可以在工作流中使用${{vars.AWS_ROLE_ARN }}}来引用这个变量。

    步骤5:配置你的GitHub Actions工作流

    在完成AWS和GitHub的配置后,你现在需要更新工作流,以便请求OIDC令牌并使用它进行身份验证。

    如何设置所需的工作流权限

    你的工作流必须声明id-token: write这一权限。如果没有这个权限,GitHub将不会向运行器发放OIDC令牌。

    permissions:
      id-token: write   # 请求OIDC JWT所必需的权限
      contents: read    # 检出仓库内容所必需的权限
    

    重要提示:如果你在作业级别设置了权限,这些权限会覆盖任何顶层设置的权限。因此,请确保无论AWS身份验证步骤在哪个层级执行,id-token: write这一权限都存在。

    完整的工作流示例

    以下是一个使用OIDC进行AWS身份验证,并将静态网站部署到S3的完整工作流示例:

    name: 部署到AWS S3
    
    on:
      push:
        branches:
          - main
    
    permissions:
      id-token: write
      contents: read
    
    jobs:
      deploy:
        name: 部署
        runs-on: ubuntu-latest
    
    steps:
      - name: 检出代码
        uses: actions/checkout@v4
    
      - name: 通过OIDC配置AWS凭据
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.AWS_ROLE_ARN }}
          aws-region: us-east-2
    
      - name: 验证AWS身份
        run: aws sts get-caller-identity
    
      - name: 部署到S3
        run: |
          aws s3 sync ./code s3://your-bucket-name
    

    在提交代码之前,请将以下内容替换为实际的值:

    占位符 需要替换的内容
    AWS_ROLE_ARN 你在GitHub中使用的IAM角色ARN对应的变量名称
    us-east-2 你的目标AWS区域
    your-bucket-name 你的S3存储桶名称
    ./code 包含你要部署到S3的文件的本地目录路径

    你可以在我的GitHub仓库中查看代码示例,链接是这里

    注意: `aws-actions/configure-aws-credentials`这个动作会自动完成整个OIDC令牌交换过程。它会从GitHub请求JWT令牌,然后调用`sts:AssumeRoleWithWebIdentity`函数,并将临时生成的凭证作为环境变量保存下来,以便后续任务使用。

    如需了解所有可用选项,请查看该动作的官方文档

    步骤6:运行并验证你的工作流程

    将你的工作流程推送到`main`分支,然后打开仓库中的Actions标签页,观察其运行过程。

    成功的运行结果是什么样的

    “通过OIDC配置AWS凭证”这个步骤应该会显示如下内容:

    Assuming role with OIDC: arn:aws:iam::YOUR_ACCOUNT_ID:role/GitHubActionsOIDCRole

    “验证AWS身份”这个步骤(使用`aws sts get-caller-identity`命令)应该会返回如下内容:

    {
        "UserId": "AROA...:GitHubActions",
        "Account": "YOUR_ACCOUNT_ID",
        "Arn": "arn:aws:sts::YOUR ACCOUNT_ID:assumed-role/GitHubActionsOIDCRole/GitHubActions"
    }
    

    如果输出结果中出现了`assumed-role` ARN,那就说明OIDC配置是正确的。此时你的工作流程在无需任何存储的凭证的情况下就能成功登录AWS。

    安全最佳实践

    让OIDC功能正常运行是第一步,接下来就是要妥善设置安全限制。

    尽可能严格地限定`sub`条件的适用范围

    不要使用像`repo:your-org/*:*`这样的通配符,因为这样会导致组织内的任何仓库都能使用该角色。应该将权限范围限定在真正需要访问的特定仓库和分支上。

    "token.actions.github.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"

    在生产环境中使用GitHub Environments

    GitHub Environments允许你设置手动审批机制,并限制哪些分支可以进行部署。当与OIDC结合使用时,你可以确保只有`production`环境才能使用相关权限:

    "token.actions.github.com:sub": "repo:your-org/your-repo:environment:production"

    为每个IAM角色分配最小权限

    对于用于CI/CD流程的角色,绝对不要赋予`AdministratorAccess`或`PowerUserAccess`权限。应该为这些角色定义仅包含其实际所需功能的自定义策略。

    为每个环境创建独立的IAM角色

    测试环境使用的角色与生产环境使用的角色应具有不同的权限范围。测试环境中的角色绝对不能拥有对生产环境资源的写入权限。

    启用AWS CloudTrail

    使用临时凭据进行的每一项操作都会被记录在CloudTrail中,这些记录会以所使用的角色的ARN作为标识。这样,你就能清楚地了解自己的工作流程在AWS中具体做了哪些操作。

    参考资料: GitHub官方关于OIDC的安全加固指南:关于使用OpenID Connect进行安全加固

    常见错误的排查与解决

    错误:无权限执行sts:AssumeRoleWithWebIdentity操作

    这种情况通常意味着你的IAM角色所配置的信任策略与JWT中的sub字段内容不匹配。

    请检查以下内容:

    • sub条件必须与你仓库的路径完全一致(区分大小写)。

    • aud条件必须设置为sts.amazonaws.com

    • 使用Federated主体时,必须确保使用的AWS账户ID是正确的。

    如果你想查看你的工作流程实际接收到了哪些token字段信息,可以临时添加以下调试步骤:

    - name: 打印OIDC token中的字段信息
      run: |
        TOKEN=\((curl -s -H "Authorization: Bearer \)ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
          "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq -r '.value')
        echo $TOKEN | cut -d '.' -f2 | base64 -d 2>/dev/null | jq .
    

    错误:无法从任何提供者处加载凭证

    这种情况几乎总是意味着你的工作流程权限配置中缺少id-token: write这一项。请再次确认你的权限设置是否正确:

    permissions:
      id-token: write
      contents: read
    

    错误:在调用AWS服务时出现AccessDenied异常

    虽然认证过程已经成功,但你的IAM角色并不具备执行该操作所需的权限。请检查与该角色关联的权限策略,并将其与错误信息中提到的具体操作进行对比。

    总结

    你已经完成了从在GitHub Secrets中存储静态的、长期有效的AWS凭证,到使用OIDC实现无密钥认证的转变。具体来说,你做到了以下几点:

    • 在AWS中将GitHub注册为受信任的OIDC身份提供者。

    • 为特定仓库创建了一个具有相应权限策略的IAM角色。

    • 为该角色配置了最低必要的权限。

    • 调整了你的GitHub Actions工作流程,使其能够请求并使用临时生成的AWS凭证。

    • 彻底验证了整个认证流程的正确性。

    这种模式适用于所有AWS服务,包括S3、ECS、Lambda、ECR、Secrets Manager等。这里的示例使用了S3,但您只需更换权限策略和部署命令,即可将其应用于任何其他服务。

    如果您想进一步了解相关内容,请探索以下选项:

    如果您正在建立DevOps实践体系,并需要关于基础设施自动化、CI/CD以及平台工程的完整参考资料,可以查看 《初创企业DevOps实战指南》。其中包含了我在实际AWS环境中使用过的各种模式、模板和操作手册。

    LinkedIn与我联系。

    参考资料

Comments are closed.