DevOps

DevSecOps: Integrando Segurança em Todo o Pipeline Já leu

16 min de leitura

DevSecOps: Integrando Segurança em Todo o Pipeline
Durante décadas, a segurança foi tratada como uma fase — algo que acontecia depois que o software estava pronto, executado por um time separado que revisava o código antes do release. Esse modelo produziu dois problemas

Durante décadas, a segurança foi tratada como uma fase — algo que acontecia depois que o software estava pronto, executado por um time separado que revisava o código antes do release. Esse modelo produziu dois problemas persistentes: vulnerabilidades encontradas tarde no ciclo de desenvolvimento são ordens de grandeza mais caras de corrigir, e times de segurança operando como gargalhos de aprovação inevitavelmente ficaram para trás da velocidade de entrega que o DevOps trouxe.

O DevSecOps é a resposta a esses dois problemas. O princípio central é deslocar a segurança para a esquerda — shift left — integrando verificações de segurança em cada etapa do ciclo de desenvolvimento, do editor de código ao ambiente de produção. Não como um gargalo, mas como feedback automático que o desenvolvedor recebe no mesmo fluxo em que recebe feedback de testes.

A segurança eficaz em pipelines modernos opera em camadas: código-fonte, dependências, imagens de container, infraestrutura como código, configurações do cluster e comportamento em runtime. Este artigo percorre cada uma dessas camadas com as ferramentas e práticas que as tornam automatizáveis.


Camada 1: Segurança no Código-Fonte

Análise Estática com Semgrep

O Semgrep é um analisador estático de código que detecta padrões de segurança diretamente no código-fonte — injeção SQL, segredos hardcoded, uso de funções inseguras, lógica de autenticação defeituosa. Opera sobre o AST do código, não sobre texto, o que elimina falsos positivos comuns em ferramentas baseadas em regex:

# .github/workflows/segurança-código.yml
name: Análise de Segurança do Código

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  semgrep:
    runs-on: ubuntu-latest
    container:
      image: returntocorp/semgrep:latest

    steps:
      - uses: actions/checkout@v4

      - name: Executa Semgrep
        run: |
          semgrep scan \
            --config=p/nodejs \
            --config=p/javascript \
            --config=p/secrets \
            --config=p/owasp-top-ten \
            --error \
            --json \
            --output=semgrep-results.json \
            --metrics=off \
            .

      - name: Publica resultados como SARIF
        if: always()
        run: |
          semgrep scan \
            --config=p/nodejs \
            --config=p/secrets \
            --sarif \
            --output=semgrep.sarif \
            --metrics=off \
            .

      - name: Upload resultados para GitHub Security
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: semgrep.sarif

Regras customizadas do Semgrep para padrões específicos do projeto:

# .semgrep/regras-customizadas.yml
rules:
  # Detecta queries SQL construídas por concatenação de strings
  - id: sql-injection-concatenacao
    patterns:
      - pattern: |
          $QUERY = "..." + $USER_INPUT
      - pattern-either:
          - pattern: db.query($QUERY, ...)
          - pattern: pool.query($QUERY, ...)
    message: |
      Query SQL construída por concatenação detectada.
      Use queries parametrizadas: db.query('SELECT * FROM users WHERE id = $1', [id])
    languages: [javascript, typescript]
    severity: ERROR

  # Detecta uso de algoritmos de hash inseguros para senhas
  - id: hash-inseguro-para-senha
    patterns:
      - pattern-either:
          - pattern: crypto.createHash('md5')
          - pattern: crypto.createHash('sha1')
          - pattern: crypto.createHash('sha256')
      - pattern-not-inside: |
          # Ignora uso legítimo para checksums de arquivos
          fs.$READ(...)
    message: |
      Algoritmo de hash inseguro para senhas.
      Use bcrypt, argon2 ou scrypt para hashing de senhas.
    languages: [javascript, typescript]
    severity: ERROR

  # Detecta JWT sem verificação de algoritmo
  - id: jwt-algorithm-none
    pattern: |
      jwt.verify($TOKEN, $SECRET, { algorithms: [..., "none", ...] })
    message: |
      O algoritmo "none" no JWT permite tokens não assinados.
      Remova "none" da lista de algoritmos permitidos.
    languages: [javascript, typescript]
    severity: ERROR

  # Detecta logging de informações sensíveis
  - id: log-dados-sensiveis
    patterns:
      - pattern-either:
          - pattern: console.log(..., $REQ.body.password, ...)
          - pattern: logger.info(..., $REQ.body.password, ...)
          - pattern: console.log(..., $REQ.headers.authorization, ...)
    message: |
      Dados sensíveis sendo logados.
      Nunca logue senhas, tokens ou dados de cartão.
    languages: [javascript, typescript]
    severity: WARNING

