27 de julho de 2026 · Elton Peixoto

GitHub Composite Actions, Workload Identity Federation e Google Cloud Build: CI/CD sem chaves de serviço

Implantação real de OIDC + Workload Identity Federation entre GitHub Actions e Google Cloud, com os três erros de IAM que a documentação oficial não deixa óbvios.

github-actions google-cloud ci-cd seguranca workload-identity-federation devops
Capa do artigo GitHub Composite Actions, Workload Identity Federation e Google Cloud Build: CI/CD sem chaves de serviço
Compartilhar:

Este artigo documenta uma implantação real, feita do zero em um projeto Google Cloud (gh-actions-wif-demo) e em um repositório público no GitHub (gh-actions-wif-cloudbuild-demo).

Integrar GitHub Actions ao Google Cloud parece simples: criar uma conta de serviço, gerar uma chave JSON, salvar o arquivo como secret e executar o pipeline.

Funciona, mas cria um problema importante: a credencial permanece válida mesmo depois que o workflow termina.

Se essa chave for copiada, exposta em logs, comprometida em uma dependência ou esquecida em algum repositório, ela poderá ser utilizada fora do contexto original do pipeline.

Uma arquitetura mais segura substitui a chave estática por três componentes:

  • GitHub Actions como emissor de identidade via OpenID Connect;
  • Google Cloud Workload Identity Federation (WIF) como camada de confiança;
  • credenciais temporárias emitidas somente durante a execução autorizada.

Quando essa autenticação é encapsulada em uma GitHub Composite Action, a organização também consegue transformar o processo em um componente reutilizável para vários repositórios.

Neste artigo, construímos esta arquitetura de ponta a ponta:

Fluxo de autenticação OIDC entre GitHub Actions e Google Cloud via Workload Identity Federation

O resultado é um pipeline capaz de executar gcloud builds submit sem armazenar uma chave JSON do Google Cloud no GitHub — e a Figura 8 mais abaixo prova isso mostrando a aba de secrets do repositório real, vazia.


1. O problema das chaves de contas de serviço

Uma chave de conta de serviço é uma credencial de longa duração. O arquivo contém material criptográfico que permite autenticação como aquela identidade até que a chave seja revogada ou expire.

Em pipelines de CI/CD, isso cria algumas dificuldades:

  • a chave precisa ser armazenada como secret;
  • o segredo precisa ser distribuído entre repositórios;
  • a rotação precisa ser planejada;
  • cópias antigas podem continuar existindo;
  • a credencial pode ser usada fora do GitHub Actions;
  • o Google Cloud não consegue determinar facilmente qual workflow originou o uso daquela chave.

O objetivo não é simplesmente esconder melhor uma chave. O objetivo é não possuir uma chave para esconder.

2. Como o Workload Identity Federation funciona

O GitHub Actions consegue emitir um token OIDC para uma execução de workflow. Esse token carrega claims que descrevem a identidade da execução: organização, repositório, branch, ambiente, workflow, actor e identificadores estáveis.

No lado do Google Cloud, criamos: um Workload Identity Pool, um Workload Identity Provider, um mapeamento entre claims do GitHub e atributos do Google Cloud, condições que definem quais identidades serão aceitas, e uma autorização para que essa identidade utilize uma conta de serviço.

Figura 1 — Pool github-actions criado no projeto real

Detalhes do pool GitHub Actions Pool no console GCP

Esta é a tela Detalhes do pool no console (IAM & Admin > Workload Identity Federation). Repare no campo Principal do IAM, que mostra o formato principal://iam.googleapis.com/projects/.../workloadIdentityPools/github-pool/subject/... — é esse principal que aparece depois no binding da service account.


3. Onde entram as Composite Actions

Uma GitHub Composite Action combina várias etapas de workflow em uma única action reutilizável. Em vez de repetir a sequência de autenticação, setup do gcloud e submissão de build em todos os repositórios, oferecemos uma interface simplificada:

- uses: ./.github/actions/gcp-cloud-build
  with:
    project_id: gh-actions-wif-demo
    workload_identity_provider: projects/573666774426/locations/global/workloadIdentityPools/github-actions/providers/github
    service_account: github-cloud-build@gh-actions-wif-demo.iam.gserviceaccount.com
RecursoMelhor aplicação
Composite ActionReutilizar uma operação composta por vários steps
Reusable WorkflowPadronizar jobs ou pipelines completos
Workflow comumImplementar o fluxo específico de um repositório

A operação reutilizável aqui é: autenticar + preparar gcloud + enviar build — uma boa candidata a Composite Action.


4. Implantação passo a passo (comandos reais)

4.1 Variáveis e projeto

