Em um mundo de desenvolvimento acelerado, os pipelines de CI/CD (Integração Contínua/Entrega Contínua) são o coração da engenharia de software moderna. Eles automatizam a construção, o teste e a implantação de aplicações, permitindo que equipes entreguem valor com frequência. No entanto, essa automação também introduz novos vetores de ataque: o próprio pipeline pode ser um alvo. Se um atacante conseguir comprometer o pipeline, ele pode injetar código malicioso, roubar credenciais ou adulterar artefatos que serão implantados em produção. Portanto, a segurança do pipeline é tão crítica quanto a segurança do código-fonte.

Nesta aula, vamos mergulhar em quatro pilares essenciais para proteger seu pipeline: o gerenciamento seguro de secrets, a integridade dos artefatos, e as práticas de teste de segurança estática (SAST) e dinâmica (DAST). Você aprenderá não apenas os conceitos, mas também como implementá-los em cenários práticos, com exemplos de código e configurações. Ao final, terá uma visão clara de como construir um pipeline seguro, evitando as armadilhas mais comuns e adotando uma postura proativa contra ameaças.

Secrets no pipeline

Secrets são informações sensíveis como chaves de API, senhas, tokens de acesso e certificados. Em um pipeline de CI/CD, você frequentemente precisa autenticar-se em serviços externos (registries de contêineres, provedores de nuvem, sistemas de terceiros) e, portanto, precisa armazenar e usar esses secrets. No entanto, se forem tratados de forma inadequada, podem vazar para logs, repositórios ou para o artefato final, comprometendo a segurança.

O princípio fundamental é: nunca coloque secrets diretamente no código ou nos arquivos de configuração versionados. Em vez disso, use um gerenciador de secrets dedicado, como o GitHub Secrets, GitLab CI Variables, HashiCorp Vault ou serviços gerenciados como AWS Secrets Manager. Essas ferramentas permitem que você armazene secrets de forma criptografada e os injete no ambiente de execução do pipeline apenas quando necessário.

Além disso, é crucial garantir que os secrets não sejam expostos em logs. Muitas ferramentas de CI/CD têm máscaras automáticas para secrets conhecidos, mas você deve ter cuidado ao imprimir variáveis de ambiente ou saídas de comandos. Uma prática comum é usar comandos que não ecoam os valores, como echo "$SECRET" | some-command em vez de echo $SECRET sem redirecionamento. Também é recomendável usar ferramentas de varredura de secrets, como gitleaks ou trufflehog, para detectar secrets que possam ter sido commitados acidentalmente.

Exemplo de como injetar secrets em um pipeline GitHub Actions:

name: CI
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test
        env:
          API_KEY: ${{ secrets.API_KEY }}

Neste exemplo, o secret API_KEY é definido nas configurações do repositório e é injetado como uma variável de ambiente somente no passo de teste. Ele não aparece no código nem nos logs, e o GitHub Actions o mascara automaticamente nos logs se for impresso.

Outra prática importante é a rotação regular de secrets e a aplicação do princípio do menor privilégio: conceda apenas as permissões necessárias para cada secret. Por exemplo, se um secret é usado apenas para leitura de um bucket S3, não dê permissões de escrita. Além disso, considere usar autenticação temporária (por exemplo, tokens de curta duração) quando possível, para reduzir o impacto de um vazamento.

Artefatos

Artefatos são os produtos gerados pelo pipeline, como binários compilados, pacotes de contêineres, arquivos de distribuição ou relatórios de teste. Eles são frequentemente armazenados em registries ou sistemas de armazenamento e podem ser baixados e executados em ambientes de produção. Se um atacante conseguir adulterar um artefato durante o pipeline, ele pode inserir código malicioso que será implantado.

Para proteger os artefatos, devemos garantir sua integridade e autenticidade. A integridade garante que o artefato não foi alterado desde a sua criação; a autenticidade garante que ele veio de uma fonte confiável (ou seja, do seu pipeline). A principal técnica é a assinatura digital dos artefatos. Ferramentas como Sigstore (para contêineres e software) ou GPG podem ser usadas para assinar artefatos, e os consumidores podem verificar a assinatura antes de usar o artefato.

Além disso, é importante armazenar artefatos em locais seguros com controle de acesso, como um registry privado (por exemplo, GitHub Container Registry, Docker Hub privado, Nexus, Artifactory). Use autenticação e autorização para impedir acesso não autorizado. Também é recomendável aplicar políticas de retenção e varredura de vulnerabilidades em imagens de contêineres, como Docker Scout ou Trivy, que verificam dependências e sistemas operacionais em busca de CVEs conhecidas.