Detecção de Segredos com Gitleaks

O Gitleaks detecta segredos — chaves de API, tokens, senhas — no histórico do Git, incluindo commits antigos:

# .github/workflows/detectar-segredos.yml
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Histórico completo para escanear todos os commits

      - name: Executa Gitleaks
        uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Configuração para reduzir falsos positivos:

# .gitleaks.toml
title = "Configuração Gitleaks"

[extend]
useDefault = true

[[allowlist]]
description = "Ignora arquivos de teste com dados falsos"
paths = [
  '''tests/fixtures/.*''',
  '''\.env\.example''',
  '''\.env\.test''',
]

[[allowlist]]
description = "Ignora padrões de placeholder"
regexes = [
  '''EXAMPLE_KEY_.*''',
  '''your[-_]api[-_]key''',
  '''<YOUR_.*>''',
  '''TODO.*key''',
]

Camada 2: Segurança em Dependências

Software Composition Analysis com npm audit e Snyk

Aplicações modernas dependem de centenas de pacotes de terceiros. Cada pacote é uma superfície de ataque potencial — e vulnerabilidades em dependências transitivas são frequentemente invisíveis até que causem um incidente.

# No pipeline de CI — verificação de dependências
  dependencias:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Instala dependências
        run: npm ci

      - name: Auditoria de segurança npm
        run: |
          # Falha se houver vulnerabilidades de alta criticidade
          npm audit --audit-level=high --json > audit-results.json || {
            echo "Vulnerabilidades críticas encontradas:"
            cat audit-results.json | jq '.vulnerabilities | to_entries[] | 
              select(.value.severity == "high" or .value.severity == "critical") |
              {pacote: .key, severidade: .value.severity, 
               CVE: .value.via[0].url}'
            exit 1
          }

      - name: Snyk para análise aprofundada
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: >
            --severity-threshold=high
            --fail-on=upgradable
            --sarif-file-output=snyk.sarif

      - name: Upload resultados Snyk
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: snyk.sarif

Renovate: Atualizações Automáticas de Dependências

O Renovate abre pull requests automaticamente quando novas versões de dependências são publicadas, incluindo informações de changelog e resultados de testes:

// renovate.json
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:recommended",
    "security:openssf-scorecard"
  ],
  "schedule": ["before 6am on Monday"],
  "timezone": "America/Sao_Paulo",
  "labels": ["dependências", "automated"],
  "prConcurrentLimit": 10,
  "packageRules": [
    {
      "matchUpdateTypes": ["patch"],
      "automerge": true,
      "automergeType": "pr",
      "platformAutomerge": true
    },
    {
      "matchUpdateTypes": ["major"],
      "dependencyDashboardApproval": true
    },
    {
      "matchPackageNames": ["express", "pg", "redis"],
      "reviewers": ["time-backend"]
    }
  ],
  "vulnerabilityAlerts": {
    "labels": ["segurança", "vulnerabilidade"],
    "automerge": true
  }
}

Camada 3: Segurança em Imagens de Container

Trivy: Scanning de Vulnerabilidades em Imagens

O Trivy escaneia imagens de container em busca de vulnerabilidades no sistema operacional base, bibliotecas de linguagens e configurações inseguras:

# Etapa de scanning no pipeline de build de imagem
  scanning-imagem:
    runs-on: ubuntu-latest
    needs: build

    steps:
      - name: Escaneia imagem com Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ghcr.io/empresa/minha-api:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: '1'
          ignore-unfixed: true

      - name: Upload resultados Trivy
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: trivy-results.sarif

