Docker & Kubernetes

Supply Chain Security: SBOM, Cosign e Verificação de Imagens: Do Básico ao Avançado

13 min de leitura

Supply Chain Security: SBOM, Cosign e Verificação de Imagens: Do Básico ao Avançado

Supply Chain Security: Protegendo sua Pipeline de Software 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: SBOM (Software Bill of Materials), Cosign para assinatura e verificação, e as práticas de verificação de imagens Docker. Você aprenderá não apenas o quê fazer, mas por quê cada camada importa na defesa em profundidade da sua aplicação. SBOM: Inventariando sua Stack de Software O que é SBOM e por que você precisa Um SBOM (Software Bill of Materials) é 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

<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 &quot;inventário completo&quot; 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: &quot;Minha aplicação usa a versão vulnerável da Log4j?&quot; ou &quot;Qual é a cadeia de dependências que leva ao OpenSSL 1.0.2?&quot;</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 &gt; nginx-sbom.json

Gerar SBOM para um repositório local (Go, Python, Node.js, etc)

syft /caminho/para/seu/projeto -o spdx &gt; project-sbom.spdx</code></pre>

<p>Um exemplo simplificado de SBOM em CycloneDX JSON:</p>

<pre><code class="language-json">{

&quot;bomFormat&quot;: &quot;CycloneDX&quot;,

&quot;specVersion&quot;: &quot;1.4&quot;,

&quot;version&quot;: 1,

&quot;components&quot;: [

{

&quot;type&quot;: &quot;library&quot;,

&quot;name&quot;: &quot;requests&quot;,

&quot;version&quot;: &quot;2.28.1&quot;,

&quot;purl&quot;: &quot;pkg:pypi/requests@2.28.1&quot;,

&quot;licenses&quot;: [

{

&quot;license&quot;: {

&quot;name&quot;: &quot;Apache-2.0&quot;

}

}

]

},

{

&quot;type&quot;: &quot;library&quot;,

&quot;name&quot;: &quot;urllib3&quot;,

&quot;version&quot;: &quot;1.26.12&quot;,

&quot;purl&quot;: &quot;pkg:pypi/urllib3@1.26.12&quot;,

&quot;licenses&quot;: [

{

&quot;license&quot;: {

&quot;name&quot;: &quot;MIT&quot;

}

}

]

}

]

}</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 \

&gt; 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">[{&quot;critical&quot;:{&quot;identity&quot;:{&quot;docker-reference&quot;:&quot;seu_usuario/myapp&quot;},...}}]</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:
  • &quot;seu_usuario/*&quot;
  • &quot;gcr.io/seu-projeto/*&quot;

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 &quot;mutate.kyverno.io&quot; 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:
  • &quot;*&quot;

attestations:

  • name: sbom

predicateType: https://cyclonedx.org/schema/ext/vulnerability/4.0/vulnerability.json

conditions:

  • all:
  • key: &quot;{{ components[0].name }}&quot;

operator: Pattern

value: &quot;?*&quot; # 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:

  • &#039;v*&#039;

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: &#039;v2.0.0&#039;

  • 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 \

&gt; 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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

Como Usar Chaos Engineering em Kubernetes: LitmusChaos e Testes de Resiliência em Produção
Como Usar Chaos Engineering em Kubernetes: LitmusChaos e Testes de Resiliência em Produção

O que é Chaos Engineering e por que é Crítico em Kubernetes Chaos Engineering...

Prometheus em Kubernetes: Operator, ServiceMonitor e PromQL Avançado na Prática
Prometheus em Kubernetes: Operator, ServiceMonitor e PromQL Avançado na Prática

Introdução ao Prometheus em Kubernetes Prometheus é uma solução open-source d...

O que Todo Dev Deve Saber sobre Docker Multi-stage Builds: Reduzindo Imagens de GB para MB na Prática
O que Todo Dev Deve Saber sobre Docker Multi-stage Builds: Reduzindo Imagens de GB para MB na Prática

O Problema: Por Que Nossas Imagens Docker São Tão Grandes? Quando você constr...