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)) }