<h2>Supply Chain Security: Protegendo sua Pipeline de Software</h2>
<p>A segurança da cadeia de suprimentos de software tornou-se uma das maiores preocupações da indústria. Ataques sofisticados como o SolarWinds (2020) e Codecov (2021) demonstraram que comprometer dependências ou componentes intermediários é tão efetivo quanto atacar o alvo final. Neste artigo, exploraremos três pilares essenciais: <strong>SBOM (Software Bill of Materials)</strong>, <strong>Cosign</strong> para assinatura e verificação, e as práticas de verificação de imagens Docker. Você aprenderá não apenas <em>o quê</em> fazer, mas <em>por quê</em> cada camada importa na defesa em profundidade da sua aplicação.</p>
<h2>SBOM: Inventariando sua Stack de Software</h2>
<h3>O que é SBOM e por que você precisa</h3>
<p>Um <strong>SBOM (Software Bill of Materials)</strong> é um documento estruturado que lista todos os componentes, dependências, versões e licenças utilizadas em um projeto. Pense nele como um "inventário completo" da sua aplicação — semelhante a uma lista de ingredientes em um produto alimentício. Sem essa visibilidade, você não consegue responder perguntas críticas: "Minha aplicação usa a versão vulnerável da Log4j?" ou "Qual é a cadeia de dependências que leva ao OpenSSL 1.0.2?"</p>
<p>Governos, reguladores (como a NIST nos EUA) e grandes empresas agora <strong>exigem</strong> SBOMs em contratos de software. Mais importante: você não consegue defender o que não consegue ver. Um SBOM bem feito permite detecção automática de vulnerabilidades conhecidas (CVEs) em toda sua stack, não apenas no código principal.</p>
<h3>Formatos: SPDX e CycloneDX</h3>
<p>Existem dois padrões principais: <strong>SPDX</strong> (ISO/IEC 5230) focado em conformidade de licenças, e <strong>CycloneDX</strong> otimizado para segurança de dependências. Para fins práticos de supply chain security, recomendo CycloneDX. Ambos são estruturados em XML ou JSON.</p>
<p>Vamos gerar um SBOM usando <strong>Syft</strong>, a ferramenta mais prática atualmente:</p>
<pre><code class="language-bash"># Instalar Syft (para macOS/Linux)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
Gerar SBOM em formato CycloneDX para uma imagem Docker
syft nginx:latest -o cyclonedx-json > nginx-sbom.json
Gerar SBOM para um repositório local (Go, Python, Node.js, etc)
syft /caminho/para/seu/projeto -o spdx > project-sbom.spdx</code></pre>
<p>Um exemplo simplificado de SBOM em CycloneDX JSON:</p>
<pre><code class="language-json">{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"components": [
{
"type": "library",
"name": "requests",
"version": "2.28.1",
"purl": "pkg:pypi/requests@2.28.1",
"licenses": [
{
"license": {
"name": "Apache-2.0"
}
}
]
},
{
"type": "library",
"name": "urllib3",
"version": "1.26.12",
"purl": "pkg:pypi/urllib3@1.26.12",
"licenses": [
{
"license": {
"name": "MIT"
}
}
]
}
]
}</code></pre>
<h3>Integrando SBOM na Pipeline CI/CD</h3>
<p>A geração de SBOM deve ser <strong>automática</strong> e <strong>obrigatória</strong> em cada build. Aqui está um exemplo com GitHub Actions:</p>
<pre><code class="language-yaml">name: Build e SBOM
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build imagem Docker
run: docker build -t myapp:${{ github.sha }} .
- name: Instalar Syft
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- name: Gerar SBOM
run: |
syft myapp:${{ github.sha }} \
-o cyclonedx-json \
> sbom-${{ github.sha }}.json
- name: Fazer upload do SBOM
uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom-*.json
- name: Verificar vulnerabilidades conhecidas
run: syft myapp:${{ github.sha }} | grype</code></pre>
<p>Note que usamos <strong>Grype</strong> (também da Anchore) para verificar vulnerabilidades no SBOM — isso é segurança <em>automatizada</em>.</p>
<h2>Cosign: Assinando e Verificando Imagens Docker</h2>
<h3>Criptografia de Confiança: Por que assinar imagens</h3>
<p>Toda imagem Docker publicada em um registro (Docker Hub, ECR, Harbor) pode ser baixada por qualquer pessoa e executada. Sem assinatura criptográfica, não há garantia de autenticidade. Um ataque <strong>Man-in-the-Middle (MITM)</strong> poderia interceptar sua imagem e substituir por uma maliciosa. <strong>Cosign</strong> resolve isso usando criptografia assimétrica: você assina a imagem com sua chave privada, e qualquer pessoa verifica com sua chave pública.</p>
<p>Cosign é a ferramenta recomendada pela Linux Foundation e funciona perfeitamente com registros OCI (Docker, ECR, GCR, etc).</p>
<h3>Instalação e Configuração de Chaves</h3>
<pre><code class="language-bash"># Instalar Cosign (requer Go 1.16+)
wget https://github.com/sigstore/cosign/releases/download/v2.0.0/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
Gerar par de chaves (será solicitada uma senha)
cosign generate-key-pair
Cria: cosign.key (privada) e cosign.pub (pública)
Exportar a chave pública em formato PEM para compartilhar
cat cosign.pub</code></pre>
<h3>Assinando uma Imagem Docker</h3>
<pre><code class="language-bash"># Fazer login no registro (Docker Hub, ECR, etc)
docker login -u seu_usuario
Build e push da imagem
docker build -t seu_usuario/myapp:v1.0.0 .
docker push seu_usuario/myapp:v1.0.0
Assinar a imagem (será solicitada a senha da chave privada)
cosign sign --key cosign.key seu_usuario/myapp:v1.0.0</code></pre>
<p>A assinatura é armazenada como uma imagem separada no registro (exemplo: <code>seu_usuario/myapp:v1.0.0.sig</code>). Você pode verificar os metadados assinados:</p>
<pre><code class="language-bash"># Verificar a assinatura (usa a chave pública automaticamente)
cosign verify --key cosign.pub seu_usuario/myapp:v1.0.0</code></pre>
<p>Saída esperada:</p>
<pre><code class="language-json">[{"critical":{"identity":{"docker-reference":"seu_usuario/myapp"},...}}]</code></pre>
<h3>Assinando com Keyless (Sigstore)</h3>
<p>A forma mais moderna é usar <strong>Keyless Signing</strong> via Sigstore, eliminando a necessidade de gerenciar chaves privadas localmente:</p>
<pre><code class="language-bash"># Login com sua conta do GitHub/Google
cosign login ghcr.io
Assinar sem chave local (usa OIDC do GitHub)
COSIGN_EXPERIMENTAL=1 cosign sign ghcr.io/seu_usuario/myapp:v1.0.0
Verificar sem chave pública explícita
COSIGN_EXPERIMENTAL=1 cosign verify ghcr.io/seu_usuario/myapp:v1.0.0</code></pre>
<p>Internamente, Cosign usa certificados OIDC do provedor (GitHub) e Rekor (um registro de transparência) para auditoria imutável.</p>
<h2>Verificação de Imagens: Policy-as-Code com Kyverno</h2>
<h3>Por que apenas assinar não é suficiente</h3>
<p>Você pode assinar todas as imagens, mas se seu cluster Kubernetes aceitasse uma imagem <strong>não assinada</strong>, o atacante poderia simplesmente fazer bypass da assinatura. Você precisa de <strong>policy enforcement</strong> automático — um sistema que <em>rejeite</em> qualquer pod que use uma imagem não assinada ou não confiável.</p>
<p><strong>Kyverno</strong> é um admission controller Kubernetes que funciona como um firewall de imagens. Ele verifica políticas antes de qualquer container ser executado.</p>
<h3>Instalando Kyverno</h3>
<pre><code class="language-bash"># Instalar Kyverno no cluster
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno --namespace kyverno --create-namespace
Aguardar o deployment
kubectl rollout status deployment/kyverno-admission-controller -n kyverno</code></pre>
<h3>Policy: Requerer Imagens Assinadas</h3>
<pre><code class="language-yaml">apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
namespace: kyverno
spec:
validationFailureAction: enforce # audit = logging apenas, enforce = bloqueia
rules:
- name: verify-image-signature
match:
resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "seu_usuario/*"
- "gcr.io/seu-projeto/*"
attestors:
- count: 1
entries:
- keys:
publicKeys: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
signatureAlgorithm: sha256</code></pre>
<h3>Testando a Policy</h3>
<pre><code class="language-bash"># Criar um pod com imagem assinada (vai passar)
kubectl run test-signed --image=seu_usuario/myapp:v1.0.0 \
--dry-run=server
Tentar criar pod com imagem não assinada (vai falhar)
kubectl run test-unsigned --image=nginx:latest \
--dry-run=server
Error: admission webhook "mutate.kyverno.io" denied the request</code></pre>
<h3>Policy Avançada: Verificar SBOM Anexado</h3>
<p>Cosign pode anexar SBOMs à imagem e Kyverno pode verificá-los:</p>
<pre><code class="language-bash"># Anexar SBOM à imagem
cosign attach sbom --sbom sbom-v1.0.0.json seu_usuario/myapp:v1.0.0
Verificar o SBOM anexado
cosign download sbom seu_usuario/myapp:v1.0.0</code></pre>
<p>Uma policy que verifica presença de SBOM:</p>
<pre><code class="language-yaml">apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-sbom
spec:
validationFailureAction: enforce
rules:
- name: verify-sbom-exists
match:
resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "*"
attestations:
- name: sbom
predicateType: https://cyclonedx.org/schema/ext/vulnerability/4.0/vulnerability.json
conditions:
- all:
- key: "{{ components[0].name }}"
operator: Pattern
value: "?*" # verifica se existe ao menos 1 componente</code></pre>
<h2>Integrando Tudo: Pipeline End-to-End</h2>
<p>Uma pipeline real deve:</p>
<ol>
<li><strong>Gerar SBOM</strong> automaticamente no build</li>
<li><strong>Verificar vulnerabilidades</strong> do SBOM contra bancos de CVE</li>
<li><strong>Assinar a imagem</strong> com Cosign</li>
<li><strong>Anexar SBOM e atestações</strong> à imagem</li>
<li><strong>Verificar assinatura</strong> no cluster com Kyverno</li>
</ol>
<p>Aqui está um exemplo completo com GitHub Actions + Kyverno:</p>
<pre><code class="language-yaml">name: Secure Build Pipeline
on:
push:
tags:
- 'v*'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
secure-build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: actions/checkout@v3
- name: Setup Cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v2.0.0'
- name: Setup Syft
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | \
sh -s -- -b /usr/local/bin
- name: Build imagem Docker
run: |
docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} .
- name: Gerar SBOM
run: |
syft ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} \
-o cyclonedx-json \
> sbom.json
- name: Verificar vulnerabilidades
run: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | \
sh -s -- -b /usr/local/bin
grype ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }} \
--fail-on high
- name: Fazer login no GHCR
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push imagem
run: |
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
- name: Assinar imagem com Cosign (Keyless)
run: |
cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
env:
COSIGN_EXPERIMENTAL: 1
- name: Anexar SBOM à imagem
run: |
cosign attach sbom --sbom sbom.json \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
env:
COSIGN_EXPERIMENTAL: 1
- name: Gerar atestação de build (provenance)
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true</code></pre>
<p>No cluster Kubernetes, a policy Kyverno automaticamente bloqueará qualquer imagem não assinada ou vulnerável.</p>
<h2>Conclusão</h2>
<p>Os três pilares da segurança supply chain que aprendemos — <strong>SBOM para visibilidade</strong>, <strong>Cosign para autenticidade</strong> e <strong>Kyverno para enforcement</strong> — formam uma defesa em profundidade que detém ataques em múltiplas camadas. Primeiro, você conhece exatamente o que está rodando (SBOM). Segundo, você garante que ninguém alterou sua imagem no caminho (Cosign). Terceiro, você rejeita qualquer coisa suspeita automaticamente no cluster (Kyverno). Essa abordagem transforma segurança de um problema reativo para proativo — você não espera ser hacked, você <em>previne</em> que seja hacked. A automatização completa dessa pipeline via CI/CD é o que separa equipes defensoras de equipes vulneráveis no mundo atual.</p>
<h2>Referências</h2>
<p>- <a href="https://csrc.nist.gov/publications/detail/sp/800-53/rev-5" target="_blank" rel="noopener noreferrer">SBOM Requirements | NIST (EO 14028)</a> - <a href="https://docs.sigstore.dev/cosign/overview/" target="_blank" rel="noopener noreferrer">Cosign Documentation | Sigstore Project</a></p>
<ul>
<li><a href="https://kyverno.io/docs/" target="_blank" rel="noopener noreferrer">Kyverno - Kubernetes Native Policy Management</a></li>
<li><a href="https://cyclonedx.org/specification/" target="_blank" rel="noopener noreferrer">CycloneDX Specification</a></li>
<li><a href="https://github.com/anchore/syft" target="_blank" rel="noopener noreferrer">Syft - SBOM Generator by Anchore</a></li>
</ul>
<p><!-- FIM --></p>