Saltar al contenido

El modelo de seguridad Zero Trust para desarrolladores

Qué significa zero trust más allá de la moda: acceso basado en identidad, TLS mutuo, microsegmentación y cómo aplicarlo en la capa de aplicación.

5 min de lectura
Diagrama de red que muestra una arquitectura zero trust con verificación de identidad en cada límite de servicio

El modelo de seguridad tradicional es como un castillo con foso: un perímetro sólido (firewall, VPN) que protege una red interna de confianza. Una vez dentro, puedes acceder a todo. Este modelo falla de forma catastrófica en cuanto un atacante logra cruzar el perímetro, algo que tarde o temprano ocurre mediante phishing, credenciales comprometidas o ataques a la cadena de suministro.

Zero trust invierte el modelo: nunca confíes, verifica siempre. Toda solicitud — venga de dentro o fuera de la red — debe autenticarse, autorizarse y cifrarse. No existe una red interna de confianza.

Los principios fundamentales

Zero trust no es un producto que se compra. Es un conjunto de principios arquitectónicos que cambian la forma en que piensas el control de acceso, la comunicación de red y la verificación de identidad.

tstypescript
// Traditional security: trust the network
interface CastleAndMoat {
  perimeter: 'firewall + VPN';
  internalTraffic: 'trusted by default';  // ← The vulnerability
  authCheck: 'at the perimeter only';
  // Once inside, an attacker has lateral movement to everything
}
 
// Zero trust: trust nothing, verify everything
interface ZeroTrustModel {
  perimeter: 'does not exist';
  allTraffic: 'encrypted and authenticated';
  authCheck: 'at every service, every request';
  principle: 'least privilege — minimum access for minimum time';
  assumption: 'the network is already compromised';
}
 
// Zero trust principles as code
const zeroTrustPrinciples = {
  verifyExplicitly: {
    rule: 'Authenticate and authorize every request',
    how: 'Use identity tokens (JWT, mTLS) — not network location',
  },
  leastPrivilege: {
    rule: 'Grant minimum required access for minimum time',
    how: 'Short-lived tokens, scoped permissions, JIT access',
  },
  assumeBreach: {
    rule: 'Design as if attackers are already inside',
    how: 'Encrypt internal traffic, segment networks, log everything',
  },
};

Identity-based access (no basado en la red)

En un modelo zero trust, las decisiones de acceso se basan en la identidad — quién eres, qué dispositivo estás usando y en qué contexto te encuentras — no en la red a la que estás conectado.

tstypescript
// ❌ Network-based access control
function handleRequest(req: Request): Response {
  // If the request comes from an internal IP, trust it
  const clientIp = req.headers.get('x-forwarded-for');
  if (isInternalIp(clientIp)) {
    // No authentication required — "it's internal"
    return processRequest(req);  // Attackers love this
  }
  return new Response('Forbidden', { status: 403 });
}
 
// ✅ Identity-based access control
async function handleRequest(req: Request): Promise<Response> {
  // Every request must present a valid identity token
  const token = req.headers.get('authorization')?.replace('Bearer ', '');
  if (!token) {
    return new Response('Unauthorized', { status: 401 });
  }
 
  // Verify the token regardless of network origin
  const identity = await verifyToken(token);
  if (!identity) {
    return new Response('Invalid token', { status: 401 });
  }
 
  // Check if this identity has access to this specific resource
  const hasAccess = await checkPermission(identity, req.url, req.method);
  if (!hasAccess) {
    return new Response('Forbidden', { status: 403 });
  }
 
  return processRequest(req, identity);
}

TLS mutuo entre servicios

En las arquitecturas tradicionales, los servicios internos se comunican por HTTP sin cifrar. En una arquitectura zero trust, cada conexión entre servicios se autentica mediante TLS mutuo (mTLS): ambas partes presentan certificados para demostrar su identidad.

tstypescript
// Setting up mTLS in Node.js
import { createServer, request } from 'https';
import { readFileSync } from 'fs';
 
// Server: requires client certificates
const server = createServer({
  key: readFileSync('server-key.pem'),
  cert: readFileSync('server-cert.pem'),
  ca: readFileSync('ca-cert.pem'),    // Trust only certs signed by this CA
  requestCert: true,                   // Require client certificate
  rejectUnauthorized: true,            // Reject connections without valid certs
}, (req, res) => {
  // The client's identity is in the certificate
  const clientCert = (req as any).socket.getPeerCertificate();
  const clientService = clientCert.subject.CN;  // e.g., "billing-service"
 
  console.log(`Authenticated request from: ${clientService}`);
 
  // Authorize based on the service identity
  if (!isAllowedCaller(clientService, req.url)) {
    res.writeHead(403);
    res.end('Service not authorized for this endpoint');
    return;
  }
 
  handleRequest(req, res, clientService);
});
 
// Client: presents its own certificate
function callService(url: string): Promise<unknown> {
  return new Promise((resolve, reject) => {
    const req = request(url, {
      key: readFileSync('client-key.pem'),
      cert: readFileSync('client-cert.pem'),
      ca: readFileSync('ca-cert.pem'),
    }, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => resolve(JSON.parse(data)));
    });
    req.on('error', reject);
    req.end();
  });
}

