Zero Trust Architecture¶
Zero Trust es el modelo de seguridad que parte de la premisa "nunca confíes, siempre verifica". A diferencia del modelo perimetral tradicional donde todo lo que está dentro de la red se considera confiable, Zero Trust exige verificación explícita de identidad, dispositivo y contexto en cada solicitud, independientemente del origen. En esta plataforma, Keycloak, Traefik y Spring WebFlux se combinan para implementar Zero Trust de forma coherente en cada capa del stack.
La adopción de Zero Trust es especialmente crítica en un entorno multi-tenant con usuarios internos (realm-internal) y socios externos (realm-partners), donde la superficie de ataque es más amplia y los requisitos de aislamiento son más estrictos.
Los 5 Principios de Zero Trust Aplicados¶
1. Verificación Explícita¶
Cada solicitud debe ser autenticada y autorizada usando todos los datos disponibles: identidad, ubicación, dispositivo, servicio solicitado y clasificación del dato. No existe la confianza implícita basada en red o IP.
Implementación en la plataforma:
- Traefik valida que toda solicitud lleve un JWT antes de reenviarla al servicio.
- Spring WebFlux verifica la firma del JWT en cada request usando las JWKs de Keycloak.
- Los claims del token (
realm_access.roles,azp,iss) se verifican por cada servicio individualmente.
2. Mínimo Privilegio (Least Privilege)¶
Los sujetos y sistemas reciben solo los permisos mínimos necesarios para su función, durante el tiempo mínimo necesario. El acceso se revoca tan pronto como deja de ser necesario.
Implementación en la plataforma:
- Los tokens tienen TTL cortos: 5 minutos para access tokens en producción.
- Los clientes OAuth2 declaran explícitamente sus scopes; Keycloak no emite claims no solicitados.
- Los roles se asignan por realm: un usuario de
realm-partnersno tiene acceso a recursos derealm-internal. - Los clientes M2M (
partner-service-m2m) usanclient_credentialssin acceso a datos de usuario.
3. Asumir Brecha (Assume Breach)¶
El sistema se diseña asumiendo que el atacante ya tiene acceso a alguna parte de la infraestructura. Esto implica segmentación, cifrado en tránsito, logs detallados y detección de anomalías.
Implementación en la plataforma:
- Toda comunicación entre contenedores usa TLS (Traefik gestiona certificados Let's Encrypt).
- La red Docker segmenta Keycloak, PostgreSQL y los servicios en redes internas distintas.
- Cada servicio Spring WebFlux registra todos los intentos de acceso denegado.
- PostgreSQL solo acepta conexiones desde la red interna de Docker.
4. Microsegmentación¶
El acceso a recursos se controla a nivel de servicio, endpoint y operación, no solo a nivel de red. Cada microservicio es responsable de su propia autorización.
Implementación en la plataforma:
- Traefik implementa rate limiting y circuit breaking por ruta.
- Cada endpoint de Spring WebFlux declara sus requisitos de autorización explícitamente.
- Los scopes OAuth2 mapean a operaciones específicas (
read:users,write:partners).
5. Visibilidad y Analítica¶
Toda actividad se registra y está disponible para auditoría. Los eventos de seguridad se correlacionan para detectar patrones anómalos.
Implementación en la plataforma:
- Keycloak genera eventos de login, logout y error configurables en la Admin Console.
- Spring WebFlux propaga
traceIdyspanId(Micrometer Tracing) en todos los logs. - PostgreSQL almacena el audit trail inmutable generado por el Audit Context.
Flujo de Verificación Continua¶
sequenceDiagram
autonumber
actor U as Usuario / Sistema
participant T as Traefik v3
participant K as Keycloak 26
participant S as Spring WebFlux Service
participant DB as PostgreSQL
U->>T: HTTPS request + JWT Bearer
T->>T: TLS termination + rate limit check
T->>K: GET /.well-known/openid-configuration (cached JWKs)
T->>T: Verificar firma JWT con JWK público
alt JWT inválido o expirado
T-->>U: 401 Unauthorized
else JWT válido
T->>S: Reenvía request con headers X-User-Id, X-Realm
S->>S: SecurityWebFilterChain verifica JWT localmente
S->>S: Verificar claim iss == Keycloak esperado
S->>S: Verificar claim azp == cliente autorizado
S->>S: Verificar roles requeridos en realm_access.roles
alt Permisos insuficientes
S-->>T: 403 Forbidden
T-->>U: 403 Forbidden
S--)DB: INSERT audit_event (ACCESS_DENIED, userId, resource)
else Autorizado
S->>DB: Operación de negocio
DB-->>S: Resultado
S--)DB: INSERT audit_event (ACCESS_GRANTED, userId, resource)
S-->>T: 200 OK
T-->>U: 200 OK
end
end
Verificación local de JWT
Spring WebFlux no llama a Keycloak en cada request para validar el token. Descarga las JWKs al inicio y las cachea, rotándolas periódicamente. La verificación es local y de muy bajo latencia (menos de 1ms por request).
Implementación Conjunta: Keycloak + Traefik + Spring WebFlux¶
Capa 1: Traefik — Control de Acceso en el Borde¶
# traefik/dynamic/middlewares.yml
http:
middlewares:
jwt-auth:
forwardAuth:
address: "http://keycloak:8080/realms/realm-internal/protocol/openid-connect/userinfo"
authResponseHeaders:
- "X-User-Id"
- "X-User-Email"
- "X-Realm"
trustForwardHeader: false
rate-limit-api:
rateLimit:
average: 100
burst: 50
period: 1s
security-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
contentTypeNosniff: true
frameDeny: true
customResponseHeaders:
X-Powered-By: ""
Capa 2: Keycloak — Emisión y Gestión de Tokens¶
Keycloak implementa Zero Trust en la emisión de tokens:
- Token TTL mínimo: access tokens con 5 minutos de vida en producción.
- Refresh token rotation: cada uso del refresh token emite uno nuevo e invalida el anterior.
- Brute force protection: bloqueo tras 5 intentos fallidos, desbloqueo automático en 10 minutos.
- Detección de sesiones simultáneas: configurable por realm.
# Configurar brute force protection via Admin CLI
/opt/keycloak/bin/kcadm.sh update realms/realm-internal \
-s bruteForceProtected=true \
-s failureFactor=5 \
-s waitIncrementSeconds=60 \
-s maxFailureWaitSeconds=900 \
--server http://localhost:8080 \
--realm master \
--user admin
Capa 3: Spring WebFlux — Autorización a Nivel de Servicio¶
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class ZeroTrustSecurityConfig {
@Bean
public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http,
ReactiveJwtDecoder jwtDecoder) {
return http
.csrf(csrf -> csrf.disable())
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
.authorizeExchange(auth -> auth
.pathMatchers("/actuator/health", "/actuator/info").permitAll()
.anyExchange().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtDecoder(jwtDecoder)
.jwtAuthenticationConverter(realmRolesConverter())
)
)
.build();
}
@Bean
public ReactiveJwtDecoder jwtDecoder(
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") String issuerUri) {
return ReactiveJwtDecoders.fromIssuerLocation(issuerUri);
}
@Bean
public Converter<Jwt, Mono<AbstractAuthenticationToken>> realmRolesConverter() {
return jwt -> {
Map<String, Object> realmAccess = jwt.getClaimAsMap("realm_access");
List<String> roles = realmAccess != null
? (List<String>) realmAccess.get("roles")
: List.of();
List<GrantedAuthority> authorities = roles.stream()
.map(r -> new SimpleGrantedAuthority("ROLE_" + r.toUpperCase()))
.collect(Collectors.toList());
return Mono.just(new JwtAuthenticationToken(jwt, authorities));
};
}
}
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@GetMapping
@PreAuthorize("hasRole('ADMIN') or hasRole('USER_READER')")
public Flux<UserResponse> listUsers(@AuthenticationPrincipal Jwt jwt) {
validateIssuer(jwt.getIssuer().toString());
return userService.findAll();
}
@DeleteMapping("/{userId}")
@PreAuthorize("hasRole('ADMIN')")
public Mono<Void> deleteUser(@PathVariable String userId,
@AuthenticationPrincipal Jwt jwt) {
auditService.log(AuditEvent.userDeletion(userId, jwt.getSubject()));
return userService.delete(userId);
}
}
Controles Implementados¶
| Control | Componente | Configuración |
|---|---|---|
| Verificación de firma JWT | Spring Security 7 | ReactiveJwtDecoder con JWKS automático |
Validación de emisor (iss) |
Spring Security 7 | issuer-uri en application.yml |
Validación de audiencia (aud) |
Spring Security 7 | audiences en resource server config |
| Expiración de tokens | Keycloak | accessTokenLifespan: 300 segundos |
| Rotación de refresh tokens | Keycloak | refreshTokenMaxReuse: 0 |
| Rate limiting por IP | Traefik | middleware rateLimit |
| TLS obligatorio + HSTS | Traefik | Let's Encrypt + stsSeconds: 31536000 |
| Segmentación de red | Docker Compose | Redes iam-internal y iam-edge separadas |
| Audit log inmutable | PostgreSQL | Tabla append-only, sin permisos UPDATE/DELETE |
| Brute force protection | Keycloak | bruteForceProtected: true por realm |
| Sin confianza implícita entre servicios | Spring WebFlux | Cada servicio valida su propio JWT |
Error común: confiar en la red interna de Docker
Un servicio Spring WebFlux que acepta llamadas sin JWT desde la red interna de Docker viola Zero Trust. Aunque Traefik proteja el borde, un contenedor comprometido puede llamar directamente a otros servicios. Cada servicio DEBE validar el JWT de forma independiente.
Cache de JWKs y disponibilidad
Configura el jwk-set-uri con timeout de conexión corto (2s) y activa la caché. Si Keycloak está temporalmente indisponible, los servicios deben seguir validando tokens con las JWKs cacheadas, no rechazarlos todos. Spring Security cachea las JWKs por defecto; no desactives esta funcionalidad.
Monitoreo de eventos de seguridad
Activa el Event Listener de Keycloak para exportar eventos a un sistema SIEM o al menos a los logs del contenedor. Los eventos LOGIN_ERROR, CLIENT_LOGIN_ERROR y LOGOUT son críticos para detectar ataques de fuerza bruta y sesiones anómalas.