Describe the bug

I'm trying to secure my 3 Eureka servers. I configured spring security through application.properties, and disabled CSRF. The biggest difference I can see is that the docs are referencing spring security 5 and the deprecated/removed WebSecurityConfigurerAdapter class. Since I'm on Spring Boot 3/Spring Security 6, I'm configuring via the SecurityFilterChain bean.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().ignoringRequestMatchers("/eureka/**");
        return http.build();
    }
}

After configuring the credentials and disabling CSRF, the 3 servers start up fine and all registered replicas show as available. However, it appears that I don't need any credentials to access eureka. I was able to replicate this via curl and the web browser. I tried hitting the main eureka dashboard, API (/eureka/apps), and actuator. None require credentials to access.


I also tried manually configuring security through the SecurityFilterChain bean, but that produced different results. My config looks like this:

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().ignoringRequestMatchers("/eureka/**")
                .and().securityMatcher("/**").authorizeHttpRequests().anyRequest().authenticated()
                .and()
                .formLogin().disable()
                .httpBasic()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return http.build();
    }

    @Bean
    public InMemoryUserDetailsManager userDetailsManager() {
        List<UserDetails> users = new ArrayList<>();
        users.add(
                User.builder()
                        .username("user")
                        .password("password")
                        .authorities("USER")
                        .build()
        );
        return new InMemoryUserDetailsManager(users);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

Under this config, security seems to work when using curl/a browser. Replication is not successful though. If I go to each individual server dashboard, I see different results for the eureka instances that are reported as UP:

eureka1

eureka2

eureka3

I also see a lot of these messages in the logs:

2023-02-13T11:58:35.475-05:00  WARN 39218 --- [arget_eureka2-4] c.n.eureka.util.batcher.TaskExecutors    : Discovery WorkerThread error

java.lang.NullPointerException: Cannot invoke "String.toLowerCase()" because the return value of "java.lang.Throwable.getMessage()" is null
    at com.netflix.eureka.cluster.ReplicationTaskProcessor.maybeReadTimeOut(ReplicationTaskProcessor.java:196) ~[eureka-core-2.0.0.jar:2.0.0]
    at com.netflix.eureka.cluster.ReplicationTaskProcessor.process(ReplicationTaskProcessor.java:95) ~[eureka-core-2.0.0.jar:2.0.0]
    at com.netflix.eureka.util.batcher.TaskExecutors$BatchWorkerRunnable.run(TaskExecutors.java:190) ~[eureka-core-2.0.0.jar:2.0.0]
    at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

I'm not sure if I'm configuring this correctly, or where I could be going wrong.

Sample

I pushed my code here (https://github.com/erbrecht/eureka-security) so you can hopefully replicate this issue. The main branch contains the first scenario, where I added the spring.security.user.name and password in application.properties and just disabled CSRF via Java config.

The security2 branch is the second scenario, where I configure security strictly through Java.

I have 3 profiles using different hostnames and ports for each server. I also modified my hosts file like so:

127.0.0.1       localhost localhost.localdomain eureka1 eureka2 eureka3

Any help is greatly appreciated.

Comment From: erbrecht

after a bit more debugging, it looks like the NPE message above comes from an org.apache.http.client.ClientProtocolException exception.

cause:

org.apache.http.client.NonRepeatableRequestException: Cannot retry request with a non-repeatable request entity.

stacktrace - sorry for the screenshot, not sure if I can copy/paste from the debugger without copying each individual call

stacktrace

not sure if this helps, but it seems like a better clue than the NPE when trying to extract the original Throwable message.

this is from an older version of httpclient, but the issue may still be relevant:

https://issues.apache.org/jira/browse/HTTPCLIENT-951

I guess the apache library doesn't pass creds until the server tells it to, but on certain requests, like POSTS, the body (input stream) may have already been read, so the request can't be repeated without recreating the input stream. apparently the BufferedHttpEntity from http components core is repeatable. I do see that a buffered http entity should be used if buffering is enabled, which looks like the case since this is returning the buffered entity:

buffered

again, not sure if this helps, is relevant to the main issue, or if this is just a bad path to go down. hopefully this is useful, even if just to discard a potential path to investigate.

Comment From: Been24

any solutions?

Comment From: OlgaMaciaszek

Thanks a lot for reporting. We're looking at it. Should be able to get back about this at the end of this week or the beginning of next.

Comment From: hazartilirot

I was going to find a solution and stumbled across the topic. 🥳

Screenshot 2023-04-12 at 00 21 43

Comment From: OlgaMaciaszek

Thanks, @erbrecht @Been24 @hazartilirot . I have taken a look at this. The simplest way to configure SecurityFilterChain to disable that CSRF check while maintaining the default setup, would probably be something like this:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests((authz) -> authz
                    .anyRequest().authenticated()
            )
            .httpBasic(withDefaults());
    http.csrf().ignoringRequestMatchers("/eureka/**");
    return http.build();
}

I have been able to replicate replication errors with this setup as well. Working on this issue.

Comment From: Nathan-ZR

@OlgaMaciaszek I also encountered the same situation as erbrecht. I configured it with reference to your configuration file. When I started it, I found that when I entered user and password, an error would be reported directly. java.lang.NullPointerException: Cannot invoke "String.toLowerCase()" because the return value of "java.lang.Throwable.getMessage()" is null.Looking forward to hearing from you. I don't know if there is a problem with my configuration file? My configuration file code is shown in the image below. Spring Cloud Netflix unable to configure spring security with spring boot 3.0.2

Comment From: OlgaMaciaszek

Yes, @Nathan-ZR, the NPE is due to lack of handling of null messages. I have submitted a fix for that to Netflix/Eureka, however this is not going to solve the problem. There are 401s being returned - looking further into that now.

Comment From: OlgaMaciaszek

The proper Basic Auth header is not being added. The bug seems to be in Netflix/Eureka rather than in Spring Cloud Netflix, but will take a look at it anyway (probably at the beginning of the next week).

Comment From: fede843

Hello. We are having the same issue with a similar configuration. Looking forward to hearing from this.

Comment From: yanping891003

We have the same issue and look forward to news

Comment From: OlgaMaciaszek

Should be fixed when in Netflix/eureka. Have submitted a PR with fix.

Comment From: OlgaMaciaszek

Have updated the sample and the docs as well.

Comment From: OlgaMaciaszek

Spring Cloud Netflix 4.0.x and main updated to fixed Netflix/Eureka version.