Spring-boot: Spring Boot 1.5.1 Actuator Endpoints security configuration issue

Created on 10 Feb 2017  路  12Comments  路  Source: spring-projects/spring-boot

This is my Spring Boot 1.5.1 Actuator application.properties:

#Spring Boot Actuator
management.contextPath: /actuator
management.security.roles=R_0

This is my WebSecurityConfig:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Value("${logout.success.url}")
    private String logoutSuccessUrl;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // @formatter:off
        http.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);

        http
            .csrf().ignoringAntMatchers("/v1.0/**", "/logout")
        .and()
            .authorizeRequests()

            .antMatchers("/oauth/authorize").authenticated()
            //Anyone can access the urls
            .antMatchers("/signin/**").permitAll()
            .antMatchers("/v1.0/**").permitAll()
            .antMatchers("/auth/**").permitAll()
            .antMatchers("/actuator/health").permitAll()
            .antMatchers("/actuator/**").hasAuthority("R_0")
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
        .and()
            .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .failureUrl("/login?error=true")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
            .and()
                .logout()
                    .logoutUrl("/logout")
                    .logoutSuccessUrl(logoutSuccessUrl)
                    .permitAll();
        // @formatter:on
    }

    /**
     * Configures the authentication manager bean which processes authentication requests.
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}

Right now I'm successfully able to login in my application with a right user that has R_0 authorities but when I trying to access for example

http://localhost:8080/api/actuator/beans

I receive a following error:

There was an unexpected error (type=Forbidden, status=403).
Access is denied. User must have one of the these roles: R_0

How to correctly configure Spring Boot Actuator in order to be aware about the correct Authentication ?

Right now in order to get it workin I have to do the following trick:

management.security.enabled=false

.antMatchers("/actuator/health").permitAll()
.antMatchers("/actuator/**").hasAuthority("R_0")

Is any chance to configure Actuator in a right way ?

This is my StackOverflow question: http://stackoverflow.com/questions/42142556/spring-boot-actuator-endpoints-security-doesnt-work-with-custom-spring-security

bug regression

Most helpful comment

@Artgit
Hi, can you please provide the final setup that worked for you? because Im experiencing this problem as well.

All 12 comments

If you add @EnableWebSecurity you're indicating that you want to be in full control. Unfortunately, that wasn't applied properly in Spring Boot 1.4 and has been fixed in 1.5. So this configuration of yours is not "a trick". You are fully configuring security so you need to configure that R_0 role of yours.

Thanks for your answer. With this approach(own configuration) I can't see right now how to control access level for /actuator/health endpoint. Previously (at Spring Boot 1.4) for anonymous users it returns minimum of information.. something like this

{"status":"UP"}

and

{"status":"UP","diskSpace": {"status":"UP","total":1951300292608,"free":1899948589056,"threshold":10485760}}

for authenticated users. But right now with

management.security.enabled=false

it always returns the fill info like

{"status":"UP","total":1951300292608,"free":1899948589056,"threshold":10485760}}.

Is it possible (and if so - how) to get the same behavior as previously with own security configuration ?

Is it possible (and if so - how) to get the same behavior as previously with own security configuration?

I don't believe you can and that looks like an oversight in the new approach to me.

I don't believe you can and that looks like an oversight in the new approach to me.

Actually, I believe you can. Leave management.security.enabled set to true and give your WebSecurityConfigurerAdapter an order with higher precedence (lower order) than ManagementServerProperties.BASIC_AUTH_ORDER. You could just use ManagementServerProperties.ACCESS_OVERRIDE_ORDER.

@Artgit Please give that a try and let us know how you get on.

I have added @Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER) :

@Configuration
@EnableWebSecurity
@Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

This is my application.properties:

security.oauth2.resource.filter-order = 3
#Spring Boot Actuator
management.contextPath: /actuator
management.security.roles=R_0
management.security.enabled=true

Right now with and without authenticated user I can only see

{"status":"UP"}

for/actuator/health endpoint which means that Actiator still doesn't understand that the user is logged in and authorized

Please let me know if I can do anything else in order to help to solve this issue.

Please let me know if I can do anything else in order to help to solve this issue.

A complete yet minimal sample that reproduces the behaviour would be very helpful. The health endpoint simply calls isUserInRole on the HttpServletRequest to determine if it should expose all of the health details. It must be returning false in both cases but it's hard to say why without being able to see the complete picture. A sample will help with that.

Unfortunately I can't extract a minimal sample from my application because it is pretty complex, but I can provide my Java config files. I hope it will help. Please find these files at attachment.

Sorry, but I don't have time to try to piece together something that behaves as you have described from eight configuration files, many of which appear to be unrelated to security. The sample doesn't have to be extracted from your application, it just has to reproduce the behaviour that you've described.

Here's a self contained example that appears to work as you want:

package com.example;

import org.springframework.boot.actuate.autoconfigure.ManagementServerProperties;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

@SpringBootApplication
@EnableWebSecurity
@Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER)
public class Gh8255Application extends WebSecurityConfigurerAdapter {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Gh8255Application.class)
                .properties("management.context-path:/actuator").run(args);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("admin").password("secret").roles("ACTUATOR");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        BasicAuthenticationEntryPoint authenticationEntryPoint = new BasicAuthenticationEntryPoint();
        authenticationEntryPoint.setRealmName("example");
        http.httpBasic().authenticationEntryPoint(authenticationEntryPoint);
        http.authorizeRequests().antMatchers("/actuator/health").permitAll();
    }

}

Accessed without any credentials, there are no details provided:

$ http localhost:8080/actuator/health
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/vnd.spring-boot.actuator.v1+json;charset=UTF-8
Date: Tue, 14 Feb 2017 10:54:26 GMT
Expires: 0
Pragma: no-cache
Transfer-Encoding: chunked
X-Application-Context: application
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

{
    "status": "UP"
}

Accessed with credentials, details are provided:

$ http --auth admin:secret localhost:8080/actuator/health
HTTP/1.1 200
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Content-Type: application/vnd.spring-boot.actuator.v1+json;charset=UTF-8
Date: Tue, 14 Feb 2017 10:54:27 GMT
Expires: 0
Pragma: no-cache
Set-Cookie: JSESSIONID=041C0D9FA63B0E052B2FC1638A7E8FDC;path=/;HttpOnly
Transfer-Encoding: chunked
X-Application-Context: application
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block

{
    "diskSpace": {
        "free": 105893588992,
        "status": "UP",
        "threshold": 10485760,
        "total": 499046809600
    },
    "status": "UP"
}

If you'd like us to spend some time trying to provide some guidance for your specific security configuration, then please spend some time producing a complete sample with that configuration and examples that show it not working as you'd like.

Since we're affected by the same issue I jumped in to create a testcase:

https://github.com/sambernet/gh8255-testcase

See the README.md in the repo for the behavior in spring-boot 1.5.1 compared to 1.4.4.

The issue is not really about security config (I took your test case as a rough base, but as you can see NO @EnableWebSecurity or custom security config is present at all), but more of a regression from 1.4.4 to 1.5.1 in how the management.security.roles property is handled by the actuator security config.

I didn't have a serious look at the spring-boot auto-config code yet, but it looks like previously the value configured in management.security.roles was taking authorities (or at least was lazy enough to allow plain authorities as well), whereas now it takes roles.

The accepted answer in this SO thread explains the difference quite well:
http://stackoverflow.com/questions/19525380/difference-between-role-and-grantedauthority-in-spring-security

Since requiring roles is much more restricting, this is a serious issue from my point of view. We don't use the common ROLE_ prefix for the authorities having access to the actuator endpoints, and I don't currently see a workaround to have the actuators standard security config accept our existing authority with the new behavior.

@Artgit: This means, if your role would be named ROLE_0 instead of R_0 and you'd set management.security.roles=0 it should work again for you. So if you have the possibility to rename the authority this might solve the issue for you.

This is unfortunately not an option for us.

@wilkinsona: I think the current behavior is actually more consistent than the 1.4.4 behavior was. The property is named management.security.*roles*, so since we're dealing with spring security here, the new behavior is actually fine.

It's still a breaking change though, even if possibly caused by a spring security version bump and upstream changes (spring-security-core was bumped from 4.1.4.RELEASE to 4.2.1.RELEASE).

The solution I would propose is thus to keep the current behavior of the management.security.roles property even though possibly breaking some setups, and add another property management.security.authorities. Both properties could be defined in parallel for a given setup, the final set of authorities given access to the actuator endpoints should be the union of the roles from management.security.roles (the authorities of these roles, respectively) and the authorities from management.security.authorities.

This would allow to keep the current behavior, use sensible defaults (ROLE_ACTUATOR) for standard setups but still provide the full degree of freedom to use authorities that are not prefixed with ROLE_ (and are thus not roles in the sense of spring security, but rather _permissions_), and also offer an easy migration path for us and users like @Artgit.

I'm also willing to provide a PR for this if desired - seems reasonably simple.

@sambernet Thanks, ROLE_0 works!

@Artgit
Hi, can you please provide the final setup that worked for you? because Im experiencing this problem as well.

Was this page helpful?
0 / 5 - 0 ratings