@pgpm/defaults 0.15.3 → 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgpm/defaults",
|
|
3
|
-
"version": "0.15.
|
|
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.
|
|
24
|
+
"@pgpm/verify": "0.15.4"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"pgpm": "^1.
|
|
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": "
|
|
37
|
+
"gitHead": "aad0dbef0336d6c18d027120ef9addc418822edd"
|
|
38
38
|
}
|
package/pgpm-defaults.control
CHANGED
|
@@ -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
|
-
});
|
|
File without changes
|