Skip to content
Draft
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
25 changes: 25 additions & 0 deletions nexus/db-queries/src/db/datastore/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::db::model::IdentityType;
use crate::db::model::RoleAssignment;
use async_bb8_diesel::AsyncRunQueryDsl;
use diesel::prelude::*;
use diesel::result::Error as DieselError;
use nexus_db_errors::ErrorHandler;
use nexus_db_errors::TransactionError;
use nexus_db_errors::public_error_from_diesel;
Expand All @@ -25,6 +26,7 @@ use nexus_types::external_api::policy;
use omicron_common::api::external::Error;
use omicron_common::api::external::ListResultVec;
use omicron_common::bail_unless;
use uuid::Uuid;

impl DataStore {
/// Load role assignments for built-in users and built-in roles into the
Expand Down Expand Up @@ -103,6 +105,29 @@ impl DataStore {
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
}

/// Delete all role assignments held by the given identity, on any
/// resource. Used when deleting users and groups so that deleted
/// identities don't linger in policies.
///
/// Returns the raw diesel error so this can run inside a transaction;
/// callers outside one should map it with `public_error_from_diesel`.
pub(super) async fn role_assignment_delete_by_identity(
&self,
conn: &async_bb8_diesel::Connection<DbConnection>,
identity_type: IdentityType,
identity_id: Uuid,
) -> Result<(), DieselError> {
use nexus_db_schema::schema::role_assignment::dsl;

diesel::delete(dsl::role_assignment)
.filter(dsl::identity_type.eq(identity_type))
.filter(dsl::identity_id.eq(identity_id))
.execute_async(conn)
.await?;

Ok(())
}

/// Removes all existing externally-visble role assignments on
/// `authz_resource` and adds those specified by `new_assignments`
///
Expand Down
32 changes: 14 additions & 18 deletions nexus/db-queries/src/db/datastore/scim_provider_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,15 +507,13 @@ impl<'a> CrdbScimProviderStore<'a> {
}

// Cleanup role assignment records for the deleted user
{
use nexus_db_schema::schema::role_assignment::dsl;

diesel::delete(dsl::role_assignment)
.filter(dsl::identity_type.eq(IdentityType::SiloUser))
.filter(dsl::identity_id.eq(to_db_typed_uuid(user_id)))
.execute_async(conn)
.await?;
}
self.datastore
.role_assignment_delete_by_identity(
conn,
IdentityType::SiloUser,
user_id.into_untyped_uuid(),
)
.await?;

// Finally we should cleanup all auth related things like sessions and
// tokens.
Expand Down Expand Up @@ -1217,15 +1215,13 @@ impl<'a> CrdbScimProviderStore<'a> {
}

// Cleanup role assignment records for the deleted group
{
use nexus_db_schema::schema::role_assignment::dsl;

diesel::delete(dsl::role_assignment)
.filter(dsl::identity_id.eq(to_db_typed_uuid(group_id)))
.filter(dsl::identity_type.eq(IdentityType::SiloGroup))
.execute_async(conn)
.await?;
}
self.datastore
.role_assignment_delete_by_identity(
conn,
IdentityType::SiloGroup,
group_id.into_untyped_uuid(),
)
.await?;

Ok(true)
}
Expand Down
71 changes: 71 additions & 0 deletions nexus/db-queries/src/db/datastore/silo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ impl DataStore {
opctx.authorize(authz::Action::Delete, authz_silo).await?;

use nexus_db_schema::schema::project;
use nexus_db_schema::schema::role_assignment;
use nexus_db_schema::schema::silo;
use nexus_db_schema::schema::silo_group;
use nexus_db_schema::schema::silo_group_membership;
Expand Down Expand Up @@ -561,6 +562,41 @@ impl DataStore {
"deleted {} password hashes for silo {}", updated_rows, id
);

// delete role assignments held by the silo's users
let updated_rows =
diesel::delete(role_assignment::dsl::role_assignment)
.filter(
role_assignment::dsl::identity_type
.eq(db::model::IdentityType::SiloUser),
)
.filter(
role_assignment::dsl::identity_id.eq_any(
silo_user::dsl::silo_user
.filter(
silo_user::dsl::silo_id
.eq(to_db_typed_uuid(id)),
)
.filter(silo_user::dsl::time_deleted.is_null())
.filter(
silo_user::dsl::user_provision_type
.eq(db_silo.user_provision_type),
)
.select(silo_user::dsl::id),
),
)
.execute_async(&*conn)
.await
.map_err(|e| {
public_error_from_diesel(e, ErrorHandler::Server)
})?;

debug!(
opctx.log,
"deleted {} silo user role assignments for silo {}",
updated_rows,
id
);

let updated_rows = diesel::update(silo_user::dsl::silo_user)
.filter(silo_user::dsl::silo_id.eq(to_db_typed_uuid(id)))
.filter(silo_user::dsl::time_deleted.is_null())
Expand Down Expand Up @@ -607,6 +643,41 @@ impl DataStore {
"deleted {} silo group memberships for silo {}", updated_rows, id
);

// delete role assignments held by the silo's groups
let updated_rows =
diesel::delete(role_assignment::dsl::role_assignment)
.filter(
role_assignment::dsl::identity_type
.eq(db::model::IdentityType::SiloGroup),
)
.filter(
role_assignment::dsl::identity_id.eq_any(
silo_group::dsl::silo_group
.filter(
silo_group::dsl::silo_id
.eq(to_db_typed_uuid(id)),
)
.filter(silo_group::dsl::time_deleted.is_null())
.filter(
silo_group::dsl::user_provision_type
.eq(db_silo.user_provision_type),
)
.select(silo_group::dsl::id),
),
)
.execute_async(&*conn)
.await
.map_err(|e| {
public_error_from_diesel(e, ErrorHandler::Server)
})?;

debug!(
opctx.log,
"deleted {} silo group role assignments for silo {}",
updated_rows,
id
);

// delete all silo groups
let updated_rows = diesel::update(silo_group::dsl::silo_group)
.filter(silo_group::dsl::silo_id.eq(to_db_typed_uuid(id)))
Expand Down
67 changes: 0 additions & 67 deletions nexus/db-queries/src/db/datastore/silo_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ use chrono::DateTime;
use chrono::Utc;
use diesel::prelude::*;
use nexus_db_errors::ErrorHandler;
use nexus_db_errors::TransactionError;
use nexus_db_errors::public_error_from_diesel;
use nexus_types::external_api::user;
use nexus_types::identity::Asset;
use omicron_common::api::external::CreateResult;
use omicron_common::api::external::DataPageParams;
use omicron_common::api::external::DeleteResult;
use omicron_common::api::external::Error;
use omicron_common::api::external::InternalContext;
use omicron_common::api::external::ListResultVec;
Expand Down Expand Up @@ -680,71 +678,6 @@ impl DataStore {
.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
}

pub async fn silo_group_delete(
&self,
opctx: &OpContext,
authz_silo_group: &authz::SiloGroup,
) -> DeleteResult {
opctx.authorize(authz::Action::Delete, authz_silo_group).await?;

#[derive(Debug, thiserror::Error)]
enum SiloDeleteError {
#[error("group {0} still has memberships")]
GroupStillHasMemberships(SiloGroupUuid),
}
type TxnError = TransactionError<SiloDeleteError>;

let group_id = authz_silo_group.id();
let conn = self.pool_connection_authorized(opctx).await?;

// Prefer to use "transaction_retry_wrapper"
self.transaction_non_retry_wrapper("silo_group_delete")
.transaction(&conn, |conn| async move {
use nexus_db_schema::schema::silo_group_membership;

// Don't delete groups that still have memberships
let group_memberships =
silo_group_membership::dsl::silo_group_membership
.filter(
silo_group_membership::dsl::silo_group_id
.eq(to_db_typed_uuid(group_id)),
)
.select(SiloGroupMembership::as_returning())
.limit(1)
.load_async(&conn)
.await?;

if !group_memberships.is_empty() {
return Err(TxnError::CustomError(
SiloDeleteError::GroupStillHasMemberships(group_id),
));
}

// Delete silo group
use nexus_db_schema::schema::silo_group::dsl;
diesel::update(dsl::silo_group)
.filter(dsl::id.eq(to_db_typed_uuid(group_id)))
.filter(dsl::time_deleted.is_null())
.set(dsl::time_deleted.eq(Utc::now()))
.execute_async(&conn)
.await?;

Ok(())
})
.await
.map_err(|e| match e {
TxnError::CustomError(
SiloDeleteError::GroupStillHasMemberships(id),
) => Error::invalid_request(&format!(
"group {0} still has memberships",
id
)),
TxnError::Database(error) => {
public_error_from_diesel(error, ErrorHandler::Server)
}
})
}

pub async fn silo_groups_list_by_id(
&self,
opctx: &OpContext,
Expand Down
8 changes: 8 additions & 0 deletions nexus/db-queries/src/db/datastore/silo_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,14 @@ impl DataStore {
.await?;
}

// delete the user's role assignments
self.role_assignment_delete_by_identity(
&conn,
model::IdentityType::SiloUser,
authz_silo_user_id.into_untyped_uuid(),
)
.await?;

// Delete ssh keys.
{
use nexus_db_schema::schema::ssh_key::dsl;
Expand Down
2 changes: 2 additions & 0 deletions nexus/db-schema/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,8 @@ allow_tables_to_appear_in_same_query!(
allow_tables_to_appear_in_same_query!(silo_group, silo);
allow_tables_to_appear_in_same_query!(silo_user, silo);
allow_tables_to_appear_in_same_query!(role_assignment, silo_group_membership);
allow_tables_to_appear_in_same_query!(role_assignment, silo_user);
allow_tables_to_appear_in_same_query!(role_assignment, silo_group);

table! {
identity_provider (silo_id, id) {
Expand Down
Loading
Loading