Compare commits

...

No commits in common. "c8" and "c9-beta" have entirely different histories.
c8 ... c9-beta

15 changed files with 298 additions and 831 deletions

View File

@ -1 +1 @@
3511edebc20381fc108c92e41218cbfc4ed78fa8 SOURCES/v2.13.1.1.tar.gz
e8f30e34eedc9280531af1bb1af9efb53d1a1039 SOURCES/v2.14.0.1.tar.gz

2
.gitignore vendored
View File

@ -1 +1 @@
SOURCES/v2.13.1.1.tar.gz
SOURCES/v2.14.0.1.tar.gz

View File

@ -1,8 +1,8 @@
From 8ac14e61d8067bd8b4e60c59b35b4a4227f8a242 Mon Sep 17 00:00:00 2001
From ccbdd1a80e22c708a11cd518de8bad3807ae5ada Mon Sep 17 00:00:00 2001
From: Mohammed Gamal <mgamal@redhat.com>
Date: Fri, 29 Jul 2022 13:07:13 +0200
Subject: redhat: Use NetworkManager to set DHCP hostnames on recent RHEL
distros
Subject: [PATCH] redhat: Use NetworkManager to set DHCP hostnames on recent
RHEL distros
RH-Author: Mohamed Gamal Morsy <mmorsy@redhat.com>
RH-MergeRequest: 3: redhat: Use NetworkManager to set DHCP hostnames on recent RHEL distros
@ -48,6 +48,3 @@ index a9a10347..b85b2d42 100644
+
+ if return_code != 0:
+ logger.error("failed to set DHCP hostname for interface {0}: return code {1}".format(ifname, return_code))
--
2.39.3

View File

@ -1,7 +1,7 @@
From 92baa5663a0baa45e37243936221b0c1795ff324 Mon Sep 17 00:00:00 2001
From 97b3d6b1c8051fc24e47b584a4f07a1abce9d232 Mon Sep 17 00:00:00 2001
From: Ani Sinha <anisinha@redhat.com>
Date: Mon, 6 May 2024 11:50:49 +0530
Subject: Disable automatic log collector
Subject: [PATCH] Disable automatic log collector
RH-Author: Ani Sinha <anisinha@redhat.com>
RH-MergeRequest: 11: Disable automatic log collector
@ -38,6 +38,3 @@ index 3c9ad5d4..62d8148e 100644
# How frequently to collect logs, default is each hour
Logs.CollectPeriod=3600
--
2.39.3

View File

@ -1,7 +1,7 @@
From c8df88fd1fedb25727fff64ecc5dde1a59d7a976 Mon Sep 17 00:00:00 2001
From 57d4ae44c3ce666bb1925abe4defc24e400a6454 Mon Sep 17 00:00:00 2001
From: Vitaly Kuznetsov <vkuznets@redhat.com>
Date: Mon, 6 Jan 2025 17:13:11 +0100
Subject: redhat: Add a udev rule to avoid managing slave NICs with
Subject: [PATCH] redhat: Add a udev rule to avoid managing slave NICs with
NetworkManager
RH-Author: Vitaly Kuznetsov <vkuznets@redhat.com>
@ -22,10 +22,9 @@ Patch-name: wla-redhat-Add-a-udev-rule-to-avoid-managing-slave-NICs-.patch
Patch-id:
Patch-present-in-specfile: True
---
.distro/WALinuxAgent.spec | 1 +
config/10-azure-unmanaged-sriov.rules | 6 ++++++
setup.py | 3 ++-
3 files changed, 9 insertions(+), 1 deletion(-)
2 files changed, 8 insertions(+), 1 deletion(-)
create mode 100644 config/10-azure-unmanaged-sriov.rules
diff --git a/config/10-azure-unmanaged-sriov.rules b/config/10-azure-unmanaged-sriov.rules
@ -41,7 +40,7 @@ index 00000000..7eefcb26
+# NM_UNMANAGED=1 for NetworkManager
+SUBSYSTEM=="net", ACTION!="remove", DRIVERS=="mana|mlx4_core|mlx5_core", ATTR{flags}=="0x?[89ABCDEF]??", ENV{AZURE_UNMANAGED_SRIOV}="1", ENV{ID_NET_MANAGED_BY}="unmanaged", ENV{NM_UNMANAGED}="1"
diff --git a/setup.py b/setup.py
index e83f5989..cf6e90b5 100755
index ca5e2d20..6471dba3 100755
--- a/setup.py
+++ b/setup.py
@@ -82,7 +82,8 @@ def set_openbsd_rc_files(data_files, dest="/etc/rc.d/", src=None):
@ -54,6 +53,3 @@ index e83f5989..cf6e90b5 100755
"config/99-azure-product-uuid.rules"]
data_files.append((dest, src))
--
2.39.3

View File

