Started with test decision JSON objects.
[policy/xacml-pdp.git] / applications / monitoring / src / main / java / org / onap / policy / xacml / pdp / application / monitoring / MonitoringPdpApplication.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019 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  *
19  * SPDX-License-Identifier: Apache-2.0
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.xacml.pdp.application.monitoring;
24
25 import com.att.research.xacml.api.AttributeAssignment;
26 import com.att.research.xacml.api.DataTypeException;
27 import com.att.research.xacml.api.Decision;
28 import com.att.research.xacml.api.Obligation;
29 import com.att.research.xacml.api.Request;
30 import com.att.research.xacml.api.Response;
31 import com.att.research.xacml.api.Result;
32 import com.att.research.xacml.api.XACML3;
33 import com.att.research.xacml.api.pdp.PDPEngine;
34 import com.att.research.xacml.api.pdp.PDPException;
35 import com.att.research.xacml.std.annotations.RequestParser;
36 import com.att.research.xacml.util.XACMLPolicyScanner;
37 import com.att.research.xacml.util.XACMLPolicyWriter;
38 import com.att.research.xacml.util.XACMLProperties;
39 import com.google.common.collect.Lists;
40 import com.google.gson.Gson;
41
42 import java.io.ByteArrayOutputStream;
43 import java.io.FileInputStream;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.nio.file.Path;
47 import java.nio.file.Paths;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Map.Entry;
54 import java.util.Properties;
55 import java.util.Set;
56
57 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
58 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeAssignmentExpressionType;
59 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
60 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
61 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
62 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
63 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionType;
64 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionsType;
65 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySetType;
66 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
67 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
68 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
69
70 import org.json.JSONObject;
71 import org.onap.policy.models.decisions.concepts.DecisionRequest;
72 import org.onap.policy.models.decisions.concepts.DecisionResponse;
73 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
74 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
75 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConverter;
76 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConverterUtils;
77 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationServiceProvider;
78 import org.onap.policy.pdp.xacml.application.common.XacmlPolicyUtils;
79 import org.slf4j.Logger;
80 import org.slf4j.LoggerFactory;
81 import org.yaml.snakeyaml.Yaml;
82
83 /**
84  * This is the engine class that manages the instance of the XACML PDP engine.
85  *
86  * <p>It is responsible for initializing it and shutting it down properly in a thread-safe manner.
87  *
88  *
89  * @author pameladragosh
90  *
91  */
92 public class MonitoringPdpApplication implements ToscaPolicyConverter, XacmlApplicationServiceProvider {
93
94     private static final Logger LOGGER = LoggerFactory.getLogger(MonitoringPdpApplication.class);
95     private static final String ONAP_MONITORING_BASE_POLICY_TYPE = "onap.Monitoring";
96     private static final String ONAP_MONITORING_DERIVED_POLICY_TYPE = "onap.policies.monitoring";
97
98     private Path pathForData = null;
99     private Properties pdpProperties = null;
100     private PDPEngine pdpEngine = null;
101     private Map<String, String> supportedPolicyTypes = new HashMap<>();
102
103     /**
104      * Constructor.
105      */
106     public MonitoringPdpApplication() {
107         //
108         // By default this supports just Monitoring policy types
109         //
110         supportedPolicyTypes.put(ONAP_MONITORING_BASE_POLICY_TYPE, "1.0.0");
111     }
112
113     @Override
114     public String applicationName() {
115         return "Monitoring Application";
116     }
117
118     @Override
119     public List<String> actionDecisionsSupported() {
120         return Arrays.asList("configure");
121     }
122
123     @Override
124     public synchronized void initialize(Path pathForData) {
125         //
126         // Save our path
127         //
128         this.pathForData = pathForData;
129         LOGGER.debug("New Path is {}", this.pathForData.toAbsolutePath());
130         //
131         // Look for and load the properties object
132         //
133         try {
134             pdpProperties = XacmlPolicyUtils.loadXacmlProperties(XacmlPolicyUtils.getPropertiesPath(pathForData));
135             LOGGER.debug("{}", pdpProperties);
136         } catch (IOException e) {
137             LOGGER.error("{}", e);
138         }
139         //
140         // Create an engine
141         //
142         PDPEngine newEngine = XacmlPolicyUtils.createEngine(pdpProperties);
143         if (newEngine != null) {
144             pdpEngine = newEngine;
145         }
146     }
147
148     @Override
149     public synchronized List<String> supportedPolicyTypes() {
150         return Lists.newArrayList(supportedPolicyTypes.keySet());
151     }
152
153     @Override
154     public boolean canSupportPolicyType(String policyType, String policyTypeVersion) {
155         //
156         // For Monitoring, we will attempt to support all versions
157         // of the policy type. Since we are only packaging a decision
158         // back with a JSON payload of the property contents.
159         //
160         return (policyType.equals(ONAP_MONITORING_BASE_POLICY_TYPE)
161                 || policyType.startsWith(ONAP_MONITORING_DERIVED_POLICY_TYPE));
162     }
163
164     @Override
165     public synchronized void loadPolicies(Map<String, Object> toscaPolicies) {
166         try {
167             //
168             // Convert the policies first
169             //
170             List<PolicyType> listPolicies = this.convertPolicies(toscaPolicies);
171             if (listPolicies.isEmpty()) {
172                 throw new ToscaPolicyConversionException("Converted 0 policies");
173             }
174             //
175             // Read in our Root Policy
176             //
177             Set<String> roots = XACMLProperties.getRootPolicyIDs(pdpProperties);
178             if (roots.isEmpty()) {
179                 throw new ToscaPolicyConversionException("There are NO root policies defined");
180             }
181             //
182             // Really only should be one
183             //
184             String rootFile = pdpProperties.getProperty(roots.iterator().next() + ".file");
185             try (InputStream is = new FileInputStream(rootFile)) {
186                 //
187                 // Read the Root Policy into memory
188                 //
189                 Object policyData = XACMLPolicyScanner.readPolicy(is);
190                 //
191                 // Should be a PolicySet
192                 //
193                 if (policyData instanceof PolicySetType) {
194                     //
195                     // Add the referenced policies into a new Root Policy
196                     //
197                     PolicyType[] newPolicies = listPolicies.toArray(new PolicyType[listPolicies.size()]);
198                     PolicySetType newRootPolicy = XacmlPolicyUtils.addPoliciesToXacmlRootPolicy(
199                             (PolicySetType) policyData, newPolicies);
200                     LOGGER.debug("New ROOT Policy");
201                     try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
202                         XACMLPolicyWriter.writePolicyFile(os, newRootPolicy);
203                         LOGGER.debug("{}", os);
204                     } catch (IOException e) {
205                         LOGGER.error("Failed to convert {}", e);
206                     }
207                     //
208                     // Save the new Policies to disk
209                     //
210                     for (PolicyType policy : newPolicies) {
211                         //
212                         // Construct the filename
213                         //
214                         Path refPath = XacmlPolicyUtils.constructUniquePolicyFilename(policy, pathForData);
215                         //
216                         // Write the policy to disk
217                         // Maybe check for an error
218                         //
219                         XACMLPolicyWriter.writePolicyFile(refPath, policy);
220                         //
221                         // Save it off
222                         //
223                         XacmlPolicyUtils.addReferencedPolicy(pdpProperties, refPath);
224                     }
225                     //
226                     // Save the root policy to disk
227                     //
228                     XACMLPolicyWriter.writePolicyFile(Paths.get(rootFile), newRootPolicy);
229                     //
230                     // Write the policies to disk
231                     //
232                     XacmlPolicyUtils.storeXacmlProperties(pdpProperties,
233                             XacmlPolicyUtils.getPropertiesPath(pathForData));
234                     //
235                     // Reload the engine
236                     //
237                     PDPEngine newEngine = XacmlPolicyUtils.createEngine(pdpProperties);
238                     if (newEngine != null) {
239                         pdpEngine = newEngine;
240                     }
241                 } else {
242                     throw new ToscaPolicyConversionException("Root policy isn't a PolicySet");
243                 }
244             }
245         } catch (IOException | ToscaPolicyConversionException e) {
246             LOGGER.error("Failed to loadPolicies {}", e);
247         }
248     }
249
250     @Override
251     public synchronized DecisionResponse makeDecision(DecisionRequest request) {
252         //
253         // Convert to a XacmlRequest
254         //
255         Request xacmlRequest = this.convertRequest(request);
256         //
257         // Now get a decision
258         //
259         Response xacmlResponse = this.xacmlDecision(xacmlRequest);
260         //
261         // Convert to a DecisionResponse
262         //
263         return this.convertResponse(xacmlResponse);
264     }
265
266     @Override
267     public List<PolicyType> convertPolicies(Map<String, Object> toscaObject) throws ToscaPolicyConversionException {
268         //
269         // Return the policies
270         //
271         return scanAndConvertPolicies(toscaObject);
272     }
273
274     @Override
275     public List<PolicyType> convertPolicies(InputStream isToscaPolicy) throws ToscaPolicyConversionException {
276         //
277         // Have snakeyaml parse the object
278         //
279         Yaml yaml = new Yaml();
280         Map<String, Object> toscaObject = yaml.load(isToscaPolicy);
281         //
282         // Return the policies
283         //
284         return scanAndConvertPolicies(toscaObject);
285     }
286
287     @SuppressWarnings("unchecked")
288     private List<PolicyType> scanAndConvertPolicies(Map<String, Object> toscaObject)
289             throws ToscaPolicyConversionException {
290         //
291         // Our return object
292         //
293         List<PolicyType> scannedPolicies = new ArrayList<>();
294         //
295         // Iterate each of the Policies
296         //
297         List<Object> policies = (List<Object>) toscaObject.get("policies");
298         for (Object policyObject : policies) {
299             //
300             // Get the contents
301             //
302             LOGGER.debug("Found policy {}", policyObject.getClass());
303             Map<String, Object> policyContents = (Map<String, Object>) policyObject;
304             for (Entry<String, Object> entrySet : policyContents.entrySet()) {
305                 LOGGER.debug("Entry set {}", entrySet);
306                 //
307                 // Convert this policy
308                 //
309                 PolicyType policy = this.convertPolicy(entrySet);
310                 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
311                     XACMLPolicyWriter.writePolicyFile(os, policy);
312                     LOGGER.debug("{}", os);
313                 } catch (IOException e) {
314                     LOGGER.error("Failed to convert {}", e);
315                 }
316                 //
317                 // Convert and add in the new policy
318                 //
319                 scannedPolicies.add(policy);
320             }
321         }
322
323         return scannedPolicies;
324     }
325
326     @SuppressWarnings("unchecked")
327     private PolicyType convertPolicy(Entry<String, Object> entrySet) throws ToscaPolicyConversionException {
328         //
329         // Policy name should be at the root
330         //
331         String policyName = entrySet.getKey();
332         Map<String, Object> policyDefinition = (Map<String, Object>) entrySet.getValue();
333         //
334         // Set it as the policy ID
335         //
336         PolicyType newPolicyType = new PolicyType();
337         newPolicyType.setPolicyId(policyName);
338         //
339         // Optional description
340         //
341         if (policyDefinition.containsKey("description")) {
342             newPolicyType.setDescription(policyDefinition.get("description").toString());
343         }
344         //
345         // There should be a metadata section
346         //
347         if (! policyDefinition.containsKey("metadata")) {
348             throw new ToscaPolicyConversionException(policyName + " missing metadata section");
349         }
350         this.fillMetadataSection(newPolicyType,
351                 (Map<String, Object>) policyDefinition.get("metadata"));
352         //
353         // Set the combining rule
354         //
355         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
356         //
357         // Generate the TargetType
358         //
359         //
360         // There should be a metadata section
361         //
362         if (! policyDefinition.containsKey("type")) {
363             throw new ToscaPolicyConversionException(policyName + " missing type value");
364         }
365         if (! policyDefinition.containsKey("version")) {
366             throw new ToscaPolicyConversionException(policyName + " missing version value");
367         }
368         TargetType target = this.generateTargetType(policyName,
369                 policyDefinition.get("type").toString(),
370                 policyDefinition.get("version").toString());
371         newPolicyType.setTarget(target);
372         //
373         // Now create the Permit Rule
374         // No target since the policy has a target
375         // With obligations.
376         //
377         RuleType rule = new RuleType();
378         rule.setDescription("Default is to PERMIT if the policy matches.");
379         rule.setRuleId(policyName + ":rule");
380         rule.setEffect(EffectType.PERMIT);
381         rule.setTarget(new TargetType());
382         //
383         // Now represent the policy as Json
384         //
385         JSONObject jsonObligation = new JSONObject();
386         jsonObligation.put(policyName, policyDefinition);
387         addObligation(rule, jsonObligation);
388         //
389         // Add the rule to the policy
390         //
391         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
392         //
393         // Return our new policy
394         //
395         return newPolicyType;
396     }
397
398     /**
399      * From the TOSCA metadata section, pull in values that are needed into the XACML policy.
400      *
401      * @param policy Policy Object to store the metadata
402      * @param metadata The Metadata TOSCA Map
403      * @return Same Policy Object
404      * @throws ToscaPolicyConversionException If there is something missing from the metadata
405      */
406     private PolicyType fillMetadataSection(PolicyType policy,
407             Map<String, Object> metadata) throws ToscaPolicyConversionException {
408         if (! metadata.containsKey("policy-id")) {
409             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-id");
410         } else {
411             //
412             // Do nothing here - the XACML PolicyId is used from TOSCA Policy Name field
413             //
414         }
415         if (! metadata.containsKey("policy-version")) {
416             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-version");
417         } else {
418             //
419             // Add in the Policy Version
420             //
421             policy.setVersion(metadata.get("policy-version").toString());
422         }
423         return policy;
424     }
425
426     private TargetType generateTargetType(String policyId, String policyType, String policyTypeVersion) {
427         //
428         // Create all the match's that are possible
429         //
430         // This is for the Policy Id
431         //
432         MatchType matchPolicyId = ToscaPolicyConverterUtils.buildMatchTypeDesignator(
433                 XACML3.ID_FUNCTION_STRING_EQUAL,
434                 policyId,
435                 XACML3.ID_DATATYPE_STRING,
436                 ToscaDictionary.ID_RESOURCE_POLICY_ID,
437                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
438         //
439         // This is for the Policy Type
440         //
441         MatchType matchPolicyType = ToscaPolicyConverterUtils.buildMatchTypeDesignator(
442                 XACML3.ID_FUNCTION_STRING_EQUAL,
443                 policyType,
444                 XACML3.ID_DATATYPE_STRING,
445                 ToscaDictionary.ID_RESOURCE_POLICY_TYPE,
446                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
447         //
448         // This is for the Policy Type version
449         //
450         MatchType matchPolicyTypeVersion = ToscaPolicyConverterUtils.buildMatchTypeDesignator(
451                 XACML3.ID_FUNCTION_STRING_EQUAL,
452                 policyTypeVersion,
453                 XACML3.ID_DATATYPE_STRING,
454                 ToscaDictionary.ID_RESOURCE_POLICY_TYPE_VERSION,
455                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
456         //
457         // This is our outer AnyOf - which is an OR
458         //
459         AnyOfType anyOf = new AnyOfType();
460         //
461         // Create AllOf (AND) of just Policy Id
462         //
463         anyOf.getAllOf().add(ToscaPolicyConverterUtils.buildAllOf(matchPolicyId));
464         //
465         // Create AllOf (AND) of just Policy Type
466         //
467         anyOf.getAllOf().add(ToscaPolicyConverterUtils.buildAllOf(matchPolicyType));
468         //
469         // Create AllOf (AND) of Policy Type and Policy Type Version
470         //
471         anyOf.getAllOf().add(ToscaPolicyConverterUtils.buildAllOf(matchPolicyType, matchPolicyTypeVersion));
472         //
473         // Now we can create the TargetType, add the top-level anyOf (OR),
474         // and return the value.
475         //
476         TargetType target = new TargetType();
477         target.getAnyOf().add(anyOf);
478         return target;
479     }
480
481     private RuleType addObligation(RuleType rule, JSONObject jsonPolicy) {
482         //
483         // Convert the YAML Policy to JSON Object
484         //
485         if (LOGGER.isDebugEnabled()) {
486             LOGGER.debug("JSON DCAE Policy {}{}", System.lineSeparator(), jsonPolicy);
487         }
488         //
489         // Create an AttributeValue for it
490         //
491         AttributeValueType value = new AttributeValueType();
492         value.setDataType(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_DATATYPE.stringValue());
493         value.getContent().add(jsonPolicy.toString());
494         //
495         // Create our AttributeAssignmentExpression where we will
496         // store the contents of the policy in JSON format.
497         //
498         AttributeAssignmentExpressionType expressionType = new AttributeAssignmentExpressionType();
499         expressionType.setAttributeId(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS.stringValue());
500         ObjectFactory factory = new ObjectFactory();
501         expressionType.setExpression(factory.createAttributeValue(value));
502         //
503         // Create an ObligationExpression for it
504         //
505         ObligationExpressionType obligation = new ObligationExpressionType();
506         obligation.setFulfillOn(EffectType.PERMIT);
507         obligation.setObligationId(ToscaDictionary.ID_OBLIGATION_REST_BODY.stringValue());
508         obligation.getAttributeAssignmentExpression().add(expressionType);
509         //
510         // Now we can add it into the rule
511         //
512         ObligationExpressionsType obligations = new ObligationExpressionsType();
513         obligations.getObligationExpression().add(obligation);
514         rule.setObligationExpressions(obligations);
515         return rule;
516     }
517
518     @Override
519     public Request convertRequest(DecisionRequest request) {
520         LOGGER.debug("Converting Request {}", request);
521         try {
522             return RequestParser.parseRequest(MonitoringRequest.createInstance(request));
523         } catch (IllegalArgumentException | IllegalAccessException | DataTypeException e) {
524             LOGGER.error("Failed to convert DecisionRequest: {}", e);
525         }
526         //
527         // TODO throw exception
528         //
529         return null;
530     }
531
532     @Override
533     public DecisionResponse convertResponse(Response xacmlResponse) {
534         LOGGER.debug("Converting Response {}", xacmlResponse);
535         DecisionResponse decisionResponse = new DecisionResponse();
536         //
537         // Iterate through all the results
538         //
539         for (Result xacmlResult : xacmlResponse.getResults()) {
540             //
541             // Check the result
542             //
543             if (xacmlResult.getDecision() == Decision.PERMIT) {
544                 //
545                 // Setup policies
546                 //
547                 decisionResponse.setPolicies(new ArrayList<>());
548                 //
549                 // Go through obligations
550                 //
551                 for (Obligation obligation : xacmlResult.getObligations()) {
552                     LOGGER.debug("Obligation: {}", obligation);
553                     for (AttributeAssignment assignment : obligation.getAttributeAssignments()) {
554                         LOGGER.debug("Attribute Assignment: {}", assignment);
555                         //
556                         // We care about the content attribute
557                         //
558                         if (ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS
559                                 .equals(assignment.getAttributeId())) {
560                             //
561                             // The contents are in Json form
562                             //
563                             Object stringContents = assignment.getAttributeValue().getValue();
564                             if (LOGGER.isDebugEnabled()) {
565                                 LOGGER.debug("DCAE contents: {}{}", System.lineSeparator(), stringContents);
566                             }
567                             //
568                             // Let's parse it into a map using Gson
569                             //
570                             Gson gson = new Gson();
571                             @SuppressWarnings("unchecked")
572                             Map<String, Object> result = gson.fromJson(stringContents.toString() ,Map.class);
573                             decisionResponse.getPolicies().add(result);
574                         }
575                     }
576                 }
577             } else {
578                 decisionResponse.setErrorMessage("A better error message");
579             }
580         }
581
582         return decisionResponse;
583     }
584
585     /**
586      * Make a decision call.
587      *
588      * @param request Incoming request object
589      * @return Response object
590      */
591     private synchronized Response xacmlDecision(Request request) {
592         //
593         // This is what we need to return
594         //
595         Response response = null;
596         //
597         // Track some timing
598         //
599         long timeStart = System.currentTimeMillis();
600         try {
601             response = this.pdpEngine.decide(request);
602         } catch (PDPException e) {
603             LOGGER.error("Xacml PDP Engine failed {}", e);
604         } finally {
605             //
606             // Track the end of timing
607             //
608             long timeEnd = System.currentTimeMillis();
609             LOGGER.info("Elapsed Time: {}ms", (timeEnd - timeStart));
610         }
611         return response;
612     }
613 }