e0cd2c9c42918f7de5d210632ec5ca72b57b8a6e
[policy/drools-applications.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * guard
4  * ================================================================================
5  * Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2019 Samsung Electronics Co., Ltd.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.policy.guard;
23
24 import com.att.research.xacml.api.Attribute;
25 import com.att.research.xacml.api.AttributeCategory;
26 import com.att.research.xacml.api.AttributeValue;
27 import com.att.research.xacml.api.Result;
28 import com.att.research.xacml.api.pdp.PDPEngine;
29 import com.att.research.xacml.std.annotations.RequestParser;
30 import com.att.research.xacmlatt.pdp.ATTPDPEngineFactory;
31
32 import java.io.BufferedReader;
33 import java.io.ByteArrayInputStream;
34 import java.io.FileInputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.InputStreamReader;
38 import java.io.OutputStream;
39 import java.io.Serializable;
40 import java.net.HttpURLConnection;
41 import java.net.URL;
42 import java.util.ArrayList;
43 import java.util.Base64;
44 import java.util.Iterator;
45 import java.util.Properties;
46 import java.util.UUID;
47
48 import org.apache.commons.io.IOUtils;
49 import org.apache.http.entity.ContentType;
50 import org.json.JSONObject;
51 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
52 import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
53 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
54 import org.onap.policy.database.operationshistory.Dbao;
55 import org.onap.policy.drools.system.PolicyEngine;
56 import org.onap.policy.guard.PolicyGuardResponse;
57 import org.onap.policy.guard.PolicyGuardXacmlRequestAttributes;
58 import org.onap.policy.guard.Util;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62
63 public class PolicyGuardXacmlHelperEmbedded {
64     private static final Logger logger = LoggerFactory.getLogger(PolicyGuardXacmlHelperEmbedded.class);
65
66     private String propfile;
67     private UrlEntry[] restUrls = null;
68     private int restUrlIndex = 0;
69
70     // REST timeout, initialized from 'pdpx.timeout' property
71     private int timeout = 20000;
72
73     public PolicyGuardXacmlHelperEmbedded() {
74         init(PolicyEngine.manager.getEnvironment());
75     }
76
77     // initialized from 'pdpx.url' property --
78     // Each entry in 'restUrls' contains a destination URL, and an optional
79     // 'Authorization' header entry. 'restUrlIndex' indicates the next
80     // entry to try -- after each failure, the index is advanced to the
81     // next entry (wrapping to the beginning, if needed).
82     private static class UrlEntry implements Serializable {
83         private static final long serialVersionUID = -8859237552195400518L;
84
85         URL restUrl;
86         String authorization = null;
87         String clientAuth = null;
88         String environment = null;
89     }
90
91     /**
92      * Call PDP.
93      *
94      * @param xacmlReq the XACML request
95      * @return the response
96      */
97     public String callPdp(PolicyGuardXacmlRequestAttributes xacmlReq) {
98         //
99         // Send it to the PDP
100         //
101         String response = null;
102
103         if ( propfile != null ) {
104             logger.debug("callEmbeddedPdp");
105             return callEmbeddedPdp(xacmlReq);
106         }
107         //
108         // Build the json request
109         //
110         JSONObject attributes = new JSONObject();
111         attributes.put("actor", xacmlReq.getActorId());
112         attributes.put("recipe", xacmlReq.getOperationId());
113         attributes.put("target", xacmlReq.getTargetId());
114         if (xacmlReq.getClnameId() != null) {
115             attributes.put("clname", xacmlReq.getClnameId());
116         }
117         if (xacmlReq.getVfCount() != null) {
118             attributes.put("vfCount", xacmlReq.getVfCount());
119         }
120         JSONObject jsonReq = new JSONObject();
121         jsonReq.put("decisionAttributes", attributes);
122         jsonReq.put("onapName", "PDPD");
123
124
125         try {
126             //
127             // Call RESTful PDP
128             //
129             UrlEntry urlEntry = restUrls[restUrlIndex];
130             String jsonRequestString = jsonReq.toString();
131             NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, urlEntry.restUrl.toString(), jsonRequestString);
132             response = callRestfulPdp(new ByteArrayInputStream(jsonReq.toString().getBytes()), urlEntry.restUrl,
133                     urlEntry.authorization, urlEntry.clientAuth, urlEntry.environment);
134             NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, urlEntry.restUrl.toString(), response);
135         } catch (Exception e) {
136             logger.error("Error in sending RESTful request", e);
137         }
138
139         return response;
140     }
141
142     /**
143      * This makes an HTTP POST call to a running PDP RESTful servlet to get a decision.
144      *
145      * @param is the InputStream
146      * @param authorization the Authorization
147      * @param clientauth the ClientAuth
148      * @param environment the Environment
149      * @return response from guard which contains "Permit" or "Deny"
150      */
151     private String callRestfulPdp(InputStream is, URL restUrl, String authorization, String clientauth,
152             String environment) {
153         HttpURLConnection connection = null;
154
155         try {
156             //
157             // Open up the connection
158             //
159             connection = (HttpURLConnection) restUrl.openConnection();
160             connection.setRequestProperty("Content-Type", "application/json");
161             //
162             // Setup our method and headers
163             //
164             connection.setRequestProperty("Accept", "application/json");
165             if (authorization != null) {
166                 connection.setRequestProperty("Authorization", authorization);
167             }
168             if (clientauth != null) {
169                 connection.setRequestProperty("ClientAuth", clientauth);
170             }
171             if (environment != null) {
172                 connection.setRequestProperty("Environment", environment);
173             }
174             connection.setConnectTimeout(timeout);
175             connection.setReadTimeout(timeout);
176             connection.setRequestMethod("POST");
177             connection.setUseCaches(false);
178             //
179             // Adding this in. It seems the HttpUrlConnection class does NOT
180             // properly forward our headers for POST re-direction. It does so
181             // for a GET re-direction.
182             //
183             // So we need to handle this ourselves.
184             //
185             connection.setInstanceFollowRedirects(false);
186             connection.setDoOutput(true);
187             connection.setDoInput(true);
188             //
189             // Send the request
190             //
191             try (OutputStream os = connection.getOutputStream()) {
192                 IOUtils.copy(is, os);
193             }
194
195             //
196             // Do the connect
197             //
198             connection.connect();
199
200             if (connection.getResponseCode() != 200) {
201                 logger.error("{} {}", connection.getResponseCode(), connection.getResponseMessage());
202                 return Util.INDETERMINATE;
203             }
204         } catch (Exception e) {
205             logger.error("Exception in 'PolicyGuardEmbeddedHelper.callRESTfulPDP'", e);
206             return Util.INDETERMINATE;
207         }
208
209         //
210         // Read the response
211         //
212         try {
213             ContentType contentType = ContentType.parse(connection.getContentType());
214
215             if (contentType.getMimeType().equalsIgnoreCase(ContentType.APPLICATION_JSON.getMimeType())) {
216                 InputStream inputStream = connection.getInputStream();
217                 int contentLength = connection.getContentLength();
218
219                 return readResponseFromStream(inputStream, contentLength);
220             } else {
221                 logger.error("unknown content-type: {}", contentType);
222                 return Util.INDETERMINATE;
223             }
224
225         } catch (Exception e) {
226             String message = "Parsing Content-Type: " + connection.getContentType();
227             logger.error(message, e);
228             return Util.INDETERMINATE;
229         }
230     }
231
232     /**
233      * Call embedded PDP.
234      *
235      * @param xacmlReq the XACML request
236      * @return the response
237      */
238     public String callEmbeddedPdp(PolicyGuardXacmlRequestAttributes xacmlReq) {
239         com.att.research.xacml.api.Response response = null;
240         Properties props = new Properties();
241         //
242         // Get properties
243         //
244         try ( InputStream is = new FileInputStream(propfile);
245               InputStreamReader isr = new InputStreamReader(is);
246               BufferedReader br = new BufferedReader(isr) ) {
247             props.load(br);
248         } catch (Exception e) {
249             logger.error("Unable to load properties file {}", propfile, e);
250         }
251         //
252         // Create embedded PDPEngine
253         //
254         PDPEngine xacmlPdpEngine;
255         try {
256             xacmlPdpEngine = ATTPDPEngineFactory.newInstance().newEngine(props);
257         } catch (Exception e) {
258             logger.error("callEmbeddedPdpEngine failed to create new PDPEngine", e);
259             return null;
260         }
261         logger.debug("embedded Engine created");
262         //
263         // Embedded call to PDP
264         //
265         long timeStart = System.currentTimeMillis();
266         if (xacmlReq.getVfCount() == null ) {
267             xacmlReq.setVfCount(1);
268         }
269         try {
270             response = xacmlPdpEngine.decide(RequestParser.parseRequest(xacmlReq));
271         } catch (Exception e) {
272             logger.error("callEmbeddedPdpEngine failed on decide", e);
273         }
274         long timeEnd = System.currentTimeMillis();
275         logger.debug("Elapsed Time: {} ms", (timeEnd - timeStart));
276         //
277         // Convert response to string
278         //
279         logger.debug("converting response to string");
280         PolicyGuardResponse pgr = parseXacmlPdpResponse(response);
281         logger.debug("parsed XacmlPdpResponse {}", pgr);
282         String decision = pgr.getResult();
283         logger.debug("decision={}",decision);
284         return decision;
285     }
286
287     /**
288      * Parse XACML PDP response.
289      *
290      * @param xacmlResponse the XACML response
291      * @return the PolicyGuardResponse
292      */
293     public static PolicyGuardResponse parseXacmlPdpResponse(com.att.research.xacml.api.Response xacmlResponse) {
294         if (xacmlResponse == null) {
295             //
296             // In case the actual XACML response was null, create an empty
297             // response object with decision "Indeterminate"
298             //
299             return new PolicyGuardResponse("Indeterminate", null, "");
300         }
301
302         Iterator<Result> itRes = xacmlResponse.getResults().iterator();
303
304         Result res = itRes.next();
305         String decisionFromXacmlResponse = res.getDecision().toString();
306         Iterator<AttributeCategory> itAttrCat = res.getAttributes().iterator();
307         UUID reqIdFromXacmlResponse = null;
308         String operationFromXacmlResponse = "";
309
310         while (itAttrCat.hasNext()) {
311             Iterator<Attribute> itAttr = itAttrCat.next().getAttributes().iterator();
312             while (itAttr.hasNext()) {
313                 Attribute currentAttr = itAttr.next();
314                 String attributeId = currentAttr.getAttributeId().stringValue();
315                 if ("urn:org:onap:guard:request:request-id".equals(attributeId)) {
316                     Iterator<AttributeValue<?>> itValues = currentAttr.getValues().iterator();
317                     reqIdFromXacmlResponse = UUID.fromString(itValues.next().getValue().toString());
318                 }
319                 if ("urn:org:onap:guard:operation:operation-id".equals(attributeId)) {
320                     Iterator<AttributeValue<?>> itValues = currentAttr.getValues().iterator();
321                     operationFromXacmlResponse = itValues.next().getValue().toString();
322                 }
323             }
324         }
325
326         return new PolicyGuardResponse(decisionFromXacmlResponse, reqIdFromXacmlResponse, operationFromXacmlResponse);
327
328     }
329
330     private void init(Properties properties) {
331         propfile = properties.getProperty("prop.guard.propfile");
332
333         // used to store error messages
334         StringBuilder sb = new StringBuilder();
335
336         // fetch these parameters, if they exist
337         String timeoutString = properties.getProperty("pdpx.timeout");
338         String disabledString = properties.getProperty("guard.disabled");
339
340         if (disabledString != null && Boolean.parseBoolean(disabledString)) {
341             return;
342         }
343
344         ArrayList<UrlEntry> entries = initEntries(properties, sb);
345
346         if (entries.isEmpty()) {
347             sb.append("'pdpx.*' -- no URLs specified, ");
348         } else {
349             restUrls = entries.toArray(new UrlEntry[0]);
350         }
351
352         if (timeoutString != null) {
353             try {
354                 // decode optional 'pdpx.timeout' parameter
355                 timeout = Integer.valueOf(timeoutString);
356             } catch (NumberFormatException e) {
357                 sb.append("'pdpx.timeout': " + e + ", ");
358                 logger.trace(e.getLocalizedMessage());
359             }
360         }
361
362
363         // if there are any errors, update 'errorMessage' & disable guard
364         // queries
365         if (sb.length() != 0) {
366             // remove the terminating ", ", and extract resulting error message
367             sb.setLength(sb.length() - 2);
368             String errorMessage = sb.toString();
369             logger.error("Initialization failure: {}", errorMessage);
370         }
371     }
372
373     private ArrayList<UrlEntry> initEntries(Properties properties, StringBuilder sb) {
374         // now, see which numeric entries (1-9) exist
375         ArrayList<UrlEntry> entries = new ArrayList<>();
376
377         for (int index = 0; index < 10; index += 1) {
378             String urlPrefix = "guard.";
379             if (index != 0) {
380                 urlPrefix = urlPrefix + index + ".";
381             }
382
383             // see if the associated URL exists
384             String restUrllist = properties.getProperty(urlPrefix + "url");
385             if (nullOrEmpty(restUrllist)) {
386                 // no entry for this index
387                 continue;
388             }
389
390             // support a list of entries separated by semicolons. Each entry
391             // can be:
392             // URL
393             // URL,user
394             // URL,user,password
395             for (String restUrl : restUrllist.split("\\s*;\\s*")) {
396                 UrlEntry entry = initRestUrl(properties, sb, restUrl);
397                 // include this URLEntry in the list
398                 if (entry != null) {
399                     entries.add(entry);
400                 }
401             }
402         }
403
404         return entries;
405     }
406
407     private UrlEntry initRestUrl(Properties properties, StringBuilder sb, String restUrl) {
408         String urlPrefix = "guard.";
409         String pdpxPrefix = "pdpx.";
410
411         String[] segments = restUrl.split("\\s*,\\s*");
412         String user = null;
413         String password = null;
414
415         if (segments.length >= 2) {
416             // user id is provided
417             restUrl = segments[0];
418             user = segments[1];
419             if (segments.length >= 3) {
420                 // password is also provided
421                 password = segments[2];
422             }
423         }
424
425         // URL does exist -- create the entry
426         UrlEntry urlEntry = new UrlEntry();
427         try {
428             urlEntry.restUrl = new URL(restUrl);
429         } catch (java.net.MalformedURLException e) {
430             // if we don't have a URL,
431             // don't bother with the rest on this one
432             sb.append("'").append(urlPrefix).append("url' '").append(restUrl).append("': ").append(e).append(",");
433             return null;
434         }
435
436         if (nullOrEmpty(user)) {
437             // user id was not provided on '*.url' line --
438             // extract it from a separate property
439             user = properties.getProperty(pdpxPrefix + "username", properties.getProperty("pdpx.username"));
440         }
441         if (nullOrEmpty(password)) {
442             // password was not provided on '*.url' line --
443             // extract it from a separate property
444             password = properties.getProperty(pdpxPrefix + "password", properties.getProperty("pdpx.password"));
445         }
446
447         // see if 'user' and 'password' entries both exist
448         if (!nullOrEmpty(user) && !nullOrEmpty(password)) {
449             urlEntry.authorization = "Basic " + Base64.getEncoder().encodeToString((user + ":" + password).getBytes());
450         }
451
452         // see if 'client.user' and 'client.password' entries both exist
453         String clientUser =
454                 properties.getProperty(pdpxPrefix + "client.username", properties.getProperty("pdpx.client.username"));
455         String clientPassword =
456                 properties.getProperty(pdpxPrefix + "client.password", properties.getProperty("pdpx.client.password"));
457         if (!nullOrEmpty(clientUser) && !nullOrEmpty(clientPassword)) {
458             urlEntry.clientAuth =
459                     "Basic " + Base64.getEncoder().encodeToString((clientUser + ":" + clientPassword).getBytes());
460         }
461
462         // see if there is an 'environment' entry
463         String environment =
464                 properties.getProperty(pdpxPrefix + "environment", properties.getProperty("pdpx.environment"));
465         if (!nullOrEmpty(environment)) {
466             urlEntry.environment = environment;
467         }
468
469         return urlEntry;
470     }
471
472     /**
473      * Check if a string is null or an empty string.
474      *
475      * @param value the string to be tested
476      * @return 'true' if the string is 'null' or has a length of 0, 'false' otherwise
477      */
478     private static boolean nullOrEmpty(String value) {
479         return (value == null || value.isEmpty());
480     }
481
482     private static String readResponseFromStream(InputStream inputStream, int contentLength) throws IOException {
483         // if content length is -1, response is chunked, and
484         // TCP connection will be dropped at the end
485         byte[] buf = new byte[contentLength < 0 ? 1024 : contentLength];
486
487         int offset = 0;
488         do {
489             int size = inputStream.read(buf, offset, buf.length - offset);
490             if (size < 0) {
491                 // In a chunked response a dropped connection is expected, but not if the response
492                 // is not chunked
493                 if (contentLength > 0) {
494                     logger.error("partial input stream");
495                 }
496                 break;
497             }
498             offset += size;
499         }
500         while (offset != contentLength);
501
502         String response = new String(buf, 0, offset);
503
504         //
505         // Connection may have failed or not been 200 OK, return Indeterminate
506         //
507         if (response.isEmpty()) {
508             return Util.INDETERMINATE;
509         }
510
511         return new JSONObject(response).getString("decision");
512
513     }
514 }