From 7fef45c7dfae18a98af2b3b78b823f088d6b4089 Mon Sep 17 00:00:00 2001 From: Diego Imbert Date: Tue, 28 Jul 2026 12:34:13 +0200 Subject: [PATCH] fix(datatables): manage instance ephemeral roles as the cluster owner across ownership transfers Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_013fBWmrMs2DbpU1iYdCvGX6 --- .../src/datatable_permissions.rs | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/backend/windmill-common/src/datatable_permissions.rs b/backend/windmill-common/src/datatable_permissions.rs index 559c662acd..928df26c81 100644 --- a/backend/windmill-common/src/datatable_permissions.rs +++ b/backend/windmill-common/src/datatable_permissions.rs @@ -815,7 +815,17 @@ async fn drop_or_disable_on_target( is_instance: bool, role: &str, ) -> Result { - let client = connect_target(owner, db).await?; + // On instance databases act as the main pool's user: a `CREATEROLE` role + // may only drop roles it created itself, so the dedicated owner role + // cannot clean up roles minted before an ownership transfer (or by a + // previous owner). The main user has authority over the whole cluster. + // External databases keep using the resource's own user, which created + // its roles and can therefore drop them. + let client = if is_instance { + connect_instance_db_as_superuser(db, &owner.dbname).await? + } else { + connect_target(owner, db).await? + }; let active: i64 = client .query_one( "SELECT count(*) FROM pg_stat_activity WHERE usename = $1", @@ -1041,13 +1051,29 @@ async fn ensure_ephemeral_role( } let client = connect_target(owner, db).await?; - if role_exists(&client, &role).await? { + // Role management on instance databases runs as the main pool's user (see + // `drop_or_disable_on_target`); grants below still run as the owner, which + // is what must appear in `ALTER DEFAULT PRIVILEGES FOR ROLE`. + let role_admin = if is_instance { + connect_instance_db_as_superuser(db, &owner.dbname).await? + } else { + connect_target(owner, db).await? + }; + if role_exists(&role_admin, &role).await? { if is_instance { revoke_instance_connect(db, &owner.dbname, &role).await; } - guarded_drop_role(&client, &role).await?; + guarded_drop_role(&role_admin, &role).await?; } - create_role(db, &client, &role, &password, is_instance, &owner.dbname).await?; + create_role( + db, + &role_admin, + &role, + &password, + is_instance, + &owner.dbname, + ) + .await?; if is_instance { harden_instance_databases(db, &owner.dbname).await?;