Skip to content

Commit a899e1c

Browse files
author
Dave Syer
committed
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]
1 parent 8f3c480 commit a899e1c

File tree

9 files changed

+119
-30
lines changed

9 files changed

+119
-30
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/SecurityAutoConfiguration.java

+35-8
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
2020
import java.util.Arrays;
2121
import java.util.List;
2222

23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
2325
import org.springframework.beans.factory.annotation.Autowired;
2426
import org.springframework.boot.actuate.endpoint.Endpoint;
2527
import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping;
28+
import org.springframework.boot.actuate.properties.ManagementServerProperties;
29+
import org.springframework.boot.actuate.properties.ManagementServerProperties.User;
2630
import org.springframework.boot.actuate.properties.SecurityProperties;
2731
import org.springframework.boot.actuate.web.ErrorController;
2832
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -43,6 +47,7 @@
4347
import org.springframework.security.config.annotation.web.builders.WebSecurity.IgnoredRequestConfigurer;
4448
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4549
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
50+
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
4651
import org.springframework.security.web.AuthenticationEntryPoint;
4752
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
4853

@@ -112,6 +117,9 @@ private static class BoostrapWebSecurityConfigurerAdapter extends
112117
@Autowired
113118
private SecurityProperties security;
114119

120+
@Autowired
121+
private ManagementServerProperties management;
122+
115123
@Autowired(required = false)
116124
private EndpointHandlerMapping endpointHandlerMapping;
117125

@@ -129,19 +137,23 @@ protected void configure(HttpSecurity http) throws Exception {
129137
}
130138

131139
if (this.security.getBasic().isEnabled()) {
132-
String[] paths = getSecurePaths();
133-
http.exceptionHandling().authenticationEntryPoint(entryPoint()).and()
134-
.requestMatchers().antMatchers(paths);
140+
http.exceptionHandling().authenticationEntryPoint(entryPoint());
135141
http.httpBasic().and().anonymous().disable();
136-
http.authorizeUrls().anyRequest()
137-
.hasRole(this.security.getBasic().getRole());
142+
ExpressionUrlAuthorizationConfigurer<HttpSecurity> authorizeUrls = http
143+
.authorizeUrls();
144+
if (getEndpointPaths(true).length > 0) {
145+
authorizeUrls.antMatchers(getEndpointPaths(true)).hasRole(
146+
this.management.getUser().getRole());
147+
}
148+
authorizeUrls.antMatchers(getSecureApplicationPaths())
149+
.hasRole(this.security.getBasic().getRole()).and().httpBasic();
138150
}
139151

140152
// No cookies for service endpoints by default
141153
http.sessionManagement().sessionCreationPolicy(this.security.getSessions());
142154
}
143155

