Merge "Fix new sonar vulnerabilities"
[dmaap/datarouter.git] / datarouter-prov / src / main / java / org / onap / dmaap / datarouter / provisioning / InternalServlet.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.ByteArrayOutputStream;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.nio.file.FileStore;
32 import java.nio.file.FileSystem;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.nio.file.StandardCopyOption;
37 import java.util.Properties;
38
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
41
42 import org.json.JSONArray;
43 import org.onap.dmaap.datarouter.provisioning.beans.EventLogRecord;
44 import org.onap.dmaap.datarouter.provisioning.beans.LogRecord;
45 import org.onap.dmaap.datarouter.provisioning.beans.Parameters;
46 import org.onap.dmaap.datarouter.provisioning.eelf.EelfMsgs;
47 import org.onap.dmaap.datarouter.provisioning.utils.DB;
48 import org.onap.dmaap.datarouter.provisioning.utils.LogfileLoader;
49 import org.onap.dmaap.datarouter.provisioning.utils.RLEBitSet;
50
51 import com.att.eelf.configuration.EELFLogger;
52 import com.att.eelf.configuration.EELFManager;
53
54 /**
55  * <p>
56  * This servlet handles requests to URLs under /internal on the provisioning server. These include:
57  * </p>
58  * <div class="contentContainer">
59  * <table class="packageSummary" border="0" cellpadding="3" cellspacing="0">
60  * <caption><span>URL Path Summary</span><span class="tabEnd">&nbsp;</span></caption>
61  * <tr>
62  * <th class="colFirst" width="15%">URL Path</th>
63  * <th class="colOne">Method</th>
64  * <th class="colLast">Purpose</th>
65  * </tr>
66  * <tr class="altColor">
67  * <td class="colFirst">/internal/prov</td>
68  * <td class="colOne">GET</td>
69  * <td class="colLast">used to GET a full JSON copy of the provisioning data.</td>
70  * </tr>
71  * <tr class="rowColor">
72  * <td class="colFirst">/internal/fetchProv</td>
73  * <td class="colOne">GET</td>
74  * <td class="colLast">used to signal to a standby POD that the provisioning data should be fetched from the active
75  * POD.</td>
76  * </tr>
77  * <tr class="altColor">
78  * <td class="colFirst" rowspan="2">/internal/logs</td>
79  * <td class="colOne">GET</td>
80  * <td class="colLast">used to GET an index of log files and individual logs for this provisioning server.</td>
81  * </tr>
82  * <tr class="altColor">
83  * <td class="colOne">POST</td>
84  * <td class="colLast">used to POST log files from the individual nodes to this provisioning server.</td>
85  * </tr>
86  * <tr class="rowColor">
87  * <td class="colFirst" rowspan="4">/internal/api</td>
88  * <td class="colOne">GET</td>
89  * <td class="colLast">used to GET an individual parameter value. The parameter name is specified by the path after
90  * /api/.</td>
91  * </tr>
92  * <tr class="rowColor">
93  * <td class="colOne">PUT</td>
94  * <td class="colLast">used to set an individual parameter value. The parameter name is specified by the path after
95  * /api/.</td>
96  * </tr>
97  * <tr class="rowColor">
98  * <td class="colOne">DELETE</td>
99  * <td class="colLast">used to remove an individual parameter value. The parameter name is specified by the path after
100  * /api/.</td>
101  * </tr>
102  * <tr class="rowColor">
103  * <td class="colOne">POST</td>
104  * <td class="colLast">used to create a new individual parameter value. The parameter name is specified by the path
105  * after /api/.</td>
106  * </tr>
107  * <tr class="altColor">
108  * <td class="colFirst">/internal/halt</td>
109  * <td class="colOne">GET</td>
110  * <td class="colLast">used to halt the server (must be accessed from 127.0.0.1).</td>
111  * </tr>
112  * <tr class="rowColor">
113  * <td class="colFirst" rowspan="2">/internal/drlogs</td>
114  * <td class="colOne">GET</td>
115  * <td class="colLast">used to get a list of DR log entries available for retrieval.
116  * Note: these are the actual data router log entries sent to the provisioning server by the nodes, not the provisioning
117  * server's internal logs (access via /internal/logs above). The range is returned as a list of record sequence
118  * numbers.</td>
119  * </tr>
120  * <tr class="rowColor">
121  * <td class="colOne">POST</td>
122  * <td class="colLast">used to retrieve specific log entries.
123  * The sequence numbers of the records to fetch are POST-ed; the records matching the sequence numbers are
124  * returned.</td>
125  * </tr>
126  * <tr class="altColor">
127  * <td class="colFirst">/internal/route/*</td>
128  * <td class="colOne">*</td>
129  * <td class="colLast">URLs under this path are handled via the {@link org.onap.dmaap.datarouter.provisioning.RouteServlet}</td>
130  * </tr>
131  * </table>
132  * </div>
133  * <p>
134  * Authorization to use these URLs is a little different than for other URLs on the provisioning server. For the most
135  * part, the IP address that the request comes from should be either:
136  * </p>
137  * <ol>
138  * <li>an IP address of a provisioning server, or</li>
139  * <li>the IP address of a node (to allow access to /internal/prov), or</li>
140  * <li>an IP address from the "<i>special subnet</i>" which is configured with
141  * the PROV_SPECIAL_SUBNET parameter.
142  * </ol>
143  * <p>
144  * In addition, requests to /internal/halt can ONLY come from localhost (127.0.0.1) on the HTTP port.
145  * </p>
146  * <p>
147  * All DELETE/GET/PUT/POST requests made to /internal/api on this servlet on the standby server are proxied to the
148  * active server (using the {@link ProxyServlet}) if it is up and reachable.
149  * </p>
150  *
151  * @author Robert Eby
152  * @version $Id: InternalServlet.java,v 1.23 2014/03/24 18:47:10 eby Exp $
153  */
154 @SuppressWarnings("serial")
155 public class InternalServlet extends ProxyServlet {
156
157     private static Integer logseq = 0; // another piece of info to make log spool file names unique
158     //Adding EELF Logger Rally:US664892
159     private static EELFLogger eelflogger = EELFManager.getInstance()
160         .getLogger("org.onap.dmaap.datarouter.provisioning.InternalServlet");
161
162     /**
163      * Delete a parameter at the address /internal/api/&lt;parameter&gt;. See the <b>Internal API</b> document for
164      * details on how this method should be invoked.
165      */
166     @Override
167     public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
168         setIpAndFqdnForEelf("doDelete");
169         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");
170         EventLogRecord elr = new EventLogRecord(req);
171         if (!isAuthorizedForInternal(req)) {
172             elr.setMessage("Unauthorized.");
173             elr.setResult(HttpServletResponse.SC_FORBIDDEN);
174             eventlogger.info(elr);
175             resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");
176             return;
177         }
178
179         String path = req.getPathInfo();
180         if (path.startsWith("/api/")) {
181             if (isProxyOK(req) && isProxyServer()) {
182                 super.doDelete(req, resp);
183                 return;
184             }
185             String key = path.substring(5);
186             if (key.length() > 0) {
187                 Parameters param = Parameters.getParameter(key);
188                 if (param != null) {
189                     if (doDelete(param)) {
190                         elr.setResult(HttpServletResponse.SC_OK);
191                         eventlogger.info(elr);
192                         resp.setStatus(HttpServletResponse.SC_OK);
193                         provisioningDataChanged();
194                         provisioningParametersChanged();
195                     } else {
196                         // Something went wrong with the DELETE
197                         elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
198                         eventlogger.info(elr);
199                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);
200                     }
201                     return;
202                 }
203             }
204         }
205         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");
206     }
207
208     /**
209      * Get some information (such as a parameter) underneath the /internal/ namespace. See the <b>Internal API</b>
210      * document for details on how this method should be invoked.
211      */
212     @Override
213     public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
214         setIpAndFqdnForEelf("doGet");
215         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");
216         String path = req.getPathInfo();
217         if (path.equals("/halt") && !req.isSecure()) {
218             // request to halt the server - can ONLY come from localhost
219             String remote = req.getRemoteAddr();
220             if (remote.equals("127.0.0.1")) {
221                 intlogger.info("PROV0009 Request to HALT received.");
222                 resp.setStatus(HttpServletResponse.SC_OK);
223                 Main.shutdown();
224             } else {
225                 intlogger.info("PROV0010 Disallowed request to HALT received from " + remote);
226                 resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
227             }
228             return;
229         }
230
231         EventLogRecord elr = new EventLogRecord(req);
232         if (!isAuthorizedForInternal(req)) {
233             elr.setMessage("Unauthorized.");
234             elr.setResult(HttpServletResponse.SC_FORBIDDEN);
235             eventlogger.info(elr);
236             resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");
237             return;
238         }
239         if (path.equals("/fetchProv") && !req.isSecure()) {
240             // if request came from active_pod or standby_pod and it is not us, reload prov data
241             SynchronizerTask s = SynchronizerTask.getSynchronizer();
242             s.doFetch();
243             resp.setStatus(HttpServletResponse.SC_OK);
244             return;
245         }
246         if (path.equals("/prov")) {
247             if (isProxyOK(req) && isProxyServer()) {
248                 try {
249                     if (super.doGetWithFallback(req, resp)) {
250                         return;
251                     }
252                 } catch (IOException ioe) {
253                     intlogger.error("Error: " + ioe.getMessage());
254                 }
255                 // fall back to returning the local data if the remote is unreachable
256                 intlogger.info("Active server unavailable; falling back to local copy.");
257             }
258             Poker p = Poker.getPoker();
259             resp.setStatus(HttpServletResponse.SC_OK);
260             resp.setContentType(PROVFULL_CONTENT_TYPE2);
261             resp.getOutputStream().print(p.getProvisioningString());
262             return;
263         }
264         if (path.equals("/logs") || path.equals("/logs/")) {
265             resp.setStatus(HttpServletResponse.SC_OK);
266             resp.setContentType("application/json");
267             resp.getOutputStream().print(generateLogfileList().toString());
268             return;
269         }
270         if (path.startsWith("/logs/")) {
271             Properties p = (new DB()).getProperties();
272             String logdir = p.getProperty("org.onap.dmaap.datarouter.provserver.accesslog.dir");
273             String logfile = path.substring(6);
274             if (logdir != null && logfile != null && logfile.indexOf('/') < 0) {
275                 File log = new File(logdir + "/" + logfile);
276                 if (log.exists() && log.isFile()) {
277                     resp.setStatus(HttpServletResponse.SC_OK);
278                     resp.setContentType("text/plain");
279                     Path logpath = Paths.get(log.getAbsolutePath());
280                     Files.copy(logpath, resp.getOutputStream());
281                     return;
282                 }
283             }
284             resp.sendError(HttpServletResponse.SC_NO_CONTENT, "No file.");
285             return;
286         }
287         if (path.startsWith("/api/")) {
288             if (isProxyOK(req) && isProxyServer()) {
289                 super.doGet(req, resp);
290                 return;
291             }
292             String key = path.substring(5);
293             if (key.length() > 0) {
294                 Parameters param = Parameters.getParameter(key);
295                 if (param != null) {
296                     resp.setStatus(HttpServletResponse.SC_OK);
297                     resp.setContentType("text/plain");
298                     resp.getOutputStream().print(param.getValue() + "\n");
299                     return;
300                 }
301             }
302         }
303         if (path.equals("/drlogs") || path.equals("/drlogs/")) {
304             // Special POD <=> POD API to determine what log file records are loaded here
305             LogfileLoader lfl = LogfileLoader.getLoader();
306             resp.setStatus(HttpServletResponse.SC_OK);
307             resp.setContentType("text/plain");
308             resp.getOutputStream().print(lfl.getBitSet().toString());
309             return;
310         }
311         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");
312     }
313
314     /**
315      * Modify a parameter at the address /internal/api/&lt;parameter&gt;. See the <b>Internal API</b> document for
316      * details on how this method should be invoked.
317      */
318     @Override
319     public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
320         setIpAndFqdnForEelf("doPut");
321         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF_AND_FEEDID, req.getHeader(BEHALF_HEADER), getIdFromPath(req) + "");
322         EventLogRecord elr = new EventLogRecord(req);
323         if (!isAuthorizedForInternal(req)) {
324             elr.setMessage("Unauthorized.");
325             elr.setResult(HttpServletResponse.SC_FORBIDDEN);
326             eventlogger.info(elr);
327             resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");
328             return;
329         }
330         String path = req.getPathInfo();
331         if (path.startsWith("/api/")) {
332             if (isProxyOK(req) && isProxyServer()) {
333                 super.doPut(req, resp);
334                 return;
335             }
336             String key = path.substring(5);
337             if (key.length() > 0) {
338                 Parameters param = Parameters.getParameter(key);
339                 if (param != null) {
340                     String t = catValues(req.getParameterValues("val"));
341                     param.setValue(t);
342                     if (doUpdate(param)) {
343                         elr.setResult(HttpServletResponse.SC_OK);
344                         eventlogger.info(elr);
345                         resp.setStatus(HttpServletResponse.SC_OK);
346                         provisioningDataChanged();
347                         provisioningParametersChanged();
348                     } else {
349                         // Something went wrong with the UPDATE
350                         elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
351                         eventlogger.info(elr);
352                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);
353                     }
354                     return;
355                 }
356             }
357         }
358         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");
359     }
360
361     /**
362      * Create some new information (such as a parameter or log entries) underneath the /internal/ namespace. See the
363      * <b>Internal API</b> document for details on how this method should be invoked.
364      */
365     @SuppressWarnings("resource")
366     @Override
367     public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
368         setIpAndFqdnForEelf("doPost");
369         eelflogger.info(EelfMsgs.MESSAGE_WITH_BEHALF, req.getHeader(BEHALF_HEADER));
370         EventLogRecord elr = new EventLogRecord(req);
371         if (!isAuthorizedForInternal(req)) {
372             elr.setMessage("Unauthorized.");
373             elr.setResult(HttpServletResponse.SC_FORBIDDEN);
374             eventlogger.info(elr);
375             resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Unauthorized.");
376             return;
377         }
378
379         String path = req.getPathInfo();
380         if (path.startsWith("/api/")) {
381             if (isProxyOK(req) && isProxyServer()) {
382                 super.doPost(req, resp);
383                 return;
384             }
385             String key = path.substring(5);
386             if (key.length() > 0) {
387                 Parameters param = Parameters.getParameter(key);
388                 if (param == null) {
389                     String t = catValues(req.getParameterValues("val"));
390                     param = new Parameters(key, t);
391                     if (doInsert(param)) {
392                         elr.setResult(HttpServletResponse.SC_OK);
393                         eventlogger.info(elr);
394                         resp.setStatus(HttpServletResponse.SC_OK);
395                         provisioningDataChanged();
396                         provisioningParametersChanged();
397                     } else {
398                         // Something went wrong with the INSERT
399                         elr.setResult(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
400                         eventlogger.info(elr);
401                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, DB_PROBLEM_MSG);
402                     }
403                     return;
404                 }
405             }
406         }
407
408         if (path.equals("/logs") || path.equals("/logs/")) {
409             String ctype = req.getHeader("Content-Type");
410             if (ctype == null || !ctype.equals("text/plain")) {
411                 elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
412                 elr.setMessage("Bad media type: " + ctype);
413                 resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
414                 eventlogger.info(elr);
415                 return;
416             }
417             String spooldir = (new DB()).getProperties().getProperty("org.onap.dmaap.datarouter.provserver.spooldir");
418             String spoolname = String.format("%d-%d-", System.currentTimeMillis(), Thread.currentThread().getId());
419             synchronized (logseq) {
420                 // perhaps unnecessary, but it helps make the name unique
421                 spoolname += logseq.toString();
422                 logseq++;
423             }
424             String encoding = req.getHeader("Content-Encoding");
425             if (encoding != null) {
426                 if (encoding.trim().equals("gzip")) {
427                     spoolname += ".gz";
428                 } else {
429                     elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
430                     resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
431                     eventlogger.info(elr);
432                     return;
433                 }
434             }
435             // Determine space available -- available space must be at least 5%
436             FileSystem fs = (Paths.get(spooldir)).getFileSystem();
437             long total = 0;
438             long avail = 0;
439             for (FileStore store : fs.getFileStores()) {
440                 total += store.getTotalSpace();
441                 avail += store.getUsableSpace();
442             }
443             try {
444                 fs.close();
445             } catch (Exception e) {
446             }
447             if (((avail * 100) / total) < 5) {
448                 elr.setResult(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
449                 resp.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
450                 eventlogger.info(elr);
451                 return;
452             }
453             Path tmppath = Paths.get(spooldir, spoolname);
454             Path donepath = Paths.get(spooldir, "IN." + spoolname);
455             Files.copy(req.getInputStream(), Paths.get(spooldir, spoolname), StandardCopyOption.REPLACE_EXISTING);
456             Files.move(tmppath, donepath, StandardCopyOption.REPLACE_EXISTING);
457             elr.setResult(HttpServletResponse.SC_CREATED);
458             resp.setStatus(HttpServletResponse.SC_CREATED);
459             eventlogger.info(elr);
460             LogfileLoader.getLoader();    // This starts the logfile loader "task"
461             return;
462         }
463
464         if (path.equals("/drlogs") || path.equals("/drlogs/")) {
465             // Receive post request and generate log entries
466             String ctype = req.getHeader("Content-Type");
467             if (ctype == null || !ctype.equals("text/plain")) {
468                 elr.setResult(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
469                 elr.setMessage("Bad media type: " + ctype);
470                 resp.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
471                 eventlogger.info(elr);
472                 return;
473             }
474             InputStream is = req.getInputStream();
475             ByteArrayOutputStream bos = new ByteArrayOutputStream();
476             int ch;
477             try {
478                 while ((ch = is.read()) >= 0) {
479                     bos.write(ch);
480                 }
481             } catch (IOException ioe) {
482                 intlogger.error("Error: " + ioe.getMessage());
483             }
484             RLEBitSet bs = new RLEBitSet(bos.toString());    // The set of records to retrieve
485             elr.setResult(HttpServletResponse.SC_OK);
486             resp.setStatus(HttpServletResponse.SC_OK);
487             resp.setContentType("text/plain");
488             LogRecord.printLogRecords(resp.getOutputStream(), bs);
489             eventlogger.info(elr);
490             return;
491         }
492
493         elr.setResult(HttpServletResponse.SC_NOT_FOUND);
494         resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Bad URL.");
495         eventlogger.info(elr);
496     }
497
498     private String catValues(String[] v) {
499         StringBuilder sb = new StringBuilder();
500         if (v != null) {
501             String pfx = "";
502             for (String s : v) {
503                 sb.append(pfx);
504                 sb.append(s);
505                 pfx = "|";
506             }
507         }
508         return sb.toString();
509     }
510
511     private JSONArray generateLogfileList() {
512         JSONArray ja = new JSONArray();
513         Properties p = (new DB()).getProperties();
514         String s = p.getProperty("org.onap.dmaap.datarouter.provserver.accesslog.dir");
515         if (s != null) {
516             String[] dirs = s.split(",");
517             for (String dir : dirs) {
518                 File f = new File(dir);
519                 String[] list = f.list();
520                 if (list != null) {
521                     for (String s2 : list) {
522                         if (!s2.startsWith(".")) {
523                             ja.put(s2);
524                         }
525                     }
526                 }
527             }
528         }
529         return ja;
530     }
531 }