Spring boot 2.1.x how to secure Actuator end points with basic auth - spring-boot

I am trying to build a spring boot application and wanted to leverage the Actuator features, but I want to secure the end points of Actuator /health,/shutdown etc. I have the below configurations which does not seem to work. I.e., application never prompts for credentials or when hit from post man does not give 403. I tried various ways, even the one from spring documentation. Can any one please help with this. Again this is spring boot 2.1.x. I know there is a configuration that can be made in application.yml in the previous version of spring
#Configuration
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.to(ShutdownEndpoint.class, InfoEndpoint.class, HealthEndpoint.class,
MetricsEndpoint.class))
.hasRole("ENDPOINT_ADMIN").requestMatchers(PathRequest.toStaticResources().atCommonLocations())
.authenticated().and().httpBasic();
}
application.yml
spring:
security:
user:
name: admin
password: admin
roles:
- ENDPOINT_ADMIN
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
shutdown:
enabled: true
health:
show-details: when-authorized
roles:
- ENDPOINT_ADMIN
mappings:
enabled: true

This code can serve you as a reference to achieve BasicAuth for Actuator Endpoints Spring Boot 2.X. This is not the exact code. While Extending WebSecurityConfigurerAdapter you have to configure AuthenticationManagerBuilder to assign roles and passwords for the roles. Here I am using "noop" password encoder you can use a different one to provide more security.
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("ROLE_USER").password("{noop}" + "USER_PWD").roles("USER").and()
.withUser("ROLE_ADMIN").password("{noop}" + "ADMIN").roles("ADMIN", "USER");
}
Once AuthenticationManagerBuilder is configured now configure HttpSecurity by disabling csrf. Below code requires authentication for metrics alone using any role. You can customize according to the end points you need to authenticate. Make sure you exclude base url of Rest Controller from this Authentication. You can insert authorizeRequests().antMatchers("/baseurl").permitAll().and() in the below configuration code to achieve that. Below is an example to configure HttpSecurity.
protected void configure(Httpsecurity http) {
http.csrf().authorizeRequests().requestMatchers(EndpointRequest.to(MetricsEndpoint.class))
.hasANyRole("ADMIN","USER").and().authorizeRequests().and().httpBasic();
}

Related

securing jolokia actuator endpoint not working when accessing through hawt.io

I've got hawtio 2.1.0 installed connecting to the jolokia endpoint exposed by a spring boot 2.0.5 app.
My app yaml contains
management:
endpoints:
enabled-by-default: true
web:
exposure:
include: "jolokia"
jmx:
exposure:
exclude: "*"
endpoint:
jolokia:
enabled: true
config:
debug: true
In addition I have a filter
#Configuration
public class ActuatorSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.to(ShutdownEndpoint.class))
.hasRole("ADMIN")
.requestMatchers(EndpointRequest.to(HealthEndpoint.class, InfoEndpoint.class))
.permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint())
.fullyAuthenticated()
.and().httpBasic();
}
When I access the jolokia actuator endpoint in my browser it correctly asks me for my credentials.
So using this method the endpoints are secured.
When I connect to the jolokia endpoint through the hawt.io web app I don't need to provide any credentials. It makes no differnence if hawt.io is running on a remote or on the local maschine the spring boot app is running on. Hawt.io is able to get all the MBean infos via jolokia.
How can that be?
Hawt.io somehow circumvents the securing of the jolokia actuator endpoint.
Any ideas why this is or how I can secure the jolokia actuator endpoint so that even hawt.io prompts for the credentials?
Thanks a lot in advance!
Cheers
Oliver

Spring Boot 2.0.0.M4 breaks http basic auth in application.yml

Spring Boot 1.5.6.RELEASE respected the basic-auth username and password as specified in my application.yml below.
I have upgraded to 2.0.0.M4 and now the application always starts with the default 'user' and randomly generated password. Basically the settings below are always completely ignored.
I saw some changes in the release note/doc specific to simplifying actuator security enabled/disabled. I didn't see anything specific to this.
Any ideas?
From my application.yml
security:
basic:
enabled: true
realm: some-service
user:
name: example_user
password: example_password
Update:
I've confirmed this functionality was just plainly taken out starting with Spring Boot 2.0.0.M4
In the appendices:
All the security.basic.* family of stuff is missing here from the M4 reference:
https://docs.spring.io/spring-boot/docs/2.0.0.M4/reference/html/common-application-properties.html
But appears here in the M3 reference:
https://docs.spring.io/spring-boot/docs/2.0.0.M3/reference/html/common-application-properties.html
I was able to temporarily downgrade to M3 to restore the previous functionality but would still appreciate some guidance on what replaced it. I just need a single hardcoded basic-auth user for this scenario. I'm aware I could use object configurations to do a much more complicated setup.
Edit 2018-01-31:
The ability to auto-configure a single user has been restored (via the spring.security.user configuration keys) starting with Spring Boot 2.0.0-RC1 (source).
Original answer:
The Spring Boot security.* properties have been deprecated starting with Spring Boot 2.0.0-M4. You can read about this in the Release Notes:
Security auto-configuration has been completely revisited: developers should read the companion blog post and refer to the Spring Boot 2.0 Security wiki page for more details about the change.
In order to restore the basic auth functionality you can register a custom WebSecurityConfigurerAdapter, like this:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder().username("user").password("password")
.authorities("ROLE_USER").build(),
User.withDefaultPasswordEncoder().username("admin").password("admin")
.authorities("ROLE_ACTUATOR", "ROLE_USER").build());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR")
.requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll()
.antMatchers("/**").hasRole("USER")
.and()
.cors()
.and()
.httpBasic();
}
}
(This will also configure basic auth for the Actuator endpoints)
If you additionally need to read the username and password from a .properties file, you can simply inject the values using the #Value annotation.

