Skip to content

Commit

Permalink
microsoft/azure: allow empty certificate chain in PKCS12 file
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
marmijo committed Jun 21, 2024
1 parent 547c950 commit a82baf2
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 17 deletions.
1 change: 1 addition & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Major changes:
Minor changes:

- Hetzner: fix duplicate attribute prefix
- Fix Azure SSH key fetching when no key provisioned

Packaging changes:

Expand Down
13 changes: 6 additions & 7 deletions src/providers/microsoft/azure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ impl Azure {
}

// put it all together
fn get_ssh_pubkey(&self, certs_endpoint: String) -> Result<PublicKey> {
fn get_ssh_pubkey(&self, certs_endpoint: String) -> Result<Option<PublicKey>> {
// 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`
Expand Down Expand Up @@ -402,18 +402,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<PublicKey> = maybe_key.into_iter().collect();

Ok(key)
}

fn boot_checkin(&self) -> Result<()> {
Expand Down
13 changes: 6 additions & 7 deletions src/providers/microsoft/azurestack/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ impl AzureStack {
}

// put it all together
fn get_ssh_pubkey(&self, certs_endpoint: String) -> Result<PublicKey> {
fn get_ssh_pubkey(&self, certs_endpoint: String) -> Result<Option<PublicKey>> {
// 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`
Expand Down Expand Up @@ -326,18 +326,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<PublicKey> = maybe_key.into_iter().collect();

Ok(key)
}

fn boot_checkin(&self) -> Result<()> {
Expand Down
13 changes: 10 additions & 3 deletions src/providers/microsoft/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub fn decrypt_cms(smime: &[u8], pkey: &PKey<Private>, x509: &X509) -> Result<Ve
Ok(p12_der)
}

pub fn p12_to_ssh_pubkey(p12_der: &[u8]) -> Result<PublicKey> {
pub fn p12_to_ssh_pubkey(p12_der: &[u8]) -> Result<Option<PublicKey>> {
// 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
Expand All @@ -65,7 +65,14 @@ pub fn p12_to_ssh_pubkey(p12_der: &[u8]) -> Result<PublicKey> {
// 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"))?;
Expand All @@ -86,5 +93,5 @@ pub fn p12_to_ssh_pubkey(p12_der: &[u8]) -> Result<PublicKey> {
let n = ssh_pubkey_rsa.n().to_vec();
let ssh_pubkey = PublicKey::from_rsa(e, n);

Ok(ssh_pubkey)
Ok(Some(ssh_pubkey))
}

0 comments on commit a82baf2

Please sign in to comment.