Securing Eureka Server for Service Discovery in Microservices

 

In the post, we will secure the Eureka server and consume it from the client application. When securing the Eureka server and the client application tries to consume it, there are a few interesting changes to consider.

When working on a microservices stack within the projects, there is an ask for a service discovery tool for services to intercommunicate with each other. Either we use a service mesh tool like Istio, or for applications that do not have a lot of complexity attached to them, teams generally lean into a Service discovery tool like Eureka. The essential components for Eureka are the service registry, service instances, and its clients.

Service discovery involves two components: the server side and the client side. In this post, we will secure the server-side component and consume the server from a locally run client side. Here is spring’s official documentation to ensure the service discovery.

With spring security 6.1 the method used in the spring documentation will give you a deprecation warning and the updated code looks something like this.

package me.sathish.sathishdiscovery.security;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity(debug = true)
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig {
    final BasicAuthBean environment;
    private static final Logger log = LoggerFactory.getLogger(WebSecurityConfig.class);

    @Value("${spring.security.debug:true}")
    boolean securityDebug;

    public WebSecurityConfig(BasicAuthBean environment) {
        this.environment = environment;
    }
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf->csrf.ignoringRequestMatchers("/eureka/**"))
                .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
                .httpBasic(Customizer.withDefaults())
                .formLogin(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        
        manager.createUser(
                User.withUsername(environment.getUsername())
                        .password(bCryptPasswordEncoder().encode(environment.getPassword()))
                        .roles("USER")
                        .build());
        manager.createUser(
                User.withUsername(environment.getUsername() + "Admin")
                        .password(bCryptPasswordEncoder().encode(environment.getPassword()))
                        .roles("USER", "ADMIN")
                        .build());
        return manager;
    }

}

The code block above matched Eureka clients who are subscribed to power through the CSRF error that might occur. In this code Eureka server, the username and password needed for authentication are passed as command line parameters to the Eureka server instance.

Let us now connect a simple Eureka client. First, add the EurekaClient annotation to the consumer main class. The next item is the configuration file. Here is a sample config file

eureka:
    client:
        registryFetchIntervalSeconds: 5
        serviceUrl:
            defaultZone: http://eurekaserverpass:eurekaserverpass@localhost:8070/eureka/
        registerWithEureka: true
server:
    port: 8065
spring:
    application:
        name: sathishstrava-data-producer
    kafka:
        bootstrap-servers: http://localhost:7092
        producer:
            key-serializer: org.apache.kafka.common.serialization.StringSerializer
            value-serializer: org.apache.kafka.common.serialization.StringSerializer
    cloud:
        config:
            username: ${configserverUsername}
            password: ${configserverPassword}
    config:
        fail-fast: true
        import: "optional:configserver:http://localhost:8888?fail-fast=true&max-attempts=10&max-interval=1500&multiplier=1.2&initial-interval=1100"
    threads:
        virtual:
            enabled: on

The most common point to remember is that the default port and the mapping to the Eureka server have different values in this scenario and are different at bootup. When Eureka boots this, and the CSRF value is not ignored in the server, the client tries to connect to the server system and throws the Transport/Server not discovered exception.

Here is the sample code of the client application

@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigurationProperties({ApplicationProperties.class})
public class SathishstravaDataProducerApplication {
    private final SathishGoveeListener sathishGoveeListener;
    private final GoveeDataProducer goveeDataProducer;
    public SathishstravaDataProducerApplication(SathishGoveeListener sathishGoveeListener, GoveeDataProducer goveeDataProducer) {
        this.sathishGoveeListener = sathishGoveeListener;
        this.goveeDataProducer=goveeDataProducer;
    }

    public static void main(String[] args) {
        SpringApplication.run(SathishstravaDataProducerApplication.class, args);
    }
}

I hope this post helps you use Eureka securely using the latest Spring security framework. The code base is in the GitHub repo.