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
This commit is contained in:
Ruben Fiszel
2022-08-14 18:20:27 +02:00
committed by GitHub
parent 95844d3e23
commit 08374e7984
13 changed files with 171 additions and 156 deletions
+1 -3
View File
@@ -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
+6 -4
View File
@@ -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:
<https://github.com/windmill-labs/windmill/blob/main/docker-compose.yml>
@@ -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
+68 -73
View File
@@ -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$;
@@ -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;
@@ -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$;
@@ -1,3 +1,2 @@
-- Add up migration script here
GRANT SELECT ON workspace TO app;
@@ -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;
+24
View File
@@ -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)
+5 -14
View File
@@ -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<Transaction<'static, Postgres>, 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)
-3
View File
@@ -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(())
}
+1 -4
View File
@@ -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:
+66
View File
@@ -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$;
-23
View File
@@ -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$;