Segurança em camadas

Como aplicar segurança em cada camada da arquitetura: frontend, BFF, microsserviços e banco de dados, com estratégias específicas para cada nível.

O princípio da defesa em profundidade

A segurança em uma arquitetura distribuída não pode depender de um único ponto de controle. Se um atacante superar uma barreira, deve encontrar outra. Esse princípio se chama defesa em profundidade: cada camada implementa suas próprias medidas de segurança, independentemente do que fazem as demais.

[Frontend] → [API Gateway] → [BFF] → [Microsserviços] → [Banco de dados]
  Camada 1       Camada 2     Camada 3      Camada 4          Camada 5

Cada camada tem ameaças específicas e, portanto, precisa de proteções específicas.

Segurança no Frontend

O frontend é a superfície de ataque mais exposta: qualquer usuário pode inspecionar o código, manipular requests e modificar o DOM.

O que proteger

  • Inputs do usuário: todo dado que o usuário insere é potencialmente malicioso
  • Tokens de sessão: se um atacante obtiver o token, pode se passar pelo usuário
  • Dados sensíveis: nunca armazenar segredos, chaves de API ou dados confidenciais no frontend

Estratégias-chave

Sanitização de inputs

Nunca confie nos dados do usuário. Valide e sanitize no frontend como primeira linha de defesa, mas sempre valide também no backend.

// ❌ Perigoso: inserir HTML do usuário diretamente
element.innerHTML = userInput;

// ✅ Seguro: usar textContent ou sanitizar
element.textContent = userInput;
// Ou usar uma biblioteca de sanitização como DOMPurify
element.innerHTML = DOMPurify.sanitize(userInput);

Armazenamento seguro de tokens

// ❌ Vulnerável a XSS: localStorage é acessível a partir de qualquer script
localStorage.setItem('token', jwt);

// ✅ Melhor: cookies httpOnly (configuradas pelo servidor)
// O frontend não consegue acessar o cookie, mas ele é enviado automaticamente
Set-Cookie: token=jwt; HttpOnly; Secure; SameSite=Strict

Content Security Policy (CSP)

Configure headers de CSP para limitar quais recursos a página pode carregar:

Content-Security-Policy: 
  default-src 'self';
  script-src 'self';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  connect-src 'self' https://api.ejemplo.com;

Segurança no API Gateway

O API Gateway é o ponto de entrada único do sistema. É o lugar ideal para implementar controles de segurança transversais.

Responsabilidades de segurança

  1. Terminação TLS: todas as conexões externas devem ser HTTPS
  2. Rate limiting: limitar o número de requests por IP ou por usuário
  3. Validação de tokens: verificar se o JWT é válido antes de rotear
  4. Filtragem de headers: remover headers internos que não devem chegar ao exterior
  5. Logging de acesso: registrar cada request para auditoria

Rate limiting

# Exemplo de configuração de rate limiting
rate_limit:
  global:
    requests_per_second: 100
  per_ip:
    requests_per_minute: 60
  per_user:
    requests_per_minute: 120
  endpoints:
    /auth/login:
      requests_per_minute: 5  # Mais restritivo para login

Validação de tokens no Gateway

Request recebido
  → Tem header Authorization?
    → Não → É rota pública? → Sim → Permitir
                               → Não → 401 Unauthorized
    → Sim → Token válido (assinatura, expiração)?
      → Não → 401 Unauthorized
      → Sim → Extrair claims, anexar ao request, rotear

Segurança no BFF

O BFF (Backend for Frontend) atua como intermediário entre o frontend e os microsserviços. Tem responsabilidades de segurança específicas.

Responsabilidades

  1. Transformação segura de dados: nunca expor dados internos ao frontend
  2. Validação de inputs: segunda linha de validação após o frontend
  3. Orquestração segura: ao combinar dados de múltiplos serviços, respeitar as permissões do usuário
  4. Tratamento de erros: não vazar informações internas nas mensagens de erro

Exemplo: filtragem de dados sensíveis

// Resposta do microsserviço (dados internos)
const userFromService = {
  id: "user-123",
  email: "maria@ejemplo.com",
  passwordHash: "$2b$10$...",  // Nunca expor!
  internalId: "db-row-456",    // Dado interno!
  role: "admin",
  lastLogin: "2024-01-15T10:30:00Z"
};

