Overhaul actuator endpoint code

Refactor several areas of the actuator endpoint code in order to make
future extensions easier. The primary goal is to introduce the concept
of an `ExposableEndpoint` that has technology specific subclasses and
can carry additional data for filters to use. Many other changes have
been made along the way including:

* A new EndpointSupplier interface that allows cleaner separation of
  supplying vs discovering endpoints. This allows cleaner class names
  and allows for better auto-configuration since a user can choose to
  provide their own supplier entirely.

* A `DiscoveredEndpoint` interface that allows the `EndpointFilter`
  to be greatly simplified. A filter now doesn't need to know about
  discovery concerns unless absolutely necessary.

* Improved naming and package structure. Many technology specific
  concerns are now grouped in a better way. Related concerns are
  co-located and concepts from one area no longer leakage into another.

* Simplified `HandlerMapping` implementations. Many common concerns have
  been pulled up helping to create simpler subclasses.

* Simplified JMX adapters. Many of the intermediary `Info` classes have
  been removed. The `DiscoveredJmxOperation` is now responsible for
  mapping methods to operations.

* A specific @`HealthEndpointCloudFoundryExtension` for Cloud Foundry.
  The extension logic used to create a "full" health endpoint extension
  has been made explicit.

Fixes gh-11428
Fixes gh-11581
This commit is contained in:
Phillip Webb
2018-01-09 20:57:10 -08:00
parent dc935fba48
commit 1d39feffea
157 changed files with 6913 additions and 5183 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@@ -23,7 +23,9 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.LocalHostUriTemplateHandler;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
@@ -32,18 +34,19 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Madhura Bhave
* @author Stephane Nicoll
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleActuatorCustomSecurityApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
private Environment environment;
@Test
public void homeIsSecure() {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/", Map.class);
ResponseEntity<Map> entity = restTemplate().getForEntity("/", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
@SuppressWarnings("unchecked")
Map<String, Object> body = entity.getBody();
@@ -54,7 +57,7 @@ public class SampleActuatorCustomSecurityApplicationTests {
@Test
public void testInsecureApplicationPath() {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/foo", Map.class);
ResponseEntity<Map> entity = restTemplate().getForEntity("/foo", Map.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR);
@SuppressWarnings("unchecked")
Map<String, Object> body = entity.getBody();
@@ -64,26 +67,64 @@ public class SampleActuatorCustomSecurityApplicationTests {
@Test
public void testInsecureStaticResources() {
ResponseEntity<String> entity = this.restTemplate
ResponseEntity<String> entity = restTemplate()
.getForEntity("/css/bootstrap.min.css", String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("body");
}
@Test
public void insecureActuator() {
ResponseEntity<String> entity = this.restTemplate.getForEntity("/actuator/health",
public void actuatorInsecureEndpoint() {
ResponseEntity<String> entity = restTemplate().getForEntity("/actuator/health",
String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
}
@Test
public void secureActuator() {
@SuppressWarnings("rawtypes")
ResponseEntity<Map> entity = this.restTemplate.getForEntity("/actuator/env",
Map.class);
public void actuatorSecureEndpointWithAnonymous() {
ResponseEntity<Object> entity = restTemplate().getForEntity("/actuator/env",
Object.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
@Test
public void actuatorSecureEndpointWithUnauthorizedUser() {
ResponseEntity<Object> entity = userRestTemplate().getForEntity("/actuator/env",
Object.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
public void actuatorSecureEndpointWithAuthorizedUser() {
ResponseEntity<Object> entity = adminRestTemplate().getForEntity("/actuator/env",
Object.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
public void actuatorCustomMvcSecureEndpointWithAnonymous() {
ResponseEntity<String> entity = restTemplate()
.getForEntity("/actuator/example/echo?text={t}", String.class, "test");
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
private TestRestTemplate restTemplate() {
return configure(new TestRestTemplate());
}
private TestRestTemplate adminRestTemplate() {
return configure(new TestRestTemplate("admin", "admin"));
}
private TestRestTemplate userRestTemplate() {
return configure(new TestRestTemplate("user", "password"));
}
private TestRestTemplate configure(TestRestTemplate restTemplate) {
restTemplate
.setUriTemplateHandler(new LocalHostUriTemplateHandler(this.environment));
return restTemplate;
}
}