@coffre/cli 0.1.1 → 0.1.2

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.
@@ -0,0 +1,505 @@
1
+ -- The schema on Postgres. Generated by `pnpm db:generate` from
2
+ -- baseline/postgres.sql and src/schema.ts; edit those, not this.
3
+
4
+ CREATE TABLE "audit_chain_head" (
5
+ "only_row" boolean PRIMARY KEY DEFAULT true NOT NULL,
6
+ "next_seq" bigint DEFAULT 0 NOT NULL,
7
+ "head_hash" "bytea" NOT NULL,
8
+ CONSTRAINT "audit_chain_head_only_row_check" CHECK ("audit_chain_head"."only_row"),
9
+ CONSTRAINT "audit_chain_head_next_seq_check" CHECK ("audit_chain_head"."next_seq" >= 0),
10
+ CONSTRAINT "audit_chain_head_head_hash_check" CHECK (octet_length("audit_chain_head"."head_hash") = 32)
11
+ );
12
+ --> statement-breakpoint
13
+ CREATE TABLE "audit_log" (
14
+ "seq" bigint PRIMARY KEY NOT NULL,
15
+ "author" text NOT NULL,
16
+ "key_id" text NOT NULL,
17
+ "occurred_at" bigint NOT NULL,
18
+ "actor" text NOT NULL,
19
+ "action" text NOT NULL,
20
+ "decision" text NOT NULL,
21
+ "code" text,
22
+ "subject_principal" text,
23
+ "project_id" uuid,
24
+ "environment_id" uuid,
25
+ "secret_id" uuid,
26
+ "secret_version_id" uuid,
27
+ "operation_id" uuid,
28
+ "request_id" text,
29
+ "source_ip" text,
30
+ "related_seq" bigint,
31
+ "metadata" text DEFAULT '{}' NOT NULL,
32
+ "prev_hash" "bytea" NOT NULL,
33
+ "mac" "bytea" NOT NULL,
34
+ "hash" "bytea" NOT NULL,
35
+ CONSTRAINT "audit_log_author_check" CHECK ("audit_log"."author" IN ('app', 'vault')),
36
+ CONSTRAINT "audit_log_actor_check" CHECK ("audit_log"."actor" ~ '^(user|token|sync|system):.+$'),
37
+ CONSTRAINT "audit_log_decision_check" CHECK ("audit_log"."decision" IN ('allow', 'deny')),
38
+ CONSTRAINT "audit_log_metadata_check" CHECK ("audit_log"."metadata"::jsonb IS NOT NULL),
39
+ CONSTRAINT "audit_log_prev_hash_check" CHECK (octet_length("audit_log"."prev_hash") = 32),
40
+ CONSTRAINT "audit_log_mac_check" CHECK (octet_length("audit_log"."mac") = 32),
41
+ CONSTRAINT "audit_log_hash_check" CHECK (octet_length("audit_log"."hash") = 32)
42
+ );
43
+ --> statement-breakpoint
44
+ CREATE TABLE "credentials" (
45
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
46
+ "kind" text NOT NULL,
47
+ "auth_mac" "bytea" NOT NULL,
48
+ "token_hash" "bytea" NOT NULL,
49
+ "token_hint" text NOT NULL,
50
+ "generation" integer NOT NULL,
51
+ "principal" text NOT NULL,
52
+ "identity_id" uuid,
53
+ "label" text,
54
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
55
+ "created_by" text NOT NULL,
56
+ "expires_at" timestamp with time zone NOT NULL,
57
+ "last_used_at" timestamp with time zone,
58
+ "last_used_ip" text,
59
+ "revoked_at" timestamp with time zone,
60
+ "revoked_by" text,
61
+ CONSTRAINT "credentials_token_hash_key" UNIQUE("token_hash"),
62
+ CONSTRAINT "credentials_kind_check" CHECK ("credentials"."kind" IN ('browser', 'cli', 'service')),
63
+ CONSTRAINT "credentials_principal_check" CHECK ("credentials"."principal" LIKE 'user:%' OR "credentials"."principal" LIKE 'token:%'),
64
+ CONSTRAINT "credentials_kind_matches_principal" CHECK (("credentials"."kind" = 'service') = ("credentials"."principal" LIKE 'token:%')),
65
+ CONSTRAINT "credentials_token_hash_check" CHECK (octet_length("credentials"."token_hash") = 32),
66
+ CONSTRAINT "credentials_auth_mac_check" CHECK (octet_length("credentials"."auth_mac") = 32)
67
+ );
68
+ --> statement-breakpoint
69
+ CREATE TABLE "device_authorizations" (
70
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
71
+ "device_code_hash" "bytea" NOT NULL,
72
+ "auth_mac" "bytea" NOT NULL,
73
+ "user_code" text NOT NULL,
74
+ "client_label" text,
75
+ "client_ip" text,
76
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
77
+ "expires_at" timestamp with time zone NOT NULL,
78
+ "decided_at" timestamp with time zone,
79
+ "decision" text,
80
+ "generation" integer DEFAULT 0 NOT NULL,
81
+ "principal" text,
82
+ "consumed_at" timestamp with time zone,
83
+ CONSTRAINT "device_authorizations_device_code_hash_key" UNIQUE("device_code_hash"),
84
+ CONSTRAINT "device_authorizations_user_code_key" UNIQUE("user_code"),
85
+ CONSTRAINT "device_authorizations_auth_mac_check" CHECK (octet_length("device_authorizations"."auth_mac") = 32),
86
+ CONSTRAINT "device_authorizations_decision_check" CHECK ("device_authorizations"."decision" IS NULL OR "device_authorizations"."decision" IN ('approved', 'denied')),
87
+ CONSTRAINT "device_authorizations_approval_names_principal" CHECK (("device_authorizations"."decision" IS NULL AND "device_authorizations"."decided_at" IS NULL AND "device_authorizations"."principal" IS NULL AND "device_authorizations"."generation" = 0 AND "device_authorizations"."consumed_at" IS NULL)
88
+ OR ("device_authorizations"."decision" IS NOT NULL AND "device_authorizations"."decided_at" IS NOT NULL AND (
89
+ ("device_authorizations"."decision" = 'denied' AND "device_authorizations"."principal" IS NULL AND "device_authorizations"."generation" = 0 AND "device_authorizations"."consumed_at" IS NULL)
90
+ OR ("device_authorizations"."decision" = 'approved' AND "device_authorizations"."principal" IS NOT NULL AND "device_authorizations"."principal" LIKE 'user:%')
91
+ )))
92
+ );
93
+ --> statement-breakpoint
94
+ CREATE TABLE "environments" (
95
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
96
+ "project_id" uuid NOT NULL,
97
+ "slug" text NOT NULL,
98
+ "name" text NOT NULL,
99
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
100
+ "archived_at" timestamp with time zone,
101
+ CONSTRAINT "environments_project_id_slug_key" UNIQUE("project_id","slug"),
102
+ CONSTRAINT "environments_project_scoped" UNIQUE("id","project_id"),
103
+ CONSTRAINT "environments_slug_check" CHECK ("environments"."slug" ~ '^[a-z0-9][a-z0-9-]{0,62}$')
104
+ );
105
+ --> statement-breakpoint
106
+ CREATE TABLE "identities" (
107
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
108
+ "provider" text NOT NULL,
109
+ "auth_mac" "bytea" NOT NULL,
110
+ "subject" text NOT NULL,
111
+ "issuer_hash" text NOT NULL,
112
+ "generation" integer NOT NULL,
113
+ "principal" text NOT NULL,
114
+ "email" text,
115
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
116
+ "created_by" text NOT NULL,
117
+ "last_sign_in_at" timestamp with time zone,
118
+ "revoked_at" timestamp with time zone,
119
+ "revoked_by" text,
120
+ "active_subject" text GENERATED ALWAYS AS (CASE WHEN revoked_at IS NULL THEN subject END) STORED,
121
+ CONSTRAINT "identities_member_generation_key" UNIQUE("id","principal","generation"),
122
+ CONSTRAINT "identities_principal_check" CHECK ("identities"."principal" LIKE 'user:%'),
123
+ CONSTRAINT "identities_provider_check" CHECK ("identities"."provider" ~ '^[a-z0-9][a-z0-9-]{0,31}$'),
124
+ CONSTRAINT "identities_auth_mac_check" CHECK (octet_length("identities"."auth_mac") = 32)
125
+ );
126
+ --> statement-breakpoint
127
+ CREATE TABLE "projects" (
128
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
129
+ "slug" text NOT NULL,
130
+ "name" text NOT NULL,
131
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
132
+ "archived_at" timestamp with time zone,
133
+ CONSTRAINT "projects_slug_key" UNIQUE("slug"),
134
+ CONSTRAINT "projects_slug_check" CHECK ("projects"."slug" ~ '^[a-z0-9][a-z0-9-]{0,62}$')
135
+ );
136
+ --> statement-breakpoint
137
+ CREATE TABLE "secret_versions" (
138
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
139
+ "secret_id" uuid NOT NULL,
140
+ "version" integer NOT NULL,
141
+ "envelope_version" integer NOT NULL,
142
+ "ciphertext" "bytea" NOT NULL,
143
+ "iv" "bytea" NOT NULL,
144
+ "auth_tag" "bytea" NOT NULL,
145
+ "wrapped_dek" "bytea" NOT NULL,
146
+ "kek_provider" text NOT NULL,
147
+ "kek_id" text NOT NULL,
148
+ "kek_version" text NOT NULL,
149
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
150
+ "created_by" text NOT NULL,
151
+ CONSTRAINT "secret_versions_secret_id_version_key" UNIQUE("secret_id","version"),
152
+ CONSTRAINT "secret_versions_version_check" CHECK ("secret_versions"."version" > 0),
153
+ CONSTRAINT "secret_versions_iv_check" CHECK (octet_length("secret_versions"."iv") = 12),
154
+ CONSTRAINT "secret_versions_auth_tag_check" CHECK (octet_length("secret_versions"."auth_tag") = 16)
155
+ );
156
+ --> statement-breakpoint
157
+ CREATE TABLE "secrets" (
158
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
159
+ "project_id" uuid NOT NULL,
160
+ "environment_id" uuid NOT NULL,
161
+ "key" text NOT NULL,
162
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
163
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
164
+ "current_version_id" uuid,
165
+ "current_version" integer DEFAULT 0 NOT NULL,
166
+ "archived_at" timestamp with time zone,
167
+ CONSTRAINT "secrets_project_id_environment_id_key_key" UNIQUE("project_id","environment_id","key"),
168
+ CONSTRAINT "secrets_key_check" CHECK ("secrets"."key" ~ '^[A-Za-z_][A-Za-z0-9_]{0,127}$')
169
+ );
170
+ --> statement-breakpoint
171
+ CREATE TABLE "sync_keys" (
172
+ "sync_id" uuid NOT NULL,
173
+ "key" text NOT NULL,
174
+ "secret_version_id" uuid,
175
+ "pushed_at" timestamp with time zone DEFAULT now() NOT NULL,
176
+ "removed_at" timestamp with time zone,
177
+ CONSTRAINT "sync_keys_pkey" PRIMARY KEY("sync_id","key")
178
+ );
179
+ --> statement-breakpoint
180
+ CREATE TABLE "syncs" (
181
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
182
+ "project_id" uuid NOT NULL,
183
+ "environment_id" uuid NOT NULL,
184
+ "provider" text NOT NULL,
185
+ "config" text NOT NULL,
186
+ "credential_secret_id" uuid NOT NULL,
187
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
188
+ "created_by" text NOT NULL,
189
+ "paused_at" timestamp with time zone,
190
+ "archived_at" timestamp with time zone,
191
+ "lease_until" timestamp with time zone,
192
+ "last_run_at" timestamp with time zone,
193
+ "last_status" text,
194
+ "last_error" text,
195
+ CONSTRAINT "syncs_config_check" CHECK ("syncs"."config"::jsonb IS NOT NULL),
196
+ CONSTRAINT "syncs_last_status_check" CHECK ("syncs"."last_status" IS NULL OR "syncs"."last_status" IN ('ok', 'partial', 'failed'))
197
+ );
198
+ --> statement-breakpoint
199
+ CREATE TABLE "vault_grants" (
200
+ "principal" text NOT NULL,
201
+ "project_id" uuid,
202
+ "environment_id" uuid,
203
+ "role" text NOT NULL,
204
+ "expires_at" bigint,
205
+ "granted_at" bigint NOT NULL,
206
+ "granted_by" text NOT NULL,
207
+ CONSTRAINT "vault_grants_on_project" UNIQUE("principal","project_id"),
208
+ CONSTRAINT "vault_grants_on_environment" UNIQUE("principal","environment_id"),
209
+ CONSTRAINT "vault_grants_one_place" CHECK (("vault_grants"."project_id" IS NULL) <> ("vault_grants"."environment_id" IS NULL)),
210
+ CONSTRAINT "vault_grants_role_check" CHECK ("vault_grants"."role" IN ('viewer', 'developer', 'maintainer', 'access-manager', 'auditor', 'owner')),
211
+ CONSTRAINT "vault_grants_environment_role_check" CHECK ("vault_grants"."environment_id" IS NULL OR "vault_grants"."role" IN ('viewer', 'developer', 'auditor'))
212
+ );
213
+ --> statement-breakpoint
214
+ CREATE TABLE "vault_members" (
215
+ "principal" text PRIMARY KEY NOT NULL,
216
+ "status" text NOT NULL,
217
+ "owner" boolean DEFAULT false NOT NULL,
218
+ "generation" integer DEFAULT 0 NOT NULL,
219
+ "created_at" bigint NOT NULL,
220
+ "created_by" text NOT NULL,
221
+ "status_changed_at" bigint NOT NULL,
222
+ "status_changed_by" text NOT NULL,
223
+ "access_seq" bigint NOT NULL,
224
+ "mac" "bytea" NOT NULL,
225
+ CONSTRAINT "vault_members_mac_check" CHECK (octet_length("vault_members"."mac") = 32),
226
+ CONSTRAINT "vault_members_principal_check" CHECK ("vault_members"."principal" ~ '^(user|token|sync):[^[:space:]:][^[:space:]]*$'),
227
+ CONSTRAINT "vault_members_user_lowercase" CHECK ("vault_members"."principal" NOT LIKE 'user:%' OR "vault_members"."principal" = lower("vault_members"."principal")),
228
+ CONSTRAINT "vault_members_status_check" CHECK ("vault_members"."status" IN ('active', 'removed')),
229
+ CONSTRAINT "vault_members_owner_check" CHECK (NOT "vault_members"."owner" OR ("vault_members"."status" = 'active' AND "vault_members"."principal" LIKE 'user:%')),
230
+ CONSTRAINT "vault_members_generation_check" CHECK ("vault_members"."generation" >= 0)
231
+ );
232
+ --> statement-breakpoint
233
+ ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
234
+ ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_environment_id_fkey" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
235
+ ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_secret_id_fkey" FOREIGN KEY ("secret_id") REFERENCES "public"."secrets"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
236
+ ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_secret_version_id_fkey" FOREIGN KEY ("secret_version_id") REFERENCES "public"."secret_versions"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
237
+ ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_related_seq_fkey" FOREIGN KEY ("related_seq") REFERENCES "public"."audit_log"("seq") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
238
+ ALTER TABLE "credentials" ADD CONSTRAINT "credentials_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
239
+ ALTER TABLE "credentials" ADD CONSTRAINT "credentials_identity_id_fkey" FOREIGN KEY ("identity_id","principal","generation") REFERENCES "public"."identities"("id","principal","generation") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
240
+ ALTER TABLE "device_authorizations" ADD CONSTRAINT "device_authorizations_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
241
+ ALTER TABLE "environments" ADD CONSTRAINT "environments_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
242
+ ALTER TABLE "identities" ADD CONSTRAINT "identities_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
243
+ ALTER TABLE "secret_versions" ADD CONSTRAINT "secret_versions_secret_id_fkey" FOREIGN KEY ("secret_id") REFERENCES "public"."secrets"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
244
+ ALTER TABLE "secrets" ADD CONSTRAINT "secrets_current_version_id_secret_versions_id_fk" FOREIGN KEY ("current_version_id") REFERENCES "public"."secret_versions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
245
+ ALTER TABLE "secrets" ADD CONSTRAINT "secrets_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
246
+ ALTER TABLE "secrets" ADD CONSTRAINT "secrets_environment_id_fkey" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
247
+ ALTER TABLE "secrets" ADD CONSTRAINT "secrets_environment_in_project" FOREIGN KEY ("environment_id","project_id") REFERENCES "public"."environments"("id","project_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
248
+ ALTER TABLE "sync_keys" ADD CONSTRAINT "sync_keys_sync_id_fkey" FOREIGN KEY ("sync_id") REFERENCES "public"."syncs"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
249
+ ALTER TABLE "sync_keys" ADD CONSTRAINT "sync_keys_secret_version_id_fkey" FOREIGN KEY ("secret_version_id") REFERENCES "public"."secret_versions"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
250
+ ALTER TABLE "syncs" ADD CONSTRAINT "syncs_environment_in_project" FOREIGN KEY ("environment_id","project_id") REFERENCES "public"."environments"("id","project_id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
251
+ ALTER TABLE "syncs" ADD CONSTRAINT "syncs_credential_secret_id_fkey" FOREIGN KEY ("credential_secret_id") REFERENCES "public"."secrets"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
252
+ ALTER TABLE "vault_grants" ADD CONSTRAINT "vault_grants_principal_fkey" FOREIGN KEY ("principal") REFERENCES "public"."vault_members"("principal") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
253
+ ALTER TABLE "vault_grants" ADD CONSTRAINT "vault_grants_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
254
+ ALTER TABLE "vault_grants" ADD CONSTRAINT "vault_grants_environment_id_fkey" FOREIGN KEY ("environment_id") REFERENCES "public"."environments"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
255
+ CREATE INDEX "audit_log_project_idx" ON "audit_log" USING btree ("project_id","seq");--> statement-breakpoint
256
+ CREATE INDEX "audit_log_environment_idx" ON "audit_log" USING btree ("environment_id","seq");--> statement-breakpoint
257
+ CREATE INDEX "audit_log_secret_idx" ON "audit_log" USING btree ("secret_id","seq");--> statement-breakpoint
258
+ CREATE INDEX "audit_log_actor_idx" ON "audit_log" USING btree ("actor","seq");--> statement-breakpoint
259
+ CREATE INDEX "audit_log_operation_idx" ON "audit_log" USING btree ("operation_id","seq");--> statement-breakpoint
260
+ CREATE INDEX "audit_log_action_idx" ON "audit_log" USING btree ("author","action","seq");--> statement-breakpoint
261
+ CREATE INDEX "audit_log_releases_idx" ON "audit_log" USING btree ("author","actor","action","decision","occurred_at");--> statement-breakpoint
262
+ CREATE INDEX "audit_log_subject_idx" ON "audit_log" USING btree ("author","subject_principal","seq");--> statement-breakpoint
263
+ CREATE INDEX "credentials_principal_idx" ON "credentials" USING btree ("principal","generation");--> statement-breakpoint
264
+ CREATE UNIQUE INDEX "identities_active_subject" ON "identities" USING btree ("provider","issuer_hash","active_subject");--> statement-breakpoint
265
+ CREATE INDEX "identities_principal_idx" ON "identities" USING btree ("principal","generation");--> statement-breakpoint
266
+ CREATE INDEX "secret_versions_secret_idx" ON "secret_versions" USING btree ("secret_id","version" DESC NULLS LAST);--> statement-breakpoint
267
+ CREATE INDEX "secrets_lookup_idx" ON "secrets" USING btree ("project_id","environment_id","key");--> statement-breakpoint
268
+ CREATE INDEX "syncs_environment_idx" ON "syncs" USING btree ("environment_id");--> statement-breakpoint
269
+
270
+ -- The chain starts at sequence 0 from 32 zero bytes.
271
+ INSERT INTO audit_chain_head (only_row, next_seq, head_hash)
272
+ VALUES (true, 0, decode(repeat('00', 32), 'hex'));
273
+ --> statement-breakpoint
274
+
275
+ -- The audit log only grows, for every login, its owner's included. The
276
+ -- grants below already keep the runtime login from changing it; this stops
277
+ -- a bug or a careless statement by the owner too, who can still lift it
278
+ -- with ALTER TABLE ... DISABLE TRIGGER. What an owner changes anyway, the
279
+ -- entries' MACs and chain show.
280
+ CREATE FUNCTION audit_log_append_only() RETURNS trigger LANGUAGE plpgsql AS $$
281
+ BEGIN
282
+ RAISE EXCEPTION 'audit_log is append-only' USING ERRCODE = 'insufficient_privilege';
283
+ END
284
+ $$;
285
+ --> statement-breakpoint
286
+ CREATE TRIGGER audit_log_no_change BEFORE UPDATE OR DELETE ON audit_log
287
+ FOR EACH ROW EXECUTE FUNCTION audit_log_append_only();
288
+ --> statement-breakpoint
289
+ CREATE TRIGGER audit_log_no_truncate BEFORE TRUNCATE ON audit_log
290
+ FOR EACH STATEMENT EXECUTE FUNCTION audit_log_append_only();
291
+ --> statement-breakpoint
292
+
293
+ -- Two logins, provisioned outside coffre (their passwords are never ours),
294
+ -- each with only the rights of its group:
295
+ --
296
+ -- coffre_runtime the server, through coffre_app: read and insert,
297
+ -- UPDATE on named columns only, and never DELETE. A
298
+ -- revoked session, a used device code and a removed
299
+ -- sync key are rows that say so, not gaps.
300
+ -- coffre_vault_runtime the vault, through coffre_vault: its members and
301
+ -- grants, and reading what it decides on.
302
+ --
303
+ -- Both append to the audit log, each only as its own author (row-level
304
+ -- security, below), and neither may change or delete an entry.
305
+ DO $$
306
+ DECLARE
307
+ name text;
308
+ BEGIN
309
+ FOREACH name IN ARRAY ARRAY['coffre_app', 'coffre_vault'] LOOP
310
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = name) THEN
311
+ EXECUTE format('CREATE ROLE %I NOLOGIN INHERIT NOCREATEDB NOCREATEROLE', name);
312
+ ELSE
313
+ -- Managed owners cannot ALTER the superuser-only attributes, even to turn them off.
314
+ IF EXISTS (
315
+ SELECT 1 FROM pg_roles WHERE rolname = name
316
+ AND (rolsuper OR rolreplication OR rolbypassrls)
317
+ ) THEN
318
+ RAISE EXCEPTION '% has unsafe role attributes: SUPERUSER, REPLICATION and BYPASSRLS must be off', name;
319
+ END IF;
320
+ EXECUTE format('ALTER ROLE %I NOLOGIN INHERIT NOCREATEDB NOCREATEROLE', name);
321
+ END IF;
322
+ END LOOP;
323
+ END
324
+ $$;
325
+ --> statement-breakpoint
326
+
327
+
328
+ DO $$
329
+ DECLARE
330
+ name text;
331
+ runtime_role oid;
332
+ BEGIN
333
+ FOREACH name IN ARRAY ARRAY['coffre_runtime', 'coffre_vault_runtime'] LOOP
334
+ SELECT oid INTO runtime_role FROM pg_roles WHERE rolname = name;
335
+ IF runtime_role IS NULL THEN
336
+ RAISE EXCEPTION '% must be provisioned before migrations', name;
337
+ END IF;
338
+
339
+ IF EXISTS (
340
+ SELECT 1
341
+ FROM pg_roles
342
+ WHERE oid = runtime_role
343
+ AND (rolsuper OR rolcreatedb OR rolcreaterole OR rolreplication OR rolbypassrls)
344
+ ) THEN
345
+ RAISE EXCEPTION '% has unsafe role attributes', name;
346
+ END IF;
347
+
348
+ IF EXISTS (
349
+ SELECT 1
350
+ FROM pg_shdepend
351
+ WHERE refclassid = 'pg_authid'::regclass
352
+ AND refobjid = runtime_role
353
+ AND deptype = 'o'
354
+ ) THEN
355
+ RAISE EXCEPTION '% owns database objects', name;
356
+ END IF;
357
+ END LOOP;
358
+ END
359
+ $$;
360
+ --> statement-breakpoint
361
+
362
+ REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public
363
+ FROM coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime;
364
+ --> statement-breakpoint
365
+ REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public
366
+ FROM coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime;
367
+ --> statement-breakpoint
368
+ REVOKE ALL PRIVILEGES ON ALL ROUTINES IN SCHEMA public
369
+ FROM coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime;
370
+ --> statement-breakpoint
371
+ REVOKE ALL PRIVILEGES ON SCHEMA public
372
+ FROM coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime;
373
+ --> statement-breakpoint
374
+ GRANT USAGE ON SCHEMA public TO coffre_app, coffre_vault;
375
+ --> statement-breakpoint
376
+ GRANT USAGE ON SCHEMA drizzle TO coffre_app, coffre_vault;
377
+ --> statement-breakpoint
378
+ GRANT SELECT ON drizzle.__drizzle_migrations TO coffre_app, coffre_vault;
379
+ --> statement-breakpoint
380
+
381
+ GRANT SELECT, INSERT ON
382
+ projects,
383
+ environments,
384
+ secrets,
385
+ secret_versions,
386
+ audit_log,
387
+ identities,
388
+ credentials,
389
+ device_authorizations,
390
+ syncs,
391
+ sync_keys
392
+ TO coffre_app;
393
+ --> statement-breakpoint
394
+
395
+ GRANT SELECT ON
396
+ audit_chain_head,
397
+ vault_members,
398
+ vault_grants
399
+ TO coffre_app;
400
+ --> statement-breakpoint
401
+
402
+ GRANT UPDATE (slug, name, archived_at) ON projects TO coffre_app;
403
+ --> statement-breakpoint
404
+ GRANT UPDATE (slug, name, archived_at) ON environments TO coffre_app;
405
+ --> statement-breakpoint
406
+ GRANT UPDATE (key, current_version_id, current_version, updated_at, archived_at) ON secrets TO coffre_app;
407
+ --> statement-breakpoint
408
+ GRANT UPDATE (next_seq, head_hash) ON audit_chain_head TO coffre_app;
409
+ --> statement-breakpoint
410
+ GRANT UPDATE (email, last_sign_in_at, revoked_at, revoked_by, auth_mac) ON identities TO coffre_app;
411
+ --> statement-breakpoint
412
+ GRANT UPDATE (last_used_at, last_used_ip, revoked_at, revoked_by, auth_mac) ON credentials TO coffre_app;
413
+ --> statement-breakpoint
414
+ GRANT UPDATE (decided_at, decision, generation, principal, consumed_at, auth_mac)
415
+ ON device_authorizations TO coffre_app;
416
+ --> statement-breakpoint
417
+ GRANT UPDATE (config, credential_secret_id, paused_at, archived_at, lease_until,
418
+ last_run_at, last_status, last_error)
419
+ ON syncs TO coffre_app;
420
+ --> statement-breakpoint
421
+ GRANT UPDATE (secret_version_id, pushed_at, removed_at) ON sync_keys TO coffre_app;
422
+ --> statement-breakpoint
423
+
424
+ -- The vault reads what it decides on and the log it chains to, and writes
425
+ -- its members and grants. A revoked grant is deleted: the log keeps it.
426
+ GRANT SELECT ON
427
+ projects,
428
+ environments,
429
+ secrets,
430
+ secret_versions,
431
+ audit_chain_head
432
+ TO coffre_vault;
433
+ --> statement-breakpoint
434
+ GRANT SELECT, INSERT ON audit_log TO coffre_vault;
435
+ --> statement-breakpoint
436
+ GRANT SELECT, INSERT, UPDATE (status, owner, generation, created_at, created_by, status_changed_at, status_changed_by, access_seq, mac)
437
+ ON vault_members TO coffre_vault;
438
+ --> statement-breakpoint
439
+ GRANT SELECT, INSERT, DELETE ON vault_grants TO coffre_vault;
440
+ --> statement-breakpoint
441
+ GRANT UPDATE (next_seq, head_hash) ON audit_chain_head TO coffre_vault;
442
+ --> statement-breakpoint
443
+
444
+ REVOKE DELETE, TRUNCATE ON ALL TABLES IN SCHEMA public FROM coffre_app;
445
+ --> statement-breakpoint
446
+ REVOKE TRUNCATE ON ALL TABLES IN SCHEMA public FROM coffre_vault;
447
+ --> statement-breakpoint
448
+ REVOKE UPDATE ON audit_log, secret_versions FROM coffre_app, coffre_vault;
449
+ --> statement-breakpoint
450
+ REVOKE CREATE ON SCHEMA public FROM PUBLIC, coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime;
451
+ --> statement-breakpoint
452
+
453
+ -- Each login appends only as its own author. Its MAC is still what proves
454
+ -- who wrote an entry: the table's owner passes these policies, as owners do.
455
+ ALTER TABLE audit_log ENABLE ROW LEVEL SECURITY;
456
+ --> statement-breakpoint
457
+ CREATE POLICY audit_log_read ON audit_log FOR SELECT TO coffre_app, coffre_vault USING (true);
458
+ --> statement-breakpoint
459
+ CREATE POLICY audit_log_app_appends ON audit_log FOR INSERT TO coffre_app WITH CHECK (author = 'app');
460
+ --> statement-breakpoint
461
+ CREATE POLICY audit_log_vault_appends ON audit_log FOR INSERT TO coffre_vault WITH CHECK (author = 'vault');
462
+ --> statement-breakpoint
463
+
464
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
465
+ REVOKE ALL PRIVILEGES ON TABLES FROM coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime;
466
+ --> statement-breakpoint
467
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
468
+ REVOKE ALL PRIVILEGES ON SEQUENCES FROM coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime;
469
+ --> statement-breakpoint
470
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
471
+ REVOKE ALL PRIVILEGES ON ROUTINES FROM coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime;
472
+ --> statement-breakpoint
473
+
474
+ -- Each login is a member of its own group and of nothing else.
475
+ DO $$
476
+ DECLARE
477
+ login record;
478
+ membership record;
479
+ BEGIN
480
+ EXECUTE format(
481
+ 'REVOKE CREATE, TEMPORARY ON DATABASE %I FROM PUBLIC, coffre_app, coffre_runtime, coffre_vault, coffre_vault_runtime',
482
+ current_database()
483
+ );
484
+
485
+ FOR login IN
486
+ SELECT * FROM (VALUES ('coffre_runtime', 'coffre_app'), ('coffre_vault_runtime', 'coffre_vault')) AS logins (name, grp)
487
+ LOOP
488
+ FOR membership IN
489
+ SELECT granted.rolname
490
+ FROM pg_auth_members member_of
491
+ JOIN pg_roles member ON member.oid = member_of.member
492
+ JOIN pg_roles granted ON granted.oid = member_of.roleid
493
+ WHERE member.rolname = login.name
494
+ AND granted.rolname <> login.grp
495
+ LOOP
496
+ EXECUTE format('REVOKE %I FROM %I', membership.rolname, login.name);
497
+ END LOOP;
498
+ END LOOP;
499
+ END
500
+ $$;
501
+ --> statement-breakpoint
502
+
503
+ GRANT coffre_app TO coffre_runtime WITH ADMIN FALSE, INHERIT TRUE, SET FALSE;
504
+ --> statement-breakpoint
505
+ GRANT coffre_vault TO coffre_vault_runtime WITH ADMIN FALSE, INHERIT TRUE, SET FALSE;