Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions nexus/src/app/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,36 @@ impl super::Nexus {
Ok(())
}

pub(super) async fn validate_disk_attach_params(
self: &Arc<Self>,
opctx: &OpContext,
authz_project: &authz::Project,
params: &params::InstanceDiskAttach,
) -> Result<(), Error> {
match &params.disk {
NameOrId::Id(disk_id) => {
let (.., authz_disk_project, _) =
LookupPath::new(opctx, &self.db_datastore)
.disk_id(*disk_id)
.lookup_for(authz::Action::Read)
.await?;

if authz_project.id() != authz_disk_project.id() {
return Err(Error::invalid_request(
format!(
"disk {} does not belong to project {}",
disk_id,
authz_project.id()
)
.as_str(),
));
}
Ok(())
}
NameOrId::Name(_) => Ok(()),
}
}

pub async fn project_create_disk(
self: &Arc<Self>,
opctx: &OpContext,
Expand Down
18 changes: 16 additions & 2 deletions nexus/src/app/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,23 @@ impl super::Nexus {
)));
}
for disk in &params.disks {
if let params::InstanceDiskAttachment::Create(create) = disk {
self.validate_disk_create_params(opctx, &authz_project, create)
match disk {
params::InstanceDiskAttachment::Create(params) => {
self.validate_disk_create_params(
opctx,
&authz_project,
params,
)
.await?;
}
params::InstanceDiskAttachment::Attach(params) => {
self.validate_disk_attach_params(
opctx,
&authz_project,
params,
)
.await?;
}
}
}
if params.ncpus.0 > MAX_VCPU_PER_INSTANCE {
Expand Down
53 changes: 35 additions & 18 deletions nexus/src/app/sagas/instance_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use omicron_common::api::external::Generation;
use omicron_common::api::external::IdentityMetadataCreateParams;
use omicron_common::api::external::InstanceState;
use omicron_common::api::external::Name;
use omicron_common::api::external::NameOrId;
use omicron_common::api::internal::nexus::InstanceRuntimeState;
use omicron_common::api::internal::shared::SwitchLocation;
use serde::Deserialize;
Expand Down Expand Up @@ -939,30 +940,45 @@ async fn ensure_instance_disk_attach_state(
let instance_id = params.instance_id;
let project_id = params.project_id;

let disk_name = match params.attach_params {
let disk: NameOrId = match params.attach_params {
InstanceDiskAttachment::Create(create_params) => {
db::model::Name(create_params.identity.name)
}
InstanceDiskAttachment::Attach(attach_params) => {
db::model::Name(attach_params.name)
create_params.identity.name.into()
}
InstanceDiskAttachment::Attach(attach_params) => attach_params.disk,
};

let (.., authz_instance, _db_instance) =
LookupPath::new(&opctx, &datastore)
.instance_id(instance_id)
.fetch()
.await
.map_err(ActionError::action_failed)?;
let (.., authz_instance) = LookupPath::new(&opctx, &datastore)
.instance_id(instance_id)
.lookup_for(authz::Action::Read)
.await
.map_err(ActionError::action_failed)?;

// TODO-correctness TODO-security It's not correct to re-resolve the
// disk name now. See oxidecomputer/omicron#1536.
let (.., authz_disk, _db_disk) = LookupPath::new(&opctx, &datastore)
.project_id(project_id)
.disk_name(&disk_name)
.fetch()
.await
.map_err(ActionError::action_failed)?;
let (.., attach_authz_project, authz_disk) = match disk {
NameOrId::Id(disk_id) => LookupPath::new(&opctx, &datastore)
.disk_id(disk_id)
.lookup_for(authz::Action::Read)
.await
.map_err(ActionError::action_failed)?,
NameOrId::Name(disk_name) => LookupPath::new(&opctx, &datastore)
.project_id(project_id)
.disk_name_owned(disk_name.into())
.lookup_for(authz::Action::Read)
.await
.map_err(ActionError::action_failed)?,
};

// Verify that the given disk ID is in the same project as the instance
if attach_authz_project.id() != project_id {
return Err(ActionError::action_failed(Error::invalid_request(
&format!(
"disk {} does not belong to project {}",
authz_disk.id(),
project_id
),
)));
}

if attached {
datastore
Expand Down Expand Up @@ -1384,6 +1400,7 @@ pub mod test {
use nexus_test_utils::resource_helpers::populate_ip_pool;
use nexus_test_utils::resource_helpers::DiskTest;
use nexus_test_utils_macros::nexus_test;
use omicron_common::api::external::Name;
use omicron_common::api::external::{
ByteCount, IdentityMetadataCreateParams, InstanceCpuCount,
};
Expand Down Expand Up @@ -1428,7 +1445,7 @@ pub mod test {
}],
disks: vec![params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach {
name: DISK_NAME.parse().unwrap(),
disk: DISK_NAME.parse::<Name>().unwrap().into(),
},
)],
start: false,
Expand Down
5 changes: 4 additions & 1 deletion nexus/src/app/sagas/instance_delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ mod test {
use nexus_test_utils::resource_helpers::DiskTest;
use nexus_test_utils_macros::nexus_test;
use nexus_types::identity::Resource;
use omicron_common::api::external::Name;
use omicron_common::api::external::{
ByteCount, IdentityMetadataCreateParams, InstanceCpuCount,
};
Expand Down Expand Up @@ -419,7 +420,9 @@ mod test {
pool_name: None,
}],
disks: vec![params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach { name: DISK_NAME.parse().unwrap() },
params::InstanceDiskAttach {
disk: DISK_NAME.parse::<Name>().unwrap().into(),
},
)],
start: false,
}
Expand Down
2 changes: 1 addition & 1 deletion nexus/src/app/sagas/snapshot_create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2251,7 +2251,7 @@ mod test {
network_interfaces:
params::InstanceNetworkInterfaceAttachment::None,
disks: vec![params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach { name: Name::from_str(DISK_NAME).unwrap() },
params::InstanceDiskAttach { disk: DISK_NAME.parse::<Name>().unwrap().into() },
)],
external_ips: vec![],
start: true,
Expand Down
4 changes: 3 additions & 1 deletion nexus/tests/integration_tests/disks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1505,7 +1505,9 @@ async fn create_instance_with_disk(client: &ClientTestContext) {
INSTANCE_NAME,
&params::InstanceNetworkInterfaceAttachment::Default,
vec![params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach { name: DISK_NAME.parse().unwrap() },
params::InstanceDiskAttach {
disk: DISK_NAME.parse::<Name>().unwrap().into(),
},
)],
Vec::<params::ExternalIpCreate>::new(),
)
Expand Down
42 changes: 28 additions & 14 deletions nexus/tests/integration_tests/instances.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2179,7 +2179,7 @@ async fn test_attach_one_disk_to_instance(cptestctx: &ControlPlaneTestContext) {
external_ips: vec![],
disks: vec![params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach {
name: Name::try_from(String::from("probablydata")).unwrap(),
disk: "probablydata".parse::<Name>().unwrap().into(),
},
)],
start: true,
Expand Down Expand Up @@ -2251,7 +2251,7 @@ async fn test_instance_create_attach_disks(
}),
params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach {
name: attachable_disk.identity.name,
disk: attachable_disk.identity.name.into(),
},
),
],
Expand Down Expand Up @@ -2344,10 +2344,14 @@ async fn test_instance_create_attach_disks_undo(
},
}),
params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach { name: regular_disk.identity.name },
params::InstanceDiskAttach {
disk: regular_disk.identity.name.into(),
},
),
params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach { name: faulted_disk.identity.name },
params::InstanceDiskAttach {
disk: faulted_disk.identity.name.into(),
},
),
],
start: true,
Expand Down Expand Up @@ -2420,8 +2424,10 @@ async fn test_attach_eight_disks_to_instance(
.map(|i| {
params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach {
name: Name::try_from(format!("probablydata{}", i))
.unwrap(),
disk: format!("probablydata{}", i)
.parse::<Name>()
.unwrap()
.into(),
},
)
})
Expand Down Expand Up @@ -2500,8 +2506,10 @@ async fn test_cannot_attach_nine_disks_to_instance(
.map(|i| {
params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach {
name: Name::try_from(format!("probablydata{}", i))
.unwrap(),
disk: format!("probablydata{}", i)
.parse::<Name>()
.unwrap()
.into(),
},
)
})
Expand Down Expand Up @@ -2594,8 +2602,10 @@ async fn test_cannot_attach_faulted_disks(cptestctx: &ControlPlaneTestContext) {
.map(|i| {
params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach {
name: Name::try_from(format!("probablydata{}", i))
.unwrap(),
disk: format!("probablydata{}", i)
.parse::<Name>()
.unwrap()
.into(),
},
)
})
Expand Down Expand Up @@ -2677,8 +2687,10 @@ async fn test_disks_detached_when_instance_destroyed(
.map(|i| {
params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach {
name: Name::try_from(format!("probablydata{}", i))
.unwrap(),
disk: format!("probablydata{}", i)
.parse::<Name>()
.unwrap()
.into(),
},
)
})
Expand Down Expand Up @@ -2761,8 +2773,10 @@ async fn test_disks_detached_when_instance_destroyed(
.map(|i| {
params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach {
name: Name::try_from(format!("probablydata{}", i))
.unwrap(),
disk: format!("probablydata{}", i)
.parse::<Name>()
.unwrap()
.into(),
},
)
})
Expand Down
4 changes: 3 additions & 1 deletion nexus/tests/integration_tests/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ async fn test_snapshot_basic(cptestctx: &ControlPlaneTestContext) {
network_interfaces:
params::InstanceNetworkInterfaceAttachment::None,
disks: vec![params::InstanceDiskAttachment::Attach(
params::InstanceDiskAttach { name: base_disk_name.clone() },
params::InstanceDiskAttach {
disk: base_disk_name.clone().into(),
},
)],
external_ips: vec![],
start: true,
Expand Down
4 changes: 2 additions & 2 deletions nexus/types/src/external_api/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -795,8 +795,8 @@ pub enum InstanceDiskAttachment {

#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct InstanceDiskAttach {
/// A disk name to attach
pub name: Name,
/// A disk to attach
pub disk: NameOrId,
}

/// Parameters for creating an external IP address for instances.
Expand Down
8 changes: 4 additions & 4 deletions openapi/nexus.json
Original file line number Diff line number Diff line change
Expand Up @@ -9717,11 +9717,11 @@
"description": "During instance creation, attach this disk",
"type": "object",
"properties": {
"name": {
"description": "A disk name to attach",
"disk": {
"description": "A disk to attach",
"allOf": [
{
"$ref": "#/components/schemas/Name"
"$ref": "#/components/schemas/NameOrId"
}
]
},
Expand All @@ -9733,7 +9733,7 @@
}
},
"required": [
"name",
"disk",
"type"
]
}
Expand Down