diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityService.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityService.java index cb303c6e5d..ce6126ba88 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityService.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/CloudFoundrySecurityService.java @@ -49,7 +49,8 @@ class CloudFoundrySecurityService { String cloudControllerUrl) { Assert.notNull(restTemplateBuilder, "RestTemplateBuilder must not be null"); Assert.notNull(cloudControllerUrl, "CloudControllerUrl must not be null"); - this.restTemplate = restTemplateBuilder.build(); + this.restTemplate = restTemplateBuilder + .requestFactory(SkipSslVerificationHttpRequestFactory.class).build(); this.cloudControllerUrl = cloudControllerUrl; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/SkipSslVerificationHttpRequestFactory.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/SkipSslVerificationHttpRequestFactory.java new file mode 100644 index 0000000000..fc35f5714a --- /dev/null +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/cloudfoundry/SkipSslVerificationHttpRequestFactory.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-2016 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 org.springframework.boot.actuate.cloudfoundry; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.springframework.http.client.SimpleClientHttpRequestFactory; + +/** + * {@link SimpleClientHttpRequestFactory} that skips SSL certificate verification. + * + * @author Madhura Bhave + */ +class SkipSslVerificationHttpRequestFactory extends SimpleClientHttpRequestFactory { + + @Override + protected void prepareConnection(HttpURLConnection connection, String httpMethod) + throws IOException { + if (connection instanceof HttpsURLConnection) { + prepareHttpsConnection((HttpsURLConnection) connection); + } + super.prepareConnection(connection, httpMethod); + } + + private void prepareHttpsConnection(HttpsURLConnection connection) { + connection.setHostnameVerifier(new SkipHostnameVerifier()); + try { + connection.setSSLSocketFactory(createSslSocketFactory()); + } + catch (Exception ex) { + // Ignore + } + } + + private SSLSocketFactory createSslSocketFactory() throws Exception { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, new TrustManager[] { new SkipX509TrustManager() }, + new SecureRandom()); + return context.getSocketFactory(); + } + + private class SkipHostnameVerifier implements HostnameVerifier { + + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + + } + + private static class SkipX509TrustManager implements X509TrustManager { + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/SkipSslVerificationHttpRequestFactoryTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/SkipSslVerificationHttpRequestFactoryTests.java new file mode 100644 index 0000000000..68828b93f0 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/SkipSslVerificationHttpRequestFactoryTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2016 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 org.springframework.boot.actuate.cloudfoundry; + +import javax.net.ssl.SSLHandshakeException; + +import org.hamcrest.Matcher; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.context.embedded.EmbeddedServletContainer; +import org.springframework.boot.context.embedded.ExampleServlet; +import org.springframework.boot.context.embedded.Ssl; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.instanceOf; + +/** + * Test for {@link SkipSslVerificationHttpRequestFactory}. + */ +public class SkipSslVerificationHttpRequestFactoryTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void restCallToSelfSignedServershouldNotThrowSslException() throws Exception { + String httpsUrl = getHttpsUrl(); + SkipSslVerificationHttpRequestFactory requestFactory = new SkipSslVerificationHttpRequestFactory(); + RestTemplate restTemplate = new RestTemplate(requestFactory); + ResponseEntity responseEntity = restTemplate.getForEntity(httpsUrl, + String.class); + assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); + this.thrown.expect(ResourceAccessException.class); + this.thrown.expectCause(isSSLHandshakeException()); + RestTemplate otherRestTemplate = new RestTemplate(); + otherRestTemplate.getForEntity(httpsUrl, String.class); + } + + private Matcher isSSLHandshakeException() { + return instanceOf(SSLHandshakeException.class); + } + + private String getHttpsUrl() { + TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory( + 0); + factory.setSsl(getSsl("password", "classpath:test.jks")); + EmbeddedServletContainer container = factory.getEmbeddedServletContainer( + new ServletRegistrationBean(new ExampleServlet(), "/hello")); + container.start(); + return "https://localhost:" + container.getPort() + "/hello"; + } + + private Ssl getSsl(String keyPassword, String keyStore) { + Ssl ssl = new Ssl(); + ssl.setEnabled(true); + ssl.setKeyPassword(keyPassword); + ssl.setKeyStore(keyStore); + ssl.setKeyStorePassword("secret"); + return ssl; + } + +} diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/TokenTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/TokenTests.java index 06e15d54ff..4807449713 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/TokenTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/cloudfoundry/TokenTests.java @@ -24,7 +24,6 @@ import org.springframework.boot.actuate.cloudfoundry.CloudFoundryAuthorizationEx import org.springframework.util.Base64Utils; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.actuate.cloudfoundry.AuthorizationExceptionMatcher.withReason; /** * Tests for {@link Token}. @@ -38,7 +37,8 @@ public class TokenTests { @Test public void invalidJwtShouldThrowException() throws Exception { - this.thrown.expect(withReason(Reason.INVALID_TOKEN)); + this.thrown + .expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN)); new Token("invalid-token"); } @@ -46,7 +46,8 @@ public class TokenTests { public void invalidJwtClaimsShouldThrowException() throws Exception { String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}"; String claims = "invalid-claims"; - this.thrown.expect(withReason(Reason.INVALID_TOKEN)); + this.thrown + .expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN)); new Token(Base64Utils.encodeToString(header.getBytes()) + "." + Base64Utils.encodeToString(claims.getBytes())); } @@ -55,7 +56,8 @@ public class TokenTests { public void invalidJwtHeaderShouldThrowException() throws Exception { String header = "invalid-header"; String claims = "{\"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\"}"; - this.thrown.expect(withReason(Reason.INVALID_TOKEN)); + this.thrown + .expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN)); new Token(Base64Utils.encodeToString(header.getBytes()) + "." + Base64Utils.encodeToString(claims.getBytes())); } @@ -64,7 +66,8 @@ public class TokenTests { public void emptyJwtSignatureShouldThrowException() throws Exception { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwu" + "Y29tIiwiZXhwIjoxNDI2NDIwODAwLCJhd2Vzb21lIjp0cnVlfQ."; - this.thrown.expect(withReason(Reason.INVALID_TOKEN)); + this.thrown + .expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN)); new Token(token); } @@ -90,7 +93,8 @@ public class TokenTests { String header = "{\"kid\": \"key-id\", \"typ\": \"JWT\"}"; String claims = "{\"exp\": 2147483647, \"iss\": \"http://localhost:8080/uaa/oauth/token\"}"; Token token = createToken(header, claims); - this.thrown.expect(withReason(Reason.INVALID_TOKEN)); + this.thrown + .expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN)); token.getSignatureAlgorithm(); } @@ -99,7 +103,8 @@ public class TokenTests { String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}"; String claims = "{\"exp\": 2147483647}"; Token token = createToken(header, claims); - this.thrown.expect(withReason(Reason.INVALID_TOKEN)); + this.thrown + .expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN)); token.getIssuer(); } @@ -108,7 +113,8 @@ public class TokenTests { String header = "{\"alg\": \"RS256\", \"kid\": \"key-id\", \"typ\": \"JWT\"}"; String claims = "{\"iss\": \"http://localhost:8080/uaa/oauth/token\"" + "}"; Token token = createToken(header, claims); - this.thrown.expect(withReason(Reason.INVALID_TOKEN)); + this.thrown + .expect(AuthorizationExceptionMatcher.withReason(Reason.INVALID_TOKEN)); token.getExpiry(); }