A segurança de integridade de software é um pilar fundamental para garantir que o código e os artefatos que você produz não foram modificados de forma maliciosa ou acidental durante o ciclo de vida do desenvolvimento. Em um mundo onde ataques à cadeia de suprimentos de software estão se tornando cada vez mais comuns, é essencial adotar mecanismos que assegurem que o software que você entrega é exatamente o que foi construído e aprovado. Nesta aula, vamos explorar os conceitos de assinatura digital, verificação, pipelines de build seguros e atestação, que juntos formam uma camada robusta de proteção contra adulteração e comprometimento.

Imagine que você baixa um aplicativo de um site oficial. Como saber se o arquivo que você recebeu é genuíno e não foi substituído por um malware? A resposta está na assinatura digital e na verificação. Da mesma forma, em pipelines de CI/CD, cada etapa do build deve ser verificada e atestada para garantir que o artefato final é íntegro. Vamos mergulhar em cada um desses tópicos, com exemplos práticos e boas práticas para implementar em seus projetos.

Signing

Assinatura digital (signing) é o processo de aplicar uma chave criptográfica a um artefato (como um binário, um pacote, ou até mesmo um commit) para garantir sua autenticidade e integridade. A assinatura é feita com uma chave privada, e a verificação é feita com a chave pública correspondente. Se qualquer parte do artefato for alterada após a assinatura, a verificação falhará, indicando que o arquivo foi adulterado.

Existem várias ferramentas para assinar artefatos, como GPG (GNU Privacy Guard) para assinar commits e tags, e ferramentas específicas como `cosign` para assinar imagens de contêiner. A assinatura pode ser aplicada em diferentes níveis: no código-fonte, nos artefatos compilados, ou até mesmo nos metadados. No contexto de integridade de software, o importante é que a assinatura seja feita por uma entidade confiável (como um mantenedor ou um sistema de build) e que a chave privada seja protegida adequadamente.

Exemplo de assinatura de um arquivo com GPG:

# Gerar uma chave GPG (se não tiver)
gpg --full-generate-key

# Assinar um arquivo
gpg --detach-sign --armor arquivo.txt

# Isso gera um arquivo .asc com a assinatura

Para assinar uma imagem de contêiner com cosign:

# Instalar cosign e configurar as credenciais
cosign sign --key cosign.key minha-imagem:latest

Boas práticas de assinatura incluem usar chaves separadas para diferentes propósitos, armazenar chaves privadas em hardware seguro (como YubiKey) ou em cofres de chaves, e nunca compartilhar chaves privadas. Além disso, é importante assinar todos os artefatos relevantes, não apenas o binário final, mas também as fontes e os manifestos.

Verification

Verificação é o processo de confirmar que a assinatura de um artefato é válida e que o conteúdo não foi alterado. Isso é feito usando a chave pública do assinante. A verificação pode ser feita manualmente (por exemplo, verificando um commit assinado) ou automaticamente em pipelines de deploy, onde cada artefato é verificado antes de ser implantado.

Ferramentas como `gpg --verify`, `cosign verify` e `slsa-verifier` são comuns. A verificação garante que o artefato veio de uma fonte confiável e que não foi modificado entre a assinatura e o consumo. Sem verificação, a assinatura é inútil, pois não há como saber se a chave pública usada é a correta ou se o artefato foi substituído.

Exemplo de verificação de um arquivo assinado com GPG:

# Verificar a assinatura
gpg --verify arquivo.txt.asc arquivo.txt

Exemplo de verificação de uma imagem de contêiner com cosign:

cosign verify --key cosign.pub minha-imagem:latest

Em pipelines, é crucial verificar a assinatura dos artefatos antes de usá-los. Isso pode ser feito em um estágio separado do pipeline, garantindo que apenas artefatos assinados e verificados sejam promovidos para produção. Além disso, a verificação deve ser feita com uma chave pública confiável, que deve ser gerenciada com cuidado para evitar que atacantes substituam a chave pública por uma maliciosa.

Build pipelines

Build pipelines são os processos automatizados que compilam, testam e empacotam o código-fonte em artefatos prontos para distribuição. A segurança desses pipelines é crítica, pois se um atacante conseguir comprometer o pipeline, ele pode injetar código malicioso em todos os artefatos produzidos. Portanto, é essencial implementar práticas de segurança em cada etapa do pipeline.

Algumas práticas recomendadas incluem: usar ambientes isolados e imutáveis para builds, garantir que as dependências sejam baixadas de fontes confiáveis e verificadas, usar segredos (como chaves de assinatura) armazenados em cofres (como HashiCorp Vault) e acessíveis apenas a tarefas autorizadas, e aplicar controle de acesso rigoroso ao pipeline. Além disso, é importante registrar logs de todas as atividades e monitorar o pipeline para detectar anomalias.

Um exemplo de pipeline de build seguro usando GitHub Actions:

name: Build and Sign
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: make build
      - name: Sign artifact
        run: |
          echo "${{ secrets.SIGNING_KEY }}" | gpg --import
          gpg --detach-sign --armor dist/meu-app
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: meu-app
          path: dist/

