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