DevSecOps: 8 práticas para integrar segurança desde o início do ciclo de desenvolvimento
- 1. Bloqueio de segredos no commit: a primeira linha de defesa que realmente pega erro bobo
- 2. SAST no pull request: análise estática que vale a pena quando o escopo é enxuto
- 3. SCA e dependências vulneráveis: o ponto onde supply chain entra na jogada
- 4. IaC com política embutida: Terraform sem guard rails é só automação rápida de erro
- 5. Hardening de containers: imagem mínima, usuário não-root e read-only por padrão
- 6. Segredos fora do Git: Vault, SOPS e variáveis efêmeras no pipeline
- 7. Observabilidade de segurança: log estruturado, trilha de auditoria e detecção de comportamento estranho
- 8. Treinamento de time com cenários reais: o item que separa dashboard bonito de prática madura
- Pipeline GitLab CI com gates reais, sem fumaça
- O ranking que eu uso quando o orçamento é curto
DevSecOps não começa no scanner; começa quando você impede que um desenvolvedor faça git push de um segredo em texto puro para um repositório que já roda em produção. A ideia de “deixar a segurança para o final” ainda aparece em muita squad porque parece mais simples. Não é. O que parece agilidade vira retrabalho, bloqueio de deploy e incidente operacional. Eu prefiro uma regra mais dura: segurança precisa entrar junto com o pipeline, junto com o IaC e junto com a definição do que é “pronto para subir”.
Em ambientes Linux e DevOps, isso significa tratar segurança como código, não como reunião. O objetivo não é criar burocracia. É automatizar verificações que antes dependiam de memória humana, revisão manual e esperança. Quando você faz isso direito, o time ganha previsibilidade. Quando faz pela metade, só cria mais um estágio vermelho no Jenkins, GitLab CI ou GitHub Actions.
1. Bloqueio de segredos no commit: a primeira linha de defesa que realmente pega erro bobo
Se eu tivesse de escolher uma única medida de DevSecOps com impacto imediato, seria essa. Vazamento de segredo em repositório continua sendo uma das falhas mais comuns em times que usam Git no dia a dia. Token de cloud, credencial de banco, chave SSH, API key de SaaS. Tudo isso já apareceu em commit de produção mais vezes do que qualquer equipe gosta de admitir.
O jeito certo é fazer o bloqueio em duas camadas: no cliente e no servidor. No lado do desenvolvedor, use pre-commit com detectores de segredo. No lado do servidor, rode varredura no pipeline e rejeite merge se houver indício real. Em Linux isso encaixa muito bem com pre-commit + gitleaks ou trufflehog.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.2
hooks:
- id: gitleaks
# instalação local em workstation Linux
curl -sSfL https://raw.githubusercontent.com/pre-commit/pre-commit/master/README.md >/dev/null
pipx install pre-commit
pre-commit install
pre-commit run --all-files
Se você prefere acoplar isso ao pipeline, um job simples já resolve boa parte do risco:
gitleaks:
image: zricethezav/gitleaks:v8.24.2
script:
- gitleaks detect --source . --no-git --redact
allow_failure: false
Eu gosto de tratar esse controle como não negociável. Segredo não entra no repositório. Ponto. E isso precisa valer para código, .env, manifesto Kubernetes, Terraform, Ansible e qualquer artefato gerado por automação.
2. SAST no pull request: análise estática que vale a pena quando o escopo é enxuto
Varredura estática de código é útil quando você foca no que realmente quebra produção: uso inseguro de deserialização, concatenação de SQL, chamadas a shell com input não sanitizado, eval, path traversal, defaults inseguros. O erro de muita equipe é ligar um motor enorme de SAST, deixar milhares de achados e ninguém corrigir. A saída não é ignorar SAST. É restringir o escopo, estabelecer baseline e impor qualidade sobre o que entra no branch principal.
Eu prefiro começar com regras enxutas e relevantes por linguagem. Para Python, por exemplo, bandit resolve bastante. Para JavaScript/TypeScript, semgrep costuma ser mais prático porque permite regras customizadas. Para Go, gosec funciona bem. Para aplicações Linux internas, o ponto é simples: o scanner tem que conversar com o ciclo de revisão, não ficar isolado em relatório PDF que ninguém lê.
# exemplo GitLab CI com Semgrep
semgrep:
image: returntocorp/semgrep:latest
stage: test
script:
- semgrep ci --config p/ci
rules:
- if: $CI_MERGE_REQUEST_ID
# .semgrep.yml simplificado
rules:
- id: python-subprocess-shell-true
patterns:
- pattern: subprocess.$F(..., shell=True)
message: Evite shell=True em subprocess
languages: [python]
severity: ERROR
Quando um time me pergunta por onde começar, eu respondo sem rodeio: pegue 10 regras que realmente reduzem risco e faça o time corrigir tudo que cair nelas. Depois amplie. Isso funciona melhor do que tentar cobrir 100% do código na primeira semana.
3. SCA e dependências vulneráveis: o ponto onde supply chain entra na jogada
Se seu projeto consome bibliotecas de terceiros, você já está no território da software supply chain. A maior parte dos ambientes quebrados que eu vejo no Linux corporativo não cai por uma falha exótica; cai por dependência desatualizada, imagem de container com pacote vulnerável ou lockfile inconsistente. Não tem glamour. Tem disciplina.
O fluxo prático é este: travar versões, gerar lockfile, revisar alertas com criticidade e impedir merge quando aparecer CVE explorável no caminho de execução. Para Node.js, npm audit ajuda no básico, mas eu prefiro combinar isso com osv-scanner ou dependabot/renovate para atualização automatizada. Em Python, pip-audit e uv lock já resolvem boa parte da vida moderna. Em Rust, o equivalente vem do ecossistema de cargo-audit.
# auditoria de dependências Python
pip install pip-audit
pip-audit -r requirements.txt
# auditoria genérica via OSV
osv-scanner -r .
O melhor uso de SCA é político e técnico ao mesmo tempo: dar ao time uma lista curta do que precisa ser corrigido agora. Se todo pacote velho vira alerta do mesmo peso, ninguém prioriza. Classifique pelo caminho de exploração real. Dependência usada apenas em build tem impacto diferente de uma lib chamada a cada request HTTP.
4. IaC com política embutida: Terraform sem guard rails é só automação rápida de erro
Infraestrutura como código sem política é uma forma elegante de espalhar misconfiguração em escala. Terraform, Ansible, Helm e Kubernetes manifest são ótimos. Também são ótimos para publicar exposição pública, abrir SG para 0.0.0.0/0, montar volume sensível do jeito errado e esquecer encryption at rest. DevSecOps de verdade usa policy-as-code junto com IaC.
O combo mais pragmático para times Linux é tfsec ou checkov no pipeline + OPA/Conftest para regras customizadas. Se você opera Kubernetes, inclua validação de manifests antes de aplicar.
# validação rápida em Terraform
terraform fmt -check -recursive
terraform validate
checkov -d .
tfsec .
# policy.rego exemplo com Conftest
package main
denylb[msg] {
input.kind == "Service"
input.spec.type == "LoadBalancer"
msg := "Service LoadBalancer proibido fora da camada de borda"
}
conftest test k8s/*.yaml -p policy/
Eu gosto de fazer uma distinção clara entre validação estrutural e validação de risco. terraform validate pega erro de sintaxe. checkov e tfsec pegam exposição. OPA pega política da empresa. Os três juntos evitam aquela cena clássica: ambiente criado rápido, segurança discutida depois, correção manual na sexta-feira à noite.
5. Hardening de containers: imagem mínima, usuário não-root e read-only por padrão
Container inseguro é o tipo de problema que parece pequeno até o momento em que um processo comprometido ganha acesso demais. Se você ainda usa imagem base gigante, roda como root e monta tudo em escrita, está criando um risco desnecessário. Em Linux, contêiner bom é contêiner com superfície reduzida. Sem romantização.
Eu parto de três decisões simples: imagem mínima, USER sem privilégios e filesystem read-only sempre que possível. Para workloads de aplicação, isso dá um ganho real de segurança e higiene operacional.
FROM debian:bookworm-slim AS runtime
RUN useradd -r -u 10001 -g root appuser
&& mkdir -p /app
&& chown -R appuser:root /app
WORKDIR /app
COPY --chown=appuser:root ./bin/app /app/app
USER 10001
ENTRYPOINT ["/app/app"]
No Kubernetes, complemente com securityContext agressivo:
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
containers:
- name: app
image: registry.local/app:1.4.2
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
Esse tipo de postura corta muitos caminhos triviais de exploração. E, para ser direto, se sua aplicação não roda com filesystem read-only, isso já diz algo sobre o acoplamento dela com o host. É melhor descobrir isso em staging do que depois de um incidente.
6. Segredos fora do Git: Vault, SOPS e variáveis efêmeras no pipeline
Guardar segredo em arquivo local é ruim. Guardar em repositório é pior. O ponto certo é centralizar e distribuir de forma efêmera. Em ambientes Linux com Kubernetes e CI/CD, eu normalmente vejo três caminhos úteis: HashiCorp Vault, sops com age e secret injection nativa do provedor de CI.
Se você quer algo operacionalmente limpo, sops resolve bem para arquivos versionados e criptografados. Já para credenciais dinâmicas, Vault é a escolha mais madura. Ele emite credenciais com TTL curto, o que reduz impacto de vazamento. Isso muda o jogo no dia a dia porque o segredo não é mais um objeto estático válido por meses.
# criptografando um secret YAML com sops + age
age-keygen -o key.txt
export SOPS_AGE_KEY_FILE=key.txt
sops --encrypt --age "$(age-keygen -y key.txt)" secrets.yaml > secrets.enc.yaml
# exemplo de secret no pipeline sem persistência
script:
- export DB_PASSWORD="$(vault kv get -field=password secret/prod/db)"
- ./migrate.sh
- unset DB_PASSWORD
O detalhe que muita gente ignora: variáveis de ambiente não são mágica. Elas podem aparecer em dump, log mal configurado, /proc e saída de debug. Use-as com disciplina e limpe o contexto após o uso. Melhor ainda: prefira tokens curtos e identidades efêmeras quando o sistema permitir.
7. Observabilidade de segurança: log estruturado, trilha de auditoria e detecção de comportamento estranho
Segurança sem observabilidade vira opinião. Em DevSecOps, os eventos precisam ser rastreáveis desde o commit até a execução. Eu defendo log estruturado em JSON, correlação por trace_id, auditoria de ações críticas e alertas em cima de sinais concretos: falha repetida de autenticação, criação de token fora do padrão, alteração de política, deploy fora da janela, acesso a segredos fora do perfil esperado.
Num stack Linux, isso pode incluir journald, auditd, Promtail/Loki, Falco e SIEM. O valor não está na ferramenta em si, e sim no evento que ela captura. Exemplo prático com auditd para monitorar acesso a arquivos sensíveis:
auditctl -w /etc/shadow -p wa -k shadow_watch
auditctl -w /etc/sudoers -p wa -k sudoers_watch
ausearch -k sudoers_watch
Para containers, Falco continua útil quando você quer regras de runtime baseadas em syscall e comportamento. Um caso clássico é bloquear shell em container de app ou escrita em diretório inesperado.
- rule: Terminal shell in container
condition: container and proc.name in (bash, sh, zsh)
output: Shell dentro do container (%proc.cmdline)
priority: WARNING
Isso não substitui prevenção no pipeline. Completa a proteção. E aqui mora uma diferença importante: prevenção evita que o problema nasça; detecção diz com precisão onde ele começou.
8. Treinamento de time com cenários reais: o item que separa dashboard bonito de prática madura
Time que não entende o motivo de cada controle burla o controle. Sem drama. É assim que funciona. Eu sempre coloco o treinamento mais próximo possível da pilha real: Linux, container, Terraform, Kubernetes, Git, API, segredo, deploy. Nada de curso genérico com slides sobre “segurança cibernética” e zero linha de comando.
Um exercício que funciona bem é montar um repositório propositalmente ruim e pedir que o time encontre e corrija: segredo exposto, Dockerfile rodando como root, imagem com pacote vulnerável, manifesto com privileged: true, Terraform abrindo SSH para o mundo, e endpoint sem validação de entrada. Isso gera aprendizagem útil porque o problema é concreto, não abstrato.
{
"exercise": "secure-a-bad-pipeline",
"findings": [
"hardcoded secret",
"privileged container",
"0.0.0.0/0 SSH rule",
"missing dependency pinning"
]
}
Se quiser um sinal claro de maturidade, observe quanto tempo o time leva para corrigir uma falha sem travar o fluxo. DevSecOps amadurece quando a correção vira parte do trabalho normal, não um evento extraordinário.
Pipeline GitLab CI com gates reais, sem fumaça
Uma implementação prática precisa de gates bem posicionados. Não faz sentido rodar scanner depois do deploy. O lugar certo é antes do merge e antes do release. Um pipeline básico, mas útil, pode ficar assim:
stages:
- lint
- test
- security
- build
- deploy
variables:
GIT_DEPTH: "0"
lint:
image: python:3.12-slim
stage: lint
script:
- pip install ruff
- ruff check .
test:
image: python:3.12-slim
stage: test
script:
- pip install -r requirements.txt
- pytest -q
secret_scan:
image: zricethezav/gitleaks:v8.24.2
stage: security
script:
- gitleaks detect --source . --redact
sca:
image: python:3.12-slim
stage: security
script:
- pip install pip-audit
- pip-audit -r requirements.txt
container_scan:
image: aquasec/trivy:latest
stage: security
script:
- trivy fs --severity HIGH,CRITICAL --exit-code 1 .
build:
image: docker:27
stage: build
services:
- docker:27-dind
script:
- docker build -t registry.local/app:${CI_COMMIT_SHA} .
- docker push registry.local/app:${CI_COMMIT_SHA}
deploy:
image: bitnami/kubectl:latest
stage: deploy
script:
- kubectl set image deployment/app app=registry.local/app:${CI_COMMIT_SHA}
when: manual
Esse pipeline não é bonito. É funcional. Ele impede algumas classes de falha antes que o artefato chegue ao cluster. E isso, em produção, vale mais do que um dashboard cheio de cores.
O ranking que eu uso quando o orçamento é curto
Se o time está no começo e não dá para implementar tudo, eu sigo uma ordem clara de impacto operacional:
- Bloqueio de segredos no commit e no pipeline.
- SCA com lockfile e atualização automatizada.
- Hardening de containers com usuário não-root e capabilities mínimas.
- IaC com policy-as-code para impedir exposição pública e recursos inseguros.
- SAST enxuto focado em falhas que realmente aparecem em produção.
- Auditoria de runtime para fechar o ciclo com evidência.
Essa ordem existe por uma razão simples: ela ataca o que mais quebra ambientes Linux e cloud com menos atrito para o time. Segurança que atrapalha demais acaba desligada. Segurança automatizada, com regra clara e falha visível, vira parte da engenharia.
Se você opera em laboratório próprio, VMs KVM, cluster Kubernetes on-prem ou ambiente híbrido, a regra não muda. O controle precisa nascer junto com o código, ser executado pelo pipeline e deixar trilha auditável. DevSecOps não é uma etiqueta para colocar no board. É o ato de transformar segurança em etapa automatizada, versionada e verificável. E o primeiro commit sem segredo já costuma mostrar se o time entendeu isso de verdade.
Sou um profissional na área de Tecnologia da informação, especializado em monitoramento de ambientes, Sysadmin e na cultura DevOps. Possuo certificações de Segurança, AWS e Zabbix.