Note que a chave de assinatura é armazenada como um segredo no GitHub Actions, e o pipeline importa a chave apenas durante o passo de assinatura. Isso reduz a exposição da chave. Além disso, o pipeline deve ser configurado para falhar se a assinatura falhar, evitando que artefatos não assinados sejam publicados.

Attestation

Atestação (attestation) é o processo de fornecer uma declaração verificável sobre a origem e as características de um artefato. Enquanto a assinatura garante a integridade e autenticidade do artefato em si, a atestação fornece informações adicionais sobre como o artefato foi construído, quais materiais foram usados, quem o construiu, etc. Isso é fundamental para a conformidade e para a confiança na cadeia de suprimentos de software.

O padrão SLSA (Supply-chain Levels for Software Artifacts) define níveis de atestação, que vão desde o nível 1 (documentação básica) até o nível 4 (atestações completas e verificáveis). Ferramentas como `in-toto` e `sigstore` (com `cosign` e `rekor`) são usadas para criar e verificar atestações. As atestações são frequentemente armazenadas em um log transparente (como o Rekor) para que possam ser auditadas publicamente.

Exemplo de atestação com cosign e Rekor:

# Criar uma atestação (por exemplo, um documento JSON descrevendo o build)
cosign attest --predicate build.json --key cosign.key minha-imagem:latest

# Verificar a atestação
cosign verify-attestation --key cosign.pub minha-imagem:latest

As atestações podem incluir informações como o repositório de origem, o commit hash, o nome do pipeline, as dependências usadas, e até mesmo a política de segurança aplicada. Isso permite que os consumidores do artefato tomem decisões informadas sobre se devem confiar nele. Por exemplo, um sistema de implantação pode exigir que a atestação indique que o build foi feito a partir de um repositório aprovado e com uma política de segurança específica.

Boas Práticas e Observações Finais

Para implementar uma estratégia robusta de integridade de software, considere as seguintes boas práticas:

  • Assine tudo: Assine não apenas os binários finais, mas também as imagens de contêiner, os pacotes de dependências e os metadados.
  • Verifique continuamente: Integre a verificação de assinatura em seus pipelines de CI/CD e em seus processos de implantação.
  • Use atestações: Adote padrões como SLSA e ferramentas como sigstore para fornecer atestações verificáveis sobre seus artefatos.
  • Proteja as chaves: Armazene chaves privadas em hardware seguro ou cofres de chaves, e use segredos em pipelines para evitar exposição.
  • Monitore e audite: Registre logs de todas as operações de assinatura, verificação e atestação, e monitore-os para detectar atividades suspeitas.

Lembre-se de que a segurança de integridade é um processo contínuo, e você deve revisar e atualizar suas práticas regularmente para se manter à frente das ameaças.

Exercícios

  1. Explique a diferença entre assinatura digital e verificação. Dê um exemplo de cada.
  2. ✓ Resposta: Assinatura digital é o processo de aplicar uma chave privada a um artefato para garantir autenticidade e integridade. Verificação é o processo de usar a chave pública para confirmar que a assinatura é válida e que o conteúdo não foi alterado. Exemplo de assinatura: usar GPG para assinar um arquivo. Exemplo de verificação: usar `gpg --verify` para confirmar a assinatura.
  3. Por que é importante proteger as chaves privadas de assinatura? Cite duas práticas recomendadas.
  4. ✓ Resposta: As chaves privadas são usadas para assinar artefatos; se comprometidas, um atacante pode assinar malware como se fosse você. Práticas recomendadas: armazenar em hardware seguro (ex.: YubiKey) ou cofres de chaves (ex.: HashiCorp Vault), e usar segredos em pipelines para nunca expor a chave em texto claro.
  5. Descreva duas medidas de segurança que você pode implementar em um pipeline de build para garantir a integridade dos artefatos.
  6. ✓ Resposta: Exemplos: (1) Usar ambientes isolados e imutáveis para o build, evitando contaminação; (2) Armazenar segredos (como chaves de assinatura) em um cofre e injetá-los apenas nos passos necessários, além de verificar a assinatura das dependências antes de usá-las.
  7. O que é atestação e como ela difere da assinatura? Dê um exemplo de uso.
  8. ✓ Resposta: Atestação é uma declaração verificável sobre a origem e características de um artefato, incluindo como foi construído, por quem, e com quais materiais. A assinatura apenas garante integridade e autenticidade do artefato em si. Exemplo: usar cosign para criar uma atestação SLSA que descreve o repositório e o commit de origem, e verificar essa atestação antes de implantar.
  9. Qual é o papel do padrão SLSA na segurança de integridade de software? Cite dois níveis e suas exigências.
  10. ✓ Resposta: SLSA define níveis de garantia para a cadeia de suprimentos de software. Nível 1 exige documentação básica do processo de build; Nível 2 exige que o build seja gerado por um serviço de hospedagem e que os artefatos sejam assinados; Nível 3 exige que o build seja reproduzível e que as atestações sejam geradas; Nível 4 exige atestação completa e verificável com um repositório de metadados.

Referências