This is an automated email from the ASF dual-hosted git repository.
pifta pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new c768815472 HDDS-10604. Whitelist based compliance check for crypto
related configuration options (#6860)
c768815472 is described below
commit c76881547267658d26511e074668fde45b2ee3a5
Author: Zita Dombi <[email protected]>
AuthorDate: Fri Jul 12 16:24:17 2024 +0200
HDDS-10604. Whitelist based compliance check for crypto related
configuration options (#6860)
---
.../hadoop/hdds/conf/DelegatingProperties.java | 190 +++++++++++++++++++++
.../hadoop/hdds/conf/OzoneConfiguration.java | 49 ++++++
.../utils/LegacyHadoopConfigurationSource.java | 58 ++++++-
.../hadoop/hdds/conf/TestOzoneConfiguration.java | 62 +++++++
4 files changed, 356 insertions(+), 3 deletions(-)
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/conf/DelegatingProperties.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/conf/DelegatingProperties.java
new file mode 100644
index 0000000000..bbf18aff8d
--- /dev/null
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/conf/DelegatingProperties.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.
+ */
+package org.apache.hadoop.hdds.conf;
+
+import org.apache.hadoop.ozone.OzoneConfigKeys;
+import org.apache.hadoop.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import static
org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_SECURITY_CRYPTO_COMPLIANCE_MODE_UNRESTRICTED;
+
+/**
+ * Delegating properties helper class. It's needed for configuration related
classes, so we are able
+ * to delegate the operations that are happening on their Properties object to
their parent's
+ * Properties object. This is needed because of the configuration compliance
checks.
+ */
+public class DelegatingProperties extends Properties {
+ private final Properties properties;
+ private final String complianceMode;
+ private final boolean checkCompliance;
+ private final Properties cryptoProperties;
+
+ public DelegatingProperties(Properties properties, String complianceMode,
Properties cryptoProperties) {
+ this.properties = properties;
+ this.complianceMode = complianceMode;
+ this.checkCompliance =
!complianceMode.equals(OZONE_SECURITY_CRYPTO_COMPLIANCE_MODE_UNRESTRICTED);
+ this.cryptoProperties = cryptoProperties;
+ }
+
+ public String checkCompliance(String config, String value) {
+ // Don't check the ozone.security.crypto.compliance.mode config, even
though it's tagged as a crypto config
+ if (checkCompliance && cryptoProperties.containsKey(config) &&
+ !config.equals(OzoneConfigKeys.OZONE_SECURITY_CRYPTO_COMPLIANCE_MODE))
{
+
+ String whitelistConfig = config + "." + complianceMode + ".whitelist";
+ String whitelistValue = properties.getProperty(whitelistConfig, "");
+
+ if (whitelistValue != null) {
+ Collection<String> whitelistOptions =
StringUtils.getTrimmedStringCollection(whitelistValue);
+
+ if (!whitelistOptions.contains(value)) {
+ throw new ConfigurationException("Not allowed configuration value!
Compliance mode is set to " +
+ complianceMode + " and " + config + " configuration's value is
not allowed. Please check the " +
+ whitelistConfig + " configuration.");
+ }
+ }
+ }
+ return value;
+ }
+
+ @Override
+ public String getProperty(String key) {
+ String value = properties.getProperty(key);
+ return checkCompliance(key, value);
+ }
+
+ @Override
+ public Object setProperty(String key, String value) {
+ return properties.setProperty(key, value);
+ }
+
+ @Override
+ public synchronized Object remove(Object key) {
+ return properties.remove(key);
+ }
+
+ @Override
+ public synchronized void clear() {
+ properties.clear();
+ }
+
+ @Override
+ public Enumeration<Object> keys() {
+ return properties.keys();
+ }
+
+ @Override
+ public Enumeration<?> propertyNames() {
+ return properties.propertyNames();
+ }
+
+ @Override
+ public Set<String> stringPropertyNames() {
+ return properties.stringPropertyNames();
+ }
+
+ @Override
+ public int size() {
+ return properties.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return properties.isEmpty();
+ }
+
+ @Override
+ public Set<Object> keySet() {
+ return properties.keySet();
+ }
+
+ @Override
+ public boolean contains(Object value) {
+ return properties.contains(value);
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return properties.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return properties.containsValue(value);
+ }
+
+ @Override
+ public Object get(Object key) {
+ return properties.get(key);
+ }
+
+ @Override
+ public synchronized boolean remove(Object key, Object value) {
+ return properties.remove(key, value);
+ }
+
+ @Override
+ public synchronized Object put(Object key, Object value) {
+ return properties.put(key, value);
+ }
+
+ @Override
+ public synchronized void putAll(Map<?, ?> t) {
+ properties.putAll(t);
+ }
+
+ @Override
+ public Collection<Object> values() {
+ return properties.values();
+ }
+
+ @Override
+ public Set<Map.Entry<Object, Object>> entrySet() {
+ return properties.entrySet();
+ }
+
+ @Override
+ public synchronized boolean equals(Object o) {
+ return properties.equals(o);
+ }
+
+ @Override
+ public synchronized int hashCode() {
+ return properties.hashCode();
+ }
+
+ public Iterator<Map.Entry<String, String>> iterator() {
+ Map<String, String> result = new HashMap<>();
+ synchronized (properties) {
+ for (Map.Entry<Object, Object> item : properties.entrySet()) {
+ if (item.getKey() instanceof String && item.getValue() instanceof
String) {
+ String value = checkCompliance((String) item.getKey(), (String)
item.getValue());
+ result.put((String) item.getKey(), value);
+ }
+ }
+ }
+ return result.entrySet().iterator();
+ }
+}
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/conf/OzoneConfiguration.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/conf/OzoneConfiguration.java
index b8742c6ba9..3ee1b70c6b 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/conf/OzoneConfiguration.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/conf/OzoneConfiguration.java
@@ -33,6 +33,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -407,4 +408,52 @@ public class OzoneConfiguration extends Configuration
}
return Integer.parseInt(value);
}
+
+ private Properties delegatingProps;
+
+ @Override
+ public synchronized void reloadConfiguration() {
+ super.reloadConfiguration();
+ delegatingProps = null;
+ }
+
+ @Override
+ protected final synchronized Properties getProps() {
+ if (delegatingProps == null) {
+ String complianceMode =
getPropertyUnsafe(OzoneConfigKeys.OZONE_SECURITY_CRYPTO_COMPLIANCE_MODE,
+ OzoneConfigKeys.OZONE_SECURITY_CRYPTO_COMPLIANCE_MODE_UNRESTRICTED);
+ Properties cryptoProperties = getCryptoProperties();
+ delegatingProps = new DelegatingProperties(super.getProps(),
complianceMode, cryptoProperties);
+ }
+ return delegatingProps;
+ }
+
+ /**
+ * Get a property value without the compliance check. It's needed to get the
compliance
+ * mode from the configuration.
+ *
+ * @param key property name
+ * @param defaultValue default value
+ * @return property value, without compliance check
+ */
+ private String getPropertyUnsafe(String key, String defaultValue) {
+ return super.getProps().getProperty(key, defaultValue);
+ }
+
+ private Properties getCryptoProperties() {
+ try {
+ return
super.getAllPropertiesByTag(ConfigTag.CRYPTO_COMPLIANCE.toString());
+ } catch (NoSuchMethodError e) {
+ // We need to handle NoSuchMethodError, because in Hadoop 2 we don't
have the
+ // getAllPropertiesByTag method. We won't be supporting the compliance
mode with
+ // that version, so we are safe to catch the exception and return a new
Properties object.
+ return new Properties();
+ }
+ }
+
+ @Override
+ public Iterator<Map.Entry<String, String>> iterator() {
+ DelegatingProperties properties = (DelegatingProperties) getProps();
+ return properties.iterator();
+ }
}
diff --git
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/utils/LegacyHadoopConfigurationSource.java
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/utils/LegacyHadoopConfigurationSource.java
index fcf6313305..4a18a6ca45 100644
---
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/utils/LegacyHadoopConfigurationSource.java
+++
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/utils/LegacyHadoopConfigurationSource.java
@@ -19,10 +19,16 @@ package org.apache.hadoop.hdds.utils;
import java.io.IOException;
import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hdds.conf.ConfigTag;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
+import org.apache.hadoop.hdds.conf.DelegatingProperties;
import org.apache.hadoop.hdds.conf.MutableConfigurationSource;
+import org.apache.hadoop.ozone.OzoneConfigKeys;
/**
* Configuration source to wrap Hadoop Configuration object.
@@ -32,9 +38,55 @@ public class LegacyHadoopConfigurationSource
private Configuration configuration;
- public LegacyHadoopConfigurationSource(
- Configuration configuration) {
- this.configuration = configuration;
+ public LegacyHadoopConfigurationSource(Configuration configuration) {
+ this.configuration = new Configuration(configuration) {
+ private Properties delegatingProps;
+
+ @Override
+ public synchronized void reloadConfiguration() {
+ super.reloadConfiguration();
+ delegatingProps = null;
+ }
+
+ @Override
+ protected synchronized Properties getProps() {
+ if (delegatingProps == null) {
+ String complianceMode =
getPropertyUnsafe(OzoneConfigKeys.OZONE_SECURITY_CRYPTO_COMPLIANCE_MODE,
+
OzoneConfigKeys.OZONE_SECURITY_CRYPTO_COMPLIANCE_MODE_UNRESTRICTED);
+ Properties cryptoProperties = getCryptoProperties();
+ delegatingProps = new DelegatingProperties(super.getProps(),
complianceMode, cryptoProperties);
+ }
+ return delegatingProps;
+ }
+
+ /**
+ * Get a property value without the compliance check. It's needed to get
the compliance mode.
+ *
+ * @param key property name
+ * @param defaultValue default value
+ * @return property value, without compliance check
+ */
+ private String getPropertyUnsafe(String key, String defaultValue) {
+ return super.getProps().getProperty(key, defaultValue);
+ }
+
+ private Properties getCryptoProperties() {
+ try {
+ return
super.getAllPropertiesByTag(ConfigTag.CRYPTO_COMPLIANCE.toString());
+ } catch (NoSuchMethodError e) {
+ // We need to handle NoSuchMethodError, because in Hadoop 2 we don't
have the
+ // getAllPropertiesByTag method. We won't be supporting the
compliance mode with
+ // that version, so we are safe to catch the exception and return a
new Properties object.
+ return new Properties();
+ }
+ }
+
+ @Override
+ public Iterator<Map.Entry<String, String>> iterator() {
+ DelegatingProperties props = (DelegatingProperties) getProps();
+ return props.iterator();
+ }
+ };
}
@Override
diff --git
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/TestOzoneConfiguration.java
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/TestOzoneConfiguration.java
index 26da27921e..be733be798 100644
---
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/TestOzoneConfiguration.java
+++
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/conf/TestOzoneConfiguration.java
@@ -31,6 +31,7 @@ import java.util.stream.Stream;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hdds.utils.LegacyHadoopConfigurationSource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -151,6 +152,67 @@ public class TestOzoneConfiguration {
assertEquals(Duration.ofSeconds(3), configuration.getDuration());
}
+ @Test
+ public void testRestrictedComplianceModeWithOzoneConf() {
+ Configuration config = new Configuration();
+ config.set("ozone.security.crypto.compliance.mode", "restricted");
+ OzoneConfiguration ozoneConfig = new OzoneConfiguration(config);
+
+ // Set it to an allowed config value
+ ozoneConfig.set("hdds.x509.signature.algorithm", "SHA512withDCA");
+ ozoneConfig.set("hdds.x509.signature.algorithm.restricted.whitelist",
"SHA512withRSA,SHA512withDCA");
+
+ assertEquals("restricted",
ozoneConfig.get("ozone.security.crypto.compliance.mode"));
+ assertEquals("SHA512withRSA,SHA512withDCA",
ozoneConfig.get("hdds.x509.signature.algorithm.restricted.whitelist"));
+ assertEquals("SHA512withDCA",
ozoneConfig.get("hdds.x509.signature.algorithm"));
+
+ // Set it to a disallowed config value
+ ozoneConfig.set("hdds.x509.signature.algorithm", "SHA256withRSA");
+
+ assertThrows(ConfigurationException.class, () ->
ozoneConfig.get("hdds.x509.signature.algorithm"));
+
+ // Check it with a Hadoop Configuration
+ Configuration hadoopConfig =
+ LegacyHadoopConfigurationSource.asHadoopConfiguration(ozoneConfig);
+ assertThrows(ConfigurationException.class, () ->
hadoopConfig.get("hdds.x509.signature.algorithm"));
+ }
+
+ @Test
+ public void testRestrictedComplianceModeWithLegacyHadoopConf() {
+ Configuration config = new Configuration();
+ config.addResource("ozone-default.xml");
+ config.set("ozone.security.crypto.compliance.mode", "restricted");
+ LegacyHadoopConfigurationSource legacyHadoopConf = new
LegacyHadoopConfigurationSource(config);
+
+ // Set it to an allowed config value
+ legacyHadoopConf.set("hdds.x509.signature.algorithm", "SHA512withDCA");
+ legacyHadoopConf.set("hdds.x509.signature.algorithm.restricted.whitelist",
"SHA512withRSA,SHA512withDCA");
+
+ assertEquals("restricted",
legacyHadoopConf.get("ozone.security.crypto.compliance.mode"));
+ assertEquals("SHA512withRSA,SHA512withDCA",
+
legacyHadoopConf.get("hdds.x509.signature.algorithm.restricted.whitelist"));
+ assertEquals("SHA512withDCA",
legacyHadoopConf.get("hdds.x509.signature.algorithm"));
+
+ // Set it to a disallowed config value
+ legacyHadoopConf.set("hdds.x509.signature.algorithm", "SHA256withRSA");
+
+ assertThrows(ConfigurationException.class, () ->
legacyHadoopConf.get("hdds.x509.signature.algorithm"));
+
+ // Check it with a Hadoop Configuration
+ Configuration legacyConf =
LegacyHadoopConfigurationSource.asHadoopConfiguration(legacyHadoopConf);
+ assertThrows(ConfigurationException.class, () ->
legacyConf.get("hdds.x509.signature.algorithm"));
+ }
+
+ @Test
+ public void testUnrestrictedComplianceMode() {
+ OzoneConfiguration ozoneConfig = new OzoneConfiguration();
+ ozoneConfig.set("hdds.x509.signature.algorithm", "SHA256");
+ ozoneConfig.set("hdds.x509.signature.algorithm.unrestricted.whitelist",
"SHA512withRSA");
+
+ assertEquals(ozoneConfig.get("hdds.x509.signature.algorithm"), "SHA256");
+ assertEquals(ozoneConfig.get("ozone.security.crypto.compliance.mode"),
"unrestricted");
+ }
+
@Test
public void getConfigurationObjectWithDefault() {
OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]