|
| 1 | +commit 4d467b14363d800b2185b89790d57871f11ea88c |
| 2 | +Author: James Falcon < [email protected]> |
| 3 | +Date: Wed Jun 29 17:27:44 2022 -0500 |
| 4 | + |
| 5 | + Remove schema errors from log (#1551) |
| 6 | + |
| 7 | + When schema errors are encountered, the section of userdata in question |
| 8 | + gets printed to the cloud-init log. As this could contain sensitive |
| 9 | + data, so log a generic warning instead and redirect user to run |
| 10 | + cloud-init schema --system as root. |
| 11 | + |
| 12 | + LP: #1978422 |
| 13 | + CVE: 2022-2084 |
| 14 | + |
| 15 | +diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py |
| 16 | +index c6303478..4f157870 100755 |
| 17 | +--- a/cloudinit/cmd/main.py |
| 18 | ++++ b/cloudinit/cmd/main.py |
| 19 | +@@ -455,7 +455,9 @@ def main_init(name, args): |
| 20 | + |
| 21 | + # Validate user-data adheres to schema definition |
| 22 | + if os.path.exists(init.paths.get_ipath_cur("userdata_raw")): |
| 23 | +- validate_cloudconfig_schema(config=init.cfg, strict=False) |
| 24 | ++ validate_cloudconfig_schema( |
| 25 | ++ config=init.cfg, strict=False, log_details=False |
| 26 | ++ ) |
| 27 | + else: |
| 28 | + LOG.debug("Skipping user-data validation. No user-data found.") |
| 29 | + |
| 30 | +diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py |
| 31 | +index a60ceb11..1e29ae5a 100644 |
| 32 | +--- a/cloudinit/config/schema.py |
| 33 | ++++ b/cloudinit/config/schema.py |
| 34 | +@@ -198,6 +198,7 @@ def validate_cloudconfig_schema( |
| 35 | + schema: dict = None, |
| 36 | + strict: bool = False, |
| 37 | + strict_metaschema: bool = False, |
| 38 | ++ log_details: bool = True, |
| 39 | + ): |
| 40 | + """Validate provided config meets the schema definition. |
| 41 | + |
| 42 | +@@ -210,6 +211,9 @@ def validate_cloudconfig_schema( |
| 43 | + logging warnings. |
| 44 | + @param strict_metaschema: Boolean, when True validates schema using strict |
| 45 | + metaschema definition at runtime (currently unused) |
| 46 | ++ @param log_details: Boolean, when True logs details of validation errors. |
| 47 | ++ If there are concerns about logging sensitive userdata, this should |
| 48 | ++ be set to False. |
| 49 | + |
| 50 | + @raises: SchemaValidationError when provided config does not validate |
| 51 | + against the provided schema. |
| 52 | +@@ -234,12 +238,17 @@ def validate_cloudconfig_schema( |
| 53 | + errors += ((path, error.message),) |
| 54 | + if errors: |
| 55 | + if strict: |
| 56 | ++ # This could output/log sensitive data |
| 57 | + raise SchemaValidationError(errors) |
| 58 | +- else: |
| 59 | ++ if log_details: |
| 60 | + messages = ["{0}: {1}".format(k, msg) for k, msg in errors] |
| 61 | +- LOG.warning( |
| 62 | +- "Invalid cloud-config provided:\n%s", "\n".join(messages) |
| 63 | ++ details = "\n" + "\n".join(messages) |
| 64 | ++ else: |
| 65 | ++ details = ( |
| 66 | ++ "Please run 'sudo cloud-init schema --system' to " |
| 67 | ++ "see the schema errors." |
| 68 | + ) |
| 69 | ++ LOG.warning("Invalid cloud-config provided: %s", details) |
| 70 | + |
| 71 | + |
| 72 | + def annotated_cloudconfig_file( |
| 73 | +diff --git a/tests/integration_tests/modules/test_cli.py b/tests/integration_tests/modules/test_cli.py |
| 74 | +index e878176f..4b8f53a8 100644 |
| 75 | +--- a/tests/integration_tests/modules/test_cli.py |
| 76 | ++++ b/tests/integration_tests/modules/test_cli.py |
| 77 | +@@ -18,11 +18,18 @@ runcmd: |
| 78 | + - echo 'hi' > /var/tmp/test |
| 79 | + """ |
| 80 | + |
| 81 | ++# The '-' in 'hashed-password' fails schema validation |
| 82 | + INVALID_USER_DATA_SCHEMA = """\ |
| 83 | + #cloud-config |
| 84 | +-updates: |
| 85 | +- notnetwork: -1 |
| 86 | +-apt_pipelining: bogus |
| 87 | ++users: |
| 88 | ++ - default |
| 89 | ++ - name: newsuper |
| 90 | ++ gecos: Big Stuff |
| 91 | ++ groups: users, admin |
| 92 | ++ sudo: ALL=(ALL) NOPASSWD:ALL |
| 93 | ++ hashed-password: asdfasdf |
| 94 | ++ shell: /bin/bash |
| 95 | ++ lock_passwd: true |
| 96 | + """ |
| 97 | + |
| 98 | + |
| 99 | +@@ -69,11 +76,12 @@ def test_invalid_userdata_schema(client: IntegrationInstance): |
| 100 | + assert result.ok |
| 101 | + log = client.read_from_file("/var/log/cloud-init.log") |
| 102 | + warning = ( |
| 103 | +- "[WARNING]: Invalid cloud-config provided:\napt_pipelining: 'bogus'" |
| 104 | +- " is not valid under any of the given schemas\nupdates: Additional" |
| 105 | +- " properties are not allowed ('notnetwork' was unexpected)" |
| 106 | ++ "[WARNING]: Invalid cloud-config provided: Please run " |
| 107 | ++ "'sudo cloud-init schema --system' to see the schema errors." |
| 108 | + ) |
| 109 | + assert warning in log |
| 110 | ++ assert "asdfasdf" not in log |
| 111 | ++ |
| 112 | + result = client.execute("cloud-init status --long") |
| 113 | + if not result.ok: |
| 114 | + raise AssertionError( |
| 115 | +diff --git a/tests/unittests/config/test_schema.py b/tests/unittests/config/test_schema.py |
| 116 | +index 1840b70d..4a41c4c1 100644 |
| 117 | +--- a/tests/unittests/config/test_schema.py |
| 118 | ++++ b/tests/unittests/config/test_schema.py |
| 119 | +@@ -286,10 +286,31 @@ class TestValidateCloudConfigSchema: |
| 120 | + assert "cloudinit.config.schema" == module |
| 121 | + assert logging.WARNING == log_level |
| 122 | + assert ( |
| 123 | +- "Invalid cloud-config provided:\np1: -1 is not of type 'string'" |
| 124 | ++ "Invalid cloud-config provided: \np1: -1 is not of type 'string'" |
| 125 | + == log_msg |
| 126 | + ) |
| 127 | + |
| 128 | ++ @skipUnlessJsonSchema() |
| 129 | ++ def test_validateconfig_schema_sensitive(self, caplog): |
| 130 | ++ """When log_details=False, ensure details are omitted""" |
| 131 | ++ schema = { |
| 132 | ++ "properties": {"hashed_password": {"type": "string"}}, |
| 133 | ++ "additionalProperties": False, |
| 134 | ++ } |
| 135 | ++ validate_cloudconfig_schema( |
| 136 | ++ {"hashed-password": "secret"}, |
| 137 | ++ schema, |
| 138 | ++ strict=False, |
| 139 | ++ log_details=False, |
| 140 | ++ ) |
| 141 | ++ [(module, log_level, log_msg)] = caplog.record_tuples |
| 142 | ++ assert "cloudinit.config.schema" == module |
| 143 | ++ assert logging.WARNING == log_level |
| 144 | ++ assert ( |
| 145 | ++ "Invalid cloud-config provided: Please run 'sudo cloud-init " |
| 146 | ++ "schema --system' to see the schema errors." == log_msg |
| 147 | ++ ) |
| 148 | ++ |
| 149 | + @skipUnlessJsonSchema() |
| 150 | + def test_validateconfig_schema_emits_warning_on_missing_jsonschema( |
| 151 | + self, caplog |
0 commit comments