Pipeline CI/CD e Segurança
Nesta aula, exploramos como integrar segurança em pipelines de CI/CD, cobrindo o gerenciamento seguro de secrets, a proteção de artefatos, e a implementação de testes de segurança estáticos (SAST) e dinâmicos (DAST). Aprenda a identificar vulnerabilidades em cada etapa do pipeline e adotar boas práticas para evitar vazamentos e ataques.
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
- GitHub Actions - Encrypted secrets
- GitLab CI/CD Variables
- HashiCorp Vault Documentation
- AWS Secrets Manager User Guide
- Sigstore Documentation
- Docker Scout
- Trivy
- Semgrep Documentation
- OWASP ZAP
Exercícios
- 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. - Qual a diferença entre SAST e DAST? Em que fase do pipeline cada um é mais adequado?
- 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.
- Cite três boas práticas para proteger artefatos em um pipeline de CI/CD.
- Explique como o princípio do menor privilégio se aplica ao gerenciamento de secrets em um pipeline. Dê um exemplo concreto.
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
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-auditouOWASP Dependency-Checkpara 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.