Todos os artigos

// Knowledge.log — 技術記事

GitHub Actions na AWS com OIDC: fim das chaves longas no repositório

Troque chaves estáticas do GitHub Actions por OIDC, IAM role com trust policy restrita, permissions boundary e testes de falha verificáveis.

Guardar AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY nos secrets do GitHub resolve o deploy, mas cria uma credencial permanente fora da AWS. Depois vêm a rotação, os consumidores esquecidos e a dúvida saudável sobre qual job ainda usa a chave antiga.

OIDC troca esse arranjo por uma credencial temporária: o job recebe um token do GitHub, apresenta esse token ao AWS STS e assume um IAM role. Não há access key de longa duração no repositório, e a AWS só aceita o token quando aud e sub correspondem ao que a trust policy autorizou.

O resultado que vamos montar é um pipeline com:

  • provedor OIDC do GitHub cadastrado na conta AWS;
  • IAM role restrito ao repositório e ao ambiente de produção;
  • permissions boundary limitando o teto desse role;
  • aws-actions/configure-aws-credentials@v6.2.3 no workflow;
  • testes que separam falha de autenticação de falha de autorização.

Por que a chave longa é um risco operacional

Uma access key de IAM user é uma credencial de longa duração formada pelo ID e pelo secret. O secret aparece uma única vez na criação. Se ele for perdido, não há botão para revelá-lo de novo: é preciso excluir a chave e criar outra.

Cada IAM user pode ter no máximo duas access keys. Por isso, a rotação costuma seguir esta sequência:

  1. criar a segunda chave;
  2. atualizar todos os consumidores;
  3. verificar que ninguém usa a primeira;
  4. excluir a primeira.

Se um workflow, script ou secret de outro repositório ficar para trás no passo 3, a remoção vira indisponibilidade. Se a chave vazar, ela continua válida até ser revogada.

A própria documentação de boas práticas do IAM recomenda credenciais temporárias e roles para workloads. Com OIDC, a credencial permanente deixa de existir no GitHub: cada job obtém uma sessão temporária para aquele deploy.

Pré-requisitos e nomes usados

Você precisa de permissão administrativa suficiente para criar um OIDC provider, policies e um IAM role. Nos exemplos, substitua estes valores pelos seus:

  • conta AWS: 111122223333;
  • organização: octo-org;
  • repositório: octo-repo;
  • bucket: meu-site-prod;
  • distribuição CloudFront: E123EXAMPLE;
  • região: us-east-1.

O role será chamado gha-deploy-prod. Evite o nome GitHubActions, conforme a observação mantida pelo próprio projeto da action. Um nome específico também ajuda quando o ARN aparece no STS.

A combinação usada aqui é o emissor https://token.actions.githubusercontent.com, audience sts.amazonaws.com e 2. Essa é a versão atual nas notas de release de 22 de julho de 2026. A página do GitHub ainda mostra em um exemplo um SHA de 2024, da época da v4; ele não é a base deste setup.

1. Cadastre o IdP OIDC do GitHub

O OIDC provider é criado uma vez por conta AWS para esse emissor:

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

O ARN resultante terá esta forma:

arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com

Não adicione thumbprint ao comando. Para esse cadastro, a AWS usa sua biblioteca de CAs confiáveis; a action também informa que o fingerprint não é necessário e é ignorado quando fornecido.

A configuração equivalente e os campos esperados estão na documentação da AWS para criação de um OIDC identity provider.

2. Restrinja a trust policy com aud e sub

Crie trust-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:environment:prod"
        }
      }
    }
  ]
}

O aud precisa ser sts.amazonaws.com, que é a audience solicitada por padrão pela action. O sub limita qual contexto do GitHub pode assumir o role. A AWS exige que essa claim esteja presente na trust policy do provedor GitHub e não aceita uma restrição composta apenas por curingas.

A forma do sub muda conforme o contexto do job:

Contexto do jobValor de sub
environment: prodrepo:ORG/REPO:environment:prod
branch sem environmentrepo:ORG/REPO:ref:refs/heads/main
tag sem environmentrepo:ORG/REPO:ref:refs/tags/VERSAO
pull request sem environmentrepo:ORG/REPO:pull_request

Há uma armadilha importante: quando o job define environment: prod, o trecho environment:prod substitui a branch no sub. Ele não é acrescentado a ref:refs/heads/main. Portanto, a trust policy acima deve ser combinada com regras de proteção do GitHub Environment que permitam deploy em prod somente a partir das branches ou tags aprovadas.

Se você não usa GitHub Environment, troque a condição por:

"token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:ref:refs/heads/main"

A referência de claims OIDC do GitHub documenta a montagem do subject. Já a AWS detalha as condition keys disponíveis para federação OIDC.

Repositórios com sub imutável

Para repositórios criados depois de 15 de julho de 2026, o GitHub.com pode emitir o subject com IDs imutáveis de owner e repositório. A forma é:

repo:OWNER@OWNER-ID/REPO@REPO-ID:ref:refs/heads/BRANCH

Renomeações, transferências e o opt-in de repositórios anteriores também podem levar a essa forma. Não copie IDs de exemplo para produção. Primeiro inspecione as claims emitidas pelo seu repositório e então grave os IDs reais na trust policy. Se o token usar o subject imutável e a policy ainda esperar apenas nomes, o AssumeRoleWithWebIdentity será negado.

3. Coloque um teto no role com permissions boundary

A trust policy responde quem pode assumir o role. A identity policy responde o que a sessão pode pedir. A permissions boundary não concede acesso sozinha: ela limita o máximo que a identity policy pode conceder, e uma ação só é autorizada quando ambas permitem.

Crie deploy-boundary.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::meu-site-prod/*"
    },
    {
      "Effect": "Allow",
      "Action": "cloudfront:CreateInvalidation",
      "Resource": "arn:aws:cloudfront::111122223333:distribution/E123EXAMPLE"
    }
  ]
}

Crie a managed policy que servirá de boundary:

aws iam create-policy \
  --policy-name gha-deploy-prod-boundary \
  --policy-document file://deploy-boundary.json

Agora crie deploy-policy.json, a identity policy do role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:DeleteBucket"
      ],
      "Resource": [
        "arn:aws:s3:::meu-site-prod",
        "arn:aws:s3:::meu-site-prod/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "cloudfront:CreateInvalidation",
      "Resource": "arn:aws:cloudfront::111122223333:distribution/E123EXAMPLE"
    }
  ]
}

O s3:DeleteBucket está ali de propósito para o teste negativo. A identity policy tenta concedê-lo; a boundary não permite. Depois da verificação, remova essa ação da identity policy — o teste já deu seu recado.

Crie o role com a boundary e anexe a policy inline:

aws iam create-role \
  --role-name gha-deploy-prod \
  --assume-role-policy-document file://trust-policy.json \
  --permissions-boundary \
    arn:aws:iam::111122223333:policy/gha-deploy-prod-boundary

aws iam put-role-policy \
  --role-name gha-deploy-prod \
  --policy-name gha-deploy-prod-policy \
  --policy-document file://deploy-policy.json

No exemplo, a boundary é o teto direto para o role de deploy em uma conta isolada, como descrito pela AWS em permissions boundaries para identidades IAM.

Se a empresa usa AWS Organizations, uma SCP pode cumprir o papel de teto organizacional para os member accounts. Ela também não concede acesso e não é requisito para este fluxo. Use a boundary como proteção do role nesta conta; use SCP quando a regra precisa valer para várias contas da organização.

4. Troque secrets estáticos pelo role no workflow

Remova do repositório os secrets AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY usados pelo CI. O workflow não deve repassá-los para a action.

Crie ou ajuste .github/workflows/deploy.yml:

name: deploy

on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: prod

    steps:
      - uses: actions/checkout@v7

      - name: Configurar credenciais temporárias da AWS
        uses: aws-actions/configure-aws-credentials@v6.2.3
        with:
          role-to-assume: arn:aws:iam::111122223333:role/gha-deploy-prod
          aws-region: us-east-1
          role-session-name: gha-octo-repo-prod

      - name: Confirmar a identidade assumida
        run: aws sts get-caller-identity

      - name: Publicar um artefato
        run: aws s3 cp dist/index.html s3://meu-site-prod/index.html

      - name: Invalidar o HTML no CloudFront
        run: >-
          aws cloudfront create-invalidation
          --distribution-id E123EXAMPLE
          --paths /index.html

permissions.id-token: write permite que o job solicite o JWT OIDC. Isso não concede escrita no repositório. contents: read continua necessário para o checkout.

A action chama AssumeRoleWithWebIdentity no STS e exporta para o job AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY e AWS_SESSION_TOKEN temporários. Eles pertencem à sessão assumida, não aos antigos secrets permanentes. A documentação do GitHub mostra o fluxo em configuração de OIDC com AWS, enquanto a API do STS está descrita em 4.

5. Verifique identidade, trust e limite de autorização

O primeiro teste é:

aws sts get-caller-identity

Dentro do workflow, a saída esperada contém a conta correta e um ARN de sessão semelhante a:

arn:aws:sts::111122223333:assumed-role/gha-deploy-prod/gha-octo-repo-prod

Isso prova qual identidade temporária está ativa. Não prova que a identity policy está correta. GetCallerIdentity não exige permissão IAM e continua identificando a sessão mesmo quando outras ações estão bloqueadas.

Faça também dois testes negativos, porque uma policy que nunca falha ainda não foi realmente apresentada ao ambiente.

Falha proposital de trust

Altere temporariamente um lado da relação. Por exemplo, retire environment: prod do job enquanto a trust policy exige:

repo:octo-org/octo-repo:environment:prod

Sem environment, o token da branch terá:

repo:octo-org/octo-repo:ref:refs/heads/main

A action deve falhar antes de obter credenciais, com erro de autorização para sts:AssumeRoleWithWebIdentity. O mesmo tipo de falha ocorre se permissions.id-token: write estiver ausente ou se aud não coincidir.

Restaure o environment antes de seguir.

Falha proposital de autorização

Com o role assumido e get-caller-identity funcionando, execute:

aws s3api delete-bucket --bucket meu-site-prod

A identity policy do exemplo contém s3:DeleteBucket, mas a permissions boundary não. O resultado esperado é AccessDenied. Essa diferença é importante:

  • falha no AssumeRoleWithWebIdentity: problema de token, audience, subject ou trust;
  • role assumido, API negada: problema — ou proteção intencional — na autorização efetiva.

Depois do teste, remova s3:DeleteBucket de deploy-policy.json e reaplique:

aws iam put-role-policy \
  --role-name gha-deploy-prod \
  --policy-name gha-deploy-prod-policy \
  --policy-document file://deploy-policy.json

Armadilha que merece revisão antes do merge

  • Não use repo:ORG/REPO:* sem aceitar conscientemente que branches, tags, pull requests e environments desse repositório entram no alcance.

Quando a DevDojo adotaria OIDC

Para CI no GitHub.com que faz deploy recorrente em uma conta AWS, a recomendação é firme: usar OIDC, trust policy restrita por aud e sub, environment protegido e permissions boundary no role. O custo de configuração é pontual; a remoção da credencial permanente resolve uma rotina contínua de exposição e rotação.

A exceção seria um acesso humano de break-glass, mantido fora do CI, com processo explícito de custódia e revogação. Mesmo nesse caso, a access key pertence ao usuário humano de emergência, não ao GitHub Actions. Se o deploy automatizado ainda depende dela, a migração não terminou.

Como próximo passo, execute o workflow primeiro com apenas get-caller-identity, faça os dois testes negativos e só então habilite as ações reais de deploy. Assim, você verifica a trust policy e o limite de autorização antes de habilitar o caminho de produção.

awsgithub-actionsiam

// Continue.training — 次のステップ

Conhecimento só conta quando vira prática.

Volte ao artigo, execute os exemplos e compartilhe o que aprendeu.

Explorar mais artigos