From 3546ae399eb91fc6db50d097da9d9de5f833d478 Mon Sep 17 00:00:00 2001 From: Alex Antonov Date: Fri, 27 May 2016 10:52:23 -0500 Subject: [PATCH 1/2] Allow management server SSL to be configured independently Closes gh-6057 --- ...dpointWebMvcChildContextConfiguration.java | 3 +++ .../ManagementServerProperties.java | 13 ++++++++++ .../EndpointWebMvcAutoConfigurationTests.java | 26 +++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java index d765209410..a61a32dceb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java @@ -188,6 +188,9 @@ public class EndpointWebMvcChildContextConfiguration { container.setContextPath(""); // and add the management-specific bits container.setPort(this.managementServerProperties.getPort()); + if (this.managementServerProperties.getSsl() != null) { + container.setSsl(this.managementServerProperties.getSsl()); + } container.setServerHeader(this.server.getServerHeader()); container.setAddress(this.managementServerProperties.getAddress()); container.addErrorPages(new ErrorPage(this.server.getError().getPath())); diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementServerProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementServerProperties.java index 7821ea8afd..0c7447bc71 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementServerProperties.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementServerProperties.java @@ -25,7 +25,9 @@ import javax.validation.constraints.NotNull; import org.springframework.boot.autoconfigure.security.SecurityPrerequisite; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.context.embedded.Ssl; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -68,6 +70,9 @@ public class ManagementServerProperties implements SecurityPrerequisite { */ private Integer port; + @NestedConfigurationProperty + private Ssl ssl; + /** * Network address that the management endpoints should bind to. */ @@ -112,6 +117,14 @@ public class ManagementServerProperties implements SecurityPrerequisite { this.port = port; } + public Ssl getSsl() { + return this.ssl; + } + + public void setSsl(Ssl ssl) { + this.ssl = ssl; + } + public InetAddress getAddress() { return this.address; } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index 34982b826f..19984d9200 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -175,6 +175,32 @@ public class EndpointWebMvcAutoConfigurationTests { 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 public void onDifferentPortWithSpecificContainer() throws Exception { this.applicationContext.register(SpecificContainerConfig.class, RootConfig.class, From 618535f576d0ee69d1b5d65561a33b65fc5cb1e6 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 27 Jun 2016 12:33:21 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Polish=20=E2=80=9CAllow=20management=20serv?= =?UTF-8?q?er=20SSL=20to=20be=20configured=20independently=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../EndpointWebMvcAutoConfiguration.java | 17 ++- .../EndpointWebMvcAutoConfigurationTests.java | 132 +++++++++++++----- .../src/test/resources/test.jks | Bin 0 -> 2248 bytes .../appendix-application-properties.adoc | 17 ++- .../asciidoc/production-ready-features.adoc | 33 +++++ 5 files changed, 162 insertions(+), 37 deletions(-) create mode 100644 spring-boot-actuator/src/test/resources/test.jks diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java index 08b95444e5..dae4058e9e 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfiguration.java @@ -160,10 +160,19 @@ public class EndpointWebMvcAutoConfiguration + "through JMX)"); } } - if (managementPort == ManagementServerPort.SAME && this.applicationContext - .getEnvironment() instanceof ConfigurableEnvironment) { - addLocalManagementPortPropertyAlias( - (ConfigurableEnvironment) this.applicationContext.getEnvironment()); + if (managementPort == ManagementServerPort.SAME) { + if (new RelaxedPropertyResolver(this.applicationContext.getEnvironment(), + "management.ssl.").getProperty("enabled") != null) { + 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()); + } } } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index 19984d9200..f44386c11e 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -29,6 +29,11 @@ import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; 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.junit.After; import org.junit.Before; @@ -75,6 +80,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Controller; import org.springframework.test.util.ReflectionTestUtils; @@ -113,6 +119,12 @@ public class EndpointWebMvcAutoConfigurationTests { private static ManagementServerProperties management = new ManagementServerProperties(); + @Before + public void defaultContextPath() { + management.setContextPath(""); + server.setContextPath(""); + } + @Before public void grabPorts() { Ports values = new Ports(); @@ -175,32 +187,6 @@ public class EndpointWebMvcAutoConfigurationTests { 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 public void onDifferentPortWithSpecificContainer() throws Exception { this.applicationContext.register(SpecificContainerConfig.class, RootConfig.class, @@ -226,8 +212,6 @@ public class EndpointWebMvcAutoConfigurationTests { assertThat(managementContainerFactory) .isInstanceOf(SpecificEmbeddedServletContainerFactory.class); assertThat(managementContainerFactory).isNotSameAs(parentContainerFactory); - this.applicationContext.close(); - assertAllClosed(); } @Test @@ -511,6 +495,73 @@ public class EndpointWebMvcAutoConfigurationTests { .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 type) { this.applicationContext.register(RootConfig.class, BaseConfiguration.class, ServerPortConfig.class, EndpointWebMvcAutoConfiguration.class); @@ -538,10 +589,27 @@ public class EndpointWebMvcAutoConfigurationTests { assertContent("/endpoint", ports.get().management, null); } - public void assertContent(String url, int port, Object expected) throws Exception { - SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory(); - ClientHttpRequest request = clientHttpRequestFactory - .createRequest(new URI("http://localhost:" + port + url), HttpMethod.GET); + private void assertHttpsContent(String url, int port, Object expected) + throws Exception { + assertContent("https", url, port, expected); + } + + 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 { ClientHttpResponse response = request.execute(); if (HttpStatus.NOT_FOUND.equals(response.getStatusCode())) { diff --git a/spring-boot-actuator/src/test/resources/test.jks b/spring-boot-actuator/src/test/resources/test.jks new file mode 100644 index 0000000000000000000000000000000000000000..cc0d7081c2e213f41f30aaba7611d55982968852 GIT binary patch literal 2248 zcmchYS5VW57RK{WLTDzS1OtM!fJje5i@P8OX(5zQrHm^`l@=+ANKFJp0!S55xHJVM zh$tYvhy)1&QbdY01@Y306saz|bLZZj@%=uWhwt}r=FE4_eCKfKa0vtgfsYFKyI=tn zZzsoqBdPB$5Mc`fv4Jr(=phCH(UBS-b&KO*g?YmrtDdmZgSRTcx1A)RGvl3lTH65Xe2 zgrJucgcQzX<{6~tFnpKwj4NKz-#(p~FTYCeSqjx?fh0H3oB~aEk zRDvPL7jeia9$YN^ClELgem{09cl@g!-Xl_kkGNL|%M>0i@1IWe@i(-&ctiO#hea^^ z@_jfH{A_x|YlrKw@4fKwu{00(=E{#4#O5{bCC_pJSTd0r0O?WI>+Zdq<-6}cKb=l- zlP=&z;tysY(kC}Yo{^Ah$?CS49M={Z2d@;=&o|k}b`rTv%iy0#o(HRWK(FvvZ;DU@ z7qdE%M4Rd(9(SoL-^*z0!4xq;bLh+v4Uq`4i!*E5JS#z>zEn!@MYgfL>DATUXHflg z4y;tK2!>IR>5d=$eyW4qRNEHQX1H=>__4heXMjW;ENH3@=wVEnv`eJTK*Sue9lKhKYBe?$kDi!ktg_WNsR|U53-M<>wXD<&>3tJ@*RKbjkEb*g{p)0N8LO zV*dWh#+Dy3`yB(h6UQ}vN%CfFL z%v>)mz93LK|6-C@fyyHdou)@(&pCG2>3PWYLAj5E!s?SuQqG~brC6d!Sy$oO&g}0t z8rqRBO9;+v^6vP0Rh5c1zY~&vNxpT^+kp*D)do*TUb|cx+lc823H1gHbG4cYp-xKr z)LmoFEs@twECT^QyXzvwLQ-Y#Ef=n}t;MWYu&RB+>UDCi*zwDz*v3^3uAi-^?%dex z%Pm?h1tJ(3m{B?+EUJUdVn zoSPFr1QhI%)6L5+^+zbVESB+p+%wGQaQH5Q<3|*J+&-l~Fw2$Dr20H~#j;i_P94UK zwVPL*2&n2Ja8L+KnjWFd<)KaE)Ct<-cmo-)GVwT>gRyg)#O70Nun>!>?!g_r)Z?y~ zyMyh&Um|VcgI*>FAQ>*DZYBoM!cdRxng{JM3$$LjB%!m)S)vH!n<2>LU`F&;npWNR z`pXH*>+7Y%k_|;#;X*YXB!h57!&60a#pC?i67a&c#t(#MjjfOgFGIdFnH_~otomjY zM3-md`gy;1701$d;-d}yoSaP%R?jv|k#_p=8czgIw4abA_iJm7W;qD%R!~NZm&Y4Q zv*#ZH2qGd~Oe$@)$4EF4M;t8LkfhmX8D#vOjBGZFs+e;i z5F{K!g9Kw}V5eLN7yyHzHu3rO7&wemHrgP18x8>6Y#@vemiH(!Ay!ZrAG?*0rw=9Y zzl7r#!u|{4`h{@+kDS2p{?!@6$LU3Ja`bYi_y=G_P;jgYMjfYu#bWWQs&;=#ECz@B zkNm&SLjxrKY01&YfY1OQ5QYYDfoT8$O4&k(o7PRcvfhTS+{E4Xc*I}FyR~vEtx`X| zSGbtbi}{8H-Vdd9qpBy=qkg+}cT_WVPK};*f{zf62$8UVVu`L2L|;Mee~#FT0;n*f z4Fem7v4cy4cR`SVCyWk1ApTo#)tDqfC5sR(MwegDu_Z&eMZRO>yrktDK5NA^KhQgS zVU3RFi$%m#2(D^qvby$;g=i<-KELg#=}O?;36clu=6px0)&o?5|dNrv5JHvcs0J5~Y6>;;$lIC(o!b+$u3PV8!L zr!Pnn76JX^0_y84e0*Snt@B;ob}M