Dockerfile Seguro

# Dockerfile com práticas de segurança integradas

# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app

# Instala dependências antes de copiar o código
# Aproveita cache de camadas do Docker
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

COPY . .
RUN npm run build

# Stage 2: Imagem final mínima
FROM node:20-alpine AS runtime

# Instala apenas o necessário para runtime
RUN apk add --no-cache \
    dumb-init \
    curl \
  && rm -rf /var/cache/apk/*

# Cria usuário não-root
RUN addgroup -g 1001 -S nodejs \
  && adduser -S nodejs -u 1001 -G nodejs

WORKDIR /app

# Copia apenas os artefatos necessários
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./

# Garante que o binário pertence ao root mas é executável pelo usuário
RUN chown -R nodejs:nodejs /app

USER nodejs

EXPOSE 3000

# Health check integrado na imagem
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# dumb-init garante propagação correta de sinais
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/server.js"]

Camada 4: Segurança em Infraestrutura como Código

Checkov: Analisando Terraform e Kubernetes

O Checkov verifica arquivos de IaC em busca de configurações inseguras — buckets S3 públicos, security groups com 0.0.0.0/0, pods sem limites de recursos, containers rodando como root:

  checkov:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Executa Checkov no Terraform
        uses: bridgecrewio/checkov-action@master
        with:
          directory: terraform/
          framework: terraform
          output_format: sarif
          output_file_path: checkov-terraform.sarif
          skip_check: >
            CKV_AWS_136,
            CKV2_AWS_62

      - name: Executa Checkov nos manifestos Kubernetes
        uses: bridgecrewio/checkov-action@master
        with:
          directory: kubernetes/
          framework: kubernetes
          output_format: sarif
          output_file_path: checkov-k8s.sarif

      - name: Upload resultados
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: checkov-terraform.sarif

Arquivo de supressão documentada para exceções legítimas:

# .checkov-suppressions.yml
# Supressões documentadas de verificações do Checkov
# Cada supressão deve ter justificativa e data de revisão

suppressions:
  - check_id: CKV_AWS_18
    resource: aws_s3_bucket.cloudfront_logs
    justification: >
      Bucket de logs do CloudFront não precisa de access logging próprio
      — logging circular não adiciona valor e aumenta custos.
    revisar_em: "2025-06-01"
    responsavel: time-infra

  - check_id: CKV_K8S_8
    resource: Deployment/cert-manager
    justification: >
      cert-manager requer capabilities específicas para gerenciar
      certificados TLS no cluster. Exceção aprovada pelo time de segurança.
    revisar_em: "2025-06-01"
    responsavel: joao-silva

Camada 5: Runtime Security com Falco

As verificações anteriores são preventivas — detectam problemas antes do deploy. O Falco complementa a postura de segurança com detecção em runtime: monitora syscalls no kernel e gera alertas quando comportamentos suspeitos são detectados em produção.

# kubernetes/falco/falco-rules.yml
customRules:
  regras-empresa.yaml: |-
    # Detecta execução de shell dentro de containers de aplicação
    - rule: Shell em container de aplicação
      desc: Detecta abertura de shell interativo em containers de aplicação
      condition: >
        spawned_process and
        container and
        proc.name in (shell_binaries) and
        not proc.pname in (runc, containerd) and
        k8s.ns.name = "producao"
      output: >
        Shell iniciado em container de produção
        (usuario=%user.name container=%container.name
         imagem=%container.image.repository:%container.image.tag
         pod=%k8s.pod.name namespace=%k8s.ns.name
         comando=%proc.cmdline)
      priority: CRITICAL
      tags: [container, shell, T1059]

    # Detecta leitura de arquivos sensíveis
    - rule: Leitura de arquivo sensível em container
      desc: Detecta tentativa de leitura de /etc/shadow ou /etc/passwd
      condition: >
        open_read and
        container and
        fd.name in (/etc/shadow, /etc/passwd, /etc/ssh/ssh_host_rsa_key) and
        not proc.name in (sshd, passwd, shadow)
      output: >
        Arquivo sensível lido em container
        (arquivo=%fd.name processo=%proc.name
         pod=%k8s.pod.name namespace=%k8s.ns.name)
      priority: WARNING
      tags: [filesystem, credentials]

    # Detecta conexões de rede inesperadas
    - rule: Conexão de rede inesperada em container
      desc: Detecta containers abrindo conexões para portas não padronizadas
      condition: >
        outbound and
        container and
        k8s.ns.name = "producao" and
        not fd.sport in (80, 443, 5432, 6379, 53) and
        not proc.name in (node, npm)
      output: >
        Conexão de rede inesperada
        (processo=%proc.name porta_destino=%fd.sport
         ip_destino=%fd.rip pod=%k8s.pod.name)
      priority: WARNING
      tags: [network, lateral-movement]

Camada 6: Gestão de Segredos com HashiCorp Vault

Para organizações com requisitos mais rigorosos de gestão de segredos — rotação automática, acesso auditado, segredos dinâmicos — o HashiCorp Vault oferece capacidades que vão além do AWS Secrets Manager:

# vault.tf — configuração do Vault no Kubernetes

resource "helm_release" "vault" {
  name             = "vault"
  repository       = "https://helm.releases.hashicorp.com"
  chart            = "vault"
  namespace        = "vault"
  create_namespace = true
  version          = "0.27.0"

  values = [yamlencode({
    server = {
      ha = {
        enabled  = true
        replicas = 3
        raft = {
          enabled   = true
          setNodeId = true
        }
      }

      auditStorage = {
        enabled      = true
        storageClass = "gp3"
        size         = "10Gi"
      }

      dataStorage = {
        enabled      = true
        storageClass = "gp3"
        size         = "20Gi"
      }
    }

    injector = {
      enabled = true
    }
  })]
}

Configurando segredos dinâmicos para o banco de dados — o Vault gera credenciais temporárias com tempo de vida limitado:

# Configura o backend de banco de dados no Vault
vault secrets enable database

vault write database/config/minha-api-db \
  plugin_name=postgresql-database-plugin \
  allowed_roles="minha-api-producao" \
  connection_url="postgresql://{{username}}:{{password}}@db.interno.empresa.com:5432/producao" \
  username="vault_root" \
  password="senha_root_vault"

# Define uma role que gera credenciais com TTL de 1 hora
vault write database/roles/minha-api-producao \
  db_name=minha-api-db \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  revocation_statements="REVOKE ALL ON ALL TABLES IN SCHEMA public FROM \"{{name}}\"; DROP ROLE IF EXISTS \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

O Vault Agent Injector injeta as credenciais geradas diretamente no pod como variáveis de ambiente:

# Annotations no Deployment para injeção do Vault Agent
spec:
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "minha-api-producao"
        vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/minha-api-producao"
        vault.hashicorp.com/agent-inject-template-db-creds: |
          {{- with secret "database/creds/minha-api-producao" -}}
          DATABASE_URL=postgresql://{{ .Data.username }}:{{ .Data.password }}@db.interno.empresa.com:5432/producao
          {{- end }}

Pipeline de Segurança Completo

A integração de todas as camadas em um único workflow garante que nenhuma etapa é pulada:

# .github/workflows/pipeline-segurança.yml
name: Pipeline de Segurança

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 2 * * 1'  # Toda segunda-feira às 2h — scan completo

jobs:
  segredos:
    name: Detectar Segredos
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  sast:
    name: Análise Estática (SAST)
    runs-on: ubuntu-latest
    needs: segredos
    steps:
      - uses: actions/checkout@v4
      - name: Semgrep
        uses: returntocorp/semgrep-action@v1
        with:
          config: >
            p/nodejs
            p/secrets
            p/owasp-top-ten
            .semgrep/regras-customizadas.yml

  sca:
    name: Análise de Composição (SCA)
    runs-on: ubuntu-latest
    needs: segredos
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm audit --audit-level=high
      - uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high

  container:
    name: Segurança da Imagem
    runs-on: ubuntu-latest
    needs: [sast, sca]
    steps:
      - uses: actions/checkout@v4
      - name: Constrói imagem
        run: docker build -t minha-api:scan .
      - name: Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: minha-api:scan
          severity: CRITICAL,HIGH
          exit-code: '1'
          ignore-unfixed: true

  iac:
    name: Segurança da Infraestrutura
    runs-on: ubuntu-latest
    needs: segredos
    steps:
      - uses: actions/checkout@v4
      - uses: bridgecrewio/checkov-action@master
        with:
          directory: .
          framework: terraform,kubernetes,dockerfile

  relatorio:
    name: Consolida Relatório de Segurança
    runs-on: ubuntu-latest
    needs: [sast, sca, container, iac]
    if: always()
    steps:
      - name: Verifica status de todos os jobs
        run: |
          if [[ "${{ needs.sast.result }}" == "failure" ]] || \
             [[ "${{ needs.sca.result }}" == "failure" ]] || \
             [[ "${{ needs.container.result }}" == "failure" ]] || \
             [[ "${{ needs.iac.result }}" == "failure" ]]; then
            echo "::error::Falhas de segurança detectadas. Revisão obrigatória antes do merge."
            exit 1
          fi
          echo "Todas as verificações de segurança passaram."

Métricas de Maturidade em DevSecOps

A maturidade de uma postura DevSecOps pode ser medida por indicadores concretos que evoluem ao longo do tempo:

Mean Time to Remediation (MTTR) de vulnerabilidades — quanto tempo leva desde a detecção de uma vulnerabilidade até a correção em produção. Times maduros medem isso em horas para criticalidades altas, não semanas.

Cobertura de scanning — percentual de repositórios, imagens e configurações de IaC cobertos por verificações automatizadas. O objetivo é 100% — sem exceções não documentadas.

Taxa de falsos positivos — verificações que geram muito ruído são ignoradas pelos desenvolvedores. Manter a taxa de falsos positivos baixa é tão importante quanto a cobertura.

Vulnerabilidades por categoria e origem — rastrear de onde vêm as vulnerabilidades (código próprio vs dependências vs configuração) direciona onde investir esforço de melhoria.


O Que Vem a Seguir

O próximo artigo completa a visão de segurança com compliance e auditoria — como mapear controles técnicos para frameworks regulatórios como LGPD, SOC2 e ISO 27001, como implementar logging de auditoria imutável e como preparar evidências de conformidade de forma automatizada.


Referências para Aprofundamento

Ferramentas - Semgrep Documentation — semgrep.dev — Documentação completa do Semgrep com guia de escrita de regras customizadas, biblioteca de regras da comunidade e integração com CI/CD. - Trivy Documentation — aquasecurity.github.io/trivy — Documentação do Trivy cobrindo scanning de imagens, filesystems, repositórios Git e configurações de IaC. - Falco Documentation — falco.org — Documentação oficial do Falco com guia de criação de regras, integração com Kubernetes e configuração de alertas.

Frameworks e referências - OWASP DevSecOps Guideline — owasp.org — Guia do OWASP para implementação de DevSecOps, cobrindo as melhores práticas para cada fase do ciclo de desenvolvimento. - NIST Secure Software Development Framework — csrc.nist.gov — Framework do NIST para desenvolvimento seguro de software, com práticas organizadas por fase e mapeamento para outros frameworks de segurança. - HashiCorp Vault Documentation — developer.hashicorp.com — Documentação completa do Vault, incluindo backends de segredos dinâmicos, políticas de acesso e integração com Kubernetes.

Comentários

Mais em DevOps

O Que é um Container e Por Que Isso Mudou Tudo
O Que é um Container e Por Que Isso Mudou Tudo

Durante décadas, o processo de colocar software em produção seguia um ritual...

Resiliência e Chaos Engineering
Resiliência e Chaos Engineering

Todo engenheiro que opera sistemas em produção por tempo suficiente aprende u...

GitLab CI/CD: A Alternativa Enterprise ao GitHub Actions
GitLab CI/CD: A Alternativa Enterprise ao GitHub Actions

O GitHub é a plataforma padrão para projetos open-source e para a maioria dos...