Exemplo de assinatura de imagem de contêiner com cosign (parte do Sigstore):

# Build da imagem
docker build -t myapp:latest .

# Assinar a imagem
cosign sign --key cosign.key myuser/myapp:latest

# Verificar a assinatura
cosign verify --key cosign.pub myuser/myapp:latest

No pipeline, você pode integrar esses comandos para assinar automaticamente cada imagem construída. Assim, qualquer pessoa que baixar a imagem pode verificar sua origem e integridade.

Outra consideração é o versionamento e a imutabilidade dos artefatos. Use tags imutáveis (por exemplo, o hash SHA256 da imagem) em vez de tags mutáveis como latest, para evitar que um artefato seja sobrescrito por um não aprovado. Além disso, mantenha um registro de auditoria de quem gerou cada artefato e quando.

SAST

SAST (Static Application Security Testing) é uma técnica que analisa o código-fonte ou os binários em busca de vulnerabilidades sem executar o programa. Ele identifica problemas como injeção de SQL, cross-site scripting (XSS), uso inseguro de criptografia, vazamento de informações, entre outros. No contexto de CI/CD, o SAST é executado como uma etapa do pipeline, geralmente após a compilação ou até mesmo antes, para fornecer feedback rápido aos desenvolvedores.

A principal vantagem do SAST é que ele pode ser executado cedo no ciclo de desenvolvimento, quando os erros são mais baratos de corrigir. No entanto, ele pode gerar falsos positivos e nem sempre detecta vulnerabilidades que surgem apenas em tempo de execução. Por isso, é importante configurar as ferramentas adequadamente e revisar os resultados periodicamente.

Ferramentas populares de SAST incluem Semgrep, SonarQube, Checkmarx, Gosec para Go, Bandit para Python, e ESLint com plugins de segurança para JavaScript. Muitas delas se integram facilmente com GitHub Actions, GitLab CI e outros.

Exemplo de etapa SAST em um pipeline usando Semgrep:

# .github/workflows/sast.yml
name: SAST
on: [push, pull_request]
jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run Semgrep
        run: semgrep scan --config=auto --json --output=semgrep-results.json
      - name: Upload results
        uses: actions/upload-artifact@v4
        with:
          name: semgrep-results
          path: semgrep-results.json

Neste exemplo, o Semgrep é executado com uma configuração automática (que inclui regras padrão) e o resultado em JSON é armazenado como artefato. Você pode então analisar os resultados ou falhar o pipeline se houver vulnerabilidades críticas.

É importante ressaltar que o SAST não substitui a revisão humana de código, mas é uma camada adicional de segurança. Incorpore-o como parte do pipeline, mas também incentive os desenvolvedores a usarem as ferramentas localmente antes do push.

DAST

DAST (Dynamic Application Security Testing) é uma técnica que testa a aplicação em execução, simulando ataques externos. Ele envia requisições maliciosas e analisa as respostas para identificar vulnerabilidades como injeção, quebras de autenticação, exposição de dados sensíveis, etc. No pipeline, o DAST é geralmente executado em um ambiente de staging ou de teste, após a aplicação ser implantada.

O DAST complementa o SAST, pois testa a aplicação de fora para dentro, considerando a configuração real, a interação entre componentes e as dependências externas. No entanto, ele requer um ambiente funcional e pode ser mais lento e complexo de configurar. Ferramentas populares incluem OWASP ZAP, Burp Suite, Acunetix e StackHawk.

No pipeline, você pode iniciar o DAST após o deploy em um ambiente de teste. Por exemplo, com OWASP ZAP, você pode usar a automação via linha de comando ou o plugin para CI/CD. Um exemplo simples:

# Executar ZAP em modo container para testar uma URL
# Supondo que a aplicação esteja rodando em http://staging.example.com
docker run -t owasp/zap2docker-stable zap-baseline.py -t http://staging.example.com -r report.html

Este comando executa o ZAP baseline (que faz uma varredura rápida) e gera um relatório HTML. Você pode então publicar esse relatório como artefato ou integrar os resultados em um painel.

Uma prática recomendada é executar o DAST em cada push ou pelo menos em pull requests, para detectar regressões de segurança. No entanto, lembre-se de que o DAST pode gerar muitos falsos positivos e exigir ajustes. É importante definir um limite de severidade para falhar o pipeline (por exemplo, falhar se houver vulnerabilidades de alta severidade).

Referências

