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