@team-semicolon/semicolony-cli 4.18.56 → 4.18.57
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/dist/bundle.js +1232 -1136
- package/migrations/164_fabric_bridge_principal_boundary.sql +678 -0
- package/package.json +1 -1
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
-- 164_fabric_bridge_principal_boundary.sql
|
|
2
|
+
-- Least-privilege credential resolver for the isolated SEMO Ops Knowledge Fabric bridge.
|
|
3
|
+
--
|
|
4
|
+
-- The bridge role receives no core-schema/table privileges. It can use one dedicated
|
|
5
|
+
-- schema and execute one SECURITY DEFINER function that returns four sanitized fields.
|
|
6
|
+
-- The migration runner wraps this file in a transaction.
|
|
7
|
+
-- @schema-retarget: off
|
|
8
|
+
|
|
9
|
+
DO $$
|
|
10
|
+
BEGIN
|
|
11
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'sc_fabric_bridge_owner') THEN
|
|
12
|
+
CREATE ROLE sc_fabric_bridge_owner NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS;
|
|
13
|
+
ELSE
|
|
14
|
+
ALTER ROLE sc_fabric_bridge_owner
|
|
15
|
+
WITH NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS;
|
|
16
|
+
END IF;
|
|
17
|
+
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'sc_fabric_bridge') THEN
|
|
18
|
+
CREATE ROLE sc_fabric_bridge LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS;
|
|
19
|
+
ELSE
|
|
20
|
+
ALTER ROLE sc_fabric_bridge
|
|
21
|
+
WITH LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS;
|
|
22
|
+
END IF;
|
|
23
|
+
EXECUTE format(
|
|
24
|
+
'GRANT CONNECT ON DATABASE %I TO sc_fabric_bridge',
|
|
25
|
+
current_database()
|
|
26
|
+
);
|
|
27
|
+
END $$;
|
|
28
|
+
|
|
29
|
+
-- NOINHERIT does not prevent SET ROLE. Reject every inbound or outbound membership
|
|
30
|
+
-- before temporarily granting the migration runner the owner role.
|
|
31
|
+
DO $$
|
|
32
|
+
DECLARE
|
|
33
|
+
v_owner_oid oid;
|
|
34
|
+
v_bridge_oid oid;
|
|
35
|
+
BEGIN
|
|
36
|
+
SELECT oid INTO STRICT v_owner_oid
|
|
37
|
+
FROM pg_catalog.pg_roles
|
|
38
|
+
WHERE rolname = 'sc_fabric_bridge_owner';
|
|
39
|
+
SELECT oid INTO STRICT v_bridge_oid
|
|
40
|
+
FROM pg_catalog.pg_roles
|
|
41
|
+
WHERE rolname = 'sc_fabric_bridge';
|
|
42
|
+
|
|
43
|
+
IF EXISTS (
|
|
44
|
+
SELECT 1
|
|
45
|
+
FROM pg_catalog.pg_auth_members m
|
|
46
|
+
WHERE m.roleid IN (v_owner_oid, v_bridge_oid)
|
|
47
|
+
OR m.member IN (v_owner_oid, v_bridge_oid)
|
|
48
|
+
) THEN
|
|
49
|
+
RAISE EXCEPTION 'fabric bridge role membership rejected';
|
|
50
|
+
END IF;
|
|
51
|
+
|
|
52
|
+
EXECUTE format('GRANT sc_fabric_bridge_owner TO %I', current_user);
|
|
53
|
+
END $$;
|
|
54
|
+
|
|
55
|
+
-- Never adopt an untrusted schema or silently preserve pre-existing objects or ACLs.
|
|
56
|
+
-- Only an empty schema owned by the migration runner, or this migration's exact
|
|
57
|
+
-- resolver and exact owner/bridge ACL, is safe to converge.
|
|
58
|
+
DO $$
|
|
59
|
+
DECLARE
|
|
60
|
+
v_bridge_oid oid;
|
|
61
|
+
v_owner_oid oid;
|
|
62
|
+
v_schema_oid oid;
|
|
63
|
+
v_schema_owner text;
|
|
64
|
+
v_unexpected_objects boolean;
|
|
65
|
+
v_unexpected_functions integer;
|
|
66
|
+
v_wrong_resolver_owner integer;
|
|
67
|
+
v_unsafe_default_acl boolean;
|
|
68
|
+
v_unsafe_schema_acl boolean;
|
|
69
|
+
v_unsafe_function_acl boolean;
|
|
70
|
+
BEGIN
|
|
71
|
+
SELECT oid INTO STRICT v_owner_oid
|
|
72
|
+
FROM pg_catalog.pg_roles
|
|
73
|
+
WHERE rolname = 'sc_fabric_bridge_owner';
|
|
74
|
+
SELECT oid INTO STRICT v_bridge_oid
|
|
75
|
+
FROM pg_catalog.pg_roles
|
|
76
|
+
WHERE rolname = 'sc_fabric_bridge';
|
|
77
|
+
|
|
78
|
+
SELECT n.oid, r.rolname
|
|
79
|
+
INTO v_schema_oid, v_schema_owner
|
|
80
|
+
FROM pg_catalog.pg_namespace n
|
|
81
|
+
JOIN pg_catalog.pg_roles r ON r.oid = n.nspowner
|
|
82
|
+
WHERE n.nspname = 'fabric_bridge';
|
|
83
|
+
|
|
84
|
+
IF v_schema_owner IS NOT NULL
|
|
85
|
+
AND v_schema_owner NOT IN (current_user, 'sc_fabric_bridge_owner') THEN
|
|
86
|
+
RAISE EXCEPTION 'fabric bridge schema ownership rejected';
|
|
87
|
+
END IF;
|
|
88
|
+
|
|
89
|
+
SELECT EXISTS (
|
|
90
|
+
SELECT 1
|
|
91
|
+
FROM pg_catalog.pg_class c
|
|
92
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
93
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
94
|
+
UNION ALL
|
|
95
|
+
SELECT 1
|
|
96
|
+
FROM pg_catalog.pg_type t
|
|
97
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
98
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
99
|
+
UNION ALL
|
|
100
|
+
SELECT 1
|
|
101
|
+
FROM pg_catalog.pg_collation c
|
|
102
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.collnamespace
|
|
103
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
104
|
+
UNION ALL
|
|
105
|
+
SELECT 1
|
|
106
|
+
FROM pg_catalog.pg_conversion c
|
|
107
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.connamespace
|
|
108
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
109
|
+
UNION ALL
|
|
110
|
+
SELECT 1
|
|
111
|
+
FROM pg_catalog.pg_operator o
|
|
112
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace
|
|
113
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
114
|
+
UNION ALL
|
|
115
|
+
SELECT 1
|
|
116
|
+
FROM pg_catalog.pg_opclass o
|
|
117
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = o.opcnamespace
|
|
118
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
119
|
+
UNION ALL
|
|
120
|
+
SELECT 1
|
|
121
|
+
FROM pg_catalog.pg_opfamily o
|
|
122
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = o.opfnamespace
|
|
123
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
124
|
+
UNION ALL
|
|
125
|
+
SELECT 1
|
|
126
|
+
FROM pg_catalog.pg_statistic_ext s
|
|
127
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = s.stxnamespace
|
|
128
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
129
|
+
UNION ALL
|
|
130
|
+
SELECT 1
|
|
131
|
+
FROM pg_catalog.pg_ts_config t
|
|
132
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.cfgnamespace
|
|
133
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
134
|
+
UNION ALL
|
|
135
|
+
SELECT 1
|
|
136
|
+
FROM pg_catalog.pg_ts_dict t
|
|
137
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.dictnamespace
|
|
138
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
139
|
+
UNION ALL
|
|
140
|
+
SELECT 1
|
|
141
|
+
FROM pg_catalog.pg_ts_parser t
|
|
142
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.prsnamespace
|
|
143
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
144
|
+
UNION ALL
|
|
145
|
+
SELECT 1
|
|
146
|
+
FROM pg_catalog.pg_ts_template t
|
|
147
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace
|
|
148
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
149
|
+
UNION ALL
|
|
150
|
+
SELECT 1
|
|
151
|
+
FROM pg_catalog.pg_constraint c
|
|
152
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.connamespace
|
|
153
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
154
|
+
UNION ALL
|
|
155
|
+
SELECT 1
|
|
156
|
+
FROM pg_catalog.pg_extension e
|
|
157
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
|
|
158
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
159
|
+
) INTO v_unexpected_objects;
|
|
160
|
+
|
|
161
|
+
IF v_unexpected_objects THEN
|
|
162
|
+
RAISE EXCEPTION 'fabric bridge schema inventory rejected';
|
|
163
|
+
END IF;
|
|
164
|
+
|
|
165
|
+
SELECT count(*)
|
|
166
|
+
INTO v_unexpected_functions
|
|
167
|
+
FROM pg_catalog.pg_proc p
|
|
168
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
169
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
170
|
+
AND NOT (
|
|
171
|
+
p.proname = 'resolve_principal'
|
|
172
|
+
AND p.prokind = 'f'
|
|
173
|
+
AND p.pronargs = 1
|
|
174
|
+
AND p.proargtypes = '25'::pg_catalog.oidvector
|
|
175
|
+
AND p.proretset
|
|
176
|
+
AND p.prorettype = 2249
|
|
177
|
+
AND p.proallargtypes = ARRAY[25, 2950, 25, 25, 25]::oid[]
|
|
178
|
+
AND p.proargmodes = ARRAY['i', 't', 't', 't', 't']::"char"[]
|
|
179
|
+
AND p.proargnames = ARRAY[
|
|
180
|
+
'p_token_hash',
|
|
181
|
+
'credential_id',
|
|
182
|
+
'credential_kind',
|
|
183
|
+
'actor_id',
|
|
184
|
+
'principal_id'
|
|
185
|
+
]::text[]
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
IF v_unexpected_functions <> 0 THEN
|
|
189
|
+
RAISE EXCEPTION 'fabric bridge schema inventory rejected';
|
|
190
|
+
END IF;
|
|
191
|
+
|
|
192
|
+
SELECT count(*)
|
|
193
|
+
INTO v_wrong_resolver_owner
|
|
194
|
+
FROM pg_catalog.pg_proc p
|
|
195
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
196
|
+
JOIN pg_catalog.pg_roles r ON r.oid = p.proowner
|
|
197
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
198
|
+
AND p.proname = 'resolve_principal'
|
|
199
|
+
AND p.prokind = 'f'
|
|
200
|
+
AND p.pronargs = 1
|
|
201
|
+
AND p.proargtypes = '25'::pg_catalog.oidvector
|
|
202
|
+
AND r.rolname <> 'sc_fabric_bridge_owner';
|
|
203
|
+
|
|
204
|
+
IF v_wrong_resolver_owner <> 0 THEN
|
|
205
|
+
RAISE EXCEPTION 'fabric bridge schema ownership rejected';
|
|
206
|
+
END IF;
|
|
207
|
+
|
|
208
|
+
SELECT EXISTS (
|
|
209
|
+
SELECT 1
|
|
210
|
+
FROM pg_catalog.pg_default_acl d
|
|
211
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(d.defaclacl) acl
|
|
212
|
+
WHERE d.defaclrole = v_owner_oid
|
|
213
|
+
AND d.defaclobjtype = 'f'
|
|
214
|
+
AND (
|
|
215
|
+
d.defaclnamespace = 0
|
|
216
|
+
OR d.defaclnamespace = v_schema_oid
|
|
217
|
+
)
|
|
218
|
+
AND NOT (
|
|
219
|
+
acl.grantee = v_owner_oid
|
|
220
|
+
AND acl.grantor = v_owner_oid
|
|
221
|
+
AND acl.privilege_type = 'EXECUTE'
|
|
222
|
+
AND NOT acl.is_grantable
|
|
223
|
+
)
|
|
224
|
+
) INTO v_unsafe_default_acl;
|
|
225
|
+
|
|
226
|
+
IF v_unsafe_default_acl THEN
|
|
227
|
+
RAISE EXCEPTION 'fabric bridge default acl rejected';
|
|
228
|
+
END IF;
|
|
229
|
+
|
|
230
|
+
SELECT EXISTS (
|
|
231
|
+
SELECT 1
|
|
232
|
+
FROM pg_catalog.pg_namespace n
|
|
233
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(
|
|
234
|
+
COALESCE(n.nspacl, pg_catalog.acldefault('n', n.nspowner))
|
|
235
|
+
) acl
|
|
236
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
237
|
+
AND NOT (
|
|
238
|
+
(
|
|
239
|
+
acl.grantee = n.nspowner
|
|
240
|
+
AND acl.grantor = n.nspowner
|
|
241
|
+
AND acl.privilege_type IN ('CREATE', 'USAGE')
|
|
242
|
+
AND NOT acl.is_grantable
|
|
243
|
+
)
|
|
244
|
+
OR (
|
|
245
|
+
acl.grantee = v_bridge_oid
|
|
246
|
+
AND acl.grantor = n.nspowner
|
|
247
|
+
AND acl.privilege_type = 'USAGE'
|
|
248
|
+
AND NOT acl.is_grantable
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
) INTO v_unsafe_schema_acl;
|
|
252
|
+
|
|
253
|
+
SELECT EXISTS (
|
|
254
|
+
SELECT 1
|
|
255
|
+
FROM pg_catalog.pg_proc p
|
|
256
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
257
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(
|
|
258
|
+
COALESCE(p.proacl, pg_catalog.acldefault('f', p.proowner))
|
|
259
|
+
) acl
|
|
260
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
261
|
+
AND NOT (
|
|
262
|
+
acl.grantee IN (v_owner_oid, v_bridge_oid)
|
|
263
|
+
AND acl.grantor = p.proowner
|
|
264
|
+
AND acl.privilege_type = 'EXECUTE'
|
|
265
|
+
AND NOT acl.is_grantable
|
|
266
|
+
)
|
|
267
|
+
) INTO v_unsafe_function_acl;
|
|
268
|
+
|
|
269
|
+
IF v_unsafe_schema_acl OR v_unsafe_function_acl THEN
|
|
270
|
+
RAISE EXCEPTION 'fabric bridge schema acl rejected';
|
|
271
|
+
END IF;
|
|
272
|
+
END $$;
|
|
273
|
+
|
|
274
|
+
CREATE SCHEMA IF NOT EXISTS fabric_bridge AUTHORIZATION sc_fabric_bridge_owner;
|
|
275
|
+
ALTER SCHEMA fabric_bridge OWNER TO sc_fabric_bridge_owner;
|
|
276
|
+
REVOKE ALL ON SCHEMA fabric_bridge FROM PUBLIC;
|
|
277
|
+
ALTER DEFAULT PRIVILEGES FOR ROLE sc_fabric_bridge_owner
|
|
278
|
+
REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
|
|
279
|
+
ALTER DEFAULT PRIVILEGES FOR ROLE sc_fabric_bridge_owner IN SCHEMA fabric_bridge
|
|
280
|
+
REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
|
|
281
|
+
|
|
282
|
+
ALTER TABLE semicolony.worker_devices
|
|
283
|
+
ADD COLUMN IF NOT EXISTS fabric_gateway_credential_id uuid
|
|
284
|
+
REFERENCES semicolony.gateway_credentials(id);
|
|
285
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_worker_devices_fabric_gateway_credential_id
|
|
286
|
+
ON semicolony.worker_devices (fabric_gateway_credential_id)
|
|
287
|
+
WHERE fabric_gateway_credential_id IS NOT NULL;
|
|
288
|
+
|
|
289
|
+
GRANT USAGE ON SCHEMA semicolony, public TO sc_fabric_bridge_owner;
|
|
290
|
+
GRANT SELECT (id, tenant_id, tenant_slug, token_hash, scopes, status, expires_at, metadata)
|
|
291
|
+
ON semicolony.gateway_credentials TO sc_fabric_bridge_owner;
|
|
292
|
+
GRANT SELECT (fabric_gateway_credential_id, worker_id, device_id, status, kb_access_mode, last_doctor_status, last_seen_at)
|
|
293
|
+
ON semicolony.worker_devices TO sc_fabric_bridge_owner;
|
|
294
|
+
GRANT SELECT (bot_id, tenant_id, status)
|
|
295
|
+
ON semicolony.bot_status TO sc_fabric_bridge_owner;
|
|
296
|
+
GRANT SELECT (slug, tenant_id, status)
|
|
297
|
+
ON semicolony.agent_personas TO sc_fabric_bridge_owner;
|
|
298
|
+
GRANT SELECT (id, slug, tenant_type, metadata)
|
|
299
|
+
ON public.tenants TO sc_fabric_bridge_owner;
|
|
300
|
+
REVOKE EXECUTE ON FUNCTION public.current_tenant_id(), public.is_provider()
|
|
301
|
+
FROM PUBLIC, sc_fabric_bridge;
|
|
302
|
+
GRANT EXECUTE ON FUNCTION public.current_tenant_id(), public.is_provider()
|
|
303
|
+
TO sc_app, sc_provider, sc_fabric_bridge_owner;
|
|
304
|
+
|
|
305
|
+
SET ROLE sc_fabric_bridge_owner;
|
|
306
|
+
|
|
307
|
+
CREATE OR REPLACE FUNCTION fabric_bridge.resolve_principal(p_token_hash text)
|
|
308
|
+
RETURNS TABLE (
|
|
309
|
+
credential_id uuid,
|
|
310
|
+
credential_kind text,
|
|
311
|
+
actor_id text,
|
|
312
|
+
principal_id text
|
|
313
|
+
)
|
|
314
|
+
LANGUAGE plpgsql
|
|
315
|
+
SECURITY DEFINER
|
|
316
|
+
SET search_path = pg_catalog
|
|
317
|
+
AS $function$
|
|
318
|
+
DECLARE
|
|
319
|
+
v_credential_id uuid;
|
|
320
|
+
v_tenant_id uuid;
|
|
321
|
+
v_metadata jsonb;
|
|
322
|
+
BEGIN
|
|
323
|
+
IF p_token_hash !~ '^[0-9a-f]{64}$' THEN
|
|
324
|
+
RETURN;
|
|
325
|
+
END IF;
|
|
326
|
+
|
|
327
|
+
SELECT gc.id, gc.tenant_id, gc.metadata
|
|
328
|
+
INTO v_credential_id, v_tenant_id, v_metadata
|
|
329
|
+
FROM semicolony.gateway_credentials gc
|
|
330
|
+
WHERE gc.token_hash = p_token_hash
|
|
331
|
+
AND gc.tenant_slug = 'team-semicolon'
|
|
332
|
+
AND gc.status = 'active'
|
|
333
|
+
AND (gc.expires_at IS NULL OR gc.expires_at > clock_timestamp())
|
|
334
|
+
AND gc.scopes = ARRAY['fabric:query']::text[]
|
|
335
|
+
AND gc.metadata->>'credential_audience' = 'semo-ops:fabric-bridge'
|
|
336
|
+
AND gc.metadata->>'credential_scope' = 'fabric:query'
|
|
337
|
+
AND gc.metadata->>'tenant_anchor' = 'team-semicolon'
|
|
338
|
+
AND COALESCE(gc.metadata->>'lineage_status', 'active') = 'active'
|
|
339
|
+
LIMIT 1;
|
|
340
|
+
IF NOT FOUND THEN
|
|
341
|
+
RETURN;
|
|
342
|
+
END IF;
|
|
343
|
+
|
|
344
|
+
-- Bind RLS to the credential's immutable tenant before consulting tenant-owned rows.
|
|
345
|
+
PERFORM set_config('app.current_tenant_id', v_tenant_id::text, true);
|
|
346
|
+
PERFORM set_config('app.is_provider', 'off', true);
|
|
347
|
+
IF NOT EXISTS (
|
|
348
|
+
SELECT 1
|
|
349
|
+
FROM public.tenants t
|
|
350
|
+
WHERE t.id = v_tenant_id
|
|
351
|
+
AND t.slug = 'team-semicolon'
|
|
352
|
+
AND t.tenant_type = 'team'
|
|
353
|
+
AND COALESCE(t.metadata->>'status', 'active') = 'active'
|
|
354
|
+
) THEN
|
|
355
|
+
RETURN;
|
|
356
|
+
END IF;
|
|
357
|
+
|
|
358
|
+
IF v_metadata->>'credential_kind' = 'agent'
|
|
359
|
+
AND v_metadata->>'agent_id' = 'colony'
|
|
360
|
+
AND v_metadata->>'actor' = 'agent:team-semicolon:colony'
|
|
361
|
+
AND v_metadata->>'principal_kind' = 'group'
|
|
362
|
+
AND v_metadata->>'principal' = 'team-semicolon:member'
|
|
363
|
+
AND EXISTS (
|
|
364
|
+
SELECT 1
|
|
365
|
+
FROM semicolony.bot_status bs
|
|
366
|
+
WHERE bs.bot_id = 'colony'
|
|
367
|
+
AND bs.tenant_id = v_tenant_id
|
|
368
|
+
AND bs.status IN ('online', 'active')
|
|
369
|
+
)
|
|
370
|
+
AND EXISTS (
|
|
371
|
+
SELECT 1
|
|
372
|
+
FROM semicolony.agent_personas ap
|
|
373
|
+
WHERE ap.slug = 'colony'
|
|
374
|
+
AND ap.tenant_id = v_tenant_id
|
|
375
|
+
AND ap.status = 'active'
|
|
376
|
+
) THEN
|
|
377
|
+
RETURN QUERY
|
|
378
|
+
SELECT
|
|
379
|
+
v_credential_id,
|
|
380
|
+
'agent'::text,
|
|
381
|
+
'agent:team-semicolon:colony'::text,
|
|
382
|
+
'team-semicolon:member'::text;
|
|
383
|
+
RETURN;
|
|
384
|
+
END IF;
|
|
385
|
+
|
|
386
|
+
IF v_metadata->>'credential_kind' = 'worker'
|
|
387
|
+
AND v_metadata->>'worker_id' ~ '^[a-z][a-z0-9-]{0,63}$'
|
|
388
|
+
AND v_metadata->>'device_id' ~ '^[a-z][a-z0-9-]{0,99}$'
|
|
389
|
+
AND EXISTS (
|
|
390
|
+
SELECT 1
|
|
391
|
+
FROM semicolony.worker_devices wd
|
|
392
|
+
WHERE wd.fabric_gateway_credential_id = v_credential_id
|
|
393
|
+
AND wd.worker_id = v_metadata->>'worker_id'
|
|
394
|
+
AND wd.device_id = v_metadata->>'device_id'
|
|
395
|
+
AND wd.status = 'active'
|
|
396
|
+
AND wd.kb_access_mode = 'worker-gateway'
|
|
397
|
+
AND wd.last_doctor_status = 'pass'
|
|
398
|
+
AND wd.last_seen_at >= clock_timestamp() - interval '24 hours'
|
|
399
|
+
) THEN
|
|
400
|
+
RETURN QUERY
|
|
401
|
+
SELECT
|
|
402
|
+
v_credential_id,
|
|
403
|
+
'worker'::text,
|
|
404
|
+
('worker:team-semicolon:' || (v_metadata->>'worker_id'))::text,
|
|
405
|
+
'team-semicolon:member'::text;
|
|
406
|
+
END IF;
|
|
407
|
+
END;
|
|
408
|
+
$function$;
|
|
409
|
+
|
|
410
|
+
RESET ROLE;
|
|
411
|
+
|
|
412
|
+
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA semicolony, public FROM sc_fabric_bridge;
|
|
413
|
+
REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA semicolony, public FROM sc_fabric_bridge;
|
|
414
|
+
REVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA semicolony, public FROM sc_fabric_bridge;
|
|
415
|
+
REVOKE ALL ON SCHEMA semicolony, public FROM sc_fabric_bridge;
|
|
416
|
+
REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA fabric_bridge
|
|
417
|
+
FROM PUBLIC, sc_app, sc_provider, sc_fabric_bridge;
|
|
418
|
+
REVOKE ALL ON SCHEMA fabric_bridge FROM sc_fabric_bridge;
|
|
419
|
+
|
|
420
|
+
SET ROLE sc_fabric_bridge_owner;
|
|
421
|
+
|
|
422
|
+
GRANT USAGE ON SCHEMA fabric_bridge TO sc_fabric_bridge;
|
|
423
|
+
GRANT EXECUTE ON FUNCTION fabric_bridge.resolve_principal(text) TO sc_fabric_bridge;
|
|
424
|
+
|
|
425
|
+
RESET ROLE;
|
|
426
|
+
|
|
427
|
+
DO $$
|
|
428
|
+
BEGIN
|
|
429
|
+
EXECUTE format('REVOKE sc_fabric_bridge_owner FROM %I', current_user);
|
|
430
|
+
END $$;
|
|
431
|
+
|
|
432
|
+
-- Treat the migration result as a catalog-level security postcondition, not merely
|
|
433
|
+
-- a sequence of successful GRANT/REVOKE statements.
|
|
434
|
+
DO $$
|
|
435
|
+
DECLARE
|
|
436
|
+
v_bridge_oid oid;
|
|
437
|
+
v_owner_oid oid;
|
|
438
|
+
v_schema_oid oid;
|
|
439
|
+
v_resolver_oid oid;
|
|
440
|
+
v_unexpected_objects boolean;
|
|
441
|
+
v_function_count integer;
|
|
442
|
+
v_schema_acl_count integer;
|
|
443
|
+
v_schema_owner_create integer;
|
|
444
|
+
v_schema_owner_usage integer;
|
|
445
|
+
v_schema_bridge_usage integer;
|
|
446
|
+
v_function_acl_count integer;
|
|
447
|
+
v_function_owner_execute integer;
|
|
448
|
+
v_function_bridge_execute integer;
|
|
449
|
+
v_unsafe_default_acl boolean;
|
|
450
|
+
v_global_default_acl_count integer;
|
|
451
|
+
v_global_owner_execute integer;
|
|
452
|
+
BEGIN
|
|
453
|
+
SELECT oid INTO STRICT v_owner_oid
|
|
454
|
+
FROM pg_catalog.pg_roles
|
|
455
|
+
WHERE rolname = 'sc_fabric_bridge_owner';
|
|
456
|
+
SELECT oid INTO STRICT v_bridge_oid
|
|
457
|
+
FROM pg_catalog.pg_roles
|
|
458
|
+
WHERE rolname = 'sc_fabric_bridge';
|
|
459
|
+
SELECT n.oid
|
|
460
|
+
INTO v_schema_oid
|
|
461
|
+
FROM pg_catalog.pg_namespace n
|
|
462
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
463
|
+
AND n.nspowner = v_owner_oid;
|
|
464
|
+
|
|
465
|
+
SELECT EXISTS (
|
|
466
|
+
SELECT 1
|
|
467
|
+
FROM pg_catalog.pg_class c
|
|
468
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
469
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
470
|
+
UNION ALL
|
|
471
|
+
SELECT 1
|
|
472
|
+
FROM pg_catalog.pg_type t
|
|
473
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
|
|
474
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
475
|
+
UNION ALL
|
|
476
|
+
SELECT 1
|
|
477
|
+
FROM pg_catalog.pg_collation c
|
|
478
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.collnamespace
|
|
479
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
480
|
+
UNION ALL
|
|
481
|
+
SELECT 1
|
|
482
|
+
FROM pg_catalog.pg_conversion c
|
|
483
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.connamespace
|
|
484
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
485
|
+
UNION ALL
|
|
486
|
+
SELECT 1
|
|
487
|
+
FROM pg_catalog.pg_operator o
|
|
488
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = o.oprnamespace
|
|
489
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
490
|
+
UNION ALL
|
|
491
|
+
SELECT 1
|
|
492
|
+
FROM pg_catalog.pg_opclass o
|
|
493
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = o.opcnamespace
|
|
494
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
495
|
+
UNION ALL
|
|
496
|
+
SELECT 1
|
|
497
|
+
FROM pg_catalog.pg_opfamily o
|
|
498
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = o.opfnamespace
|
|
499
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
500
|
+
UNION ALL
|
|
501
|
+
SELECT 1
|
|
502
|
+
FROM pg_catalog.pg_statistic_ext s
|
|
503
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = s.stxnamespace
|
|
504
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
505
|
+
UNION ALL
|
|
506
|
+
SELECT 1
|
|
507
|
+
FROM pg_catalog.pg_ts_config t
|
|
508
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.cfgnamespace
|
|
509
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
510
|
+
UNION ALL
|
|
511
|
+
SELECT 1
|
|
512
|
+
FROM pg_catalog.pg_ts_dict t
|
|
513
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.dictnamespace
|
|
514
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
515
|
+
UNION ALL
|
|
516
|
+
SELECT 1
|
|
517
|
+
FROM pg_catalog.pg_ts_parser t
|
|
518
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.prsnamespace
|
|
519
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
520
|
+
UNION ALL
|
|
521
|
+
SELECT 1
|
|
522
|
+
FROM pg_catalog.pg_ts_template t
|
|
523
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace
|
|
524
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
525
|
+
UNION ALL
|
|
526
|
+
SELECT 1
|
|
527
|
+
FROM pg_catalog.pg_constraint c
|
|
528
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.connamespace
|
|
529
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
530
|
+
UNION ALL
|
|
531
|
+
SELECT 1
|
|
532
|
+
FROM pg_catalog.pg_extension e
|
|
533
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
|
|
534
|
+
WHERE n.nspname = 'fabric_bridge'
|
|
535
|
+
) INTO v_unexpected_objects;
|
|
536
|
+
|
|
537
|
+
SELECT count(*)
|
|
538
|
+
INTO v_function_count
|
|
539
|
+
FROM pg_catalog.pg_proc p
|
|
540
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
|
|
541
|
+
WHERE n.nspname = 'fabric_bridge';
|
|
542
|
+
|
|
543
|
+
SELECT p.oid
|
|
544
|
+
INTO v_resolver_oid
|
|
545
|
+
FROM pg_catalog.pg_proc p
|
|
546
|
+
WHERE p.pronamespace = v_schema_oid
|
|
547
|
+
AND p.proname = 'resolve_principal'
|
|
548
|
+
AND p.prokind = 'f'
|
|
549
|
+
AND p.pronargs = 1
|
|
550
|
+
AND p.proargtypes = '25'::pg_catalog.oidvector
|
|
551
|
+
AND p.proretset
|
|
552
|
+
AND p.prorettype = 2249
|
|
553
|
+
AND p.proallargtypes = ARRAY[25, 2950, 25, 25, 25]::oid[]
|
|
554
|
+
AND p.proargmodes = ARRAY['i', 't', 't', 't', 't']::"char"[]
|
|
555
|
+
AND p.proargnames = ARRAY[
|
|
556
|
+
'p_token_hash',
|
|
557
|
+
'credential_id',
|
|
558
|
+
'credential_kind',
|
|
559
|
+
'actor_id',
|
|
560
|
+
'principal_id'
|
|
561
|
+
]::text[]
|
|
562
|
+
AND p.proowner = v_owner_oid
|
|
563
|
+
AND p.prosecdef
|
|
564
|
+
AND p.proconfig = ARRAY['search_path=pg_catalog']::text[];
|
|
565
|
+
|
|
566
|
+
SELECT
|
|
567
|
+
count(*),
|
|
568
|
+
count(*) FILTER (
|
|
569
|
+
WHERE acl.grantee = v_owner_oid
|
|
570
|
+
AND acl.grantor = v_owner_oid
|
|
571
|
+
AND acl.privilege_type = 'CREATE'
|
|
572
|
+
AND NOT acl.is_grantable
|
|
573
|
+
),
|
|
574
|
+
count(*) FILTER (
|
|
575
|
+
WHERE acl.grantee = v_owner_oid
|
|
576
|
+
AND acl.grantor = v_owner_oid
|
|
577
|
+
AND acl.privilege_type = 'USAGE'
|
|
578
|
+
AND NOT acl.is_grantable
|
|
579
|
+
),
|
|
580
|
+
count(*) FILTER (
|
|
581
|
+
WHERE acl.grantee = v_bridge_oid
|
|
582
|
+
AND acl.grantor = v_owner_oid
|
|
583
|
+
AND acl.privilege_type = 'USAGE'
|
|
584
|
+
AND NOT acl.is_grantable
|
|
585
|
+
)
|
|
586
|
+
INTO
|
|
587
|
+
v_schema_acl_count,
|
|
588
|
+
v_schema_owner_create,
|
|
589
|
+
v_schema_owner_usage,
|
|
590
|
+
v_schema_bridge_usage
|
|
591
|
+
FROM pg_catalog.pg_namespace n
|
|
592
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(
|
|
593
|
+
COALESCE(n.nspacl, pg_catalog.acldefault('n', n.nspowner))
|
|
594
|
+
) acl
|
|
595
|
+
WHERE n.oid = v_schema_oid;
|
|
596
|
+
|
|
597
|
+
SELECT
|
|
598
|
+
count(*),
|
|
599
|
+
count(*) FILTER (
|
|
600
|
+
WHERE acl.grantee = v_owner_oid
|
|
601
|
+
AND acl.grantor = v_owner_oid
|
|
602
|
+
AND acl.privilege_type = 'EXECUTE'
|
|
603
|
+
AND NOT acl.is_grantable
|
|
604
|
+
),
|
|
605
|
+
count(*) FILTER (
|
|
606
|
+
WHERE acl.grantee = v_bridge_oid
|
|
607
|
+
AND acl.grantor = v_owner_oid
|
|
608
|
+
AND acl.privilege_type = 'EXECUTE'
|
|
609
|
+
AND NOT acl.is_grantable
|
|
610
|
+
)
|
|
611
|
+
INTO
|
|
612
|
+
v_function_acl_count,
|
|
613
|
+
v_function_owner_execute,
|
|
614
|
+
v_function_bridge_execute
|
|
615
|
+
FROM pg_catalog.pg_proc p
|
|
616
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(
|
|
617
|
+
COALESCE(p.proacl, pg_catalog.acldefault('f', p.proowner))
|
|
618
|
+
) acl
|
|
619
|
+
WHERE p.oid = v_resolver_oid;
|
|
620
|
+
|
|
621
|
+
SELECT EXISTS (
|
|
622
|
+
SELECT 1
|
|
623
|
+
FROM pg_catalog.pg_default_acl d
|
|
624
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(d.defaclacl) acl
|
|
625
|
+
WHERE d.defaclrole = v_owner_oid
|
|
626
|
+
AND d.defaclobjtype = 'f'
|
|
627
|
+
AND (
|
|
628
|
+
d.defaclnamespace = 0
|
|
629
|
+
OR d.defaclnamespace = v_schema_oid
|
|
630
|
+
)
|
|
631
|
+
AND NOT (
|
|
632
|
+
acl.grantee = v_owner_oid
|
|
633
|
+
AND acl.grantor = v_owner_oid
|
|
634
|
+
AND acl.privilege_type = 'EXECUTE'
|
|
635
|
+
AND NOT acl.is_grantable
|
|
636
|
+
)
|
|
637
|
+
) INTO v_unsafe_default_acl;
|
|
638
|
+
|
|
639
|
+
SELECT
|
|
640
|
+
count(*),
|
|
641
|
+
count(*) FILTER (
|
|
642
|
+
WHERE acl.grantee = v_owner_oid
|
|
643
|
+
AND acl.grantor = v_owner_oid
|
|
644
|
+
AND acl.privilege_type = 'EXECUTE'
|
|
645
|
+
AND NOT acl.is_grantable
|
|
646
|
+
)
|
|
647
|
+
INTO
|
|
648
|
+
v_global_default_acl_count,
|
|
649
|
+
v_global_owner_execute
|
|
650
|
+
FROM pg_catalog.pg_default_acl d
|
|
651
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(d.defaclacl) acl
|
|
652
|
+
WHERE d.defaclrole = v_owner_oid
|
|
653
|
+
AND d.defaclobjtype = 'f'
|
|
654
|
+
AND d.defaclnamespace = 0;
|
|
655
|
+
|
|
656
|
+
IF v_schema_oid IS NULL
|
|
657
|
+
OR v_resolver_oid IS NULL
|
|
658
|
+
OR v_unexpected_objects
|
|
659
|
+
OR v_function_count <> 1
|
|
660
|
+
OR v_schema_acl_count <> 3
|
|
661
|
+
OR v_schema_owner_create <> 1
|
|
662
|
+
OR v_schema_owner_usage <> 1
|
|
663
|
+
OR v_schema_bridge_usage <> 1
|
|
664
|
+
OR v_function_acl_count <> 2
|
|
665
|
+
OR v_function_owner_execute <> 1
|
|
666
|
+
OR v_function_bridge_execute <> 1
|
|
667
|
+
OR v_unsafe_default_acl
|
|
668
|
+
OR v_global_default_acl_count <> 1
|
|
669
|
+
OR v_global_owner_execute <> 1
|
|
670
|
+
OR EXISTS (
|
|
671
|
+
SELECT 1
|
|
672
|
+
FROM pg_catalog.pg_auth_members m
|
|
673
|
+
WHERE m.roleid IN (v_owner_oid, v_bridge_oid)
|
|
674
|
+
OR m.member IN (v_owner_oid, v_bridge_oid)
|
|
675
|
+
) THEN
|
|
676
|
+
RAISE EXCEPTION 'fabric bridge postcondition rejected';
|
|
677
|
+
END IF;
|
|
678
|
+
END $$;
|