diff --git a/backend/.sqlx/query-e881975096292b5c4ffaa5fe4256cde74a2aa805c4901e2a00510e70f52dc4a0.json b/backend/.sqlx/query-e881975096292b5c4ffaa5fe4256cde74a2aa805c4901e2a00510e70f52dc4a0.json
new file mode 100644
index 0000000000..f6bb8fba4d
--- /dev/null
+++ b/backend/.sqlx/query-e881975096292b5c4ffaa5fe4256cde74a2aa805c4901e2a00510e70f52dc4a0.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT workspace_id FROM datatable_owner_role WHERE dbname = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "workspace_id",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false
+ ]
+ },
+ "hash": "e881975096292b5c4ffaa5fe4256cde74a2aa805c4901e2a00510e70f52dc4a0"
+}
diff --git a/backend/windmill-api-settings/src/lib.rs b/backend/windmill-api-settings/src/lib.rs
index bfd0848bd4..520762a70f 100644
--- a/backend/windmill-api-settings/src/lib.rs
+++ b/backend/windmill-api-settings/src/lib.rs
@@ -1698,8 +1698,7 @@ async fn setup_custom_instance_pg_database_inner(
GRANT CREATE ON SCHEMA public TO custom_instance_user;
GRANT CREATE ON DATABASE \"{dbname}\" TO custom_instance_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
- GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO custom_instance_user;
- ALTER ROLE custom_instance_user CREATEROLE;"
+ GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO custom_instance_user;"
))
.await
.map_err(|e| {
@@ -1709,6 +1708,21 @@ async fn setup_custom_instance_pg_database_inner(
))
})?;
+ // Re-running setup on a data table database that has fine-grained
+ // permissions enabled just re-granted the shared role the access the
+ // feature deliberately took away. Hand the database back to its dedicated
+ // owner role (idempotent, re-asserts the revoke).
+ if let Some(w_id) = sqlx::query_scalar!(
+ "SELECT workspace_id FROM datatable_owner_role WHERE dbname = $1",
+ dbname
+ )
+ .fetch_optional(db)
+ .await?
+ {
+ windmill_common::datatable_permissions::provision_instance_owner_role(db, &w_id, dbname)
+ .await?;
+ }
+
// The replication attribute lives on a dedicated role used by postgres trigger
// connections. The getter creates the role (with its stored password) when the
// migration couldn't.
diff --git a/backend/windmill-api-workspaces/src/datatable_permissions_api.rs b/backend/windmill-api-workspaces/src/datatable_permissions_api.rs
index 483351999e..2ea104a195 100644
--- a/backend/windmill-api-workspaces/src/datatable_permissions_api.rs
+++ b/backend/windmill-api-workspaces/src/datatable_permissions_api.rs
@@ -159,9 +159,10 @@ pub(crate) async fn check_shared_database_forbid(
Ok(())
}
-/// The owner role creates/drops the ephemeral roles, so it needs CREATEROLE
-/// (or superuser). True by construction for freshly provisioned instance
-/// databases; verified here for external resources and legacy instance DBs.
+/// External data tables mint their ephemeral roles as the resource's own user,
+/// so that user needs CREATEROLE (or superuser). Instance databases don't:
+/// their roles are managed by the main pool user, and the shared role is
+/// deliberately kept without CREATEROLE — never call this for them.
pub(crate) async fn check_owner_can_create_roles(
db: &DB,
w_id: &str,
@@ -249,8 +250,9 @@ async fn set_datatable_permissions(
== windmill_common::workspaces::DataTableCatalogResourceType::Instance
{
provision_instance_owner_role(&db, &w_id, &config.database.resource_path).await?;
+ } else {
+ check_owner_can_create_roles(&db, &w_id, &config).await?;
}
- check_owner_can_create_roles(&db, &w_id, &config).await?;
}
let args_for_audit = format!("{:?}", perms);
audit_log(
@@ -317,6 +319,15 @@ async fn check_datatable_permissions_setup(
check_datatable_permissions_ee_license().await?;
let config = get_datatable_config(&db, &w_id, &datatable_name).await?;
check_shared_database_forbid(&db, &w_id, &datatable_name, &config.database, true).await?;
+ // Only external targets need the CREATEROLE check: running it on an
+ // instance target would report on the shared role and advise granting it
+ // CREATEROLE — the very attribute this feature must keep away from the
+ // role that user SQL runs as.
+ if config.database.resource_type
+ != windmill_common::workspaces::DataTableCatalogResourceType::Postgresql
+ {
+ return Ok("ok".to_string());
+ }
check_owner_can_create_roles(&db, &w_id, &config).await?;
Ok("ok".to_string())
}
diff --git a/backend/windmill-api-workspaces/src/workspaces.rs b/backend/windmill-api-workspaces/src/workspaces.rs
index c0d0649663..d5272453c6 100644
--- a/backend/windmill-api-workspaces/src/workspaces.rs
+++ b/backend/windmill-api-workspaces/src/workspaces.rs
@@ -3139,10 +3139,9 @@ async fn edit_datatable_config(
}
}
// An external target must be able to create the enforcement roles.
- // Instance targets are skipped: their shared role's CREATEROLE is
- // cluster-wide (granted at provisioning, re-checked at grant-save),
- // and the instance database may legitimately not be set up yet when
- // the config is saved.
+ // Instance targets are skipped: their roles are managed by the main
+ // pool user, and the instance database may legitimately not be set up
+ // yet when the config is saved.
if self_enabled && dt.database.resource_type == DataTableCatalogResourceType::Postgresql {
if let Err(e) =
crate::datatable_permissions_api::check_owner_can_create_roles(&db, &w_id, dt).await
diff --git a/backend/windmill-api-workspaces/src/workspaces_extra.rs b/backend/windmill-api-workspaces/src/workspaces_extra.rs
index d21c306d61..2308cf040a 100644
--- a/backend/windmill-api-workspaces/src/workspaces_extra.rs
+++ b/backend/windmill-api-workspaces/src/workspaces_extra.rs
@@ -949,7 +949,8 @@ pub(crate) async fn delete_workspace(
.await
.map_err(|e| {
Error::internal_err(format!(
- "cannot delete workspace {w_id}: returning ownership of instance database {dbname} to the shared role failed: {e}. Retry when it is reachable."
+ "cannot delete workspace {w_id}: returning ownership of instance database \
+ {dbname} to the shared role failed: {e}. Retry when it is reachable."
))
})?;
}
diff --git a/backend/windmill-common/src/datatable_permissions.rs b/backend/windmill-common/src/datatable_permissions.rs
index 928df26c81..a29101bdc5 100644
--- a/backend/windmill-common/src/datatable_permissions.rs
+++ b/backend/windmill-common/src/datatable_permissions.rs
@@ -421,7 +421,11 @@ pub fn instance_owner_role_name(dbname: &str) -> String {
/// Credentials of a protected instance database's dedicated owner role, if it
/// has been provisioned.
-pub async fn instance_owner_creds(db: &DB, dbname: &str) -> Result