From 622b0d0e70ba1a3fadda2a7b0b84c1b69f0822f3 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 | 1 + src/providers/microsoft/azure/mod.rs | 18 +++++++----------- src/providers/microsoft/azurestack/mod.rs | 18 +++++++----------- src/providers/microsoft/crypto/mod.rs | 13 ++++++++++--- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index 12ebc662..aedccfef 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -13,6 +13,7 @@ Major changes: Minor changes: - Hetzner: fix duplicate attribute prefix +- 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..2d6ab602 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,7 @@ 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")?; - - Ok(ssh_pubkey) + crypto::p12_to_ssh_pubkey(&p12).context("failed to convert pkcs12 blob to ssh pubkey") } #[cfg(test)] @@ -402,18 +399,17 @@ 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 maybe_key = self.get_ssh_pubkey(certs_endpoint)?; + let key: Vec = maybe_key.into_iter().collect(); + + 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..61e5b78b 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,7 @@ 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")?; - - Ok(ssh_pubkey) + crypto::p12_to_ssh_pubkey(&p12).context("failed to convert pkcs12 blob to ssh pubkey") } fn fetch_hostname(&self) -> Result> { @@ -326,18 +323,17 @@ 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 maybe_key = self.get_ssh_pubkey(certs_endpoint)?; + let key: Vec = maybe_key.into_iter().collect(); + + 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..8e263089 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)) }