Repository: Azure/WALinuxAgent
Pull request #3197 (2024-08-28 16:59:53)

Accommodate the new OpenSSL behavior when failed to decrypt certs
<!-- DO NOT DELETE THIS TEMPLATE -->

## Description
fixing https://github.com/Azure/WALinuxAgent/issues/3163

If the decryption fails, old version of openssl overwrite the output file(if exist) with empty data while
new version of openssl does not overwrite the output file, so output file may contain old certs data and agent process the old certs again. Correcting the behavior by removing temporary state certificate files.


```
2024-08-28T00:22:15.391660Z ERROR ExtHandler ExtHandler Failed to decrypt /var/lib/waagent/Certificates.p7m (return code: 1)
[stdout]
 
[stderr]
Error reading SMIME Content Info
4072163C0D7B0000:error:0680008E:asn1 encoding routines:asn1_d2i_read_bio:not enough data:crypto/asn1/a_d2i_fp.c:216:
4072163C0D7B0000:error:0680006E:asn1 encoding routines:b64_read_asn1:decode error:crypto/asn1/asn_mime.c:151:
4072163C0D7B0000:error:068000CB:asn1 encoding routines:SMIME_read_ASN1_ex:asn1 parse error:crypto/asn1/asn_mime.c:500:
 
2024-08-28T00:22:15.391869Z INFO ExtHandler ExtHandler Removing temporary state certificate files ['/var/lib/waagent/Certificates.p7m', '/var/lib/waagent/Certificates.pem']
2024-08-28T00:22:15.392123Z INFO ExtHandler ExtHandler Removed file /var/lib/waagent/Certificates.p7m
2024-08-28T00:22:15.392267Z INFO ExtHandler ExtHandler Removed file /var/lib/waagent/Certificates.pem
2024-08-28T00:22:15.392632Z INFO ExtHandler Fetch goal state completed
```

Issue # <!-- if any -->
<!--
Please add an informative description that covers that changes made by the pull request. 
This checklist is used to make sure that common issues in a pull request are addressed.
This will expedite the process of getting your pull request merged and avoid extra work on your part to fix issues discovered during the review process.
-->

---

