Repository: Azure/WALinuxAgent
Pull request #3406 (2025-06-24 16:54:23)

Log collector should not re-create transport cert
The log collector and the extension handler can be in a race where the collector is resetting the transport cert while the handler is trying to use it.

Added flag 'create_transport_certificate' to initialize the protocol without creating/resetting the transport cert.


Patch:
diff --git a/azurelinuxagent/common/protocol/util.py b/azurelinuxagent/common/protocol/util.py
index f8225980..573b4073 100644
--- a/azurelinuxagent/common/protocol/util.py
+++ b/azurelinuxagent/common/protocol/util.py
@@ -188,7 +188,7 @@ class ProtocolUtil(SingletonPerThread):
                 return
             logger.error("Failed to clear wiresever endpoint: {0}", e)
 
-    def _detect_protocol(self, save_to_history, init_goal_state=True):
+    def _detect_protocol(self, init_goal_state, create_transport_certificate, save_to_history):
         """
         Probe protocol endpoints in turn.
         """
@@ -223,7 +223,7 @@ class ProtocolUtil(SingletonPerThread):
 
                 try:
                     protocol = WireProtocol(endpoint)
-                    protocol.detect(init_goal_state=init_goal_state, save_to_history=save_to_history)
+                    protocol.detect(init_goal_state=init_goal_state, create_transport_certificate=create_transport_certificate, save_to_history=save_to_history)
                     self._set_wireserver_endpoint(endpoint)
                     return protocol
 
@@ -274,7 +274,7 @@ class ProtocolUtil(SingletonPerThread):
         finally:
             self._lock.release()
 
-    def get_protocol(self, init_goal_state=True, save_to_history=False):
+    def get_protocol(self, init_goal_state=True, create_transport_certificate=True, save_to_history=False):
         """
         Detect protocol by endpoint.
         :returns: protocol instance
@@ -302,7 +302,7 @@ class ProtocolUtil(SingletonPerThread):
 
             logger.info("Detect protocol endpoint")
 
-            protocol = self._detect_protocol(save_to_history=save_to_history, init_goal_state=init_goal_state)
+            protocol = self._detect_protocol(init_goal_state=init_goal_state, create_transport_certificate=create_transport_certificate, save_to_history=save_to_history)
 
             IOErrorCounter.set_protocol_endpoint(endpoint=protocol.get_endpoint())
             self._save_protocol(WIRE_PROTOCOL_NAME)
diff --git a/azurelinuxagent/common/protocol/wire.py b/azurelinuxagent/common/protocol/wire.py
index 78fb21d2..e7d312a6 100644
--- a/azurelinuxagent/common/protocol/wire.py
+++ b/azurelinuxagent/common/protocol/wire.py
@@ -75,15 +75,16 @@ class WireProtocol(DataContract):
             raise ProtocolError("WireProtocol endpoint is None")
         self.client = WireClient(endpoint)
 
-    def detect(self, init_goal_state=True, save_to_history=False):
+    def detect(self, init_goal_state=True, create_transport_certificate=True, save_to_history=False):
         self.client.check_wire_protocol_version()
 
-        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)
-        cryptutil = CryptUtil(conf.get_openssl_cmd())
-        cryptutil.gen_transport_cert(trans_prv_file, trans_cert_file)
+        if create_transport_certificate:
+            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)
+            cryptutil = CryptUtil(conf.get_openssl_cmd())
+            cryptutil.gen_transport_cert(trans_prv_file, trans_cert_file)
 
         # Initialize the goal state, including all the inner properties
         if init_goal_state:
diff --git a/azurelinuxagent/ga/logcollector.py b/azurelinuxagent/ga/logcollector.py
index 33b8a249..f4b05be0 100644
--- a/azurelinuxagent/ga/logcollector.py
+++ b/azurelinuxagent/ga/logcollector.py
@@ -110,7 +110,7 @@ class LogCollector(object):
 
     @staticmethod
     def initialize_telemetry():
-        protocol = get_protocol_util().get_protocol(init_goal_state=False)
+        protocol = get_protocol_util().get_protocol(init_goal_state=False, create_transport_certificate=False, save_to_history=False)
         protocol.client.reset_goal_state(goal_state_properties=GoalStateProperties.RoleConfig | GoalStateProperties.HostingEnv)
         # Initialize the common parameters for telemetry events
         initialize_event_logger_vminfo_common_parameters_and_protocol(protocol)


Test patch:
diff --git a/tests/common/protocol/test_protocol_util.py b/tests/common/protocol/test_protocol_util.py
index a0a71d77..fd9a7d05 100644
--- a/tests/common/protocol/test_protocol_util.py
+++ b/tests/common/protocol/test_protocol_util.py
@@ -127,14 +127,14 @@ class TestProtocolUtil(AgentTestCase):
         endpoint_file = protocol_util._get_wireserver_endpoint_file_path()  # pylint: disable=unused-variable
 
         # Test wire protocol when no endpoint file has been written
-        protocol_util._detect_protocol(save_to_history=False)
+        protocol_util._detect_protocol(init_goal_state=True, create_transport_certificate=True, save_to_history=False)
         self.assertEqual(KNOWN_WIRESERVER_IP, protocol_util.get_wireserver_endpoint())
 
         # Test wire protocol on dhcp failure
         protocol_util.osutil.is_dhcp_available.return_value = True
         protocol_util.dhcp_handler.run.side_effect = DhcpError()
 
-        self.assertRaises(ProtocolError, lambda: protocol_util._detect_protocol(save_to_history=False))
+        self.assertRaises(ProtocolError, lambda: protocol_util._detect_protocol(init_goal_state=True, create_transport_certificate=True, save_to_history=False))
 
     @patch("azurelinuxagent.common.conf.get_lib_dir")
     @patch("azurelinuxagent.common.protocol.util.WireProtocol")
@@ -151,7 +151,7 @@ class TestProtocolUtil(AgentTestCase):
         protocol_util.dhcp_handler.run = Mock()
 
         # Test wire protocol when no endpoint file has been written, dhcp handler should not be called
-        protocol_util._detect_protocol(save_to_history=False)
+        protocol_util._detect_protocol(init_goal_state=True, create_transport_certificate=True, save_to_history=False)
         self.assertEqual(KNOWN_WIRESERVER_IP, protocol_util.get_wireserver_endpoint())
         self.assertTrue(protocol_util.dhcp_handler.run.call_count == 0)