From 327ac05ea0e29a8d604f187d78f1a48aa35d8b4a Mon Sep 17 00:00:00 2001 From: jhh Date: Wed, 16 Oct 2019 21:30:26 -0500 Subject: Allow encrypted property values Issue-ID: POLICY-1945 Signed-off-by: jhh Change-Id: I0317a6de838d99b579638252859e42fc49cedfa8 Signed-off-by: jhh --- .../drools/utils/CryptoCoderValueLookup.java | 51 ++++++++ .../EnvironmentVariableWithDefaultLookup.java | 57 +++++++++ .../org/onap/policy/drools/utils/PropertyUtil.java | 142 ++++++++++++++++----- .../onap/policy/drools/utils/PropertyUtilTest.java | 112 +++++++++++++--- .../src/test/resources/interpolation.properties | 7 + 5 files changed, 320 insertions(+), 49 deletions(-) create mode 100644 policy-utils/src/main/java/org/onap/policy/drools/utils/CryptoCoderValueLookup.java create mode 100644 policy-utils/src/main/java/org/onap/policy/drools/utils/EnvironmentVariableWithDefaultLookup.java (limited to 'policy-utils') diff --git a/policy-utils/src/main/java/org/onap/policy/drools/utils/CryptoCoderValueLookup.java b/policy-utils/src/main/java/org/onap/policy/drools/utils/CryptoCoderValueLookup.java new file mode 100644 index 00000000..9d4a8824 --- /dev/null +++ b/policy-utils/src/main/java/org/onap/policy/drools/utils/CryptoCoderValueLookup.java @@ -0,0 +1,51 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.drools.utils; + +import org.apache.commons.configuration2.interpol.Lookup; +import org.apache.commons.lang3.StringUtils; +import org.onap.policy.common.utils.security.CryptoCoder; + +/** + * Crypto Coder value look up. Syntax: ${enc:encoded-value}. + */ +public class CryptoCoderValueLookup implements Lookup { + + protected final CryptoCoder cryptoCoder; + + /** + * Crypto Coder Lookup. + * + * @param crypto crypto coder + */ + public CryptoCoderValueLookup(CryptoCoder crypto) { + this.cryptoCoder = crypto; + } + + @Override + public String lookup(String key) { + if (StringUtils.isBlank(key)) { + return null; + } + + return cryptoCoder.decrypt(PropertyUtil.CRYPTO_CODER_PROPERTY_PREFIX + ":" + key); + } +} diff --git a/policy-utils/src/main/java/org/onap/policy/drools/utils/EnvironmentVariableWithDefaultLookup.java b/policy-utils/src/main/java/org/onap/policy/drools/utils/EnvironmentVariableWithDefaultLookup.java new file mode 100644 index 00000000..e0d3e388 --- /dev/null +++ b/policy-utils/src/main/java/org/onap/policy/drools/utils/EnvironmentVariableWithDefaultLookup.java @@ -0,0 +1,57 @@ +/* + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.drools.utils; + +import com.google.common.base.Splitter; +import java.util.List; +import org.apache.commons.configuration2.interpol.Lookup; +import org.apache.commons.lang3.StringUtils; + +/** + * Environment Variable with a default value. The syntax is + * ${envd:variable-name:default-value}. + */ +public class EnvironmentVariableWithDefaultLookup implements Lookup { + + @Override + public String lookup(String key) { + if (StringUtils.isBlank(key)) { + return null; + } + + List envWithDefault = + Splitter.on(":").trimResults().omitEmptyStrings().splitToList(key); + if (envWithDefault.isEmpty()) { + return StringUtils.EMPTY; + } + + String envVar = System.getenv(envWithDefault.get(0)); + if (StringUtils.isNotEmpty(envVar)) { + return envVar; + } + + if (envWithDefault.size() > 1) { + return String.join(":", envWithDefault.subList(1, envWithDefault.size())); + } + + return StringUtils.EMPTY; + } +} diff --git a/policy-utils/src/main/java/org/onap/policy/drools/utils/PropertyUtil.java b/policy-utils/src/main/java/org/onap/policy/drools/utils/PropertyUtil.java index 8594995d..0b49993d 100644 --- a/policy-utils/src/main/java/org/onap/policy/drools/utils/PropertyUtil.java +++ b/policy-utils/src/main/java/org/onap/policy/drools/utils/PropertyUtil.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * policy-utils + * ONAP * ================================================================================ * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved. * ================================================================================ @@ -30,9 +30,10 @@ import java.util.Properties; import java.util.Set; import java.util.Timer; import java.util.TimerTask; - +import org.apache.commons.configuration2.Configuration; import org.apache.commons.configuration2.ConfigurationConverter; import org.apache.commons.configuration2.SystemConfiguration; +import org.onap.policy.common.utils.security.CryptoCoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,6 +42,8 @@ import org.slf4j.LoggerFactory; * file, and optionally get notifications of future changes. */ public class PropertyUtil { + public static final String ENV_WITH_DEFAULT_PROPERTY_PREFIX = "envd"; + public static final String CRYPTO_CODER_PROPERTY_PREFIX = "enc"; // timer thread used for polling for property file changes private static Timer timer = null; @@ -51,10 +54,17 @@ public class PropertyUtil { private static final Logger logger = LoggerFactory.getLogger(PropertyUtil.class.getName()); + private static volatile CryptoCoder cryptoCoder; + /** - * Read in a properties file. Variable interpolation is performed by using - * apache commons configuration2 library. This allows for embedding system properties, - * constants, and environment variables in property files. + * Sets a default Crypto Coder. + */ + public static void setDefaultCryptoCoder(CryptoCoder cryptoCoder) { + PropertyUtil.cryptoCoder = cryptoCoder; + } + + /** + * Read in a properties file and performs variable interpolation. * * @param file the properties file * @return a Properties object, containing the associated properties @@ -63,22 +73,22 @@ public class PropertyUtil { * a problem loading the properties file. */ public static Properties getProperties(File file) throws IOException { - // create an InputStream (may throw a FileNotFoundException) - Properties rval = new Properties(); - try (FileInputStream fis = new FileInputStream(file)) { - // create the properties instance - - // load properties (may throw an IOException) - rval.load(fis); - } + return getInterpolatedProperties(getPropertiesFile(file)); + } - /* - * Return properties file with environment variables interpolated. - * It is necessary to construct the object in this fashion and avoid - * builders since they use the commons-beanutils (optional) library that has been - * flagged as insecured. - */ - return getInterpolatedProperties(rval); + /** + * Reads a property files and performs variable interpolation. It uses a + * crypto coder to interpolate encrypted sensitive data. + * + * @param file the properties file + * @param cryptoCoder a custom CryptoCoder implementation + * @return a Properties object, containing the associated properties + * @throws IOException - subclass 'FileNotFoundException' if the file + * does not exist or can't be opened, and 'IOException' if there is + * a problem loading the properties file. + */ + public static Properties getProperties(File file, CryptoCoder cryptoCoder) throws IOException { + return getInterpolatedProperties(getPropertiesFile(file), cryptoCoder); } /** @@ -88,12 +98,27 @@ public class PropertyUtil { * @return a Properties object, containing the associated properties * @throws IOException - subclass 'FileNotFoundException' if the file * does not exist or can't be opened, and 'IOException' if there is - * a problem loading the properties file. + * a problem loading the properties file */ public static Properties getProperties(String fileName) throws IOException { return getProperties(new File(fileName)); } + /** + * Reads a property files and performs variable interpolation. It uses a + * crypto coder to interpolate encrypted sensitive data. + * + * @param fileName the properties file name + * @param cryptoCoder a custom CryptoCoder implementation + * @return a Properties object, containing the associated properties + * @throws IOException - subclass 'FileNotFoundException' if the file + * does not exist or can't be opened, and 'IOException' if there is + * a problem loading the properties file + */ + public static Properties getProperties(String fileName, CryptoCoder cryptoCoder) throws IOException { + return getProperties(new File(fileName), cryptoCoder); + } + /** * Read in a properties file, and register for update notifications. * NOTE: it is possible that the first callback will occur while this @@ -110,8 +135,7 @@ public class PropertyUtil { * does not exist or can't be opened, and 'IOException' if there is * a problem loading the properties file. */ - public static Properties getProperties(File file, Listener listener) - throws IOException { + public static Properties getProperties(File file, Listener listener) throws IOException { File propFile = file; if (listener == null) { // no listener specified -- just fetch the properties @@ -151,19 +175,63 @@ public class PropertyUtil { * does not exist or can't be opened, and 'IOException' if there is * a problem loading the properties file. */ - public static Properties getProperties(String fileName, Listener listener) - throws IOException { + public static Properties getProperties(String fileName, Listener listener) throws IOException { return getProperties(new File(fileName), listener); } /** - * gets interpolated properties from a properties object. + * Get an interpolated properties object from a properties one. * - * @param properties object - * @return properties + * @param properties non-interpolated properties object + * @return Properties - interpolated properties object */ public static Properties getInterpolatedProperties(Properties properties) { - return ConfigurationConverter.getProperties(ConfigurationConverter.getConfiguration(properties)); + return getInterpolatedProperties(properties, cryptoCoder); + } + + /** + * Get an interpolated properties object from a properties one. + * + * @param properties non-interpolated properties object + * @param cryptoCoder crypto coder + * @return Properties - interpolated properties object + */ + public static Properties getInterpolatedProperties(Properties properties, CryptoCoder cryptoCoder) { + Configuration config = ConfigurationConverter.getConfiguration(properties); + config.getInterpolator() + .registerLookup(ENV_WITH_DEFAULT_PROPERTY_PREFIX, new EnvironmentVariableWithDefaultLookup()); + + if (cryptoCoder == null) { + return ConfigurationConverter.getProperties(config); + } + + config.getInterpolator().registerLookup(CRYPTO_CODER_PROPERTY_PREFIX, new CryptoCoderValueLookup(cryptoCoder)); + Properties props = ConfigurationConverter.getProperties(config); + props.stringPropertyNames().forEach(key -> { + props.setProperty(key, cryptoCoder.decrypt(props.getProperty(key))); + }); + return props; + } + + /** + * Read in a properties file from disk. + * + * @param file the properties file + * @return a Properties object, containing the associated properties + * @throws IOException - subclass 'FileNotFoundException' if the file + * does not exist or can't be opened, and 'IOException' if there is + * a problem loading the properties file. + */ + protected static Properties getPropertiesFile(File file) throws IOException { + // create an InputStream (may throw a FileNotFoundException) + Properties rval = new Properties(); + try (FileInputStream fis = new FileInputStream(file)) { + // create the properties instance + + // load properties (may throw an IOException) + rval.load(fis); + } + return rval; } /** @@ -172,8 +240,20 @@ public class PropertyUtil { * @param properties properties file */ public static void setSystemProperties(Properties properties) { - Properties interpolatedProps = getInterpolatedProperties(properties); - SystemConfiguration.setSystemProperties(ConfigurationConverter.getConfiguration(interpolatedProps)); + SystemConfiguration + .setSystemProperties(ConfigurationConverter + .getConfiguration(getInterpolatedProperties(properties))); + } + + /** + * sets system properties from a properties file. + * + * @param properties properties file + */ + public static void setSystemProperties(Properties properties, CryptoCoder cryptoCoder) { + SystemConfiguration + .setSystemProperties(ConfigurationConverter + .getConfiguration(getInterpolatedProperties(properties, cryptoCoder))); } /* ============================================================ */ diff --git a/policy-utils/src/test/java/org/onap/policy/drools/utils/PropertyUtilTest.java b/policy-utils/src/test/java/org/onap/policy/drools/utils/PropertyUtilTest.java index f05974c6..006d709c 100644 --- a/policy-utils/src/test/java/org/onap/policy/drools/utils/PropertyUtilTest.java +++ b/policy-utils/src/test/java/org/onap/policy/drools/utils/PropertyUtilTest.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * policy-utils + * ONAP * ================================================================================ * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved. * ================================================================================ @@ -30,22 +30,38 @@ import java.util.Properties; import java.util.Set; import java.util.TreeSet; import java.util.UUID; - +import org.apache.commons.lang3.StringUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import org.onap.policy.common.utils.security.CryptoUtils; import org.onap.policy.drools.utils.logging.LoggerUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PropertyUtilTest { - private static final Logger logger = LoggerFactory.getLogger(PropertyUtilTest.class); private static final String INTERPOLATION_PROPERTIES = "src/test/resources/interpolation.properties"; + private static final String INTERPOLATION_CRYPTO_KEY = "MTIzNDU2Nzg5MDEyMzQ1Ng=="; + private static final String INTERPOLATION_PLAINTEXT = "HelloWorld"; + private static final String INTERPOLATION_ENVD_DEFAULT_VALUE = "default"; + private static final String INTERPOLATION_ENC_HELLOWORLD = "enc:hcI2XVX+cxPz/6rlbebkWpCFF6WPbBtT7iJRr2VHUkA="; + private static final String INTERPOLATION_ENC_HELLOWORLD_VAR = "${" + INTERPOLATION_ENC_HELLOWORLD + "}"; + private static final String INTERPOLATION_NO = "interpolation.no"; private static final String INTERPOLATION_ENV = "interpolation.env"; + private static final String INTERPOLATION_ENVD = "interpolation.envd"; private static final String INTERPOLATION_CONST = "interpolation.const"; private static final String INTERPOLATION_SYS = "interpolation.sys"; + private static final String INTERPOLATION_ENVD_NONE = "interpolation.envd.none"; + private static final String INTERPOLATION_ENVD_DEFAULT = "interpolation.envd.default"; + private static final String INTERPOLATION_ENVD_NO_DEFAULT = "interpolation.envd.nodefault"; + private static final String INTERPOLATION_ENC = "interpolation.enc"; + private static final String INTERPOLATION_ENC2 = "interpolation.enc2"; + private static final String INTERPOLATION_ENVENC = "interpolation.envenc"; + + + private static final Logger logger = LoggerFactory.getLogger(PropertyUtilTest.class); private static File directory = null; @@ -136,25 +152,85 @@ public class PropertyUtilTest { // they should match assertEquals(prop1, prop2); - Properties prop3 = PropertyUtil.getProperties(INTERPOLATION_PROPERTIES); + // tests performed in sequence + testGetCryptoCoderArg(); + testGetNoCryptoProps(); + testGetDefaultCryptoProps(); + testGetNoCryptoSystemProps(); + testGetCryptoArgSystemProps(); + testGetDefaultCryptoSystemProps(); - assertEquals("no", prop3.getProperty(INTERPOLATION_NO)); - assertEquals(System.getenv("HOME"), prop3.getProperty(INTERPOLATION_ENV)); - assertEquals(LoggerUtil.ROOT_LOGGER, prop3.getProperty(INTERPOLATION_CONST)); - assertEquals(System.getProperty("user.home"), prop3.getProperty(INTERPOLATION_SYS)); + } - Properties prop4 = new Properties(); - prop4.put(INTERPOLATION_NO, "no"); - prop4.put(INTERPOLATION_ENV, "${env:HOME}"); - prop4.put(INTERPOLATION_CONST, "${const:org.onap.policy.drools.utils.logging.LoggerUtil.ROOT_LOGGER}"); - prop4.put(INTERPOLATION_SYS, "${sys:user.home}"); + private void testGetDefaultCryptoSystemProps() throws IOException { + // system properties + default crypto coder + PropertyUtil.setDefaultCryptoCoder(new CryptoUtils(INTERPOLATION_CRYPTO_KEY)); + PropertyUtil.setSystemProperties(PropertyUtil.getPropertiesFile(new File(INTERPOLATION_PROPERTIES))); + assertPropInterpolation(System.getProperties()); + assertPropEncInterpolation(System.getProperties()); + } - PropertyUtil.setSystemProperties(prop4); + private void testGetCryptoArgSystemProps() throws IOException { + // system properties + crypto coder passed in + PropertyUtil + .setSystemProperties(PropertyUtil + .getPropertiesFile(new File(INTERPOLATION_PROPERTIES)), new CryptoUtils(INTERPOLATION_CRYPTO_KEY)); + assertPropInterpolation(System.getProperties()); + assertPropEncInterpolation(System.getProperties()); + } + + private void testGetNoCryptoSystemProps() throws IOException { + /* system properties + no crypto coder */ + PropertyUtil.setDefaultCryptoCoder(null); + PropertyUtil.setSystemProperties(PropertyUtil.getPropertiesFile(new File(INTERPOLATION_PROPERTIES))); + assertPropInterpolation(System.getProperties()); + assertPropNoEncInterpolation(System.getProperties()); + } + + private void testGetDefaultCryptoProps() throws IOException { + /* properties + default crypto coder */ + PropertyUtil.setDefaultCryptoCoder(new CryptoUtils(INTERPOLATION_CRYPTO_KEY)); + Properties props = PropertyUtil.getProperties(INTERPOLATION_PROPERTIES); + assertPropInterpolation(props); + assertPropEncInterpolation(props); + } + + private void testGetNoCryptoProps() throws IOException { + /* properties + no crypto coder */ + Properties props = PropertyUtil.getProperties(INTERPOLATION_PROPERTIES); + assertPropInterpolation(props); + assertPropNoEncInterpolation(props); + } + + private void testGetCryptoCoderArg() throws IOException { + /* properties + crypto coder passed in */ + Properties props = + PropertyUtil.getProperties(INTERPOLATION_PROPERTIES, new CryptoUtils(INTERPOLATION_CRYPTO_KEY)); + assertPropInterpolation(props); + assertPropEncInterpolation(props); + } + + private void assertPropNoEncInterpolation(Properties props) { + assertEquals(INTERPOLATION_ENC_HELLOWORLD_VAR, props.getProperty(INTERPOLATION_ENC)); + assertEquals(INTERPOLATION_ENC_HELLOWORLD, props.getProperty(INTERPOLATION_ENC2)); + assertEquals(INTERPOLATION_ENC_HELLOWORLD, props.getProperty(INTERPOLATION_ENVENC)); + } + + private void assertPropEncInterpolation(Properties props) { + assertEquals(INTERPOLATION_PLAINTEXT, props.getProperty(INTERPOLATION_ENC)); + assertEquals(INTERPOLATION_PLAINTEXT, props.getProperty(INTERPOLATION_ENC2)); + assertEquals(INTERPOLATION_PLAINTEXT, props.getProperty(INTERPOLATION_ENVENC)); + } - assertEquals("no", System.getProperty(INTERPOLATION_NO)); - assertEquals(System.getenv("HOME"), System.getProperty(INTERPOLATION_ENV)); - assertEquals(LoggerUtil.ROOT_LOGGER, System.getProperty(INTERPOLATION_CONST)); - assertEquals(System.getProperty("user.home"), System.getProperty(INTERPOLATION_SYS)); + private void assertPropInterpolation(Properties props) { + assertEquals("no", props.getProperty(INTERPOLATION_NO)); + assertEquals(System.getenv("HOME"), props.getProperty(INTERPOLATION_ENV)); + assertEquals(System.getenv("HOME"), props.getProperty(INTERPOLATION_ENVD)); + assertEquals(StringUtils.EMPTY, props.getProperty(INTERPOLATION_ENVD_NONE)); + assertEquals(StringUtils.EMPTY, props.getProperty(INTERPOLATION_ENVD_NO_DEFAULT)); + assertEquals(LoggerUtil.ROOT_LOGGER, props.getProperty(INTERPOLATION_CONST)); + assertEquals(System.getProperty("user.home"), props.getProperty(INTERPOLATION_SYS)); + assertEquals(INTERPOLATION_ENVD_DEFAULT_VALUE, props.getProperty(INTERPOLATION_ENVD_DEFAULT)); } /** diff --git a/policy-utils/src/test/resources/interpolation.properties b/policy-utils/src/test/resources/interpolation.properties index 66554337..2145fb61 100644 --- a/policy-utils/src/test/resources/interpolation.properties +++ b/policy-utils/src/test/resources/interpolation.properties @@ -2,3 +2,10 @@ interpolation.no=no interpolation.env=${env:HOME} interpolation.const=${const:org.onap.policy.drools.utils.logging.LoggerUtil.ROOT_LOGGER} interpolation.sys=${sys:user.home} +interpolation.envd=${envd:HOME} +interpolation.envd.default=${envd:BLAH:default} +interpolation.envd.none=${envd:BLAH} +interpolation.envd.nodefault=${envd:BLAH:} +interpolation.enc=${enc:hcI2XVX+cxPz/6rlbebkWpCFF6WPbBtT7iJRr2VHUkA=} +interpolation.enc2=enc:hcI2XVX+cxPz/6rlbebkWpCFF6WPbBtT7iJRr2VHUkA= +interpolation.envenc=${envd:HELLOWORLD:enc:hcI2XVX+cxPz/6rlbebkWpCFF6WPbBtT7iJRr2VHUkA=} \ No newline at end of file -- cgit 1.2.3-korg