Secrets e variáveis no GitHub Actions

Vazar um segredo de produção em um workflow do GitHub Actions é um dos erros mais comuns, e mais custosos, de segurança em times de desenvolvimento. Entender a hierarquia de secrets, usar environments corretamente e nunca imprimir secrets em logs são práticas que protegem sua infraestrutura.

Hierarquia de secrets no GitHub

Como os secrets são organizados por escopo:

bash
# Três níveis de secrets no GitHub:

# 1. Organization secrets — compartilhados entre repositórios:
#    GitHub Org → Settings → Secrets and variables → Actions
#    Exemplo: DOCKER_HUB_TOKEN, SLACK_WEBHOOK

# 2. Repository secrets — apenas para um repositório:
#    Repo → Settings → Secrets and variables → Actions → New secret
#    Exemplo: SSH_PRIVATE_KEY, DATABASE_URL

# 3. Environment secrets — vinculados a um ambiente (production/staging):
#    Repo → Settings → Environments → production → Add secret
#    Exemplo: PROD_DATABASE_URL (só disponível no job que usa environment: production)

# Hierarquia de prioridade (do mais específico ao mais geral):
# Environment > Repository > Organization

# Variáveis (não-secretas, visíveis nos logs):
# Repo → Settings → Secrets and variables → Actions → Variables
# Exemplo: NODE_VERSION, AWS_REGION, APP_NAME

# No workflow — usar secrets:
# ${{ secrets.NOME_DO_SECRET }}
# ${{ vars.NOME_DA_VARIAVEL }}  (variáveis não-secretas)

Usar secrets com segurança nos workflows

Boas práticas para evitar vazamento de secrets:

yaml
# .github/workflows/deploy.yml
name: Deploy

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production   # ← ativa os secrets do ambiente production

    steps:
      - uses: actions/checkout@v4

      # ✅ CORRETO — passar secret como variável de ambiente:
      - name: Deploy
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
        run: ./scripts/deploy.sh

      # ❌ ERRADO — nunca interpolar secret diretamente no script inline:
      # - run: echo ${{ secrets.DATABASE_URL }}  # vaza no log!

      # ❌ ERRADO — nunca usar secret em contexto de shell não-confiável:
      # - run: curl -u ${{ secrets.API_KEY }} http://...  # expansão no shell

      # ✅ CORRETO — GitHub mascara automaticamente os valores de secrets nos logs:
      - name: Testar conexão (o valor do secret fica mascarado nos logs)
        env:
          SSH_HOST: ${{ secrets.SSH_HOST }}
        run: |
          echo "Conectando em $SSH_HOST"  # exibe "Conectando em ***"
          # O GitHub mascara automaticamente o valor do secret

      # ✅ Criar arquivo de configuração a partir de secrets:
      - name: Criar .env de produção
        run: |
          cat > /tmp/.env.production << EOF
          DATABASE_URL=${{ secrets.DATABASE_URL }}
          REDIS_URL=${{ secrets.REDIS_URL }}
          JWT_SECRET=${{ secrets.JWT_SECRET }}
          EOF

Environments com aprovação manual

Proteger o ambiente de produção com revisores obrigatórios:

yaml
# Configurar environment com proteção:
# GitHub → Repo → Settings → Environments → New environment

# Environment: production
# ✓ Required reviewers: adicionar @username ou @team
# ✓ Wait timer: 0 minutes (ou X para delay obrigatório)
# ✓ Deployment branches: "Selected branches" → main only

# No workflow:
jobs:
  deploy-staging:
    environment: staging   # sem proteção — deploy automático
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploy em staging"

  deploy-production:
    needs: deploy-staging
    environment: production  # ← pausa aqui para aprovação manual
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploy em produção (após aprovação)"

# Fluxo:
# 1. Push em main
# 2. Tests passam
# 3. Deploy em staging automaticamente
# 4. GitHub envia notificação para os revisores
# 5. Revisor acessa a URL de aprovação e clica "Approve and deploy"
# 6. Deploy em produção executa

# Secrets específicos do environment:
# ${{ secrets.DATABASE_URL }} no job de production usa o valor
# configurado em Environments → production → Secrets

OpenID Connect: credenciais temporárias sem secrets

Autenticar no AWS sem armazenar access keys permanentes:

