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
45 changes: 44 additions & 1 deletion scripts/ops/test-postgres-retention.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ CREATE TABLE public.webhook_events (id text PRIMARY KEY, status text NOT NULL, c
CREATE TABLE public.audit_logs (id text PRIMARY KEY, created_at timestamptz NOT NULL);
CREATE TABLE public._prisma_migrations (id text PRIMARY KEY);
CREATE TABLE public.unrelated (id text PRIMARY KEY);
CREATE TABLE public.store_notification_recipients (id text PRIMARY KEY);
GRANT USAGE ON SCHEMA public TO wctm_runtime;
GRANT SELECT, INSERT, UPDATE ON public.telegram_link_tokens, public.stores, public.telegram_callback_references, public.webhook_events TO wctm_runtime;
GRANT SELECT, INSERT ON public.telegram_search_references, public.audit_logs TO wctm_runtime;
GRANT SELECT, INSERT, DELETE ON public.store_notification_recipients TO wctm_runtime;
INSERT INTO public.telegram_link_tokens VALUES ('old', now()-interval '2 days'), ('fresh', now()-interval '12 hours');
INSERT INTO public.stores VALUES ('old', 'hash', now()-interval '2 days'), ('fresh', 'hash', now()-interval '12 hours');
INSERT INTO public.telegram_callback_references VALUES ('old', now()-interval '2 days', 'cipher', 'hash'), ('fresh', now()-interval '12 hours', 'cipher', 'hash');
Expand Down Expand Up @@ -75,13 +77,54 @@ SELECT has_table_privilege('wctm_runtime','public.telegram_link_tokens','DELETE'
has_table_privilege('wctm_runtime','public.audit_logs','UPDATE'),
has_table_privilege('wctm_runtime','public.audit_logs','DELETE'),
has_table_privilege('wctm_runtime','public.telegram_search_references','UPDATE'),
has_table_privilege('wctm_runtime','public.store_notification_recipients','DELETE'),
has_table_privilege('wctm_retention','public.unrelated','DELETE'),
has_table_privilege('wctm_retention','public._prisma_migrations','UPDATE'),
has_schema_privilege('wctm_retention','public','CREATE'),
has_database_privilege('wctm_retention','retention_test','TEMP'),
(SELECT count(*) FROM pg_class WHERE relowner='wctm_retention'::regrole),
(SELECT rolsuper OR rolcreatedb OR rolcreaterole OR rolbypassrls OR rolinherit FROM pg_roles WHERE rolname='wctm_retention');")
[[ $roles = 'f|f|f|f|f|f|f|f|0|f' ]]
[[ $roles = 'f|f|f|f|t|f|f|f|f|0|f' ]]

# Recipient mappings need runtime DELETE, but each retention-table DELETE must fail verification.
for table in telegram_link_tokens stores telegram_callback_references telegram_search_references webhook_events audit_logs; do
docker exec "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-c "GRANT DELETE ON public.$table TO wctm_runtime"
if docker exec -i "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-f - <scripts/ops/verify-retention-role.sql >"$workspace/unapproved-runtime-grant.log" 2>&1; then
echo >&2 'test failed: verifier accepted runtime DELETE on a retention table'
exit 1
fi
docker exec "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-c "REVOKE DELETE ON public.$table FROM wctm_runtime"
done
docker exec "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-c 'GRANT UPDATE ON public.audit_logs TO wctm_runtime'
if docker exec -i "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-f - <scripts/ops/verify-retention-role.sql >"$workspace/unapproved-runtime-grant.log" 2>&1; then
echo >&2 'test failed: verifier accepted runtime UPDATE on audit logs'
exit 1
fi
docker exec "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-c 'REVOKE UPDATE ON public.audit_logs FROM wctm_runtime'

# A column-only UPDATE grant must also violate audit immutability.
docker exec "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-c 'GRANT UPDATE (created_at) ON public.audit_logs TO wctm_runtime'
audit_column_privileges=$(docker exec "$container" psql -XAt -v ON_ERROR_STOP=1 -U postgres -d retention_test -c "
SELECT has_table_privilege('wctm_runtime','public.audit_logs','UPDATE'),
has_any_column_privilege('wctm_runtime','public.audit_logs','UPDATE');")
[[ $audit_column_privileges = 'f|t' ]]
if docker exec -i "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-f - <scripts/ops/verify-retention-role.sql >"$workspace/unapproved-runtime-grant.log" 2>&1; then
echo >&2 'test failed: verifier accepted runtime column UPDATE on audit logs'
exit 1
fi
docker exec "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-c 'REVOKE UPDATE (created_at) ON public.audit_logs FROM wctm_runtime'
docker exec -i "$container" psql -X -q -v ON_ERROR_STOP=1 -U postgres -d retention_test \
-f - <scripts/ops/verify-retention-role.sql >"$workspace/role-after-revokes.log"
grep -q 'retention role: PASS' "$workspace/role-after-revokes.log"

unset_path=$WCTM_RETENTION_PGPASS_FILE
unset WCTM_RETENTION_PGPASS_FILE
Expand Down
7 changes: 5 additions & 2 deletions scripts/ops/verify-retention-role.sql
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,16 @@ BEGIN
(pg_catalog.has_table_privilege('wctm_retention', c.oid, 'INSERT') OR
(pg_catalog.has_table_privilege('wctm_retention', c.oid, 'DELETE') AND
c.relname NOT IN ('telegram_link_tokens', 'audit_logs')) OR
pg_catalog.has_table_privilege('wctm_runtime', c.oid, 'DELETE'))
(c.relname IN (
'telegram_link_tokens', 'stores', 'telegram_callback_references',
'telegram_search_references', 'webhook_events', 'audit_logs'
) AND pg_catalog.has_table_privilege('wctm_runtime', c.oid, 'DELETE')))
) OR NOT pg_catalog.has_table_privilege('wctm_retention', 'public.telegram_link_tokens', 'DELETE') OR
NOT pg_catalog.has_table_privilege('wctm_retention', 'public.audit_logs', 'DELETE') OR
pg_catalog.has_table_privilege('wctm_retention', 'public._prisma_migrations', 'SELECT') OR
pg_catalog.has_table_privilege('wctm_retention', 'public._prisma_migrations', 'UPDATE') OR
pg_catalog.has_table_privilege('wctm_retention', 'public._prisma_migrations', 'DELETE') OR
pg_catalog.has_table_privilege('wctm_runtime', 'public.audit_logs', 'UPDATE') THEN
pg_catalog.has_any_column_privilege('wctm_runtime', 'public.audit_logs', 'UPDATE') THEN
RAISE EXCEPTION 'retention table allowlist or runtime immutability failed';
END IF;
IF EXISTS (
Expand Down
Loading