Koywe
En esta página

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');
}

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;
}

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

JavaScript
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

JavaScript
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

JavaScript
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.

JavaScript
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

JavaScript
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

JavaScript
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

JavaScript
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

JavaScript
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

JavaScript
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
JavaScript
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