From a8cecd0a95088a0ea9d1984eee05f3d1b0c761c9 Mon Sep 17 00:00:00 2001 From: Isaac Mosquera Date: Mon, 19 Jun 2017 13:03:42 -0700 Subject: [PATCH] feat(rest): introduces a new configuration option,`insecure` which creates a https client which is insecure by ignoring host verification and does not validate certificate chains. (#148) --- .../spinnaker/echo/config/RestConfig.groovy | 11 +-- .../echo/config/RestProperties.groovy | 1 + .../echo/events/RestClientFactory.groovy | 75 +++++++++++++++++++ .../echo/config/RestConfigSpec.groovy | 3 +- .../events/OkHttpClientFactorySpec.groovy | 24 ++++++ .../echo/events/RestClientFactorySpec.groovy | 43 +++++++++++ 6 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 echo-rest/src/main/groovy/com/netflix/spinnaker/echo/events/RestClientFactory.groovy create mode 100644 echo-rest/src/test/groovy/com/netflix/spinnaker/echo/events/OkHttpClientFactorySpec.groovy create mode 100644 echo-rest/src/test/groovy/com/netflix/spinnaker/echo/events/RestClientFactorySpec.groovy diff --git a/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/config/RestConfig.groovy b/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/config/RestConfig.groovy index 89425c04a..e5bc046bf 100644 --- a/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/config/RestConfig.groovy +++ b/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/config/RestConfig.groovy @@ -16,6 +16,7 @@ package com.netflix.spinnaker.echo.config +import com.netflix.spinnaker.echo.events.RestClientFactory import com.netflix.spinnaker.echo.rest.RestService import groovy.transform.CompileStatic import groovy.util.logging.Slf4j @@ -45,12 +46,6 @@ import static retrofit.Endpoints.newFixedEndpoint @SuppressWarnings('GStringExpressionWithinString') class RestConfig { - @Bean - @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - Client retrofitClient() { - new OkClient() - } - @Bean LogLevel retrofitLogLevel(@Value('${retrofit.logLevel:BASIC}') String retrofitLogLevel) { return LogLevel.valueOf(retrofitLogLevel) @@ -93,7 +88,7 @@ class RestConfig { } @Bean - RestUrls restServices(RestProperties restProperties, Client retrofitClient, LogLevel retrofitLogLevel, RequestInterceptorAttacher requestInterceptorAttacher, HeadersFromFile headersFromFile) { + RestUrls restServices(RestProperties restProperties, RestClientFactory clientFactory, LogLevel retrofitLogLevel, RequestInterceptorAttacher requestInterceptorAttacher, HeadersFromFile headersFromFile) { RestUrls restUrls = new RestUrls() @@ -102,7 +97,7 @@ class RestConfig { restProperties.endpoints.each { RestProperties.RestEndpointConfiguration endpoint -> RestAdapter.Builder restAdapterBuilder = new RestAdapter.Builder() .setEndpoint(newFixedEndpoint(endpoint.url as String)) - .setClient(retrofitClient) + .setClient(clientFactory.getClient(endpoint.insecure)) .setLogLevel(retrofitLogLevel) .setConverter(new JacksonConverter()) diff --git a/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/config/RestProperties.groovy b/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/config/RestProperties.groovy index 2f365a9c5..0534f67f9 100644 --- a/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/config/RestProperties.groovy +++ b/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/config/RestProperties.groovy @@ -40,6 +40,7 @@ class RestProperties { Boolean wrap = false @NotEmpty String url + Boolean insecure = false String username String password Map headers diff --git a/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/events/RestClientFactory.groovy b/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/events/RestClientFactory.groovy new file mode 100644 index 000000000..dd34e77f0 --- /dev/null +++ b/echo-rest/src/main/groovy/com/netflix/spinnaker/echo/events/RestClientFactory.groovy @@ -0,0 +1,75 @@ +/* + * Copyright 2017 Armory, Inc. + * + * 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 com.netflix.spinnaker.echo.events + +import com.squareup.okhttp.OkHttpClient +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import retrofit.client.Client +import retrofit.client.OkClient + +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager + + +interface OkHttpClientFactory { + OkHttpClient getInsecureClient() +} + +@Component +class OkHttpClientFactoryImpl implements OkHttpClientFactory { + + OkHttpClient getInsecureClient() { + // Create a trust manager that does not validate certificate chains + def trustAllCerts = [ + checkClientTrusted: { chain, authType -> }, + checkServerTrusted: { chain, authType -> }, + getAcceptedIssuers: { null } + ] + + def nullHostnameVerifier = [ + verify: { hostname, session -> true } + ] + + SSLContext sc = SSLContext.getInstance("SSL") + sc.init(null, [trustAllCerts as X509TrustManager] as TrustManager[], null) + + SSLSocketFactory sslSocketFactory = sc.getSocketFactory() + OkHttpClient okHttpClient = new OkHttpClient() + okHttpClient.setSslSocketFactory(sslSocketFactory) + okHttpClient.setHostnameVerifier(nullHostnameVerifier as HostnameVerifier) + return okHttpClient + } +} + +@Component +class RestClientFactory { + + @Autowired + OkHttpClientFactory httpClientFactory + + Client getClient(Boolean insecure) { + if (insecure) { + return new OkClient(httpClientFactory.getInsecureClient()) + } else { + return new OkClient() + } + } +} diff --git a/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/config/RestConfigSpec.groovy b/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/config/RestConfigSpec.groovy index b28570b79..272f9c56c 100644 --- a/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/config/RestConfigSpec.groovy +++ b/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/config/RestConfigSpec.groovy @@ -1,5 +1,6 @@ package com.netflix.spinnaker.echo.config +import com.netflix.spinnaker.echo.events.RestClientFactory import retrofit.RequestInterceptor import retrofit.RestAdapter import spock.lang.Specification @@ -22,7 +23,7 @@ class RestConfigSpec extends Specification { void configureRestServices(RestProperties.RestEndpointConfiguration endpoint, RestConfig.HeadersFromFile headersFromFile) { RestProperties restProperties = new RestProperties(endpoints: [endpoint]) - config.restServices(restProperties, config.retrofitClient(), config.retrofitLogLevel("BASIC"), attacher, headersFromFile) + config.restServices(restProperties, new RestClientFactory(), config.retrofitLogLevel("BASIC"), attacher, headersFromFile) } void "Generate basic auth header"() { diff --git a/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/events/OkHttpClientFactorySpec.groovy b/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/events/OkHttpClientFactorySpec.groovy new file mode 100644 index 000000000..1dda9e834 --- /dev/null +++ b/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/events/OkHttpClientFactorySpec.groovy @@ -0,0 +1,24 @@ +package com.netflix.spinnaker.echo.events + +import com.squareup.okhttp.OkHttpClient +import spock.lang.Specification +import spock.lang.Subject + +import javax.net.ssl.HostnameVerifier + +class OkHttpClientFactorySpec extends Specification { + + @Subject + OkHttpClientFactory clientFactory = new OkHttpClientFactoryImpl() + OkHttpClient insecureClient + + void 'insecure client does not verify hostname'() { + + when: + insecureClient = clientFactory.getInsecureClient() + + then: + insecureClient.getHostnameVerifier().verify("mockdomain", null) + + } +} diff --git a/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/events/RestClientFactorySpec.groovy b/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/events/RestClientFactorySpec.groovy new file mode 100644 index 000000000..1488a9ab8 --- /dev/null +++ b/echo-rest/src/test/groovy/com/netflix/spinnaker/echo/events/RestClientFactorySpec.groovy @@ -0,0 +1,43 @@ +package com.netflix.spinnaker.echo.events + +import com.squareup.okhttp.OkHttpClient +import retrofit.client.Client +import spock.lang.Specification +import spock.lang.Subject + +class RestClientFactorySpec extends Specification{ + + @Subject + RestClientFactory clientFactory = new RestClientFactory() + + Boolean insecure + + OkHttpClientFactory httpClientFactory + + void setup() { + httpClientFactory = Mock(OkHttpClientFactory) + clientFactory.httpClientFactory = httpClientFactory + } + + void 'returns insecure client'() { + given: + insecure = true + + when: + clientFactory.getClient(insecure) + + then: + 1 * httpClientFactory.getInsecureClient() >> new OkHttpClient() + } + + void 'returns secure client'() { + given: + insecure = false + + when: + clientFactory.getClient(insecure) + + then: + 0 * httpClientFactory.getInsecureClient() >> new OkHttpClient() + } +}