Fix AAI connection issues
[dcaegen2/analytics/tca-gen2.git] / dcae-analytics / dcae-analytics-web / src / main / java / org / onap / dcae / analytics / web / http / HttpClientPreferencesCustomizer.java
1 /*
2  * ================================================================================
3  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
4  * ================================================================================
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  * ============LICENSE_END=========================================================
17  *
18  */
19
20 package org.onap.dcae.analytics.web.http;
21
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.net.URL;
26 import java.security.KeyManagementException;
27 import java.security.KeyStoreException;
28 import java.security.NoSuchAlgorithmException;
29 import java.security.cert.CertificateException;
30 import java.security.cert.X509Certificate;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Optional;
34
35 import org.apache.http.HttpHost;
36 import org.apache.http.auth.AuthScope;
37 import org.apache.http.auth.Credentials;
38 import org.apache.http.auth.UsernamePasswordCredentials;
39 import org.apache.http.client.CredentialsProvider;
40 import org.apache.http.conn.ssl.NoopHostnameVerifier;
41 import org.apache.http.conn.ssl.TrustStrategy;
42 import org.apache.http.impl.client.BasicCredentialsProvider;
43 import org.apache.http.impl.client.HttpClientBuilder;
44 import org.apache.http.impl.client.HttpClients;
45 import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
46 import org.apache.http.ssl.SSLContextBuilder;
47 import org.onap.dcae.analytics.model.AnalyticsHttpConstants;
48 import org.onap.dcae.analytics.model.util.function.StringToURLFunction;
49 import org.onap.dcae.analytics.web.util.AnalyticsWebUtils;
50 import org.onap.dcaegen2.services.sdk.security.ssl.Password;
51 import org.onap.dcaegen2.services.sdk.security.ssl.Passwords;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.springframework.boot.web.client.RestTemplateCustomizer;
55 import org.springframework.http.HttpHeaders;
56 import org.springframework.http.HttpRequest;
57 import org.springframework.http.client.BufferingClientHttpRequestFactory;
58 import org.springframework.http.client.ClientHttpRequestExecution;
59 import org.springframework.http.client.ClientHttpRequestInterceptor;
60 import org.springframework.http.client.ClientHttpResponse;
61 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
62 import org.springframework.http.client.support.BasicAuthorizationInterceptor;
63 import org.springframework.util.ReflectionUtils;
64 import org.springframework.util.StringUtils;
65 import org.springframework.web.client.RestTemplate;
66 import org.springframework.web.util.DefaultUriBuilderFactory;
67
68 /**
69  * Creates a {@link RestTemplateCustomizer} which can be used to configure the spring rest templates
70  * based on given {@link HttpClientPreferences}
71  *
72  * @param <T> Http Client Configurations
73  *
74  * @author Rajiv Singla
75  */
76 public class HttpClientPreferencesCustomizer<T extends HttpClientPreferences> implements RestTemplateCustomizer {
77
78     private static final Logger logger = LoggerFactory.getLogger(HttpClientPreferencesCustomizer.class);
79
80     private final T httpClientConfig;
81
82     public HttpClientPreferencesCustomizer(final T httpClientConfig) {
83         this.httpClientConfig = httpClientConfig;
84     }
85
86     @Override
87     public void customize(final RestTemplate restTemplate) {
88
89         final String httpClientId = httpClientConfig.getHttpClientId() != null ? httpClientConfig.getHttpClientId()
90                 : AnalyticsHttpConstants.DEFAULT_HTTP_CLIENT_ID_PREFIX + AnalyticsWebUtils.RANDOM_ID_SUPPLIER.get();
91         logger.debug("Customizing Rest Template for Http Client Id: {}", httpClientId);
92
93         // set request url
94         final URL requestURL = new StringToURLFunction().apply(httpClientConfig.getRequestURL())
95                 .orElseThrow(() -> new IllegalArgumentException("Http Client URL is required"));
96         restTemplate.setUriTemplateHandler(new DefaultUriBuilderFactory(requestURL.toString()));
97
98         // add basic authentication headers
99         final String username = httpClientConfig.getUsername();
100         if (username != null) {
101             restTemplate.getInterceptors().add(
102                     new BasicAuthorizationInterceptor(username, httpClientConfig.getPassword()));
103         }
104
105         // set default request headers
106         final HttpHeaders defaultRequestHeaders = httpClientConfig.getRequestHeaders();
107         if (defaultRequestHeaders != null) {
108             restTemplate.getInterceptors().add(new DefaultHeadersRequestInterceptor(defaultRequestHeaders));
109         }
110
111         // create new http client builder
112         final HttpClientBuilder httpClientBuilder = HttpClients.custom().useSystemProperties()
113                 .disableContentCompression();
114         final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
115
116         // set basic authentication credentials
117         configureAuthenticationCredentials(httpClientId, requestURL, credentialsProvider);
118         // set up proxy url
119         configureProxySettings(httpClientId, httpClientBuilder, credentialsProvider);
120         // setup credentials provider
121         httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
122         // set up ssl Context
123         configureSSLContext(httpClientId, httpClientBuilder);
124
125         // set rest client builder
126         final HttpComponentsClientHttpRequestFactory httpRequestFactory =
127                 new HttpComponentsClientHttpRequestFactory(httpClientBuilder.build());
128
129         // set ecomp logging interceptor
130         if (httpClientConfig.getEnableEcompAuditLogging()) {
131             restTemplate.getInterceptors().add(new EelfAuditLogInterceptor(httpClientConfig));
132         }
133
134         restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(httpRequestFactory));
135     }
136
137     /**
138      * Configures authentication credentials
139      *
140      * @param httpClientId http client id
141      * @param requestURL request url
142      * @param credentialsProvider credentials provider
143      */
144     private void configureAuthenticationCredentials(final String httpClientId, final URL requestURL,
145                                                     final CredentialsProvider credentialsProvider) {
146         final String username = httpClientConfig.getUsername();
147         if (username != null) {
148             logger.info("Setting basic Authentication credentials for Http Client Id: {} with username: {}",
149                     httpClientId, username);
150             final String requestURLProtocol = requestURL.getProtocol();
151             final String requestUrlHost = requestURL.getHost();
152             final Integer requestUrlPortNumber = requestURL.getPort();
153             final HttpHost requestURLHost = new HttpHost(requestUrlHost, requestUrlPortNumber, requestURLProtocol);
154             final String password = httpClientConfig.getPassword();
155             final AuthScope httpClientAuthScope = new AuthScope(requestURLHost);
156             final Credentials credentials = new UsernamePasswordCredentials(username, password);
157             credentialsProvider.setCredentials(httpClientAuthScope, credentials);
158         } else {
159             logger.warn("No credentials set for Http Client Id: {}. No username present", httpClientId);
160         }
161     }
162
163     /**
164      * Configures proxy host, port and authentication
165      *
166      * @param httpClientId http client id
167      * @param httpClientBuilder http client builder
168      * @param credentialsProvider http credentials provider
169      */
170     private void configureProxySettings(final String httpClientId, final HttpClientBuilder httpClientBuilder,
171                                         final CredentialsProvider credentialsProvider) {
172
173         final URL proxyURL = httpClientConfig.getProxyURL();
174
175         if (proxyURL == null) {
176             logger.debug("Proxy not Enabled - bypassing setting Proxy settings for Http Client Id: {}", httpClientId);
177             return;
178         }
179
180         final String proxyProtocol = proxyURL.getProtocol();
181         final String proxyHost = proxyURL.getHost();
182         final Integer proxyPort = proxyURL.getPort();
183         final HttpHost proxy = new HttpHost(proxyHost, proxyPort, proxyProtocol);
184
185         logger.info("Setting up proxy for Http Client Id: {} as: {}", httpClientId, proxy);
186
187         final DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
188         httpClientBuilder.setRoutePlanner(routePlanner);
189
190         // get proxy credentials information
191         final String userInfo = proxyURL.getUserInfo();
192
193         if (!StringUtils.hasText(userInfo)) {
194             logger.debug("Proxy username not present. " +
195                     "No proxy authentication credentials will be set for Http Client Id: {}", httpClientId);
196             return;
197         }
198
199         final String[] userInfoArray = userInfo.split(":");
200         final String proxyUsername = userInfoArray[0];
201         String proxyPassword = null;
202         if (userInfoArray.length > 1) {
203             proxyPassword = userInfoArray[1];
204         }
205         logger.info("Setting proxy credentials with username: {} for Http Client Id: {}", proxyUsername, httpClientId);
206         final AuthScope proxyAuthScope = new AuthScope(proxyHost, proxyPort);
207         final Credentials proxyCredentials = new UsernamePasswordCredentials(proxyUsername, proxyPassword);
208         credentialsProvider.setCredentials(proxyAuthScope, proxyCredentials);
209     }
210
211     /**
212      * Configures SSL Context
213      *
214      * @param httpClientId http client id
215      * @param httpClientBuilder http client builder
216      */
217     private void configureSSLContext(final String httpClientId, final HttpClientBuilder httpClientBuilder) {
218
219         // Setup SSL Context to ignore SSL certificate issues if ignoreSSLCertificateErrors is true
220         final boolean ignoreSSLValidation =
221                 Optional.ofNullable(httpClientConfig.getIgnoreSSLValidation()).orElse(false);
222         logger.info("Ignore SSL Certificate Errors attributed is set to: {} for Http Client Id: {}",
223                 ignoreSSLValidation, httpClientId);
224
225         if (!ignoreSSLValidation) {
226             logger.info("SSL Validation will be enforced for Http Client Id: {}", httpClientId);
227             setSslContextFromEnvironment(httpClientBuilder);
228             return;
229         }
230
231         logger.warn("SSL Certificate Errors will be ignored for Http Client Id: {}", httpClientId);
232         try {
233             SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
234             sslContextBuilder.loadTrustMaterial(null, new AlwaysTrustingTrustStrategy());
235             httpClientBuilder.setSSLContext(sslContextBuilder.build());
236         } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
237             ReflectionUtils.rethrowRuntimeException(e);
238         }
239         httpClientBuilder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
240
241     }
242
243     private void setSslContextFromEnvironment(HttpClientBuilder httpClientBuilder) {
244         final String caCertPath = System.getenv("DCAE_CA_CERTPATH");
245         if (!StringUtils.hasText(caCertPath)) {
246             return;
247         }
248         final SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
249         final String truststoreFilename = "trust.jks";
250         final String truststorePassFilename = "trust.pass";
251         final String certDirPath = caCertPath.substring(0, caCertPath.lastIndexOf("/"));
252         final File truststoreFile = new File(certDirPath, truststoreFilename);
253         final File truststorePassFile = new File(certDirPath, truststorePassFilename);
254         final Password password = Passwords.fromFile(truststorePassFile);
255         password.use(chars -> {
256             try {
257                 sslContextBuilder.loadTrustMaterial(truststoreFile, chars);
258                 httpClientBuilder.setSSLContext(sslContextBuilder.build());
259             } catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException |
260                 KeyManagementException e) {
261                 logger.warn("Could not load trusted certificates from environment");
262             }
263             return null;
264         });
265     }
266
267
268     /**
269      * Header Request Interceptor adds defaults headers if not set explicitly
270      */
271     private static class DefaultHeadersRequestInterceptor implements ClientHttpRequestInterceptor {
272         private final HttpHeaders httpHeaders;
273
274         DefaultHeadersRequestInterceptor(final HttpHeaders httpHeaders) {
275             this.httpHeaders = httpHeaders;
276         }
277
278         @Override
279         public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
280                                             final ClientHttpRequestExecution execution) throws IOException {
281             final HttpHeaders currentRequestHeaders = request.getHeaders();
282             for (Map.Entry<String, List<String>> defaultHttpHeader : httpHeaders.entrySet()) {
283                 if (!currentRequestHeaders.containsKey(defaultHttpHeader.getKey())) {
284                     currentRequestHeaders.addAll(defaultHttpHeader.getKey(), defaultHttpHeader.getValue());
285                 }
286             }
287             currentRequestHeaders.setAccept(httpHeaders.getAccept());
288             currentRequestHeaders.setAcceptCharset(httpHeaders.getAcceptCharset());
289             currentRequestHeaders.remove(HttpHeaders.ACCEPT_ENCODING);
290             return execution.execute(request, body);
291         }
292     }
293
294     /**
295      * An implementation of SSL Trust Strategy which does no SSL certificate validation effectively
296      * bypassing any SSL certificate related issues
297      */
298     private static class AlwaysTrustingTrustStrategy implements TrustStrategy {
299         @Override
300         public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
301             return true;
302         }
303     }
304
305 }