export PROJECT_ID="gh-actions-wif-demo"
export PROJECT_NUMBER="$(gcloud projects describe "${PROJECT_ID}" --format='value(projectNumber)')"
# PROJECT_NUMBER=573666774426

export GITHUB_REPOSITORY="elton-peixoto-lu/gh-actions-wif-cloudbuild-demo"
export POOL_ID="github-actions"
export PROVIDER_ID="github"
export SERVICE_ACCOUNT_NAME="github-cloud-build"

4.2 Habilitando as APIs

gcloud services enable \
  iam.googleapis.com \
  iamcredentials.googleapis.com \
  sts.googleapis.com \
  cloudresourcemanager.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com

4.3 Service account do pipeline

gcloud iam service-accounts create "${SERVICE_ACCOUNT_NAME}" \
  --project="${PROJECT_ID}" \
  --display-name="GitHub Cloud Build"

Figura 2 — Service account conectada ao pool, com o binding de repositório visível

Contas de serviço conectadas ao pool, mostrando o atributo de repositório

Esta tela (aba Contas de serviço conectadas dentro do pool) mostra a service account github-cloud-build e, expandida, a condição exata que autoriza o acesso: attribute.repository="elton-peixoto-lu/gh-actions-wif-cloudbuild-demo" Se esse atributo não bater exatamente com o repositório que está rodando o workflow, a troca de token falha — é o mecanismo central de toda a segurança do WIF.

4.4 Workload Identity Pool e Provider

gcloud iam workload-identity-pools create "${POOL_ID}" \
  --project="${PROJECT_ID}" --location="global" \
  --display-name="GitHub Actions"

gcloud iam workload-identity-pools providers create-oidc "${PROVIDER_ID}" \
  --project="${PROJECT_ID}" --location="global" \
  --workload-identity-pool="${POOL_ID}" \
  --display-name="GitHub OIDC" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner,attribute.repository_id=assertion.repository_id,attribute.repository_owner_id=assertion.repository_owner_id,attribute.ref=assertion.ref" \
  --attribute-condition="assertion.repository == '${GITHUB_REPOSITORY}'"

Figura 3 — Provider github criado (aba Provedores do pool)

Lista de provedores dentro do pool, mostrando o GitHub OIDC ativo

Figura 4 — Attribute Condition do provider, na prática

Condição do atributo do provider mostrando assertion.repository restrito ao repositório do demo

Este é o coração da política de segurança: assertion.repository == 'elton-peixoto-lu/gh-actions-wif-cloudbuild-demo'. Sem essa condição, qualquer repositório da conta que apresentasse um token OIDC do GitHub seria aceito pelo provider — a etapa seguinte (o binding IAM) ainda restringiria o acesso à service account, mas a defesa em profundidade começa aqui.

4.5 Binding IAM (repositório → service account)

export WORKLOAD_IDENTITY_POOL_NAME="$(gcloud iam workload-identity-pools describe "${POOL_ID}" \
  --project="${PROJECT_ID}" --location="global" --format="value(name)")"

gcloud iam service-accounts add-iam-policy-binding \
  "github-cloud-build@${PROJECT_ID}.iam.gserviceaccount.com" \
  --project="${PROJECT_ID}" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_NAME}/attribute.repository/${GITHUB_REPOSITORY}"

Essa combinação — token do GitHub + provider correto + condição satisfeita + atributo mapeado + binding workloadIdentityUser + papéis da service account — constitui a cadeia completa de confiança.


5. A Composite Action e o workflow consumidor

.github/actions/gcp-cloud-build/action.yml (resumido — o arquivo completo está no repositório):

runs:
  using: "composite"
  steps:
    - name: Authenticate to Google Cloud
      id: auth
      uses: google-github-actions/auth@v3
      with:
        project_id: ${{ inputs.project_id }}
        workload_identity_provider: ${{ inputs.workload_identity_provider }}
        service_account: ${{ inputs.service_account }}
        create_credentials_file: true
        export_environment_variables: true

    - name: Install Google Cloud CLI
      uses: google-github-actions/setup-gcloud@v3

    - name: Submit Cloud Build
      shell: bash
      run: |
        gcloud builds submit "${SOURCE}" \
          --project="${PROJECT_ID}" \
          --config="${BUILD_CONFIG}" \
          --substitutions="${SUBSTITUTIONS}"

.github/workflows/build.yml, no repositório consumidor:

permissions:
  contents: read
  id-token: write   # autoriza SOMENTE a emissão do token OIDC, não escrita no GCP

