diff --git a/nexus/src/cidata.rs b/nexus/src/cidata.rs index 0f79afc992..3a0e15fdae 100644 --- a/nexus/src/cidata.rs +++ b/nexus/src/cidata.rs @@ -9,12 +9,15 @@ use uuid::Uuid; pub const MAX_USER_DATA_BYTES: usize = 32 * 1024; // 32 KiB impl Instance { - pub fn generate_cidata(&self) -> Result, Error> { + pub fn generate_cidata( + &self, + public_keys: &[String], + ) -> Result, Error> { // cloud-init meta-data is YAML, but YAML is a strict superset of JSON. let meta_data = serde_json::to_vec(&MetaData { instance_id: self.id(), local_hostname: &self.runtime().hostname, - public_keys: &[], // TODO + public_keys, }) .map_err(|_| Error::internal_error("failed to serialize meta-data"))?; let cidata = @@ -67,10 +70,10 @@ fn build_vfat(meta_data: &[u8], user_data: &[u8]) -> io::Result> { let root_dir = fs.root_dir(); for (file, data) in [("meta-data", meta_data), ("user-data", user_data)] { - if !data.is_empty() { - let mut file = root_dir.create_file(file)?; - file.write_all(data)?; - } + // Cloud-init requires the files `meta-data` and `user-data` + // to be present, even if empty. + let mut file = root_dir.create_file(file)?; + file.write_all(data)?; } } diff --git a/nexus/src/nexus.rs b/nexus/src/nexus.rs index dded9e6abe..fa04499d86 100644 --- a/nexus/src/nexus.rs +++ b/nexus/src/nexus.rs @@ -124,6 +124,8 @@ pub(crate) const MAX_DISKS_PER_INSTANCE: u32 = 8; pub(crate) const MAX_NICS_PER_INSTANCE: u32 = 8; +pub(crate) const MAX_KEYS_PER_INSTANCE: u32 = 8; + /// Manages an Oxide fleet -- the heart of the control plane pub struct Nexus { /// uuid for this nexus instance. @@ -1852,6 +1854,32 @@ impl Nexus { .derive_guest_network_interface_info(&opctx, &authz_instance) .await?; + // Gather the SSH public keys of the actor make the request so + // that they may be injected into the new image via cloud-init. + // TODO-security: this should be replaced with a lookup based on + // on `SiloUser` role assignments once those are in place. + let actor = opctx.authn.actor_required()?; + let (.., authz_user) = LookupPath::new(opctx, &self.db_datastore) + .silo_user_id(actor.actor_id()) + .lookup_for(authz::Action::ListChildren) + .await?; + let public_keys = self + .db_datastore + .ssh_keys_list( + opctx, + &authz_user, + &DataPageParams { + marker: None, + direction: dropshot::PaginationOrder::Ascending, + limit: std::num::NonZeroU32::new(MAX_KEYS_PER_INSTANCE) + .unwrap(), + }, + ) + .await? + .into_iter() + .map(|ssh_key| ssh_key.public_key) + .collect::>(); + // Ask the sled agent to begin the state change. Then update the // database to reflect the new intermediate state. If this update is // not the newest one, that's fine. That might just mean the sled agent @@ -1864,7 +1892,7 @@ impl Nexus { nics, disks: disk_reqs, cloud_init_bytes: Some(base64::encode( - db_instance.generate_cidata()?, + db_instance.generate_cidata(&public_keys)?, )), };