From 08374e79845f2793cab06e95191be489127d51ad Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 14 Aug 2022 18:20:27 +0200 Subject: [PATCH] feat: db users: admin -> windmill_admin, app -> windmill_user (#404) * feat: db users: admin -> windmill_admin, app -> windmill_user * clean up * backend tests * backend tests * backend tests * lock roles in first migration * check if user is superuser too * add init-db * add init-db --- .github/workflows/backend-test.yml | 4 +- README.md | 10 +- .../migrations/20220123221903_first.up.sql | 141 +++++++++--------- .../20220428085013_private_key.up.sql | 3 - ...508152326_create_role_if_not_exists.up.sql | 23 --- ...20624180013_workspace_premium_grant.up.sql | 1 - ...0220707094935_add_client_to_account.up.sql | 5 - backend/overwrite_migrations.py | 24 +++ backend/src/db.rs | 19 +-- backend/src/lib.rs | 3 - docker-compose.yml | 5 +- init-db-as-superuser.sql | 66 ++++++++ init-db.sql | 23 --- 13 files changed, 171 insertions(+), 156 deletions(-) create mode 100644 backend/overwrite_migrations.py create mode 100644 init-db-as-superuser.sql delete mode 100644 init-db.sql diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index e903ad245e..6f1f8d2fc1 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -24,7 +24,6 @@ jobs: image: postgres env: POSTGRES_DB: windmill - POSTGRES_USER: admin POSTGRES_PASSWORD: changeme options: >- @@ -37,7 +36,6 @@ jobs: - uses: Swatinem/rust-cache@v2 with: workspaces: backend -> target - - run: psql postgres://admin:changeme@postgres:5432/windmill -c "CREATE ROLE app LOGIN PASSWORD 'changeme';" - name: cargo test timeout-minutes: 5 - run: mkdir frontend/build && cd backend && DATABASE_URL=postgres://admin:changeme@postgres:5432/windmill cargo test + run: mkdir frontend/build && cd backend && DATABASE_URL=postgres://postgres:changeme@postgres:5432/windmill cargo test diff --git a/README.md b/README.md index 10f7e66b15..1ecf3df041 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,12 @@ workers, we are ## How to self-host +We only provide docker-compose setup here. For more advanced setups, like +compiling from source or using without a postgres super user, see +[documentation](https://docs.windmill.dev/docs/how-tos/self_host) + +### Docker compose + `docker compose up` with the following docker-compose is sufficient: @@ -176,10 +182,6 @@ The default super-admin user is: admin@windmill.dev / changeme From there, you can create other users (do not forget to change the password!) -Detailed instructions for more complex deployments will come soon. For simpler -docker based ones, the docker-compose.yml file contains all the necessary -informations. - ### Commercial license To self-host Windmill, you must respect the terms of the AGPLv3 license which diff --git a/backend/migrations/20220123221903_first.up.sql b/backend/migrations/20220123221903_first.up.sql index 52d7d23cc7..477ee71738 100644 --- a/backend/migrations/20220123221903_first.up.sql +++ b/backend/migrations/20220123221903_first.up.sql @@ -2,6 +2,74 @@ create SCHEMA IF NOT exists extensions; create extension if not exists "uuid-ossp" with schema extensions; +DO +$do$ +BEGIN + IF EXISTS ( + select usesuper from pg_user where usename = CURRENT_USER AND usesuper = 't') + AND NOT EXISTS ( + SELECT + FROM pg_catalog.pg_roles + WHERE rolname = 'windmill_user') THEN + + LOCK TABLE pg_catalog.pg_roles; + + CREATE ROLE windmill_user; + + GRANT ALL + ON ALL TABLES IN SCHEMA public + TO windmill_user; + + GRANT ALL PRIVILEGES + ON ALL SEQUENCES IN SCHEMA public + TO windmill_user; + + ALTER DEFAULT PRIVILEGES + FOR ROLE windmill_user + IN SCHEMA public + GRANT ALL ON TABLES TO windmill_user; + + ALTER DEFAULT PRIVILEGES + FOR ROLE windmill_user + IN SCHEMA public + GRANT ALL ON SEQUENCES TO windmill_user; + + END IF; +END +$do$; + +DO +$do$ +BEGIN + IF EXISTS (select usesuper from pg_user where usename = CURRENT_USER AND usesuper = 't') + AND NOT EXISTS ( + SELECT + FROM pg_catalog.pg_roles + WHERE rolname = 'windmill_admin') THEN + CREATE ROLE windmill_admin WITH BYPASSRLS; + + GRANT ALL + ON ALL TABLES IN SCHEMA public + TO windmill_admin; + + GRANT ALL PRIVILEGES + ON ALL SEQUENCES IN SCHEMA public + TO windmill_admin; + + ALTER DEFAULT PRIVILEGES + FOR ROLE windmill_admin + IN SCHEMA public + GRANT ALL ON TABLES TO windmill_admin; + + ALTER DEFAULT PRIVILEGES + FOR ROLE windmill_admin + IN SCHEMA public + GRANT ALL ON SEQUENCES TO windmill_admin; + END IF; +END +$do$; + + CREATE TABLE workspace ( id VARCHAR(50) PRIMARY KEY, name VARCHAR(50) NOT NULL, @@ -205,12 +273,6 @@ CREATE TABLE password ( company VARCHAR(30) ); --- CREATE TABLE invite_code ( --- code VARCHAR(20) PRIMARY KEY, --- seats_left INTEGER NOT NULL DEFAULT 0, --- seats_given INTEGER NOT NULL DEFAULT 1 --- ); - CREATE TABLE workspace_settings ( workspace_id VARCHAR(50) PRIMARY KEY REFERENCES workspace(id), @@ -277,17 +339,6 @@ CREATE TABLE variable ( CONSTRAINT proper_id CHECK (path ~ '^[ug](\/[\w-]+){2,}$') ); --- CREATE TABLE oauth( --- id VARCHAR(150) NOT NULL PRIMARY KEY, --- owner VARCHAR(50), --- workspace_id VARCHAR(50) NOT NULL REFERENCES workspace(id), --- type VARCHAR(50) NOT NULL, --- refresh_token VARCHAR(255), --- access_token VARCHAR(255) NOT NULL --- ); - --- CREATE INDEX index_oauth ON oauth (workspace_id, type, owner); - CREATE TYPE ACTION_KIND AS ENUM ('create', 'update', 'delete', 'execute'); CREATE TABLE audit ( @@ -420,35 +471,6 @@ CREATE INDEX worker_ping_on_ping_at ON worker_ping (ping_at); ALTER TABLE audit ENABLE ROW LEVEL SECURITY; CREATE POLICY audit_log_see_own ON audit FOR SELECT USING(audit.username = current_setting('session.user') or current_setting('session.is_admin')::boolean); --- USING(current_setting('session.is_admin')::boolean); - - -DO -$do$ -BEGIN - IF NOT EXISTS ( - SELECT FROM pg_catalog.pg_roles - WHERE rolname = 'app') THEN - - CREATE ROLE app LOGIN PASSWORD 'changeme'; - END IF; -END -$do$; - -GRANT SELECT ON audit TO app; - -REVOKE ALL -ON ALL TABLES IN SCHEMA public -FROM PUBLIC; - -GRANT ALL -ON ALL TABLES IN SCHEMA public -TO admin; - -ALTER DEFAULT PRIVILEGES - FOR ROLE admin - IN SCHEMA public - GRANT ALL ON TABLES TO admin; INSERT INTO usr_to_group @@ -456,12 +478,10 @@ SELECT workspace_id, 'all', username FROM (SELECT workspace_id, username from us ; DROP POLICY audit_log_see_own on audit; -GRANT ALL ON audit TO app; CREATE POLICY see_own ON audit FOR ALL USING (audit.username = current_setting('session.user')); -GRANT ALL ON queue TO app; ALTER TABLE queue ENABLE ROW LEVEL SECURITY; CREATE POLICY see_own ON queue FOR ALL @@ -470,7 +490,6 @@ USING (SPLIT_PART(queue.permissioned_as, '/', 1) = 'u' AND SPLIT_PART(queue.perm CREATE POLICY see_member ON queue FOR ALL USING (SPLIT_PART(queue.permissioned_as, '/', 1) = 'g' AND SPLIT_PART(queue.permissioned_as, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[])); -GRANT ALL ON completed_job TO app; ALTER TABLE completed_job ENABLE ROW LEVEL SECURITY; @@ -483,16 +502,6 @@ USING (SPLIT_PART(completed_job.permissioned_as, '/', 1) = 'u' AND SPLIT_PART(co CREATE POLICY see_member ON completed_job FOR ALL USING (SPLIT_PART(completed_job.permissioned_as, '/', 1) = 'g' AND SPLIT_PART(completed_job.permissioned_as, '/', 2) = any(regexp_split_to_array(current_setting('session.groups'), ',')::text[])); -GRANT SELECT ON pipenv to app; -GRANT SELECT (email, username, is_admin, workspace_id) ON usr to app; - -GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public to app; -GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public to admin; -GRANT SELECT, INSERT ON resource_type to app; - -GRANT SELECT ON worker_ping to app; -GRANT SELECT ON worker_ping to admin; - CREATE POLICY schedule ON audit FOR INSERT WITH CHECK (audit.username LIKE 'schedule-%'); @@ -508,7 +517,6 @@ $do$ EXECUTE FORMAT( $$ - GRANT ALL ON %1$I TO app; ALTER TABLE %1$I ENABLE ROW LEVEL SECURITY; CREATE POLICY see_starter ON %1$I FOR SELECT @@ -542,13 +550,11 @@ $do$ END $do$; -GRANT ALL ON group_ TO app; ALTER TABLE group_ ADD COLUMN extra_perms JSONB NOT NULL DEFAULT '{}'; CREATE INDEX group_extra_perms ON group_ USING GIN (extra_perms); -GRANT ALL ON usr_to_group TO app; ALTER TABLE usr_to_group ENABLE ROW LEVEL SECURITY; CREATE POLICY see_extra_perms_user ON usr_to_group FOR ALL @@ -562,14 +568,3 @@ WITH CHECK (exists( SELECT f.* FROM group_ g, jsonb_each_text(g.extra_perms) f WHERE usr_to_group.group_ = g.name AND usr_to_group.workspace_id = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY(regexp_split_to_array(current_setting('session.pgroups'), ',')::text[]) AND value::boolean)); - -DO -$do$ -BEGIN - IF NOT EXISTS ( - SELECT FROM pg_catalog.pg_roles -- SELECT list can be empty for this - WHERE rolname = 'admin') THEN - CREATE ROLE admin WITH BYPASSRLS LOGIN PASSWORD 'changeme'; - END IF; -END -$do$; diff --git a/backend/migrations/20220428085013_private_key.up.sql b/backend/migrations/20220428085013_private_key.up.sql index dd6dcdee70..7cb668b1c3 100644 --- a/backend/migrations/20220428085013_private_key.up.sql +++ b/backend/migrations/20220428085013_private_key.up.sql @@ -9,8 +9,5 @@ CREATE TABLE workspace_key ( PRIMARY KEY (workspace_id, kind) ); -GRANT SELECT ON workspace_key TO app; -GRANT SELECT ON workspace_key TO admin; - INSERT INTO workspace_key SELECT id as workspace_id, 'cloud' as kind, 'changeme' as key FROM workspace; diff --git a/backend/migrations/20220508152326_create_role_if_not_exists.up.sql b/backend/migrations/20220508152326_create_role_if_not_exists.up.sql index 46cac9b162..0da0a538a3 100644 --- a/backend/migrations/20220508152326_create_role_if_not_exists.up.sql +++ b/backend/migrations/20220508152326_create_role_if_not_exists.up.sql @@ -1,24 +1 @@ -- Add up migration script here -DO -$do$ -BEGIN - IF NOT EXISTS ( - SELECT - FROM pg_catalog.pg_roles - WHERE rolname = 'app') THEN - CREATE ROLE app LOGIN PASSWORD 'changeme'; - END IF; -END -$do$; - -DO -$do$ -BEGIN - IF NOT EXISTS ( - SELECT - FROM pg_catalog.pg_roles - WHERE rolname = 'admin') THEN - CREATE ROLE admin LOGIN PASSWORD 'changeme'; - END IF; -END -$do$; diff --git a/backend/migrations/20220624180013_workspace_premium_grant.up.sql b/backend/migrations/20220624180013_workspace_premium_grant.up.sql index 08ab13a24c..763abdc9b9 100644 --- a/backend/migrations/20220624180013_workspace_premium_grant.up.sql +++ b/backend/migrations/20220624180013_workspace_premium_grant.up.sql @@ -1,3 +1,2 @@ -- Add up migration script here -GRANT SELECT ON workspace TO app; diff --git a/backend/migrations/20220707094935_add_client_to_account.up.sql b/backend/migrations/20220707094935_add_client_to_account.up.sql index 149236f38e..94e0b275ba 100644 --- a/backend/migrations/20220707094935_add_client_to_account.up.sql +++ b/backend/migrations/20220707094935_add_client_to_account.up.sql @@ -10,11 +10,6 @@ ALTER TABLE account ALTER COLUMN expires_at TYPE TIMESTAMP WITH TIME ZONE; ALTER TABLE account ALTER COLUMN expires_at SET NOT NULL; ALTER TABLE account ALTER COLUMN refresh_token SET NOT NULL; -GRANT ALL ON account TO app; -GRANT ALL ON account TO admin; -GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO admin; -GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app; - ALTER TABLE account ENABLE ROW LEVEL SECURITY; diff --git a/backend/overwrite_migrations.py b/backend/overwrite_migrations.py new file mode 100644 index 0000000000..f4e8df6771 --- /dev/null +++ b/backend/overwrite_migrations.py @@ -0,0 +1,24 @@ +import sys +import os +import subprocess + +database_url = sys.argv[1] + +print(f"database_url: {database_url}") + +if database_url is None: + print("Please provide a database url") + sys.exit(1) + +for f in os.listdir("migrations"): + if f.endswith(".up.sql"): + version = f.split("_")[0] + cmd = f"cat migrations/{f} | openssl dgst -sha384 | cut -d ' ' -f 2" + ps = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT) + digest = ps.communicate()[0].decode("utf-8").strip() + + cmd = f"psql '{database_url}' -c \"UPDATE _sqlx_migrations SET checksum = '\\x{digest}' WHERE version = {version};\"" + ps = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT) + out = ps.communicate()[0].decode("utf-8").strip() + print(version, digest, out) + diff --git a/backend/src/db.rs b/backend/src/db.rs index c486c7b7c6..f3f8508b25 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -30,19 +30,6 @@ pub async fn migrate(db: &DB) -> Result<(), Error> { Ok(()) } -pub async fn setup_app_user(db: &DB, password: &str) -> Result<(), Error> { - let mut tx = db.begin().await?; - - sqlx::query(&format!("ALTER USER app WITH PASSWORD '{}'", password)) - .execute(&mut tx) - .await?; - sqlx::query(&format!("ALTER USER admin WITH PASSWORD '{}'", password)) - .execute(&mut tx) - .await?; - tx.commit().await?; - - Ok(()) -} #[derive(Clone)] pub struct UserDB { db: DB, @@ -58,7 +45,11 @@ impl UserDB { authed: &Authed, ) -> Result, sqlx::Error> { let mut tx = self.db.begin().await?; - let user = if authed.is_admin { "admin" } else { "app" }; + let user = if authed.is_admin { + "windmill_admin" + } else { + "windmill_user" + }; sqlx::query(&format!("SET LOCAL SESSION AUTHORIZATION {}", user)) .execute(&mut tx) diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 403aa11a4f..ad40b5048d 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -70,10 +70,7 @@ pub const DEFAULT_SLEEP_QUEUE: u64 = 50; pub const DEFAULT_MAX_CONNECTIONS: u32 = 100; pub async fn migrate_db(db: &DB) -> anyhow::Result<()> { - let app_password = std::env::var("APP_USER_PASSWORD").unwrap_or_else(|_| "changeme".to_owned()); - db::migrate(db).await?; - db::setup_app_user(db, &app_password).await?; Ok(()) } diff --git a/docker-compose.yml b/docker-compose.yml index bf5da58889..f95e9e81d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: restart: always volumes: - db_data:/var/lib/postgresql/data - - ./init-db.sql:/docker-entrypoint-initdb.d/create_tables.sql + # - ./init-db.sql:/docker-entrypoint-initdb.d/create_tables.sql ports: - 5432:5432 environment: @@ -26,14 +26,11 @@ services: - 80:8000 environment: - DATABASE_URL=postgres://postgres:${DB_PASSWORD}@db/windmill?sslmode=disable - - APP_USER_PASSWORD=changeme - BASE_URL=http://localhost - BASE_INTERNAL_URL=http://localhost:8000 - RUST_LOG=info - NUM_WORKERS=3 - RUST_BACKTRACE=1 - - GITHUB_OAUTH_CLIENT_ID=${GITHUB_OAUTH_CLIENT_ID} - - GITHUB_OAUTH_CLIENT_SECRET=${GITHUB_OAUTH_CLIENT_SECRET} - DISABLE_NUSER=false depends_on: diff --git a/init-db-as-superuser.sql b/init-db-as-superuser.sql new file mode 100644 index 0000000000..3633148516 --- /dev/null +++ b/init-db-as-superuser.sql @@ -0,0 +1,66 @@ +DO +$do$ +BEGIN + IF EXISTS ( + select usesuper from pg_user where usename = CURRENT_USER AND usesuper = 't') + AND NOT EXISTS ( + SELECT + FROM pg_catalog.pg_roles + WHERE rolname = 'windmill_user') THEN + + LOCK TABLE pg_catalog.pg_roles; + + CREATE ROLE windmill_user; + + GRANT ALL + ON ALL TABLES IN SCHEMA public + TO windmill_user; + + GRANT ALL PRIVILEGES + ON ALL SEQUENCES IN SCHEMA public + TO windmill_user; + + ALTER DEFAULT PRIVILEGES + FOR ROLE windmill_user + IN SCHEMA public + GRANT ALL ON TABLES TO windmill_user; + + ALTER DEFAULT PRIVILEGES + FOR ROLE windmill_user + IN SCHEMA public + GRANT ALL ON SEQUENCES TO windmill_user; + + END IF; +END +$do$; + +DO +$do$ +BEGIN + IF EXISTS (select usesuper from pg_user where usename = CURRENT_USER AND usesuper = 't') + AND NOT EXISTS ( + SELECT + FROM pg_catalog.pg_roles + WHERE rolname = 'windmill_admin') THEN + CREATE ROLE windmill_admin WITH BYPASSRLS; + + GRANT ALL + ON ALL TABLES IN SCHEMA public + TO windmill_admin; + + GRANT ALL PRIVILEGES + ON ALL SEQUENCES IN SCHEMA public + TO windmill_admin; + + ALTER DEFAULT PRIVILEGES + FOR ROLE windmill_admin + IN SCHEMA public + GRANT ALL ON TABLES TO windmill_admin; + + ALTER DEFAULT PRIVILEGES + FOR ROLE windmill_admin + IN SCHEMA public + GRANT ALL ON SEQUENCES TO windmill_admin; + END IF; +END +$do$; diff --git a/init-db.sql b/init-db.sql deleted file mode 100644 index 142d801c58..0000000000 --- a/init-db.sql +++ /dev/null @@ -1,23 +0,0 @@ -DO -$do$ -BEGIN - IF NOT EXISTS ( - SELECT - FROM pg_catalog.pg_roles - WHERE rolname = 'app') THEN - CREATE ROLE app LOGIN PASSWORD 'changeme'; - END IF; -END -$do$; - -DO -$do$ -BEGIN - IF NOT EXISTS ( - SELECT - FROM pg_catalog.pg_roles - WHERE rolname = 'admin') THEN - CREATE ROLE admin WITH BYPASSRLS LOGIN PASSWORD 'changeme'; - END IF; -END -$do$;