TL;DR: Este artigo analisa as práticas de segurança de CI/CD adotadas pelo Cilium para controlar quem dispara builds e qual código é executado. Com o bot Ariane, checkouts em duas fases, CODEOWNERS e pinning de ações por SHA, o projeto reduz drasticamente o risco de comprometimento da supply chain. A conclusão principal é que esses padrões são aplicáveis a qualquer projeto open source ou corporativo que use GitHub Actions, desde que adaptados ao contexto de cada organização.
Parte 1

Os últimos doze meses foram duros para a supply chain de código aberto. O Axios foi comprometido no npm e distribuiu um trojan de acesso remoto dentro de versões que pareciam normais. O pacote PyPI do LiteLLM foi sequestrado para exfiltrar variáveis de ambiente. Forks typosquatted do Trivy foram publicados para pegar quem digita go install de forma errada. E o exemplo clássico, a violação da SolarWinds em 2020, ainda é o conto de advertência ao qual voltamos: atacantes invadiram o sistema de build e inseriram malware através de atualizações normais do Orion em cerca de 18.000 organizações, incluindo agências federais dos EUA, a OTAN e a Microsoft. O malware ficou dormente por meses. A violação passou despercebida por quase um ano.
O Cilium opera no caminho de rede em nível de kernel de milhões de pods Kubernetes. Se nossa supply chain fosse comprometida, o raio de explosão não seria pequeno. Endurecer o projeto contra esse cenário é algo em que trabalhamos continuamente, e queremos registrar o que realmente fazemos, em detalhes. A maior parte do que segue não é específica do Cilium: qualquer projeto open source que execute CI/CD no GitHub Actions pode aplicar esses padrões. Também destacamos onde ainda ficamos aquém, caso isso sirva como ponto de partida útil para outros.
Este é o primeiro post de uma série de três partes. Este post cobre controle de acesso: quem pode disparar builds e qual código o CI tem permissão para executar. A Parte 2 cobrirá hardening de dependências, e a Parte 3 isolamento de credenciais, verificação de releases e as lacunas que ainda estamos fechando.
TL;DR
Se você não tem tempo para ler a série inteira, aqui está o que o Cilium faz para endurecer sua supply chain hoje, organizado por qual camada do pipeline cada controle reside:
| Camada | Controle | O que faz |
|---|---|---|
| Quem dispara builds | Controle de trigger via Ariane | Apenas membros verificados da organização podem disparar workflows CI a partir de comentários em PRs, contra uma lista explícita de workflows autorizados. |
| Qual código o CI executa | Checkouts em duas fases para pull_request_target | O código confiável (ações compostas, scripts, lógica de assinatura) é carregado da branch base; o head do PR é usado apenas como contexto de build do Docker, nunca executado como script. |
| Quem revisa mudanças no CI | Gates via CODEOWNERS | Qualquer coisa abaixo de .github/ exige revisão do time de segurança focado em CI, e auto-approve.yaml requer um mantenedor. |
| Quais dependências o CI puxa | Ações e imagens fixadas por SHA | Cada uses: referencia um commit SHA de 40 caracteres; imagens de contêiner são fixadas por @sha256: digest. O Renovate mantém os pins atualizados e espera 5 dias antes de pegar novas versões. |
| Quais módulos Go vão no binário | Dependências Go vendored | Tudo é verificado em vendor/ e revisado pelo time @cilium/vendor, então um módulo typosquatted ou sequestrado aparece como diff no momento da revisão. |
| Como os workflows podem se parecer | Análise estática em workflows | CodeQL impõe permissões explícitas em cada workflow, actionlint captura padrões inseguros, e ambos sinalizam injeção de expressão do GitHub Actions em blocos run:. |
| Quais credenciais são acessíveis | Isolamento de credenciais CI vs. produção | Credenciais CI só podem fazer push para tags de desenvolvimento *-ci; credenciais de registry de produção ficam atrás de um ambiente de release protegido que requer aprovação de mantenedor. |
| O que os consumidores podem verificar | Releases assinados | Toda imagem de release e Helm chart é assinada com Sigstore Cosign usando OIDC keyless, com atestações SBOM anexadas. |
| Onde ainda ficamos aquém | Lacunas que ainda estamos fechando | Nenhuma proveniência SLSA ainda, nenhuma revisão de dependência em tempo de PR, nenhum govulncheck no CI, e algumas referências internas @main que precisam ser migradas para um repositório dedicado de ações compostas. |
Como controlar quem executa o quê no CI/CD?
A primeira pergunta em qualquer história de supply chain de CI é: quem pode disparar um build, e qual código ele executa? Muitos comprometimentos de CI começam exatamente aqui, enganando o sistema para executar código controlado pelo atacante com privilégios elevados.
Restrições de trigger de workflow com Ariane
Ariane é um bot GitHub que escrevemos internamente para despachar workflows CI a partir de comentários em PRs. Quando um mantenedor digita /test ou /ci-eks em um pull request, o Ariane verifica se o comentarista pertence ao time organization-members, descobre quais workflows disparar (incluindo dependências, como testes que precisam de uma nova build de imagem primeiro) e os despacha via workflow_dispatch.
O detalhe interessante é a lista de permissões. Apenas membros verificados da organização podem disparar workflows, e o conjunto de workflows que podem ser disparados é enumerado manualmente na configuração:
.github/ariane-config.yaml
allowed-teams:
- organization-members
triggers:
/test\s*:
workflows:
- conformance-aws-cni.yaml
- conformance-clustermesh.yaml
- conformance-eks.yaml
# ...e assim por diante
depends-on:
- /build-images-dependency
/ci-aks:
workflows:
- conformance-aks.yaml
depends-on:
- /build-images-dependency
Um comentarista externo aleatório digitando /test em um PR é ignorado. Ele não pode iniciar nossas suítes de conformidade caras em provedores cloud ou queimar nossos minutos de CI.
Separando código confiável e não confiável no CI
Quando alguém abre um PR, precisamos construir o código dele, mas obviamente não podemos confiar nele. Este é o clássico problema do pull_request_target. Evitamos pull_request_target onde podemos, mas alguns workflows ainda precisam dele, e os envolvemos em controles mitigadores.
O workflow de build de imagem é o exemplo canônico. Ele divide o checkout em dois:
.github/workflows/build-images-ci.yaml
- name: Checkout base or default branch (trusted)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.base_ref || github.event.repository.default_branch }}
persist-credentials: false
# ...etapas de setup confiáveis executam aqui, incluindo carregar ações compostas...
# Warning: since this is a privileged workflow, subsequent workflow job
# steps must take care not to execute untrusted code.
- name: Checkout pull request branch (NOT TRUSTED)
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
ref: ${{ steps.tag.outputs.sha }}
O primeiro checkout pega a branch base (código já revisado e mesclado) para carregar nossas ações compostas, scripts e a lógica de assinatura Cosign de uma fonte conhecida e confiável. Só depois disso o workflow faz checkout do head do PR, e esse checkout é usado puramente como contexto de build para docker build. Nada da branch do PR é executado como script.
Recebemos relatórios de segurança sobre esse padrão com bastante frequência. Scanners automatizados e pesquisadores bem-intencionados veem "pull_request_target mais um segundo checkout" e sinalizam como vulnerabilidade. No caso geral, eles estão certos. No nosso, o workflow foi intencionalmente projetado para que o padrão seja seguro:
- Nenhum passo run: executa scripts do checkout não confiável. Cada bloco shell após o segundo checkout é escrito inline no YAML do workflow (verificações de uso de disco, cópias de arquivos, saída de digest). Nada é originado da branch do PR.
- Nenhuma ação composta é carregada do checkout não confiável. Todas as ações compostas (set-runtime-image, cosign, set-env-variables) vêm do checkout da branch base confiável ou do diretório ../cilium-base-branch/ salvo. Também estamos trabalhando para mover essas ações compostas para um repositório dedicado, para não precisarmos fazer checkout do código fonte para executá-las.
- O Docker BuildKit executa o Dockerfile não confiável, e esse é o ponto principal de construir uma imagem CI a partir de um PR. O BuildKit é executado em isolamento: sem variáveis de ambiente do GitHub Actions, sem secrets do repositório, sem acesso ao credential store do Docker do runner. Os build args que passamos não contêm secrets, apenas a referência da imagem runtime e o nome da variante do operador.
- Dados não confiáveis fluem para exatamente uma ação confiável. O arquivo runtime-image*.txt do PR é alimentado na ação confiável set-runtime-image, que verifica se a referência da imagem começa com quay.io/cilium/ e remove quebras de linha para que um atacante não possa contrabandear uma injeção de GITHUB_ENV. Não há como redirecionar o build para fora do namespace Cilium.
- Apenas credenciais de CI estão em escopo. O login Docker usa QUAY_USERNAME_CI / QUAY_PASSWORD_CI, que só podem fazer push para o registry de desenvolvimento -ci. As credenciais de produção não estão no runner.
O pior cenário de um build de PR comprometido é uma imagem CI maliciosa no registry de desenvolvimento, que é o mesmo raio de explosão que qualquer sistema de CI que constrói código de contribuidores carrega. Agradecemos cada relatório e lemos cada um com atenção, mas esse padrão é intencional.
CODEOWNERS como gate de revisão
Usamos bastante CODEOWNERS para que mudanças sempre caiam nas mãos das pessoas com mais contexto. Para configuração de CI, isso significa que qualquer coisa abaixo de .github/ é de propriedade de @cilium/github-sec (nosso time de segurança focado em CI) mais @cilium/ci-structure, e o workflow auto-approve.yaml é de propriedade de @cilium/cilium-maintainers:
CODEOWNERS
/.github/ @cilium/github-sec @cilium/ci-structure
/.github/ariane-config.yaml @cilium/github-sec @cilium/ci-structure
/.github/renovate.json5 @cilium/github-sec @cilium/ci-structure
/.github/workflows/ @cilium/github-sec @cilium/ci-structure
/.github/workflows/auto-approve.yaml @cilium/cilium-maintainers
Ninguém pode alterar o pipeline de CI sem uma revisão explícita do time responsável por mantê-lo seguro.
A seguir, na Parte 2, cobriremos como bloqueamos o que os builds realmente puxam: ações fixadas por SHA, atualizações automatizadas de dependências e vendoring de módulos Go.
Artigo originalmente publicado por André Martins e Feroz Salam em Cloud Native Computing Foundation.
Perguntas Frequentes
-
O que é o bot Ariane e como ele protege o disparo de workflows?
Ariane é um bot GitHub desenvolvido pelo Cilium que só permite que membros verificados da organização disparem workflows a partir de comentários em PRs. Ele usa uma lista explícita de workflows autorizados e ignora comandos de colaboradores externos, prevenindo uso indevido de recursos e execução não autorizada. -
Como o Cilium lida com o problema de pull_request_target no CI?
O Cilium evita pull_request_target sempre que possível, mas quando necessário (ex.: build de imagens), faz dois checkouts: primeiro da branch base (confiável) para carregar ações e scripts, e depois da branch do PR (não confiável), usada apenas como contexto de build no Docker. Nenhum script da branch do PR é executado diretamente. -
Por que usar dois checkouts separados é seguro nesse caso?
Porque todos os passos shell após o segundo checkout são escritos inline no YAML do workflow, as ações compostas vêm da branch base, e o Docker BuildKit é executado em isolamento, sem acesso a secrets ou variáveis de ambiente do runner. Além disso, as credenciais usadas são apenas do registry de desenvolvimento (-ci), não de produção. -
Quais são as lacunas de segurança ainda abertas no pipeline do Cilium?
O Cilium reconhece que ainda faltam: proveniência SLSA, revisão de dependências em tempo de PR, govulncheck no CI, e algumas referências internas @main que precisam ser migradas para um repositório dedicado de ações compostas. Essas melhorias estão sendo trabalhadas. -
Como empresas brasileiras podem aplicar esses padrões de segurança em seus próprios pipelines?
Empresas podem adotar o bot Ariane para controlar disparo de workflows, implementar checkouts em duas fases em workflows que exigem pull_request_target, usar CODEOWNERS para revisar mudanças em .github/, e fixar ações e imagens por SHA. Essas práticas são independentes de linguagem ou provedor cloud e reduzem significativamente a superfície de ataque.