jobs:
  build:
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/gcp-cloud-build
        with:
          project_id: gh-actions-wif-demo
          workload_identity_provider: projects/573666774426/locations/global/workloadIdentityPools/github-actions/providers/github
          service_account: github-cloud-build@gh-actions-wif-demo.iam.gserviceaccount.com

Figura 5 — Log real do step de autenticação, run bem-sucedido

Log do step Submit Google Cloud Build mostrando autenticação bem-sucedida

Trecho do run 30378943632: Successfully authenticated seguido de Authenticated account: github-cloud-build@gh-actions-wif-demo.iam.gserviceaccount.com — prova de que o job está autenticado como a service account do pipeline, não como um usuário humano, e sem nenhum arquivo de chave commitado.

Figura 6 — Build correspondente no Cloud Build, com origem rastreável

Detalhes da versão do build no Cloud Build, mostrando steps e origem do tarball

A origem do build (gs://gh-actions-wif-demo_cloudbuild/source/...tgz) e os dois steps (build-container, push-container) — a mesma execução que aparece nos logs do GitHub Actions, agora do lado do Google Cloud.

Figura 7 — Imagem publicada no Artifact Registry

Detalhes da imagem demo publicada no Artifact Registry, com digest e tag do commit

A tag da imagem (090103ca8cd3acbff571370d2b092eaf8b3cb154) é o próprio SHA do commit que disparou o workflow — rastreabilidade completa entre código-fonte, execução do CI e artefato publicado.


6. Prova de que não há chave armazenada

Figura 8 — Secrets do repositório, vazio

Aba de Secrets and variables do repositório GitHub, sem nenhum secret cadastrado

Settings → Secrets and variables → Actions do repositório real, mostrando “This repository has no secrets”. O pipeline inteiro funciona sem GCP_SA_KEY ou qualquer outro secret de credencial — a única permissão declarada no workflow é id-token: write, que autoriza a emissão do token OIDC, não acesso direto ao Google Cloud.


7. Os três erros reais de IAM (e como cada um foi corrigido)

Esta implantação passou por três falhas reais antes de funcionar. Elas revelam algo importante: a superfície de troubleshooting do WIF é maior que a de uma chave JSON, porque a autorização depende de múltiplas peças (bucket de staging, “act as” de outra service account, leitura de objeto) além da autenticação em si.

Erro 1 — Bucket de staging negado

Figura 9 — Log de erro real do primeiro run

Log de erro mostrando bucket forbidden no primeiro run do workflow

ERROR: (gcloud.builds.submit) The user is forbidden from accessing the bucket
[gh-actions-wif-demo_cloudbuild]. Please check your organization's policy or
if the user has the "serviceusage.services.use" permission...

A autenticação já havia sido concluída com sucesso (Successfully authenticated, linha 42) — o problema é de autorização, não de identidade. A mensagem de erro do gcloud é enganosa: ela sugere o papel Service Usage Admin, mas a causa real era a ausência de roles/storage.admin na service account, necessário para o Cloud Build criar/usar o bucket de staging automático.

Correção aplicada:

gcloud projects add-iam-policy-binding gh-actions-wif-demo \
  --member="serviceAccount:github-cloud-build@gh-actions-wif-demo.iam.gserviceaccount.com" \
  --role="roles/storage.admin"

Erro 2 — Não é permitido “agir como” a service account do Cloud Build

Figura 10 — Log de erro do segundo run

Log de erro mostrando permission_denied ao tentar act as service account

ERROR: (gcloud.builds.submit) PERMISSION_DENIED: generic::permission_denied:
caller does not have permission to act as service account
projects/gh-actions-wif-demo/serviceAccounts/107067108817030564604...

O número 107067108817030564604 corresponde à Compute Engine default service account (573666774426-compute@developer.gserviceaccount.com), que o Cloud Build usa por padrão para executar os steps do build. A service account do GitHub precisa de permissão explícita para “atuar como” essa identidade.

Correção aplicada:

gcloud iam service-accounts add-iam-policy-binding \
  573666774426-compute@developer.gserviceaccount.com \
  --member="serviceAccount:github-cloud-build@gh-actions-wif-demo.iam.gserviceaccount.com" \
  --role="roles/iam.serviceAccountUser"

Erro 3 — Sem permissão de leitura no objeto de source

Figura 11 — Log de erro do terceiro run

Log de erro mostrando storage.objects.get denied ao ler o tarball de source

ERROR: (gcloud.builds.submit) INVALID_ARGUMENT: could not resolve source:
googleapi: Error 403: 573666774426-compute@developer.gserviceaccount.com
does not have storage.objects.get access to the Google Cloud Storage object...

Mesmo já podendo “atuar como” a Compute default service account, essa identidade ainda não conseguia ler o próprio tarball de código-fonte que o GitHub Actions havia enviado ao bucket de staging.

Correção aplicada:

gcloud projects add-iam-policy-binding gh-actions-wif-demo \
  --member="serviceAccount:573666774426-compute@developer.gserviceaccount.com" \
  --role="roles/storage.objectViewer"

O padrão por trás dos três erros

Service account do GitHub (github-cloud-build)
    -> precisa enviar o build           => roles/cloudbuild.builds.editor
    -> precisa escrever no staging      => roles/storage.admin
    -> precisa "ser" a SA executora     => roles/iam.serviceAccountUser (na Compute SA)

Service account executora (Compute default SA)
    -> precisa ler o source enviado     => roles/storage.objectViewer
    -> precisa publicar a imagem        => roles/artifactregistry.writer

Isso é um tradeoff real do modelo, não só do exemplo: uma chave JSON com papel de Editor teria funcionado de primeira, escondendo essas fronteiras de permissão. O WIF com least privilege obriga a modelar essas duas identidades (quem envia o build vs. quem o executa) separadamente — mais seguro, mas com uma curva de depuração maior na primeira implantação.


8. Checklist de produção (validado nesta implantação)

  • Nenhuma chave JSON armazenada no GitHub (Figura 8).
  • Workflow com id-token: write.
  • Provider usando o project number (573666774426), não o project ID textual.
  • Issuer https://token.actions.githubusercontent.com.
  • Attribute condition restrita ao repositório exato (Figura 4).
  • Binding IAM limitado ao mesmo repositório (Figura 2).
  • Service account do pipeline com papéis mínimos: cloudbuild.builds.editor, storage.admin, serviceusage.serviceUsageConsumer, artifactregistry.writer.
  • Service account executora do build com storage.objectViewer e iam.serviceAccountUser concedido pela SA do pipeline.
  • Run de ponta a ponta validado, com imagem publicada no Artifact Registry (Figuras 6 e 7).

Conclusão

Workload Identity Federation altera a autenticação entre GitHub Actions e Google Cloud de um modelo baseado em segredo para um modelo baseado em identidade verificável. Em vez de perguntar “onde devemos armazenar a chave da conta de serviço?”, a arquitetura passa a perguntar “quais execuções, de quais repositórios e sob quais condições podem receber credenciais temporárias?”.

O preço dessa mudança, comprovado nesta implantação, é uma superfície de troubleshooting maior — três erros de IAM distintos antes do primeiro build bem-sucedido, cada um isolando uma fronteira de permissão que uma chave de Editor teria mascarado. Para times que operam múltiplos repositórios e levam a sério o menor privilégio, essa é uma troca que vale a pena; para um pipeline pessoal de baixo risco, é um custo de setup real que merece ser dito, não só o benefício de segurança.

Repositório de referência: github.com/elton-peixoto-lu/gh-actions-wif-cloudbuild-demo

Referências

GOOGLE CLOUD. Workload identity federation. Google Cloud IAM Documentation. Disponível em: cloud.google.com/iam/docs/workload-identity-federation.

GOOGLE CLOUD. Best practices for using workload identity federation. Google Cloud IAM Documentation. Disponível em: cloud.google.com/iam/docs/best-practices-for-using-workload-identity-federation.

GOOGLE CLOUD. Best practices for using service accounts. Google Cloud IAM Documentation. Disponível em: cloud.google.com/iam/docs/best-practices-service-accounts.

GOOGLE CLOUD. Cloud Build overview. Google Cloud Build Documentation. Disponível em: cloud.google.com/build/docs/overview.

GOOGLE CLOUD. IAM roles for Cloud Build. Google Cloud Build Documentation. Disponível em: cloud.google.com/build/docs/iam-roles-permissions.

GITHUB. About security hardening with OpenID Connect. GitHub Actions Documentation. Disponível em: docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect.

GITHUB. Configuring OpenID Connect in Google Cloud Platform. GitHub Actions Documentation. Disponível em: docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-google-cloud-platform.

GITHUB. Creating a composite action. GitHub Actions Documentation. Disponível em: docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action.

GITHUB. Reusing workflows. GitHub Actions Documentation. Disponível em: docs.github.com/en/actions/sharing-automations/reusing-workflows.

GOOGLE GITHUB ACTIONS. auth — authenticate to Google Cloud from GitHub Actions. Repositório oficial. Disponível em: github.com/google-github-actions/auth.

GOOGLE GITHUB ACTIONS. setup-gcloud — install and configure the Google Cloud CLI. Repositório oficial. Disponível em: github.com/google-github-actions/setup-gcloud.

Compartilhar: