From 1f651c480d4dd3922e1c05461f7d901c47293c4a Mon Sep 17 00:00:00 2001 From: Temoc Rodriguez Date: Tue, 26 Sep 2017 16:03:34 -0700 Subject: Add missing auth headers to guard request Added guard authentication, client authentication, and environment http headers to the pdp-x guard restful request. Properties are set to PolicyEngine.manager. Property getter, setter, and properties in guard/.../Util.java. Test properties defined in junits themselves. Added code from GuardContext.java to grab the properties and make restful request. PolicyGuardXacmlHelper now closely resembles GuardContext in order to mimic functionality. Guard url no longer is passed into CallGuardTask, it is now a property. Issue-ID: POLICY-260 Change-Id: I5b144764828b6da0e7b738a578e4f6596a0f4f36 Signed-off-by: Temoc Rodriguez --- .../java/org/onap/policy/guard/CallGuardTask.java | 17 +- .../onap/policy/guard/PolicyGuardXacmlHelper.java | 446 ++++++++++++++++----- .../src/main/java/org/onap/policy/guard/Util.java | 40 ++ 3 files changed, 386 insertions(+), 117 deletions(-) (limited to 'controlloop/common/guard') diff --git a/controlloop/common/guard/src/main/java/org/onap/policy/guard/CallGuardTask.java b/controlloop/common/guard/src/main/java/org/onap/policy/guard/CallGuardTask.java index 8ea4ec1b3..dde18ed31 100644 --- a/controlloop/common/guard/src/main/java/org/onap/policy/guard/CallGuardTask.java +++ b/controlloop/common/guard/src/main/java/org/onap/policy/guard/CallGuardTask.java @@ -42,9 +42,10 @@ public class CallGuardTask implements Runnable { String target; String requestId; - public CallGuardTask(String guardUrl, WorkingMemory wm, String cl, String act, String rec, String tar, String reqId) { - - restfulPdpUrl = guardUrl; + /* + * Guard url is grabbed from PolicyEngine.manager properties + */ + public CallGuardTask(WorkingMemory wm, String cl, String act, String rec, String tar, String reqId) { workingMemory = wm; clname = cl; actor = act; @@ -69,15 +70,13 @@ public class CallGuardTask implements Runnable { logger.debug("{}", request); logger.debug("********** XACML REQUEST END ********\n"); - String guardUrl = PolicyEngine.manager.getEnvironmentProperty("guard.url"); + String guardUrl = PolicyEngine.manager.getEnvironmentProperty(Util.PROP_GUARD_URL); String guardDecision = null; // - // Check if guard url property exists + // Make guard request // - if(guardUrl != null){ - guardDecision = PolicyGuardXacmlHelper.callPDP(guardUrl, xacmlReq); - } + guardDecision = new PolicyGuardXacmlHelper().callPDP(xacmlReq); logger.debug("\n********** XACML RESPONSE START ********"); logger.debug("{}", guardDecision); @@ -88,7 +87,7 @@ public class CallGuardTask implements Runnable { // if(guardDecision == null){ logger.error("********** XACML FAILED TO CONNECT ********"); - guardDecision = "Indeterminate"; + guardDecision = Util.INDETERMINATE; } PolicyGuardResponse guardResponse = new PolicyGuardResponse(guardDecision, UUID.fromString(this.requestId), this.recipe); diff --git a/controlloop/common/guard/src/main/java/org/onap/policy/guard/PolicyGuardXacmlHelper.java b/controlloop/common/guard/src/main/java/org/onap/policy/guard/PolicyGuardXacmlHelper.java index 826f05652..f5a93d8be 100644 --- a/controlloop/common/guard/src/main/java/org/onap/policy/guard/PolicyGuardXacmlHelper.java +++ b/controlloop/common/guard/src/main/java/org/onap/policy/guard/PolicyGuardXacmlHelper.java @@ -25,14 +25,19 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.Serializable; import java.net.HttpURLConnection; import java.net.URL; +import java.util.ArrayList; +import java.util.Base64; import java.util.Iterator; +import java.util.Properties; import java.util.UUID; import org.apache.commons.io.IOUtils; import org.apache.http.entity.ContentType; import org.json.JSONObject; +import org.onap.policy.drools.system.PolicyEngine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,49 +48,86 @@ import com.att.research.xacml.api.Result; public class PolicyGuardXacmlHelper { - - private static final Logger logger = LoggerFactory.getLogger(PolicyGuardXacmlHelper.class); - public static String callPDP(String restfulPdpUrl, PolicyGuardXacmlRequestAttributes xacmlReq) { + private static final Logger logger = LoggerFactory + .getLogger(PolicyGuardXacmlHelper.class); + + public PolicyGuardXacmlHelper() { + init(PolicyEngine.manager.getEnvironment()); + } + + // initialized from 'pdpx.url' property -- + // Each entry in 'restUrls' contains a destination URL, and an optional + // 'Authorization' header entry. 'restUrlIndex' indicates the next + // entry to try -- after each failure, the index is advanced to the + // next entry (wrapping to the beginning, if needed). + static private class URLEntry implements Serializable { + URL restURL; + String authorization = null; + String clientAuth = null; + String environment = null; + }; + + private URLEntry[] restUrls = null; + private int restUrlIndex = 0; + + // REST timeout, initialized from 'pdpx.timeout' property + private int timeout = 20000; + + + // initialized from 'guard.disabled', but may also be set to 'true' if + // there is an initialization error + private boolean disabled = false; + + // errors that forced 'disabled' to be set to 'true' + private String errorMessage = null; + + public String callPDP(PolicyGuardXacmlRequestAttributes xacmlReq) { // // Send it to the PDP // -// com.att.research.xacml.api.Response response = null; String response = null; - + + // + // Build the json request + // JSONObject attributes = new JSONObject(); attributes.put("actor", xacmlReq.getActor_id()); attributes.put("recipe", xacmlReq.getOperation_id()); attributes.put("target", xacmlReq.getTarget_id()); - if (xacmlReq.getClname_id() != null){ + if (xacmlReq.getClname_id() != null) { attributes.put("clname", xacmlReq.getClname_id()); } JSONObject jsonReq = new JSONObject(); jsonReq.put("decisionAttributes", attributes); jsonReq.put("onapName", "PDPD"); - + + URLEntry urlEntry = restUrls[restUrlIndex]; + try { // // Call RESTful PDP // - response = callRESTfulPDP(new ByteArrayInputStream(jsonReq.toString().getBytes()), new URL(restfulPdpUrl/*"https://localhost:8443/pdp/"*/)); + response = callRESTfulPDP(new ByteArrayInputStream(jsonReq + .toString().getBytes()), urlEntry.restURL, + urlEntry.authorization, urlEntry.clientAuth, + urlEntry.environment); } catch (Exception e) { logger.error("Error in sending RESTful request: ", e); } - - + return response; } - - + /** - * This makes an HTTP POST call to a running PDP RESTful servlet to get a decision. + * This makes an HTTP POST call to a running PDP RESTful servlet to get a + * decision. * * @param file * @return response from guard which contains "Permit" or "Deny" */ - private static String callRESTfulPDP(InputStream is, URL restURL) { -// com.att.research.xacml.api.Response response = null; + private String callRESTfulPDP(InputStream is, URL restURL, + String authorization, String clientauth, String environment) { String response = null; String rawDecision = null; HttpURLConnection connection = null; @@ -99,16 +141,28 @@ public class PolicyGuardXacmlHelper { // // Setup our method and headers // - connection.setRequestMethod("POST"); - connection.setUseCaches(false); - // - // Adding this in. It seems the HttpUrlConnection class does NOT - // properly forward our headers for POST re-direction. It does so - // for a GET re-direction. - // - // So we need to handle this ourselves. - // - connection.setInstanceFollowRedirects(false); + connection.setRequestProperty("Accept", "application/json"); + if (authorization != null) { + connection.setRequestProperty("Authorization", authorization); + } + if (clientauth != null) { + connection.setRequestProperty("ClientAuth", clientauth); + } + if (environment != null) { + connection.setRequestProperty("Environment", environment); + } + connection.setConnectTimeout(timeout); + connection.setReadTimeout(timeout); + connection.setRequestMethod("POST"); + connection.setUseCaches(false); + // + // Adding this in. It seems the HttpUrlConnection class does NOT + // properly forward our headers for POST re-direction. It does so + // for a GET re-direction. + // + // So we need to handle this ourselves. + // + connection.setInstanceFollowRedirects(false); connection.setDoOutput(true); connection.setDoInput(true); // @@ -117,115 +171,291 @@ public class PolicyGuardXacmlHelper { try (OutputStream os = connection.getOutputStream()) { IOUtils.copy(is, os); } - // - // Do the connect - // - connection.connect(); - if (connection.getResponseCode() == 200) { - // - // Read the response - // - ContentType contentType = null; - try { - contentType = ContentType.parse(connection.getContentType()); - - if (contentType.getMimeType().equalsIgnoreCase(ContentType.APPLICATION_JSON.getMimeType())) { - InputStream iStream = connection.getInputStream(); - int contentLength = connection.getContentLength(); - - // if content length is -1, respose is chunked, and - // TCP connection will be dropped at the end - byte[] buf = - new byte[contentLength < 0 ? 1024 : contentLength]; - int offset = 0; - for ( ; ; ) - { - if (offset == contentLength) - { - // all expected bytes have been read - response = new String(buf); - break; + // + // Do the connect + // + connection.connect(); + if (connection.getResponseCode() == 200) { + // + // Read the response + // + ContentType contentType = null; + try { + contentType = ContentType + .parse(connection.getContentType()); + + if (contentType.getMimeType().equalsIgnoreCase( + ContentType.APPLICATION_JSON.getMimeType())) { + InputStream iStream = connection.getInputStream(); + int contentLength = connection.getContentLength(); + + // if content length is -1, respose is chunked, and + // TCP connection will be dropped at the end + byte[] buf = new byte[contentLength < 0 ? 1024 + : contentLength]; + int offset = 0; + for (;;) { + if (offset == contentLength) { + // all expected bytes have been read + response = new String(buf); + break; } - int size = iStream.read(buf, offset, - buf.length - offset); - if (size < 0) - { - if (contentLength > 0) - { - logger.error("partial input stream"); - } - else - { - // chunked response -- - // dropped connection is expected - response = new String(buf, 0, offset); + int size = iStream.read(buf, offset, buf.length + - offset); + if (size < 0) { + if (contentLength > 0) { + logger.error("partial input stream"); + } else { + // chunked response -- + // dropped connection is expected + response = new String(buf, 0, offset); } - break; + break; } - offset += size; + offset += size; } - } else { - logger.error("unknown content-type: " + contentType); - } + } else { + logger.error("unknown content-type: " + contentType); + } - } catch (Exception e) { - String message = "Parsing Content-Type: " + connection.getContentType(); + } catch (Exception e) { + String message = "Parsing Content-Type: " + + connection.getContentType(); logger.error(message, e); - } + } - } else { - logger.error(connection.getResponseCode() + " " + connection.getResponseMessage()); - } + } else { + logger.error(connection.getResponseCode() + " " + + connection.getResponseMessage()); + } } catch (Exception e) { - logger.error("Exception in 'PolicyGuardXacmlHelper.callRESTfulPDP'", e); + logger.error( + "Exception in 'PolicyGuardXacmlHelper.callRESTfulPDP'", e); + // + // Connection may have failed, return Indeterminate + // + if(response == null || "".equals(response)){ + return Util.INDETERMINATE; + } } rawDecision = new JSONObject(response).getString("decision"); - + return rawDecision; } - - - public static PolicyGuardResponse ParseXacmlPdpResponse(com.att.research.xacml.api.Response xacmlResponse){ - - if(xacmlResponse == null){ - + + public static PolicyGuardResponse ParseXacmlPdpResponse( + com.att.research.xacml.api.Response xacmlResponse) { + + if (xacmlResponse == null) { + // - //In case the actual XACML response was null, create an empty response object with decision "Indeterminate" + // In case the actual XACML response was null, create an empty + // response object with decision "Indeterminate" // return new PolicyGuardResponse("Indeterminate", null, ""); } - + Iterator it_res = xacmlResponse.getResults().iterator(); - - Result res = it_res.next(); + + Result res = it_res.next(); String decision_from_xacml_response = res.getDecision().toString(); - Iterator it_attr_cat = res.getAttributes().iterator(); + Iterator it_attr_cat = res.getAttributes() + .iterator(); UUID req_id_from_xacml_response = null; String operation_from_xacml_response = ""; - - while(it_attr_cat.hasNext()){ - Iterator it_attr = it_attr_cat.next().getAttributes().iterator(); - while(it_attr.hasNext()){ + + while (it_attr_cat.hasNext()) { + Iterator it_attr = it_attr_cat.next().getAttributes() + .iterator(); + while (it_attr.hasNext()) { Attribute current_attr = it_attr.next(); String s = current_attr.getAttributeId().stringValue(); - if(s.equals("urn:oasis:names:tc:xacml:1.0:request:request-id")){ - Iterator> it_values = current_attr.getValues().iterator(); - req_id_from_xacml_response = UUID.fromString(it_values.next().getValue().toString()); + if ("urn:oasis:names:tc:xacml:1.0:request:request-id".equals(s)) { + Iterator> it_values = current_attr + .getValues().iterator(); + req_id_from_xacml_response = UUID.fromString(it_values + .next().getValue().toString()); } - if(s.equals("urn:oasis:names:tc:xacml:1.0:operation:operation-id")){ - Iterator> it_values = current_attr.getValues().iterator(); - operation_from_xacml_response = it_values.next().getValue().toString(); + if ("urn:oasis:names:tc:xacml:1.0:operation:operation-id" + .equals(s)) { + Iterator> it_values = current_attr + .getValues().iterator(); + operation_from_xacml_response = it_values.next().getValue() + .toString(); } - + } } - - - return new PolicyGuardResponse(decision_from_xacml_response, req_id_from_xacml_response, operation_from_xacml_response); - + + return new PolicyGuardResponse(decision_from_xacml_response, + req_id_from_xacml_response, operation_from_xacml_response); + + } + + private void init(Properties properties) { + // used to store error messages + StringBuilder sb = new StringBuilder(); + + // fetch these parameters, if they exist + String timeoutString = properties.getProperty("pdpx.timeout"); + String disabledString = properties.getProperty("guard.disabled"); + + if (disabledString != null) { + // decode optional 'guard.disabled' parameter + disabled = new Boolean(disabledString); + if (disabled) { + // skip everything else + return; + } + } + + /* + * Decode 'pdpx.*' parameters + */ + + // first, the default parameters + String defaultUser = properties.getProperty("pdpx.username"); + String defaultPassword = properties + .getProperty("pdpx.password"); + String defaultClientUser = properties + .getProperty("pdpx.client.username"); + String defaultClientPassword = properties + .getProperty("pdpx.client.password"); + String defaultEnvironment = properties + .getProperty("pdpx.environment"); + + // now, see which numeric entries (1-9) exist + ArrayList entries = new ArrayList<>(); + + for (int index = 0; index < 10; index += 1) { + String urlPrefix = "guard."; + String pdpxPrefix = "pdpx."; + if (index != 0) { + urlPrefix = urlPrefix + index + "."; + } + + // see if the associated URL exists + String restURLlist = properties.getProperty(urlPrefix + "url"); + if (nullOrEmpty(restURLlist)) { + // no entry for this index + continue; + } + + // support a list of entries separated by semicolons. Each entry + // can be: + // URL + // URL,user + // URL,user,password + for (String restURL : restURLlist.split("\\s*;\\s*")) { + String[] segments = restURL.split("\\s*,\\s*"); + String user = null; + String password = null; + + if (segments.length >= 2) { + // user id is provided + restURL = segments[0]; + user = segments[1]; + if (segments.length >= 3) { + // password is also provided + password = segments[2]; + } + } + + // URL does exist -- create the entry + URLEntry urlEntry = new URLEntry(); + try { + urlEntry.restURL = new URL(restURL); + } catch (java.net.MalformedURLException e) { + // if we don't have a URL, + // don't bother with the rest on this one + sb.append("'").append(urlPrefix).append("url' '") + .append(restURL).append("': ").append(e) + .append(","); + continue; + } + + if (nullOrEmpty(user)) { + // user id was not provided on '*.url' line -- + // extract it from a separate property + user = properties.getProperty(pdpxPrefix + "username", defaultUser); + } + if (nullOrEmpty(password)) { + // password was not provided on '*.url' line -- + // extract it from a separate property + password = properties.getProperty(pdpxPrefix + "password", + defaultPassword); + } + + // see if 'user' and 'password' entries both exist + if (!nullOrEmpty(user) && !nullOrEmpty(password)) { + urlEntry.authorization = "Basic " + + Base64.getEncoder().encodeToString( + (user + ":" + password).getBytes()); + } + + // see if 'client.user' and 'client.password' entries both exist + String clientUser = properties.getProperty(pdpxPrefix + + "client.username", defaultClientUser); + String clientPassword = properties.getProperty(pdpxPrefix + + "client.password", defaultClientPassword); + if (!nullOrEmpty(clientUser) && !nullOrEmpty(clientPassword)) { + urlEntry.clientAuth = "Basic " + + Base64.getEncoder().encodeToString( + (clientUser + ":" + clientPassword) + .getBytes()); + } + + // see if there is an 'environment' entry + String environment = properties.getProperty(pdpxPrefix + + "environment", defaultEnvironment); + if (!nullOrEmpty(environment)) { + urlEntry.environment = environment; + } + + // include this URLEntry in the list + entries.add(urlEntry); + } + } + + if (entries.size() == 0) { + sb.append("'pdpx.*' -- no URLs specified, "); + } else { + restUrls = entries.toArray(new URLEntry[0]); + } + + if (timeoutString != null) { + try { + // decode optional 'pdpx.timeout' parameter + timeout = Integer.valueOf(timeoutString); + } catch (NumberFormatException e) { + sb.append("'pdpx.timeout': " + e + ", "); + logger.trace(e.getLocalizedMessage()); + } + } + + + // if there are any errors, update 'errorMessage' & disable guard + // queries + if (sb.length() != 0) { + // remove the terminating ", ", and extract resulting error message + sb.setLength(sb.length() - 2); + errorMessage = sb.toString(); + disabled = true; + logger.error("Initialization failure: " + errorMessage); + } + } + + /** + * Check if a string is null or an empty string + * + * @param value + * the string to be tested + * @return 'true' if the string is 'null' or has a length of 0, 'false' + * otherwise + */ + static private boolean nullOrEmpty(String value) { + return (value == null || value.isEmpty()); } - - - + } diff --git a/controlloop/common/guard/src/main/java/org/onap/policy/guard/Util.java b/controlloop/common/guard/src/main/java/org/onap/policy/guard/Util.java index b594aff0a..f572cd7fa 100644 --- a/controlloop/common/guard/src/main/java/org/onap/policy/guard/Util.java +++ b/controlloop/common/guard/src/main/java/org/onap/policy/guard/Util.java @@ -31,12 +31,31 @@ import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.onap.policy.controlloop.policy.ControlLoopPolicy; import org.onap.policy.controlloop.policy.guard.ControlLoopGuard; +import org.onap.policy.drools.system.PolicyEngine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; public final class Util { + + /* + * Keys for guard properties + */ + public static final String PROP_GUARD_URL = "guard.url"; + public static final String PROP_GUARD_USER = "pdpx.username"; + public static final String PROP_GUARD_PASS = "pdpx.password"; + public static final String PROP_GUARD_CLIENT_USER = "pdpx.client.username"; + public static final String PROP_GUARD_CLIENT_PASS = "pdpx.client.password"; + public static final String PROP_GUARD_ENV = "pdpx.environment"; + + /* + * Guard responses + */ + public static final String INDETERMINATE = "Indeterminate"; + public static final String PERMIT = "Permit"; + public static final String DENY = "Deny"; + private static final Logger logger = LoggerFactory.getLogger(Util.class); public static class Pair { @@ -83,5 +102,26 @@ public final class Util { } return null; } + + /** + * Sets Guard Properties. + * + * @see /guard/src/test/java/org/onap/policy/guard/UtilTest.java + * for setting test properties + */ + public static void setGuardEnvProps(String url, String username, String password, String clientName, String clientPassword, String environment){ + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_URL, url); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_USER, username); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_PASS, password); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_CLIENT_USER, clientName); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_CLIENT_PASS, clientPassword); + PolicyEngine.manager.setEnvironmentProperty(org.onap.policy.guard.Util.PROP_GUARD_ENV, environment); + } + public static void setGuardEnvProp(String key, String value){ + PolicyEngine.manager.setEnvironmentProperty(key, value); + } + public static String getGuardProp(String propName){ + return PolicyEngine.manager.getEnvironmentProperty(propName); + } } -- cgit 1.2.3-korg