From 17e3136ff325c1e6d5749c100505bd97980ffa1e Mon Sep 17 00:00:00 2001 From: Miroslav Rezanina Date: Tue, 7 Apr 2026 09:17:59 +0200 Subject: [PATCH] * Tue Apr 07 2026 Miroslav Rezanina - 24.4-7.el10_2.1 - ci-fix-Pass-interface-string-to-get_newest_lease-6648.patch [RHEL-159033] - ci-fix-cloudstack-Improve-domain-name-DHCP-lease-lookup.patch [RHEL-159033] - ci-downstream-fix-test_cloudstack.py-since-pytest-fixtu.patch [RHEL-159033] - Resolves: RHEL-159033 ([GSS][Secure Support] [RHEL-10] cloud-init requests lease before DHCP can provide one [rhel-10.2.z]) --- ...est_cloudstack.py-since-pytest-fixtu.patch | 96 +++++++++++ ...face-string-to-get_newest_lease-6648.patch | 40 +++++ ...mprove-domain-name-DHCP-lease-lookup.patch | 156 ++++++++++++++++++ cloud-init.spec | 15 +- 4 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 ci-downstream-fix-test_cloudstack.py-since-pytest-fixtu.patch create mode 100644 ci-fix-Pass-interface-string-to-get_newest_lease-6648.patch create mode 100644 ci-fix-cloudstack-Improve-domain-name-DHCP-lease-lookup.patch diff --git a/ci-downstream-fix-test_cloudstack.py-since-pytest-fixtu.patch b/ci-downstream-fix-test_cloudstack.py-since-pytest-fixtu.patch new file mode 100644 index 0000000..2a74b1e --- /dev/null +++ b/ci-downstream-fix-test_cloudstack.py-since-pytest-fixtu.patch @@ -0,0 +1,96 @@ +From 0c0b91bb94308c97514cd0464c0911f7c4b1ddbc Mon Sep 17 00:00:00 2001 +From: Ani Sinha +Date: Mon, 23 Mar 2026 13:50:11 +0530 +Subject: [PATCH 4/4] downstream: fix test_cloudstack.py since pytest fixtures + cannot be used + +RH-Author: Ani Sinha +RH-MergeRequest: 135: fix(cloudstack): Improve domain-name DHCP lease lookup +RH-Jira: RHEL-159033 +RH-Acked-by: xiachen +RH-Acked-by: Miroslav Rezanina +RH-Commit: [3/3] af3ac74a3a2389d1a989571f124c76d33ad264e8 (anisinha/cloud-init) + +pytest.mark.parametrize decorator cannot be used for functions inside classes +that are derived from unittest.TestCase[1]. Here, the test class is derived +from CiTestCase which in turn is derived from TestCase class which is again +itself derived from unittest.TestCase. This was removed from the upstream +commit 589c9461db1 ("Fix: Add Ephemeral Network for CloudStackLocal DS (#6144)") +which we are not backporting. Hence, we change the test code such that the +main test function is called for each of the previously declared parameterized +values. + +This patch should not be needed after a rebase when 589c9461db1 pulled in +through rebase. + +X-downstream-only: true + +1. https://stackoverflow.com/questions/63720118/pytest-parametrize-i-am-getting-missing-required-positional-arguments +Signed-off-by: Ani Sinha +--- + tests/unittests/sources/test_cloudstack.py | 45 ++++++++++++---------- + 1 file changed, 24 insertions(+), 21 deletions(-) + +diff --git a/tests/unittests/sources/test_cloudstack.py b/tests/unittests/sources/test_cloudstack.py +index 0f9870665..f3b08a475 100644 +--- a/tests/unittests/sources/test_cloudstack.py ++++ b/tests/unittests/sources/test_cloudstack.py +@@ -328,31 +328,34 @@ class TestCloudStackHostname(CiTestCase): + result = ds.get_hostname(fqdn=True) + self.assertTupleEqual(expected, result) + +- @pytest.mark.parametrize( +- "lease_key,expected_domain", +- [ +- ("DOMAINNAME", "example.com"), +- ("Domain", "example.com"), +- ("domain-name", "example.com"), +- ], +- ) + def test__get_domainname_supports_all_casing_variants( +- self, lease_key, expected_domain ++ self, + ): + """Ensure _get_domainname works with DOMAINNAME, Domain and + domain-name.""" +- # Mock the helper to return the domain only when the exact key is asked +- with patch( +- "cloudinit.net.dhcp.networkd_get_option_from_leases" +- ) as m_get: +- m_get.side_effect = lambda key, extra_keys=None: ( +- "example.com " if key == lease_key else None +- ) +- +- ds = DataSourceCloudStack( +- {}, distro=MockDistro(), paths=helpers.Paths({}) +- ) +- assert ds._get_domainname() == expected_domain ++ cases = [ ++ ("DOMAINNAME", "example.com"), ++ ("Domain", "example.com"), ++ ("domain-name", "example.com"), ++ ] ++ ++ def testit(lease_key, expected_domain): ++ # Mock the helper to return the domain only when ++ # the exact key is asked ++ with patch( ++ "cloudinit.net.dhcp.networkd_get_option_from_leases" ++ ) as m_get: ++ m_get.side_effect = lambda key, extra_keys=None: ( ++ "example.com " if key == lease_key else None ++ ) ++ ++ ds = DataSourceCloudStack( ++ {}, distro=MockDistro(), paths=helpers.Paths({}) ++ ) ++ assert ds._get_domainname() == expected_domain ++ ++ for lease_key, expected_domain in cases: ++ testit(lease_key, expected_domain) + + + @pytest.mark.usefixtures("dhclient_exists") +-- +2.47.3 + diff --git a/ci-fix-Pass-interface-string-to-get_newest_lease-6648.patch b/ci-fix-Pass-interface-string-to-get_newest_lease-6648.patch new file mode 100644 index 0000000..1bd1976 --- /dev/null +++ b/ci-fix-Pass-interface-string-to-get_newest_lease-6648.patch @@ -0,0 +1,40 @@ +From 20a3d2274134fa7336b3803882004e2f70a756de Mon Sep 17 00:00:00 2001 +From: Leah <76408777+goldberl@users.noreply.github.com> +Date: Tue, 6 Jan 2026 15:01:59 -0500 +Subject: [PATCH 2/4] fix: Pass interface string to get_newest_lease() (#6648) + +RH-Author: Ani Sinha +RH-MergeRequest: 135: fix(cloudstack): Improve domain-name DHCP lease lookup +RH-Jira: RHEL-159033 +RH-Acked-by: xiachen +RH-Acked-by: Miroslav Rezanina +RH-Commit: [1/3] 5f74c14b51432facb96647410ed6a6ec52c99935 (anisinha/cloud-init) + +In DataSourceCloudStack.py, get_newest_lease() is currently being +passed a Distro object, causing a Python type error. This PR +changes the Distro object to the interface string to fix this error. + +(cherry picked from commit e02b739fc094eb8b56391705c2989f4aecbe139b) +Signed-off-by: Ani Sinha +--- + cloudinit/sources/DataSourceCloudStack.py | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py +index 61bf94f51..6d19077cd 100644 +--- a/cloudinit/sources/DataSourceCloudStack.py ++++ b/cloudinit/sources/DataSourceCloudStack.py +@@ -314,7 +314,9 @@ def get_vr_address(distro): + return latest_address + + with suppress(FileNotFoundError): +- latest_lease = distro.dhcp_client.get_newest_lease(distro) ++ latest_lease = distro.dhcp_client.get_newest_lease( ++ distro.fallback_interface ++ ) + if latest_lease: + LOG.debug( + "Found SERVER_ADDRESS '%s' via ephemeral %s lease ", +-- +2.47.3 + diff --git a/ci-fix-cloudstack-Improve-domain-name-DHCP-lease-lookup.patch b/ci-fix-cloudstack-Improve-domain-name-DHCP-lease-lookup.patch new file mode 100644 index 0000000..2cf4777 --- /dev/null +++ b/ci-fix-cloudstack-Improve-domain-name-DHCP-lease-lookup.patch @@ -0,0 +1,156 @@ +From 5b771aa7ba9853f17a051348d0a5ce3e9b945d9f Mon Sep 17 00:00:00 2001 +From: CodeBleu <400979+CodeBleu@users.noreply.github.com> +Date: Tue, 20 Jan 2026 13:52:54 -0500 +Subject: [PATCH 3/4] fix(cloudstack): Improve domain-name DHCP lease lookup + (Cloudstack) (#6554) + +RH-Author: Ani Sinha +RH-MergeRequest: 135: fix(cloudstack): Improve domain-name DHCP lease lookup +RH-Jira: RHEL-159033 +RH-Acked-by: xiachen +RH-Acked-by: Miroslav Rezanina +RH-Commit: [2/3] f9e1f775d40bd402eb10e3e74a4e88045a48036d (anisinha/cloud-init) + +* Adding case-insensitive options for systemd-networkd leases ("DOMAINNAME", "Domain", "domain-name"). +* Falling back gracefully from systemd leases to ISC dhclient leases. +* Including dhcpcd ephemeral leases as an additional fallback. +* Returning an empty string when no domain name found instead of None for non-fatal missing cases. + +(cherry picked from commit d2bf883931723cfdc9f529eba5395d83059d09c6) +Signed-off-by: Ani Sinha +--- + cloudinit/sources/DataSourceCloudStack.py | 50 +++++++++++----------- + tests/unittests/sources/test_cloudstack.py | 27 ++++++++++++ + 2 files changed, 53 insertions(+), 24 deletions(-) + +diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py +index 6d19077cd..98189c23c 100644 +--- a/cloudinit/sources/DataSourceCloudStack.py ++++ b/cloudinit/sources/DataSourceCloudStack.py +@@ -23,6 +23,7 @@ from cloudinit import sources, subp + from cloudinit import url_helper as uhelp + from cloudinit import util + from cloudinit.net import dhcp ++from cloudinit.net.dhcp import NoDHCPLeaseError + from cloudinit.sources.helpers import ec2 + + LOG = logging.getLogger(__name__) +@@ -97,46 +98,47 @@ class DataSourceCloudStack(sources.DataSource): + self.cfg = {} + + def _get_domainname(self): ++ """Try obtaining a "domain-name" DHCP lease parameter: ++ - From systemd-networkd lease (case-insensitive) ++ - From ISC dhclient ++ - From dhcpcd (ephemeral) ++ - Return empty string if not found (non-fatal) + """ +- Try obtaining a "domain-name" DHCP lease parameter: +- - From systemd-networkd lease +- - From dhclient lease +- """ ++ + LOG.debug("Try obtaining domain name from networkd leases") +- domainname = dhcp.networkd_get_option_from_leases("DOMAINNAME") +- if domainname: +- return domainname ++ for key in ["DOMAINNAME", "Domain", "domain-name"]: ++ domainname = dhcp.networkd_get_option_from_leases(key) ++ if domainname: ++ return domainname.strip() ++ + LOG.debug( +- "Could not obtain FQDN from networkd leases. " +- "Falling back to ISC dhclient" ++ "Could not obtain FQDN from networkd leases. Falling back to " ++ "ISC dhclient" + ) +- +- # some distros might use isc-dhclient for network setup via their +- # network manager. If this happens, the lease is more recent than the +- # ephemeral lease, so use it first. + with suppress(dhcp.NoDHCPLeaseMissingDhclientError): + domain_name = dhcp.IscDhclient().get_key_from_latest_lease( + self.distro, "domain-name" + ) + if domain_name: +- return domain_name ++ return domain_name.strip() + + LOG.debug( +- "Could not obtain FQDN from ISC dhclient leases. " +- "Falling back to %s", ++ "Could not obtain FQDN from ISC dhclient leases. Falling back to " ++ "%s", + self.distro.dhcp_client.client_name, + ) +- +- # If no distro leases were found, check the ephemeral lease that +- # cloud-init set up. +- with suppress(FileNotFoundError): ++ try: + latest_lease = self.distro.dhcp_client.get_newest_lease( + self.distro.fallback_interface + ) +- domain_name = latest_lease.get("domain-name") or None +- return domain_name +- LOG.debug("No dhcp leases found") +- return None ++ domain_name = latest_lease.get("domain-name") ++ if domain_name: ++ return domain_name.strip() ++ except (NoDHCPLeaseError, FileNotFoundError, AttributeError): ++ pass ++ ++ LOG.debug("No domain name found in any DHCP lease; returning empty") ++ return "" + + def get_hostname( + self, +diff --git a/tests/unittests/sources/test_cloudstack.py b/tests/unittests/sources/test_cloudstack.py +index a64d80f37..0f9870665 100644 +--- a/tests/unittests/sources/test_cloudstack.py ++++ b/tests/unittests/sources/test_cloudstack.py +@@ -1,5 +1,6 @@ + # This file is part of cloud-init. See LICENSE file for license information. + from textwrap import dedent ++from unittest.mock import patch + + import pytest + +@@ -327,6 +328,32 @@ class TestCloudStackHostname(CiTestCase): + result = ds.get_hostname(fqdn=True) + self.assertTupleEqual(expected, result) + ++ @pytest.mark.parametrize( ++ "lease_key,expected_domain", ++ [ ++ ("DOMAINNAME", "example.com"), ++ ("Domain", "example.com"), ++ ("domain-name", "example.com"), ++ ], ++ ) ++ def test__get_domainname_supports_all_casing_variants( ++ self, lease_key, expected_domain ++ ): ++ """Ensure _get_domainname works with DOMAINNAME, Domain and ++ domain-name.""" ++ # Mock the helper to return the domain only when the exact key is asked ++ with patch( ++ "cloudinit.net.dhcp.networkd_get_option_from_leases" ++ ) as m_get: ++ m_get.side_effect = lambda key, extra_keys=None: ( ++ "example.com " if key == lease_key else None ++ ) ++ ++ ds = DataSourceCloudStack( ++ {}, distro=MockDistro(), paths=helpers.Paths({}) ++ ) ++ assert ds._get_domainname() == expected_domain ++ + + @pytest.mark.usefixtures("dhclient_exists") + class TestCloudStackPasswordFetching(CiTestCase): +-- +2.47.3 + diff --git a/cloud-init.spec b/cloud-init.spec index 7b794da..0ab26cd 100644 --- a/cloud-init.spec +++ b/cloud-init.spec @@ -6,7 +6,7 @@ Name: cloud-init Version: 24.4 -Release: 7%{?dist} +Release: 7%{?dist}.1 Summary: Cloud instance init scripts License: Apache-2.0 OR GPL-3.0-only URL: https://github.com/canonical/cloud-init @@ -32,6 +32,12 @@ Patch11: ci-feat-aliyun-datasource-support-crawl-metadata-at-onc.patch Patch12: ci-fix-Don-t-attempt-to-identify-non-x86-OpenStack-inst.patch # For RHEL-100617 - CVE-2024-6174 cloud-init: From CVEorg collector [rhel-10.1] Patch13: ci-fix-strict-disable-in-ds-identify-on-no-datasources-.patch +# For RHEL-159033 - [GSS][Secure Support] [RHEL-10] cloud-init requests lease before DHCP can provide one [rhel-10.2.z] +Patch14: ci-fix-Pass-interface-string-to-get_newest_lease-6648.patch +# For RHEL-159033 - [GSS][Secure Support] [RHEL-10] cloud-init requests lease before DHCP can provide one [rhel-10.2.z] +Patch15: ci-fix-cloudstack-Improve-domain-name-DHCP-lease-lookup.patch +# For RHEL-159033 - [GSS][Secure Support] [RHEL-10] cloud-init requests lease before DHCP can provide one [rhel-10.2.z] +Patch16: ci-downstream-fix-test_cloudstack.py-since-pytest-fixtu.patch BuildArch: noarch @@ -237,6 +243,13 @@ fi %changelog +* Tue Apr 07 2026 Miroslav Rezanina - 24.4-7.el10_2.1 +- ci-fix-Pass-interface-string-to-get_newest_lease-6648.patch [RHEL-159033] +- ci-fix-cloudstack-Improve-domain-name-DHCP-lease-lookup.patch [RHEL-159033] +- ci-downstream-fix-test_cloudstack.py-since-pytest-fixtu.patch [RHEL-159033] +- Resolves: RHEL-159033 + ([GSS][Secure Support] [RHEL-10] cloud-init requests lease before DHCP can provide one [rhel-10.2.z]) + * Wed Dec 10 2025 Miroslav Rezanina - 24.4-7 - ci-downstream-Do-not-override-changes-in-disable-sshd-k.patch [RHEL-128097] - Resolves: RHEL-128097