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.
Dado
Como é armazenado
Chaves de API dos gateways
AES-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ão
Nunca 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ção
Comportamento
Status HTTP
Header x-api-key ausente
Rejeitado antes de qualquer processamento
401
Chave inválida (hash não encontrado)
Rejeitado pelo ApiKeyAuthFilter
401
Chave válida, tenant INACTIVE
Rejeitado pelo GlobalExceptionHandler
403
Chave válida, tenant ACTIVE
Requisiçã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 attacksreturn 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 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).