From 4d467b14363d800b2185b89790d57871f11ea88c Mon Sep 17 00:00:00 2001
From: James Falcon <james.falcon@canonical.com>
Date: Wed, 29 Jun 2022 17:27:44 -0500
Subject: [PATCH] Remove schema errors from log (#1551)

When schema errors are encountered, the section of userdata in question
gets printed to the cloud-init log. As this could contain sensitive
data, so log a generic warning instead and redirect user to run
cloud-init schema --system as root.

LP: #1978422
CVE: 2022-2084
---
 cloudinit/config/schema.py                  | 15 ++++++++++++---
 tests/unittests/test_handler/test_schema.py | 17 +++++++++++++++++
 2 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py
index 807c3ee..cc0a2a5 100644
--- a/cloudinit/config/schema.py
+++ b/cloudinit/config/schema.py
@@ -58,7 +58,7 @@ class SchemaValidationError(ValueError):
         super(SchemaValidationError, self).__init__(message)
 
 
-def validate_cloudconfig_schema(config, schema, strict=False):
+def validate_cloudconfig_schema(config, schema, strict=False, log_details=True):
     """Validate provided config meets the schema definition.
 
     @param config: Dict of cloud configuration settings validated against
@@ -67,6 +67,9 @@ def validate_cloudconfig_schema(config, schema, strict=False):
        for the cloud config module (config.cc_*).
     @param strict: Boolean, when True raise SchemaValidationErrors instead of
        logging warnings.
+    @param log_details: Boolean, when True logs details of validation errors.
+       If there are concerns about logging sensitive userdata, this should
+       be set to False.
 
     @raises: SchemaValidationError when provided config does not validate
         against the provided schema.
@@ -84,10 +87,16 @@ def validate_cloudconfig_schema(config, schema, strict=False):
         errors += ((path, error.message),)
     if errors:
         if strict:
+            # This could output/log sensitive data
             raise SchemaValidationError(errors)
-        else:
+        if log_details:
             messages = ['{0}: {1}'.format(k, msg) for k, msg in errors]
-            logging.warning('Invalid config:\n%s', '\n'.join(messages))
+            details = "\n" + "\n".join(messages)
+        else:
+            details = (
+                "Please run 'sudo cloud-init devel schema --system' or 'cloud-init devel schema -c config-file' to see the schema errors."
+            )
+        logging.warning('Invalid config:%s', details)
 
 
 def annotated_cloudconfig_file(cloudconfig, original_content, schema_errors):
diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py
index e69a47a..85f281d 100644
--- a/tests/unittests/test_handler/test_schema.py
+++ b/tests/unittests/test_handler/test_schema.py
@@ -78,6 +78,23 @@ class ValidateCloudConfigSchemaTest(CiTestCase):
             "Invalid config:\np1: -1 is not of type 'string'\n",
             self.logs.getvalue())
 
+    @skipUnlessJsonSchema()
+    def test_validateconfig_schema_sensitive(self):
+        """When log_details=False, ensure details are omitted"""
+        schema = {
+            "properties": {"hashed_password": {"type": "string"}},
+            "additionalProperties": False,
+        }
+        validate_cloudconfig_schema(
+            {"hashed-password": "secret"},
+            schema,
+            strict=False,
+            log_details=False,
+        )
+        self.assertIn(
+            "Please run 'sudo cloud-init devel schema --system' or 'cloud-init devel schema -c config-file' to see the schema errors.",
+            self.logs.getvalue())
+
     @skipUnlessJsonSchema()
     def test_validateconfig_schema_emits_warning_on_missing_jsonschema(self):
         """Warning from validate_cloudconfig_schema when missing jsonschema."""
-- 
2.33.0