Exercícios

  1. Explique por que é perigoso colocar secrets em variáveis de ambiente diretamente no arquivo de configuração do pipeline (ex.: env: PASSWORD: "1234"). Cite pelo menos dois riscos.
  2. ✓ Resposta: Colocar secrets diretamente no arquivo de configuração do pipeline é perigoso porque: 1) O arquivo fica versionado no repositório, então qualquer pessoa com acesso ao repositório (ou a um fork) pode ver o secret; 2) Os logs do pipeline podem exibir o valor do secret se ele for impresso, e muitas ferramentas não mascaram valores que não foram declarados como secrets; 3) O secret fica exposto em histórico de commits, mesmo que seja removido depois, pois o histórico é imutável. Isso pode levar a vazamento de credenciais para atacantes ou para pessoas não autorizadas.
  3. Qual a diferença entre SAST e DAST? Em que fase do pipeline cada um é mais adequado?
  4. ✓ Resposta: SAST analisa o código-fonte ou binários sem executar o programa, procurando padrões de vulnerabilidades. Ele é mais adequado nas fases iniciais do pipeline, como após o checkout ou durante a compilação, pois fornece feedback rápido aos desenvolvedores. DAST testa a aplicação em execução, simulando ataques externos. Ele é mais adequado após o deploy em um ambiente de teste, pois requer a aplicação rodando. SAST detecta problemas estáticos, enquanto DAST detecta vulnerabilidades que só aparecem em tempo de execução.
  5. Escreva um trecho de configuração de um pipeline (use YAML) que execute uma etapa SAST com Semgrep e uma etapa DAST com OWASP ZAP, em jobs separados.
  6. ✓ Resposta: Exemplo de configuração em GitHub Actions:
    name: Security Pipeline
    on: [push]
    jobs:
      sast:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Run Semgrep
            run: semgrep scan --config=auto --json --output=semgrep-results.json
      dast:
        runs-on: ubuntu-latest
        needs: [sast]  # opcional, pode rodar em paralelo
        steps:
          - name: Deploy to staging
            run: echo "Deploying to staging..."
          - name: Run OWASP ZAP
            run: |
              docker run -t owasp/zap2docker-stable zap-baseline.py -t http://staging.example.com -r report.html
          - name: Upload report
            uses: actions/upload-artifact@v4
            with:
              name: zap-report
              path: report.html
    
  7. Cite três boas práticas para proteger artefatos em um pipeline de CI/CD.
  8. ✓ Resposta: Três boas práticas são: 1) Assinar digitalmente os artefatos (ex.: com cosign) para garantir autenticidade e integridade; 2) Armazenar artefatos em repositórios seguros com controle de acesso e autenticação, como um registry privado; 3) Realizar varredura de vulnerabilidades nos artefatos (ex.: com Trivy ou Docker Scout) antes de publicá-los, para evitar que vulnerabilidades conhecidas sejam implantadas.
  9. Explique como o princípio do menor privilégio se aplica ao gerenciamento de secrets em um pipeline. Dê um exemplo concreto.
  10. ✓ Resposta: O princípio do menor privilégio significa que cada secret deve ter apenas as permissões mínimas necessárias para a tarefa. Por exemplo, se um job precisa apenas ler de um bucket S3, o secret (credenciais AWS) deve ter uma política que permita somente a ação s3:GetObject naquele bucket específico, e não permissões de escrita ou acesso a outros recursos. Isso limita o dano caso o secret seja comprometido, pois o atacante não poderá usá-lo para modificar dados ou acessar outros serviços.

Boas práticas e observações finais

Além dos tópicos abordados, aqui estão algumas recomendações adicionais para reforçar a segurança do seu pipeline:

  • Audite dependências: Use ferramentas como npm audit, pip-audit ou OWASP Dependency-Check para identificar vulnerabilidades em bibliotecas de terceiros.
  • Isolamento de ambientes: Execute stages do pipeline em ambientes isolados (por exemplo, contêineres efêmeros) para limitar o impacto de uma execução maliciosa.
  • Proteja o próprio pipeline: Use controle de acesso rigoroso em seus repositórios e pipelines, e evite executar ações de terceiros sem revisão.
  • Monitore e registre: Mantenha logs detalhados do pipeline e monitore atividades anômalas.
  • Atualize regularmente: Mantenha as ferramentas de segurança e as imagens de construção atualizadas com as últimas correções de segurança.

Lembre-se de que a segurança não é um estado final, mas um processo contínuo. Integre essas práticas desde o início do projeto e revise-as periodicamente para se adaptar a novas ameaças e mudanças na arquitetura.