Commit 618535f5 authored by Andy Wilkinson's avatar Andy Wilkinson

Polish “Allow management server SSL to be configured independently”

This commit polishes b0fbc7e, throwing an exception when an attempt is
made to configure management-specific SSL without also configuring a
custom management port. The testing of management-specific SSL
configuration has also been improved.

See gh-6057
Closes gh-4810
parent 3546ae39
...@@ -160,10 +160,19 @@ public class EndpointWebMvcAutoConfiguration ...@@ -160,10 +160,19 @@ public class EndpointWebMvcAutoConfiguration
+ "through JMX)"); + "through JMX)");
} }
} }
if (managementPort == ManagementServerPort.SAME && this.applicationContext if (managementPort == ManagementServerPort.SAME) {
.getEnvironment() instanceof ConfigurableEnvironment) { if (new RelaxedPropertyResolver(this.applicationContext.getEnvironment(),
addLocalManagementPortPropertyAlias( "management.ssl.").getProperty("enabled") != null) {
(ConfigurableEnvironment) this.applicationContext.getEnvironment()); throw new IllegalStateException(
"Management-specific SSL cannot be configured as the management "
+ "server is not listening on a separate port");
}
if (this.applicationContext
.getEnvironment() instanceof ConfigurableEnvironment) {
addLocalManagementPortPropertyAlias(
(ConfigurableEnvironment) this.applicationContext
.getEnvironment());
}
} }
} }
......
...@@ -29,6 +29,11 @@ import javax.servlet.ServletContext; ...@@ -29,6 +29,11 @@ import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
...@@ -75,6 +80,7 @@ import org.springframework.http.HttpMethod; ...@@ -75,6 +80,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
...@@ -113,6 +119,12 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -113,6 +119,12 @@ public class EndpointWebMvcAutoConfigurationTests {
private static ManagementServerProperties management = new ManagementServerProperties(); private static ManagementServerProperties management = new ManagementServerProperties();
@Before
public void defaultContextPath() {
management.setContextPath("");
server.setContextPath("");
}
@Before @Before
public void grabPorts() { public void grabPorts() {
Ports values = new Ports(); Ports values = new Ports();
...@@ -175,32 +187,6 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -175,32 +187,6 @@ public class EndpointWebMvcAutoConfigurationTests {
assertThat(interceptors).hasSize(1); assertThat(interceptors).hasSize(1);
} }
@Test
public void onDifferentPortManagementSslDisabled() throws Exception {
EnvironmentTestUtils.addEnvironment(this.applicationContext,
"management.ssl.enabled:false");
this.applicationContext.register(RootConfig.class, EndpointConfig.class,
DifferentPortConfig.class, BaseConfiguration.class,
EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class);
this.applicationContext.refresh();
assertContent("/controller", ports.get().server, "controlleroutput");
assertContent("/endpoint", ports.get().server, null);
assertContent("/controller", ports.get().management, null);
assertContent("/endpoint", ports.get().management, "endpointoutput");
assertContent("/error", ports.get().management, startsWith("{"));
ApplicationContext managementContext = this.applicationContext
.getBean(ManagementContextResolver.class).getApplicationContext();
List<?> interceptors = (List<?>) ReflectionTestUtils.getField(
managementContext.getBean(EndpointHandlerMapping.class), "interceptors");
assertThat(interceptors).hasSize(1);
ManagementServerProperties managementServerProperties = this.applicationContext
.getBean(ManagementServerProperties.class);
assertThat(managementServerProperties.getSsl()).isNotNull();
assertThat(managementServerProperties.getSsl().isEnabled()).isFalse();
this.applicationContext.close();
assertAllClosed();
}
@Test @Test
public void onDifferentPortWithSpecificContainer() throws Exception { public void onDifferentPortWithSpecificContainer() throws Exception {
this.applicationContext.register(SpecificContainerConfig.class, RootConfig.class, this.applicationContext.register(SpecificContainerConfig.class, RootConfig.class,
...@@ -226,8 +212,6 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -226,8 +212,6 @@ public class EndpointWebMvcAutoConfigurationTests {
assertThat(managementContainerFactory) assertThat(managementContainerFactory)
.isInstanceOf(SpecificEmbeddedServletContainerFactory.class); .isInstanceOf(SpecificEmbeddedServletContainerFactory.class);
assertThat(managementContainerFactory).isNotSameAs(parentContainerFactory); assertThat(managementContainerFactory).isNotSameAs(parentContainerFactory);
this.applicationContext.close();
assertAllClosed();
} }
@Test @Test
...@@ -511,6 +495,73 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -511,6 +495,73 @@ public class EndpointWebMvcAutoConfigurationTests {
.hasSize(1); .hasSize(1);
} }
@Test
public void managementSpecificSslUsingDifferentPort() throws Exception {
EnvironmentTestUtils.addEnvironment(this.applicationContext,
"management.ssl.enabled=true",
"management.ssl.key-store=classpath:test.jks",
"management.ssl.key-password=password");
this.applicationContext.register(RootConfig.class, EndpointConfig.class,
DifferentPortConfig.class, BaseConfiguration.class,
EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class);
this.applicationContext.refresh();
assertContent("/controller", ports.get().server, "controlleroutput");
assertContent("/endpoint", ports.get().server, null);
assertHttpsContent("/controller", ports.get().management, null);
assertHttpsContent("/endpoint", ports.get().management, "endpointoutput");
assertHttpsContent("/error", ports.get().management, startsWith("{"));
ApplicationContext managementContext = this.applicationContext
.getBean(ManagementContextResolver.class).getApplicationContext();
List<?> interceptors = (List<?>) ReflectionTestUtils.getField(
managementContext.getBean(EndpointHandlerMapping.class), "interceptors");
assertThat(interceptors).hasSize(1);
ManagementServerProperties managementServerProperties = this.applicationContext
.getBean(ManagementServerProperties.class);
assertThat(managementServerProperties.getSsl()).isNotNull();
assertThat(managementServerProperties.getSsl().isEnabled()).isTrue();
}
@Test
public void managementSpecificSslUsingSamePortFails() throws Exception {
EnvironmentTestUtils.addEnvironment(this.applicationContext,
"management.ssl.enabled=true",
"management.ssl.key-store=classpath:test.jks",
"management.ssl.key-password=password");
this.applicationContext.register(RootConfig.class, EndpointConfig.class,
BaseConfiguration.class, EndpointWebMvcAutoConfiguration.class,
ErrorMvcAutoConfiguration.class, ServerPortConfig.class);
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Management-specific SSL cannot be configured as the "
+ "management server is not listening on a separate port");
this.applicationContext.refresh();
}
@Test
public void managementServerCanDisableSslWhenUsingADifferentPort() throws Exception {
EnvironmentTestUtils.addEnvironment(this.applicationContext,
"server.ssl.enabled=true", "server.ssl.key-store=classpath:test.jks",
"server.ssl.key-password=password", "management.ssl.enabled=false");
this.applicationContext.register(RootConfig.class, EndpointConfig.class,
DifferentPortConfig.class, BaseConfiguration.class,
EndpointWebMvcAutoConfiguration.class, ErrorMvcAutoConfiguration.class);
this.applicationContext.refresh();
assertHttpsContent("/controller", ports.get().server, "controlleroutput");
assertHttpsContent("/endpoint", ports.get().server, null);
assertContent("/controller", ports.get().management, null);
assertContent("/endpoint", ports.get().management, "endpointoutput");
assertContent("/error", ports.get().management, startsWith("{"));
ApplicationContext managementContext = this.applicationContext
.getBean(ManagementContextResolver.class).getApplicationContext();
List<?> interceptors = (List<?>) ReflectionTestUtils.getField(
managementContext.getBean(EndpointHandlerMapping.class), "interceptors");
assertThat(interceptors).hasSize(1);
ManagementServerProperties managementServerProperties = this.applicationContext
.getBean(ManagementServerProperties.class);
assertThat(managementServerProperties.getSsl()).isNotNull();
assertThat(managementServerProperties.getSsl().isEnabled()).isFalse();
}
private void endpointDisabled(String name, Class<? extends MvcEndpoint> type) { private void endpointDisabled(String name, Class<? extends MvcEndpoint> type) {
this.applicationContext.register(RootConfig.class, BaseConfiguration.class, this.applicationContext.register(RootConfig.class, BaseConfiguration.class,
ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class); ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class);
...@@ -538,10 +589,27 @@ public class EndpointWebMvcAutoConfigurationTests { ...@@ -538,10 +589,27 @@ public class EndpointWebMvcAutoConfigurationTests {
assertContent("/endpoint", ports.get().management, null); assertContent("/endpoint", ports.get().management, null);
} }
public void assertContent(String url, int port, Object expected) throws Exception { private void assertHttpsContent(String url, int port, Object expected)
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory(); throws Exception {
ClientHttpRequest request = clientHttpRequestFactory assertContent("https", url, port, expected);
.createRequest(new URI("http://localhost:" + port + url), HttpMethod.GET); }
private void assertContent(String url, int port, Object expected) throws Exception {
assertContent("http", url, port, expected);
}
private void assertContent(String scheme, String url, int port, Object expected)
throws Exception {
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy()).build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
ClientHttpRequest request = requestFactory.createRequest(
new URI(scheme + "://localhost:" + port + url), HttpMethod.GET);
try { try {
ClientHttpResponse response = request.execute(); ClientHttpResponse response = request.execute();
if (HttpStatus.NOT_FOUND.equals(response.getStatusCode())) { if (HttpStatus.NOT_FOUND.equals(response.getStatusCode())) {
......
...@@ -1006,10 +1006,25 @@ content into your application; rather pick only the properties that you need. ...@@ -1006,10 +1006,25 @@ content into your application; rather pick only the properties that you need.
management.add-application-context-header=true # Add the "X-Application-Context" HTTP header in each response. management.add-application-context-header=true # Add the "X-Application-Context" HTTP header in each response.
management.address= # Network address that the management endpoints should bind to. management.address= # Network address that the management endpoints should bind to.
management.context-path= # Management endpoint context-path. For instance `/actuator` management.context-path= # Management endpoint context-path. For instance `/actuator`
management.port= # Management endpoint HTTP port. Use the same port as the application by default. management.port= # Management endpoint HTTP port. Uses the same port as the application by default. Configure a different port to use management-specific SSL.
management.security.enabled=true # Enable security. management.security.enabled=true # Enable security.
management.security.roles=ADMIN # Comma-separated list of roles that can access the management endpoint. management.security.roles=ADMIN # Comma-separated list of roles that can access the management endpoint.
management.security.sessions=stateless # Session creating policy to use (always, never, if_required, stateless). management.security.sessions=stateless # Session creating policy to use (always, never, if_required, stateless).
management.ssl.ciphers= # Supported SSL ciphers. Requires a custom management.port.
management.ssl.client-auth= # Whether client authentication is wanted ("want") or needed ("need"). Requires a trust store. Requires a custom management.port.
management.ssl.enabled= # Enable SSL support. Requires a custom management.port.
management.ssl.enabled-protocols= # Enabled SSL protocols. Requires a custom management.port.
management.ssl.key-alias= # Alias that identifies the key in the key store. Requires a custom management.port.
management.ssl.key-password= # Password used to access the key in the key store. Requires a custom management.port.
management.ssl.key-store= # Path to the key store that holds the SSL certificate (typically a jks file). Requires a custom management.port.
management.ssl.key-store-password= # Password used to access the key store. Requires a custom management.port.
management.ssl.key-store-provider= # Provider for the key store. Requires a custom management.port.
management.ssl.key-store-type= # Type of the key store. Requires a custom management.port.
management.ssl.protocol=TLS # SSL protocol to use. Requires a custom management.port.
management.ssl.trust-store= # Trust store that holds SSL certificates. Requires a custom management.port.
management.ssl.trust-store-password= # Password used to access the trust store. Requires a custom management.port.
management.ssl.trust-store-provider= # Provider for the trust store. Requires a custom management.port.
management.ssl.trust-store-type= # Type of the trust store. Requires a custom management.port.
# HEALTH INDICATORS (previously health.*) # HEALTH INDICATORS (previously health.*)
management.health.db.enabled=true # Enable database health check. management.health.db.enabled=true # Enable database health check.
......
...@@ -595,6 +595,39 @@ disable the management security in this way, and it might even break the applica ...@@ -595,6 +595,39 @@ disable the management security in this way, and it might even break the applica
[[production-ready-management-specific-ssl]]
=== Configuring management-specific SSL
When configured to use a custom port, the management server can also be configured with
its own SSL using the various `management.ssl.*` properties. For example, this allows a
management server to be available via HTTP while the main application uses HTTPS:
[source,properties,indent=0]
----
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=classpath:store.jks
server.ssl.key-password=secret
management.port=8080
management.ssl.enable=false
----
Alternatively, both the main server and the management server can use SSL but with
different key stores:
[source,properties,indent=0]
----
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=classpath:main.jks
server.ssl.key-password=secret
management.port=8080
management.ssl.enable=true
management.ssl.key-store=classpath:management.jks
management.ssl.key-password=secret
----
[[production-ready-customizing-management-server-address]] [[production-ready-customizing-management-server-address]]
=== Customizing the management server address === Customizing the management server address
You can customize the address that the management endpoints are available on by You can customize the address that the management endpoints are available on by
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment