mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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$;
|
||||
Reference in New Issue
Block a user