yaml
# OIDC elimina a necessidade de armazenar AWS_ACCESS_KEY_ID no GitHub.
# O GitHub Actions se autentica diretamente no AWS com token temporário.

# .github/workflows/deploy-aws.yml:
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write    # necessário para OIDC
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Configurar credenciais AWS via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
          aws-region: sa-east-1
          # Nenhum access key necessário — token OIDC é gerado automaticamente

      # A partir daqui, aws CLI está autenticado com credenciais temporárias:
      - name: Push imagem para ECR
        run: |
          aws ecr get-login-password --region sa-east-1 | \
            docker login --username AWS --password-stdin 123456789.dkr.ecr.sa-east-1.amazonaws.com
          docker push 123456789.dkr.ecr.sa-east-1.amazonaws.com/minha-api:latest

# Na AWS — configurar IAM Role para GitHub Actions:
# Trust policy do Role:
# {
#   "Principal": {
#     "Federated": "arn:aws:iam::123456789:oidc-provider/token.actions.githubusercontent.com"
#   },
#   "Condition": {
#     "StringEquals": {
#       "token.actions.githubusercontent.com:sub": "repo:org/repo:ref:refs/heads/main"
#     }
#   }
# }

Auditoria de secrets e boas práticas

Detectar secrets vazados e prevenir exposição acidental:

bash
# Ferramentas para detectar secrets no código:

# 1. git-secrets (AWS) — instalar e configurar:
brew install git-secrets  # ou apt
git secrets --install     # instalar hooks no repositório
git secrets --register-aws  # patterns do AWS

# 2. gitleaks — detectar secrets em commits:
# .github/workflows/security.yml:
  secret-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # histórico completo
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# 3. .gitignore — sempre ignorar:
# .env
# .env.local
# .env.production
# *.pem
# *.key
# credentials.json

# 4. Se um secret vazar em um commit:
# (a) Rotacionar IMEDIATAMENTE — assume comprometido
# (b) Remover do histórico: git filter-repo --path .env --invert-paths
# (c) Force push (após comunicar o time)
# AVISO: remover do histórico não garante que foi deletado de forks ou caches

# 5. Rotação programada de secrets:
# Use secrets com expiração (tokens de 90 dias)
# Configure lembretes no calendário para renovar

Não quer configurar manualmente?

Implante o VPS para APIs em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Sim, o GITHUB_TOKEN é gerado automaticamente para cada run do workflow com escopo limitado ao repositório e expira quando o job termina. Nunca persiste entre runs. Por padrão tem permissões read-only, você deve declarar explicitamente as permissões necessárias (permissions: packages: write). É mais seguro que um Personal Access Token (PAT) porque é temporário e com escopo mínimo.

Duas abordagens: (1) Criar o .env diretamente no runner e copiá-lo via SCP antes do deploy. (2) Armazenar o .env no servidor (criado manualmente uma vez) e nunca sobrescrevê-lo no deploy, mais seguro pois os secrets não transitam pelo CI. Para secrets que mudam com frequência: use um serviço como HashiCorp Vault, AWS Secrets Manager ou Doppler, a aplicação lê os secrets na inicialização.

Não, secrets são criptografados e nunca expostos na UI do GitHub após serem salvos. Você consegue atualizar ou deletar, mas não visualizar o valor. Nos logs do workflow, GitHub substitui automaticamente o valor por "***". Mas: qualquer pessoa com write access ao repositório pode criar um workflow que exfiltra secrets, limite o write access cuidadosamente e use environments com proteção para secrets críticos.

Variables (vars.*): valores não-sensíveis visíveis nos logs, URLs de API, nomes de ambientes, feature flags, versão do Node. Secrets (secrets.*): valores sensíveis mascarados nos logs, senhas, tokens, chaves privadas. Use variables para qualquer coisa que você pudesse colocar no código sem risco. Use secrets apenas para credenciais. Misturar os dois (colocar tudo em secrets) dificulta o debug sem nenhum ganho de segurança.

Organization secrets: defina na org e escolha quais repositórios têm acesso, a solução nativa do GitHub. Para secrets muito sensíveis que não devem ser organizacionais: use um serviço centralizado, HashiCorp Vault (self-hosted), Doppler, AWS Secrets Manager ou 1Password Secrets Automation. Todos têm integração nativa com GitHub Actions via actions de terceiros.