// Resposta do BFF ao frontend (dados filtrados)
const userForFrontend = {
  id: userFromService.id,
  email: userFromService.email,
  role: userFromService.role,
  lastLogin: userFromService.lastLogin
};

Tratamento seguro de erros

// ❌ Expõe informação interna
res.status(500).json({
  error: "Connection refused to postgres://db-host:5432/users"
});

// ✅ Erro genérico para o frontend
res.status(500).json({
  error: "Erro interno do servidor. Tente novamente."
});
// O detalhe é registrado nos logs internos

Segurança nos Microsserviços

Cada microsserviço deve assumir que os requests que recebe podem ser maliciosos, mesmo que venham de outros serviços internos.

Zero Trust entre serviços

O modelo Zero Trust estabelece que nenhum serviço confia automaticamente em outro, mesmo dentro da mesma rede:

  • Cada serviço verifica a identidade de quem chamou
  • Cada serviço valida as permissões para a operação solicitada
  • A comunicação entre serviços é criptografada (mTLS)

Validação de inputs em cada serviço

// Cada microsserviço valida seus próprios inputs
function createOrder(data) {
  // Validar estrutura
  if (!data.userId || !data.items || data.items.length === 0) {
    throw new ValidationError("Datos de orden inválidos");
  }
  
  // Validar tipos e intervalos
  if (typeof data.userId !== 'string' || data.userId.length > 50) {
    throw new ValidationError("userId inválido");
  }
  
  for (const item of data.items) {
    if (item.quantity < 1 || item.quantity > 1000) {
      throw new ValidationError("Cantidad fuera de rango");
    }
  }
  
  // Processar somente após validar
  return orderRepository.create(data);
}

Princípio do menor privilégio

Cada microsserviço só deve ter acesso aos recursos de que precisa:

  • O serviço de pedidos não precisa de acesso à tabela de usuários
  • O serviço de notificações não precisa poder modificar pedidos
  • As credenciais de banco de dados são exclusivas por serviço

Segurança no Banco de Dados

O banco de dados é a última linha de defesa. Se um atacante chegar até aqui, os dados devem estar protegidos.

Estratégias-chave

Criptografia em repouso

Os dados sensíveis devem estar criptografados em disco:

-- Exemplo: colunas criptografadas
CREATE TABLE users (
  id UUID PRIMARY KEY,
  email VARCHAR(255) NOT NULL,
  password_hash VARCHAR(255) NOT NULL,  -- Hash, não criptografia reversível
  ssn_encrypted BYTEA,                  -- Dado sensível criptografado com AES-256
  created_at TIMESTAMP DEFAULT NOW()
);

Acesso com menor privilégio

-- Cada serviço tem seu próprio usuário de BD com permissões limitadas
-- Serviço de pedidos: acesso somente às tabelas de pedidos
GRANT SELECT, INSERT, UPDATE ON orders TO orders_service;
GRANT SELECT ON products TO orders_service;
-- Não tem acesso à tabela de usuários

-- Serviço de usuários: acesso somente às tabelas de usuários
GRANT SELECT, INSERT, UPDATE ON users TO users_service;
-- Não tem acesso à tabela de pedidos

Prevenção de injeção de SQL

// ❌ Vulnerável a injeção de SQL
const query = `SELECT * FROM users WHERE email = '${email}'`;

// ✅ Consulta parametrizada
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [email]);

Auditoria de acesso

Registre quem acessa quais dados e quando:

CREATE TABLE audit_log (
  id SERIAL PRIMARY KEY,
  table_name VARCHAR(100),
  operation VARCHAR(10),  -- SELECT, INSERT, UPDATE, DELETE
  user_id VARCHAR(100),
  service_name VARCHAR(100),
  timestamp TIMESTAMP DEFAULT NOW(),
  details JSONB
);

Resumo

A segurança em camadas garante que não exista um único ponto de falha. Cada camada — frontend, API Gateway, BFF, microsserviços e banco de dados — implementa suas próprias defesas. O frontend sanitiza inputs e protege tokens, o Gateway controla o acesso e limita o tráfego, o BFF filtra dados sensíveis, os microsserviços validam tudo e aplicam o menor privilégio, e o banco de dados criptografa e audita. Juntas, essas camadas criam um sistema em que comprometer uma não significa comprometer todas.