Segurança de Infraestrutura
Defense in Depth
Segurança não é um produto que você instala. É uma propriedade emergente do sistema inteiro. O modelo de Defense in Depth assume que qualquer camada individual pode falhar — e projeta múltiplas camadas independentes para que a falha de uma não comprometa o sistema.
┌── Defense in Depth ─────────────────────────────────────────────────┐
│ │
│ Camada 1: Perímetro (Network) │
│ ├── Firewall, WAF, DDoS protection, rate limiting │
│ │ │
│ Camada 2: Rede Interna (Network Segmentation) │
│ ├── VPC subnets, security groups, NACLs, mTLS │
│ │ │
│ Camada 3: Host (Server Hardening) │
│ ├── CIS Benchmarks, SELinux/AppArmor, auditd, patches │
│ │ │
│ Camada 4: Aplicação (Application Security) │
│ ├── AuthN/AuthZ, input validation, SAST/DAST, WAF rules │
│ │ │
│ Camada 5: Dados (Data Protection) │
│ ├── Encryption at rest/in transit, backup, access control │
│ │ │
│ Camada 6: Monitoramento (Detection & Response) │
│ ├── Audit logs, alertas, incident response, SIEM │
│ │
│ Se o atacante passar pela camada 1, encontra a camada 2. │
│ Se passar pela 2, encontra a 3. E assim por diante. │
└──────────────────────────────────────────────────────────────────────┘
Threat Modeling com STRIDE
Antes de proteger, você precisa entender o que está protegendo e de quem. O STRIDE é um framework da Microsoft para categorizar ameaças:
┌──────────────────────┬──────────────────────────────────────────────┐
│ Ameaça │ Descrição │
├──────────────────────┼──────────────────────────────────────────────┤
│ Spoofing │ Fingir ser outra entidade (user, service) │
│ Tampering │ Modificar dados em trânsito ou em repouso │
│ Repudiation │ Negar ter feito uma ação (sem audit trail) │
│ Information Disclosure│ Vazamento de dados sensíveis │
│ Denial of Service │ Tornar o sistema indisponível │
│ Elevation of Privilege│ Ganhar acesso além do autorizado │
└──────────────────────┴──────────────────────────────────────────────┘
Para cada componente do sistema, pergunte:
- Quem pode acessar isso?
- O que acontece se os dados forem modificados?
- Temos logs suficientes para auditoria?
- O que acontece se este serviço ficar fora do ar?
- Um usuário comum pode escalar privilégios?
Princípio do Menor Privilégio
Cada componente, usuário ou serviço deve ter apenas as permissões estritamente necessárias para sua função. Nada mais.
# IAM Policy: princípio do menor privilégio
# Ruim: acesso total ao S3
resource "aws_iam_policy" "bad_example" {
policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = "s3:*" # Tudo no S3
Resource = "*" # Todos os buckets
}]
})
}
# Bom: apenas o necessário
resource "aws_iam_policy" "good_example" {
policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = [
"s3:GetObject", # Apenas leitura
"s3:PutObject" # E escrita
]
Resource = "arn:aws:s3:::my-app-uploads/*" # Apenas neste bucket
}]
})
}
Security through obscurity (esconder como o sistema funciona) nunca funciona como estratégia primária. O atacante vai descobrir. Segurança real assume que o atacante conhece seu sistema por completo — e mesmo assim não consegue comprometê-lo.
Hardening de Servidores
Hardening é o processo de reduzir a superfície de ataque de um servidor. Cada serviço rodando, cada porta aberta, cada usuário com acesso é um vetor potencial.
CIS Benchmarks
O Center for Internet Security publica benchmarks detalhados para hardening de sistemas operacionais. São centenas de controles específicos:
# Exemplos de controles CIS para Ubuntu/Debian
# 1. Desabilitar filesystems desnecessários
cat >> /etc/modprobe.d/CIS.conf << 'EOF'
install cramfs /bin/true
install freevxfs /bin/true
install jffs2 /bin/true
install hfs /bin/true
install hfsplus /bin/true
install squashfs /bin/true
install udf /bin/true
EOF
# 2. Configurar permissões em arquivos críticos
chmod 644 /etc/passwd
chmod 600 /etc/shadow
chmod 644 /etc/group
chmod 600 /etc/gshadow
# 3. Garantir que root é a única conta UID 0
awk -F: '($3 == 0) { print }' /etc/passwd
# Deve retornar apenas: root:x:0:0:root:/root:/bin/bash
# 4. Configurar login timeout
echo "TMOUT=900" >> /etc/profile.d/timeout.sh
# Sessões inativas são desconectadas após 15 min
# 5. Desabilitar core dumps
echo "* hard core 0" >> /etc/security/limits.conf
echo "fs.suid_dumpable = 0" >> /etc/sysctl.conf
Kernel Hardening
# /etc/sysctl.d/99-security.conf
# Proteção contra IP spoofing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Desabilitar IP source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Ignorar ICMP redirects (previne MITM)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Ignorar ICMP broadcasts (previne Smurf attack)
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Log pacotes marcianos (IPs impossíveis)
net.ipv4.conf.all.log_martians = 1
# Proteção contra SYN flood
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
# Desabilitar IP forwarding (se não for router/gateway)
net.ipv4.ip_forward = 0
# ASLR (Address Space Layout Randomization) — dificulta exploits
kernel.randomize_va_space = 2
# Restringir acesso a dmesg
kernel.dmesg_restrict = 1
# Restringir acesso a kernel pointers
kernel.kptr_restrict = 2
AppArmor e SELinux
São sistemas de controle de acesso mandatório (MAC) que restringem o que processos podem fazer, mesmo que rodem como root.
# AppArmor (Ubuntu/Debian) — verificar status
sudo aa-status
# Exemplo de profile AppArmor para nginx
# /etc/apparmor.d/usr.sbin.nginx
/usr/sbin/nginx {
# Pode ler configuração
/etc/nginx/** r,
# Pode ler conteúdo web
/var/www/** r,
# Pode escrever logs
/var/log/nginx/** w,
# Pode escutar em portas de rede
network inet stream,
# Não pode acessar /home, /root, /etc/shadow, etc.
# Tudo que não está explicitamente permitido é bloqueado
}
# SELinux (RHEL/CentOS) — verificar status
getenforce # Enforcing, Permissive, Disabled
# SELinux labels: contexto de segurança em cada arquivo/processo
ls -Z /var/www/html/
# system_u:object_r:httpd_sys_content_t:s0 index.html
# Se nginx não pode ler um arquivo, verifique o contexto SELinux
chcon -t httpd_sys_content_t /var/www/html/new-file.html
# Ou melhor, use semanage para regras permanentes
Infraestrutura Imutável
A abordagem moderna para hardening: não corrija servidores, substitua-os. Servidores são descartáveis. Se precisa de atualização, crie uma nova imagem e faça deploy.
# Packer — criar golden images automaticamente
# server-image.pkr.hcl
source "amazon-ebs" "ubuntu" {
ami_name = "hardened-ubuntu-{{timestamp}}"
instance_type = "t3.micro"
region = "us-east-1"
source_ami_filter {
filters = {
name = "ubuntu/images/*ubuntu-jammy-22.04-amd64-server-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
owners = ["099720109477"] # Canonical
most_recent = true
}
ssh_username = "ubuntu"
}
build {
sources = ["source.amazon-ebs.ubuntu"]
# Aplicar hardening CIS
provisioner "shell" {
script = "scripts/cis-hardening.sh"
}
# Instalar agentes necessários
provisioner "shell" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y cloudwatch-agent fail2ban auditd",
"sudo apt-get autoremove -y",
"sudo apt-get clean"
]
}
# Remover credenciais SSH temporárias
provisioner "shell" {
inline = [
"sudo rm -f /home/ubuntu/.ssh/authorized_keys",
"sudo rm -f /root/.ssh/authorized_keys",
"sudo shred -u /etc/ssh/*_key /etc/ssh/*_key.pub"
]
}
}
SSH Hardening
# /etc/ssh/sshd_config — configuração segura
# Desabilitar login como root
PermitRootLogin no
# Apenas autenticação por chave (nunca senha)
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
# Algoritmos seguros apenas
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Timeout e limites
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 3
MaxSessions 3
LoginGraceTime 30
# Restringir usuários e grupos
AllowGroups ssh-users
# Desabilitar forwarding desnecessário
AllowTcpForwarding no
X11Forwarding no
AllowStreamLocalForwarding no
Para ambientes maiores, use SSH com certificados em vez de chaves públicas individuais:
# SSH Certificate Authority — centralizar confiança
# 1. Criar CA (uma vez)
ssh-keygen -t ed25519 -f /etc/ssh/ca_host_key -C "Host CA"
ssh-keygen -t ed25519 -f /etc/ssh/ca_user_key -C "User CA"
# 2. Assinar chave do servidor (cada servidor)
ssh-keygen -s /etc/ssh/ca_host_key -I "web-server-01" \
-h -n web-server-01.internal \
-V +52w \
/etc/ssh/ssh_host_ed25519_key.pub
# 3. Assinar chave do usuário (cada desenvolvedor)
ssh-keygen -s /etc/ssh/ca_user_key -I "lucas@company" \
-n ubuntu,deploy \
-V +8h \
~/.ssh/id_ed25519.pub
# Certificado válido por 8 horas apenas
# 4. Configurar servidor para confiar na CA
echo "TrustedUserCAKeys /etc/ssh/ca_user_key.pub" >> /etc/ssh/sshd_config
Bastion Host / Jump Box
┌── Internet ──┐ ┌── VPC ────────────────────────────────────┐
│ │ │ │
│ Developer ───┼──SSH──▶ Bastion Host (public subnet) │
│ │ │ ├── Único ponto de entrada SSH │
│ │ │ ├── MFA obrigatório │
│ │ │ ├── Sessões logadas (auditd) │
│ │ │ └───SSH──▶ App servers (private subnet) │
│ │ │ ├── Server 1 │
│ │ │ ├── Server 2 │
│ │ │ └── Server 3 │
│ │ │ │
│ │ │ Security Group do Bastion: │
│ │ │ Inbound: 22/tcp de VPN CIDR apenas │
│ │ │ │
│ │ │ Security Group dos App Servers: │
│ │ │ Inbound: 22/tcp APENAS do Bastion SG │
└───────────────┘ └────────────────────────────────────────────┘
# Alternativa moderna: AWS SSM Session Manager
# Sem necessidade de bastion, sem porta SSH aberta
aws ssm start-session --target i-0abc123def456
System Auditing com auditd
# /etc/audit/rules.d/audit.rules
# Monitorar alterações em arquivos de autenticação
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
# Monitorar alterações em configuração SSH
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Monitorar execuções com sudo
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -k privileged_commands
# Monitorar modificações de data/hora
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change
-a always,exit -F arch=b64 -S clock_settime -k time-change
# Monitorar mount/unmount de filesystems
-a always,exit -F arch=b64 -S mount -F auid>=1000 -k mounts
# Monitorar deleção de arquivos
-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -k delete
# Tornar regras imutáveis (requer reboot para alterar)
-e 2
Network Security
Firewall: iptables e nftables
# iptables — regras básicas de servidor web
# Política padrão: negar tudo
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Permitir loopback
iptables -A INPUT -i lo -j ACCEPT
# Permitir conexões estabelecidas (respostas)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Permitir SSH apenas de rede interna
iptables -A INPUT -p tcp --dport 22 -s 10.0.0.0/8 -j ACCEPT
# Permitir HTTP/HTTPS de qualquer origem
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Rate limiting para prevenir brute force SSH
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --set --name SSH
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP
# Log pacotes descartados (debugging)
iptables -A INPUT -j LOG --log-prefix "IPTables-Dropped: " --log-level 4
iptables -A INPUT -j DROP
# nftables — substituto moderno do iptables
# /etc/nftables.conf
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Loopback e conexões estabelecidas
iif lo accept
ct state established,related accept
# SSH rate limited
tcp dport 22 ip saddr 10.0.0.0/8 ct state new \
limit rate 3/minute accept
# HTTP/HTTPS
tcp dport { 80, 443 } accept
# Log e dropar o resto
log prefix "nftables-drop: " counter drop
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Network Segmentation
┌── Arquitetura DMZ ──────────────────────────────────────────────────┐
│ │
│ Internet │
│ │ │
│ ▼ │
│ ┌── DMZ (Zona Desmilitarizada) ──────────────────────────────────┐ │
│ │ Firewall Externo │ │
│ │ ├── WAF / Load Balancer │ │
│ │ ├── Reverse Proxy (nginx) │ │
│ │ └── Apenas portas 80/443 abertas │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ (apenas tráfego HTTP para app servers) │
│ ▼ │
│ ┌── Application Zone ────────────────────────────────────────────┐ │
│ │ Firewall Interno │ │
│ │ ├── App servers (portas 3000, 8080) │ │
│ │ ├── Workers / Message consumers │ │
│ │ └── Sem acesso direto da internet │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │ (apenas portas de DB) │
│ ▼ │
│ ┌── Data Zone ───────────────────────────────────────────────────┐ │
│ │ ├── Databases (5432, 3306, 27017) │ │
│ │ ├── Cache (6379) │ │
│ │ └── Sem rota para internet (air-gapped) │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
│ Regra: cada zona só fala com a zona adjacente. │
│ DMZ nunca acessa Data Zone diretamente. │
└──────────────────────────────────────────────────────────────────────┘
VPN e WireGuard
# WireGuard — VPN moderna, rápida e simples
# Muito mais simples que OpenVPN, performance superior
# Instalar (servidor)
apt install wireguard
# Gerar chaves (cada peer)
wg genkey | tee privatekey | wg pubkey > publickey
# Configuração do servidor (/etc/wireguard/wg0.conf)
[Interface]
Address = 10.200.0.1/24
ListenPort = 51820
PrivateKey = <server_private_key>
# Regras de firewall automáticas
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; \
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; \
iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# Peer: desenvolvedor
[Peer]
PublicKey = <developer_public_key>
AllowedIPs = 10.200.0.2/32
# Peer: outro desenvolvedor
[Peer]
PublicKey = <dev2_public_key>
AllowedIPs = 10.200.0.3/32
# Configuração do cliente (desenvolvedor)
[Interface]
Address = 10.200.0.2/24
PrivateKey = <developer_private_key>
DNS = 10.0.0.2 # DNS interno da empresa
[Peer]
PublicKey = <server_public_key>
Endpoint = vpn.company.com:51820
AllowedIPs = 10.0.0.0/8 # Rotear apenas tráfego interno pela VPN
PersistentKeepalive = 25
# Ativar
sudo wg-quick up wg0
Service Mesh Security (mTLS)
# Istio — mTLS automático entre todos os serviços
# PeerAuthentication: exigir mTLS no namespace
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT # Apenas conexões mTLS aceitas
---
# AuthorizationPolicy: controle fino de acesso
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: api-policy
namespace: production
spec:
selector:
matchLabels:
app: api-server
rules:
- from:
- source:
principals:
- "cluster.local/ns/production/sa/frontend"
- "cluster.local/ns/production/sa/mobile-bff"
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/*"]
# Apenas frontend e mobile-bff podem acessar o api-server
# Todo o resto é negado implicitamente
WAF (Web Application Firewall)
# AWS WAF com regras customizadas
resource "aws_wafv2_web_acl" "main" {
name = "production-waf"
scope = "REGIONAL"
default_action { allow {} }
# Regra 1: Bloquear SQL Injection
rule {
name = "SQLInjection"
priority = 1
action { block {} }
statement {
sqli_match_statement {
field_to_match {
body {}
}
text_transformation {
priority = 1
type = "URL_DECODE"
}
text_transformation {
priority = 2
type = "HTML_ENTITY_DECODE"
}
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "SQLInjection"
}
}
# Regra 2: Regras gerenciadas AWS (OWASP Top 10)
rule {
name = "AWSManagedRules"
priority = 2
override_action { none {} }
statement {
managed_rule_group_statement {
vendor_name = "AWS"
name = "AWSManagedRulesCommonRuleSet"
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRules"
}
}
# Regra 3: Rate limiting por IP
rule {
name = "RateLimit"
priority = 3
action { block {} }
statement {
rate_based_statement {
limit = 1000
aggregate_key_type = "IP"
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "RateLimit"
}
}
visibility_config {
sampled_requests_enabled = true
cloudwatch_metrics_enabled = true
metric_name = "ProductionWAF"
}
}
DDoS Protection
Estratégia em camadas contra DDoS:
1. Camada de Rede (L3/L4):
├── AWS Shield Standard (gratuito, automático)
├── Rate limiting no firewall
└── Blackhole routing para IPs atacantes
2. Camada de Aplicação (L7):
├── AWS Shield Advanced (pago, com suporte DRT)
├── WAF rate limiting por IP
├── CloudFront como primeiro ponto de contato
└── Regras de bot detection
3. Camada de DNS:
├── Route 53 com anycast (distribui globalmente)
└── DNS rate limiting
4. Arquitetura resiliente:
├── Auto-scaling: absorver tráfego legítimo durante ataque
├── CDN: edge locations absorvem bulk do tráfego
├── Queues: buffer para proteger backend
└── Circuit breakers: isolar serviços sob pressão
O objetivo não é bloquear 100% do ataque — é manter o serviço
disponível para usuários legítimos durante o ataque.
Secrets Management
Por que Variáveis de Ambiente Não São Suficientes
# Problema com env vars:
# 1. Visíveis no processo listing
ps aux -e # Mostra env vars de todos os processos
# 2. Herdadas por processos filhos (incluindo maliciosos)
# Um child process de debug ou log pode capturar todas as env vars
# 3. Sem audit trail — ninguém sabe quem acessou
# 4. Sem rotação automática — trocar uma env var requer redeploy
# 5. Em container orchestrators, ficam no manifesto
# kubectl get deployment api -o yaml
# ... mostra DATABASE_URL em plaintext no spec
# 6. Aparecem em crash dumps, logs, e error reports
# Error: connect ECONNREFUSED postgresql://admin:S3cr3t@db:5432/app
# ^^^^^^^^^^^^^^^^
# Senha no log de erro
HashiCorp Vault
┌── Vault Architecture ───────────────────────────────────────────────┐
│ │
│ ┌─── Client (App) ───┐ ┌─── Vault Server ──────────────────┐ │
│ │ │ │ │ │
│ │ 1. Autenticar │────▶│ Auth Methods: │ │
│ │ (JWT, K8s SA, │ │ ├── Kubernetes Service Account │ │
│ │ AWS IAM role) │ │ ├── AWS IAM Role │ │
│ │ │ │ ├── JWT/OIDC │ │
│ │ 2. Receber token │◀────│ └── AppRole │ │
│ │ │ │ │ │
│ │ 3. Ler secrets │────▶│ Secrets Engines: │ │
│ │ com token │ │ ├── KV (key-value) │ │
│ │ │ │ ├── Database (dynamic creds) │ │
│ │ 4. Usar secret │◀────│ ├── PKI (certificados) │ │
│ │ (TTL limitado) │ │ └── Transit (encryption as API) │ │
│ │ │ │ │ │
│ └─────────────────────┘ │ Storage Backend: │ │
│ │ ├── Consul, Raft, PostgreSQL │ │
│ │ └── Encrypted at rest │ │
│ └─────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
# Vault: secrets dinâmicos para PostgreSQL
# Vault gera credenciais temporárias sob demanda
# Configurar engine de database
vault secrets enable database
vault write database/config/myapp-db \
plugin_name="postgresql-database-plugin" \
allowed_roles="readonly","readwrite" \
connection_url="postgresql://{{username}}:{{password}}@db.internal:5432/myapp" \
username="vault_admin" \
password="initial-password"
# Definir role (template de credenciais)
vault write database/roles/readonly \
db_name="myapp-db" \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' \
VALID UNTIL '{{expiration}}'; \
GRANT SELECT 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"
# Aplicação solicita credenciais
vault read database/creds/readonly
# Key Value
# --- -----
# lease_id database/creds/readonly/abc123
# lease_duration 1h
# username v-app-readonly-x7k2j3
# password A1b2C3d4E5-randomized
# Credenciais expiram automaticamente em 1 hora
# Cada instância da app recebe credenciais únicas
# Audit log registra quem pediu e quando
# Vault Transit: encryption as a service
# Sua app não gerencia chaves — Vault faz isso
# Ativar transit engine
vault secrets enable transit
vault write -f transit/keys/user-data
# Criptografar (app envia plaintext, Vault retorna ciphertext)
vault write transit/encrypt/user-data \
plaintext=$(echo "cpf:123.456.789-00" | base64)
# ciphertext: vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM
# Descriptografar
vault write transit/decrypt/user-data \
ciphertext="vault:v1:8SDd3WHDOjf7mq69CyCqYjBXAiQQAVZRkFM"
# Benefícios:
# - App nunca tem acesso à chave de criptografia
# - Rotação de chaves sem re-criptografar dados
# - Audit trail de todas as operações de criptografia
AWS Secrets Manager e Parameter Store
# Secrets Manager: para credenciais com rotação automática
resource "aws_secretsmanager_secret" "db_credentials" {
name = "production/database/credentials"
recovery_window_in_days = 7
}
resource "aws_secretsmanager_secret_rotation" "db" {
secret_id = aws_secretsmanager_secret.db_credentials.id
rotation_lambda_arn = aws_lambda_function.rotate_secret.arn
rotation_rules {
automatically_after_days = 30 # Rotação automática a cada 30 dias
}
}
# Parameter Store: para configurações (mais barato)
resource "aws_ssm_parameter" "api_key" {
name = "/production/api/stripe-key"
type = "SecureString" # Criptografado com KMS
value = var.stripe_api_key
tier = "Standard" # Grátis até 10.000 parâmetros
}
Kubernetes Secrets e External Secrets
# Kubernetes Secrets: base64, NÃO criptografado
# Qualquer pessoa com acesso ao namespace pode ler
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
# Isso é base64, não criptografia
# echo -n "minha-senha" | base64 → bWluaGEtc2VuaGE=
password: bWluaGEtc2VuaGE=
# Para secrets reais, use External Secrets Operator
---
# External Secrets: sincroniza secrets de fontes externas
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: production/database/credentials
property: password
- secretKey: username
remoteRef:
key: production/database/credentials
property: username
# O secret no Kubernetes é atualizado automaticamente
# quando o secret no AWS Secrets Manager muda
SOPS para Secrets em Git
# SOPS (Secrets OPerationS) — criptografar arquivos no git
# Suporta KMS, GCP KMS, Azure Key Vault, age, PGP
# Instalar
brew install sops age
# Gerar chave age (mais simples que PGP)
age-keygen -o keys.txt
# Public key: age1abc123...
# Criar arquivo de configuração
# .sops.yaml
cat > .sops.yaml << 'EOF'
creation_rules:
- path_regex: \.enc\.yaml$
age: >-
age1abc123...,age1def456...
- path_regex: \.enc\.json$
age: >-
age1abc123...
EOF
# Criptografar
sops -e secrets.yaml > secrets.enc.yaml
# O arquivo criptografado mantém as chaves em plaintext
# mas criptografa os valores:
# database:
# password: ENC[AES256_GCM,data:abc123...,tag:xyz...]
# host: ENC[AES256_GCM,data:def456...,tag:uvw...]
# Editar (descriptografa, abre editor, re-criptografa)
sops secrets.enc.yaml
# Descriptografar para uso
sops -d secrets.enc.yaml > secrets.yaml
# No CI/CD: export a chave age como variável secreta
# SOPS_AGE_KEY=AGE-SECRET-KEY-1abc... sops -d secrets.enc.yaml
Rotação de Secrets
Estratégia de rotação:
1. Rotação automática (ideal):
├── Vault dynamic secrets: novo secret a cada request
├── AWS Secrets Manager: Lambda de rotação
└── Certificados: cert-manager com auto-renewal
2. Rotação manual com zero downtime:
├── Passo 1: Gerar novo secret
├── Passo 2: Configurar sistema para aceitar AMBOS (old + new)
├── Passo 3: Atualizar todas as aplicações para usar o novo
├── Passo 4: Verificar que ninguém usa o antigo
└── Passo 5: Revogar o secret antigo
3. Frequência recomendada:
├── Database passwords: 30-90 dias
├── API keys: 90 dias
├── TLS certificates: 90 dias (Let's Encrypt padrão)
├── SSH keys: 6-12 meses
└── Encryption keys: 12 meses (com re-wrap, não re-encrypt)
Container Security
Scanning de Imagens
# Trivy — scanner open-source da Aqua Security
# Detecta vulnerabilidades em imagens, filesystem, IaC
# Scan de imagem
trivy image myapp:latest
# Scan com severidade mínima e fail no CI
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:latest
# Scan de filesystem (detectar secrets, vulnerabilidades em deps)
trivy fs --security-checks vuln,secret,config .
# Scan de IaC (Terraform, Kubernetes manifests)
trivy config ./terraform/
# Exemplo de output:
# myapp:latest (ubuntu 22.04)
# ============================
# Total: 3 (HIGH: 2, CRITICAL: 1)
#
# ┌──────────────┬────────────────┬──────────┬─────────────────────┐
# │ Library │ Vulnerability │ Severity │ Fixed Version │
# ├──────────────┼────────────────┼──────────┼─────────────────────┤
# │ openssl │ CVE-2024-1234 │ CRITICAL │ 3.0.13-1 │
# │ curl │ CVE-2024-5678 │ HIGH │ 8.5.0-2 │
# │ libxml2 │ CVE-2024-9012 │ HIGH │ 2.12.4-1 │
# └──────────────┴────────────────┴──────────┴─────────────────────┘
Containers Seguros
# Dockerfile com práticas de segurança
# 1. Usar imagem base específica e minimal
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 2. Multi-stage: imagem final sem ferramentas de build
FROM gcr.io/distroless/nodejs22-debian12
# 3. Nunca rodar como root
# Distroless já roda como nonroot por padrão
WORKDIR /app
# 4. Copiar apenas o necessário
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
EXPOSE 3000
CMD ["dist/server.js"]
# Kubernetes: Pod com security context restritivo
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
securityContext:
runAsNonRoot: true # Proibir root
runAsUser: 1000 # UID específico
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault # Seccomp padrão do runtime
containers:
- name: app
image: myapp:v1.2.3 # Tag específica, nunca :latest
securityContext:
allowPrivilegeEscalation: false # Bloquear setuid
readOnlyRootFilesystem: true # Filesystem read-only
capabilities:
drop:
- ALL # Remover todas as capabilities
resources:
limits:
cpu: "500m"
memory: "256Mi"
requests:
cpu: "100m"
memory: "128Mi"
volumeMounts:
- name: tmp
mountPath: /tmp # Único dir com escrita
volumes:
- name: tmp
emptyDir:
sizeLimit: 100Mi
Pod Security Standards
# Pod Security Admission: enforce no nível do namespace
# Três níveis: privileged, baseline, restricted
# Forçar modo restricted no namespace production
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
# restricted proíbe:
# - Containers como root
# - Privilege escalation
# - Host networking/ports/PID
# - Capabilities perigosas
# - Volumes hostPath
# - Seccomp profiles inseguros
Supply Chain Security
# Cosign — assinar e verificar imagens de container
# Parte do projeto Sigstore
# Gerar chave
cosign generate-key-pair
# Assinar imagem após build
cosign sign --key cosign.key myregistry.com/myapp:v1.2.3
# Verificar antes de deploy
cosign verify --key cosign.pub myregistry.com/myapp:v1.2.3
# Keyless signing (recomendado): usa OIDC identity
cosign sign myregistry.com/myapp:v1.2.3
# Autentica via GitHub Actions, GitLab CI, etc.
# SBOM: gerar lista de componentes
syft myregistry.com/myapp:v1.2.3 -o spdx-json > sbom.json
# Scan do SBOM para vulnerabilidades
grype sbom:./sbom.json
# Kyverno: policy para exigir imagens assinadas
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: verify-cosign-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "myregistry.com/*"
attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
-----END PUBLIC KEY-----
Imagens Base Seguras
Escolha de imagem base (menor = mais seguro):
┌─────────────────────┬──────────┬──────────────────────────────────┐
│ Imagem │ Tamanho │ Quando usar │
├─────────────────────┼──────────┼──────────────────────────────────┤
│ scratch │ 0 MB │ Go/Rust binários estáticos │
│ distroless │ ~20 MB │ Java, Node.js, Python (produção) │
│ alpine │ ~5 MB │ Quando precisa de shell │
│ ubuntu/debian slim │ ~80 MB │ Quando precisa de apt/ferramentas│
│ ubuntu/debian full │ ~200 MB │ Evitar em produção │
└─────────────────────┴──────────┴──────────────────────────────────┘
Menos pacotes = menos vulnerabilidades = menor superfície de ataque
distroless não tem:
- Shell (sh, bash)
- Package manager (apt, apk)
- Ferramentas de debug (curl, wget, netcat)
Isso é feature, não bug: se o atacante conseguir RCE,
não tem shell para usar.
CI/CD Security
SAST (Static Application Security Testing)
# GitHub Actions: pipeline com security scanning
name: Security Pipeline
on: [push, pull_request]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Semgrep: SAST rápido e configurável
- name: Semgrep Scan
uses: semgrep/semgrep-action@v1
with:
config: >-
p/security-audit
p/secrets
p/owasp-top-ten
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
# CodeQL: análise profunda do GitHub
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript, python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
SCA (Software Composition Analysis)
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Trivy para vulnerabilidades em dependências
- name: Trivy Dependency Scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
exit-code: '1'
# Snyk como segunda opinião
- name: Snyk Test
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
Secret Scanning em Commits
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Histórico completo para scan
# gitleaks: detectar secrets em código
- name: Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Exemplos do que gitleaks detecta:
# - AWS Access Keys (AKIA...)
# - Private keys (-----BEGIN RSA PRIVATE KEY-----)
# - Tokens (ghp_, sk_live_, etc.)
# - Database connection strings com senha
# - .env files commitados acidentalmente
# Pre-commit hook: prevenir antes de commitar
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
# Instalar
pip install pre-commit
pre-commit install
# Agora, git commit roda gitleaks automaticamente
# Se detectar um secret, o commit é bloqueado
DAST (Dynamic Application Security Testing)
dast:
runs-on: ubuntu-latest
needs: deploy-staging
steps:
# OWASP ZAP: scan dinâmico contra ambiente de staging
- name: OWASP ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.12.0
with:
target: 'https://staging.myapp.com'
rules_file_name: '.zap-rules.tsv'
fail_action: 'warn' # Ou 'fail' para bloquear
# ZAP testa:
# - SQL Injection
# - XSS (Cross-Site Scripting)
# - CSRF
# - Headers de segurança faltando
# - Information disclosure
# - Server misconfiguration
SLSA Framework e Signed Builds
SLSA (Supply-chain Levels for Software Artifacts):
┌────────┬───────────────────────────────────────────────────────────┐
│ Level │ Requisitos │
├────────┼───────────────────────────────────────────────────────────┤
│ SLSA 1 │ Build process documentado │
│ SLSA 2 │ Build service hospedado + logs retidos │
│ SLSA 3 │ Build service hardened + provenance verificável │
│ SLSA 4 │ Hermético, reproduzível, two-person review │
└────────┴───────────────────────────────────────────────────────────┘
Provenance: metadados que descrevem COMO um artefato foi construído.
- Qual código fonte?
- Qual pipeline de build?
- Quais dependências?
- Em qual ambiente?
# GitHub Actions: gerar SLSA provenance
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write # Para OIDC
contents: read
attestations: write
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Generate SLSA Provenance
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
with:
image: myapp
digest: ${{ steps.build.outputs.digest }}
Policy as Code com OPA/Gatekeeper
# Gatekeeper: OPA para Kubernetes
# Constraint Template: definir a regra
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Labels obrigatórias faltando: %v", [missing])
}
---
# Constraint: aplicar a regra
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
- apiGroups: ["apps"]
kinds: ["Deployment"]
parameters:
labels:
- "team"
- "environment"
- "cost-center"
# Deploy sem essas labels sera rejeitado pelo admission controller
Zero Trust Architecture
Além do Perímetro
Modelo Tradicional (Castle and Moat):
┌─────────────────────────────────────────────────┐
│ Firewall (moat) │
│ ┌─────────────────────────────────────────────┐ │
│ │ Rede Interna = CONFIÁVEL │ │
│ │ ├── Todos os serviços se comunicam livre │ │
│ │ ├── VPN = dentro do "castelo" │ │
│ │ └── Movimentação lateral trivial │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Problema: se o atacante entra, tem acesso a tudo.
Modelo Zero Trust:
┌─────────────────────────────────────────────────┐
│ Nenhuma rede é confiável │
│ ┌───────┐ mTLS ┌───────┐ mTLS ┌───────┐ │
│ │ Svc A │◄──────►│ Svc B │◄──────►│ Svc C │ │
│ │ AuthZ │ │ AuthZ │ │ AuthZ │ │
│ └───────┘ └───────┘ └───────┘ │
│ │
│ Cada request: │
│ ├── Identidade verificada (quem está pedindo?) │
│ ├── Autorização verificada (pode fazer isso?) │
│ ├── Criptografado em trânsito (mTLS) │
│ └── Logado e auditado │
└─────────────────────────────────────────────────┘
BeyondCorp: Zero Trust na Prática
Princípios do BeyondCorp (Google):
1. Acesso não depende da rede:
- Estar na VPN corporativa NÃO te dá acesso
- Estar em um cafe com WiFi pública NÃO te nega acesso
- Acesso depende de identidade + contexto
2. Contexto de acesso:
├── Quem: identidade verificada (MFA obrigatório)
├── O quê: dispositivo saudável (patches, disk encryption)
├── Onde: localização (risk scoring)
├── Quando: horário usual vs suspeito
└── Como: tipo de acesso (API, browser, CLI)
3. Access Proxy:
- Todo acesso passa por um proxy central
- Proxy verifica identidade + dispositivo + contexto
- Decisão de acesso a cada request (não por sessão)
- Sem conceito de "dentro da rede"
Ferramentas:
├── Google BeyondCorp Enterprise
├── Cloudflare Access
├── Tailscale + ACLs
└── Pomerium (open-source)
SPIFFE/SPIRE: Identidade de Workload
SPIFFE (Secure Production Identity Framework for Everyone):
Identidade para serviços, não só para pessoas.
┌── SPIRE Server ──────────────────────────────────────────────┐
│ │
│ Registra workloads e emite identidades (SVIDs) │
│ SVID = SPIFFE Verifiable Identity Document │
│ Formato: spiffe://trust-domain/path │
│ │
│ Exemplos: │
│ spiffe://company.com/production/api-server │
│ spiffe://company.com/production/payment-service │
│ spiffe://company.com/staging/api-server │
│ │
│ ┌── SPIRE Agent (cada node) ──────────────────────────────┐ │
│ │ - Atesta identidade do workload (PID, K8s SA, etc.) │ │
│ │ - Entrega X.509 certificate ou JWT com SVID │ │
│ │ - Rotação automática de certificados │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
# Com SPIFFE, cada serviço sabe quem está chamando:
# api-server recebe request com certificado:
# Subject: spiffe://company.com/production/frontend
# api-server verifica: frontend pode acessar /api/users? Sim → allow
Network Policies no Kubernetes
# Default deny: bloquear todo tráfego no namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # Aplica a todos os pods
policyTypes:
- Ingress
- Egress
---
# Permitir apenas tráfego específico
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-server-policy
namespace: production
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Ingress
- Egress
ingress:
# Aceitar tráfego apenas do frontend e do ingress controller
- from:
- podSelector:
matchLabels:
app: frontend
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 3000
egress:
# Pode acessar apenas o database e o cache
- to:
- podSelector:
matchLabels:
app: postgresql
ports:
- protocol: TCP
port: 5432
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
# Permitir DNS
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
Microsegmentation
Microsegmentação: firewall rules entre cada par de serviços.
Diferente de segmentação de rede (subnets), microsegmentação
opera no nível do workload individual.
Antes (flat network):
┌─────────────────────────────────────┐
│ Todos os serviços se comunicam │
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │ A │↔│ B │↔│ C │↔│ D │↔│ E │ │
│ └───┘ └───┘ └───┘ └───┘ └───┘ │
└─────────────────────────────────────┘
Depois (microsegmented):
┌─────────────────────────────────────┐
│ Apenas fluxos autorizados │
│ ┌───┐ ┌───┐ ┌───┐ │
│ │ A │─→│ B │─→│ C │ │
│ └───┘ └───┘ └───┘ │
│ │ │
│ ▼ │
│ ┌───┐ ┌───┐ │
│ │ D │←─│ E │ │
│ └───┘ └───┘ │
│ │
│ A→B: permitido A→C: bloqueado │
│ B→C: permitido D→A: bloqueado │
│ C→E: permitido E→B: bloqueado │
└─────────────────────────────────────┘
Implementações:
├── Kubernetes Network Policies (CNI: Calico, Cilium)
├── Service mesh (Istio AuthorizationPolicy)
├── Cilium com eBPF (L3/L4/L7 policies)
└── Cloud-native: AWS Security Groups referencing SGs
Compliance e Governanca
Frameworks de Compliance
┌──────────────┬─────────────────────────────────────────────────────┐
│ Framework │ O que é │
├──────────────┼─────────────────────────────────────────────────────┤
│ SOC 2 │ Controles de segurança para SaaS. │
│ Type I │ Design dos controles em um ponto no tempo. │
│ Type II │ Efetividade dos controles durante 6-12 meses. │
│ │ Maioria dos clientes enterprise exige Type II. │
├──────────────┼─────────────────────────────────────────────────────┤
│ ISO 27001 │ Sistema de gestão de segurança da informação. │
│ │ Certificação internacional. Cobre políticas, │
│ │ processos, controles técnicos. │
├──────────────┼─────────────────────────────────────────────────────┤
│ LGPD │ Lei Geral de Proteção de Dados (Brasil). │
│ │ Consentimento, direito ao acesso/exclusão, │
│ │ DPO obrigatório, notificação de vazamento. │
├──────────────┼─────────────────────────────────────────────────────┤
│ GDPR │ Equivalente europeu da LGPD. │
│ │ Multas de até 4% do revenue global. │
├──────────────┼─────────────────────────────────────────────────────┤
│ PCI-DSS │ Obrigatório se processa/armazena dados de cartão. │
│ │ 12 requisitos rigorosos. │
│ │ Penalidades severas por non-compliance. │
└──────────────┴─────────────────────────────────────────────────────┘
LGPD para Engenheiros
O que a LGPD exige na prática (para engenharia):
1. Minimização de dados:
- Colete apenas o necessário
- Não guarde dados "caso precise no futuro"
- Defina TTL para dados pessoais
2. Criptografia:
- Dados pessoais criptografados at rest e in transit
- Pseudonimização quando possível
3. Direito ao acesso e exclusão:
- API para exportar dados do usuário
- API para deletar dados do usuário (todos os sistemas!)
- Cascata de deleção em todos os serviços e backups
4. Audit trail:
- Log de quem acessou dados pessoais
- Log de consentimento (quando e para quê)
5. Notificação de vazamento:
- Prazo: "prazo razoável" (ANPD recomenda 2 dias úteis)
- Ter runbook de incident response pronto
6. Privacy by Design:
- Segurança e privacidade desde o design, não como patch
- Data Protection Impact Assessment para novos features
Infrastructure Compliance Scanning
# Checkov: scan de IaC para compliance
pip install checkov
# Scan de arquivos Terraform
checkov -d ./terraform/
# Checks:
# - S3 bucket sem encryption ❌
# - Security group com 0.0.0.0/0 no SSH ❌
# - RDS sem backup habilitado ❌
# - CloudTrail sem log file validation ❌
# Scan com framework específico
checkov -d ./terraform/ --framework terraform --check CIS_AWS
# tfsec: análise estática de Terraform (agora parte do Trivy)
trivy config --severity HIGH,CRITICAL ./terraform/
# Exemplo: Terraform configurado para compliance
# CloudTrail: audit log obrigatório
resource "aws_cloudtrail" "main" {
name = "main-trail"
s3_bucket_name = aws_s3_bucket.cloudtrail.id
include_global_service_events = true
is_multi_region_trail = true
enable_log_file_validation = true # Checkov CKV_AWS_36
cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.trail.arn}:*"
cloud_watch_logs_role_arn = aws_iam_role.cloudtrail.arn
event_selector {
read_write_type = "All"
include_management_events = true
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3"]
}
}
kms_key_id = aws_kms_key.cloudtrail.arn # Criptografar logs
}
# AWS Config: monitorar compliance contínua
resource "aws_config_configuration_recorder" "main" {
name = "main"
role_arn = aws_iam_role.config.arn
recording_group {
all_supported = true
include_global_resource_types = true
}
}
# Config Rule: detectar recursos não-compliant
resource "aws_config_config_rule" "encrypted_volumes" {
name = "encrypted-volumes"
source {
owner = "AWS"
source_identifier = "ENCRYPTED_VOLUMES"
}
# Alerta se algum EBS volume não está criptografado
}
Audit Logs e Evidence Collection
Audit logs servem para duas coisas:
1. Investigar incidentes (o que aconteceu?)
2. Provar compliance para auditores (podemos provar que os controles funcionam?)
O que logar:
├── Autenticação: login, logout, MFA, failed attempts
├── Autorização: acesso negado, escalação de privilégio
├── Mudanças: CRUD em dados sensíveis, mudanças de config
├── Acesso a dados: quem leu dados pessoais e quando
└── Sistema: deploy, scaling, mudanças de infra
Onde centralizar:
├── CloudTrail → S3 + Athena (AWS API calls)
├── VPC Flow Logs → S3 ou CloudWatch (rede)
├── Application logs → CloudWatch/Datadog/Elastic
├── Audit logs → SIEM (Splunk, Elastic Security, Datadog)
└── Kubernetes audit logs → Falco + SIEM
Retenção:
├── SOC 2: mínimo 1 ano
├── PCI-DSS: mínimo 1 ano (3 meses online)
├── LGPD: não especifica, mas "razoável"
└── Prática: 1 ano hot, 7 anos cold (S3 Glacier)
Incident Response Plan
Incident Response — todo time de infra precisa de um plano.
Não é para ler durante o incidente. É para treinar antes.
┌── Fases do Incident Response ────────────────────────────────────┐
│ │
│ 1. Preparação │
│ ├── Runbooks documentados │
│ ├── Canais de comunicação definidos (Slack channel, bridge) │
│ ├── Roles: Incident Commander, Scribe, Technical Lead │
│ ├── Ferramentas prontas (acesso a logs, dashboards) │
│ └── Game days regulares (simular incidentes) │
│ │
│ 2. Identificação │
│ ├── Alertas automáticos (monitoria) │
│ ├── Classificar severidade (SEV1-4) │
│ └── Abrir incident ticket │
│ │
│ 3. Contenção │
│ ├── Curto prazo: isolar sistema comprometido │
│ ├── Network isolation, revoke credentials │
│ └── Preservar evidências (não deletar logs!) │
│ │
│ 4. Erradicação │
│ ├── Identificar root cause │
│ ├── Remover acesso do atacante │
│ └── Patchar vulnerabilidade explorada │
│ │
│ 5. Recuperação │
│ ├── Restaurar serviços │
│ ├── Monitorar de perto │
│ └── Verificar que atacante não manteve acesso │
│ │
│ 6. Post-mortem │
│ ├── Timeline detalhada │
│ ├── Root cause analysis (blameless) │
│ ├── Action items com owners e deadlines │
│ └── Compartilhar aprendizados com a empresa │
└────────────────────────────────────────────────────────────────────┘
Exercicios
Exercicio 1: Hardening de Servidor com CIS Benchmark
Crie um script de hardening baseado nos CIS Benchmarks para Ubuntu 22.04. O script deve:
#!/bin/bash
# hardening.sh — CIS Level 1 Benchmark (subset)
set -euo pipefail
echo "=== CIS Hardening Script ==="
# 1. Desabilitar filesystems desnecessários
echo "install cramfs /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install squashfs /bin/true" >> /etc/modprobe.d/CIS.conf
echo "install udf /bin/true" >> /etc/modprobe.d/CIS.conf
# 2. Configurar sysctl (kernel hardening)
cat >> /etc/sysctl.d/99-cis.conf << 'EOF'
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.log_martians = 1
net.ipv4.tcp_syncookies = 1
kernel.randomize_va_space = 2
kernel.dmesg_restrict = 1
EOF
sysctl --system
# 3. Configurar SSH
sed -i 's/#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/#MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config
sed -i 's/X11Forwarding yes/X11Forwarding no/' /etc/ssh/sshd_config
echo "ClientAliveInterval 300" >> /etc/ssh/sshd_config
echo "ClientAliveCountMax 2" >> /etc/ssh/sshd_config
systemctl restart sshd
# 4. Configurar auditd
apt install -y auditd
cat >> /etc/audit/rules.d/cis.rules << 'EOF'
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/ssh/sshd_config -p wa -k sshd
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -k privileged
EOF
systemctl enable auditd
systemctl restart auditd
# 5. Remover serviços desnecessários
systemctl disable --now avahi-daemon 2>/dev/null || true
systemctl disable --now cups 2>/dev/null || true
systemctl disable --now rpcbind 2>/dev/null || true
apt purge -y telnet rsh-client 2>/dev/null || true
# 6. Configurar fail2ban
apt install -y fail2ban
cat > /etc/fail2ban/jail.local << 'EOF'
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
EOF
systemctl enable --now fail2ban
# 7. Permissões de arquivos críticos
chmod 644 /etc/passwd
chmod 600 /etc/shadow
chmod 644 /etc/group
chmod 600 /etc/gshadow
echo "=== Hardening completo. Verifique com: sudo lynis audit system ==="
Valide o resultado com Lynis:
# Instalar Lynis (auditor de segurança)
apt install -y lynis
# Rodar audit
lynis audit system
# Score alvo: >80 (sem hardening fica ~60)
Exercicio 2: Vault para Credenciais Dinamicas de Database
Configure o HashiCorp Vault para gerar credenciais PostgreSQL dinamicas com TTL de 1 hora:
# 1. Subir Vault em modo dev (apenas para exercício)
docker run -d --name vault \
-p 8200:8200 \
-e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' \
hashicorp/vault:latest
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='myroot'
# 2. Subir PostgreSQL
docker run -d --name postgres \
-p 5432:5432 \
-e POSTGRES_PASSWORD=adminpassword \
-e POSTGRES_DB=myapp \
postgres:16
# 3. Configurar engine de database no Vault
vault secrets enable database
vault write database/config/myapp \
plugin_name="postgresql-database-plugin" \
allowed_roles="readonly","readwrite" \
connection_url="postgresql://{{username}}:{{password}}@host.docker.internal:5432/myapp?sslmode=disable" \
username="postgres" \
password="adminpassword"
# 4. Criar role readonly
vault write database/roles/readonly \
db_name="myapp" \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' \
VALID UNTIL '{{expiration}}'; \
GRANT SELECT 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"
# 5. Criar role readwrite
vault write database/roles/readwrite \
db_name="myapp" \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' \
VALID UNTIL '{{expiration}}'; \
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="4h"
# 6. Testar: solicitar credenciais
vault read database/creds/readonly
# username: v-token-readonly-abc123
# password: randomized-password
# lease_duration: 1h
# 7. Conectar com as credenciais dinâmicas
psql "postgresql://v-token-readonly-abc123:randomized-password@localhost:5432/myapp"
# 8. Verificar: credencial expira automaticamente após 1h
vault lease revoke -prefix database/creds/
Exercicio 3: Pipeline de CI/CD com Security Gates
Crie um GitHub Actions workflow que integra scanning de seguranca como gates obrigatorios:
# .github/workflows/security-pipeline.yml
name: Security Pipeline
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
# Gate 1: Secret scanning (bloqueia se encontrar secrets)
secret-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Gate 2: Dependency vulnerabilities
dependency-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Trivy Dependency Scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
severity: 'HIGH,CRITICAL'
exit-code: '1'
# Gate 3: SAST (static analysis)
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: semgrep/semgrep-action@v1
with:
config: p/security-audit p/owasp-top-ten
# Gate 4: Container image scan
container-scan:
runs-on: ubuntu-latest
needs: [secret-scan, dependency-scan, sast]
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Trivy Image Scan
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
severity: 'HIGH,CRITICAL'
exit-code: '1'
- name: Sign image with cosign
if: github.ref == 'refs/heads/main'
uses: sigstore/cosign-installer@v3
- run: cosign sign --yes myregistry.com/myapp:${{ github.sha }}
if: github.ref == 'refs/heads/main'
# Gate 5: IaC compliance scan
iac-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkov IaC Scan
uses: bridgecrewio/checkov-action@master
with:
directory: ./terraform/
framework: terraform
soft_fail: false
# Deploy: apenas se todos os gates passarem
deploy:
runs-on: ubuntu-latest
needs: [container-scan, iac-scan]
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to production
run: echo "All security gates passed. Deploying..."
Exercicio 4: Network Policy Zero Trust no Kubernetes
Implemente Network Policies que seguem o modelo Zero Trust para uma aplicacao com frontend, API e database:
# 1. Default deny em todo o namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: myapp
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# 2. Frontend: recebe do ingress, acessa apenas a API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-policy
namespace: myapp
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 80
egress:
- to:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 3000
- to: # DNS
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
---
# 3. API: recebe do frontend, acessa database e redis
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-policy
namespace: myapp
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 3000
egress:
- to:
- podSelector:
matchLabels:
app: postgresql
ports:
- protocol: TCP
port: 5432
- to:
- podSelector:
matchLabels:
app: redis
ports:
- protocol: TCP
port: 6379
- to: # DNS
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
---
# 4. Database: recebe APENAS da API, sem egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgresql-policy
namespace: myapp
spec:
podSelector:
matchLabels:
app: postgresql
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- protocol: TCP
port: 5432
# Sem regras de egress = sem saída permitida
# Teste: kubectl exec frontend -- curl api:3000 (funciona)
# Teste: kubectl exec frontend -- curl postgresql:5432 (bloqueado)
# Teste: kubectl exec api -- curl frontend:80 (bloqueado)
Seguranca de infraestrutura nao e um destino, e uma jornada. Nao existe “seguro” como estado binario — existe “mais seguro” e “menos seguro”. O objetivo e tornar o custo de um ataque maior que o valor do que voce esta protegendo, em cada camada, o tempo todo. Comece pelo basico (secrets management, scanning automatizado, least privilege), e evolua para zero trust e compliance conforme a maturidade do time e os requisitos do negocio exigirem.
Referencias e Fontes
- OWASP — https://owasp.org/ — referência em segurança de aplicações web
- “The Phoenix Project” — Gene Kim, Kevin Behr, George Spafford — DevOps e segurança integrada
- CIS Benchmarks — https://www.cisecurity.org/cis-benchmarks — hardening guides para OS, cloud, containers
- NIST Cybersecurity Framework — https://www.nist.gov/cyberframework — framework de segurança do governo americano
- HashiCorp Vault Documentation — https://developer.hashicorp.com/vault/docs — secrets management
- “Zero Trust Networks” — Evan Gilman & Doug Barth — arquitetura zero trust