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