Provide security matchers for actuator links
Fixes gh-12353
This commit is contained in:
parent
7d1faa1c88
commit
89e42d40c5
|
|
@ -30,6 +30,7 @@ import java.util.stream.Stream;
|
|||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
|
||||
import org.springframework.boot.security.reactive.ApplicationContextServerWebExchangeMatcher;
|
||||
|
|
@ -39,6 +40,7 @@ import org.springframework.security.web.server.util.matcher.PathPatternParserSer
|
|||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
|
|
@ -57,7 +59,8 @@ public final class EndpointRequest {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a matcher that includes all {@link Endpoint actuator endpoints}. The
|
||||
* Returns a matcher that includes all {@link Endpoint actuator endpoints}. It also includes
|
||||
* the links endpoint which is present at the base path of the actuator endpoints. The
|
||||
* {@link EndpointServerWebExchangeMatcher#excluding(Class...) excluding} method can
|
||||
* be used to further remove specific endpoints if required. For example:
|
||||
* <pre class="code">
|
||||
|
|
@ -66,7 +69,7 @@ public final class EndpointRequest {
|
|||
* @return the configured {@link ServerWebExchangeMatcher}
|
||||
*/
|
||||
public static EndpointServerWebExchangeMatcher toAnyEndpoint() {
|
||||
return new EndpointServerWebExchangeMatcher();
|
||||
return new EndpointServerWebExchangeMatcher(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -78,7 +81,7 @@ public final class EndpointRequest {
|
|||
* @return the configured {@link ServerWebExchangeMatcher}
|
||||
*/
|
||||
public static EndpointServerWebExchangeMatcher to(Class<?>... endpoints) {
|
||||
return new EndpointServerWebExchangeMatcher(endpoints);
|
||||
return new EndpointServerWebExchangeMatcher(endpoints, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -90,7 +93,21 @@ public final class EndpointRequest {
|
|||
* @return the configured {@link ServerWebExchangeMatcher}
|
||||
*/
|
||||
public static EndpointServerWebExchangeMatcher to(String... endpoints) {
|
||||
return new EndpointServerWebExchangeMatcher(endpoints);
|
||||
return new EndpointServerWebExchangeMatcher(endpoints, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a matcher that matches only on the links endpoint. It can be used when security configuration
|
||||
* for the links endpoint is different from the other {@link Endpoint actuator endpoints}. The
|
||||
* {@link EndpointServerWebExchangeMatcher#excludingLinks() excludingLinks} method can be used in combination with this
|
||||
* to remove the links endpoint from {@link EndpointRequest#toAnyEndpoint() toAnyEndpoint}.
|
||||
* For example: <pre class="code">
|
||||
* EndpointRequest.toLinks()
|
||||
* </pre>
|
||||
* @return the configured {@link ServerWebExchangeMatcher}
|
||||
*/
|
||||
public static LinksServerWebExchangeMatcher toLinks() {
|
||||
return new LinksServerWebExchangeMatcher();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -106,35 +123,42 @@ public final class EndpointRequest {
|
|||
|
||||
private ServerWebExchangeMatcher delegate;
|
||||
|
||||
private EndpointServerWebExchangeMatcher() {
|
||||
this(Collections.emptyList(), Collections.emptyList());
|
||||
private boolean includeLinks;
|
||||
|
||||
private EndpointServerWebExchangeMatcher(boolean includeLinks) {
|
||||
this(Collections.emptyList(), Collections.emptyList(), includeLinks);
|
||||
}
|
||||
|
||||
private EndpointServerWebExchangeMatcher(Class<?>[] endpoints) {
|
||||
this(Arrays.asList((Object[]) endpoints), Collections.emptyList());
|
||||
private EndpointServerWebExchangeMatcher(Class<?>[] endpoints, boolean includeLinks) {
|
||||
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
|
||||
}
|
||||
|
||||
private EndpointServerWebExchangeMatcher(String[] endpoints) {
|
||||
this(Arrays.asList((Object[]) endpoints), Collections.emptyList());
|
||||
private EndpointServerWebExchangeMatcher(String[] endpoints, boolean includeLinks) {
|
||||
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
|
||||
}
|
||||
|
||||
private EndpointServerWebExchangeMatcher(List<Object> includes,
|
||||
List<Object> excludes) {
|
||||
List<Object> excludes, boolean includeLinks) {
|
||||
super(PathMappedEndpoints.class);
|
||||
this.includes = includes;
|
||||
this.excludes = excludes;
|
||||
this.includeLinks = includeLinks;
|
||||
}
|
||||
|
||||
public EndpointServerWebExchangeMatcher excluding(Class<?>... endpoints) {
|
||||
List<Object> excludes = new ArrayList<>(this.excludes);
|
||||
excludes.addAll(Arrays.asList((Object[]) endpoints));
|
||||
return new EndpointServerWebExchangeMatcher(this.includes, excludes);
|
||||
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks);
|
||||
}
|
||||
|
||||
public EndpointServerWebExchangeMatcher excluding(String... endpoints) {
|
||||
List<Object> excludes = new ArrayList<>(this.excludes);
|
||||
excludes.addAll(Arrays.asList((Object[]) endpoints));
|
||||
return new EndpointServerWebExchangeMatcher(this.includes, excludes);
|
||||
return new EndpointServerWebExchangeMatcher(this.includes, excludes, this.includeLinks);
|
||||
}
|
||||
|
||||
public EndpointServerWebExchangeMatcher excludingLinks() {
|
||||
return new EndpointServerWebExchangeMatcher(this.includes, this.excludes, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -160,7 +184,11 @@ public final class EndpointRequest {
|
|||
}
|
||||
streamPaths(this.includes, pathMappedEndpoints).forEach(paths::add);
|
||||
streamPaths(this.excludes, pathMappedEndpoints).forEach(paths::remove);
|
||||
return new OrServerWebExchangeMatcher(getDelegateMatchers(paths));
|
||||
List<ServerWebExchangeMatcher> delegateMatchers = getDelegateMatchers(paths);
|
||||
if (this.includeLinks && StringUtils.hasText(pathMappedEndpoints.getBasePath())) {
|
||||
delegateMatchers.add(new PathPatternParserServerWebExchangeMatcher(pathMappedEndpoints.getBasePath()));
|
||||
}
|
||||
return new OrServerWebExchangeMatcher(delegateMatchers);
|
||||
}
|
||||
|
||||
private Stream<String> streamPaths(List<Object> source,
|
||||
|
|
@ -200,4 +228,36 @@ public final class EndpointRequest {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* The The {@link ServerWebExchangeMatcher} used to match against the links endpoint.
|
||||
*/
|
||||
public static final class LinksServerWebExchangeMatcher
|
||||
extends ApplicationContextServerWebExchangeMatcher<WebEndpointProperties> {
|
||||
|
||||
private ServerWebExchangeMatcher delegate;
|
||||
|
||||
private LinksServerWebExchangeMatcher() {
|
||||
super(WebEndpointProperties.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialized(Supplier<WebEndpointProperties> propertiesSupplier) {
|
||||
WebEndpointProperties webEndpointProperties = propertiesSupplier.get();
|
||||
if (StringUtils.hasText(webEndpointProperties.getBasePath())) {
|
||||
this.delegate = new PathPatternParserServerWebExchangeMatcher(
|
||||
webEndpointProperties.getBasePath());
|
||||
}
|
||||
else {
|
||||
this.delegate = EMPTY_MATCHER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<MatchResult> matches(ServerWebExchange exchange,
|
||||
Supplier<WebEndpointProperties> context) {
|
||||
return this.delegate.matches(exchange);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import java.util.stream.Stream;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
|
||||
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
|
||||
|
|
@ -38,6 +39,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Factory that can be used to create a {@link RequestMatcher} for actuator endpoint
|
||||
|
|
@ -55,7 +57,8 @@ public final class EndpointRequest {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns a matcher that includes all {@link Endpoint actuator endpoints}. The
|
||||
* Returns a matcher that includes all {@link Endpoint actuator endpoints}. It also includes
|
||||
* the links endpoint which is present at the base path of the actuator endpoints. The
|
||||
* {@link EndpointRequestMatcher#excluding(Class...) excluding} method can be used to
|
||||
* further remove specific endpoints if required. For example: <pre class="code">
|
||||
* EndpointRequest.toAnyEndpoint().excluding(ShutdownEndpoint.class)
|
||||
|
|
@ -63,7 +66,7 @@ public final class EndpointRequest {
|
|||
* @return the configured {@link RequestMatcher}
|
||||
*/
|
||||
public static EndpointRequestMatcher toAnyEndpoint() {
|
||||
return new EndpointRequestMatcher();
|
||||
return new EndpointRequestMatcher(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -75,7 +78,7 @@ public final class EndpointRequest {
|
|||
* @return the configured {@link RequestMatcher}
|
||||
*/
|
||||
public static EndpointRequestMatcher to(Class<?>... endpoints) {
|
||||
return new EndpointRequestMatcher(endpoints);
|
||||
return new EndpointRequestMatcher(endpoints, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -87,7 +90,21 @@ public final class EndpointRequest {
|
|||
* @return the configured {@link RequestMatcher}
|
||||
*/
|
||||
public static EndpointRequestMatcher to(String... endpoints) {
|
||||
return new EndpointRequestMatcher(endpoints);
|
||||
return new EndpointRequestMatcher(endpoints, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a matcher that matches only on the links endpoint. It can be used when security configuration
|
||||
* for the links endpoint is different from the other {@link Endpoint actuator endpoints}. The
|
||||
* {@link EndpointRequestMatcher#excludingLinks() excludingLinks} method can be used in combination with this
|
||||
* to remove the links endpoint from {@link EndpointRequest#toAnyEndpoint() toAnyEndpoint}.
|
||||
* For example: <pre class="code">
|
||||
* EndpointRequest.toLinks()
|
||||
* </pre>
|
||||
* @return the configured {@link RequestMatcher}
|
||||
*/
|
||||
public static LinksRequestMatcher toLinks() {
|
||||
return new LinksRequestMatcher();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,36 +117,43 @@ public final class EndpointRequest {
|
|||
|
||||
private final List<Object> excludes;
|
||||
|
||||
private final boolean includeLinks;
|
||||
|
||||
private volatile RequestMatcher delegate;
|
||||
|
||||
private EndpointRequestMatcher() {
|
||||
this(Collections.emptyList(), Collections.emptyList());
|
||||
private EndpointRequestMatcher(boolean includeLinks) {
|
||||
this(Collections.emptyList(), Collections.emptyList(), includeLinks);
|
||||
}
|
||||
|
||||
private EndpointRequestMatcher(Class<?>[] endpoints) {
|
||||
this(Arrays.asList((Object[]) endpoints), Collections.emptyList());
|
||||
private EndpointRequestMatcher(Class<?>[] endpoints, boolean includeLinks) {
|
||||
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
|
||||
}
|
||||
|
||||
private EndpointRequestMatcher(String[] endpoints) {
|
||||
this(Arrays.asList((Object[]) endpoints), Collections.emptyList());
|
||||
private EndpointRequestMatcher(String[] endpoints, boolean includeLinks) {
|
||||
this(Arrays.asList((Object[]) endpoints), Collections.emptyList(), includeLinks);
|
||||
}
|
||||
|
||||
private EndpointRequestMatcher(List<Object> includes, List<Object> excludes) {
|
||||
private EndpointRequestMatcher(List<Object> includes, List<Object> excludes, boolean includeLinks) {
|
||||
super(PathMappedEndpoints.class);
|
||||
this.includes = includes;
|
||||
this.excludes = excludes;
|
||||
this.includeLinks = includeLinks;
|
||||
}
|
||||
|
||||
public EndpointRequestMatcher excluding(Class<?>... endpoints) {
|
||||
List<Object> excludes = new ArrayList<>(this.excludes);
|
||||
excludes.addAll(Arrays.asList((Object[]) endpoints));
|
||||
return new EndpointRequestMatcher(this.includes, excludes);
|
||||
return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks);
|
||||
}
|
||||
|
||||
public EndpointRequestMatcher excluding(String... endpoints) {
|
||||
List<Object> excludes = new ArrayList<>(this.excludes);
|
||||
excludes.addAll(Arrays.asList((Object[]) endpoints));
|
||||
return new EndpointRequestMatcher(this.includes, excludes);
|
||||
return new EndpointRequestMatcher(this.includes, excludes, this.includeLinks);
|
||||
}
|
||||
|
||||
public EndpointRequestMatcher excludingLinks() {
|
||||
return new EndpointRequestMatcher(this.includes, this.excludes, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -154,7 +178,11 @@ public final class EndpointRequest {
|
|||
}
|
||||
streamPaths(this.includes, pathMappedEndpoints).forEach(paths::add);
|
||||
streamPaths(this.excludes, pathMappedEndpoints).forEach(paths::remove);
|
||||
return new OrRequestMatcher(getDelegateMatchers(paths));
|
||||
List<RequestMatcher> delegateMatchers = getDelegateMatchers(paths);
|
||||
if (this.includeLinks && StringUtils.hasText(pathMappedEndpoints.getBasePath())) {
|
||||
delegateMatchers.add(new AntPathRequestMatcher(pathMappedEndpoints.getBasePath()));
|
||||
}
|
||||
return new OrRequestMatcher(delegateMatchers);
|
||||
}
|
||||
|
||||
private Stream<String> streamPaths(List<Object> source,
|
||||
|
|
@ -193,4 +221,33 @@ public final class EndpointRequest {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* The request matcher used to match against the links endpoint.
|
||||
*/
|
||||
public static final class LinksRequestMatcher
|
||||
extends ApplicationContextRequestMatcher<WebEndpointProperties> {
|
||||
|
||||
private RequestMatcher delegate;
|
||||
|
||||
private LinksRequestMatcher() {
|
||||
super(WebEndpointProperties.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialized(Supplier<WebEndpointProperties> propertiesSupplier) {
|
||||
WebEndpointProperties webEndpointProperties = propertiesSupplier.get();
|
||||
if (StringUtils.hasText(webEndpointProperties.getBasePath())) {
|
||||
this.delegate = new AntPathRequestMatcher(webEndpointProperties.getBasePath());
|
||||
}
|
||||
else {
|
||||
this.delegate = EMPTY_MATCHER;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matches(HttpServletRequest request, Supplier<WebEndpointProperties> context) {
|
||||
return this.delegate.matches(request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
|||
import org.assertj.core.api.AssertDelegateTarget;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
|
|
@ -54,6 +55,16 @@ public class EndpointRequestTests {
|
|||
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||
assertMatcher(matcher).matches("/actuator/foo");
|
||||
assertMatcher(matcher).matches("/actuator/bar");
|
||||
assertMatcher(matcher).matches("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toAnyEndpointWhenBasePathIsEmptyShouldNotMatchLinks() {
|
||||
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||
RequestMatcherAssert assertMatcher = assertMatcher(matcher, "");
|
||||
assertMatcher.doesNotMatch("/");
|
||||
assertMatcher.matches("/foo");
|
||||
assertMatcher.matches("/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -86,12 +97,38 @@ public class EndpointRequestTests {
|
|||
assertMatcher(matcher).doesNotMatch("/actuator/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLinksShouldOnlyMatchLinks() {
|
||||
ServerWebExchangeMatcher matcher = EndpointRequest.toLinks();
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/bar");
|
||||
assertMatcher(matcher).matches("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLinksWhenBasePathEmptyShouldNotMatch() {
|
||||
ServerWebExchangeMatcher matcher = EndpointRequest.toLinks();
|
||||
RequestMatcherAssert assertMatcher = assertMatcher(matcher, "");
|
||||
assertMatcher.doesNotMatch("/actuator/foo");
|
||||
assertMatcher.doesNotMatch("/actuator/bar");
|
||||
assertMatcher.doesNotMatch("/");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeByClassShouldNotMatchExcluded() {
|
||||
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint()
|
||||
.excluding(FooEndpoint.class);
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).matches("/actuator/bar");
|
||||
assertMatcher(matcher).matches("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeByClassShouldNotMatchLinksIfExcluded() {
|
||||
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint()
|
||||
.excludingLinks().excluding(FooEndpoint.class);
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -100,24 +137,54 @@ public class EndpointRequestTests {
|
|||
.excluding("foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).matches("/actuator/bar");
|
||||
assertMatcher(matcher).matches("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeByIdShouldNotMatchLinksIfExcluded() {
|
||||
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint()
|
||||
.excludingLinks().excluding("foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeLinksShouldNotMatchBasePath() {
|
||||
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint().excludingLinks();
|
||||
assertMatcher(matcher).doesNotMatch("/actuator");
|
||||
assertMatcher(matcher).matches("/actuator/foo");
|
||||
assertMatcher(matcher).matches("/actuator/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeLinksShouldNotMatchBasePathIfEmptyAndExcluded() {
|
||||
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint().excludingLinks();
|
||||
RequestMatcherAssert assertMatcher = assertMatcher(matcher, "");
|
||||
assertMatcher.doesNotMatch("/");
|
||||
assertMatcher.matches("/foo");
|
||||
assertMatcher.matches("/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noEndpointPathsBeansShouldNeverMatch() {
|
||||
ServerWebExchangeMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||
assertMatcher(matcher, null).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher, null).doesNotMatch("/actuator/bar");
|
||||
assertMatcher(matcher, (PathMappedEndpoints) null).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher, (PathMappedEndpoints) null).doesNotMatch("/actuator/bar");
|
||||
}
|
||||
|
||||
private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher) {
|
||||
return assertMatcher(matcher, mockPathMappedEndpoints());
|
||||
return assertMatcher(matcher, mockPathMappedEndpoints("/actuator"));
|
||||
}
|
||||
|
||||
private PathMappedEndpoints mockPathMappedEndpoints() {
|
||||
private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher, String basePath) {
|
||||
return assertMatcher(matcher, mockPathMappedEndpoints(basePath));
|
||||
}
|
||||
|
||||
private PathMappedEndpoints mockPathMappedEndpoints(String basePath) {
|
||||
List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
|
||||
endpoints.add(mockEndpoint("foo", "foo"));
|
||||
endpoints.add(mockEndpoint("bar", "bar"));
|
||||
return new PathMappedEndpoints("/actuator", () -> endpoints);
|
||||
return new PathMappedEndpoints(basePath, () -> endpoints);
|
||||
}
|
||||
|
||||
private TestEndpoint mockEndpoint(String id, String rootPath) {
|
||||
|
|
@ -130,8 +197,13 @@ public class EndpointRequestTests {
|
|||
private RequestMatcherAssert assertMatcher(ServerWebExchangeMatcher matcher,
|
||||
PathMappedEndpoints pathMappedEndpoints) {
|
||||
StaticApplicationContext context = new StaticApplicationContext();
|
||||
context.registerBean(WebEndpointProperties.class);
|
||||
if (pathMappedEndpoints != null) {
|
||||
context.registerBean(PathMappedEndpoints.class, () -> pathMappedEndpoints);
|
||||
WebEndpointProperties properties = context.getBean(WebEndpointProperties.class);
|
||||
if (!properties.getBasePath().equals(pathMappedEndpoints.getBasePath())) {
|
||||
properties.setBasePath(pathMappedEndpoints.getBasePath());
|
||||
}
|
||||
}
|
||||
return assertThat(new RequestMatcherAssert(context, matcher));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import org.assertj.core.api.AssertDelegateTarget;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.Operation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
|
|
@ -43,6 +44,7 @@ import static org.mockito.Mockito.mock;
|
|||
* Tests for {@link EndpointRequest}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
public class EndpointRequestTests {
|
||||
|
||||
|
|
@ -51,6 +53,16 @@ public class EndpointRequestTests {
|
|||
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||
assertMatcher(matcher).matches("/actuator/foo");
|
||||
assertMatcher(matcher).matches("/actuator/bar");
|
||||
assertMatcher(matcher).matches("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toAnyEndpointWhenBasePathIsEmptyShouldNotMatchLinks() {
|
||||
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||
RequestMatcherAssert assertMatcher = assertMatcher(matcher, "");
|
||||
assertMatcher.doesNotMatch("/");
|
||||
assertMatcher.matches("/foo");
|
||||
assertMatcher.matches("/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -69,6 +81,7 @@ public class EndpointRequestTests {
|
|||
public void toEndpointClassShouldNotMatchOtherPath() {
|
||||
RequestMatcher matcher = EndpointRequest.to(FooEndpoint.class);
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/bar");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -81,6 +94,24 @@ public class EndpointRequestTests {
|
|||
public void toEndpointIdShouldNotMatchOtherPath() {
|
||||
RequestMatcher matcher = EndpointRequest.to("foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/bar");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLinksShouldOnlyMatchLinks() {
|
||||
RequestMatcher matcher = EndpointRequest.toLinks();
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/bar");
|
||||
assertMatcher(matcher).matches("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toLinksWhenBasePathEmptyShouldNotMatch() {
|
||||
RequestMatcher matcher = EndpointRequest.toLinks();
|
||||
RequestMatcherAssert assertMatcher = assertMatcher(matcher, "");
|
||||
assertMatcher.doesNotMatch("/actuator/foo");
|
||||
assertMatcher.doesNotMatch("/actuator/bar");
|
||||
assertMatcher.doesNotMatch("/");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -89,6 +120,15 @@ public class EndpointRequestTests {
|
|||
.excluding(FooEndpoint.class);
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).matches("/actuator/bar");
|
||||
assertMatcher(matcher).matches("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeByClassShouldNotMatchLinksIfExcluded() {
|
||||
RequestMatcher matcher = EndpointRequest.toAnyEndpoint()
|
||||
.excludingLinks().excluding(FooEndpoint.class);
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -96,24 +136,54 @@ public class EndpointRequestTests {
|
|||
RequestMatcher matcher = EndpointRequest.toAnyEndpoint().excluding("foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).matches("/actuator/bar");
|
||||
assertMatcher(matcher).matches("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeByIdShouldNotMatchLinksIfExcluded() {
|
||||
RequestMatcher matcher = EndpointRequest.toAnyEndpoint()
|
||||
.excludingLinks().excluding("foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher).doesNotMatch("/actuator");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeLinksShouldNotMatchBasePath() {
|
||||
RequestMatcher matcher = EndpointRequest.toAnyEndpoint().excludingLinks();
|
||||
assertMatcher(matcher).doesNotMatch("/actuator");
|
||||
assertMatcher(matcher).matches("/actuator/foo");
|
||||
assertMatcher(matcher).matches("/actuator/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void excludeLinksShouldNotMatchBasePathIfEmptyAndExcluded() {
|
||||
RequestMatcher matcher = EndpointRequest.toAnyEndpoint().excludingLinks();
|
||||
RequestMatcherAssert assertMatcher = assertMatcher(matcher, "");
|
||||
assertMatcher.doesNotMatch("/");
|
||||
assertMatcher.matches("/foo");
|
||||
assertMatcher.matches("/bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noEndpointPathsBeansShouldNeverMatch() {
|
||||
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
|
||||
assertMatcher(matcher, null).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher, null).doesNotMatch("/actuator/bar");
|
||||
assertMatcher(matcher, (PathMappedEndpoints) null).doesNotMatch("/actuator/foo");
|
||||
assertMatcher(matcher, (PathMappedEndpoints) null).doesNotMatch("/actuator/bar");
|
||||
}
|
||||
|
||||
private RequestMatcherAssert assertMatcher(RequestMatcher matcher) {
|
||||
return assertMatcher(matcher, mockPathMappedEndpoints());
|
||||
return assertMatcher(matcher, mockPathMappedEndpoints("/actuator"));
|
||||
}
|
||||
|
||||
private PathMappedEndpoints mockPathMappedEndpoints() {
|
||||
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String basePath) {
|
||||
return assertMatcher(matcher, mockPathMappedEndpoints(basePath));
|
||||
}
|
||||
|
||||
private PathMappedEndpoints mockPathMappedEndpoints(String basePath) {
|
||||
List<ExposableEndpoint<?>> endpoints = new ArrayList<>();
|
||||
endpoints.add(mockEndpoint("foo", "foo"));
|
||||
endpoints.add(mockEndpoint("bar", "bar"));
|
||||
return new PathMappedEndpoints("/actuator", () -> endpoints);
|
||||
return new PathMappedEndpoints(basePath, () -> endpoints);
|
||||
}
|
||||
|
||||
private TestEndpoint mockEndpoint(String id, String rootPath) {
|
||||
|
|
@ -126,8 +196,13 @@ public class EndpointRequestTests {
|
|||
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
|
||||
PathMappedEndpoints pathMappedEndpoints) {
|
||||
StaticWebApplicationContext context = new StaticWebApplicationContext();
|
||||
context.registerBean(WebEndpointProperties.class);
|
||||
if (pathMappedEndpoints != null) {
|
||||
context.registerBean(PathMappedEndpoints.class, () -> pathMappedEndpoints);
|
||||
WebEndpointProperties properties = context.getBean(WebEndpointProperties.class);
|
||||
if (!properties.getBasePath().equals(pathMappedEndpoints.getBasePath())) {
|
||||
properties.setBasePath(pathMappedEndpoints.getBasePath());
|
||||
}
|
||||
}
|
||||
return assertThat(new RequestMatcherAssert(context, matcher));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,14 @@ public class PathMappedEndpoints implements Iterable<PathMappedEndpoint> {
|
|||
return Collections.unmodifiableMap(endpoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base path for the endpoints.
|
||||
* @return the base path
|
||||
*/
|
||||
public String getBasePath() {
|
||||
return this.basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root path for the endpoint with the given ID or {@code null} if the
|
||||
* endpoint cannot be found.
|
||||
|
|
|
|||
|
|
@ -81,6 +81,16 @@ public class SampleActuatorCustomSecurityApplicationTests {
|
|||
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void actuatorLinksIsSecure() {
|
||||
ResponseEntity<Object> entity = restTemplate().getForEntity("/actuator",
|
||||
Object.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
entity = adminRestTemplate().getForEntity("/actuator",
|
||||
Object.class);
|
||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void actuatorSecureEndpointWithAnonymous() {
|
||||
ResponseEntity<Object> entity = restTemplate().getForEntity("/actuator/env",
|
||||
|
|
|
|||
|
|
@ -92,6 +92,16 @@ public class SampleSecureWebFluxCustomSecurityTests {
|
|||
.accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void actuatorLinksIsSecure() {
|
||||
this.webClient.get().uri("/actuator").accept(MediaType.APPLICATION_JSON)
|
||||
.exchange()
|
||||
.expectStatus().isUnauthorized();
|
||||
this.webClient.get().uri("/actuator").accept(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", "basic " + getBasicAuthForAdmin()).exchange()
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SecurityConfiguration {
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue