8. Seguridad en microservicios

La seguridad en microservicios abarca mucho más que colocar un firewall delante de la aplicación. Requiere proteger identidades, credenciales, canales de comunicación, componentes de infraestructura y automatizaciones de despliegue. Cada servicio es un posible vector de ataque, por lo que se deben adoptar controles en capas que garanticen autenticación robusta, autorización granular y auditoría continua.

8.1 Autenticación y autorización

Los microservicios suelen delegar la autenticación en un proveedor central que emite tokens firmados. Protocolos como OAuth 2.0 permiten gestionar el acceso delegando credenciales en servidores de autorización. Los tokens se entregan al cliente tras un flujo de autorización y se utilizan para llamar a los servicios protegidos. La autorización se modela con scopes, roles o atributos que describen los permisos concedidos.

Para transportar la identidad y las claims se emplean tokens compactos como JSON Web Token (JWT). El servicio receptor valida la firma, comprueba la vigencia y evalúa el alcance antes de continuar la ejecución. Esto evita almacenar sesiones compartidas y favorece la escalabilidad horizontal.

8.1.1 Configuración de recursos protegidos

El ejemplo siguiente muestra cómo un servicio protege sus endpoints utilizando Spring Security como recurso OAuth2. Se configura la validación del token y se aplica autorización basada en autoridades.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers(HttpMethod.GET, "/api/orders/**").hasAuthority("SCOPE_orders:read")
                .requestMatchers(HttpMethod.POST, "/api/orders/**").hasAuthority("SCOPE_orders:write")
                .anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
        return http.build();
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        NimbusJwtDecoder decoder = NimbusJwtDecoder
                .withJwkSetUri("https://identity.example.com/oauth2/jwks").build();
        decoder.setJwtValidator(JwtValidators.createDefaultWithIssuer("https://identity.example.com"));
        return decoder;
    }
}

El microservicio se configura como recurso protegido, valida la firma con la clave pública publicada por el servidor de autorización y aplica reglas de acceso alineadas al dominio.

8.1.2 Consideraciones para tokens

Los tokens deben tener vigencia corta y revocarse cuando se compongan permisos sensibles. Resulta conveniente implementar introspección o listas negras para tokens comprometidos y evitar el almacenamiento de claves privadas en el repositorio. Además, los registros de auditoría deben incluir el identificador del usuario, la huella del token y la acción realizada.

8.2 Seguridad en las comunicaciones

Las comunicaciones cifradas protegen contra escuchas y manipulaciones. Toda comunicación externa debe usar HTTPS con certificados emitidos por una autoridad confiable. Para el tráfico interno es recomendable adoptar mTLS (mutual Transport Layer Security), donde cliente y servidor se autentican mediante certificados y el plano de control rota credenciales periódicamente.

El cifrado se extiende a mensajería y almacenamiento compartido. Los brokers de eventos y bases de datos deben forzar conexiones protegidas, y el acceso desde redes privadas se limita a subredes autorizadas. Las claves privadas se alojan en gestores de secretos que permiten rotación y control de acceso centralizado.

8.2.1 Configuración mTLS en una aplicación

El siguiente fragmento ilustra cómo un cliente habilita mTLS configurando el almacén de claves y de confianza antes de realizar la llamada.

public WebClient secureWebClient(Path keyStorePath, char[] keyStorePassword) {
    SslContext sslContext = SslContextBuilder
            .forClient()
            .keyManager(keyStorePath.toFile(), new String(keyStorePassword))
            .trustManager(new File("truststore.pem"))
            .protocols("TLSv1.3")
            .build();

    HttpClient httpClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext));
    return WebClient.builder()
            .baseUrl("https://inventory.internal")
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .build();
}

Las credenciales se cargan desde un almacén administrado y se usa TLS 1.3 para aprovechar algoritmos modernos. La renovación de certificados se automatiza para evitar expiraciones inesperadas.

8.3 Control de acceso entre servicios

Los microservicios no deben confiar ciegamente en otros componentes internos. El control de acceso entre servicios establece políticas que definen qué interacciones están permitidas. Se pueden aplicar listas de control de acceso, polĂ­ticas de seguridad a nivel de malla (service mesh) o gateways internos que validan la identidad del emisor.

La autorización basada en atributos (ABAC) permite evaluar condiciones como el tipo de servicio, el canal o el nivel de confidencialidad. Las reglas se versionan junto con el código y se someten a revisiones de seguridad para prevenir combinaciones peligrosas.

8.3.1 Filtro para controlar llamadas salientes

En el ejemplo siguiente, un filtro verifica si el microservicio actual está autorizado para invocar la operación solicitada antes de despachar la llamada HTTP.

@Component
public class OutboundAuthorizationFilter implements ExchangeFilterFunction {

    private final AccessPolicyStore policyStore;

    public OutboundAuthorizationFilter(AccessPolicyStore policyStore) {
        this.policyStore = policyStore;
    }

    @Override
    public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
        String targetService = request.url().getHost();
        String action = request.method().name();
        if (!policyStore.isAllowed("payments-service", targetService, action)) {
            return Mono.error(new AccessDeniedException("Acceso denegado a " + targetService));
        }
        return next.exchange(request);
    }
}

El filtro se integra con un almacén de políticas administrado centralmente y corta la comunicación si la combinación no está autorizada, evitando que un servicio comprometido abuse de sus credenciales.

8.4 Seguridad del entorno y del pipeline de despliegue

El entorno de ejecución y la automatización de despliegues también forman parte de la superficie de ataque. Se deben aplicar principios de menor privilegio a las cuentas de servicio del clúster, aislar los espacios de nombres y firmar las imágenes de contenedores. El pipeline de CI/CD verifica la integridad de dependencias, ejecuta escáneres de vulnerabilidades y bloquea artefactos que no superan las políticas.

Los secretos se almacenan cifrados en gestores dedicados y se inyectan en tiempo de despliegue. Las auditorías registran cambios en las configuraciones, y el monitoreo detecta comportamientos anómalos como picos de ejecución en contenedores o modificaciones no autorizadas en los manifiestos. El cumplimiento normativo se respalda con controles de acceso, revisiones de pares y evidencia de las pruebas de seguridad ejecutadas.

8.4.1 Lista de verificación del pipeline seguro

  • Escanear dependencias y contenedores en cada commit antes de publicar la imagen.
  • Firmar artefactos y validar la firma durante el despliegue automatizado.
  • Restringir el acceso al registro de imágenes y rotar credenciales periódicamente.
  • Ejecutar pruebas de seguridad automatizadas (SAST, DAST) e incluir resultados en el tablero de release.
  • Aplicar policy as code para asegurar que los manifiestos cumplan con requisitos de seguridad y recursos.