Skip to Content
Temas AvanzadosMejores Prácticas de Seguridad

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

// .env (NUNCA commitear este archivo) KOYWE_API_KEY=tu_api_key_aqui KOYWE_SECRET=tu_secret_aqui KOYWE_ORG_ID=tu_org_id KOYWE_MERCHANT_ID=tu_merchant_id // app.js require('dotenv').config(); const koyweConfig = { apiKey: process.env.KOYWE_API_KEY, secret: process.env.KOYWE_SECRET, orgId: process.env.KOYWE_ORG_ID, merchantId: process.env.KOYWE_MERCHANT_ID }; // Validar que existen if (!koyweConfig.apiKey || !koyweConfig.secret) { throw new Error('Credenciales Koywe faltantes'); }
import os from dotenv import load_dotenv load_dotenv() KOYWE_CONFIG = { 'api_key': os.getenv('KOYWE_API_KEY'), 'secret': os.getenv('KOYWE_SECRET'), 'org_id': os.getenv('KOYWE_ORG_ID'), 'merchant_id': os.getenv('KOYWE_MERCHANT_ID') } # Validar if not all(KOYWE_CONFIG.values()): raise ValueError('Credenciales Koywe faltantes')

Rotación de Secretos API

La rotación regular de secretos es una práctica de seguridad crítica. Rota tus secretos API:

  • Por calendario: Se recomienda rotación trimestral
  • Después de eventos de seguridad: Compromiso sospechado, desvinculación de empleados
  • Por cumplimiento: Muchos marcos de seguridad requieren rotación periódica

Endpoint de Rotación

Endpoint: POST /api/v1/auth/credentials/rotate-secret

Autenticación: Se requiere token Bearer

Restricción: Solo usuarios API pueden rotar secretos (no usuarios autenticados por email)

Cómo Rotar