144-
private String[] getSecurePaths() {
156+
private String[] getSecureApplicationPaths() {
145157
List<String> list = new ArrayList<String>();
146158
for (String path : this.security.getBasic().getPath()) {
147159
path = (path == null ? "" : path.trim());
@@ -203,11 +215,26 @@ protected AuthenticationManager authenticationManager() throws Exception {
203215
@Configuration
204216
public static class AuthenticationManagerConfiguration {
205217

218+
private static Log logger = LogFactory
219+
.getLog(AuthenticationManagerConfiguration.class);
220+
221+
@Autowired
222+
private ManagementServerProperties management;
223+
206224
@Bean
207225
public AuthenticationManager authenticationManager() throws Exception {
226+
User user = this.management.getUser();
227+
if (user.isDefaultPassword()) {
228+
logger.info("Using default password for ");
229+
}
230+
List<String> roles = new ArrayList<String>();
231+
roles.add("USER");
232+
if (!"USER".equals(user.getRole())) {
233+
roles.add(user.getRole());
234+
}
208235
return new AuthenticationManagerBuilder().inMemoryAuthentication()
209-
.withUser("user").password("password").roles("USER").and().and()
210-
.build();
236+
.withUser(user.getName()).password(user.getPassword())
237+
.roles(roles.toArray(new String[roles.size()])).and().and().build();
211238
}
212239

213240
}

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/ManagementServerProperties.java

+48
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.actuate.properties;
1818

1919
import java.net.InetAddress;
20+
import java.util.UUID;
2021

2122
import javax.validation.constraints.NotNull;
2223

@@ -39,8 +40,14 @@ public class ManagementServerProperties {
3940
@NotNull
4041
private String contextPath = "";
4142

43+
private User user = new User();
44+
4245
private boolean allowShutdown = false;
4346

47+
public User getUser() {
48+
return this.user;
49+
}
50+
4451
public boolean isAllowShutdown() {
4552
return this.allowShutdown;
4653
}
@@ -82,4 +89,45 @@ public void setContextPath(String contextPath) {
8289
this.contextPath = contextPath;
8390
}
8491

92+
public static class User {
93+
94+
private String name = "user";
95+
96+
private String password = UUID.randomUUID().toString();
97+
98+
private String role = "ADMIN";
99+
100+
private boolean defaultPassword;
101+
102+
public String getName() {
103+
return this.name;
104+
}
105+
106+
public void setName(String name) {
107+
this.name = name;
108+
}
109+
110+
public String getPassword() {
111+
return this.password;
112+
}
113+
114+
public void setPassword(String password) {
115+
this.defaultPassword = false;
116+
this.password = password;
117+
}
118+
119+
public String getRole() {
120+
return this.role;
121+
}
122+
123+
public void setRole(String role) {
124+
this.role = role;
125+
}
126+
127+
public boolean isDefaultPassword() {
128+
return this.defaultPassword;
129+
}
130+
131+
}
132+
85133
}

spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/SecurityAutoConfigurationTests.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.boot.actuate.autoconfigure;
1818

1919
import org.junit.Test;
20-
import org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration;
21-
import org.springframework.boot.actuate.autoconfigure.SecurityAutoConfiguration;
2220
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
2321
import org.springframework.context.annotation.Bean;
2422
import org.springframework.context.annotation.Configuration;
@@ -47,6 +45,7 @@ public void testWebConfiguration() throws Exception {
4745
this.context.setServletContext(new MockServletContext());
4846
this.context.register(SecurityAutoConfiguration.class,
4947
EndpointAutoConfiguration.class,
48+
ManagementServerPropertiesAutoConfiguration.class,
5049
PropertyPlaceholderAutoConfiguration.class);
5150
this.context.refresh();
5251
assertNotNull(this.context.getBean(AuthenticationManager.class));
@@ -58,6 +57,7 @@ public void testOverrideAuthenticationManager() throws Exception {
5857
this.context.setServletContext(new MockServletContext());
5958
this.context.register(TestConfiguration.class, SecurityAutoConfiguration.class,
6059
EndpointAutoConfiguration.class,
60+
ManagementServerPropertiesAutoConfiguration.class,
6161
PropertyPlaceholderAutoConfiguration.class);
6262
this.context.refresh();
6363
assertEquals(this.context.getBean(TestConfiguration.class).authenticationManager,

spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ManagementAddressSampleActuatorApplicationTests.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.junit.Ignore;
3131
import org.junit.Test;
3232
import org.springframework.boot.SpringApplication;
33-
import org.springframework.boot.sample.ops.SampleActuatorApplication;
33+
import org.springframework.boot.actuate.properties.ManagementServerProperties;
3434
import org.springframework.context.ConfigurableApplicationContext;
3535
import org.springframework.http.HttpRequest;
3636
import org.springframework.http.HttpStatus;
@@ -84,7 +84,7 @@ public static void stop() {
8484
@Test
8585
public void testHome() throws Exception {
8686
@SuppressWarnings("rawtypes")
87-
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
87+
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
8888
"http://localhost:" + port, Map.class);
8989
assertEquals(HttpStatus.OK, entity.getStatusCode());
9090
@SuppressWarnings("unchecked")
@@ -126,6 +126,10 @@ public void testErrorPage() throws Exception {
126126
assertEquals(999, body.get("status"));
127127
}
128128

129+
private String getPassword() {
130+
return context.getBean(ManagementServerProperties.class).getUser().getPassword();
131+
}
132+
129133
private RestTemplate getRestTemplate() {
130134
return getRestTemplate(null, null);
131135
}

spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/NoManagementSampleActuatorApplicationTests.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.junit.BeforeClass;
3030
import org.junit.Test;
3131
import org.springframework.boot.SpringApplication;
32-
import org.springframework.boot.sample.ops.SampleActuatorApplication;
32+
import org.springframework.boot.actuate.properties.ManagementServerProperties;
3333
import org.springframework.context.ConfigurableApplicationContext;
3434
import org.springframework.http.HttpRequest;
3535
import org.springframework.http.HttpStatus;
@@ -82,7 +82,7 @@ public static void stop() {
8282
@Test
8383
public void testHome() throws Exception {
8484
@SuppressWarnings("rawtypes")
85-
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
85+
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
8686
"http://localhost:8080", Map.class);
8787
assertEquals(HttpStatus.OK, entity.getStatusCode());
8888
@SuppressWarnings("unchecked")
@@ -94,11 +94,15 @@ public void testHome() throws Exception {
9494
public void testMetricsNotAvailable() throws Exception {
9595
testHome(); // makes sure some requests have been made
9696
@SuppressWarnings("rawtypes")
97-
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
97+
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
9898
"http://localhost:" + managementPort + "/metrics", Map.class);
9999
assertEquals(HttpStatus.NOT_FOUND, entity.getStatusCode());
100100
}
101101

102+
private String getPassword() {
103+
return context.getBean(ManagementServerProperties.class).getUser().getPassword();
104+
}
105+
102106
private RestTemplate getRestTemplate(final String username, final String password) {
103107

104108
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();

spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/SampleActuatorApplicationTests.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.junit.BeforeClass;
3030
import org.junit.Test;
3131
import org.springframework.boot.SpringApplication;
32-
import org.springframework.boot.sample.ops.SampleActuatorApplication;
32+
import org.springframework.boot.actuate.properties.ManagementServerProperties;
3333
import org.springframework.context.ConfigurableApplicationContext;
3434
import org.springframework.http.HttpRequest;
3535
import org.springframework.http.HttpStatus;
@@ -92,7 +92,7 @@ public void testHomeIsSecure() throws Exception {
9292
@Test
9393
public void testHome() throws Exception {
9494
@SuppressWarnings("rawtypes")
95-
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
95+
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
9696
"http://localhost:8080", Map.class);
9797
assertEquals(HttpStatus.OK, entity.getStatusCode());
9898
@SuppressWarnings("unchecked")
@@ -104,7 +104,7 @@ public void testHome() throws Exception {
104104
public void testMetrics() throws Exception {
105105
testHome(); // makes sure some requests have been made
106106
@SuppressWarnings("rawtypes")
107-
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
107+
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
108108
"http://localhost:8080/metrics", Map.class);
109109
assertEquals(HttpStatus.OK, entity.getStatusCode());
110110
@SuppressWarnings("unchecked")
@@ -115,7 +115,7 @@ public void testMetrics() throws Exception {
115115
@Test
116116
public void testEnv() throws Exception {
117117
@SuppressWarnings("rawtypes")
118-
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
118+
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
119119
"http://localhost:8080/env", Map.class);
120120
assertEquals(HttpStatus.OK, entity.getStatusCode());
121121
@SuppressWarnings("unchecked")
@@ -134,7 +134,7 @@ public void testHealth() throws Exception {
134134
@Test
135135
public void testErrorPage() throws Exception {
136136
@SuppressWarnings("rawtypes")
137-
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
137+
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
138138
"http://localhost:8080/foo", Map.class);
139139
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, entity.getStatusCode());
140140
@SuppressWarnings("unchecked")
@@ -157,8 +157,8 @@ public void testErrorPageDirectAccess() throws Exception {
157157
@Test
158158
public void testBeans() throws Exception {
159159
@SuppressWarnings("rawtypes")
160-
ResponseEntity<List> entity = getRestTemplate("user", "password").getForEntity(
161-
"http://localhost:8080/beans", List.class);
160+
ResponseEntity<List> entity = getRestTemplate("user", getPassword())
161+
.getForEntity("http://localhost:8080/beans", List.class);
162162
assertEquals(HttpStatus.OK, entity.getStatusCode());
163163
assertEquals(1, entity.getBody().size());
164164
@SuppressWarnings("unchecked")
@@ -167,6 +167,10 @@ public void testBeans() throws Exception {
167167
((String) body.get("context")).startsWith("application"));
168168
}
169169

170+
private String getPassword() {
171+
return context.getBean(ManagementServerProperties.class).getUser().getPassword();
172+
}
173+
170174
private RestTemplate getRestTemplate() {
171175
return getRestTemplate(null, null);
172176
}

spring-boot-samples/spring-boot-sample-actuator/src/test/java/org/springframework/boot/sample/ops/ShutdownSampleActuatorApplicationTests.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.junit.BeforeClass;
3030
import org.junit.Test;
3131
import org.springframework.boot.SpringApplication;
32-
import org.springframework.boot.sample.ops.SampleActuatorApplication;
32+
import org.springframework.boot.actuate.properties.ManagementServerProperties;
3333
import org.springframework.context.ConfigurableApplicationContext;
3434
import org.springframework.http.HttpRequest;
3535
import org.springframework.http.HttpStatus;
@@ -79,7 +79,7 @@ public static void stop() {
7979
@Test
8080
public void testHome() throws Exception {
8181
@SuppressWarnings("rawtypes")
82-
ResponseEntity<Map> entity = getRestTemplate("user", "password").getForEntity(
82+
ResponseEntity<Map> entity = getRestTemplate("user", getPassword()).getForEntity(
8383
"http://localhost:8080", Map.class);
8484
assertEquals(HttpStatus.OK, entity.getStatusCode());
8585
@SuppressWarnings("unchecked")
@@ -90,15 +90,19 @@ public void testHome() throws Exception {
9090
@Test
9191
public void testShutdown() throws Exception {
9292
@SuppressWarnings("rawtypes")
93-
ResponseEntity<Map> entity = getRestTemplate("user", "password").postForEntity(
94-
"http://localhost:8080/shutdown", null, Map.class);
93+
ResponseEntity<Map> entity = getRestTemplate("user", getPassword())
94+
.postForEntity("http://localhost:8080/shutdown", null, Map.class);
9595
assertEquals(HttpStatus.OK, entity.getStatusCode());
9696
@SuppressWarnings("unchecked")
9797
Map<String, Object> body = entity.getBody();
9898
assertTrue("Wrong body: " + body,
9999
((String) body.get("message")).contains("Shutting down"));
100100
}
101101

102+
private String getPassword() {
103+
return context.getBean(ManagementServerProperties.class).getUser().getPassword();
104+
}
105+
102106
private RestTemplate getRestTemplate(final String username, final String password) {
103107

104108
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();

spring-boot-samples/spring-boot-sample-tomcat/src/test/java/org/springframework/boot/sample/tomcat/NonAutoConfigurationSampleTomcatApplicationTests.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
3030
import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration;
3131
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
32-
import org.springframework.boot.sample.tomcat.SampleTomcatApplication;
3332
import org.springframework.boot.sample.tomcat.service.HelloWorldService;
3433
import org.springframework.boot.sample.tomcat.web.SampleController;
3534
import org.springframework.context.ConfigurableApplicationContext;
@@ -76,7 +75,7 @@ public ConfigurableApplicationContext call() throws Exception {
7675
.run(NonAutoConfigurationSampleTomcatApplication.class);
7776
}
7877
});
79-
context = future.get(10, TimeUnit.SECONDS);
78+
context = future.get(60, TimeUnit.SECONDS);
8079
}
8180

8281
@AfterClass

spring-boot-samples/spring-boot-sample-tomcat/src/test/java/org/springframework/boot/sample/tomcat/SampleTomcatApplicationTests.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.junit.BeforeClass;
2727
import org.junit.Test;
2828
import org.springframework.boot.SpringApplication;
29-
import org.springframework.boot.sample.tomcat.SampleTomcatApplication;
3029
import org.springframework.context.ConfigurableApplicationContext;
3130
import org.springframework.http.HttpStatus;
3231
import org.springframework.http.ResponseEntity;
@@ -56,7 +55,7 @@ public ConfigurableApplicationContext call() throws Exception {
5655
.run(SampleTomcatApplication.class);
5756
}
5857
});
59-
context = future.get(10, TimeUnit.SECONDS);
58+
context = future.get(60, TimeUnit.SECONDS);
6059
}
6160

6261
@AfterClass

0 commit comments

Comments
 (0)