UserInfoRestTemplateFactory setup fails with ResourceServerConfigurerAdapter and two spring-cloud-services-starters

I've slightly updated an Spring Cloud Services example to illustrate a problem that I'm having:
https://github.com/spring-cloud-services-samples/greeting/compare/master...timtebeek:master
After the above changes I'm using:
spring-cloud-services-starter-config-client:1.5.0.RELEASE
spring-cloud-services-starter-service-registry:1.5.0.RELEASE
spring-cloud-starter-oauth2:2.0.14.RELEASE
I've also added a minimal ResourceServerConfigurerAdapter
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}
#Override
public void configure(final ResourceServerSecurityConfigurer resources) {
resources.resourceId("greeter");
}
}
And the bare minimum configuration setting:
security:
oauth2:
resource:
jwt:
key-uri: https://example.com/oauth/token_key
With these changes my application fails to deploy in PCF-DEV; I've not tried PCF proper, but expect the results to be similar. Here's the error message I get:
Method userInfoRestTemplateFactory in org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerTokenServicesConfiguration required a single bean, but 2 were found:
- eurekaOAuth2ResourceDetails: defined by method 'eurekaOAuth2ResourceDetails' in class path resource [io/pivotal/spring/cloud/service/eureka/EurekaOAuth2AutoConfiguration.class]
- configClientOAuth2ResourceDetails: defined by method 'configClientOAuth2ResourceDetails' in io.pivotal.spring.cloud.service.config.ConfigClientOAuth2BootstrapConfiguration
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
So it's trying to use what should be completely separate OAuth2ProtectedResourceDetails from spring-cloud-services-starters to setup my application security; Where I would want it to use the external JWT key only.
Can anyone help me with how I can have my PCF deployed application using both config and discovery services also use an external JWT token for authentication setup?
I ran into the same thing. I saw this thread a few months ago. I also upgraded to spring boot 1.5.4 and cloud Dalston.SR4 and that got me over the hump. Thanks.
I was shown that http://start.spring.io was using spring boot 1.5.9. It runs on PivotalWS, so I knew there was a solution.
Try this change:
security:
oauth2:
client:
client-id: someclient
client-secret: someclientpass
resource:
jwt:
key-uri: https://example.com/oauth/token_key
The client-id and client-secret are dummy values in my case. I assume since you are also using JWT token, that your resource doesn't need to validate the token with your JWT token provider, only the signature (key-uri).
So by adding the client-id and client-secret, I'm guessing (totally guessing) that it creates the required OAuth2ProtectedResourceDetails with a better (closer) scope.
The fact that it was looking for "userInfoRestTemplateFactory" when we don't need to lookup user info is what pointed me in this direction.
My services are successfully deploying on PivotalWS (run.pivotal.io) with this change, using spring boot 1.5.9 and Dalston.SR4 with io.pivotal.spring.cloud:spring-cloud-services-dependencies:1.5.0.RELEASE
change spring-boot-starter-parent to be 1.5.2.RELEASE ,spring-cloud-dependencies to be Dalston.RC1 , spring-cloud-services-dependencies 1.5.0.RELEASE

Use oauth2 scope instead of role to secure spring actuator management endpoints

