@pgpm/defaults 0.15.2 → 0.15.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Makefile CHANGED
@@ -1,5 +1,5 @@
1
1
  EXTENSION = pgpm-defaults
2
- DATA = sql/pgpm-defaults--0.14.0.sql
2
+ DATA = sql/pgpm-defaults--0.15.3.sql
3
3
 
4
4
  PG_CONFIG = pg_config
5
5
  PGXS := $(shell $(PG_CONFIG) --pgxs)
package/README.md CHANGED
@@ -165,6 +165,21 @@ All access must be explicitly granted:
165
165
 
166
166
  ## Integration with Other Packages
167
167
 
168
+ ### With PGPM roles
169
+
170
+ ```bash
171
+ # Ensure standard roles exist
172
+ pgpm admin-users bootstrap
173
+ ```
174
+
175
+ Then grant permissions to roles:
176
+
177
+ ```sql
178
+ -- Grant permissions to roles
179
+ GRANT CONNECT ON DATABASE mydb TO anonymous, authenticated, administrator;
180
+ GRANT USAGE ON SCHEMA public TO anonymous, authenticated, administrator;
181
+ ```
182
+
168
183
  ### With Application Tables
169
184
 
170
185
  ```sql
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpm/defaults",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "description": "Security defaults and baseline configurations",
5
5
  "author": "Dan Lynch <pyramation@gmail.com>",
6
6
  "contributors": [
@@ -21,10 +21,10 @@
21
21
  "test:watch": "jest --watch"
22
22
  },
23
23
  "dependencies": {
24
- "@pgpm/verify": "0.15.2"
24
+ "@pgpm/verify": "0.15.4"
25
25
  },
26
26
  "devDependencies": {
27
- "pgpm": "^1.0.0"
27
+ "pgpm": "^1.2.2"
28
28
  },
29
29
  "repository": {
30
30
  "type": "git",
@@ -34,5 +34,5 @@
34
34
  "bugs": {
35
35
  "url": "https://github.com/constructive-io/pgpm-modules/issues"
36
36
  },
37
- "gitHead": "92a241bab64c7b20e85e55a7bd314089907fabba"
37
+ "gitHead": "aad0dbef0336d6c18d027120ef9addc418822edd"
38
38
  }
@@ -1,6 +1,6 @@
1
1
  # pgpm-defaults extension
2
2
  comment = 'pgpm-defaults extension'
3
- default_version = '0.14.0'
3
+ default_version = '0.15.3'
4
4
  module_pathname = '$libdir/pgpm-defaults'
5
5
  requires = 'plpgsql,pgpm-verify'
6
6
  relocatable = false
@@ -1,17 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`defaults security configurations configuration verification should create snapshot of security configuration 1`] = `
4
- {
5
- "securityState": [
6
- {
7
- "current_user": "postgres",
8
- "database_name": "test-database",
9
- "default_func_acl_count": "2",
10
- "public_db_connect": false,
11
- "public_db_create": false,
12
- "public_schema_create": false,
13
- "public_schema_usage": true,
14
- },
15
- ],
16
- }
17
- `;
@@ -1,326 +0,0 @@
1
- import { getConnections, PgTestClient, snapshot } from 'pgsql-test';
2
-
3
- let pg: PgTestClient;
4
- let teardown: () => Promise<void>;
5
-
6
- describe('defaults security configurations', () => {
7
- beforeAll(async () => {
8
- ({ pg, teardown } = await getConnections());
9
- });
10
-
11
- afterAll(async () => {
12
- await teardown();
13
- });
14
-
15
- beforeEach(async () => {
16
- await pg.beforeEach();
17
- });
18
-
19
- afterEach(async () => {
20
- await pg.afterEach();
21
- });
22
-
23
- describe('database privileges', () => {
24
- it('should revoke all database privileges from PUBLIC', async () => {
25
- // Get current database name
26
- const [dbInfo] = await pg.any(`SELECT current_database() as db_name`);
27
-
28
- // Check database privileges for PUBLIC role
29
- const privileges = await pg.any(`
30
- SELECT
31
- datacl
32
- FROM pg_database
33
- WHERE datname = $1
34
- `, [dbInfo.db_name]);
35
-
36
- expect(privileges).toHaveLength(1);
37
-
38
- // If datacl is null, it means no explicit privileges (good)
39
- // If datacl exists, check that PUBLIC doesn't appear or has no privileges
40
- if (privileges[0].datacl) {
41
- const aclString = privileges[0].datacl.toString();
42
- // PUBLIC should not appear in the ACL with any privileges
43
- // The ACL format is {user=privileges/grantor,...}
44
- // PUBLIC would appear as "=privileges/grantor" or "PUBLIC=privileges/grantor"
45
- expect(aclString).not.toMatch(/(?:^|,)(?:PUBLIC)?=[^,}]*[CTc][^,}]*(?:,|$)/);
46
- }
47
- // If datacl is null, that's actually what we want - no explicit privileges
48
- });
49
-
50
- it('should verify database connection still works for current user', async () => {
51
- // Verify we can still connect and run queries
52
- const [result] = await pg.any(`SELECT 1 as test`);
53
- expect(result.test).toBe(1);
54
-
55
- // Verify we can see current user
56
- const [userResult] = await pg.any(`SELECT current_user as username`);
57
- expect(userResult.username).toBeDefined();
58
- });
59
- });
60
-
61
- describe('function execution privileges', () => {
62
- beforeEach(async () => {
63
- // Create a test function to check default privileges
64
- await pg.any(`
65
- CREATE OR REPLACE FUNCTION test_default_function()
66
- RETURNS text AS $$
67
- BEGIN
68
- RETURN 'test result';
69
- END;
70
- $$ LANGUAGE plpgsql;
71
- `);
72
- });
73
-
74
- it('should have revoked default EXECUTE privileges from PUBLIC on functions', async () => {
75
- // Check if PUBLIC has execute privileges on our test function
76
- const privileges = await pg.any(`
77
- SELECT
78
- has_function_privilege('public', 'test_default_function()', 'execute') as public_can_execute
79
- `);
80
-
81
- expect(privileges[0].public_can_execute).toBe(false);
82
- });
83
-
84
- it('should allow current user to execute functions they create', async () => {
85
- // Current user should still be able to execute their own functions
86
- const [result] = await pg.any(`SELECT test_default_function() as result`);
87
- expect(result.result).toBe('test result');
88
- });
89
-
90
- it('should verify default privileges are set for new functions', async () => {
91
- // Check the default privileges configuration
92
- const defaultPrivs = await pg.any(`
93
- SELECT
94
- defaclrole,
95
- defaclnamespace,
96
- defaclobjtype,
97
- defaclacl
98
- FROM pg_default_acl
99
- WHERE defaclobjtype = 'f' -- functions
100
- `);
101
-
102
- // Should have at least one default ACL entry for functions
103
- expect(defaultPrivs.length).toBeGreaterThanOrEqual(0);
104
-
105
- // The key test is that PUBLIC cannot execute functions by default
106
- // We already verified this in the previous test, so this is more about
107
- // confirming the configuration exists
108
- if (defaultPrivs.length > 0) {
109
- for (const priv of defaultPrivs) {
110
- if (priv.defaclacl) {
111
- const aclString = priv.defaclacl.toString();
112
- // Check that PUBLIC doesn't have execute (X) privileges
113
- // PUBLIC would appear as "=X/grantor" or "PUBLIC=X/grantor"
114
- expect(aclString).not.toMatch(/(?:^|,)(?:PUBLIC)?=[^,}]*X[^,}]*(?:,|$)/);
115
- }
116
- }
117
- }
118
- });
119
- });
120
-
121
- describe('schema privileges', () => {
122
- it('should revoke CREATE privileges on public schema from PUBLIC', async () => {
123
- // Check if PUBLIC can create objects in public schema
124
- const privileges = await pg.any(`
125
- SELECT
126
- has_schema_privilege('public', 'public', 'create') as public_can_create
127
- `);
128
-
129
- expect(privileges[0].public_can_create).toBe(false);
130
- });
131
-
132
- it('should allow current user to create objects in public schema', async () => {
133
- // Current user should still be able to create tables
134
- await expect(
135
- pg.any(`
136
- CREATE TABLE test_table_creation (
137
- id serial PRIMARY KEY,
138
- name text
139
- )
140
- `)
141
- ).resolves.not.toThrow();
142
-
143
- // Clean up
144
- await pg.any(`DROP TABLE test_table_creation`);
145
- });
146
-
147
- it('should verify public schema still exists and is usable', async () => {
148
- // Verify public schema exists
149
- const schemas = await pg.any(`
150
- SELECT schema_name
151
- FROM information_schema.schemata
152
- WHERE schema_name = 'public'
153
- `);
154
-
155
- expect(schemas).toHaveLength(1);
156
- expect(schemas[0].schema_name).toBe('public');
157
- });
158
- });
159
-
160
- describe('security verification', () => {
161
- it('should have secure default configuration applied', async () => {
162
- // Test that we can create objects but PUBLIC cannot access them by default
163
- await pg.any(`
164
- CREATE TABLE test_security_table (
165
- id serial PRIMARY KEY,
166
- secret_data text
167
- )
168
- `);
169
-
170
- // Insert test data
171
- await pg.any(`
172
- INSERT INTO test_security_table (secret_data)
173
- VALUES ('confidential information')
174
- `);
175
-
176
- // Current user should be able to access
177
- const [result] = await pg.any(`
178
- SELECT secret_data FROM test_security_table LIMIT 1
179
- `);
180
- expect(result.secret_data).toBe('confidential information');
181
-
182
- // Check table privileges for PUBLIC
183
- const tablePrivs = await pg.any(`
184
- SELECT
185
- has_table_privilege('public', 'test_security_table', 'select') as public_can_select,
186
- has_table_privilege('public', 'test_security_table', 'insert') as public_can_insert
187
- `);
188
-
189
- // PUBLIC should not have privileges unless explicitly granted
190
- expect(tablePrivs[0].public_can_select).toBe(false);
191
- expect(tablePrivs[0].public_can_insert).toBe(false);
192
- });
193
-
194
- it('should verify current user retains necessary privileges', async () => {
195
- const [currentUser] = await pg.any(`SELECT current_user as username`);
196
-
197
- // Current user should have necessary privileges to work
198
- const userPrivs = await pg.any(`
199
- SELECT
200
- has_schema_privilege($1, 'public', 'usage') as can_use_public,
201
- has_schema_privilege($1, 'public', 'create') as can_create_in_public
202
- `, [currentUser.username]);
203
-
204
- expect(userPrivs[0].can_use_public).toBe(true);
205
- expect(userPrivs[0].can_create_in_public).toBe(true);
206
- });
207
- });
208
-
209
- describe('privilege inheritance', () => {
210
- it('should verify that new schemas inherit secure defaults', async () => {
211
- // Create a new schema
212
- await pg.any(`CREATE SCHEMA test_new_schema`);
213
-
214
- // Create a function in the new schema
215
- await pg.any(`
216
- CREATE OR REPLACE FUNCTION test_new_schema.test_inherited_function()
217
- RETURNS text AS $$
218
- BEGIN
219
- RETURN 'inherited test';
220
- END;
221
- $$ LANGUAGE plpgsql;
222
- `);
223
-
224
- // PUBLIC should not have execute privileges due to default privilege settings
225
- const privileges = await pg.any(`
226
- SELECT
227
- has_function_privilege('public', 'test_new_schema.test_inherited_function()', 'execute') as public_can_execute
228
- `);
229
-
230
- expect(privileges[0].public_can_execute).toBe(false);
231
-
232
- // Clean up
233
- await pg.any(`DROP SCHEMA test_new_schema CASCADE`);
234
- });
235
-
236
- it('should allow explicit privilege grants to work', async () => {
237
- // Create a test table
238
- await pg.any(`
239
- CREATE TABLE test_explicit_grants (
240
- id serial PRIMARY KEY,
241
- data text
242
- )
243
- `);
244
-
245
- // Explicitly grant SELECT to PUBLIC
246
- await pg.any(`GRANT SELECT ON test_explicit_grants TO PUBLIC`);
247
-
248
- // Now PUBLIC should have SELECT privilege
249
- const privileges = await pg.any(`
250
- SELECT
251
- has_table_privilege('public', 'test_explicit_grants', 'select') as public_can_select
252
- `);
253
-
254
- expect(privileges[0].public_can_select).toBe(true);
255
-
256
- // But not other privileges
257
- const otherPrivs = await pg.any(`
258
- SELECT
259
- has_table_privilege('public', 'test_explicit_grants', 'insert') as public_can_insert,
260
- has_table_privilege('public', 'test_explicit_grants', 'update') as public_can_update
261
- `);
262
-
263
- expect(otherPrivs[0].public_can_insert).toBe(false);
264
- expect(otherPrivs[0].public_can_update).toBe(false);
265
- });
266
- });
267
-
268
- describe('configuration verification', () => {
269
- it('should have applied all expected security configurations', async () => {
270
- // Verify the three main security configurations are in place:
271
-
272
- // 1. Database privileges revoked from PUBLIC
273
- const [dbInfo] = await pg.any(`SELECT current_database() as db_name`);
274
- const [dbPrivs] = await pg.any(`
275
- SELECT
276
- has_database_privilege('public', $1, 'connect') as public_can_connect,
277
- has_database_privilege('public', $1, 'create') as public_can_create_db
278
- `, [dbInfo.db_name]);
279
-
280
- // Note: PUBLIC might still have CONNECT (that's usually okay)
281
- // but should not have CREATE privileges
282
- expect(dbPrivs.public_can_create_db).toBe(false);
283
-
284
- // 2. Function execution revoked from PUBLIC by default
285
- const defaultFuncPrivs = await pg.any(`
286
- SELECT count(*) as count
287
- FROM pg_default_acl
288
- WHERE defaclobjtype = 'f'
289
- `);
290
- // Should have some default ACL configuration
291
- expect(parseInt(defaultFuncPrivs[0].count)).toBeGreaterThanOrEqual(0);
292
-
293
- // 3. CREATE on public schema revoked from PUBLIC
294
- const [schemaPrivs] = await pg.any(`
295
- SELECT
296
- has_schema_privilege('public', 'public', 'create') as public_can_create_in_public
297
- `);
298
- expect(schemaPrivs.public_can_create_in_public).toBe(false);
299
- });
300
-
301
- it('should create snapshot of security configuration', async () => {
302
- // Get current security state for snapshot testing
303
- const [currentUser] = await pg.any(`SELECT current_user as username`);
304
- const [dbInfo] = await pg.any(`SELECT current_database() as db_name`);
305
-
306
- const securityState = await pg.any(`
307
- SELECT
308
- $1 as current_user,
309
- $2 as database_name,
310
- has_database_privilege('public', $2, 'connect') as public_db_connect,
311
- has_database_privilege('public', $2, 'create') as public_db_create,
312
- has_schema_privilege('public', 'public', 'usage') as public_schema_usage,
313
- has_schema_privilege('public', 'public', 'create') as public_schema_create,
314
- (SELECT count(*) FROM pg_default_acl WHERE defaclobjtype = 'f') as default_func_acl_count
315
- `, [currentUser.username, dbInfo.db_name]);
316
-
317
- // Normalize the database name for consistent snapshots
318
- const normalizedSecurityState = securityState.map(state => ({
319
- ...state,
320
- database_name: 'test-database' // Replace dynamic name with static value
321
- }));
322
-
323
- expect(snapshot({ securityState: normalizedSecurityState })).toMatchSnapshot();
324
- });
325
- });
326
- });