-
Notifications
You must be signed in to change notification settings - Fork 377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for FIPS 140-3 #3324
Support for FIPS 140-3 #3324
Conversation
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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this logging to the Certificates class
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to distinguish between failures in the encryption protocol with the WireServer and failures parsing the PFX, so I split those into separate methods. I also moved the creation of the *.prv and *.crt files from the the Certificates.pem to its own method.
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"]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The WireServer and OpenSSL on the VM should both support AES128, but I am keeping Triple DES as a fallback. Depending on telemetry, I may remove the fallback on a subsequent release.
|
||
try: | ||
xml_text = wire_client.fetch_config(uri, headers) | ||
except Exception as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am catching a generic Exception to collect telemetry that will help me decide what specific errors should be handled with a retry. For the moment, in the worst case, we are doing a retry that won't help, but this is done on a per-goal-state basis, so the impact is minimal.
Same applies for catching a generic shellutil.CommandError in the code below and in _convert_certificates_pfx_to_pem()
thumbprints[pub] = thumbprint | ||
# Rename crt with thumbprint as the file name | ||
crt = "{0}.crt".format(thumbprint) | ||
v1_cert_list.append({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this certificate list was not being used, and most of the properties in each item were not even populated
@@ -85,6 +85,7 @@ | |||
"*.p7m", | |||
"*.pem", | |||
"*.prv", | |||
"Certificates.xml", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The permissions for those files should be set at creation time. For now, just adding a missing file, will set the permissions on creation on a separate PR.
@@ -365,8 +365,7 @@ def test_fallback_channel_failure(self, patch_put, patch_upload, _): | |||
# ensure host plugin is not set as default | |||
self.assertFalse(wire.HostPluginProtocol.is_default_channel) | |||
|
|||
@patch("azurelinuxagent.common.event.add_event") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reduced the scope of this mock to the call to upload_status_blob(), otherwise it ends up capturing extra telemetry
@@ -497,12 +497,6 @@ def test_report_event_http_req_should_do_max_retries_on_throttling_error(self, m | |||
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, *_): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not applicable anymore
|
||
def info(self, op, fmt, *args): | ||
self._logger.info(fmt, *args) | ||
add_event(op=op, message=fmt.format(*args), is_success=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should log_event=False for info events?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log_event is used only when is_success == False:
def add_event(self, name, op=WALAEventOperation.Unknown, is_success=True, duration=0, version=str(CURRENT_VERSION),
message="", log_event=True, flush=False):
if (not is_success) and log_event:
_log_event(name, op, message, duration, is_success=is_success)
def decrypt_certificates_p7m(self, p7m_file, trans_prv_file, trans_cert_file, pfx_file): | ||
umask = None | ||
try: | ||
umask = os.umask(0o077) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do this because pfx file should have read and write access for owner only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I had to do it this way because I could not find an API to create a file with a specific mode that is backwards compatible all the way to 2.6
return | ||
try: | ||
pem_file = self._convert_certificates_pfx_to_pem(pfx_file) | ||
finally: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add a finally block to line 529 and do the pfx removal there if it exists? It seems possible for umask() to raise Exception when we reset the file mode creation in the finally block of decrypt_certificates_p7m?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, thanks. I think it is the responsibility of _download_certificates_pfx to cleanup when it raises. I'll handle this case there
buf = [] | ||
index += 1 | ||
|
||
cryptutil = CryptUtil(conf.get_openssl_cmd()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use self._crypt_util instead?
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.
Fixes #2600