@ -1,4 +1,4 @@
From 756fe22f41c0607394a9b9ba20c15677b3389a21 Mon Sep 17 00:00:00 2001
From 5fb82a76eb6c14b2802e073c497a8c3de143375f Mon Sep 17 00:00:00 2001
From: Li Tian <94442129+litian1992@users.noreply.github.com>
Date: Tue, 5 Aug 2025 03:18:10 +0800
Subject: [PATCH] docs: add waagent manpage (#3401)
@ -20,11 +20,14 @@ Signed-off-by: Li Tian <litian@redhat.com>
Co-authored-by: maddieford <93676569+maddieford@users.noreply.github.com>
(cherry picked from commit 7f9b5568b6572745b7d0776de0ff6fb70a28dba0)
Signed-off-by: Li Tian <litian@redhat.com>
Patch-name: wla-docs-add-waagent-manpage-3401.patch
Patch-id:
Patch-present-in-specfile: True
---
.distro/WALinuxAgent.spec | 1 +
doc/man/waagent.1 | 117 ++++++++++++++++++++++++++++++++++++++
setup.py | 14 +++++
3 files changed, 132 insertions(+)
doc/man/waagent.1 | 117 ++++++++++++++++++++++++++++++++++++++++++++++
setup.py | 14 ++++++
2 files changed, 131 insertions(+)
create mode 100644 doc/man/waagent.1
diff --git a/doc/man/waagent.1 b/doc/man/waagent.1
@ -151,7 +154,7 @@ index 00000000..b1d8e9eb
+.SH AUTHORS
+Microsoft Azure Linux Team
diff --git a/setup.py b/setup.py
index cf6e90b5..9ed135fb 100755
index 6471dba3..30784ca6 100755
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,9 @@
@ -194,9 +197,6 @@ index cf6e90b5..9ed135fb 100755
set_udev_files(data_files)
set_systemd_files(data_files, dest=systemd_dir_path)
+ set_man_files(data_files)
else:
# Use default setting
set_bin_files(data_files, dest=agent_bin_path)
--
2.50.1
elif name == 'chainguard':
set_bin_files(data_files, dest=agent_bin_path, src=["bin/py3/waagent"])
set_conf_files(data_files, src=["config/chainguard/waagent.conf"])

View File

@ -1,4 +1,4 @@
From 846d9f18e2ee331e35a7243f73de3bb3c18875df Mon Sep 17 00:00:00 2001
From f08fe8708565901e1cfe453d5847095aaefb9355 Mon Sep 17 00:00:00 2001
From: Yuxin Sun <yuxisun@redhat.com>
Date: Fri, 27 Jun 2025 01:34:20 +0800
Subject: [PATCH] Use systemctl instead of service to manager services in new
@ -19,6 +19,10 @@ Upstream PR: https://github.com/Azure/WALinuxAgent/pull/3403
(cherry picked from commit a6cfdfdc3e04884a08cd6dd20fa035b687943fe9)
Signed-off-by: Yuxin Sun <yuxisun@redhat.com>
Patch-name: wla-Use-systemctl-instead-of-service-to-manager-services.patch
Patch-id:
Patch-present-in-specfile: True
---
azurelinuxagent/common/osutil/redhat.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
@ -55,6 +59,3 @@ index b85b2d42..cf2d2f78 100644
DefaultOSUtil.publish_hostname(self, hostname)
def set_dhcp_hostname(self, hostname):
--
2.50.1

View File

@ -1,31 +0,0 @@
From 246586f31209b70667b494db30c847c5b87fe7a3 Mon Sep 17 00:00:00 2001
From: Darren Archibald <darren.archibald@oracle.com>
Date: Wed, 21 Sep 2022 05:40:34 -0700
Subject: [PATCH] Add Oracle support
Add oracle support to fix waagent.service build issue
Signed-off-by: Darren Archibald <darren.archibald@oracle.com>
Updated for OL9.7
Signed-off-by: Mark Will <mark.will@oracle.com>
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index cf6e90b..f2a49f1 100755
--- a/setup.py
+++ b/setup.py
@@ -97,7 +97,7 @@ def get_data_files(name, version, fullname): # pylint: disable=R0912
systemd_dir_path = osutil.get_systemd_unit_file_install_path()
agent_bin_path = osutil.get_agent_bin_path()
- if name in ('redhat', 'rhel', 'centos', 'almalinux', 'cloudlinux', 'rocky'):
+ if name in ('redhat', 'rhel', 'centos', 'almalinux', 'cloudlinux', 'rocky', 'oracle'):
if version.startswith("8") or version.startswith("9"):
# redhat8+ default to py3
set_bin_files(data_files, dest=agent_bin_path,
--
2.27.0

View File

@ -13,5 +13,5 @@ depends() {
# called by dracut
install() {
inst_multiple cut readlink chmod
inst_rules 10-azure-unmanaged-sriov.rules 66-azure-storage.rules 99-azure-product-uuid.rules
inst_rules 66-azure-storage.rules 99-azure-product-uuid.rules
}

0
SOURCES/tpm2-luks-import.sh Normal file → Executable file
View File

View File

@ -0,0 +1,153 @@
From d316428f569ddc35561c86e29e1d4b14423b239d Mon Sep 17 00:00:00 2001
From: Yuxin Sun <yuxisun@redhat.com>
Date: Fri, 8 Aug 2025 04:11:48 +0800
Subject: [PATCH] Change redhat waagent-network-setup.service path to /etc
(#3392)
RH-Author: yuxisun <None>
RH-MergeRequest: 27: [c9s]Change redhat waagent-network-setup.service path to /etc (#3392)
RH-Jira: RHEL-82232
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [1/1] a975e122bba51e666a762c4967fa4db559b7e027 (yuxisun/centos_src_WALinuxAgent)
JIRA: https://issues.redhat.com/browse/RHEL-82232
In image mode, the /usr path is readonly. WALA intends to write a /usr/lib/systemd/system/waagent-network-setup.service file to setup the persistent firewall rules, which is not permitted in image mode. So we change the service file path to /etc/systemd/system/waagent-network-setup.service to resolve this issue.
Upstream PR: https://github.com/Azure/WALinuxAgent/pull/3392
Signed-off-by: Yuxin Sun <yuxisun@redhat.com>
Co-authored-by: Norberto Arrieta <narrieta@users.noreply.github.com>
Co-authored-by: maddieford <93676569+maddieford@users.noreply.github.com>
(cherry picked from commit 1d8e1c915b9551d8cd0587f32a80e66fe153c9da)
Signed-off-by: Yuxin Sun <yuxisun@redhat.com>
---
azurelinuxagent/common/osutil/default.py | 4 ++++
azurelinuxagent/common/osutil/redhat.py | 12 +++++++++++
azurelinuxagent/ga/persist_firewall_rules.py | 22 ++++++++++++++++----
tests/ga/test_persist_firewall_rules.py | 2 +-
4 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py
index 9eeb0da8..a8830ff3 100644
--- a/azurelinuxagent/common/osutil/default.py
+++ b/azurelinuxagent/common/osutil/default.py
@@ -123,6 +123,10 @@ class DefaultOSUtil(object):
def get_systemd_unit_file_install_path():
return "/lib/systemd/system"
+ @classmethod
+ def get_network_setup_service_install_path(cls):
+ return cls.get_systemd_unit_file_install_path()
+
@staticmethod
def get_agent_bin_path():
return "/usr/sbin"
diff --git a/azurelinuxagent/common/osutil/redhat.py b/azurelinuxagent/common/osutil/redhat.py
index cf2d2f78..a80fc889 100644
--- a/azurelinuxagent/common/osutil/redhat.py
+++ b/azurelinuxagent/common/osutil/redhat.py
@@ -103,6 +103,18 @@ class RedhatOSUtil(Redhat6xOSUtil):
def get_systemd_unit_file_install_path():
return "/usr/lib/systemd/system"
+ @classmethod
+ def get_network_setup_service_install_path(cls):
+ """
+ In image mode, /usr is readonly, so the
+ waagent-network-setup.service is written in /etc/systemd/system.
+ In non-image mode, the default location is /usr/lib/systemd/system.
+ """
+ if os.path.exists('/run/ostree-booted'):
+ return "/etc/systemd/system"
+ else:
+ return cls.get_systemd_unit_file_install_path()
+
def set_hostname(self, hostname):
"""
Unlike redhat 6.x, redhat 7.x will set hostname via hostnamectl
diff --git a/azurelinuxagent/ga/persist_firewall_rules.py b/azurelinuxagent/ga/persist_firewall_rules.py
index 1f80d272..a876de2c 100644
--- a/azurelinuxagent/ga/persist_firewall_rules.py
+++ b/azurelinuxagent/ga/persist_firewall_rules.py
@@ -27,6 +27,7 @@ from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.osutil import get_osutil, systemd
from azurelinuxagent.common.utils import shellutil, fileutil, textutil
from azurelinuxagent.common.utils.shellutil import CommandError
+from azurelinuxagent.common.version import get_distro
class PersistFirewallRulesHandler(object):
@@ -71,23 +72,25 @@ if __name__ == '__main__':
# modify the unit file on VM too
_UNIT_VERSION = "1.4"
+ _DISTRO = get_distro()[0]
+
@staticmethod
def get_service_file_path():
osutil = get_osutil()
service_name = PersistFirewallRulesHandler._AGENT_NETWORK_SETUP_NAME_FORMAT.format(osutil.get_service_name())
- return os.path.join(osutil.get_systemd_unit_file_install_path(), service_name)
+ return os.path.join(osutil.get_network_setup_service_install_path(), service_name)
def __init__(self, dst_ip):
"""
This class deals with ensuring that Firewall rules are persisted over system reboots.
It tries to employ using Firewalld.service if present first as it already has provisions for persistent rules.
- If not, it then creates a new agent-network-setup.service file and copy it over to the osutil.get_systemd_unit_file_install_path() dynamically
+ If not, it then creates a new agent-network-setup.service file and copy it over to the osutil.get_network_setup_service_install_path() dynamically
On top of it, on every service restart it ensures that the WireIP is overwritten and the new IP is blocked as well.
"""
osutil = get_osutil()
self._network_setup_service_name = self._AGENT_NETWORK_SETUP_NAME_FORMAT.format(osutil.get_service_name())
self._is_systemd = systemd.is_systemd()
- self._systemd_file_path = osutil.get_systemd_unit_file_install_path()
+ self._systemd_file_path = osutil.get_network_setup_service_install_path()
self._dst_ip = dst_ip
# The custom service will try to call the current agent executable to setup the firewall rules
self._current_agent_executable_path = os.path.join(os.getcwd(), sys.argv[0])
@@ -118,7 +121,7 @@ if __name__ == '__main__':
def setup(self):
if not self._is_firewall_service_running():
- logger.info("Firewalld service not running/unavailable, trying to set up {0}".format(self._network_setup_service_name))
+ logger.info("Firewalld service not running/unavailable, trying to set up {0}".format(self.get_service_file_path()))
if systemd.is_systemd():
self._setup_network_setup_service()
else:
@@ -176,6 +179,17 @@ if __name__ == '__main__':
# This is to handle the case where WireIP can change midway on service restarts.
# Additionally, incase of auto-update this would also update the location of the new EGG file ensuring that
# the service is always run from the most latest agent.
+
+ # If RHEL and in image mode, we need to clean up the service file in old path
+ if self._DISTRO == 'rhel' and os.path.exists('/run/ostree-booted'):
+ old_service_file_path = os.path.join('/usr/lib/systemd/system/', self._network_setup_service_name)
+ if os.path.exists(old_service_file_path):
+ logger.info("Cleaning up old service file in image mode: {0}".format(old_service_file_path))
+ try:
+ fileutil.rm_files(old_service_file_path)
+ except Exception as error:
+ logger.warn("Unable to delete old service in image mode {0}: {1}".format(self._network_setup_service_name, ustr(error)))
+
self.__setup_binary_file()
network_service_enabled = self.__verify_network_setup_service_enabled()
diff --git a/tests/ga/test_persist_firewall_rules.py b/tests/ga/test_persist_firewall_rules.py
index 18b302f8..1f3f7e56 100644
--- a/tests/ga/test_persist_firewall_rules.py
+++ b/tests/ga/test_persist_firewall_rules.py
@@ -76,7 +76,7 @@ class TestPersistFirewallRulesHandler(AgentTestCase):
osutil = DefaultOSUtil()
osutil.get_agent_bin_path = MagicMock(return_value=self.__agent_bin_dir)
- osutil.get_systemd_unit_file_install_path = MagicMock(return_value=self.__systemd_dir)
+ osutil.get_network_setup_service_install_path = MagicMock(return_value=self.__systemd_dir)
self._expected_service_name = PersistFirewallRulesHandler._AGENT_NETWORK_SETUP_NAME_FORMAT.format(
osutil.get_service_name())
--
2.47.3

View File

@ -1,14 +1,14 @@
From cbf30e0eebbedc5242d03f53d355113a53209635 Mon Sep 17 00:00:00 2001
From 950482d0e2147546dbb00d064937e6375bf860d4 Mon Sep 17 00:00:00 2001
From: Chris Patterson <cpatterson@microsoft.com>
Date: Thu, 1 Sep 2022 10:45:47 -0400
Subject: [PATCH] Jira: https://issues.redhat.com/browse/RHEL-134939
Subject: [PATCH] Jira: https://issues.redhat.com/browse/RHEL-133507
RH-Author: yuxisun <None>
RH-MergeRequest: 25: waagent.service: set ConditionVirtualization=|microsoft
RH-Jira: RHEL-134939
RH-Jira: RHEL-133507
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [1/1] 95d939cb838949a4509bd9415873fbdc7e7191f3
RH-Commit: [1/1] 667b85d61146b144ba24baf41156ae32e301b194 (yuxisun/centos_src_WALinuxAgent)
waagent.service: set ConditionVirtualization=|microsoft
@ -18,7 +18,7 @@ Set it as a triggering condition to make it easier for downstreams or
test setups to add another condition (i.e. run outside of hyperv).
Signed-off-by: Chris Patterson <cpatterson@microsoft.com>
(cherry picked from commit 2d78c9ebaba4742390e92dc5994391949b90ec4c)
(cherry picked from commit d1fad5317e2dac9fc82d9c56d2d016e20db5f7a2)
Downstream only

View File

@ -0,0 +1,50 @@
From bd24da8a9ec066f354a10581eecbc8b85617adf8 Mon Sep 17 00:00:00 2001
From: Yuxin Sun <yuxisun@redhat.com>
Date: Fri, 31 Oct 2025 17:49:02 +0800
Subject: [PATCH] Remove 10-azure-unmanaged-sriov.rules
RH-Author: yuxisun <None>
RH-MergeRequest: 24: Remove 10-azure-unmanaged-sriov.rules
RH-Jira: RHEL-124218
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
RH-Commit: [1/1] 4223f72f9f68ba9182c0b05ed76209930a8071de (yuxisun/centos_src_WALinuxAgent)
Signed-off-by: Yuxin Sun <yuxisun@redhat.com>
---
.distro/WALinuxAgent.spec | 2 +-
.distro/sources/module-setup-udev.sh | 2 +-
config/10-azure-unmanaged-sriov.rules | 6 ------
setup.py | 3 +--
4 files changed, 3 insertions(+), 10 deletions(-)
delete mode 100644 config/10-azure-unmanaged-sriov.rules
diff --git a/config/10-azure-unmanaged-sriov.rules b/config/10-azure-unmanaged-sriov.rules
deleted file mode 100644
index 7eefcb26..00000000
--- a/config/10-azure-unmanaged-sriov.rules
+++ /dev/null
@@ -1,6 +0,0 @@
-# Azure VMs with accelerated networking may have MANA, mlx4, or mlx5 SR-IOV devices which are transparently bonded to a synthetic
-# hv_netvsc device. Mark devices with the IFF_SLAVE bit set as unmanaged devices:
-# AZURE_UNMANAGED_SRIOV=1 for 10-azure-unmanaged-sriov.network
-# ID_NET_MANAGED_BY=unmanaged for systemd-networkd >= 255
-# NM_UNMANAGED=1 for NetworkManager
-SUBSYSTEM=="net", ACTION!="remove", DRIVERS=="mana|mlx4_core|mlx5_core", ATTR{flags}=="0x?[89ABCDEF]??", ENV{AZURE_UNMANAGED_SRIOV}="1", ENV{ID_NET_MANAGED_BY}="unmanaged", ENV{NM_UNMANAGED}="1"
diff --git a/setup.py b/setup.py
index 30784ca6..c8e77988 100755
--- a/setup.py
+++ b/setup.py
@@ -84,8 +84,7 @@ def set_openbsd_rc_files(data_files, dest="/etc/rc.d/", src=None):
def set_udev_files(data_files, dest="/etc/udev/rules.d/", src=None):
if src is None:
- src = ["config/10-azure-unmanaged-sriov.rules",
- "config/66-azure-storage.rules",
+ src = ["config/66-azure-storage.rules",
"config/99-azure-product-uuid.rules"]
data_files.append((dest, src))
--
2.47.3

View File

@ -1,704 +0,0 @@
From c446f444a897fc7094a5ce00bd77bb430c79d8ea Mon Sep 17 00:00:00 2001
From: Norberto Arrieta <narrieta@users.noreply.github.com>
Date: Tue, 4 Mar 2025 12:55:27 -0800
Subject: [PATCH] Support for FIPS 140-3 (#3324)
RH-Author: yuxisun <None>
RH-MergeRequest: 24: Support for FIPS 140-3 (#3324)
RH-Jira: RHEL-124949
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
RH-Acked-by: Jon Maloy <jmaloy@redhat.com>
RH-Commit: [1/1] 3aadd91d56764017d13d8dc2cdada02551a7deff
Jira: https://issues.redhat.com/browse/RHEL-124949
When fetching certificates from WireServer, the Agent uses DES_EDE3_CBC. The PFX it receives has a MAC computed using PKCS12KDF. Both are deprecated on FIPS 140-3. This PR switches to AES128_CBC for communication with the WireServer (a subsequent PR will change it to AES256_CBC) and skips MAC verification when it is not needed. The changes also include some minor cleanup to remove data structures that are not used.
Upstream PR: https://github.com/Azure/WALinuxAgent/pull/3324
Signed-off-by: Yuxin Sun yuxisun@redhat.com
---
azurelinuxagent/common/event.py | 20 ++
azurelinuxagent/common/protocol/goal_state.py | 216 +++++++++++-------
azurelinuxagent/common/protocol/restapi.py | 24 --
azurelinuxagent/common/protocol/wire.py | 18 +-
azurelinuxagent/common/utils/cryptutil.py | 46 ++--
azurelinuxagent/ga/update.py | 1 +
tests/common/protocol/test_goal_state.py | 81 ++++++-
tests/common/protocol/test_hostplugin.py | 35 ++-
tests/common/protocol/test_wire.py | 8 +-
tests/ga/test_update.py | 2 +-
10 files changed, 275 insertions(+), 176 deletions(-)
diff --git a/azurelinuxagent/common/event.py b/azurelinuxagent/common/event.py
index 6b9521ca..9b8a926e 100644
--- a/azurelinuxagent/common/event.py
+++ b/azurelinuxagent/common/event.py
@@ -93,6 +93,7 @@ class WALAEventOperation:
FetchGoalState = "FetchGoalState"
Firewall = "Firewall"
GoalState = "GoalState"
+ GoalStateCertificates = "GoalStateCertificates"
GoalStateUnsupportedFeatures = "GoalStateUnsupportedFeatures"
HealthCheck = "HealthCheck"
HealthObservation = "HealthObservation"
@@ -733,6 +734,25 @@ def error(op, fmt, *args):
add_event(op=op, message=fmt.format(*args), is_success=False, log_event=False)
+class LogEvent(object):
+ """
+ Helper class that allows the use of info()/warn()/error() using a specific instance of a logger.
+ """
+ def __init__(self, logger_):
+ self._logger = logger_
+
+ def info(self, op, fmt, *args):
+ self._logger.info(fmt, *args)
+ add_event(op=op, message=fmt.format(*args), is_success=True)
+
+ def warn(self, op, fmt, *args):
+ self._logger.warn(fmt, *args)
+ add_event(op=op, message="[WARNING] " + fmt.format(*args), is_success=False, log_event=False)
+
+ def error(self, op, fmt, *args):
+ self._logger.error(fmt, *args)
+ add_event(op=op, message=fmt.format(*args), is_success=False, log_event=False)
+
def add_log_event(level, message, forced=False, reporter=__event_logger__):
"""
:param level: LoggerLevel of the log event
diff --git a/azurelinuxagent/common/protocol/goal_state.py b/azurelinuxagent/common/protocol/goal_state.py
index f94f3ae5..2556cc73 100644
--- a/azurelinuxagent/common/protocol/goal_state.py
+++ b/azurelinuxagent/common/protocol/goal_state.py
@@ -24,15 +24,14 @@ import json
from azurelinuxagent.common import conf
from azurelinuxagent.common import logger
from azurelinuxagent.common.AgentGlobals import AgentGlobals
-from azurelinuxagent.common.datacontract import set_properties
-from azurelinuxagent.common.event import add_event, WALAEventOperation
+from azurelinuxagent.common.event import add_event, WALAEventOperation, LogEvent
from azurelinuxagent.common.exception import ProtocolError, ResourceGoneError
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.protocol.extensions_goal_state_factory import ExtensionsGoalStateFactory
from azurelinuxagent.common.protocol.extensions_goal_state import VmSettingsParseError, GoalStateSource
from azurelinuxagent.common.protocol.hostplugin import VmSettingsNotSupported, VmSettingsSupportStopped
-from azurelinuxagent.common.protocol.restapi import Cert, CertList, RemoteAccessUser, RemoteAccessUsersList, ExtHandlerPackage, ExtHandlerPackageList
-from azurelinuxagent.common.utils import fileutil
+from azurelinuxagent.common.protocol.restapi import RemoteAccessUser, RemoteAccessUsersList, ExtHandlerPackage, ExtHandlerPackageList
+from azurelinuxagent.common.utils import fileutil, shellutil
from azurelinuxagent.common.utils.archive import GoalStateHistory, SHARED_CONF_FILE_NAME
from azurelinuxagent.common.utils.cryptutil import CryptUtil
from azurelinuxagent.common.utils.textutil import parse_doc, findall, find, findtext, getattrib, gettext
@@ -41,6 +40,7 @@ from azurelinuxagent.common.utils.textutil import parse_doc, findall, find, find
GOAL_STATE_URI = "http://{0}/machine/?comp=goalstate"
CERTS_FILE_NAME = "Certificates.xml"
P7M_FILE_NAME = "Certificates.p7m"
+PFX_FILE_NAME = "Certificates.pfx"
PEM_FILE_NAME = "Certificates.pem"
TRANSPORT_CERT_FILE_NAME = "TransportCert.pem"
TRANSPORT_PRV_FILE_NAME = "TransportPrivate.pem"
@@ -282,16 +282,8 @@ class GoalState(object):
self._check_and_download_missing_certs_on_disk()
def _download_certificates(self, certs_uri):
- xml_text = self._wire_client.fetch_config(certs_uri, self._wire_client.get_header_for_cert())
- certs = Certificates(xml_text, self.logger)
- # Log and save the certificates summary (i.e. the thumbprint but not the certificate itself) to the goal state history
- for c in certs.summary:
- message = "Downloaded certificate {0}".format(c)
- self.logger.info(message)
- add_event(op=WALAEventOperation.GoalState, message=message)
- if len(certs.warnings) > 0:
- self.logger.warn(certs.warnings)
- add_event(op=WALAEventOperation.GoalState, message=certs.warnings)
+ certs = Certificates(self._wire_client, certs_uri, self.logger)
+ # Save the certificates summary (i.e. the thumbprints but not the certificates themselves) to the goal state history
if self._save_to_history:
self._history.save_certificates(json.dumps(certs.summary))
return certs
@@ -511,31 +503,83 @@ class SharedConfig(object):
self.xml_text = xml_text
-class Certificates(object):
- def __init__(self, xml_text, my_logger):
- self.cert_list = CertList()
- self.summary = [] # debugging info
- self.warnings = []
+class Certificates(LogEvent):
+ def __init__(self, wire_client, uri, logger_):
+ super(Certificates, self).__init__(logger_)
+ self.summary = []
+ self._crypt_util = CryptUtil(conf.get_openssl_cmd())
- # Save the certificates
- local_file = os.path.join(conf.get_lib_dir(), CERTS_FILE_NAME)
- fileutil.write_file(local_file, xml_text)
+ try:
+ pfx_file = self._download_certificates_pfx(wire_client, uri)
+ if pfx_file is None: # The response from the WireServer may not have any certificates
+ return
- # Separate the certificates into individual files.
- xml_doc = parse_doc(xml_text)
- data = findtext(xml_doc, "Data")
- if data is None:
- return
+ try:
+ pem_file = self._convert_certificates_pfx_to_pem(pfx_file)
+ finally:
+ self._remove_file(pfx_file)
- # if the certificates format is not Pkcs7BlobWithPfxContents do not parse it
- certificate_format = findtext(xml_doc, "Format")
- if certificate_format and certificate_format != "Pkcs7BlobWithPfxContents":
- message = "The Format is not Pkcs7BlobWithPfxContents. Format is {0}".format(certificate_format)
- my_logger.warn(message)
- add_event(op=WALAEventOperation.GoalState, message=message)
- return
+ self.summary = self._extract_certificate(pem_file)
+
+ for c in self.summary:
+ self.info(WALAEventOperation.GoalStateCertificates, "Downloaded certificate {0}", c)
+
+ except Exception as e:
+ self.error(WALAEventOperation.GoalStateCertificates, "Error fetching the goal state certificates: {0}", ustr(e))
+
+ def _remove_file(self, file):
+ if os.path.exists(file):
+ try:
+ os.remove(file)
+ except Exception as e:
+ self.warn(WALAEventOperation.GoalStateCertificates, "Failed to remove {0}: {1}", file, ustr(e))
+
+ def _download_certificates_pfx(self, wire_client, uri):
+ """
+ Downloads the certificates from the WireServer and saves them to a pfx file.
+ Returns the full path of the pfx file, or None, if the WireServer response does not have a "Data" element
+ """
+ trans_prv_file = os.path.join(conf.get_lib_dir(), TRANSPORT_PRV_FILE_NAME)
+ trans_cert_file = os.path.join(conf.get_lib_dir(), TRANSPORT_CERT_FILE_NAME)
+ xml_file = os.path.join(conf.get_lib_dir(), CERTS_FILE_NAME)
+ pfx_file = os.path.join(conf.get_lib_dir(), PFX_FILE_NAME)
+
+ for cypher in ["AES128_CBC", "DES_EDE3_CBC"]:
+ headers = wire_client.get_headers_for_encrypted_request(cypher)
+
+ try:
+ xml_text = wire_client.fetch_config(uri, headers)
+ except Exception as e:
+ self.warn(WALAEventOperation.GoalStateCertificates, "Error in Certificates request [cypher: {0}]: {1}", cypher, ustr(e))
+ continue
- cryptutil = CryptUtil(conf.get_openssl_cmd())
+ fileutil.write_file(xml_file, xml_text)
+
+ xml_doc = parse_doc(xml_text)
+ data = findtext(xml_doc, "Data")
+ if data is None:
+ self.info(WALAEventOperation.GoalStateCertificates, "The Data element of the Certificates response is empty")
+ return None
+ certificate_format = findtext(xml_doc, "Format")
+ if certificate_format and certificate_format != "Pkcs7BlobWithPfxContents":
+ self.warn(WALAEventOperation.GoalStateCertificates, "The Certificates format is not Pkcs7BlobWithPfxContents; skipping. Format is {0}", certificate_format)
+ return None
+
+ p7m_file = Certificates._create_p7m_file(data)
+
+ try:
+ self._crypt_util.decrypt_certificates_p7m(p7m_file, trans_prv_file, trans_cert_file, pfx_file)
+ except shellutil.CommandError as e:
+ self.warn(WALAEventOperation.GoalState, "Error in transport decryption [cypher: {0}]: {1}", cypher, ustr(e))
+ self._remove_file(pfx_file)
+ continue
+
+ return pfx_file
+
+ raise Exception("Cannot download certificates using any of the supported cyphers")
+
+ @staticmethod
+ def _create_p7m_file(data):
p7m_file = os.path.join(conf.get_lib_dir(), P7M_FILE_NAME)
p7m = ("MIME-Version:1.0\n" # pylint: disable=W1308
"Content-Disposition: attachment; filename=\"{0}\"\n"
@@ -543,68 +587,72 @@ class Certificates(object):
"Content-Transfer-Encoding: base64\n"
"\n"
"{2}").format(p7m_file, p7m_file, data)
-
fileutil.write_file(p7m_file, p7m)
+ return p7m_file
- trans_prv_file = os.path.join(conf.get_lib_dir(), TRANSPORT_PRV_FILE_NAME)
- trans_cert_file = os.path.join(conf.get_lib_dir(), TRANSPORT_CERT_FILE_NAME)
+ def _convert_certificates_pfx_to_pem(self, pfx_file):
+ """
+ Convert the pfx file to pem file.
+ """
pem_file = os.path.join(conf.get_lib_dir(), PEM_FILE_NAME)
- # decrypt certificates
- cryptutil.decrypt_p7m(p7m_file, trans_prv_file, trans_cert_file, pem_file)
+ for nomacver in [True, False]:
+ try:
+ self._crypt_util.convert_pfx_to_pem(pfx_file, nomacver, pem_file)
+ return pem_file
+ except shellutil.CommandError as e:
+ self._remove_file(pem_file) # An error may leave an empty pem file, which can produce a failure on some versions of open SSL (e.g. 3.2.2) on the next invocation
+ self.warn(WALAEventOperation.GoalState, "Error converting PFX to PEM [-nomacver: {0}]: {1}", nomacver, ustr(e))
+ continue
+
+ raise Exception("Cannot convert PFX to PEM")
+
+ def _extract_certificate(self, pem_file):
+ """
+ Parse the certificates and private keys from the pem file and store them in the certificates directory.
+ """
# The parsing process use public key to match prv and crt.
- buf = []
- prvs = {}
- thumbprints = {}
+ private_keys = {} # map of private keys indexed by public key
+ thumbprints = {} # map of thumbprints indexed by public key
+ buffer = [] # buffer for reading lines belonging to a certificate or private key
index = 0
- v1_cert_list = []
-
- # Ensure pem_file exists before read the certs data since decrypt_p7m may clear the pem_file wen decryption fails
- if os.path.exists(pem_file):
- with open(pem_file) as pem:
- for line in pem.readlines():
- buf.append(line)
- if re.match(r'[-]+END.*KEY[-]+', line):
- tmp_file = Certificates._write_to_tmp_file(index, 'prv', buf)
- pub = cryptutil.get_pubkey_from_prv(tmp_file)
- prvs[pub] = tmp_file
- buf = []
- index += 1
- elif re.match(r'[-]+END.*CERTIFICATE[-]+', line):
- tmp_file = Certificates._write_to_tmp_file(index, 'crt', buf)
- pub = cryptutil.get_pubkey_from_crt(tmp_file)
- thumbprint = cryptutil.get_thumbprint_from_crt(tmp_file)
- thumbprints[pub] = thumbprint
- # Rename crt with thumbprint as the file name
- crt = "{0}.crt".format(thumbprint)
- v1_cert_list.append({
- "name": None,
- "thumbprint": thumbprint
- })
- os.rename(tmp_file, os.path.join(conf.get_lib_dir(), crt))
- buf = []
- index += 1
+
+ with open(pem_file) as pem:
+ for line in pem.readlines():
+ buffer.append(line)
+ if re.match(r'[-]+END.*KEY[-]+', line):
+ tmp_file = Certificates._write_to_tmp_file(index, 'prv', buffer)
+ pub = self._crypt_util.get_pubkey_from_prv(tmp_file)
+ private_keys[pub] = tmp_file
+ buffer = []
+ index += 1
+ elif re.match(r'[-]+END.*CERTIFICATE[-]+', line):
+ tmp_file = Certificates._write_to_tmp_file(index, 'crt', buffer)
+ pub = self._crypt_util.get_pubkey_from_crt(tmp_file)
+ thumbprint = self._crypt_util.get_thumbprint_from_crt(tmp_file)
+ thumbprints[pub] = thumbprint
+ # Rename crt with thumbprint as the file name
+ crt = "{0}.crt".format(thumbprint)
+ os.rename(tmp_file, os.path.join(conf.get_lib_dir(), crt))
+ buffer = []
+ index += 1
# Rename prv key with thumbprint as the file name
- for pubkey in prvs:
+ for pubkey in private_keys:
thumbprint = thumbprints[pubkey]
if thumbprint:
- tmp_file = prvs[pubkey]
+ tmp_file = private_keys[pubkey]
prv = "{0}.prv".format(thumbprint)
os.rename(tmp_file, os.path.join(conf.get_lib_dir(), prv))
else:
- # Since private key has *no* matching certificate,
- # it will not be named correctly
- self.warnings.append("Found NO matching cert/thumbprint for private key!")
+ # Since private key has *no* matching certificate, it will not be named correctly
+ self.warn(WALAEventOperation.GoalState, "Found a private key with no matching cert/thumbprint!")
+ certificates = []
for pubkey, thumbprint in thumbprints.items():
- has_private_key = pubkey in prvs
- self.summary.append({"thumbprint": thumbprint, "hasPrivateKey": has_private_key})
-
- for v1_cert in v1_cert_list:
- cert = Cert()
- set_properties("certs", cert, v1_cert)
- self.cert_list.certificates.append(cert)
+ has_private_key = pubkey in private_keys
+ certificates.append({"thumbprint": thumbprint, "hasPrivateKey": has_private_key})
+ return certificates
@staticmethod
def _write_to_tmp_file(index, suffix, buf):
@@ -614,9 +662,7 @@ class Certificates(object):
class EmptyCertificates:
def __init__(self):
- self.cert_list = CertList()
- self.summary = [] # debugging info
- self.warnings = []
+ self.summary = []
class RemoteAccess(object):
"""
diff --git a/azurelinuxagent/common/protocol/restapi.py b/azurelinuxagent/common/protocol/restapi.py
index 54e020c1..7e563b4a 100644
--- a/azurelinuxagent/common/protocol/restapi.py
+++ b/azurelinuxagent/common/protocol/restapi.py
@@ -43,30 +43,6 @@ class VMInfo(DataContract):
self.tenantName = tenantName
-class CertificateData(DataContract):
- def __init__(self, certificateData=None):
- self.certificateData = certificateData
-
-
-class Cert(DataContract):
- def __init__(self,
- name=None,
- thumbprint=None,
- certificateDataUri=None,
- storeName=None,
- storeLocation=None):
- self.name = name
- self.thumbprint = thumbprint
- self.certificateDataUri = certificateDataUri
- self.storeLocation = storeLocation
- self.storeName = storeName
-
-
-class CertList(DataContract):
- def __init__(self):
- self.certificates = DataContractList(Cert)
-
-
class VMAgentFamily(object):
def __init__(self, name):
self.name = name
diff --git a/azurelinuxagent/common/protocol/wire.py b/azurelinuxagent/common/protocol/wire.py
index 00a01f09..0277b7f0 100644
--- a/azurelinuxagent/common/protocol/wire.py
+++ b/azurelinuxagent/common/protocol/wire.py
@@ -115,8 +115,7 @@ class WireProtocol(DataContract):
return vminfo
def get_certs(self):
- certificates = self.client.get_certs()
- return certificates.cert_list
+ return self.client.get_certs()
def get_goal_state(self):
return self.client.get_goal_state()
@@ -1140,13 +1139,11 @@ class WireClient(object):
"Content-Type": "text/xml;charset=utf-8"
}
- def get_header_for_cert(self):
- return self._get_header_for_encrypted_request("DES_EDE3_CBC")
-
def get_header_for_remote_access(self):
- return self._get_header_for_encrypted_request("AES128_CBC")
+ return self.get_headers_for_encrypted_request("AES128_CBC")
- def _get_header_for_encrypted_request(self, cypher):
+ @staticmethod
+ def get_headers_for_encrypted_request(cypher):
trans_cert_file = os.path.join(conf.get_lib_dir(), TRANSPORT_CERT_FILE_NAME)
try:
content = fileutil.read_file(trans_cert_file)
@@ -1154,12 +1151,15 @@ class WireClient(object):
raise ProtocolError("Failed to read {0}: {1}".format(trans_cert_file, e))
cert = get_bytes_from_pem(content)
- return {
+ headers = {
"x-ms-agent-name": "WALinuxAgent",
"x-ms-version": PROTOCOL_VERSION,
- "x-ms-cipher-name": cypher,
"x-ms-guest-agent-public-x509-cert": cert
}
+ if cypher is not None: # the cypher header is optional, currently defaults to AES128_CBC
+ headers["x-ms-cipher-name"] = cypher
+
+ return headers
def get_host_plugin(self):
if self._host_plugin is None:
diff --git a/azurelinuxagent/common/utils/cryptutil.py b/azurelinuxagent/common/utils/cryptutil.py
index 00126e25..789a9486 100644
--- a/azurelinuxagent/common/utils/cryptutil.py
+++ b/azurelinuxagent/common/utils/cryptutil.py
@@ -86,36 +86,22 @@ class CryptUtil(object):
thumbprint = thumbprint.rstrip().split('=')[1].replace(':', '').upper()
return thumbprint
- def decrypt_p7m(self, p7m_file, trans_prv_file, trans_cert_file, pem_file):
-
- def _cleanup_files(files_to_cleanup):
- for file_path in files_to_cleanup:
- if os.path.exists(file_path):
- try:
- os.remove(file_path)
- logger.info("Removed file {0}", file_path)
- except Exception as e:
- logger.error("Failed to remove file {0}: {1}", file_path, ustr(e))
-
- if not os.path.exists(p7m_file):
- raise IOError(errno.ENOENT, "File not found", p7m_file)
- elif not os.path.exists(trans_prv_file):
- raise IOError(errno.ENOENT, "File not found", trans_prv_file)
- else:
- try:
- shellutil.run_pipe([
- [self.openssl_cmd, "cms", "-decrypt", "-in", p7m_file, "-inkey", trans_prv_file, "-recip", trans_cert_file],
- [self.openssl_cmd, "pkcs12", "-nodes", "-password", "pass:", "-out", pem_file]])
- except shellutil.CommandError as command_error:
- logger.error("Failed to decrypt {0} (return code: {1})\n[stdout]\n{2}\n[stderr]\n{3}",
- p7m_file, command_error.returncode, command_error.stdout, command_error.stderr)
- # If the decryption fails, old version of openssl overwrite the output file(if exist) with empty data while
- # new version of openssl(3.2.2) does not overwrite the output file, So output file may contain old certs data.
- # Correcting the behavior by removing the temporary output files since having empty/no data is makes sense when decryption fails
- # otherwise we end up processing old certs again.
- files_to_remove = [p7m_file, pem_file]
- logger.info("Removing temporary state certificate files {0}", files_to_remove)
- _cleanup_files(files_to_remove)
+ def decrypt_certificates_p7m(self, p7m_file, trans_prv_file, trans_cert_file, pfx_file):
+ umask = None
+ try:
+ umask = os.umask(0o077)
+ with open(pfx_file, "wb") as pfx_file_:
+ shellutil.run_command([self.openssl_cmd, "cms", "-decrypt", "-in", p7m_file, "-inkey", trans_prv_file, "-recip", trans_cert_file], stdout=pfx_file_)
+ finally:
+ if umask is not None:
+ os.umask(umask)
+
+ def convert_pfx_to_pem(self, pfx_file, nomacver, pem_file):
+ command = [self.openssl_cmd, "pkcs12", "-nodes", "-password", "pass:", "-in", pfx_file, "-out", pem_file]
+ if nomacver:
+ command.append("-nomacver")
+
+ shellutil.run_command(command)
def crt_to_ssh(self, input_file, output_file):
with open(output_file, "ab") as file_out:
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 7ab19101..f806ff26 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -85,6 +85,7 @@ READONLY_FILE_GLOBS = [
"*.p7m",
"*.pem",
"*.prv",
+ "Certificates.xml",
"ovf-env.xml"
]
diff --git a/tests/common/protocol/test_goal_state.py b/tests/common/protocol/test_goal_state.py
index a5f89587..9b70ce05 100644
--- a/tests/common/protocol/test_goal_state.py
+++ b/tests/common/protocol/test_goal_state.py
@@ -6,6 +6,7 @@ import datetime
import glob
import os
import re
+import subprocess
import shutil
import time
@@ -492,9 +493,85 @@ class GoalStateTestCase(AgentTestCase, HttpRequestPredicates):
goal_state = GoalState(protocol.client)
- self.assertEqual(0, len(goal_state.certs.summary), "Cert list should be empty")
- self.assertEqual(1, http_get_handler.certificate_requests, "There should have been exactly 1 requests for the goal state certificates")
+ self.assertEqual(0, len(goal_state.certs.summary), "Certificates should be empty")
+ self.assertEqual(2, http_get_handler.certificate_requests, "There should have been exactly 2 requests for the goal state certificates") # 1 for the initial request, 1 for the retry with an older cypher
+ def test_goal_state_should_try_legacy_cypher_and_then_fail_when_no_cyphers_are_supported_by_the_wireserver(self):
+ cyphers = []
+ def http_get_handler(url, *_, **kwargs):
+ if HttpRequestPredicates.is_certificates_request(url):
+ cypher = kwargs["headers"].get("x-ms-cipher-name")
+ if cypher is None:
+ raise Exception("x-ms-cipher-name header is missing from the Certificates request")
+ cyphers.append(cypher)
+ return MockHttpResponse(status=400, body="unsupported cypher: {0}".format(cypher).encode('utf-8'))
+ return None
+
+ with mock_wire_protocol(wire_protocol_data.DATA_FILE) as protocol:
+ with patch("azurelinuxagent.common.event.LogEvent.error") as log_error_patch:
+ protocol.set_http_handlers(http_get_handler=http_get_handler)
+ goal_state = GoalState(protocol.client)
+
+ log_error_args, _ = log_error_patch.call_args
+
+ self.assertEqual(cyphers, ["AES128_CBC", "DES_EDE3_CBC"], "There should have been 2 requests for the goal state certificates (AES128_CBC and DES_EDE3_CBC)")
+ self.assertEqual(log_error_args[0], "GoalStateCertificates", "An error fetching the goal state Certificates should have been reported")
+ self.assertEqual(0, len(goal_state.certs.summary), "Certificates should be empty")
+ self.assertFalse(os.path.exists(os.path.join(conf.get_lib_dir(), "Certificates.pfx")), "The Certificates.pfx file should not have been created")
+
+ def test_goal_state_should_try_legacy_cypher_and_then_fail_when_no_cyphers_are_supported_by_openssl(self):
+ cyphers = []
+ def http_get_handler(url, *_, **kwargs):
+ if HttpRequestPredicates.is_certificates_request(url):
+ cyphers.append(kwargs["headers"].get("x-ms-cipher-name"))
+ return None
+
+ original_popen = subprocess.Popen
+ openssl = conf.get_openssl_cmd()
+ decrypt_calls = []
+ def mock_fail_popen(command, *args, **kwargs):
+ if len(command) > 3 and command[0:3] == [openssl, "cms", "-decrypt"]:
+ decrypt_calls.append(command)
+ command[1] = "fake_openssl_command" # force an error on the openssl to simulate a decryption failure
+ return original_popen(command, *args, **kwargs)
+
+ with mock_wire_protocol(wire_protocol_data.DATA_FILE) as protocol:
+ protocol.set_http_handlers(http_get_handler=http_get_handler)
+ with patch("azurelinuxagent.common.event.LogEvent.error") as log_error_patch:
+ with patch("azurelinuxagent.ga.cgroupapi.subprocess.Popen", mock_fail_popen):
+ goal_state = GoalState(protocol.client)
+
+ log_error_args, _ = log_error_patch.call_args
+
+ self.assertEqual(cyphers, ["AES128_CBC", "DES_EDE3_CBC"], "There should have been 2 requests for the goal state certificates (AES128_CBC and DES_EDE3_CBC)")
+ self.assertEqual(2, len(decrypt_calls), "There should have been 2 calls to 'openssl cms -decrypt'")
+ self.assertEqual(log_error_args[0], "GoalStateCertificates", "An error fetching the goal state Certificates should have been reported")
+ self.assertEqual(0, len(goal_state.certs.summary), "Certificates should be empty")
+ self.assertFalse(os.path.exists(os.path.join(conf.get_lib_dir(), "Certificates.pfx")), "The Certificates.pfx file should not have been created")
+
+ def test_goal_state_should_try_without_and_with_mac_verification_then_fail_when_the_pfx_cannot_be_converted(self):
+ original_popen = subprocess.Popen
+ openssl = conf.get_openssl_cmd()
+ nomacver = []
+
+ def mock_fail_popen(command, *args, **kwargs):
+ if len(command) > 2 and command[0] == openssl and command[1] == "pkcs12":
+ nomacver.append("-nomacver" in command)
+ # force an error on the openssl to simulate the conversion failure
+ command[1] = "fake_openssl_command"
+ return original_popen(command, *args, **kwargs)
+
+
+ with mock_wire_protocol(wire_protocol_data.DATA_FILE) as protocol:
+ with patch("azurelinuxagent.common.event.LogEvent.error") as log_error_patch:
+ with patch("azurelinuxagent.ga.cgroupapi.subprocess.Popen", mock_fail_popen):
+ goal_state = GoalState(protocol.client)
+
+ log_error_args, _ = log_error_patch.call_args
+
+ self.assertEqual(nomacver, [True, False], "There should have been 2 attempts to parse the PFX (with and without -nomacver)")
+ self.assertEqual(log_error_args[0], "GoalStateCertificates", "An error fetching the goal state Certificates should have been reported")
+ self.assertEqual(0, len(goal_state.certs.summary), "Certificates should be empty")
def test_it_should_raise_when_goal_state_properties_not_initialized(self):
with GoalStateTestCase._create_protocol_ws_and_hgap_in_sync() as protocol:
diff --git a/tests/common/protocol/test_hostplugin.py b/tests/common/protocol/test_hostplugin.py
index 4c97c73f..7d94139b 100644
--- a/tests/common/protocol/test_hostplugin.py
+++ b/tests/common/protocol/test_hostplugin.py
@@ -365,8 +365,7 @@ class TestHostPlugin(HttpRequestPredicates, AgentTestCase):
# ensure host plugin is not set as default
self.assertFalse(wire.HostPluginProtocol.is_default_channel)
- @patch("azurelinuxagent.common.event.add_event")
- def test_put_status_error_reporting(self, patch_add_event):
+ def test_put_status_error_reporting(self):
"""
Validate the telemetry when uploading status fails
"""
@@ -377,22 +376,22 @@ class TestHostPlugin(HttpRequestPredicates, AgentTestCase):
put_error = wire.HttpError("put status http error")
with patch.object(restutil, "http_put", side_effect=put_error):
- with patch.object(wire.HostPluginProtocol,
- "ensure_initialized", return_value=True):
- self.assertRaises(wire.ProtocolError, wire_protocol_client.upload_status_blob)
-
- # The agent tries to upload via HostPlugin and that fails due to
- # http_put having a side effect of "put_error"
- #
- # The agent tries to upload using a direct connection, and that succeeds.
- self.assertEqual(1, wire_protocol_client.status_blob.upload.call_count) # pylint: disable=no-member
- # The agent never touches the default protocol is this code path, so no change.
- self.assertFalse(wire.HostPluginProtocol.is_default_channel)
- # The agent never logs telemetry event for direct fallback
- self.assertEqual(1, patch_add_event.call_count)
- self.assertEqual('ReportStatus', patch_add_event.call_args[1]['op'])
- self.assertTrue('Falling back to direct' in patch_add_event.call_args[1]['message'])
- self.assertEqual(True, patch_add_event.call_args[1]['is_success'])
+ with patch.object(wire.HostPluginProtocol, "ensure_initialized", return_value=True):
+ with patch("azurelinuxagent.common.event.add_event") as patch_add_event:
+ self.assertRaises(wire.ProtocolError, wire_protocol_client.upload_status_blob)
+
+ # The agent tries to upload via HostPlugin and that fails due to
+ # http_put having a side effect of "put_error"
+ #
+ # The agent tries to upload using a direct connection, and that succeeds.
+ self.assertEqual(1, wire_protocol_client.status_blob.upload.call_count) # pylint: disable=no-member
+ # The agent never touches the default protocol is this code path, so no change.
+ self.assertFalse(wire.HostPluginProtocol.is_default_channel)
+ # The agent never logs telemetry event for direct fallback
+ self.assertEqual(1, patch_add_event.call_count)
+ self.assertEqual('ReportStatus', patch_add_event.call_args[1]['op'])
+ self.assertTrue('Falling back to direct' in patch_add_event.call_args[1]['message'])
+ self.assertEqual(True, patch_add_event.call_args[1]['is_success'])
def test_validate_http_request_when_uploading_status(self):
"""Validate correct set of data is sent to HostGAPlugin when reporting VM status"""
diff --git a/tests/common/protocol/test_wire.py b/tests/common/protocol/test_wire.py
index c3dc9461..bec4634f 100644
--- a/tests/common/protocol/test_wire.py
+++ b/tests/common/protocol/test_wire.py
@@ -497,12 +497,6 @@ class TestWireProtocol(AgentTestCase, HttpRequestPredicates):
client.report_event(self._get_telemetry_events_generator(event_list), flush=True)
self.assertEqual(mock_http_request.call_count, 3)
- def test_get_header_for_cert_should_use_triple_des(self, *_):
- with mock_wire_protocol(wire_protocol_data.DATA_FILE) as protocol:
- headers = protocol.client.get_header_for_cert()
- self.assertIn("x-ms-cipher-name", headers)
- self.assertEqual(headers["x-ms-cipher-name"], "DES_EDE3_CBC", "Unexpected x-ms-cipher-name")
-
def test_get_header_for_remote_access_should_use_aes128(self, *_):
with mock_wire_protocol(wire_protocol_data.DATA_FILE) as protocol:
headers = protocol.client.get_header_for_remote_access()
@@ -1096,7 +1090,7 @@ class UpdateGoalStateTestCase(HttpRequestPredicates, AgentTestCase):
self.assertEqual(protocol.client.get_hosting_env().deployment_name, new_hosting_env_deployment_name)
self.assertEqual(protocol.client.get_shared_conf().xml_text, new_shared_conf)
self.assertEqual(sequence_number, new_sequence_number)
- self.assertEqual(len(protocol.client.get_certs().cert_list.certificates), 0)
+ self.assertEqual(len(protocol.client.get_certs().summary), 0)
self.assertEqual(protocol.client.get_host_plugin().container_id, new_container_id)
self.assertEqual(protocol.client.get_host_plugin().role_config_name, new_role_config_name)
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index 167e69dc..376e9fc0 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -2059,7 +2059,7 @@ class TryUpdateGoalStateTestCase(HttpRequestPredicates, AgentTestCase):
# Double check the certificates are correct
goal_state = protocol.get_goal_state()
- thumbprints = [c.thumbprint for c in goal_state.certs.cert_list.certificates]
+ thumbprints = [c["thumbprint"] for c in goal_state.certs.summary]
for extension in goal_state.extensions_goal_state.extensions:
for settings in extension.settings:
--
2.51.1

View File

@ -2,52 +2,54 @@
%global dracut_modname_udev 97walinuxagent
%global dracut_modname_cvm 97walinuxagentcvm
Name: WALinuxAgent
Version: 2.13.1.1
Release: 3.0.1%{?dist}.2
Summary: The Microsoft Azure Linux Agent
Name: WALinuxAgent
Version: 2.14.0.1
Release: 4%{?dist}
Summary: The Microsoft Azure Linux Agent
License: ASL 2.0
URL: https://github.com/Azure/%{name}
Source0: https://github.com/Azure/%{name}/archive/v%{version}.tar.gz
Source1: module-setup-udev.sh
Source2: module-setup-cvm.sh
Source3: 90-tpm2-import.rules
Source4: tpm2-luks-import.sh
License: ASL 2.0
URL: https://github.com/Azure/%{name}
Source0: https://github.com/Azure/%{name}/archive/v%{version}.tar.gz
Source1: module-setup-udev.sh
Source2: module-setup-cvm.sh
Source3: 90-tpm2-import.rules
Source4: tpm2-luks-import.sh
Patch1: 0001-redhat-Use-NetworkManager-to-set-DHCP-hostnames-on-r.patch
Patch2: 0002-Disable-automatic-log-collector.patch
Patch3: 0003-redhat-Add-a-udev-rule-to-avoid-managing-slave-NICs-.patch
Patch4: 0004-docs-add-waagent-manpage-3401.patch
Patch5: 0005-Use-systemctl-instead-of-service-to-manager-services.patch
# For RHEL-124218 - [Azure][WALA][RHEL-9] Remove 10-azure-unmanaged-sriov.rules
Patch6: wla-Remove-10-azure-unmanaged-sriov.rules.patch
# For RHEL-133507 - Backport ConditionVirtualization=|microsoft for waagent in RHEL 9.x
Patch7: wla-Jira-https-issues.redhat.com-browse-RHEL-133507.patch
# For RHEL-82232 - [Azure][image mode][WALA][RHEL-9] Unable to setup the persistent firewall rules
Patch8: wla-Change-redhat-waagent-network-setup.service-path-to-.patch
# For bz#2114830 - [Azure][WALA][RHEL-9.1] Provisioning failed if no ifcfg-eth0
Patch0001: wla-redhat-Use-NetworkManager-to-set-DHCP-hostnames-on-r.patch
# For RHEL-7273 - [Azure][WALA] Consider to disable Log collector
Patch0002: wla-Disable-automatic-log-collector.patch
# For RHEL-5880 - [Azure][RHEL-9]68-azure-sriov-nm-unmanaged.rules cannot stop NetworkManager-wait-online.service checking SRIOV interface
Patch0003: wla-redhat-Add-a-udev-rule-to-avoid-managing-slave-NICs-.patch
# For RHEL-109496 - [Azure][WALA][RHEL-9] Missing man page
Patch4: wla-docs-add-waagent-manpage-3401.patch
# For RHEL-97572 - [Azure][RHEL-9][WALA][Image mode] Cannot find 'service' command
Patch5: wla-Use-systemctl-instead-of-service-to-manager-services.patch
# For RHEL-124949 - Update walagent to 2.14 to support FIPS 140-3 on Azure [rhel-9.7.z]
Patch6: wla-Support-for-FIPS-140-3-3324.patch
# For RHEL-134939 - Backport ConditionVirtualization=|microsoft for waagent in RHEL 9.x [rhel-9.7.z]
Patch7: wla-Jira-https-issues.redhat.com-browse-RHEL-134939.patch
Patch1000: 0100-add-oracle-support.patch
BuildArch: noarch
BuildArch: noarch
BuildRequires: python3-devel
BuildRequires: python3-setuptools
BuildRequires: python3-distro
Requires: %name-udev = %version-%release
BuildRequires: python3-devel
BuildRequires: python3-setuptools
BuildRequires: python3-distro
Requires: %name-udev = %version-%release
%if 0%{?fedora}
Requires: ntfsprogs
Requires: ntfsprogs
%endif
Requires: openssh
Requires: openssh-server
Requires: openssl
Requires: parted
Requires: python3-pyasn1
Requires: iptables
Requires: openssh
Requires: openssh-server
Requires: openssl
Requires: parted
Requires: python3-pyasn1
Requires: iptables
Requires: azure-vm-utils >= 0.7.0-1
BuildRequires: systemd
BuildRequires: systemd
Requires(post): systemd
Requires(preun): systemd
Requires(postun): systemd
@ -59,25 +61,25 @@ images that are built to run in the Microsoft Azure environment.
%if 0%{?with_legacy}
%package legacy
Summary: The Microsoft Azure Linux Agent (legacy)
Requires: %name = %version-%release
Requires: python2
Requires: net-tools
Summary: The Microsoft Azure Linux Agent (legacy)
Requires: %name = %version-%release
Requires: python2
Requires: net-tools
%description legacy
The Microsoft Azure Linux Agent supporting old version of extensions.
%endif
%package udev
Summary: Udev rules for Microsoft Azure
Summary: Udev rules for Microsoft Azure
%description udev
Udev rules specific to Microsoft Azure Virtual Machines.
%package cvm
Summary: Microsoft Azure CVM specific tools
Requires: tpm2-tools
Requires: cryptsetup
Summary: Microsoft Azure CVM specific tools
Requires: tpm2-tools
Requires: cryptsetup
%description cvm
Scripts and udev rules specific to Microsoft Azure Confidential Virtual Machines.
@ -147,7 +149,6 @@ rm -rf %{_unitdir}/waagent.service.d/
%{python3_sitelib}/*.egg-info
%files udev
%{_udevrulesdir}/10-azure-unmanaged-sriov.rules
%{_udevrulesdir}/66-azure-storage.rules
%{_udevrulesdir}/99-azure-product-uuid.rules
%dir %{_prefix}/lib/dracut/modules.d/%{dracut_modname_udev}
@ -165,18 +166,25 @@ rm -rf %{_unitdir}/waagent.service.d/
%endif
%changelog
* Tue Feb 17 2026 Darren Archibald <darren.archibald@oracle.com> - 2.13.1.1-3.0.1.el9_7.2
- Add oracle support to fix waagent.service build issue
* Wed Jan 21 2026 Miroslav Rezanina <mrezanin@redhat.com> - 2.14.0.1-4
- wla-Change-redhat-waagent-network-setup.service-path-to-.patch [RHEL-82232]
- Resolves: RHEL-82232
([Azure][image mode][WALA][RHEL-9] Unable to setup the persistent firewall rules)
* Tue Jan 06 2026 Jon Maloy <jmaloy@redhat.com> - 2.13.1.1-3.el9_7.2
- wla-Jira-https-issues.redhat.com-browse-RHEL-134939.patch [RHEL-134939]
- Resolves: RHEL-134939
(Backport ConditionVirtualization=|microsoft for waagent in RHEL 9.x [rhel-9.7.z])
* Tue Jan 06 2026 Jon Maloy <jmaloy@redhat.com> - 2.14.0.1-3
- wla-Jira-https-issues.redhat.com-browse-RHEL-133507.patch [RHEL-133507]
- Resolves: RHEL-133507
(Backport ConditionVirtualization=|microsoft for waagent in RHEL 9.x)
* Tue Dec 02 2025 Jon Maloy <jmaloy@redhat.com> - 2.13.1.1-3.el9_7.1
- wla-Support-for-FIPS-140-3-3324.patch [RHEL-124949]
- Resolves: RHEL-124949
(Update walagent to 2.14 to support FIPS 140-3 on Azure [rhel-9.7.z])
* Wed Nov 12 2025 Miroslav Rezanina <mrezanin@redhat.com> - 2.14.0.1-2
- wla-Remove-10-azure-unmanaged-sriov.rules.patch [RHEL-124218]
- Resolves: RHEL-124218
([Azure][WALA][RHEL-9] Remove 10-azure-unmanaged-sriov.rules)
* Thu Oct 30 2025 Yuxin Sun <yuxisun@redhat.com> - 2.14.0.1-1
- Rebase to 2.14.0.1 [RHEL-116436]
- Resolves: RHEL-116436
(Rebase to v2.14.0.1)
* Thu Aug 21 2025 Jon Maloy <jmaloy@redhat.com> - 2.13.1.1-3
- wla-Use-systemctl-instead-of-service-to-manager-services.patch [RHEL-97572]