From 388b98d25f9742128d9c77c42a4c4b0a5b0d3625 Mon Sep 17 00:00:00 2001 From: Michael Armijo Date: Wed, 8 May 2024 13:26:07 -0600 Subject: [PATCH] microsoft/azure: allow empty certificate chain in PKCS12 file Azure is populating the certs endpoint even when an SSH key is not provided. The certificate chain in the PKCS12 file is empty in this case causing afterburn to fail with the current logic. Check if the certificate chain is empty before retrieving the SSH public key and handle this case as a valid option. The more general `crypto/mod.rs` was updated here which also affected the azurestack code. Update azurestack to accomodate the changes when retrieving the SSH public key. --- docs/release-notes.md | 2 ++ src/providers/microsoft/azure/mod.rs | 21 +++++++++++---------- src/providers/microsoft/azurestack/mod.rs | 21 +++++++++++---------- src/providers/microsoft/crypto/mod.rs | 13 ++++++++++--- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 2c09e9b2..cf8ec8df 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,6 +12,8 @@ Major changes: Minor changes: +- Fix Azure SSH key fetching when no key provisioned + Packaging changes: diff --git a/src/providers/microsoft/azure/mod.rs b/src/providers/microsoft/azure/mod.rs index 6c7ba8b5..5c0a9b5c 100644 --- a/src/providers/microsoft/azure/mod.rs +++ b/src/providers/microsoft/azure/mod.rs @@ -251,7 +251,7 @@ impl Azure { } // put it all together - fn get_ssh_pubkey(&self, certs_endpoint: String) -> Result { + fn get_ssh_pubkey(&self, certs_endpoint: String) -> Result>> { // we have to generate the rsa public/private keypair and the x509 cert // that we use to make the request. this is equivalent to // `openssl req -x509 -nodes -subj /CN=LinuxTransport -days 365 -newkey rsa:2048 -keyout private.pem -out cert.pem` @@ -271,10 +271,13 @@ impl Azure { .context("failed to decrypt cms blob")?; // convert that to the OpenSSH public key format - let ssh_pubkey = crypto::p12_to_ssh_pubkey(&p12) - .context("failed to convert pkcs12 blob to ssh pubkey")?; + let ssh_pubkey = match crypto::p12_to_ssh_pubkey(&p12) + .context("failed to convert pkcs12 blob to ssh pubkey")? { + Some(key) => key, + None => return Ok(None) + }; - Ok(ssh_pubkey) + Ok(Some(vec![ssh_pubkey])) } #[cfg(test)] @@ -402,18 +405,16 @@ impl MetadataProvider for Azure { let goalstate = self.fetch_goalstate()?; let certs_endpoint = match goalstate.certs_endpoint() { Some(ep) => ep, - None => { - warn!("SSH pubkeys requested, but not provisioned for this instance"); - return Ok(vec![]); - } + None => return Ok(vec![]), }; if certs_endpoint.is_empty() { bail!("unexpected empty certificates endpoint"); } - let key = self.get_ssh_pubkey(certs_endpoint)?; - Ok(vec![key]) + let key: Vec = self.get_ssh_pubkey(certs_endpoint)?.unwrap_or(vec![]); + + Ok(key) } fn boot_checkin(&self) -> Result<()> { diff --git a/src/providers/microsoft/azurestack/mod.rs b/src/providers/microsoft/azurestack/mod.rs index 4810f2b8..4fba6a14 100644 --- a/src/providers/microsoft/azurestack/mod.rs +++ b/src/providers/microsoft/azurestack/mod.rs @@ -267,7 +267,7 @@ impl AzureStack { } // put it all together - fn get_ssh_pubkey(&self, certs_endpoint: String) -> Result { + fn get_ssh_pubkey(&self, certs_endpoint: String) -> Result>> { // we have to generate the rsa public/private keypair and the x509 cert // that we use to make the request. this is equivalent to // `openssl req -x509 -nodes -subj /CN=LinuxTransport -days 365 -newkey rsa:2048 -keyout private.pem -out cert.pem` @@ -287,10 +287,13 @@ impl AzureStack { .context("failed to decrypt cms blob")?; // convert that to the OpenSSH public key format - let ssh_pubkey = crypto::p12_to_ssh_pubkey(&p12) - .context("failed to convert pkcs12 blob to ssh pubkey")?; + let ssh_pubkey = match crypto::p12_to_ssh_pubkey(&p12) + .context("failed to convert pkcs12 blob to ssh pubkey")? { + Some(key) => key, + None => return Ok(None) + }; - Ok(ssh_pubkey) + Ok(Some(vec![ssh_pubkey])) } fn fetch_hostname(&self) -> Result> { @@ -326,18 +329,16 @@ impl MetadataProvider for AzureStack { let goalstate = self.fetch_goalstate()?; let certs_endpoint = match goalstate.certs_endpoint() { Some(ep) => ep, - None => { - warn!("SSH pubkeys requested, but not provisioned for this instance"); - return Ok(vec![]); - } + None => return Ok(vec![]), }; if certs_endpoint.is_empty() { bail!("unexpected empty certificates endpoint"); } - let key = self.get_ssh_pubkey(certs_endpoint)?; - Ok(vec![key]) + let key: Vec = self.get_ssh_pubkey(certs_endpoint)?.unwrap_or(vec![]); + + Ok(key) } fn boot_checkin(&self) -> Result<()> { diff --git a/src/providers/microsoft/crypto/mod.rs b/src/providers/microsoft/crypto/mod.rs index d009baef..f8360b4a 100644 --- a/src/providers/microsoft/crypto/mod.rs +++ b/src/providers/microsoft/crypto/mod.rs @@ -54,7 +54,7 @@ pub fn decrypt_cms(smime: &[u8], pkey: &PKey, x509: &X509) -> Result Result { +pub fn p12_to_ssh_pubkey(p12_der: &[u8]) -> Result> { // the contents of that encrypted cms blob we got are actually a different // cryptographic structure. we read that in from the contents and parse it. // PKCS12 has the ability to have a password, but we don't have one, hence @@ -65,7 +65,14 @@ pub fn p12_to_ssh_pubkey(p12_der: &[u8]) -> Result { // ParsedPKCS12_2 has three parts: a pkey, a main x509 cert, and a list of other // x509 certs. The list of other x509 certs are called the `certificate chain` // currently denoted as `ca`; there is only one cert in this `certificate chain`, - // which is the ssh public key. + // which is the ssh public key. The certs endpoint may still be populated even if + // an SSH public key hasn't been provided, which would lead to this code being + // executed. The certificate chain will be empty in this case, so just return + // None and handle it further up the stack. + if p12.ca.is_none() { + return Ok(None) + } + let ca = p12 .ca .ok_or_else(|| anyhow!("failed to get chain from pkcs12"))?; @@ -86,5 +93,5 @@ pub fn p12_to_ssh_pubkey(p12_der: &[u8]) -> Result { let n = ssh_pubkey_rsa.n().to_vec(); let ssh_pubkey = PublicKey::from_rsa(e, n); - Ok(ssh_pubkey) + Ok(Some(ssh_pubkey)) }