Autorización a nivel de servicio

La autenticación te dice quién realiza la solicitud. La autorización te dice si esa persona tiene permiso para hacer lo que solicita. En zero trust, cada servicio aplica su propia autorización, sin depender de que los servicios previos ya hayan verificado los permisos.

tstypescript
// Policy-based authorization at every service
interface AccessPolicy {
  service: string;          // Which service can access
  resource: string;         // Which endpoint/resource
  actions: string[];        // Which HTTP methods
  conditions?: Condition[]; // Additional constraints
}
 
const policies: AccessPolicy[] = [
  {
    service: 'api-gateway',
    resource: '/api/users/*',
    actions: ['GET'],
    conditions: [{ type: 'time', allowedHours: { start: 6, end: 22 } }],
  },
  {
    service: 'billing-service',
    resource: '/api/users/*/payment-methods',
    actions: ['GET'],
    // Billing can read payment methods but not user profiles
  },
  {
    service: 'admin-service',
    resource: '/api/users/*',
    actions: ['GET', 'PUT', 'DELETE'],
    conditions: [{ type: 'approval', requiredApprovers: 1 }],
  },
];
 
function isAllowedCaller(
  callerService: string,
  requestPath: string,
  method: string = 'GET'
): boolean {
  return policies.some(policy =>
    policy.service === callerService &&
    matchesPath(requestPath, policy.resource) &&
    policy.actions.includes(method) &&
    (policy.conditions?.every(c => evaluateCondition(c)) ?? true)
  );
}
tstypescript
// ❌ Trusting upstream services to enforce authorization
function userService(req: Request): Response {
  // "The API gateway already checked permissions"
  // But what if the request bypasses the gateway?
  // What if the gateway has a bug in its auth logic?
  return getUser(req.params.id);  // No authorization check here
}
 
// ✅ Every service enforces its own authorization
async function userService(req: AuthenticatedRequest): Promise<Response> {
  // Verify the caller's identity (from mTLS or token)
  const caller = req.identity;
 
  // Check if THIS specific service allows THIS action
  if (!isAllowedCaller(caller.service, req.url, req.method)) {
    auditLog.warn('Unauthorized access attempt', {
      caller: caller.service,
      resource: req.url,
      method: req.method,
    });
    return new Response('Forbidden', { status: 403 });
  }
 
  return getUser(req.params.id);
}

Credenciales de corta duración

Las claves de API de larga duración y las contraseñas de cuentas de servicio son un riesgo importante: si se filtran, otorgan acceso indefinido. Zero trust utiliza credenciales de corta duración que se rotan automáticamente.

tstypescript
// Token lifecycle management
interface TokenPolicy {
  maxLifetime: number;      // 15 minutes for access tokens
  refreshWindow: number;    // Can refresh within 5 minutes of expiry
  rotationSchedule: string; // Service credentials rotate daily
}
 
// Short-lived service-to-service tokens
async function getServiceToken(
  targetService: string
): Promise<string> {
  const token = await tokenService.issue({
    issuer: 'user-service',
    audience: targetService,
    expiresIn: '15m',           // 15 minutes — not days or months
    scopes: ['read:users'],      // Minimum required scope
  });
 
  return token;
}
 
// The token service issues short-lived JWTs
interface ServiceToken {
  iss: string;    // Issuing service
  aud: string;    // Target service
  exp: number;    // Expires in 15 minutes
  iat: number;    // Issued at
  scopes: string[]; // Minimum required permissions
  jti: string;    // Unique ID for revocation tracking
}

Registro y monitoreo

Zero trust exige un registro exhaustivo, porque ya no puedes asumir que el tráfico interno es seguro. Cada decisión de autenticación, cada verificación de autorización y cada intento de acceso deben quedar registrados para su auditoría y la detección de anomalías.

tstypescript
// Audit logging for zero trust
interface AuditLogEntry {
  timestamp: string;
  requestId: string;
  caller: {
    service: string;
    identity: string;
    ipAddress: string;
  };
  target: {
    service: string;
    resource: string;
    method: string;
  };
  decision: 'allowed' | 'denied';
  reason: string;
  policyMatched?: string;
}
 
function logAccessDecision(entry: AuditLogEntry): void {
  // All access decisions are logged — both allowed and denied
  // Denied requests are especially important for detecting attacks
  console.log(JSON.stringify(entry));
  
  // Alert on anomalies
  if (entry.decision === 'denied') {
    anomalyDetector.track(entry);
  }
}

Puntos clave

  1. Nunca confíes, verifica siempre — autentica y autoriza cada solicitud sin importar su origen en la red
  2. Usa identidad, no direcciones IP — las decisiones de acceso deben basarse en quién hace la solicitud, verificado mediante tokens o certificados
  3. Aplica autorización en cada servicio — no dependas de que los servicios previos ya hayan verificado los permisos
  4. Usa TLS mutuo entre servicios — tanto el cliente como el servidor se autentican mutuamente con certificados
  5. Emite credenciales de corta duración — los tokens de acceso de 15 minutos limitan el radio de impacto de una credencial comprometida
  6. Registra cada decisión de acceso — los registros de auditoría exhaustivos permiten la detección de anomalías y la investigación forense
Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX