Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions migrations/tenant/0073-revoke-grants-to-unused-operations.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
DO $$
DECLARE
anon_role text = COALESCE(current_setting('storage.anon_role', true), 'anon');
authenticated_role text = COALESCE(current_setting('storage.authenticated_role', true), 'authenticated');
BEGIN
EXECUTE 'REVOKE TRUNCATE, REFERENCES, TRIGGER ON storage.objects, storage.buckets, storage.buckets_analytics FROM ' || anon_role || ', ' || authenticated_role;
EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA storage REVOKE TRUNCATE, REFERENCES, TRIGGER ON TABLES FROM ' || anon_role || ', ' || authenticated_role;
Comment thread
ferhatelmas marked this conversation as resolved.

IF current_setting('server_version_num')::int >= 170000 THEN
EXECUTE 'REVOKE MAINTAIN ON storage.objects, storage.buckets, storage.buckets_analytics FROM ' || anon_role || ', ' || authenticated_role;
EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA storage REVOKE MAINTAIN ON TABLES FROM ' || anon_role || ', ' || authenticated_role;
END IF;

EXECUTE 'REVOKE ALL ON storage.buckets_vectors, storage.vector_indexes FROM ' || anon_role || ', ' || authenticated_role;
EXECUTE 'GRANT SELECT ON storage.buckets_vectors, storage.vector_indexes TO ' || anon_role || ', ' || authenticated_role;
END$$;
1 change: 1 addition & 0 deletions src/internal/database/migrations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ export const DBMigration = {
'list-objects-with-versions': 70,
'objects-delete-marker-index': 71,
'drop-bucketid-objname-index': 72,
'revoke-grants-to-unused-operations': 73,
} as const
69 changes: 69 additions & 0 deletions src/test/database-protection.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TenantConnection } from '@internal/database'
import { DatabaseError } from 'pg'
import { useStorage, withDeleteEnabled } from './utils/storage'

Expand Down Expand Up @@ -132,4 +133,72 @@ describe('Database Protection Triggers', () => {
})
})
})

describe('Table privilege restrictions (migration 0073)', () => {
const crudTables = ['objects', 'buckets', 'buckets_analytics']
const selectOnlyTables = ['buckets_vectors', 'vector_indexes']
const restrictedRoles = ['anon', 'authenticated']

let checkedPrivileges: string[]

beforeAll(async () => {
const db = tHelper.database.connection
const result = await db.query<{ server_version_num: string }>(
`SELECT current_setting('server_version_num') AS server_version_num`
)
const supportsMaintain = Number(result.rows[0].server_version_num) >= 170000

checkedPrivileges = [
'SELECT',
'INSERT',
'UPDATE',
'DELETE',
'TRUNCATE',
'REFERENCES',
'TRIGGER',
...(supportsMaintain ? ['MAINTAIN'] : []),
]
})

async function getGrantedPrivileges(
db: TenantConnection,
role: string,
table: string
): Promise<string[]> {
const result = await db.query<{ privilege: string }>(
`SELECT p AS privilege
FROM unnest($1::text[]) AS p
WHERE has_table_privilege($2, $3, p)`,
[checkedPrivileges, role, `storage.${table}`]
)

return result.rows.map((row) => row.privilege)
}

it.each(
restrictedRoles
)('should only grant SELECT, INSERT, UPDATE, DELETE to %s on CRUD storage tables', async (role) => {
const db = tHelper.database.connection

for (const table of crudTables) {
const granted = await getGrantedPrivileges(db, role, table)

expect(granted.sort(), `unexpected privileges for ${role} on storage.${table}`).toEqual(
['DELETE', 'INSERT', 'SELECT', 'UPDATE'].sort()
)
}
})

it.each(
restrictedRoles
)('should only grant SELECT to %s on vector storage tables', async (role) => {
const db = tHelper.database.connection

for (const table of selectOnlyTables) {
const granted = await getGrantedPrivileges(db, role, table)

expect(granted, `unexpected privileges for ${role} on storage.${table}`).toEqual(['SELECT'])
}
})
})
})