Segurança

5 min de leitura

Camadas de segurança implementadas no Orquestraio — desde autenticação de requisições até criptografia de credenciais em repouso.

Credenciais de Gateway em Repouso (AES-256)

As credenciais de cada gateway (chaves de API do Stripe, MercadoPago, PayPal) são armazenadas criptografadas no banco de dados usando AES-256-GCM. A chave mestre não é persistida no banco — ela é injetada via variável de ambiente no startup da aplicação.

DadoComo é armazenado
Chaves de API dos gatewaysAES-256-GCM, criptografado em repouso
API keys dos tenants (x-api-key)Hash SHA-256 — nunca o valor bruto
Dados do cliente (customer)Armazenados normalmente — não são dados de cartão
Números de cartãoNunca armazenados — processados somente via token
PCI-DSS scope reduzido: Como o Orquestraio não armazena dados brutos de cartão (somente tokens), o escopo de compliance PCI-DSS do seu sistema é significativamente reduzido. Você ainda é responsável pela tokenização no frontend.

TLS em Trânsito

Toda comunicação entre o cliente e a API do Orquestraio exige TLS 1.2 ou superior. Requisições HTTP puro são rejeitadas. Internamente, a comunicação com os gateways também é realizada sobre HTTPS com validação de certificado.

Ambientes de desenvolvimento: No ambiente local com Docker, TLS não é obrigatório (http://localhost:8080). Em produção, configure um reverse proxy (Nginx, Caddy) com certificado válido à frente da aplicação.

Segurança das API Keys

As API keys dos tenants são o único mecanismo de autenticação da API. O Orquestraio armazena apenas o hash SHA-256 da chave — o valor original não pode ser recuperado após a criação.

SituaçãoComportamentoStatus HTTP
Header x-api-key ausenteRejeitado antes de qualquer processamento401
Chave inválida (hash não encontrado)Rejeitado pelo ApiKeyAuthFilter401
Chave válida, tenant INACTIVERejeitado pelo GlobalExceptionHandler403
Chave válida, tenant ACTIVERequisição processada normalmente
Se sua chave for comprometida: Revogue imediatamente pelo painel de administração — a revogação é instantânea e não causa downtime. Uma nova chave pode ser gerada no mesmo momento. Nunca compartilhe chaves de produção (Oz_live_) entre ambientes.

Verificação de Webhooks (HMAC-SHA256)

Webhooks são assinados com HMAC-SHA256 usando um secret exclusivo por tenant. Verificar a assinatura é obrigatório em produção — sem isso, qualquer pessoa que conheça sua URL de webhook pode injetar eventos falsos.

Cada notificação inclui o header X-Orquestraio-Signature no formato:

X-Orquestraio-Signature: sha256=a1b2c3d4e5f6...

Para validar, compute o HMAC-SHA256 do body bruto da requisição usando seu webhook secret, e compare com o valor do header.

const crypto = require('crypto'); function verifyWebhookSignature(rawBody, signatureHeader, secret) { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody) // rawBody deve ser Buffer ou string sem parse .digest('hex'); // Comparação segura — evita timing attacks return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signatureHeader) ); } // Uso em Express — IMPORTANTE: usar express.raw() antes do json() app.post('/webhooks/orquestraio', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['x-orquestraio-signature']; const secret = process.env.ORQUESTRAIO_WEBHOOK_SECRET; if (!verifyWebhookSignature(req.body, sig, secret)) { return res.status(401).json({ error: 'Assinatura invalida' }); } const event = JSON.parse(req.body); // processar evento... res.status(200).json({ received: true }); });
import hmac, hashlib, os from flask import request, jsonify def verify_webhook_signature(raw_body: bytes, signature_header: str, secret: str) -> bool: expected = 'sha256=' + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() # compare_digest evita timing attacks return hmac.compare_digest(expected, signature_header) @app.route('/webhooks/orquestraio', methods=['POST']) def webhook(): sig = request.headers.get('X-Orquestraio-Signature', '') secret = os.environ['ORQUESTRAIO_WEBHOOK_SECRET'] if not verify_webhook_signature(request.get_data(), sig, secret): return jsonify({'error': 'Assinatura invalida'}), 401 event = request.json # processar evento... return jsonify({'received': True}), 200
import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; public class WebhookVerifier { public static boolean verify(byte[] rawBody, String signatureHeader, String secret) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256")); String computed = "sha256=" + bytesToHex(mac.doFinal(rawBody)); // MessageDigest.isEqual faz comparação em tempo constante return MessageDigest.isEqual(computed.getBytes(), signatureHeader.getBytes()); } private static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) sb.append(String.format("%02x", b)); return sb.toString(); } }
Nunca use == para comparar assinaturas. Comparação direta de strings é vulnerável a timing attacks — um atacante pode inferir a assinatura correta medindo o tempo de resposta. Sempre use crypto.timingSafeEqual (Node.js), hmac.compare_digest (Python) ou MessageDigest.isEqual (Java).

Checklist de Segurança para Produção

Checklist