Mejores Prácticas de Seguridad

Directrices de seguridad para implementaciones en producción

Mejores Prácticas de Seguridad

Directrices de seguridad esenciales para implementaciones en producción.

Seguridad de Credenciales

Almacenamiento de API Keys

Nunca hacer:

  • ❌ Codificar credenciales en código fuente
  • ❌ Incluir credenciales en repositorios Git
  • ❌ Compartir credenciales por email o chat
  • ❌ Usar mismas credenciales en múltiples entornos
  • ❌ Registrar credenciales en logs

Siempre hacer:

  • ✅ Usar variables de entorno
  • ✅ Usar servicios de gestión de secretos (AWS Secrets Manager, Azure Key Vault)
  • ✅ Rotar credenciales regularmente
  • ✅ Usar credenciales diferentes para sandbox y producción
  • ✅ Limitar acceso a credenciales (principio de menor privilegio)

Implementación

1// .env (NUNCA commitear este archivo)
2KOYWE_API_KEY=tu_api_key_aqui
3KOYWE_SECRET=tu_secret_aqui
4KOYWE_ORG_ID=tu_org_id
5KOYWE_MERCHANT_ID=tu_merchant_id
6
7// app.js
8require('dotenv').config();
9
10const koyweConfig = {
11 apiKey: process.env.KOYWE_API_KEY,
12 secret: process.env.KOYWE_SECRET,
13 orgId: process.env.KOYWE_ORG_ID,
14 merchantId: process.env.KOYWE_MERCHANT_ID
15};
16
17// Validar que existen
18if (!koyweConfig.apiKey || !koyweConfig.secret) {
19 throw new Error('Credenciales Koywe faltantes');
20}

Seguridad de Webhooks

Verificar Firmas

Verificación de Firma
1const crypto = require('crypto');
2
3function verifyWebhookSignature(payload, signature, secret) {
4 // payload DEBE ser el body crudo (Buffer o string)
5 const expectedSignature = crypto
6 .createHmac('sha256', secret)
7 .update(payload)
8 .digest('hex');
9
10 // Comparación tiempo-constante para prevenir timing attacks
11 return crypto.timingSafeEqual(
12 Buffer.from(signature),
13 Buffer.from(expectedSignature)
14 );
15}
16
17// Express - usar express.raw
18app.post('/webhooks/koywe',
19 express.raw({ type: 'application/json' }),
20 (req, res) => {
21 const signature = req.headers['koywe-signature'];
22 const secret = process.env.KOYWE_WEBHOOK_SECRET;
23
24 if (!verifyWebhookSignature(req.body, signature, secret)) {
25 return res.status(401).send('Firma inválida');
26 }
27
28 // Procesar webhook...
29 res.status(200).send('OK');
30 }
31);

Proteger Endpoint

Límite de Tasa
1const rateLimit = require('express-rate-limit');
2
3// Limitar webhook requests
4const webhookLimiter = rateLimit({
5 windowMs: 1 * 60 * 1000, // 1 minuto
6 max: 100, // máximo 100 requests por minuto
7 message: 'Demasiadas solicitudes, intenta más tarde'
8});
9
10app.post('/webhooks/koywe',
11 webhookLimiter,
12 express.raw({ type: 'application/json' }),
13 handleWebhook
14);

Seguridad de Transporte

Usar HTTPS

Requisito de Producción: Todos los endpoints de webhook DEBEN usar HTTPS en producción.

Validar HTTPS
1app.post('/webhooks/koywe', (req, res) => {
2 // Rechazar requests no HTTPS en producción
3 if (process.env.NODE_ENV === 'production' && req.protocol !== 'https') {
4 return res.status(403).send('HTTPS requerido');
5 }
6
7 // Procesar webhook...
8});

Seguridad de Datos

Redactar Información Sensible

Logging Seguro
1function sanitizeForLogging(data) {
2 const sensitiveFields = [
3 'apiKey',
4 'secret',
5 'token',
6 'password',
7 'accountNumber',
8 'cardNumber',
9 'cvv'
10 ];
11
12 const sanitized = { ...data };
13
14 for (const field of sensitiveFields) {
15 if (sanitized[field]) {
16 sanitized[field] = '***REDACTED***';
17 }
18 }
19
20 return sanitized;
21}
22
23// Uso
24console.log('Orden creada:', sanitizeForLogging(orderData));

Encriptar Datos Sensibles

Encriptar en Reposo
1const crypto = require('crypto');
2
3class SecureStorage {
4 constructor(encryptionKey) {
5 this.algorithm = 'aes-256-gcm';
6 this.key = Buffer.from(encryptionKey, 'hex');
7 }
8
9 encrypt(text) {
10 const iv = crypto.randomBytes(16);
11 const cipher = crypto.createCipheriv(this.algorithm, this.key, iv);
12
13 let encrypted = cipher.update(text, 'utf8', 'hex');
14 encrypted += cipher.final('hex');
15
16 const authTag = cipher.getAuthTag();
17
18 return {
19 encrypted,
20 iv: iv.toString('hex'),
21 authTag: authTag.toString('hex')
22 };
23 }
24
25 decrypt(encrypted, iv, authTag) {
26 const decipher = crypto.createDecipheriv(
27 this.algorithm,
28 this.key,
29 Buffer.from(iv, 'hex')
30 );
31
32 decipher.setAuthTag(Buffer.from(authTag, 'hex'));
33
34 let decrypted = decipher.update(encrypted, 'hex', 'utf8');
35 decrypted += decipher.final('utf8');
36
37 return decrypted;
38 }
39}
40
41// Uso
42const storage = new SecureStorage(process.env.ENCRYPTION_KEY);
43const encrypted = storage.encrypt('datos-sensibles');

Validación de Entrada

Sanitizar Datos de Usuario

Validación de Entrada
1const validator = require('validator');
2
3function validateOrderInput(orderData) {
4 const errors = [];
5
6 // Validar amount
7 if (!orderData.amount || typeof orderData.amount !== 'number') {
8 errors.push('Monto inválido');
9 }
10
11 if (orderData.amount <= 0 || orderData.amount > 10000000) {
12 errors.push('Monto fuera de rango');
13 }
14
15 // Validar currency
16 const validCurrencies = ['COP', 'USD', 'BRL', 'MXN', 'CLP'];
17 if (!validCurrencies.includes(orderData.currency)) {
18 errors.push('Moneda inválida');
19 }
20
21 // Validar email si existe
22 if (orderData.email && !validator.isEmail(orderData.email)) {
23 errors.push('Email inválido');
24 }
25
26 // Prevenir inyección en externalId
27 if (orderData.externalId) {
28 orderData.externalId = validator.escape(orderData.externalId);
29 }
30
31 if (errors.length > 0) {
32 throw new Error(`Validación fallida: ${errors.join(', ')}`);
33 }
34
35 return orderData;
36}

Control de Acceso

Implementar RBAC

Middleware de Autorización
1const permissions = {
2 'admin': ['read', 'write', 'delete'],
3 'operator': ['read', 'write'],
4 'viewer': ['read']
5};
6
7function requirePermission(permission) {
8 return (req, res, next) => {
9 const userRole = req.user.role;
10 const userPermissions = permissions[userRole] || [];
11
12 if (!userPermissions.includes(permission)) {
13 return res.status(403).json({
14 error: 'Permisos insuficientes'
15 });
16 }
17
18 next();
19 };
20}
21
22// Uso
23app.post('/api/orders',
24 authenticate,
25 requirePermission('write'),
26 createOrderHandler
27);

Auditoría y Monitoreo

Registrar Eventos de Seguridad

Logger de Auditoría
1class SecurityAuditLogger {
2 static async log(event, details) {
3 const auditEntry = {
4 timestamp: new Date().toISOString(),
5 event,
6 userId: details.userId,
7 ip: details.ip,
8 userAgent: details.userAgent,
9 action: details.action,
10 resource: details.resource,
11 success: details.success,
12 reason: details.reason
13 };
14
15 // Registrar en sistema seguro
16 await db.query(
17 `INSERT INTO security_audit_log
18 (timestamp, event, user_id, ip, action, success)
19 VALUES ($1, $2, $3, $4, $5, $6)`,
20 [
21 auditEntry.timestamp,
22 auditEntry.event,
23 auditEntry.userId,
24 auditEntry.ip,
25 auditEntry.action,
26 auditEntry.success
27 ]
28 );
29 }
30}
31
32// Uso
33await SecurityAuditLogger.log('AUTHENTICATION_ATTEMPT', {
34 userId: user.id,
35 ip: req.ip,
36 userAgent: req.headers['user-agent'],
37 action: 'login',
38 success: true
39});

Lista de Verificación de Seguridad

Antes de Producción

  • Credenciales almacenadas en variables de entorno/secrets manager
  • HTTPS habilitado en todos los endpoints
  • Verificación de firma de webhook implementada
  • Límite de tasa configurado
  • Logging de seguridad habilitado
  • Datos sensibles redactados en logs
  • Validación de entrada implementada
  • Encripción en reposo para datos sensibles
  • Monitoreo y alertas configurados
  • Plan de respuesta a incidentes documentado

Respuesta a Incidentes

Plan de Respuesta

  1. Detectar: Monitoreo y alertas automatizadas
  2. Contener: Revocar credenciales comprometidas
  3. Erradicar: Identificar y corregir vulnerabilidad
  4. Recuperar: Restaurar servicio normal
  5. Lecciones Aprendidas: Documentar y mejorar
Revocar Credenciales
1async function emergencyCredentialRevocation() {
2 // 1. Deshabilitar API keys comprometidas
3 await disableApiKey(compromisedApiKey);
4
5 // 2. Generar nuevas credenciales
6 const newCredentials = await generateNewApiKey();
7
8 // 3. Notificar equipo
9 await sendSecurityAlert({
10 type: 'CREDENTIAL_COMPROMISED',
11 action: 'REVOKED_AND_REGENERATED',
12 newKeyId: newCredentials.id
13 });
14
15 // 4. Actualizar sistemas
16 await updateProductionCredentials(newCredentials);
17
18 return newCredentials;
19}

Cumplimiento

GDPR / Protección de Datos

Requisitos de Retención de Datos:

  • Almacenar solo datos necesarios
  • Implementar derecho al olvido
  • Proporcionar exportación de datos
  • Documentar procesamiento de datos

Próximos Pasos