ManagementServerConfiguration security

Management endpoints are still secure by default if
Spring Security is present, but now the default
user details have an ADMIN role, and a random password
(which is logged at INFO level if not overridden).

To override you add management.user.password (name, role)
to external properties.

[Fixes #53029715] [bs-203]
This commit is contained in:
Dave Syer 2013-08-22 10:34:40 +01:00 committed by Phillip Webb
parent c582fa2067
commit 621116c9b8
9 changed files with 119 additions and 30 deletions

View File

@ -20,9 +20,13 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.endpoint.Endpoint;
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
import org.springframework.boot.actuate.properties.ManagementServerProperties;
import org.springframework.boot.actuate.properties.ManagementServerProperties.User;
import org.springframework.boot.actuate.properties.SecurityProperties;
import org.springframework.boot.actuate.web.ErrorController;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@ -43,6 +47,7 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
@ -112,6 +117,9 @@ public class SecurityAutoConfiguration {
@Autowired
private SecurityProperties security;
@Autowired
private ManagementServerProperties management;
@Autowired(required = false)
private EndpointHandlerMapping endpointHandlerMapping;
@ -129,19 +137,23 @@ public class SecurityAutoConfiguration {
}
if (this.security.getBasic().isEnabled()) {
String[] paths = getSecurePaths();
http.exceptionHandling().authenticationEntryPoint(entryPoint()).and()
.requestMatchers().antMatchers(paths);
http.exceptionHandling().authenticationEntryPoint(entryPoint());
http.httpBasic().and().anonymous().disable();
http.authorizeUrls().anyRequest()
.hasRole(this.security.getBasic().getRole());
ExpressionUrlAuthorizationConfigurer<HttpSecurity> authorizeUrls = http
.authorizeUrls();
if (getEndpointPaths(true).length > 0) {
authorizeUrls.antMatchers(getEndpointPaths(true)).hasRole(
this.management.getUser().getRole());
}
authorizeUrls.antMatchers(getSecureApplicationPaths())
.hasRole(this.security.getBasic().getRole()).and().httpBasic();
}
// No cookies for service endpoints by default
http.sessionManagement().sessionCreationPolicy(this.security.getSessions());
}
private String[] getSecurePaths() {
private String[] getSecureApplicationPaths() {
List<String> list = new ArrayList<String>();
for (String path : this.security.getBasic().getPath()) {
path = (path == null ? "" : path.trim());
@ -203,11 +215,26 @@ public class SecurityAutoConfiguration {
@Configuration
public static class AuthenticationManagerConfiguration {
private static Log logger = LogFactory
.getLog(AuthenticationManagerConfiguration.class);
@Autowired
private ManagementServerProperties management;
@Bean
public AuthenticationManager authenticationManager() throws Exception {
User user = this.management.getUser();
if (user.isDefaultPassword()) {
logger.info("Using default password for ");
}
List<String> roles = new ArrayList<String>();
roles.add("USER");
if (!"USER".equals(user.getRole())) {
roles.add(user.getRole());
}
return new AuthenticationManagerBuilder().inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and().and()
.build();
.withUser(user.getName()).password(user.getPassword())
.roles(roles.toArray(new String[roles.size()])).and().and().build();
}
}

View File

@ -17,6 +17,7 @@
package org.springframework.boot.actuate.properties;
import java.net.InetAddress;
import java.util.UUID;
import javax.validation.constraints.NotNull;
@ -39,8 +40,14 @@ public class ManagementServerProperties {
@NotNull
private String contextPath = "";
private User user = new User();
private boolean allowShutdown = false;
public User getUser() {
return this.user;
}
public boolean isAllowShutdown() {
return this.allowShutdown;
}
@ -82,4 +89,45 @@ public class ManagementServerProperties {
this.contextPath = contextPath;
}
public static class User {
private String name = "user";
private String password = UUID.randomUUID().toString();
private String role = "ADMIN";
private boolean defaultPassword;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.defaultPassword = false;
this.password = password;
}
public String getRole() {
return this.role;
}
public void setRole(String role) {
this.role = role;
}
public boolean isDefaultPassword() {
return this.defaultPassword;
}
}
}

View File

@ -17,8 +17,6 @@
package org.springframework.boot.actuate.autoconfigure;
import org.junit.Test;
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.SecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -47,6 +45,7 @@ public class SecurityAutoConfigurationTests {
this.context.setServletContext(new MockServletContext());
this.context.register(SecurityAutoConfiguration.class,
EndpointAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertNotNull(this.context.getBean(AuthenticationManager.class));
@ -58,6 +57,7 @@ public class SecurityAutoConfigurationTests {
this.context.setServletContext(new MockServletContext());
this.context.register(TestConfiguration.class, SecurityAutoConfiguration.class,
EndpointAutoConfiguration.class,
ManagementServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
assertEquals(this.context.getBean(TestConfiguration.class).authenticationManager,

View File

@ -30,7 +30,7 @@ import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.sample.ops.SampleActuatorApplication;
import org.springframework.boot.actuate.properties.ManagementServerProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
@ -84,7 +84,7 @@ public class ManagementAddressSampleActuatorApplicationTests {
@Test
public void testHome() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
"http://localhost:" + port, Map.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
@SuppressWarnings("unchecked")
@ -126,6 +126,10 @@ public class ManagementAddressSampleActuatorApplicationTests {
assertEquals(999, body.get("status"));
}
private String getPassword() {
return context.getBean(ManagementServerProperties.class).getUser().getPassword();
}
private RestTemplate getRestTemplate() {
return getRestTemplate(null, null);
}

View File

@ -29,7 +29,7 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.sample.ops.SampleActuatorApplication;
import org.springframework.boot.actuate.properties.ManagementServerProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
@ -82,7 +82,7 @@ public class NoManagementSampleActuatorApplicationTests {
@Test
public void testHome() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
"http://localhost:8080", Map.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
@SuppressWarnings("unchecked")
@ -94,11 +94,15 @@ public class NoManagementSampleActuatorApplicationTests {
public void testMetricsNotAvailable() throws Exception {
testHome(); // makes sure some requests have been made
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
"http://localhost:" + managementPort + "/metrics", Map.class);
assertEquals(HttpStatus.NOT_FOUND, entity.getStatusCode());
}
private String getPassword() {
return context.getBean(ManagementServerProperties.class).getUser().getPassword();
}
private RestTemplate getRestTemplate(final String username, final String password) {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();

View File

@ -29,7 +29,7 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.sample.ops.SampleActuatorApplication;
import org.springframework.boot.actuate.properties.ManagementServerProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
@ -92,7 +92,7 @@ public class SampleActuatorApplicationTests {
@Test
public void testHome() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
"http://localhost:8080", Map.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
@SuppressWarnings("unchecked")
@ -104,7 +104,7 @@ public class SampleActuatorApplicationTests {
public void testMetrics() throws Exception {
testHome(); // makes sure some requests have been made
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
"http://localhost:8080/metrics", Map.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
@SuppressWarnings("unchecked")
@ -115,7 +115,7 @@ public class SampleActuatorApplicationTests {
@Test
public void testEnv() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
"http://localhost:8080/env", Map.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
@SuppressWarnings("unchecked")
@ -134,7 +134,7 @@ public class SampleActuatorApplicationTests {
@Test
public void testErrorPage() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
"http://localhost:8080/foo", Map.class);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, entity.getStatusCode());
@SuppressWarnings("unchecked")
@ -157,8 +157,8 @@ public class SampleActuatorApplicationTests {
@Test
public void testBeans() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<List> entity = getRestTemplate("user", "password").getForEntity(
"http://localhost:8080/beans", List.class);
ResponseEntity<List> entity = getRestTemplate("user", getPassword())
.getForEntity("http://localhost:8080/beans", List.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals(1, entity.getBody().size());
@SuppressWarnings("unchecked")
@ -167,6 +167,10 @@ public class SampleActuatorApplicationTests {
((String) body.get("context")).startsWith("application"));
}
private String getPassword() {
return context.getBean(ManagementServerProperties.class).getUser().getPassword();
}
private RestTemplate getRestTemplate() {
return getRestTemplate(null, null);
}

View File

@ -29,7 +29,7 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.sample.ops.SampleActuatorApplication;
import org.springframework.boot.actuate.properties.ManagementServerProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
@ -79,7 +79,7 @@ public class ShutdownSampleActuatorApplicationTests {
@Test
public void testHome() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
"http://localhost:8080", Map.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
@SuppressWarnings("unchecked")
@ -90,8 +90,8 @@ public class ShutdownSampleActuatorApplicationTests {
@Test
public void testShutdown() throws Exception {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = getRestTemplate("user", "password").postForEntity(
"http://localhost:8080/shutdown", null, Map.class);
ResponseEntity<Map> entity = getRestTemplate("user", getPassword())
.postForEntity("http://localhost:8080/shutdown", null, Map.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
@SuppressWarnings("unchecked")
Map<String, Object> body = entity.getBody();
@ -99,6 +99,10 @@ public class ShutdownSampleActuatorApplicationTests {
((String) body.get("message")).contains("Shutting down"));
}
private String getPassword() {
return context.getBean(ManagementServerProperties.class).getUser().getPassword();
}
private RestTemplate getRestTemplate(final String username, final String password) {
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();

View File

@ -29,7 +29,6 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.sample.tomcat.SampleTomcatApplication;
import org.springframework.boot.sample.tomcat.service.HelloWorldService;
import org.springframework.boot.sample.tomcat.web.SampleController;
import org.springframework.context.ConfigurableApplicationContext;
@ -76,7 +75,7 @@ public class NonAutoConfigurationSampleTomcatApplicationTests {
.run(NonAutoConfigurationSampleTomcatApplication.class);
}
});
context = future.get(10, TimeUnit.SECONDS);
context = future.get(60, TimeUnit.SECONDS);
}
@AfterClass

View File

@ -26,7 +26,6 @@ import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.sample.tomcat.SampleTomcatApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -56,7 +55,7 @@ public class SampleTomcatApplicationTests {
.run(SampleTomcatApplication.class);
}
});
context = future.get(10, TimeUnit.SECONDS);
context = future.get(60, TimeUnit.SECONDS);
}
@AfterClass