I've upgraded to Spring Cloud Dalston recently, that means Spring Boot 1.5.1, and I can not secure the management endpoints by checking an oauth2 scope anymore. It worked before in Spring Cloud Camden.
This is the configuration that worked before :
#Configuration
public class SecurityConfiguration extends ResourceServerConfigurerAdapter {
#Value("${management.context-path}")
private String managementContextPath;
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// some paths I don't want to secure at all
.antMatchers("/path1/**", "/path2/**").permitAll()
// access to health endpoint is open to anyone
.antMatchers(HttpMethod.GET, managementContextPath + "/health").permitAll()
// but app.admin scope is necessary for other management endpoints
.antMatchers(managementContextPath + "/**").access("#oauth2.hasScope('my-super-scope')") //
// And we make sure the user is authenticated for all the other cases
.anyRequest().authenticated();
}
}
And this is the important part of the config :
security:
oauth2:
client:
clientId: a-client
clientSecret: the-client-password
resource:
tokenInfoUri: http://my-spring-oauth2-provider/oauth/check_token
management:
context-path: /my-context
security:
enabled: true
endpoints:
health:
sensitive: false
When I try to POST on /my-context/refresh I get a HTTP 401 "Full authentication is needed" even though I give a valid OAuth2 token
Looking through the log I saw that my request was considered anonymous, and checking the FilterChainProxy log saw that the OAuth2AuthenticationProcessingFilter was not called. After a bit of digging I found that I could change the oauth2 resource filter order, so I tried that and now I have an OAuth2 authentication, yeah, finished right ?
Hum, no, now I have an Access is denied. User must have one of the these roles: ACTUATOR error.
I tried a few other things, including disabling management security (but my rules are not applied and access is open to everyone), playing with (ugh) #Order (no change), and even, lo and behold, reading and applying the documentation which says :
To override the application access rules add a #Bean of type
WebSecurityConfigurerAdapter and use
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) if you don’t want to
override the actuator access rules, or
#Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER) if you do
want to override the actuator access rules.
But this did not change the error : User must have one of these roles: ACTUATOR
Does anybody have a workaround/idea ?
Update : I'm also using Zuul, so I finally created a specific zuul route to the endpoint I needed (cloud bus refresh in my case), unprotected on an other backend service that was not exposed otherwise, and protected that zuul route with oauth2.
I'm leaving this question here nevertheless, if anyone finds a workaround, could be useful.
Probably being captain obvious, but see http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-monitoring.html. You can override the role with management.security.roles and simply add whatever role your Oauth2 credentials have.
I confronted with this issue also. The workaround that I used was to expose the actuator action on a new endpoint which I defined, and just call the actuator bean to handle request.
For example to secure /my-context/refresh with Oauth2 , I just expose a new resource at {whatever-api-prefix}/refreshConfig and I exposed a request handler on the rest controller for this URL; in the rest controller I wire the RefreshEndpoint bean and in the request handler I just call the refreshEndpoint.refresh().

Spring boot actuator secure services does not work fine

I an Trying to secure spring actuator services /manage context path when calling for example:
http://localhost:9091/manage/metrics
with this config in my yalm.properties
management:
port: 9091
address: 127.0.0.1
context-path: /manage
security:
enabled: true
role: ADMIN.
Git branch with security actuator service layer
but access to every service is still free.
Spring security config:
'#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/pizzas","/info","/addPizza").hasAnyRole("USER","ADMIN").and().authorizeRequests().antMatchers("/users","/addUser").hasRole("ADMIN").and().authorizeRequests().antMatchers("/static/**","/logout","/login").permitAll();
http.formLogin().loginPage("/login").failureUrl("/login?error").permitAll();
http.logout().logoutSuccessUrl("/?logout").deleteCookies("remember-me").permitAll();
http.sessionManagement().maximumSessions(1).
expiredUrl("/?expired").maxSessionsPreventsLogin(true).and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}
/**
* Configure global security with Bccyptenoncder and custom userDetailService with Spring Security
* #param auth
* #throws Exception
*/
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceImpl).passwordEncoder(passwordEncoder());
}
/**
* Bcrypt password encoding configuration, more info at http://www.baeldung.com/spring-security-registration-password-encoding-bcrypt
* #return
*/
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
'
Spring boot team has resolved me this issue. I share the solution here:
Same Origin Policy
You cannot use the login page from your main Spring Application within actuator security. The reason is that the cookie is going to be associated with the domain + port + context path of the application. This is part of the Same Origin Policy
This means if you sent the user to localhost:9090/pizza/login and authenticated, when you visited localhost:9091/manage/ the JSESSIONID cookie would not be submitted to the management application which means you would not be seen as authenticated.
In order to authenticate across domains (i.e. different ports in this case) you would need some single sign on (OpenID, CAS, SAML, etc) mechanism.
Mapping a Login Page in the Management Application
In order to use this configuration you would need to setup a login page within the management application. To do this you would just need to return an HTML form when /login is requested. However, I'm not really certain how you would do that within the Boot management application. Perhaps #philwebb or #dsyer can elaborate on how one would do that.
Distinct Security Configuration for the Management Application
Alternatively you could create separate security configuration for the management application that allows authenticating with Basic Authentication. To do this you would create another Security Configuration that looks something like this:
#Order(0)
#Configuration
public class ManagementSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.requestMatchers(request -> "/manage".equals(request.getContextPath()))
.and()
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
This would make sure that if the context root is "/manage" that this security configuration is used. A few points of interest:
#Order(0) makes sure the configuration occurs before your other security configuration since by default any subclass of WebSecurityConfigurerAdapter will be ordered at 100. This is important because only the first WebSecurityConfigurerAdapter is used (similar to the authorizeRequests() matchers).
The request matcher is using a lambda for matching on the contextPath. I had thought there was a better way to distinguish Spring Boot application from the main application, but it does not appear that is the case. Perhaps #dsyer knows how this should be done.
NOTE
You can rewrite your configuration much more concisely as:
http
.authorizeRequests()
.antMatchers("/pizzas","/info","/addPizza").hasAnyRole("USER","ADMIN")
.antMatchers("/users","/addUser").hasRole("ADMIN")
.antMatchers("/static/**","/logout","/login").permitAll()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/?logout")
.deleteCookies("remember-me")
.permitAll();
You might consider reading Spring Security Java Config Preview: Readability for details on how to format the configuration to better read it too.

Resources