7542360236764ec8fdcc2f592315dbf73f8fa137
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / ProxyServlet.java
1 /*******************************************************************************
2  * ============LICENSE_START==================================================
3  * * org.onap.dmaap
4  * * ===========================================================================
5  * * Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6  * * ===========================================================================
7  * * Licensed under the Apache License, Version 2.0 (the "License");
8  * * you may not use this file except in compliance with the License.
9  * * You may obtain a copy of the License at
10  * *
11  *  *      http://www.apache.org/licenses/LICENSE-2.0
12  * *
13  *  * Unless required by applicable law or agreed to in writing, software
14  * * distributed under the License is distributed on an "AS IS" BASIS,
15  * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * * See the License for the specific language governing permissions and
17  * * limitations under the License.
18  * * ============LICENSE_END====================================================
19  * *
20  * * ECOMP is a trademark and service mark of AT&T Intellectual Property.
21  * *
22  ******************************************************************************/
23
24
25 package org.onap.dmaap.datarouter.provisioning;
26
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.net.URI;
33 import java.security.KeyStore;
34 import java.security.KeyStoreException;
35 import java.util.Collections;
36 import java.util.List;
37 import java.util.Properties;
38 import javax.servlet.ServletConfig;
39 import javax.servlet.ServletException;
40 import javax.servlet.http.HttpServletRequest;
41 import javax.servlet.http.HttpServletResponse;
42 import org.apache.commons.io.IOUtils;
43 import org.apache.http.Header;
44 import org.apache.http.HttpEntity;
45 import org.apache.http.HttpResponse;
46 import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
47 import org.apache.http.client.methods.HttpGet;
48 import org.apache.http.client.methods.HttpRequestBase;
49 import org.apache.http.conn.scheme.Scheme;
50 import org.apache.http.conn.ssl.SSLSocketFactory;
51 import org.apache.http.entity.BasicHttpEntity;
52 import org.apache.http.impl.client.AbstractHttpClient;
53 import org.apache.http.impl.client.DefaultHttpClient;
54 import org.onap.dmaap.datarouter.provisioning.utils.DB;
55 import org.onap.dmaap.datarouter.provisioning.utils.URLUtilities;
56
57 import static org.onap.dmaap.datarouter.provisioning.utils.HttpServletUtils.sendResponseError;
58
59 /**
60  * This class is the base class for those servlets that need to proxy their requests from the standby to active server.
61  * Its methods perform the proxy function to the active server. If the active server is not reachable, a 503
62  * (SC_SERVICE_UNAVAILABLE) is returned.  Only DELETE/GET/PUT/POST are supported.
63  *
64  * @author Robert Eby
65  * @version $Id: ProxyServlet.java,v 1.3 2014/03/24 18:47:10 eby Exp $
66  */
67 @SuppressWarnings("serial")
68 public class ProxyServlet extends BaseServlet {
69
70     private boolean inited = false;
71     private Scheme sch;
72
73     /**
74      * Initialize this servlet, by setting up SSL.
75      */
76     @SuppressWarnings("deprecation")
77     @Override
78     public void init(ServletConfig config) throws ServletException {
79         super.init(config);
80         try {
81             // Set up keystore
82             Properties props = (new DB()).getProperties();
83             String type = props.getProperty(Main.KEYSTORE_TYPE_PROPERTY, "jks");
84             String store = props.getProperty(Main.KEYSTORE_PATH_PROPERTY);
85             String pass = props.getProperty(Main.KEYSTORE_PASS_PROPERTY);
86             KeyStore keyStore = readStore(store, pass, type);
87
88             store = props.getProperty(Main.TRUSTSTORE_PATH_PROPERTY);
89             pass = props.getProperty(Main.TRUSTSTORE_PASS_PROPERTY);
90             if (store == null || store.length() == 0) {
91                 store = Main.DEFAULT_TRUSTSTORE;
92                 pass = "changeit";
93             }
94             KeyStore trustStore = readStore(store, pass, KeyStore.getDefaultType());
95
96             // We are connecting with the node name, but the certificate will have the CNAME
97             // So we need to accept a non-matching certificate name
98             SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore,
99                 props.getProperty(Main.KEYSTORE_PASS_PROPERTY), trustStore);
100             socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
101             sch = new Scheme("https", 443, socketFactory);
102             inited = true;
103         } catch (Exception e) {
104             intlogger.error("ProxyServlet.init: " + e.getMessage(), e);
105         }
106         intlogger.info("ProxyServlet: inited = " + inited);
107     }
108
109     private KeyStore readStore(String store, String pass, String type) throws KeyStoreException {
110         KeyStore ks = KeyStore.getInstance(type);
111         try (FileInputStream instream = new FileInputStream(new File(store))) {
112             ks.load(instream, pass.toCharArray());
113         } catch (FileNotFoundException fileNotFoundException) {
114             intlogger.error("ProxyServlet.readStore: " + fileNotFoundException.getMessage(), fileNotFoundException);
115         } catch (Exception x) {
116             intlogger.error("READING TRUSTSTORE: " + x);
117         }
118         return ks;
119     }
120
121     /**
122      * Return <i>true</i> if the requester has NOT set the <i>noproxy</i> CGI variable. If they have, this indicates
123      * they want to forcibly turn the proxy off.
124      *
125      * @param req the HTTP request
126      * @return true or false
127      */
128     protected boolean isProxyOK(final HttpServletRequest req) {
129         String t = req.getQueryString();
130         if (t != null) {
131             t = t.replaceAll("&amp;", "&");
132             for (String s : t.split("&")) {
133                 if ("noproxy".equals(s) || s.startsWith("noproxy=")) {
134                     return false;
135                 }
136             }
137         }
138         return true;
139     }
140
141     /**
142      * Is this the standby server?  If it is, the proxy functions can be used. If not, the proxy functions should not be
143      * called, and will send a response of 500 (Internal Server Error).
144      *
145      * @return true if this server is the standby (and hence a proxy server).
146      */
147     public boolean isProxyServer() {
148         SynchronizerTask st = SynchronizerTask.getSynchronizer();
149         return st.getPodState() == SynchronizerTask.STANDBY_POD;
150     }
151
152     /**
153      * Issue a proxy DELETE to the active provisioning server.
154      */
155     @Override
156     public void doDelete(HttpServletRequest req, HttpServletResponse resp) {
157         doProxy(req, resp, "DELETE");
158     }
159
160     /**
161      * Issue a proxy GET to the active provisioning server.
162      */
163     @Override
164     public void doGet(HttpServletRequest req, HttpServletResponse resp) {
165         doProxy(req, resp, "GET");
166     }
167
168     /**
169      * Issue a proxy PUT to the active provisioning server.
170      */
171     @Override
172     public void doPut(HttpServletRequest req, HttpServletResponse resp) {
173         doProxy(req, resp, "PUT");
174     }
175
176     /**
177      * Issue a proxy POST to the active provisioning server.
178      */
179     @Override
180     public void doPost(HttpServletRequest req, HttpServletResponse resp) {
181         doProxy(req, resp, "POST");
182     }
183
184     /**
185      * Issue a proxy GET to the active provisioning server.  Unlike doGet() above, this method will allow the caller to
186      * fall back to other code if the remote server is unreachable.
187      *
188      * @return true if the proxy succeeded
189      */
190     public boolean doGetWithFallback(HttpServletRequest req, HttpServletResponse resp) {
191         boolean rv = false;
192         if (inited) {
193             String url = buildUrl(req);
194             intlogger.info("ProxyServlet: proxying with fallback GET " + url);
195             try (AbstractHttpClient httpclient = new DefaultHttpClient()) {
196                 HttpRequestBase proxy = new HttpGet(url);
197                 try {
198                     httpclient.getConnectionManager().getSchemeRegistry().register(sch);
199
200                     // Copy request headers and request body
201                     copyRequestHeaders(req, proxy);
202
203                     // Execute the request
204                     HttpResponse pxyResponse = httpclient.execute(proxy);
205
206                     // Get response headers and body
207                     int code = pxyResponse.getStatusLine().getStatusCode();
208                     resp.setStatus(code);
209                     copyResponseHeaders(pxyResponse, resp);
210                     copyEntityContent(pxyResponse, resp);
211                     rv = true;
212
213                 } catch (IOException e) {
214                     intlogger.error("ProxyServlet.doGetWithFallback: " + e.getMessage(), e);
215                 } finally {
216                     proxy.releaseConnection();
217                     httpclient.getConnectionManager().shutdown();
218                 }
219             }
220         } else {
221             intlogger.warn("ProxyServlet: proxy disabled");
222         }
223         return rv;
224     }
225
226     private void doProxy(HttpServletRequest req, HttpServletResponse resp, final String method) {
227         if (inited && isProxyServer()) {
228             String url = buildUrl(req);
229             intlogger.info("ProxyServlet: proxying " + method + " " + url);
230             try (AbstractHttpClient httpclient = new DefaultHttpClient()) {
231                 ProxyHttpRequest proxy = new ProxyHttpRequest(method, url);
232                 try {
233                     httpclient.getConnectionManager().getSchemeRegistry().register(sch);
234
235                     // Copy request headers and request body
236                     copyRequestHeaders(req, proxy);
237                     if ("POST".equals(method) || "PUT".equals(method)) {
238                         BasicHttpEntity body = new BasicHttpEntity();
239                         body.setContent(req.getInputStream());
240                         body.setContentLength(-1);    // -1 = unknown
241                         proxy.setEntity(body);
242                     }
243
244                     // Execute the request
245                     HttpResponse pxyResponse = httpclient.execute(proxy);
246
247                     // Get response headers and body
248                     int code = pxyResponse.getStatusLine().getStatusCode();
249                     resp.setStatus(code);
250                     copyResponseHeaders(pxyResponse, resp);
251                     copyEntityContent(pxyResponse, resp);
252                 } catch (IOException e) {
253                     intlogger.warn("ProxyServlet.doProxy: " + e.getMessage(), e);
254                     sendResponseError(resp, HttpServletResponse.SC_SERVICE_UNAVAILABLE, "", intlogger);
255                 } finally {
256                     proxy.releaseConnection();
257                     httpclient.getConnectionManager().shutdown();
258                 }
259             }
260         } else {
261             intlogger.warn("ProxyServlet: proxy disabled");
262             sendResponseError(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG, intlogger);
263         }
264     }
265
266     private String buildUrl(HttpServletRequest req) {
267         StringBuilder sb = new StringBuilder("https://");
268         sb.append(URLUtilities.getPeerPodName());
269         sb.append(req.getRequestURI());
270         String q = req.getQueryString();
271         if (q != null) {
272             sb.append("?").append(q);
273         }
274         return sb.toString();
275     }
276
277     private void copyRequestHeaders(HttpServletRequest from, HttpRequestBase to) {
278         @SuppressWarnings("unchecked")
279         List<String> list = Collections.list(from.getHeaderNames());
280         for (String name : list) {
281             // Proxy code will add this one
282             if (!"Content-Length".equalsIgnoreCase(name)) {
283                 to.addHeader(name, from.getHeader(name));
284             }
285         }
286     }
287
288     private void copyResponseHeaders(HttpResponse from, HttpServletResponse to) {
289         for (Header hdr : from.getAllHeaders()) {
290             // Don't copy Date: our Jetty will add another Date header
291             if (!"Date".equals(hdr.getName())) {
292                 to.addHeader(hdr.getName(), hdr.getValue());
293             }
294         }
295     }
296
297     private void copyEntityContent(HttpResponse pxyResponse, HttpServletResponse resp) {
298         HttpEntity entity = pxyResponse.getEntity();
299         if (entity != null) {
300             try (InputStream in = entity.getContent()) {
301                 IOUtils.copy(in, resp.getOutputStream());
302             } catch (Exception e) {
303                 intlogger.error("ProxyServlet.copyEntityContent: " + e.getMessage(), e);
304             }
305         }
306     }
307
308     public class ProxyHttpRequest extends HttpEntityEnclosingRequestBase {
309
310         private final String method;
311
312         public ProxyHttpRequest(final String method, final String uri) {
313             super();
314             this.method = method;
315             setURI(URI.create(uri));
316         }
317
318         @Override
319         public String getMethod() {
320             return method;
321         }
322     }
323 }