-
Notifications
You must be signed in to change notification settings - Fork 29
fix: revoke default privilege grants on new tables #257
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b543284
0da97ff
f5a13be
34f7e6c
9bd8a9c
4da5f88
6cdc180
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -356,3 +356,77 @@ func privilegesCoveredBy(privs, coveringPrivs []string) bool { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // computeRevokedDefaultGrants finds privileges that would be auto-granted by default privileges | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // on new tables, but should be explicitly revoked because the user didn't include them in the new state. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // See https://github.com/pgschema/pgschema/issues/253 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func computeRevokedDefaultGrants(addedTables []*ir.Table, newPrivs map[string]*ir.Privilege, defaultPrivileges []*ir.DefaultPrivilege) []*ir.Privilege { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var revokedPrivs []*ir.Privilege | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Build an index of privileges by (ObjectType:ObjectName:Grantee) for O(1) lookups | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This avoids O(n²) complexity when scanning newPrivs for each (table, default privilege) pair | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use a separate map to track merged privilege sets to avoid mutating shared IR objects | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privSetByObjectKey := make(map[string]map[string]bool) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, p := range newPrivs { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key := p.GetObjectKey() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if existing, ok := privSetByObjectKey[key]; ok { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Merge privileges from both entries | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, priv := range p.Privileges { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| existing[priv] = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+366
to
+375
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privSet := make(map[string]bool) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, priv := range p.Privileges { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privSet[priv] = true | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privSetByObjectKey[key] = privSet | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // For each new table, check which default privileges would auto-grant | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, table := range addedTables { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, dp := range defaultPrivileges { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only process default privileges for TABLES | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if dp.ObjectType != ir.DefaultPrivilegeObjectTypeTables { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Look up explicit privilege for this exact (table, grantee) pair | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectKey := string(ir.PrivilegeObjectTypeTable) + ":" + table.Name + ":" + dp.Grantee | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| existingPrivSet := privSetByObjectKey[objectKey] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Compute which default privileges need to be revoked | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // (privileges in dp.Privileges but not in existingPrivSet) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var privsToRevoke []string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if existingPrivSet == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // No explicit privilege exists - revoke all default privileges | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privsToRevoke = dp.Privileges | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Compute set difference: dp.Privileges - existingPrivSet | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, p := range dp.Privileges { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !existingPrivSet[p] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| privsToRevoke = append(privsToRevoke, p) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
386
to
411
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(privsToRevoke) > 0 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create a synthetic privilege to revoke the missing default grants | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| revokedPrivs = append(revokedPrivs, &ir.Privilege{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectType: ir.PrivilegeObjectTypeTable, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ObjectName: table.Name, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Grantee: dp.Grantee, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Privileges: privsToRevoke, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| WithGrantOption: dp.WithGrantOption, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+398
to
+422
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Compute which default privileges need to be revoked | |
| // (privileges in dp.Privileges but not in existingPrivSet) | |
| var privsToRevoke []string | |
| if existingPrivSet == nil { | |
| // No explicit privilege exists - revoke all default privileges | |
| privsToRevoke = dp.Privileges | |
| } else { | |
| // Compute set difference: dp.Privileges - existingPrivSet | |
| for _, p := range dp.Privileges { | |
| if !existingPrivSet[p] { | |
| privsToRevoke = append(privsToRevoke, p) | |
| } | |
| } | |
| } | |
| if len(privsToRevoke) > 0 { | |
| // Create a synthetic privilege to revoke the missing default grants | |
| revokedPrivs = append(revokedPrivs, &ir.Privilege{ | |
| ObjectType: ir.PrivilegeObjectTypeTable, | |
| ObjectName: table.Name, | |
| Grantee: dp.Grantee, | |
| Privileges: privsToRevoke, | |
| WithGrantOption: dp.WithGrantOption, | |
| }) | |
| } | |
| // Compute which default privileges need to be revoked. | |
| // We distinguish between: | |
| // - full revokes (privilege type should not exist at all), and | |
| // - grant-option-only revokes (privilege type should exist, but | |
| // the auto-granted WITH GRANT OPTION from default privileges | |
| // should be removed). | |
| var fullRevokes []string | |
| var grantOptionOnlyRevokes []string | |
| if existingPrivSet == nil { | |
| // No explicit privilege exists - revoke all default privileges fully. | |
| fullRevokes = dp.Privileges | |
| } else { | |
| for _, p := range dp.Privileges { | |
| if !existingPrivSet[p] { | |
| // Privilege type is not desired at all: full revoke. | |
| fullRevokes = append(fullRevokes, p) | |
| } else if dp.WithGrantOption { | |
| // Privilege type is desired, but the default privilege | |
| // would auto-grant WITH GRANT OPTION. Since the desired | |
| // state only guarantees the presence of the privilege type | |
| // (tracked via existingPrivSet) and not the grant option, | |
| // emit a separate revoke of the grant option. | |
| grantOptionOnlyRevokes = append(grantOptionOnlyRevokes, p) | |
| } | |
| } | |
| } | |
| if len(fullRevokes) > 0 { | |
| // Create a synthetic privilege to fully revoke the missing default grants. | |
| revokedPrivs = append(revokedPrivs, &ir.Privilege{ | |
| ObjectType: ir.PrivilegeObjectTypeTable, | |
| ObjectName: table.Name, | |
| Grantee: dp.Grantee, | |
| Privileges: fullRevokes, | |
| WithGrantOption: false, | |
| }) | |
| } | |
| if len(grantOptionOnlyRevokes) > 0 { | |
| // Create a synthetic privilege to revoke only the grant option | |
| // for default privileges that would otherwise auto-grant it. | |
| revokedPrivs = append(revokedPrivs, &ir.Privilege{ | |
| ObjectType: ir.PrivilegeObjectTypeTable, | |
| ObjectName: table.Name, | |
| Grantee: dp.Grantee, | |
| Privileges: grantOptionOnlyRevokes, | |
| WithGrantOption: true, | |
| }) | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,9 @@ | ||
| ALTER DEFAULT PRIVILEGES FOR ROLE testuser IN SCHEMA public GRANT SELECT ON TABLES TO PUBLIC; | ||
|
|
||
| ALTER DEFAULT PRIVILEGES FOR ROLE testuser IN SCHEMA public GRANT INSERT, UPDATE ON TABLES TO app_user; | ||
|
|
||
| CREATE TABLE IF NOT EXISTS users ( | ||
| id integer, | ||
| name text NOT NULL, | ||
| CONSTRAINT users_pkey PRIMARY KEY (id) | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,9 @@ | ||
| ALTER DEFAULT PRIVILEGES FOR ROLE testuser IN SCHEMA public GRANT SELECT ON TABLES TO PUBLIC; | ||
|
|
||
| ALTER DEFAULT PRIVILEGES FOR ROLE testuser IN SCHEMA public GRANT INSERT, UPDATE ON TABLES TO app_user; | ||
|
|
||
| CREATE TABLE IF NOT EXISTS users ( | ||
| id integer, | ||
| name text NOT NULL, | ||
| CONSTRAINT users_pkey PRIMARY KEY (id) | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,25 @@ | ||
| Plan: 2 to add. | ||
| Plan: 3 to add. | ||
|
|
||
| Summary by type: | ||
| default privileges: 2 to add | ||
| tables: 1 to add | ||
|
|
||
| Default privileges: | ||
| + PUBLIC | ||
| + app_user | ||
|
|
||
| Tables: | ||
| + users | ||
|
|
||
| DDL to be executed: | ||
| -------------------------------------------------- | ||
|
|
||
| ALTER DEFAULT PRIVILEGES FOR ROLE testuser IN SCHEMA public GRANT SELECT ON TABLES TO PUBLIC; | ||
|
|
||
| ALTER DEFAULT PRIVILEGES FOR ROLE testuser IN SCHEMA public GRANT INSERT, UPDATE ON TABLES TO app_user; | ||
|
|
||
| CREATE TABLE IF NOT EXISTS users ( | ||
| id integer, | ||
| name text NOT NULL, | ||
| CONSTRAINT users_pkey PRIMARY KEY (id) | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| CREATE TABLE IF NOT EXISTS readonly_data ( | ||
| id integer, | ||
| value text, | ||
| CONSTRAINT readonly_data_pkey PRIMARY KEY (id) | ||
| ); | ||
|
|
||
| CREATE TABLE IF NOT EXISTS secrets ( | ||
| id integer, | ||
| data text, | ||
| CONSTRAINT secrets_pkey PRIMARY KEY (id) | ||
| ); | ||
|
|
||
| REVOKE DELETE, INSERT, UPDATE ON TABLE readonly_data FROM app_user; | ||
|
|
||
| REVOKE SELECT ON TABLE secrets FROM reader; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| -- Create roles for testing | ||
| DO $$ | ||
| BEGIN | ||
| IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'reader') THEN | ||
| CREATE ROLE reader; | ||
| END IF; | ||
| IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN | ||
| CREATE ROLE app_user; | ||
| END IF; | ||
| END $$; | ||
|
|
||
| -- Default privileges grant SELECT on all new tables to reader | ||
| ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO reader; | ||
|
|
||
| -- Default privileges grant SELECT, INSERT, UPDATE, DELETE on all new tables to app_user | ||
| ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user; | ||
|
|
||
| -- Create a table that should NOT inherit any default privileges for reader (full revoke) | ||
| CREATE TABLE secrets ( | ||
| id integer PRIMARY KEY, | ||
| data text | ||
| ); | ||
|
|
||
| -- Explicitly revoke the auto-granted SELECT from reader | ||
| REVOKE SELECT ON TABLE secrets FROM reader; | ||
|
|
||
| -- Create a read-only table - app_user should only have SELECT, not INSERT/UPDATE/DELETE (partial revoke) | ||
| CREATE TABLE readonly_data ( | ||
| id integer PRIMARY KEY, | ||
| value text | ||
| ); | ||
|
|
||
| -- Revoke write privileges from app_user - keep only SELECT from the default grants | ||
| REVOKE INSERT, UPDATE, DELETE ON TABLE readonly_data FROM app_user; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| -- Create roles for testing | ||
| DO $$ | ||
| BEGIN | ||
| IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'reader') THEN | ||
| CREATE ROLE reader; | ||
| END IF; | ||
| IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN | ||
| CREATE ROLE app_user; | ||
| END IF; | ||
| END $$; | ||
|
|
||
| -- Default privileges grant SELECT on all new tables to reader | ||
| ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO reader; | ||
|
|
||
| -- Default privileges grant SELECT, INSERT, UPDATE, DELETE on all new tables to app_user | ||
| ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| { | ||
| "version": "1.0.0", | ||
| "pgschema_version": "1.6.1", | ||
| "created_at": "1970-01-01T00:00:00Z", | ||
| "source_fingerprint": { | ||
| "hash": "60a01e6c2c18215bcce70b3d9d1153e7ca0371ef8e12252c152547462d1d92bd" | ||
| }, | ||
| "groups": [ | ||
| { | ||
| "steps": [ | ||
| { | ||
| "sql": "CREATE TABLE IF NOT EXISTS readonly_data (\n id integer,\n value text,\n CONSTRAINT readonly_data_pkey PRIMARY KEY (id)\n);", | ||
| "type": "table", | ||
| "operation": "create", | ||
| "path": "public.readonly_data" | ||
| }, | ||
| { | ||
| "sql": "CREATE TABLE IF NOT EXISTS secrets (\n id integer,\n data text,\n CONSTRAINT secrets_pkey PRIMARY KEY (id)\n);", | ||
| "type": "table", | ||
| "operation": "create", | ||
| "path": "public.secrets" | ||
| }, | ||
| { | ||
| "sql": "REVOKE DELETE, INSERT, UPDATE ON TABLE readonly_data FROM app_user;", | ||
| "type": "privilege", | ||
| "operation": "drop", | ||
| "path": "privileges.TABLE.readonly_data.app_user" | ||
| }, | ||
| { | ||
| "sql": "REVOKE SELECT ON TABLE secrets FROM reader;", | ||
| "type": "privilege", | ||
| "operation": "drop", | ||
| "path": "privileges.TABLE.secrets.reader" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| CREATE TABLE IF NOT EXISTS readonly_data ( | ||
| id integer, | ||
| value text, | ||
| CONSTRAINT readonly_data_pkey PRIMARY KEY (id) | ||
| ); | ||
|
|
||
| CREATE TABLE IF NOT EXISTS secrets ( | ||
| id integer, | ||
| data text, | ||
| CONSTRAINT secrets_pkey PRIMARY KEY (id) | ||
| ); | ||
|
|
||
| REVOKE DELETE, INSERT, UPDATE ON TABLE readonly_data FROM app_user; | ||
|
|
||
| REVOKE SELECT ON TABLE secrets FROM reader; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generateCreateSQLcreates added default privileges before tables, but default privilege modifications still run later ingenerateModifySQL(after CREATE TABLE). SinceALTER DEFAULT PRIVILEGESis not retroactive, any tables created in the same migration will inherit the old defaults if a default privilege is modified, which can leave newly created tables with broader or narrower access than the desired state until a follow-up apply (security window). Consider ensuring the effective default privileges (including modifications relevant to TABLES) are applied before creating new tables, or avoiding relying on default privileges for new-table privilege convergence when defaults are being modified in the same migration.