-
Notifications
You must be signed in to change notification settings - Fork 34
mvp vm attestation #1091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mvp vm attestation #1091
Changes from 27 commits
f6d25c1
5dbf46c
e12a38f
6335323
ef01e4b
591b9f5
4ca28cb
5f12a78
b1c710c
e4b4a52
1c55d2b
1c6ed47
14122a2
449a3b2
19cfbf7
d89273b
2d0a0e4
fea9dbb
60c8c04
9efdfb6
b137a90
cf55c6e
7f84255
776795a
9af75aa
60935ca
50c24ff
014950e
71b14da
2d8818d
38cb234
c096720
e6bbd3a
1ff4e3e
26f31f0
786ef27
0f843dd
a42814d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,6 @@ | |
|
|
||
| use std::convert::TryInto; | ||
| use std::fs::File; | ||
| use std::net::{IpAddr, Ipv4Addr, SocketAddr}; | ||
| use std::num::{NonZeroU8, NonZeroUsize}; | ||
| use std::os::unix::fs::FileTypeExt; | ||
| use std::sync::Arc; | ||
|
|
@@ -25,6 +24,9 @@ use crucible_client_types::VolumeConstructionRequest; | |
| pub use nexus_client::Client as NexusClient; | ||
| use oximeter::types::ProducerRegistry; | ||
| use oximeter_instruments::kstat::KstatSampler; | ||
| use propolis::attestation; | ||
| use propolis::attestation::server::AttestationServerConfig; | ||
| use propolis::attestation::server::AttestationSock; | ||
| use propolis::block; | ||
| use propolis::chardev::{self, BlockingSource, Source}; | ||
| use propolis::common::{Lifecycle, GB, MB, PAGE_SIZE}; | ||
|
|
@@ -96,6 +98,12 @@ pub enum MachineInitError { | |
| #[error("boot order entry {0:?} does not refer to an attached disk")] | ||
| BootOrderEntryWithoutDevice(SpecKey), | ||
|
|
||
| #[error( | ||
| "disk device {device_id:?} refers to a \ | ||
| non-existent block backend {backend_id:?}" | ||
| )] | ||
| DeviceWithoutBlockBackend { device_id: SpecKey, backend_id: SpecKey }, | ||
|
|
||
| #[error("boot entry {0:?} refers to a device on non-zero PCI bus {1}")] | ||
| BootDeviceOnDownstreamPciBus(SpecKey, u8), | ||
|
|
||
|
|
@@ -105,6 +113,9 @@ pub enum MachineInitError { | |
| #[error("failed to specialize CPUID for vcpu {0}")] | ||
| CpuidSpecializationFailed(i32, #[source] propolis::cpuid::SpecializeError), | ||
|
|
||
| #[error("failed to start attestation server")] | ||
| AttestationServer(#[source] std::io::Error), | ||
|
|
||
| #[cfg(feature = "falcon")] | ||
| #[error("softnpu p9 device missing")] | ||
| SoftNpuP9Missing, | ||
|
|
@@ -478,25 +489,20 @@ impl MachineInitializer<'_> { | |
| Ok(()) | ||
| } | ||
|
|
||
| pub fn initialize_vsock( | ||
| pub async fn initialize_vsock( | ||
| &mut self, | ||
| chipset: &RegisteredChipset, | ||
| ) -> Result<(), MachineInitError> { | ||
| attest_cfg: Option<AttestationServerConfig>, | ||
| ) -> Result<Option<AttestationSock>, MachineInitError> { | ||
| use propolis::vsock::proxy::VsockPortMapping; | ||
|
|
||
| // OANA Port 605 - VM Attestation RFD 605 | ||
| const ATTESTATION_PORT: u16 = 605; | ||
| const ATTESTATION_ADDR: SocketAddr = SocketAddr::new( | ||
| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), | ||
| ATTESTATION_PORT, | ||
| ); | ||
|
|
||
| // TODO: early return if none? | ||
| if let Some(vsock) = &self.spec.vsock { | ||
| let bdf: pci::Bdf = vsock.spec.pci_path.into(); | ||
|
|
||
| let mappings = vec![VsockPortMapping::new( | ||
| ATTESTATION_PORT.into(), | ||
| ATTESTATION_ADDR, | ||
| attestation::ATTESTATION_PORT.into(), | ||
| attestation::ATTESTATION_ADDR, | ||
| )]; | ||
|
|
||
| let guest_cid = GuestCid::try_from(vsock.spec.guest_cid) | ||
|
|
@@ -516,9 +522,20 @@ impl MachineInitializer<'_> { | |
|
|
||
| self.devices.insert(vsock.id.clone(), device.clone()); | ||
| chipset.pci_attach(bdf, device); | ||
|
|
||
| // Spawn attestation server that will go over the vsock | ||
| if let Some(cfg) = attest_cfg { | ||
| let attest = AttestationSock::new( | ||
| self.log.new(slog::o!("component" => "attestation-server")), | ||
| cfg.sled_agent_addr, | ||
| ) | ||
| .await | ||
| .map_err(MachineInitError::AttestationServer)?; | ||
| return Ok(Some(attest)); | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| Ok(None) | ||
| } | ||
|
|
||
| async fn create_storage_backend_from_spec( | ||
|
|
@@ -672,6 +689,96 @@ impl MachineInitializer<'_> { | |
| } | ||
| } | ||
|
|
||
| /// Collect the necessary information out of the VM under construction into | ||
| /// the provided `AttestationSocketInit`. This is expected to populate | ||
| /// `attest_init` with information so the caller can spawn off | ||
| /// `AttestationSockInit::run`. | ||
| pub fn prepare_rot_initializer( | ||
| &self, | ||
| vm_rot: &mut AttestationSock, | ||
| ) -> Result<(), MachineInitError> { | ||
| let uuid = self.properties.id; | ||
|
|
||
| // The first boot entry is a key into `self.spec.disks`, which is how | ||
| // we'll get to a Crucible volume backing this boot option. | ||
| let boot_disk_entry = | ||
| self.spec.boot_settings.as_ref().and_then(|settings| { | ||
| if settings.order.len() >= 2 { | ||
| // In a rack we only configure propolis-server with zero or | ||
| // one boot disks. It's possible to provide a fuller list, | ||
| // and in the future the product may actually expose such a | ||
| // capability. At that time, we'll need to have a reckoning | ||
| // for what "boot disk measurement" from the RoT actually | ||
| // means; it probably "should" be "the measurement of the | ||
| // disk that EDK2 decided to boot into", but that | ||
| // communication to and from the guest is a little more | ||
| // complicated than we want or need to build out today. | ||
| // | ||
| // Since as the system exists we either have no specific | ||
| // boot disk (and don't know where the guest is expected to | ||
| // end up), or one boot disk (and can determine which disk | ||
| // to collect a measurement of before even running guest | ||
| // firmware), we encode this expectation up front. If the | ||
| // product has changed such that this assert is reached, | ||
| // "that's exciting!" and "sorry for crashing your | ||
| // Propolis". | ||
| panic!( | ||
| "Unsupported VM RoT configuration: \ | ||
| more than one boot disk" | ||
| ); | ||
|
hawkw marked this conversation as resolved.
|
||
| } | ||
|
|
||
| settings.order.first() | ||
| }); | ||
|
|
||
| let crucible_volume = if let Some(entry) = boot_disk_entry { | ||
| let disk_dev = | ||
| self.spec.disks.get(&entry.device_id).ok_or_else(|| { | ||
| MachineInitError::BootOrderEntryWithoutDevice( | ||
| entry.device_id.clone(), | ||
| ) | ||
| })?; | ||
|
|
||
| let backend_id = match &disk_dev.device_spec { | ||
| spec::StorageDevice::Virtio(disk) => &disk.backend_id, | ||
| spec::StorageDevice::Nvme(disk) => &disk.backend_id, | ||
| }; | ||
|
|
||
| let Some(block_backend) = self.block_backends.get(backend_id) | ||
| else { | ||
| return Err(MachineInitError::DeviceWithoutBlockBackend { | ||
| device_id: entry.device_id.to_owned(), | ||
| backend_id: backend_id.to_owned(), | ||
| }); | ||
| }; | ||
|
|
||
| if let Some(backend) = | ||
| block_backend.as_any().downcast_ref::<block::CrucibleBackend>() | ||
| { | ||
| if backend.is_read_only() { | ||
| Some(backend.clone_volume()) | ||
| } else { | ||
| // Disk must be read-only to be used for attestation. | ||
| slog::info!(self.log, "boot disk is not read-only"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe this should explicitly state that this means it will not be attested?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. took a crack at this in 014950e |
||
| None | ||
| } | ||
| } else { | ||
| // Probably fine, just not handled right now. | ||
| slog::warn!( | ||
| self.log, | ||
| "VM RoT ignoring boot disk: not a Crucible volume" | ||
| ); | ||
| None | ||
| } | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| vm_rot.prepare_instance_conf(uuid, crucible_volume); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| /// Initializes the storage devices and backends listed in this | ||
| /// initializer's instance spec. | ||
| /// | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.