### PR information
- [ ] The title of the PR is clear and informative.
- [ ] There are a small number of commits, each of which has an informative message. This means that previously merged commits do not appear in the history of the PR. For information on cleaning up the commits in your pull request, [see this page](https://github.com/Azure/azure-powershell/blob/master/documentation/development-docs/cleaning-up-commits.md).
- [ ] If applicable, the PR references the bug/issue that it fixes in the description.
- [ ] New Unit tests were added for the changes made

### Quality of Code and Contribution Guidelines
- [ ] I have read the [contribution guidelines](https://github.com/Azure/WALinuxAgent/blob/master/.github/CONTRIBUTING.md).

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/Azure/WALinuxAgent/pull/3197?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) Report
Attention: Patch coverage is `90.32258%` with `3 lines` in your changes missing coverage. Please review.
> Project coverage is 72.30%. Comparing base [(`3aebcdd`)](https://app.codecov.io/gh/Azure/WALinuxAgent/commit/3aebcdd781549b55356d0682d135eec6d53b7831?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) to head [(`2181751`)](https://app.codecov.io/gh/Azure/WALinuxAgent/commit/21817511882faa7aaa3170c97fbf81c373d14127?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure).
> Report is 297 commits behind head on develop.

| [Files with missing lines](https://app.codecov.io/gh/Azure/WALinuxAgent/pull/3197?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) | Patch % | Lines |
|---|---|---|
| [azurelinuxagent/common/utils/cryptutil.py](https://app.codecov.io/gh/Azure/WALinuxAgent/pull/3197?src=pr&el=tree&filepath=azurelinuxagent%2Fcommon%2Futils%2Fcryptutil.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVsaW51eGFnZW50L2NvbW1vbi91dGlscy9jcnlwdHV0aWwucHk=) | 72.72% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/Azure/WALinuxAgent/pull/3197?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) |

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           develop    #3197      +/-   ##
===========================================
+ Coverage    71.97%   72.30%   +0.32%     
===========================================
  Files          103      114      +11     
  Lines        15692    16953    +1261     
  Branches      2486     2448      -38     
===========================================
+ Hits         11295    12258     +963     
- Misses        3881     4131     +250     
- Partials       516      564      +48     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/Azure/WALinuxAgent/pull/3197?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure).


Patch:
diff --git a/azurelinuxagent/common/protocol/goal_state.py b/azurelinuxagent/common/protocol/goal_state.py
index 607710e3..2730a062 100644
--- a/azurelinuxagent/common/protocol/goal_state.py
+++ b/azurelinuxagent/common/protocol/goal_state.py
@@ -593,29 +593,32 @@ class Certificates(object):
         thumbprints = {}
         index = 0
         v1_cert_list = []
-        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
+
+        # 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
 
         # Rename prv key with thumbprint as the file name
         for pubkey in prvs:
diff --git a/azurelinuxagent/common/utils/cryptutil.py b/azurelinuxagent/common/utils/cryptutil.py
index bed829ae..00126e25 100644
--- a/azurelinuxagent/common/utils/cryptutil.py
+++ b/azurelinuxagent/common/utils/cryptutil.py
@@ -87,6 +87,16 @@ class CryptUtil(object):
             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):
@@ -99,6 +109,13 @@ class CryptUtil(object):
             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 crt_to_ssh(self, input_file, output_file):
         with open(output_file, "ab") as file_out:


Test patch:
diff --git a/tests/common/protocol/test_goal_state.py b/tests/common/protocol/test_goal_state.py
index 5a63586e..9755b72c 100644
--- a/tests/common/protocol/test_goal_state.py
+++ b/tests/common/protocol/test_goal_state.py
@@ -483,6 +483,63 @@ class GoalStateTestCase(AgentTestCase, HttpRequestPredicates):
                     if settings.protectedSettings is not None:
                         self.assertIn(settings.certificateThumbprint, thumbprints, "Certificate is missing from the goal state.")
 
+    def test_goal_state_should_contain_empty_certs_when_it_is_fails_to_decrypt_certs(self):
+        #  This test simulates that scenario by mocking the goal state request is fabric, and it contains incorrect certs(incorrect-certs.xml)
+
+        data_file = "wire/incorrect-certs.xml"
+
+        def http_get_handler(url, *_, **__):
+            if HttpRequestPredicates.is_certificates_request(url):
+                http_get_handler.certificate_requests += 1
+                data = load_data(data_file)
+                return MockHttpResponse(status=200, body=data.encode('utf-8'))
+            return None
+
+        http_get_handler.certificate_requests = 0
+
+        with mock_wire_protocol(wire_protocol_data.DATA_FILE) as protocol:
+            protocol.set_http_handlers(http_get_handler=http_get_handler)
+            protocol.mock_wire_data.reset_call_counts()
+
+            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")
+
+    def test_it_should_refresh_the_goal_state_when_it_is_fails_to_decrypt_cert(self):
+        # This test simulates that scenario by mocking the certificates request and returning first a set of certificates (incorrect-certs.xml) that will fail while decrypting, and then a
+        # set (certs.xml) that does match with what extensions are needed. The test then ensures that the goal state was refreshed and the correct certificates were fetched.
+
+        data_files = [
+            "wire/incorrect-certs.xml",
+            "wire/certs.xml"
+        ]
+
+        def http_get_handler(url, *_, **__):
+            if HttpRequestPredicates.is_certificates_request(url):
+                http_get_handler.certificate_requests += 1
+                if http_get_handler.certificate_requests < len(data_files):
+                    data = load_data(data_files[http_get_handler.certificate_requests - 1])
+                    return MockHttpResponse(status=200, body=data.encode('utf-8'))
+            return None
+        http_get_handler.certificate_requests = 0
+
+        with mock_wire_protocol(wire_protocol_data.DATA_FILE_VM_SETTINGS) as protocol:
+            protocol.set_http_handlers(http_get_handler=http_get_handler)
+            protocol.mock_wire_data.reset_call_counts()
+
+            goal_state = GoalState(protocol.client)
+
+            self.assertEqual(2, protocol.mock_wire_data.call_counts['goalstate'], "There should have been exactly 2 requests for the goal state (original + refresh)")
+            self.assertEqual(2, http_get_handler.certificate_requests, "There should have been exactly 2 requests for the goal state certificates (original + refresh)")
+
+            thumbprints = [c.thumbprint for c in goal_state.certs.cert_list.certificates]
+
+            for extension in goal_state.extensions_goal_state.extensions:
+                for settings in extension.settings:
+                    if settings.protectedSettings is not None:
+                        self.assertIn(settings.certificateThumbprint, thumbprints, "Certificate is missing from the goal state.")
+
     def test_it_should_raise_when_goal_state_properties_not_initialized(self):
         with GoalStateTestCase._create_protocol_ws_and_hgap_in_sync() as protocol:
             goal_state = GoalState(
diff --git a/tests/data/wire/incorrect-certs.xml b/tests/data/wire/incorrect-certs.xml
new file mode 100644
index 00000000..66a231ee
--- /dev/null
+++ b/tests/data/wire/incorrect-certs.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<CertificateFile xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="certificates10.xsd">
+  <Version>2012-11-30</Version>
+  <Incarnation>5</Incarnation>
+  <Format>Pkcs7BlobWithPfxContents</Format>
+  <Data>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
+wfl+7QbXYf+k5WnELf3IqYvh8ZWexa0=
+</Data>
+</CertificateFile>
\ No newline at end of file