preliminary AAF changes for DR
[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 /**
58  * This class is the base class for those servlets that need to proxy their requests from the standby to active server.
59  * Its methods perform the proxy function to the active server. If the active server is not reachable, a 503
60  * (SC_SERVICE_UNAVAILABLE) is returned.  Only DELETE/GET/PUT/POST are supported.
61  *
62  * @author Robert Eby
63  * @version $Id: ProxyServlet.java,v 1.3 2014/03/24 18:47:10 eby Exp $
64  */
65 @SuppressWarnings("serial")
66 public class ProxyServlet extends BaseServlet {
67
68     private boolean inited = false;
69     private Scheme sch;
70
71     /**
72      * Initialize this servlet, by setting up SSL.
73      */
74     @SuppressWarnings("deprecation")
75     @Override
76     public void init(ServletConfig config) throws ServletException {
77         super.init(config);
78         try {
79             // Set up keystore
80             Properties props = (new DB()).getProperties();
81             String type = props.getProperty(Main.KEYSTORE_TYPE_PROPERTY, "jks");
82             String store = props.getProperty(Main.KEYSTORE_PATH_PROPERTY);
83             String pass = props.getProperty(Main.KEYSTORE_PASSWORD_PROPERTY);
84             KeyStore keyStore = readStore(store, pass, type);
85
86             store = props.getProperty(Main.TRUSTSTORE_PATH_PROPERTY);
87             pass = props.getProperty(Main.TRUSTSTORE_PASSWORD_PROPERTY);
88             if (store == null || store.length() == 0) {
89                 store = Main.DEFAULT_TRUSTSTORE;
90                 pass = "changeit";
91             }
92             KeyStore trustStore = readStore(store, pass, KeyStore.getDefaultType());
93
94             // We are connecting with the node name, but the certificate will have the CNAME
95             // So we need to accept a non-matching certificate name
96             SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore,
97                 props.getProperty(Main.KEYSTORE_PASSWORD_PROPERTY), trustStore);
98             socketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
99             sch = new Scheme("https", 443, socketFactory);
100             inited = true;
101         } catch (Exception e) {
102             e.printStackTrace();
103         }
104         intlogger.info("ProxyServlet: inited = " + inited);
105     }
106
107     private KeyStore readStore(String store, String pass, String type) throws KeyStoreException {
108         KeyStore ks = KeyStore.getInstance(type);
109         try (FileInputStream instream = new FileInputStream(new File(store))) {
110             ks.load(instream, pass.toCharArray());
111         } catch (FileNotFoundException fileNotFoundException) {
112             System.err.println("ProxyServlet: " + fileNotFoundException);
113             fileNotFoundException.printStackTrace();
114         } catch (Exception x) {
115             System.err.println("READING TRUSTSTORE: " + x);
116         }
117         return ks;
118     }
119
120     /**
121      * Return <i>true</i> if the requester has NOT set the <i>noproxy</i> CGI variable. If they have, this indicates
122      * they want to forcibly turn the proxy off.
123      *
124      * @param req the HTTP request
125      * @return true or false
126      */
127     protected boolean isProxyOK(final HttpServletRequest req) {
128         String t = req.getQueryString();
129         if (t != null) {
130             t = t.replaceAll("&amp;", "&");
131             for (String s : t.split("&")) {
132                 if (s.equals("noproxy") || s.startsWith("noproxy=")) {
133                     return false;
134                 }
135             }
136         }
137         return true;
138     }
139
140     /**
141      * Is this the standby server?  If it is, the proxy functions can be used. If not, the proxy functions should not be
142      * called, and will send a response of 500 (Internal Server Error).
143      *
144      * @return true if this server is the standby (and hence a proxy server).
145      */
146     public boolean isProxyServer() {
147         SynchronizerTask st = SynchronizerTask.getSynchronizer();
148         return st.getState() == SynchronizerTask.STANDBY;
149     }
150
151     /**
152      * Issue a proxy DELETE to the active provisioning server.
153      */
154     @Override
155     public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
156         doProxy(req, resp, "DELETE");
157     }
158
159     /**
160      * Issue a proxy GET to the active provisioning server.
161      */
162     @Override
163     public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
164         doProxy(req, resp, "GET");
165     }
166
167     /**
168      * Issue a proxy PUT to the active provisioning server.
169      */
170     @Override
171     public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
172         doProxy(req, resp, "PUT");
173     }
174
175     /**
176      * Issue a proxy POST to the active provisioning server.
177      */
178     @Override
179     public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
180         doProxy(req, resp, "POST");
181     }
182
183     /**
184      * Issue a proxy GET to the active provisioning server.  Unlike doGet() above, this method will allow the caller to
185      * fall back to other code if the remote server is unreachable.
186      *
187      * @return true if the proxy succeeded
188      */
189     public boolean doGetWithFallback(HttpServletRequest req, HttpServletResponse resp) throws IOException {
190         boolean rv = false;
191         if (inited) {
192             String url = buildUrl(req);
193             intlogger.info("ProxyServlet: proxying with fallback GET " + url);
194             try (AbstractHttpClient httpclient = new DefaultHttpClient()) {
195                 HttpRequestBase proxy = new HttpGet(url);
196                 try {
197                     httpclient.getConnectionManager().getSchemeRegistry().register(sch);
198
199                     // Copy request headers and request body
200                     copyRequestHeaders(req, proxy);
201
202                     // Execute the request
203                     HttpResponse pxy_response = httpclient.execute(proxy);
204
205                     // Get response headers and body
206                     int code = pxy_response.getStatusLine().getStatusCode();
207                     resp.setStatus(code);
208                     copyResponseHeaders(pxy_response, resp);
209
210                     HttpEntity entity = pxy_response.getEntity();
211                     if (entity != null) {
212                         InputStream in = entity.getContent();
213                         IOUtils.copy(in, resp.getOutputStream());
214                         in.close();
215                     }
216                     rv = true;
217
218                 } catch (IOException e) {
219                     System.err.println("ProxyServlet: " + e);
220                     e.printStackTrace();
221                 } finally {
222                     proxy.releaseConnection();
223                     httpclient.getConnectionManager().shutdown();
224                 }
225             }
226         } else {
227             intlogger.warn("ProxyServlet: proxy disabled");
228         }
229         return rv;
230     }
231
232     private void doProxy(HttpServletRequest req, HttpServletResponse resp, final String method) throws IOException {
233         if (inited && isProxyServer()) {
234             String url = buildUrl(req);
235             intlogger.info("ProxyServlet: proxying " + method + " " + url);
236             try (AbstractHttpClient httpclient = new DefaultHttpClient()) {
237                 ProxyHttpRequest proxy = new ProxyHttpRequest(method, url);
238                 try {
239                     httpclient.getConnectionManager().getSchemeRegistry().register(sch);
240
241                     // Copy request headers and request body
242                     copyRequestHeaders(req, proxy);
243                     if (method.equals("POST") || method.equals("PUT")) {
244                         BasicHttpEntity body = new BasicHttpEntity();
245                         body.setContent(req.getInputStream());
246                         body.setContentLength(-1);    // -1 = unknown
247                         proxy.setEntity(body);
248                     }
249
250                     // Execute the request
251                     HttpResponse pxy_response = httpclient.execute(proxy);
252
253                     // Get response headers and body
254                     int code = pxy_response.getStatusLine().getStatusCode();
255                     resp.setStatus(code);
256                     copyResponseHeaders(pxy_response, resp);
257
258                     HttpEntity entity = pxy_response.getEntity();
259                     if (entity != null) {
260                         InputStream in = entity.getContent();
261                         IOUtils.copy(in, resp.getOutputStream());
262                         in.close();
263                     }
264                 } catch (IOException e) {
265                     intlogger.warn("ProxyServlet: " + e);
266                     resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
267                     e.printStackTrace();
268                 } finally {
269                     proxy.releaseConnection();
270                     httpclient.getConnectionManager().shutdown();
271                 }
272             }
273         } else {
274             intlogger.warn("ProxyServlet: proxy disabled");
275             resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
276         }
277     }
278
279     private String buildUrl(HttpServletRequest req) {
280         StringBuilder sb = new StringBuilder("https://");
281         sb.append(URLUtilities.getPeerPodName());
282         sb.append(req.getRequestURI());
283         String q = req.getQueryString();
284         if (q != null) {
285             sb.append("?").append(q);
286         }
287         return sb.toString();
288     }
289
290     private void copyRequestHeaders(HttpServletRequest from, HttpRequestBase to) {
291         @SuppressWarnings("unchecked")
292         List<String> list = Collections.list(from.getHeaderNames());
293         for (String name : list) {
294             // Proxy code will add this one
295             if (!name.equalsIgnoreCase("Content-Length")) {
296                 to.addHeader(name, from.getHeader(name));
297             }
298         }
299     }
300
301     private void copyResponseHeaders(HttpResponse from, HttpServletResponse to) {
302         for (Header hdr : from.getAllHeaders()) {
303             // Don't copy Date: our Jetty will add another Date header
304             if (!hdr.getName().equals("Date")) {
305                 to.addHeader(hdr.getName(), hdr.getValue());
306             }
307         }
308     }
309
310     public class ProxyHttpRequest extends HttpEntityEnclosingRequestBase {
311
312         private final String method;
313
314         public ProxyHttpRequest(final String method, final String uri) {
315             super();
316             this.method = method;
317             setURI(URI.create(uri));
318         }
319
320         @Override
321         public String getMethod() {
322             return method;
323         }
324     }
325 }