Merge branch '2.1.x'

pull/16013/head
Madhura Bhave 6 years ago
commit 6f045d8891

@ -31,10 +31,12 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.security.reactive.ApplicationContextServerWebExchangeMatcher; import org.springframework.boot.security.reactive.ApplicationContextServerWebExchangeMatcher;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
@ -239,9 +241,28 @@ public final class EndpointRequest {
@Override @Override
protected Mono<MatchResult> matches(ServerWebExchange exchange, protected Mono<MatchResult> matches(ServerWebExchange exchange,
Supplier<PathMappedEndpoints> context) { Supplier<PathMappedEndpoints> context) {
if (!isManagementContext(exchange)) {
return MatchResult.notMatch();
}
return this.delegate.matches(exchange); return this.delegate.matches(exchange);
} }
static boolean isManagementContext(ServerWebExchange exchange) {
ApplicationContext applicationContext = exchange.getApplicationContext();
if (ManagementPortType.get(applicationContext
.getEnvironment()) == ManagementPortType.DIFFERENT) {
if (applicationContext.getParent() == null) {
return false;
}
String managementContextId = applicationContext.getParent().getId()
+ ":management";
if (!managementContextId.equals(applicationContext.getId())) {
return false;
}
}
return true;
}
} }
/** /**
@ -273,6 +294,9 @@ public final class EndpointRequest {
@Override @Override
protected Mono<MatchResult> matches(ServerWebExchange exchange, protected Mono<MatchResult> matches(ServerWebExchange exchange,
Supplier<WebEndpointProperties> context) { Supplier<WebEndpointProperties> context) {
if (!EndpointServerWebExchangeMatcher.isManagementContext(exchange)) {
return MatchResult.notMatch();
}
return this.delegate.matches(exchange); return this.delegate.matches(exchange);
} }

@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementPortType;
import org.springframework.boot.actuate.endpoint.EndpointId; import org.springframework.boot.actuate.endpoint.EndpointId;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints; import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
@ -43,6 +44,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
/** /**
* Factory that can be used to create a {@link RequestMatcher} for actuator endpoint * Factory that can be used to create a {@link RequestMatcher} for actuator endpoint
@ -133,6 +135,19 @@ public final class EndpointRequest {
@Override @Override
protected final boolean matches(HttpServletRequest request, protected final boolean matches(HttpServletRequest request,
Supplier<WebApplicationContext> context) { Supplier<WebApplicationContext> context) {
WebApplicationContext applicationContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
if (ManagementPortType.get(applicationContext
.getEnvironment()) == ManagementPortType.DIFFERENT) {
if (applicationContext.getParent() == null) {
return false;
}
String managementContextId = applicationContext.getParent().getId()
+ ":management";
if (!managementContextId.equals(applicationContext.getId())) {
return false;
}
}
return this.delegate.matches(request); return this.delegate.matches(request);
} }

@ -41,7 +41,16 @@ public enum ManagementPortType {
*/ */
DIFFERENT; DIFFERENT;
static ManagementPortType get(Environment environment) { /**
* Look at the given environment to determine if the {@link ManagementPortType} is
* {@link #DISABLED}, {@link #SAME} or {@link #DIFFERENT}.
* @param environment the Spring environment
* @return {@link #DISABLED} if `management.server.port` is set to a negative value,
* {@link #SAME} if `management.server.port` is not specified or equal to
* `server.port`and {@link #DIFFERENT} otherwise.
* @since 2.1.4
*/
public static ManagementPortType get(Environment environment) {
Integer managementPort = getPortProperty(environment, "management.server."); Integer managementPort = getPortProperty(environment, "management.server.");
if (managementPort != null && managementPort < 0) { if (managementPort != null && managementPort < 0) {
return DISABLED; return DISABLED;

@ -56,6 +56,14 @@ public class ManagementPortAndPathSampleActuatorApplicationTests {
assertThat(entity.getBody()).contains("Hello World"); assertThat(entity.getBody()).contains("Hello World");
} }
@Test
public void actuatorPathOnMainPortShouldNotMatch() {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(
"http://localhost:" + this.port + "/actuator/health",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test @Test
public void testSecureActuator() { public void testSecureActuator() {
ResponseEntity<String> entity = new TestRestTemplate().getForEntity( ResponseEntity<String> entity = new TestRestTemplate().getForEntity(

@ -0,0 +1,117 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sample.secure.webflux;
import java.util.Base64;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.reactive.EndpointRequest;
import org.springframework.boot.actuate.autoconfigure.web.server.LocalManagementPort;
import org.springframework.boot.actuate.web.mappings.MappingsEndpoint;
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
/**
* Integration tests for separate management and main service ports.
*
* @author Madhura Bhave
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"management.server.port=0" }, classes = {
ManagementPortSampleSecureWebFluxTests.SecurityConfiguration.class,
SampleSecureWebFluxApplication.class })
public class ManagementPortSampleSecureWebFluxTests {
@LocalServerPort
private int port = 9010;
@LocalManagementPort
private int managementPort = 9011;
@Autowired
private WebTestClient webClient;
@Test
public void testHome() {
this.webClient.get().uri("http://localhost:" + this.port, String.class)
.header("Authorization", "basic " + getBasicAuth()).exchange()
.expectStatus().isOk().expectBody(String.class).isEqualTo("Hello user");
}
@Test
public void actuatorPathOnMainPortShouldNotMatch() {
this.webClient.get()
.uri("http://localhost:" + this.port + "/actuator", String.class)
.exchange().expectStatus().isUnauthorized();
this.webClient.get()
.uri("http://localhost:" + this.port + "/actuator/health", String.class)
.exchange().expectStatus().isUnauthorized();
}
@Test
public void testSecureActuator() {
this.webClient.get()
.uri("http://localhost:" + this.managementPort + "/actuator/env",
String.class)
.exchange().expectStatus().isUnauthorized();
}
@Test
public void testInsecureActuator() {
String responseBody = this.webClient.get()
.uri("http://localhost:" + this.managementPort + "/actuator/health",
String.class)
.exchange().expectStatus().isOk().expectBody(String.class).returnResult()
.getResponseBody();
Assertions.assertThat(responseBody).contains("\"status\":\"UP\"");
}
private String getBasicAuth() {
return new String(Base64.getEncoder().encode(("user:password").getBytes()));
}
@Configuration
static class SecurityConfiguration {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange().matchers(EndpointRequest.to("health", "info"))
.permitAll()
.matchers(EndpointRequest.toAnyEndpoint()
.excluding(MappingsEndpoint.class))
.hasRole("ACTUATOR")
.matchers(PathRequest.toStaticResources().atCommonLocations())
.permitAll().pathMatchers("/login").permitAll().anyExchange()
.authenticated().and().httpBasic().and().build();
}
}
}
Loading…
Cancel
Save