@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.
- package/README.md +4 -1
- package/dist/access-CO1kGjWW.js +74 -0
- package/dist/connect-BONR52al.js +770 -0
- package/dist/connect-BZdPYNbZ.js +2 -0
- package/dist/esm-B69mMLcR.js +2 -0
- package/dist/esm-Bts-esAl.js +4202 -0
- package/dist/keys-D9CG_wYV.js +64 -0
- package/dist/main.js +9 -136
- package/dist/migrations/postgres/0000_baseline.sql +505 -0
- package/dist/migrations/postgres/meta/0000_snapshot.json +1939 -0
- package/dist/migrations/postgres/meta/_journal.json +13 -0
- package/dist/migrator-W1a-ooWi.js +37 -0
- package/dist/node-postgres-BlNvYfq_.js +2 -0
- package/dist/node-postgres-_y-EeSHQ.js +2831 -0
- package/dist/portable-CPh2thNN.js +87 -0
- package/dist/relations-CJCGPRst.js +2712 -0
- package/dist/schema-sqlite-Byp7B3yI.js +1078 -0
- package/dist/setup-DPHVyr2N.js +521 -0
- package/dist/templates/node/README.md +37 -47
- package/dist/templates/node/package.json +3 -3
- package/dist/templates/node/server.env.example +3 -2
- package/dist/templates/node/src/server.ts +1 -1
- package/dist/templates/node/src/vault.ts +5 -3
- package/dist/templates/node/vault.env.example +4 -5
- package/dist/templates/workers/README.md +40 -55
- package/dist/templates/workers/app/wrangler.jsonc +1 -1
- package/dist/templates/workers/package.json +4 -4
- package/dist/templates/workers/vault/src/worker.ts +5 -4
- package/dist/templates/workers/vault/wrangler.jsonc +4 -3
- package/package.json +7 -4
|
@@ -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;
|