Securing Microservice Discovery with Eureka: End-to-End Protection Patterns

Why Discovery Security Matters

When every microservice is reachable over the network, the service registry becomes the first line of defense. An unprotected Eureka instance can be used to:

  • Inject rogue endpoints into the load-balancer
  • Exfiltrate configuration metadata
  • Trigger cascading failures via forged health checks

This guide shows how to turn the registry itself into a hardened security gateway.

Hardening the Registry

Step 1 – Lock Down the Dashboard

@EnableWebSecurity
class RegistrySecurityConfigurer {

  @Bean
  SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      http
        .csrf().disable()
        .authorizeHttpRequests()
            .requestMatchers("/actuator/health").permitAll()
            .anyRequest().hasRole("ADMIN")
        .and()
        .httpBasic(Customizer.withDefaults());
      return http.build();
  }

  @Bean
  UserDetailsService users() {
      return new InMemoryUserDetailsManager(
          User.withUsername("registry")
              .password("{bcrypt}$2a$10$…")
              .roles("ADMIN")
              .build());
  }
}

Step 2 – Mutual TLS Between Server and Clients

Create a PKCS12 keystore and truststore, then expose them via Spring Boot properties:

server:
  port: 8761
  ssl:
    enabled: true
    key-store: classpath:eureka-server.p12
    key-store-password: changeit
    trust-store: classpath:eureka-trust.p12
    trust-store-password: changeit
    client-auth: need

Step 3 – Client Side Configuration

eureka:
  client:
    serviceUrl:
      defaultZone: https://registry:${REGISTRY_PASSWORD}@eureka:8761/eureka/
  instance:
    metadataMap:
      zone: dmz
    securePortEnabled: true
    nonSecurePortEnabled: false

Token-Based Service-to-Service Auth

Instead of embedding passwords in defaultZone, issue short-lived JWTs:

@Component
class TokenRelayFilter implements ClientHttpRequestInterceptor {

  private final OAuth2AuthorizedClientManager clientManager;

  public ClientHttpRequestInterceptor interceptor() {
      return (request, body, execution) -> {
          OAuth2AuthorizeRequest req =
              OAuth2AuthorizeRequest.withClientRegistrationId("eureka")
                  .principal("discovery-client")
                  .build();
          OAuth2AuthorizedClient client = clientManager.authorize(req);
          request.getHeaders().setBearerAuth(client.getAccessToken().getTokenValue());
          return execution.execute(request, body);
      };
  }
}

Register the interceptor with the Eureka client’s RestTemplate via a EurekaClientHttpRequestFactorySupplier bean.

Least-Privilege Metadata

Use Eureka’s metadata map to carry role information:

eureka:
  instance:
    metadataMap:
      roles: "orders,audit"
      criticality: "high"

A sidecar filter can then enforce that only services with criticality=high may invoke a given endpoint.

Runtime Monitoring

Export security events to Prometheus:

@Component
public class SecurityAudit {
  private final MeterRegistry registry;

  @EventListener
  public void onUnauthorized(AbstractAuthorizationEvent event) {
      registry.counter("eureka.auth.denied",
          "source", event.getSource().toString()).increment();
  }
}

Pair the metric with an alert that fires when the rate of denied fetches exceeds 1 % in any 5-minute window.

Rotation Without Downtime

  1. Stage new certificates in an alternate truststore.
  2. Update clients to dual-trust both keystores.
  3. Roll the server to the new keystore.
  4. Remove the old truststore from clients in the next deployment wave.

Spring Cloud Config’s encryption endpoints can push the new truststore to all nodes atomically.

Tags: Eureka Spring Cloud Spring Security mtls JWT

Posted on Sun, 17 May 2026 14:56:21 +0000 by markmusicman