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.

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


