Destino Dev

Blog

Vault + GitHub Actions (OIDC): secrets dinâmicos e melhores práticas

Por John Pires · 20 de março de 2026

Vault + GitHub Actions (OIDC): secrets dinâmicos e melhores práticas

1) Motivacao e trade-offs

Por que usar OIDC com Vault

O objetivo é eliminar credenciais estáticas armazenadas no repositório ou em secrets do GitHub. Em vez disso, o pipeline solicita um token OIDC curto (fornecido pelo runtime do GitHub Actions) e faz login no Vault para obter credenciais dinâmicas — por exemplo, chaves IAM temporárias ou segredos de banco com TTL reduzido. Isso reduz blast radius e automatiza rotação.

O foco aqui é praticidade: mostro como configurar o Vault para aceitar o token OIDC do GitHub, como criar políticas mínimas, e como o workflow consome o secret temporário para operações reais como deploy de imagem Docker. Exemplos são comandos CLI do Vault, payloads JSON e workflows do GitHub Actions.

Trade-offs e limites

Usar OIDC com Vault tem vantagens claras, mas também restrições. Você depende da disponibilidade do provedor OIDC (GitHub), e do relógio das máquinas (time skew). A configuração de roles no Vault precisa ser exata: bound_claims ou bound_subject mal configurados resultam em falhas de autenticação.

Além disso, Vault continua sendo um ponto crítico: políticas muito liberais ou endpoints expostos aumentam risco. Por isso mostro padrões de least-privilege e checagens práticas para reduzir risco operacional.

2) Fundamentos tecnicos

Como funciona o fluxo

Resumo do fluxo mínimo que vamos implementar nas demonstrações:

  1. Workflow GitHub com permission id-token: write solicita um token OIDC para o runner.
  2. Workflow pega o token OIDC (via ACTIONS_ID_TOKEN_REQUEST_URL) e faz login no Vault usando o auth method jwt/oidc.
  3. Vault valida claims (repository, ref, audience) contra uma role configurada e retorna um client_token com políticas específicas.
  4. Workflow usa o client_token para ler segredos dinâmicos (ex: aws/creds/) e executa deploy.

O ponto crítico: o bound_claims / bound_subject na role do Vault devem corresponder ao payload do token OIDC emitido pelo GitHub. Sem isso, o Vault nega o login.

Configurações do Vault

Vamos habilitar o auth method JWT (ou OIDC dependendo da versão) e criar uma role que permita apenas um repositório e um branch.

Exemplo: habilitar o auth/jwt e criar uma role chamada github-actions. Os comandos abaixo funcionam com a CLI do Vault (supondo VAULT_ADDR e VAULT_TOKEN configurados).

vault auth enable jwt

vault write auth/jwt/config oidc_discovery_url="https://token.actions.githubusercontent.com" \
  oidc_client_id="api"

O que faz: habilita o auth method jwt e aponta discovery para o endpoint de OIDC do GitHub. O client_id/audience deve bater com o pedido do runner (audience que vamos solicitar no token).

Agora criamos a role. Esse exemplo limita o login ao repositório myorg/myrepo no branch main:

vault write auth/jwt/role/github-actions \
  role_type="jwt" \
  bound_claims='{"repository":"myorg/myrepo","ref":"refs/heads/main"}' \
  user_claim="sub" \
  policies="github-runner" \
  ttl="1h"

O que acontece: se o token OIDC conter as claims repository e ref correspondentes, Vault emitirá um client_token com a policy github-runner.

Policy exemplo (minimal):

{
  "path": {
    "aws/creds/github-deployer": { "capabilities": ["read"] },
    "secret/data/company/*": { "capabilities": ["read"] }
  }
}

Coloque isso em uma policy chamada github-runner. Ela dá acesso apenas ao endpoint AWS credenciado usado no exemplo avançado e a um prefixo de segredos.

3) Exemplo 1: caso simples

Objetivo

Caso simples: o workflow precisa obter um secret estático do Vault (por ex. chave API curta) e executar um teste de integração. O segredo é lido via OIDC-login no Vault. Aqui mostramos o login via curl (sem actions externos) para deixar tudo transparente.

Workflow e comandos

Contexto: o repositório é myorg/myrepo e o role no Vault foi configurado conforme seção anterior. A workflow pede id-token com audience "vault". Primeiro pedimos token OIDC ao GitHub e depois trocamos por client_token no Vault.

name: ci-oidc-vault
on: [push]
permissions:
  id-token: write
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Request OIDC token
        id: id-token
        run: |
          TOKEN=$(curl -s "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=vault" -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}")
          echo "::set-output name=token::$TOKEN"

      - name: Vault login using OIDC token
        id: vault-login
        env:
          VAULT_ADDR: https://vault.example
        run: |
          JWT=${{ steps.id-token.outputs.token }}
          LOGIN_RESP=$(curl -s --request POST "${VAULT_ADDR}/v1/auth/jwt/login" -d '{"role":"github-actions","jwt":"'

Assuntos: DevOps, Git

Leia também