async function rotarSecretoApi(tokenActual) { // Paso 1: Llamar endpoint de rotación con token actual const response = await axios.post( 'https://api.koywe.com/api/v1/auth/credentials/rotate-secret', {}, { headers: { 'Authorization': `Bearer ${tokenActual}` } } ); const nuevoSecreto = response.data.secret; console.log('Nuevo secreto generado (¡almacenar inmediatamente!)'); // Paso 2: Almacenar nuevo secreto en tu gestor de secretos await secretManager.update('KOYWE_SECRET', nuevoSecreto); // Paso 3: Verificar que el nuevo secreto funciona const verifyResponse = await axios.post( 'https://api.koywe.com/api/v1/auth/sign-in', { apiKey: process.env.KOYWE_API_KEY, secret: nuevoSecreto } ); if (verifyResponse.data.token) { console.log('✓ Nuevo secreto verificado exitosamente'); // Paso 4: Eliminar secreto antiguo del vault await secretManager.delete('KOYWE_SECRET_OLD'); } return nuevoSecreto; }
def rotar_secreto_api(token_actual): # Paso 1: Llamar endpoint de rotación con token actual response = requests.post( 'https://api.koywe.com/api/v1/auth/credentials/rotate-secret', headers={'Authorization': f'Bearer {token_actual}'} ) nuevo_secreto = response.json()['secret'] print('Nuevo secreto generado (¡almacenar inmediatamente!)') # Paso 2: Almacenar nuevo secreto en tu gestor de secretos secret_manager.update('KOYWE_SECRET', nuevo_secreto) # Paso 3: Verificar que el nuevo secreto funciona verify_response = requests.post( 'https://api.koywe.com/api/v1/auth/sign-in', json={ 'apiKey': os.environ['KOYWE_API_KEY'], 'secret': nuevo_secreto } ) if verify_response.json().get('token'): print('✓ Nuevo secreto verificado exitosamente') # Paso 4: Eliminar secreto antiguo del vault secret_manager.delete('KOYWE_SECRET_OLD') return nuevo_secreto
# Rotar secreto curl -X POST 'https://api.koywe.com/api/v1/auth/credentials/rotate-secret' \ -H 'Authorization: Bearer TU_TOKEN_ACTUAL' # Respuesta: { "secret": "2af81190b3a153r48a3df3a1eefcc386ca763b99fba53d39666751ffd4e2ae81" }

Importante: El nuevo secreto solo se muestra una vez en la respuesta. Almacénalo inmediatamente en tu gestor de secretos antes de descartar el secreto antiguo.

Mejores Prácticas de Rotación

Lista de Verificación de Rotación:

  • Respaldar secreto actual antes de rotar
  • Almacenar nuevo secreto en gestor de secretos inmediatamente
  • Verificar que el nuevo secreto funciona antes de eliminar el antiguo
  • Actualizar todas las aplicaciones/servicios que usan el secreto
  • Documentar la rotación para auditoría

Manejo de Errores

async function rotarSecretoConReintentos(tokenActual, maxReintentos = 3) { for (let intento = 1; intento <= maxReintentos; intento++) { try { const nuevoSecreto = await rotarSecretoApi(tokenActual); return nuevoSecreto; } catch (error) { console.error(`Intento ${intento} falló:`, error.message); if (error.response?.status === 401) { throw new Error('Token inválido - no se puede rotar'); } if (intento === maxReintentos) { throw new Error('Rotación de secreto falló después de múltiples intentos'); } // Esperar antes de reintentar await new Promise(resolve => setTimeout(resolve, 1000 * intento)); } } }

Seguridad de Webhooks

Verificar Firmas

const crypto = require('crypto'); function verifyWebhookSignature(payload, signature, secret) { // payload DEBE ser el body crudo (Buffer o string) const expectedSignature = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); // Comparación tiempo-constante para prevenir timing attacks return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } // Express - usar express.raw app.post('/webhooks/koywe', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['koywe-signature']; const secret = process.env.KOYWE_WEBHOOK_SECRET; if (!verifyWebhookSignature(req.body, signature, secret)) { return res.status(401).send('Firma inválida'); } // Procesar webhook... res.status(200).send('OK'); } );

Proteger Endpoint

const rateLimit = require('express-rate-limit'); // Limitar webhook requests const webhookLimiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1 minuto max: 100, // máximo 100 requests por minuto message: 'Demasiadas solicitudes, intenta más tarde' }); app.post('/webhooks/koywe', webhookLimiter, express.raw({ type: 'application/json' }), handleWebhook );

Seguridad de Transporte

Usar HTTPS

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

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

Seguridad de Datos

Redactar Información Sensible

function sanitizeForLogging(data) { const sensitiveFields = [ 'apiKey', 'secret', 'token', 'password', 'accountNumber', 'cardNumber', 'cvv' ]; const sanitized = { ...data }; for (const field of sensitiveFields) { if (sanitized[field]) { sanitized[field] = '***REDACTED***'; } } return sanitized; } // Uso console.log('Orden creada:', sanitizeForLogging(orderData));

Encriptar Datos Sensibles

const crypto = require('crypto'); class SecureStorage { constructor(encryptionKey) { this.algorithm = 'aes-256-gcm'; this.key = Buffer.from(encryptionKey, 'hex'); } encrypt(text) { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(this.algorithm, this.key, iv); let encrypted = cipher.update(text, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return { encrypted, iv: iv.toString('hex'), authTag: authTag.toString('hex') }; } decrypt(encrypted, iv, authTag) { const decipher = crypto.createDecipheriv( this.algorithm, this.key, Buffer.from(iv, 'hex') ); decipher.setAuthTag(Buffer.from(authTag, 'hex')); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } } // Uso const storage = new SecureStorage(process.env.ENCRYPTION_KEY); const encrypted = storage.encrypt('datos-sensibles');

Validación de Entrada

Sanitizar Datos de Usuario

const validator = require('validator'); function validateOrderInput(orderData) { const errors = []; // Validar amount if (!orderData.amount || typeof orderData.amount !== 'number') { errors.push('Monto inválido'); } if (orderData.amount <= 0 || orderData.amount > 10000000) { errors.push('Monto fuera de rango'); } // Validar currency const validCurrencies = ['COP', 'USD', 'BRL', 'MXN', 'CLP']; if (!validCurrencies.includes(orderData.currency)) { errors.push('Moneda inválida'); } // Validar email si existe if (orderData.email && !validator.isEmail(orderData.email)) { errors.push('Email inválido'); } // Prevenir inyección en externalId if (orderData.externalId) { orderData.externalId = validator.escape(orderData.externalId); } if (errors.length > 0) { throw new Error(`Validación fallida: ${errors.join(', ')}`); } return orderData; }

Control de Acceso

Implementar RBAC

const permissions = { 'admin': ['read', 'write', 'delete'], 'operator': ['read', 'write'], 'viewer': ['read'] }; function requirePermission(permission) { return (req, res, next) => { const userRole = req.user.role; const userPermissions = permissions[userRole] || []; if (!userPermissions.includes(permission)) { return res.status(403).json({ error: 'Permisos insuficientes' }); } next(); }; } // Uso app.post('/api/orders', authenticate, requirePermission('write'), createOrderHandler );

Auditoría y Monitoreo

Registrar Eventos de Seguridad

class SecurityAuditLogger { static async log(event, details) { const auditEntry = { timestamp: new Date().toISOString(), event, userId: details.userId, ip: details.ip, userAgent: details.userAgent, action: details.action, resource: details.resource, success: details.success, reason: details.reason }; // Registrar en sistema seguro await db.query( `INSERT INTO security_audit_log (timestamp, event, user_id, ip, action, success) VALUES ($1, $2, $3, $4, $5, $6)`, [ auditEntry.timestamp, auditEntry.event, auditEntry.userId, auditEntry.ip, auditEntry.action, auditEntry.success ] ); } } // Uso await SecurityAuditLogger.log('AUTHENTICATION_ATTEMPT', { userId: user.id, ip: req.ip, userAgent: req.headers['user-agent'], action: 'login', success: true });

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
async function emergencyCredentialRevocation() { // 1. Deshabilitar API keys comprometidas await disableApiKey(compromisedApiKey); // 2. Generar nuevas credenciales const newCredentials = await generateNewApiKey(); // 3. Notificar equipo await sendSecurityAlert({ type: 'CREDENTIAL_COMPROMISED', action: 'REVOKED_AND_REGENERATED', newKeyId: newCredentials.id }); // 4. Actualizar sistemas await updateProductionCredentials(newCredentials); return newCredentials; }

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

Last updated on