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]

Reply via email to