fix: create windmill_admin without BYPASSRLS on managed dbs

On managed Postgres (Scaleway, RDS, Azure, ...) the connecting user is not
a superuser and cannot create a role WITH BYPASSRLS. The early migrations
create windmill_admin WITH BYPASSRLS inside DO/EXCEPTION blocks that swallow
the resulting "permission denied to create role" error, so the role is never
created. The first bare `GRANT ... TO windmill_admin` migration
(20221211192539) then hard-fails with `role "windmill_admin" does not exist`,
aborting all initial migrations.

Bootstrap windmill_user and windmill_admin before migrations run, falling
back to a plain windmill_admin (no BYPASSRLS) when BYPASSRLS is not permitted.
Admin access remains correct in that case via the explicit admin_policy
(USING true) entries already created for every RLS table.

Fixes WIN-2042

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-06-14 22:24:19 +00:00
parent 3cf4083960
commit 57ad009f9f
+45
View File
@@ -254,10 +254,55 @@ impl Migrate for CustomMigrator {
}
}
/// Ensure the `windmill_user` and `windmill_admin` roles exist before migrations run.
///
/// The early migrations create these roles inside `DO`/`EXCEPTION` blocks that swallow any
/// error, and `windmill_admin` is created `WITH BYPASSRLS`. On managed Postgres (Scaleway,
/// RDS, Azure, ...) the connecting user is not a superuser and cannot create a BYPASSRLS
/// role, so that creation silently fails and the role never exists — which then makes the
/// first bare `GRANT ... TO windmill_admin` migration hard-fail. We bootstrap the roles here,
/// falling back to a plain `windmill_admin` (no BYPASSRLS) when BYPASSRLS is not permitted;
/// admin access is then enforced by the per-table `FOR ALL TO windmill_admin USING (true)`
/// policies that the migrations create on every RLS-enabled table.
async fn ensure_windmill_roles(db: &DB) {
let bootstrap = r#"
DO
$$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'windmill_user') THEN
BEGIN
CREATE ROLE windmill_user;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Could not create windmill_user role: %', SQLERRM;
END;
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'windmill_admin') THEN
BEGIN
CREATE ROLE windmill_admin WITH BYPASSRLS;
EXCEPTION WHEN OTHERS THEN
BEGIN
CREATE ROLE windmill_admin;
RAISE NOTICE 'Created windmill_admin without BYPASSRLS (managed db without superuser)';
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Could not create windmill_admin role: %', SQLERRM;
END;
END;
END IF;
END
$$;
"#;
if let Err(err) = sqlx::query(bootstrap).execute(db).await {
tracing::info!("Could not bootstrap windmill roles: {err:#}");
}
}
pub async fn migrate(
db: &DB,
mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
) -> Result<Option<JoinHandle<()>>, Error> {
ensure_windmill_roles(db).await;
let migrator = db.acquire().await?;
let mut custom_migrator = CustomMigrator { inner: migrator };