@creatornader/agent-bridge 0.2.0 → 0.3.1
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/CHANGELOG.md +86 -4
- package/README.md +567 -55
- package/ROADMAP.md +114 -0
- package/SECURITY.md +90 -0
- package/SKILL.md +68 -2
- package/bin/agent-bridge +2 -2
- package/clients/claude-desktop.json +3 -1
- package/dist/archive.d.ts +163 -0
- package/dist/archive.js +50 -0
- package/dist/archive.js.map +1 -0
- package/dist/bridge-domain-CBX3IVqC.d.ts +153 -0
- package/dist/{bridge-service-CfqMcMAn.d.ts → bridge-service-hX1ZQp4W.d.ts} +15 -5
- package/dist/bridge-store-DPVSCwfs.d.ts +102 -0
- package/dist/bridge.d.ts +76 -17
- package/dist/bridge.js +52 -24
- package/dist/chunk-2DFNOLMO.js +618 -0
- package/dist/chunk-2DFNOLMO.js.map +1 -0
- package/dist/{chunk-KVR4JBRF.js → chunk-2EVLG3YA.js} +2 -2
- package/dist/{chunk-KVR4JBRF.js.map → chunk-2EVLG3YA.js.map} +1 -1
- package/dist/chunk-45BZGB77.js +709 -0
- package/dist/chunk-45BZGB77.js.map +1 -0
- package/dist/chunk-4VB44KJU.js +230 -0
- package/dist/chunk-4VB44KJU.js.map +1 -0
- package/dist/chunk-5GMB3A77.js +1013 -0
- package/dist/chunk-5GMB3A77.js.map +1 -0
- package/dist/chunk-A6W4QALF.js +1258 -0
- package/dist/chunk-A6W4QALF.js.map +1 -0
- package/dist/chunk-BZLMF5O5.js +328 -0
- package/dist/chunk-BZLMF5O5.js.map +1 -0
- package/dist/{chunk-AXL2I7FH.js → chunk-CEXBMOI5.js} +240 -79
- package/dist/chunk-CEXBMOI5.js.map +1 -0
- package/dist/chunk-GADH244D.js +527 -0
- package/dist/chunk-GADH244D.js.map +1 -0
- package/dist/chunk-MBPZXTJA.js +2428 -0
- package/dist/chunk-MBPZXTJA.js.map +1 -0
- package/dist/chunk-NCO7RLBM.js +146 -0
- package/dist/chunk-NCO7RLBM.js.map +1 -0
- package/dist/chunk-Q6PPMXJC.js +1050 -0
- package/dist/chunk-Q6PPMXJC.js.map +1 -0
- package/dist/{chunk-4KAOBC5X.js → chunk-SPSMZOCJ.js} +190 -165
- package/dist/chunk-SPSMZOCJ.js.map +1 -0
- package/dist/chunk-UAHMP6GC.js +54 -0
- package/dist/chunk-UAHMP6GC.js.map +1 -0
- package/dist/{chunk-YD6YJATN.js → chunk-VOQIDTCJ.js} +4 -2
- package/dist/chunk-VOQIDTCJ.js.map +1 -0
- package/dist/chunk-WAI2IYIQ.js +554 -0
- package/dist/chunk-WAI2IYIQ.js.map +1 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +3555 -214
- package/dist/cli.js.map +1 -1
- package/dist/client-config.d.ts +1 -1
- package/dist/client-config.js +1 -1
- package/dist/client-runtime.d.ts +10 -6
- package/dist/client-runtime.js +9 -6
- package/dist/contracts/generate.d.ts +2 -0
- package/dist/contracts/generate.js +247 -0
- package/dist/contracts/generate.js.map +1 -0
- package/dist/dr.d.ts +324 -0
- package/dist/dr.js +124 -0
- package/dist/dr.js.map +1 -0
- package/dist/gateway-CggQ8vTC.d.ts +78 -0
- package/dist/gateway-main.js +190 -8
- package/dist/gateway-main.js.map +1 -1
- package/dist/gateway.d.ts +4 -3
- package/dist/gateway.js +4 -3
- package/dist/index.js +11 -8
- package/dist/index.js.map +1 -1
- package/dist/postgres-bridge-store-Bfd3hRId.d.ts +49 -0
- package/dist/server.d.ts +3 -2
- package/dist/server.js +13 -8
- package/dist/sqlite.d.ts +94 -12
- package/dist/sqlite.js +7 -4
- package/docs/architecture-v2.md +394 -0
- package/docs/decisions/0001-protocol-layers-and-acknowledgment-semantics.md +85 -0
- package/docs/decisions/0002-canonical-operation-contract-registry.md +46 -0
- package/docs/decisions/0003-host-adapters-and-consumer-instance-keys.md +88 -0
- package/docs/ecosystem.md +86 -0
- package/docs/postmortems/2026-07-08-wrapper-source-drift.md +82 -0
- package/docs/troubleshooting.md +89 -0
- package/llms.txt +107 -6
- package/openapi/agent-bridge-v2.openapi.json +9610 -0
- package/package.json +24 -4
- package/schemas/agent-bridge-v2.capabilities.json +1829 -0
- package/schemas/agent-bridge-v2.mcp.json +2074 -0
- package/schemas/agent-bridge-v2.schema.json +4560 -0
- package/sql/migrations/008_message_projects.sql +36 -0
- package/sql/migrations/009_mailbox_query_indexes.sql +28 -0
- package/sql/migrations/010_publisher_delivery_policy.sql +210 -0
- package/sql/migrations/011_credential_security.sql +875 -0
- package/sql/migrations/012_request_authority.sql +177 -0
- package/sql/migrations/013_row_isolation.sql +495 -0
- package/sql/migrations/014_owner_control_plane.sql +1641 -0
- package/sql/migrations/015_portable_archives.sql +1813 -0
- package/sql/migrations/016_native_dr_fence.sql +1005 -0
- package/bin/CLAUDE.md +0 -11
- package/dist/bridge-domain-BcnBeHOn.d.ts +0 -97
- package/dist/bridge-store-BuGjxTn4.d.ts +0 -61
- package/dist/chunk-4KAOBC5X.js.map +0 -1
- package/dist/chunk-6MAZ6YLZ.js +0 -146
- package/dist/chunk-6MAZ6YLZ.js.map +0 -1
- package/dist/chunk-AXL2I7FH.js.map +0 -1
- package/dist/chunk-BV6W4AJK.js +0 -211
- package/dist/chunk-BV6W4AJK.js.map +0 -1
- package/dist/chunk-CKSKUBDQ.js +0 -153
- package/dist/chunk-CKSKUBDQ.js.map +0 -1
- package/dist/chunk-JOUBDRYX.js +0 -272
- package/dist/chunk-JOUBDRYX.js.map +0 -1
- package/dist/chunk-JTC5YURG.js +0 -49
- package/dist/chunk-JTC5YURG.js.map +0 -1
- package/dist/chunk-O26W5QHS.js +0 -409
- package/dist/chunk-O26W5QHS.js.map +0 -1
- package/dist/chunk-WEULPMUY.js +0 -988
- package/dist/chunk-WEULPMUY.js.map +0 -1
- package/dist/chunk-YD6YJATN.js.map +0 -1
- package/dist/gateway-BULozv3K.d.ts +0 -54
|
@@ -0,0 +1,1005 @@
|
|
|
1
|
+
begin;
|
|
2
|
+
|
|
3
|
+
select pg_advisory_xact_lock(1646705660);
|
|
4
|
+
|
|
5
|
+
-- Native DR fence contract: backup holds pg_advisory_lock(1646705664).
|
|
6
|
+
-- Migration and Agent Bridge membership mutations hold the shared transaction form.
|
|
7
|
+
select pg_advisory_xact_lock_shared(1646705664);
|
|
8
|
+
|
|
9
|
+
do $identifier_preflight$
|
|
10
|
+
declare
|
|
11
|
+
rejected_role text;
|
|
12
|
+
begin
|
|
13
|
+
with names as (select
|
|
14
|
+
namespace.nspowner schema_owner,
|
|
15
|
+
('agent_bridge_control_owner_'||substr(md5(current_database()),1,16))::name control_owner
|
|
16
|
+
from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge'
|
|
17
|
+
), derived_roles(oid) as (
|
|
18
|
+
select role.oid from pg_catalog.pg_roles role where role.rolname in (
|
|
19
|
+
'agent_bridge_runtime_'||substr(md5(current_database()),1,16),
|
|
20
|
+
'agent_bridge_data_owner_'||substr(md5(current_database()),1,16),
|
|
21
|
+
'agent_bridge_context_reader_'||substr(md5(current_database()),1,16),
|
|
22
|
+
'agent_bridge_event_writer_'||substr(md5(current_database()),1,16),
|
|
23
|
+
'agent_bridge_control_owner_'||substr(md5(current_database()),1,16),
|
|
24
|
+
'agent_bridge_control_operator_'||substr(md5(current_database()),1,16),
|
|
25
|
+
'agent_bridge_control_auditor_'||substr(md5(current_database()),1,16),
|
|
26
|
+
'agent_bridge_archive_operator_'||substr(md5(current_database()),1,16)
|
|
27
|
+
)
|
|
28
|
+
), object_roles(oid) as (
|
|
29
|
+
select schema_owner from names
|
|
30
|
+
union select relation.relowner from pg_catalog.pg_class relation
|
|
31
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
|
|
32
|
+
where namespace.nspname='agent_bridge'
|
|
33
|
+
union select procedure.proowner from pg_catalog.pg_proc procedure
|
|
34
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
|
|
35
|
+
where namespace.nspname='agent_bridge'
|
|
36
|
+
union select type_record.typowner from pg_catalog.pg_type type_record
|
|
37
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=type_record.typnamespace
|
|
38
|
+
where namespace.nspname='agent_bridge'
|
|
39
|
+
union select privilege.grantor from pg_catalog.pg_namespace namespace
|
|
40
|
+
cross join lateral pg_catalog.aclexplode(namespace.nspacl) privilege
|
|
41
|
+
where namespace.nspname='agent_bridge'
|
|
42
|
+
union select privilege.grantee from pg_catalog.pg_namespace namespace
|
|
43
|
+
cross join lateral pg_catalog.aclexplode(namespace.nspacl) privilege
|
|
44
|
+
where namespace.nspname='agent_bridge' and privilege.grantee<>0
|
|
45
|
+
union select privilege.grantor from pg_catalog.pg_class relation
|
|
46
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
|
|
47
|
+
cross join lateral pg_catalog.aclexplode(relation.relacl) privilege
|
|
48
|
+
where namespace.nspname='agent_bridge'
|
|
49
|
+
union select privilege.grantee from pg_catalog.pg_class relation
|
|
50
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
|
|
51
|
+
cross join lateral pg_catalog.aclexplode(relation.relacl) privilege
|
|
52
|
+
where namespace.nspname='agent_bridge' and privilege.grantee<>0
|
|
53
|
+
union select privilege.grantor from pg_catalog.pg_attribute attribute
|
|
54
|
+
join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
|
|
55
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
|
|
56
|
+
cross join lateral pg_catalog.aclexplode(attribute.attacl) privilege
|
|
57
|
+
where namespace.nspname='agent_bridge' and attribute.attnum>0
|
|
58
|
+
and not attribute.attisdropped and attribute.attacl is not null
|
|
59
|
+
union select privilege.grantee from pg_catalog.pg_attribute attribute
|
|
60
|
+
join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
|
|
61
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
|
|
62
|
+
cross join lateral pg_catalog.aclexplode(attribute.attacl) privilege
|
|
63
|
+
where namespace.nspname='agent_bridge' and attribute.attnum>0
|
|
64
|
+
and not attribute.attisdropped and attribute.attacl is not null
|
|
65
|
+
and privilege.grantee<>0
|
|
66
|
+
union select privilege.grantor from pg_catalog.pg_proc procedure
|
|
67
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
|
|
68
|
+
cross join lateral pg_catalog.aclexplode(procedure.proacl) privilege
|
|
69
|
+
where namespace.nspname='agent_bridge'
|
|
70
|
+
union select privilege.grantee from pg_catalog.pg_proc procedure
|
|
71
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
|
|
72
|
+
cross join lateral pg_catalog.aclexplode(procedure.proacl) privilege
|
|
73
|
+
where namespace.nspname='agent_bridge' and privilege.grantee<>0
|
|
74
|
+
union select default_acl.defaclrole from pg_catalog.pg_default_acl default_acl
|
|
75
|
+
left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
|
|
76
|
+
cross join names where namespace.nspname='agent_bridge'
|
|
77
|
+
or default_acl.defaclrole in (names.schema_owner,
|
|
78
|
+
(select oid from pg_catalog.pg_roles where rolname=names.control_owner))
|
|
79
|
+
union select privilege.grantor from pg_catalog.pg_default_acl default_acl
|
|
80
|
+
left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
|
|
81
|
+
cross join lateral pg_catalog.aclexplode(default_acl.defaclacl) privilege
|
|
82
|
+
cross join names where namespace.nspname='agent_bridge'
|
|
83
|
+
or default_acl.defaclrole in (names.schema_owner,
|
|
84
|
+
(select oid from pg_catalog.pg_roles where rolname=names.control_owner))
|
|
85
|
+
union select privilege.grantee from pg_catalog.pg_default_acl default_acl
|
|
86
|
+
left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
|
|
87
|
+
cross join lateral pg_catalog.aclexplode(default_acl.defaclacl) privilege
|
|
88
|
+
cross join names where privilege.grantee<>0 and (
|
|
89
|
+
namespace.nspname='agent_bridge'
|
|
90
|
+
or default_acl.defaclrole in (names.schema_owner,
|
|
91
|
+
(select oid from pg_catalog.pg_roles where rolname=names.control_owner))
|
|
92
|
+
)
|
|
93
|
+
), base_roles(oid) as (
|
|
94
|
+
select oid from derived_roles union select oid from object_roles
|
|
95
|
+
), relevant_roles(oid) as (
|
|
96
|
+
select oid from base_roles
|
|
97
|
+
union select membership.roleid from pg_catalog.pg_auth_members membership
|
|
98
|
+
where membership.member in (select oid from base_roles)
|
|
99
|
+
union select membership.member from pg_catalog.pg_auth_members membership
|
|
100
|
+
where membership.roleid in (select oid from base_roles)
|
|
101
|
+
)
|
|
102
|
+
select role.rolname into rejected_role from relevant_roles relevant
|
|
103
|
+
join pg_catalog.pg_roles role on role.oid=relevant.oid
|
|
104
|
+
where role.rolname='PUBLIC' or position(':' in role.rolname)>0
|
|
105
|
+
or position(chr(30) in role.rolname)>0 or position(chr(31) in role.rolname)>0
|
|
106
|
+
order by role.rolname limit 1;
|
|
107
|
+
|
|
108
|
+
if rejected_role is not null then
|
|
109
|
+
raise exception 'native DR attestation cannot encode a reserved Agent Bridge role name'
|
|
110
|
+
using detail='rejected role: '||quote_literal(rejected_role);
|
|
111
|
+
end if;
|
|
112
|
+
end
|
|
113
|
+
$identifier_preflight$;
|
|
114
|
+
|
|
115
|
+
create or replace function agent_bridge.assert_control_actor(requested_capability text)
|
|
116
|
+
returns void language plpgsql security definer set search_path = '' as $$
|
|
117
|
+
declare
|
|
118
|
+
suffix text := substr(md5(current_database()),1,16);
|
|
119
|
+
schema_owner name;
|
|
120
|
+
operator_role name := ('agent_bridge_control_operator_'||suffix)::name;
|
|
121
|
+
auditor_role name := ('agent_bridge_control_auditor_'||suffix)::name;
|
|
122
|
+
operator_allowed boolean := false;
|
|
123
|
+
auditor_allowed boolean := false;
|
|
124
|
+
begin
|
|
125
|
+
select pg_catalog.pg_get_userbyid(namespace.nspowner)::name into schema_owner
|
|
126
|
+
from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge';
|
|
127
|
+
if requested_capability is null or requested_capability not in ('operator','inventory') then
|
|
128
|
+
raise exception 'control capability is invalid';
|
|
129
|
+
end if;
|
|
130
|
+
|
|
131
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
132
|
+
'control-membership'||chr(31)||session_user||chr(31)||'global',1646705661
|
|
133
|
+
));
|
|
134
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
135
|
+
'control-membership'||chr(31)||session_user||chr(31)||'operator',1646705661
|
|
136
|
+
));
|
|
137
|
+
if requested_capability='inventory' then
|
|
138
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
139
|
+
'control-membership'||chr(31)||session_user||chr(31)||'auditor',1646705661
|
|
140
|
+
));
|
|
141
|
+
end if;
|
|
142
|
+
|
|
143
|
+
with latest as (
|
|
144
|
+
select distinct on (event.control_role) event.control_role,event.action
|
|
145
|
+
from agent_bridge.control_membership_events event
|
|
146
|
+
where event.member_role=session_user and event.control_role in ('operator','auditor')
|
|
147
|
+
order by event.control_role,event.sequence desc
|
|
148
|
+
), direct_membership as (
|
|
149
|
+
select granted.rolname control_role,membership.admin_option,
|
|
150
|
+
coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
|
|
151
|
+
coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option
|
|
152
|
+
from pg_catalog.pg_auth_members membership
|
|
153
|
+
join pg_catalog.pg_roles granted on granted.oid=membership.roleid
|
|
154
|
+
join pg_catalog.pg_roles member on member.oid=membership.member
|
|
155
|
+
where member.rolname=session_user and granted.rolname in (operator_role,auditor_role)
|
|
156
|
+
)
|
|
157
|
+
select
|
|
158
|
+
coalesce(bool_or(membership.control_role=operator_role
|
|
159
|
+
and membership.inherit_option and membership.set_option and (
|
|
160
|
+
(session_user=schema_owner and membership.admin_option)
|
|
161
|
+
or (latest.control_role='operator' and latest.action='register'
|
|
162
|
+
and not membership.admin_option)
|
|
163
|
+
)),false),
|
|
164
|
+
coalesce(bool_or(membership.control_role=auditor_role
|
|
165
|
+
and membership.inherit_option and membership.set_option and (
|
|
166
|
+
(session_user=schema_owner and membership.admin_option)
|
|
167
|
+
or (latest.control_role='auditor' and latest.action='register'
|
|
168
|
+
and not membership.admin_option)
|
|
169
|
+
)),false)
|
|
170
|
+
into operator_allowed,auditor_allowed
|
|
171
|
+
from direct_membership membership left join latest on latest.control_role=case
|
|
172
|
+
membership.control_role when operator_role then 'operator' else 'auditor' end;
|
|
173
|
+
|
|
174
|
+
if session_user<>schema_owner and (exists (
|
|
175
|
+
select 1 from pg_catalog.pg_roles inherited
|
|
176
|
+
where inherited.rolname<>session_user
|
|
177
|
+
and pg_catalog.pg_has_role(session_user,inherited.rolname,'MEMBER')
|
|
178
|
+
and not (
|
|
179
|
+
(inherited.rolname=operator_role and operator_allowed)
|
|
180
|
+
or (inherited.rolname=auditor_role and auditor_allowed)
|
|
181
|
+
)
|
|
182
|
+
) or exists (
|
|
183
|
+
select 1 from pg_catalog.pg_roles candidate
|
|
184
|
+
where candidate.rolname not in (session_user,schema_owner)
|
|
185
|
+
and not candidate.rolsuper
|
|
186
|
+
and pg_catalog.pg_has_role(candidate.rolname,session_user,'MEMBER')
|
|
187
|
+
) or exists (
|
|
188
|
+
select 1 from pg_catalog.pg_roles actor
|
|
189
|
+
where actor.rolname=session_user and (
|
|
190
|
+
not actor.rolcanlogin or actor.rolsuper or actor.rolcreaterole or actor.rolcreatedb
|
|
191
|
+
or actor.rolreplication or actor.rolbypassrls
|
|
192
|
+
)
|
|
193
|
+
)) then
|
|
194
|
+
raise exception 'control actor has an unsafe membership graph';
|
|
195
|
+
end if;
|
|
196
|
+
|
|
197
|
+
if (requested_capability='operator' and not operator_allowed)
|
|
198
|
+
or (requested_capability='inventory' and not (operator_allowed or auditor_allowed)) then
|
|
199
|
+
raise exception 'control actor is not registered for requested capability';
|
|
200
|
+
end if;
|
|
201
|
+
end
|
|
202
|
+
$$;
|
|
203
|
+
|
|
204
|
+
create or replace function agent_bridge.register_control_member(
|
|
205
|
+
requested_request_id uuid, requested_member_role name, requested_control_role text
|
|
206
|
+
) returns table(replayed boolean)
|
|
207
|
+
language plpgsql security definer set search_path = '' as $$
|
|
208
|
+
declare
|
|
209
|
+
suffix text := substr(md5(current_database()),1,16);
|
|
210
|
+
target_role name;
|
|
211
|
+
prior agent_bridge.control_membership_events%rowtype;
|
|
212
|
+
member_record pg_catalog.pg_roles%rowtype;
|
|
213
|
+
begin
|
|
214
|
+
if requested_request_id is null or requested_member_role is null
|
|
215
|
+
or requested_control_role is null
|
|
216
|
+
or requested_control_role not in ('operator','auditor') then
|
|
217
|
+
raise exception 'invalid control membership registration';
|
|
218
|
+
end if;
|
|
219
|
+
perform pg_catalog.pg_advisory_xact_lock_shared(1646705664);
|
|
220
|
+
target_role := ('agent_bridge_control_'||requested_control_role||'_'||suffix)::name;
|
|
221
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
222
|
+
'control-membership'||chr(31)||requested_member_role||chr(31)||'global',
|
|
223
|
+
1646705661
|
|
224
|
+
));
|
|
225
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
226
|
+
'control-membership'||chr(31)||requested_member_role||chr(31)||requested_control_role,
|
|
227
|
+
1646705661
|
|
228
|
+
));
|
|
229
|
+
perform pg_catalog.pg_advisory_xact_lock(
|
|
230
|
+
pg_catalog.hashtextextended(requested_request_id::text,1646705660)
|
|
231
|
+
);
|
|
232
|
+
select * into prior from agent_bridge.control_membership_events
|
|
233
|
+
where request_id=requested_request_id;
|
|
234
|
+
if found then
|
|
235
|
+
if prior.action<>'register' or prior.member_role<>requested_member_role
|
|
236
|
+
or prior.control_role<>requested_control_role then
|
|
237
|
+
raise exception 'request id was already used with different content';
|
|
238
|
+
end if;
|
|
239
|
+
return query select true;
|
|
240
|
+
return;
|
|
241
|
+
end if;
|
|
242
|
+
select * into member_record from pg_catalog.pg_roles where rolname=requested_member_role;
|
|
243
|
+
if not found or not member_record.rolcanlogin or member_record.rolsuper
|
|
244
|
+
or member_record.rolcreaterole or member_record.rolcreatedb
|
|
245
|
+
or member_record.rolreplication or member_record.rolbypassrls
|
|
246
|
+
or requested_member_role in (
|
|
247
|
+
current_user::name,
|
|
248
|
+
('agent_bridge_control_owner_'||suffix)::name,
|
|
249
|
+
('agent_bridge_control_operator_'||suffix)::name,
|
|
250
|
+
('agent_bridge_control_auditor_'||suffix)::name
|
|
251
|
+
) then
|
|
252
|
+
raise exception 'control membership target is not an eligible login';
|
|
253
|
+
end if;
|
|
254
|
+
if exists (
|
|
255
|
+
select 1 from pg_catalog.pg_roles inherited
|
|
256
|
+
where inherited.rolname<>requested_member_role
|
|
257
|
+
and pg_catalog.pg_has_role(requested_member_role,inherited.rolname,'MEMBER')
|
|
258
|
+
and inherited.rolname<>target_role
|
|
259
|
+
and not exists (
|
|
260
|
+
select 1 from (
|
|
261
|
+
select distinct on (event.control_role)
|
|
262
|
+
event.control_role,event.action
|
|
263
|
+
from agent_bridge.control_membership_events event
|
|
264
|
+
where event.member_role=requested_member_role
|
|
265
|
+
order by event.control_role,event.sequence desc
|
|
266
|
+
) latest
|
|
267
|
+
where latest.action='register'
|
|
268
|
+
and inherited.rolname=(
|
|
269
|
+
'agent_bridge_control_'||latest.control_role||'_'||suffix
|
|
270
|
+
)::name
|
|
271
|
+
)
|
|
272
|
+
) or exists (
|
|
273
|
+
select 1 from pg_catalog.pg_roles candidate
|
|
274
|
+
where candidate.rolname not in (requested_member_role,current_user)
|
|
275
|
+
and not candidate.rolsuper
|
|
276
|
+
and pg_catalog.pg_has_role(candidate.rolname,requested_member_role,'MEMBER')
|
|
277
|
+
) then
|
|
278
|
+
raise exception 'control membership target has an unsafe membership graph';
|
|
279
|
+
end if;
|
|
280
|
+
if exists (
|
|
281
|
+
select 1 from pg_catalog.pg_auth_members membership
|
|
282
|
+
join pg_catalog.pg_roles granted on granted.oid=membership.roleid
|
|
283
|
+
join pg_catalog.pg_roles member on member.oid=membership.member
|
|
284
|
+
where granted.rolname=target_role and member.rolname=requested_member_role
|
|
285
|
+
and (
|
|
286
|
+
membership.admin_option
|
|
287
|
+
or not coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
|
|
288
|
+
or not coalesce((to_jsonb(membership)->>'set_option')::boolean,true)
|
|
289
|
+
)
|
|
290
|
+
) then
|
|
291
|
+
raise exception 'control membership target has an unsafe direct grant';
|
|
292
|
+
end if;
|
|
293
|
+
execute format('grant %I to %I',target_role,requested_member_role);
|
|
294
|
+
insert into agent_bridge.control_membership_events(
|
|
295
|
+
request_id,action,member_role,control_role,actor
|
|
296
|
+
) values (
|
|
297
|
+
requested_request_id,'register',requested_member_role,requested_control_role,session_user
|
|
298
|
+
);
|
|
299
|
+
return query select false;
|
|
300
|
+
end
|
|
301
|
+
$$;
|
|
302
|
+
|
|
303
|
+
create or replace function agent_bridge.revoke_control_member(
|
|
304
|
+
requested_request_id uuid, requested_member_role name, requested_control_role text
|
|
305
|
+
) returns table(replayed boolean)
|
|
306
|
+
language plpgsql security definer set search_path = '' as $$
|
|
307
|
+
declare
|
|
308
|
+
suffix text := substr(md5(current_database()),1,16);
|
|
309
|
+
target_role name;
|
|
310
|
+
prior agent_bridge.control_membership_events%rowtype;
|
|
311
|
+
begin
|
|
312
|
+
if requested_request_id is null or requested_member_role is null
|
|
313
|
+
or requested_control_role is null
|
|
314
|
+
or requested_control_role not in ('operator','auditor')
|
|
315
|
+
or requested_member_role in (
|
|
316
|
+
current_user::name,
|
|
317
|
+
('agent_bridge_control_owner_'||suffix)::name,
|
|
318
|
+
('agent_bridge_control_operator_'||suffix)::name,
|
|
319
|
+
('agent_bridge_control_auditor_'||suffix)::name
|
|
320
|
+
) then
|
|
321
|
+
raise exception 'invalid control membership revocation';
|
|
322
|
+
end if;
|
|
323
|
+
perform pg_catalog.pg_advisory_xact_lock_shared(1646705664);
|
|
324
|
+
target_role := ('agent_bridge_control_'||requested_control_role||'_'||suffix)::name;
|
|
325
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
326
|
+
'control-membership'||chr(31)||requested_member_role||chr(31)||'global',
|
|
327
|
+
1646705661
|
|
328
|
+
));
|
|
329
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
330
|
+
'control-membership'||chr(31)||requested_member_role||chr(31)||requested_control_role,
|
|
331
|
+
1646705661
|
|
332
|
+
));
|
|
333
|
+
perform pg_catalog.pg_advisory_xact_lock(
|
|
334
|
+
pg_catalog.hashtextextended(requested_request_id::text,1646705660)
|
|
335
|
+
);
|
|
336
|
+
select * into prior from agent_bridge.control_membership_events
|
|
337
|
+
where request_id=requested_request_id;
|
|
338
|
+
if found then
|
|
339
|
+
if prior.action<>'revoke' or prior.member_role<>requested_member_role
|
|
340
|
+
or prior.control_role<>requested_control_role then
|
|
341
|
+
raise exception 'request id was already used with different content';
|
|
342
|
+
end if;
|
|
343
|
+
return query select true;
|
|
344
|
+
return;
|
|
345
|
+
end if;
|
|
346
|
+
if exists(select 1 from pg_catalog.pg_roles where rolname=requested_member_role) then
|
|
347
|
+
execute format('revoke %I from %I',target_role,requested_member_role);
|
|
348
|
+
end if;
|
|
349
|
+
insert into agent_bridge.control_membership_events(
|
|
350
|
+
request_id,action,member_role,control_role,actor
|
|
351
|
+
) values (
|
|
352
|
+
requested_request_id,'revoke',requested_member_role,requested_control_role,session_user
|
|
353
|
+
);
|
|
354
|
+
return query select false;
|
|
355
|
+
end
|
|
356
|
+
$$;
|
|
357
|
+
|
|
358
|
+
create or replace function agent_bridge.assert_archive_actor()
|
|
359
|
+
returns void language plpgsql security definer set search_path = '' as $$
|
|
360
|
+
declare
|
|
361
|
+
suffix text := substr(md5(current_database()),1,16);
|
|
362
|
+
schema_owner name;
|
|
363
|
+
archive_role name := ('agent_bridge_archive_operator_'||suffix)::name;
|
|
364
|
+
archive_allowed boolean := false;
|
|
365
|
+
begin
|
|
366
|
+
if not agent_bridge.portable_archive_ready() then
|
|
367
|
+
raise exception 'portable archive readiness validation failed';
|
|
368
|
+
end if;
|
|
369
|
+
select pg_catalog.pg_get_userbyid(namespace.nspowner)::name into schema_owner
|
|
370
|
+
from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge';
|
|
371
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
372
|
+
'archive-membership'||chr(31)||session_user||chr(31)||'global',1646705662
|
|
373
|
+
));
|
|
374
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
375
|
+
'archive-membership'||chr(31)||session_user||chr(31)||'operator',1646705662
|
|
376
|
+
));
|
|
377
|
+
|
|
378
|
+
with latest as (
|
|
379
|
+
select event.action from agent_bridge.archive_membership_events event
|
|
380
|
+
where event.member_role=session_user order by event.sequence desc limit 1
|
|
381
|
+
), direct_membership as (
|
|
382
|
+
select membership.admin_option,
|
|
383
|
+
coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
|
|
384
|
+
coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option
|
|
385
|
+
from pg_catalog.pg_auth_members membership
|
|
386
|
+
join pg_catalog.pg_roles granted on granted.oid=membership.roleid
|
|
387
|
+
join pg_catalog.pg_roles member on member.oid=membership.member
|
|
388
|
+
where member.rolname=session_user and granted.rolname=archive_role
|
|
389
|
+
)
|
|
390
|
+
select coalesce(bool_or(
|
|
391
|
+
membership.inherit_option and membership.set_option and (
|
|
392
|
+
(session_user=schema_owner and membership.admin_option)
|
|
393
|
+
or (latest.action='register' and not membership.admin_option)
|
|
394
|
+
)
|
|
395
|
+
),false) into archive_allowed
|
|
396
|
+
from direct_membership membership left join latest on true;
|
|
397
|
+
|
|
398
|
+
if session_user<>schema_owner and (exists (
|
|
399
|
+
select 1 from pg_catalog.pg_roles inherited
|
|
400
|
+
where inherited.rolname<>session_user
|
|
401
|
+
and pg_catalog.pg_has_role(session_user,inherited.rolname,'MEMBER')
|
|
402
|
+
and inherited.rolname<>archive_role
|
|
403
|
+
) or exists (
|
|
404
|
+
select 1 from pg_catalog.pg_roles candidate
|
|
405
|
+
where candidate.rolname not in (session_user,schema_owner)
|
|
406
|
+
and not candidate.rolsuper
|
|
407
|
+
and pg_catalog.pg_has_role(candidate.rolname,session_user,'MEMBER')
|
|
408
|
+
) or exists (
|
|
409
|
+
select 1 from pg_catalog.pg_roles actor where actor.rolname=session_user and (
|
|
410
|
+
not actor.rolcanlogin or actor.rolsuper or actor.rolcreaterole or actor.rolcreatedb
|
|
411
|
+
or actor.rolreplication or actor.rolbypassrls
|
|
412
|
+
)
|
|
413
|
+
)) then
|
|
414
|
+
raise exception 'archive actor has an unsafe membership graph';
|
|
415
|
+
end if;
|
|
416
|
+
if not archive_allowed then
|
|
417
|
+
raise exception 'archive actor is not registered';
|
|
418
|
+
end if;
|
|
419
|
+
end
|
|
420
|
+
$$;
|
|
421
|
+
|
|
422
|
+
create or replace function agent_bridge.register_archive_member(
|
|
423
|
+
requested_request_id uuid, requested_member_role name
|
|
424
|
+
) returns table(replayed boolean)
|
|
425
|
+
language plpgsql security definer set search_path = '' as $$
|
|
426
|
+
declare
|
|
427
|
+
suffix text := substr(md5(current_database()),1,16);
|
|
428
|
+
archive_role name := ('agent_bridge_archive_operator_'||suffix)::name;
|
|
429
|
+
prior agent_bridge.archive_membership_events%rowtype;
|
|
430
|
+
member_record pg_catalog.pg_roles%rowtype;
|
|
431
|
+
begin
|
|
432
|
+
perform pg_catalog.pg_advisory_xact_lock_shared(1646705664);
|
|
433
|
+
if not agent_bridge.portable_archive_ready() then
|
|
434
|
+
raise exception 'portable archive readiness validation failed';
|
|
435
|
+
end if;
|
|
436
|
+
if requested_request_id is null or requested_member_role is null
|
|
437
|
+
or requested_member_role in (current_user::name,archive_role) then
|
|
438
|
+
raise exception 'invalid archive membership registration';
|
|
439
|
+
end if;
|
|
440
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
441
|
+
'archive-membership'||chr(31)||requested_member_role||chr(31)||'global',1646705662
|
|
442
|
+
));
|
|
443
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
444
|
+
'archive-membership'||chr(31)||requested_member_role||chr(31)||'operator',1646705662
|
|
445
|
+
));
|
|
446
|
+
perform pg_catalog.pg_advisory_xact_lock(
|
|
447
|
+
pg_catalog.hashtextextended(requested_request_id::text,1646705660)
|
|
448
|
+
);
|
|
449
|
+
select * into prior from agent_bridge.archive_membership_events
|
|
450
|
+
where request_id=requested_request_id;
|
|
451
|
+
if found then
|
|
452
|
+
if prior.action<>'register' or prior.member_role<>requested_member_role then
|
|
453
|
+
raise exception 'request id was already used with different content';
|
|
454
|
+
end if;
|
|
455
|
+
return query select true;
|
|
456
|
+
return;
|
|
457
|
+
end if;
|
|
458
|
+
select * into member_record from pg_catalog.pg_roles where rolname=requested_member_role;
|
|
459
|
+
if not found or not member_record.rolcanlogin or member_record.rolsuper
|
|
460
|
+
or member_record.rolcreaterole or member_record.rolcreatedb
|
|
461
|
+
or member_record.rolreplication or member_record.rolbypassrls then
|
|
462
|
+
raise exception 'archive membership target is not an eligible login';
|
|
463
|
+
end if;
|
|
464
|
+
if exists (
|
|
465
|
+
select 1 from pg_catalog.pg_roles inherited
|
|
466
|
+
where inherited.rolname<>requested_member_role
|
|
467
|
+
and pg_catalog.pg_has_role(requested_member_role,inherited.rolname,'MEMBER')
|
|
468
|
+
and inherited.rolname<>archive_role
|
|
469
|
+
) or exists (
|
|
470
|
+
select 1 from pg_catalog.pg_roles candidate
|
|
471
|
+
where candidate.rolname not in (requested_member_role,current_user)
|
|
472
|
+
and not candidate.rolsuper
|
|
473
|
+
and pg_catalog.pg_has_role(candidate.rolname,requested_member_role,'MEMBER')
|
|
474
|
+
) then
|
|
475
|
+
raise exception 'archive membership target has an unsafe membership graph';
|
|
476
|
+
end if;
|
|
477
|
+
if exists (
|
|
478
|
+
select 1 from pg_catalog.pg_auth_members membership
|
|
479
|
+
join pg_catalog.pg_roles granted on granted.oid=membership.roleid
|
|
480
|
+
join pg_catalog.pg_roles member on member.oid=membership.member
|
|
481
|
+
where granted.rolname=archive_role and member.rolname=requested_member_role and (
|
|
482
|
+
membership.admin_option
|
|
483
|
+
or not coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true)
|
|
484
|
+
or not coalesce((to_jsonb(membership)->>'set_option')::boolean,true)
|
|
485
|
+
)
|
|
486
|
+
) then
|
|
487
|
+
raise exception 'archive membership target has an unsafe direct grant';
|
|
488
|
+
end if;
|
|
489
|
+
execute format('grant %I to %I',archive_role,requested_member_role);
|
|
490
|
+
insert into agent_bridge.archive_membership_events(request_id,action,member_role,actor)
|
|
491
|
+
values(requested_request_id,'register',requested_member_role,session_user);
|
|
492
|
+
return query select false;
|
|
493
|
+
end
|
|
494
|
+
$$;
|
|
495
|
+
|
|
496
|
+
create or replace function agent_bridge.revoke_archive_member(
|
|
497
|
+
requested_request_id uuid, requested_member_role name
|
|
498
|
+
) returns table(replayed boolean)
|
|
499
|
+
language plpgsql security definer set search_path = '' as $$
|
|
500
|
+
declare
|
|
501
|
+
suffix text := substr(md5(current_database()),1,16);
|
|
502
|
+
archive_role name := ('agent_bridge_archive_operator_'||suffix)::name;
|
|
503
|
+
prior agent_bridge.archive_membership_events%rowtype;
|
|
504
|
+
begin
|
|
505
|
+
perform pg_catalog.pg_advisory_xact_lock_shared(1646705664);
|
|
506
|
+
if not agent_bridge.portable_archive_ready() then
|
|
507
|
+
raise exception 'portable archive readiness validation failed';
|
|
508
|
+
end if;
|
|
509
|
+
if requested_request_id is null or requested_member_role is null
|
|
510
|
+
or requested_member_role in (current_user::name,archive_role) then
|
|
511
|
+
raise exception 'invalid archive membership revocation';
|
|
512
|
+
end if;
|
|
513
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
514
|
+
'archive-membership'||chr(31)||requested_member_role||chr(31)||'global',1646705662
|
|
515
|
+
));
|
|
516
|
+
perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
|
|
517
|
+
'archive-membership'||chr(31)||requested_member_role||chr(31)||'operator',1646705662
|
|
518
|
+
));
|
|
519
|
+
perform pg_catalog.pg_advisory_xact_lock(
|
|
520
|
+
pg_catalog.hashtextextended(requested_request_id::text,1646705660)
|
|
521
|
+
);
|
|
522
|
+
select * into prior from agent_bridge.archive_membership_events
|
|
523
|
+
where request_id=requested_request_id;
|
|
524
|
+
if found then
|
|
525
|
+
if prior.action<>'revoke' or prior.member_role<>requested_member_role then
|
|
526
|
+
raise exception 'request id was already used with different content';
|
|
527
|
+
end if;
|
|
528
|
+
return query select true;
|
|
529
|
+
return;
|
|
530
|
+
end if;
|
|
531
|
+
if exists(select 1 from pg_catalog.pg_roles where rolname=requested_member_role) then
|
|
532
|
+
execute format('revoke %I from %I',archive_role,requested_member_role);
|
|
533
|
+
end if;
|
|
534
|
+
insert into agent_bridge.archive_membership_events(request_id,action,member_role,actor)
|
|
535
|
+
values(requested_request_id,'revoke',requested_member_role,session_user);
|
|
536
|
+
return query select false;
|
|
537
|
+
end
|
|
538
|
+
$$;
|
|
539
|
+
|
|
540
|
+
alter table agent_bridge.portable_archive_attestations
|
|
541
|
+
drop constraint portable_archive_attestation_name;
|
|
542
|
+
alter table agent_bridge.portable_archive_attestations
|
|
543
|
+
add constraint portable_archive_attestation_name
|
|
544
|
+
check(name ~ '^portable-archive-v[0-9]+$');
|
|
545
|
+
|
|
546
|
+
create or replace function agent_bridge.owner_control_attestation_definition()
|
|
547
|
+
returns text language sql stable set search_path = '' as $$
|
|
548
|
+
with names as (select
|
|
549
|
+
('agent_bridge_control_owner_'||substr(md5(current_database()),1,16))::name owner_role,
|
|
550
|
+
('agent_bridge_control_operator_'||substr(md5(current_database()),1,16))::name operator_role,
|
|
551
|
+
('agent_bridge_control_auditor_'||substr(md5(current_database()),1,16))::name auditor_role,
|
|
552
|
+
(select namespace.nspowner from pg_catalog.pg_namespace namespace
|
|
553
|
+
where namespace.nspname='agent_bridge') schema_owner
|
|
554
|
+
), control_roles(role_name) as (
|
|
555
|
+
select owner_role from names union all select operator_role from names
|
|
556
|
+
union all select auditor_role from names
|
|
557
|
+
), raw_records(record) as (
|
|
558
|
+
select unnest(string_to_array(
|
|
559
|
+
agent_bridge.owner_control_catalog_definition(),E'\x1e'
|
|
560
|
+
))
|
|
561
|
+
), protected_relations(oid) as (
|
|
562
|
+
select distinct pg_catalog.to_regclass(split_part(record,E'\x1f',2))
|
|
563
|
+
from raw_records where split_part(record,E'\x1f',1) in ('relation','relation_acl')
|
|
564
|
+
union
|
|
565
|
+
select distinct relation.oid
|
|
566
|
+
from raw_records
|
|
567
|
+
join pg_catalog.pg_attribute attribute
|
|
568
|
+
on not attribute.attisdropped and attribute.attnum>0
|
|
569
|
+
join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
|
|
570
|
+
and relation.oid::regclass::text||'.'||attribute.attname=
|
|
571
|
+
split_part(record,E'\x1f',2)
|
|
572
|
+
where split_part(record,E'\x1f',1)='column_acl'
|
|
573
|
+
), default_acl_keys(owner_oid,namespace_oid,object_type) as (
|
|
574
|
+
select role.oid,0::oid,object_type
|
|
575
|
+
from pg_catalog.pg_roles role
|
|
576
|
+
cross join (values ('r'::"char"),('S'::"char"),('f'::"char"),
|
|
577
|
+
('T'::"char"),('n'::"char")) object_types(object_type)
|
|
578
|
+
where role.oid in (
|
|
579
|
+
(select schema_owner from names),
|
|
580
|
+
(select oid from pg_catalog.pg_roles where rolname=(select owner_role from names))
|
|
581
|
+
)
|
|
582
|
+
union
|
|
583
|
+
select default_acl.defaclrole,default_acl.defaclnamespace,default_acl.defaclobjtype
|
|
584
|
+
from pg_catalog.pg_default_acl default_acl
|
|
585
|
+
left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
|
|
586
|
+
where (namespace.nspname='agent_bridge' or default_acl.defaclnamespace=0)
|
|
587
|
+
and default_acl.defaclrole in (
|
|
588
|
+
(select schema_owner from names),
|
|
589
|
+
(select oid from pg_catalog.pg_roles where rolname=(select owner_role from names))
|
|
590
|
+
)
|
|
591
|
+
), catalog_objects(kind,identity,definition) as (
|
|
592
|
+
select split_part(record,E'\x1f',1),split_part(record,E'\x1f',2),
|
|
593
|
+
split_part(record,E'\x1f',3)
|
|
594
|
+
from raw_records where split_part(record,E'\x1f',1) not in (
|
|
595
|
+
'membership','relation_acl','column_acl','default_acl'
|
|
596
|
+
)
|
|
597
|
+
union all
|
|
598
|
+
select 'membership',granted.rolname||'->'||member.rolname,
|
|
599
|
+
membership.admin_option::text||':'||
|
|
600
|
+
coalesce(to_jsonb(membership)->>'inherit_option','true')||':'||
|
|
601
|
+
coalesce(to_jsonb(membership)->>'set_option','true')
|
|
602
|
+
from pg_catalog.pg_auth_members membership
|
|
603
|
+
join pg_catalog.pg_roles granted on granted.oid=membership.roleid
|
|
604
|
+
join pg_catalog.pg_roles member on member.oid=membership.member
|
|
605
|
+
where member.rolname in (select role_name from control_roles)
|
|
606
|
+
or granted.rolname=(select owner_role from names)
|
|
607
|
+
union all
|
|
608
|
+
select 'relation_acl',relation.oid::regclass::text,
|
|
609
|
+
pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
|
|
610
|
+
case when privilege.grantee=0 then 'PUBLIC'
|
|
611
|
+
else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
|
|
612
|
+
privilege.privilege_type||':'||privilege.is_grantable::text
|
|
613
|
+
from pg_catalog.pg_class relation
|
|
614
|
+
cross join lateral pg_catalog.aclexplode(coalesce(relation.relacl,
|
|
615
|
+
pg_catalog.acldefault(
|
|
616
|
+
case when relation.relkind='S' then 'S'::"char" else 'r'::"char" end,
|
|
617
|
+
relation.relowner
|
|
618
|
+
))) privilege
|
|
619
|
+
where relation.oid in (select oid from protected_relations)
|
|
620
|
+
and relation.relkind in ('r','p','v','m','f','S')
|
|
621
|
+
and privilege.grantee<>relation.relowner
|
|
622
|
+
union all
|
|
623
|
+
select 'column_acl',relation.oid::regclass::text||'.'||attribute.attname,
|
|
624
|
+
pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
|
|
625
|
+
case when privilege.grantee=0 then 'PUBLIC'
|
|
626
|
+
else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
|
|
627
|
+
privilege.privilege_type||':'||privilege.is_grantable::text
|
|
628
|
+
from pg_catalog.pg_attribute attribute
|
|
629
|
+
join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
|
|
630
|
+
cross join lateral pg_catalog.aclexplode(attribute.attacl) privilege
|
|
631
|
+
where relation.oid in (select oid from protected_relations)
|
|
632
|
+
and attribute.attnum>0 and not attribute.attisdropped
|
|
633
|
+
and attribute.attacl is not null and privilege.grantee<>relation.relowner
|
|
634
|
+
union all
|
|
635
|
+
select 'default_acl',owner.rolname||':'||coalesce(namespace.nspname,'')||':'||
|
|
636
|
+
keys.object_type::text,
|
|
637
|
+
pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
|
|
638
|
+
case when privilege.grantee=0 then 'PUBLIC'
|
|
639
|
+
else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
|
|
640
|
+
privilege.privilege_type||':'||privilege.is_grantable::text
|
|
641
|
+
from default_acl_keys keys
|
|
642
|
+
join pg_catalog.pg_roles owner on owner.oid=keys.owner_oid
|
|
643
|
+
left join pg_catalog.pg_namespace namespace on namespace.oid=keys.namespace_oid
|
|
644
|
+
left join pg_catalog.pg_default_acl default_acl
|
|
645
|
+
on default_acl.defaclrole=keys.owner_oid
|
|
646
|
+
and default_acl.defaclnamespace=keys.namespace_oid
|
|
647
|
+
and default_acl.defaclobjtype=keys.object_type
|
|
648
|
+
cross join lateral pg_catalog.aclexplode(case when keys.namespace_oid=0 then
|
|
649
|
+
coalesce(default_acl.defaclacl,pg_catalog.acldefault(keys.object_type,keys.owner_oid))
|
|
650
|
+
else coalesce(default_acl.defaclacl,'{}'::pg_catalog.aclitem[]) end) privilege
|
|
651
|
+
where privilege.grantee<>keys.owner_oid
|
|
652
|
+
union all
|
|
653
|
+
select 'attestation_function',procedure.oid::regprocedure::text,
|
|
654
|
+
pg_catalog.pg_get_userbyid(procedure.proowner)||':'||
|
|
655
|
+
pg_catalog.pg_get_functiondef(procedure.oid)
|
|
656
|
+
from pg_catalog.pg_proc procedure
|
|
657
|
+
where procedure.oid=
|
|
658
|
+
'agent_bridge.owner_control_attestation_definition()'::regprocedure
|
|
659
|
+
)
|
|
660
|
+
select string_agg(kind||E'\x1f'||identity||E'\x1f'||definition,E'\x1e'
|
|
661
|
+
order by kind,identity,definition) from catalog_objects
|
|
662
|
+
$$;
|
|
663
|
+
|
|
664
|
+
create or replace function agent_bridge.portable_archive_attestation_definition()
|
|
665
|
+
returns text language sql stable set search_path = '' as $$
|
|
666
|
+
with names as (select
|
|
667
|
+
('agent_bridge_archive_operator_'||substr(md5(current_database()),1,16))::name archive_role,
|
|
668
|
+
namespace.nspowner schema_owner
|
|
669
|
+
from pg_catalog.pg_namespace namespace where namespace.nspname='agent_bridge'
|
|
670
|
+
), raw_records(record) as (
|
|
671
|
+
select unnest(string_to_array(
|
|
672
|
+
agent_bridge.portable_archive_catalog_definition(),E'\x1e'
|
|
673
|
+
))
|
|
674
|
+
), protected_relations(oid) as (
|
|
675
|
+
select distinct pg_catalog.to_regclass(split_part(record,E'\x1f',2))
|
|
676
|
+
from raw_records where split_part(record,E'\x1f',1)='relation'
|
|
677
|
+
), default_acl_keys(owner_oid,namespace_oid,object_type) as (
|
|
678
|
+
select (select schema_owner from names),0::oid,object_type
|
|
679
|
+
from (values ('r'::"char"),('S'::"char"),('f'::"char"),
|
|
680
|
+
('T'::"char"),('n'::"char")) object_types(object_type)
|
|
681
|
+
union
|
|
682
|
+
select default_acl.defaclrole,default_acl.defaclnamespace,default_acl.defaclobjtype
|
|
683
|
+
from pg_catalog.pg_default_acl default_acl
|
|
684
|
+
left join pg_catalog.pg_namespace namespace on namespace.oid=default_acl.defaclnamespace
|
|
685
|
+
where namespace.nspname='agent_bridge'
|
|
686
|
+
or default_acl.defaclrole=(select schema_owner from names)
|
|
687
|
+
), catalog_objects(kind,identity,definition) as (
|
|
688
|
+
select split_part(record,E'\x1f',1),split_part(record,E'\x1f',2),
|
|
689
|
+
split_part(record,E'\x1f',3)
|
|
690
|
+
from raw_records where split_part(record,E'\x1f',1) not in (
|
|
691
|
+
'owner_membership','relation_acl','column_acl','default_acl'
|
|
692
|
+
)
|
|
693
|
+
union all
|
|
694
|
+
select 'owner_membership',granted.rolname||'->'||member.rolname,
|
|
695
|
+
membership.admin_option::text||':'||
|
|
696
|
+
coalesce(to_jsonb(membership)->>'inherit_option','true')||':'||
|
|
697
|
+
coalesce(to_jsonb(membership)->>'set_option','true')
|
|
698
|
+
from pg_catalog.pg_auth_members membership
|
|
699
|
+
join pg_catalog.pg_roles granted on granted.oid=membership.roleid
|
|
700
|
+
join pg_catalog.pg_roles member on member.oid=membership.member
|
|
701
|
+
where granted.rolname=(select archive_role from names)
|
|
702
|
+
and member.oid=(select schema_owner from names)
|
|
703
|
+
union all
|
|
704
|
+
select 'relation_acl',relation.oid::regclass::text,
|
|
705
|
+
pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
|
|
706
|
+
case when privilege.grantee=0 then 'PUBLIC'
|
|
707
|
+
else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
|
|
708
|
+
privilege.privilege_type||':'||privilege.is_grantable::text
|
|
709
|
+
from pg_catalog.pg_class relation
|
|
710
|
+
cross join lateral pg_catalog.aclexplode(coalesce(relation.relacl,
|
|
711
|
+
pg_catalog.acldefault(
|
|
712
|
+
case when relation.relkind='S' then 'S'::"char" else 'r'::"char" end,
|
|
713
|
+
relation.relowner
|
|
714
|
+
))) privilege
|
|
715
|
+
where relation.oid in (select oid from protected_relations)
|
|
716
|
+
and relation.relkind in ('r','p','v','m','f','S')
|
|
717
|
+
and privilege.grantee<>relation.relowner
|
|
718
|
+
union all
|
|
719
|
+
select 'column_acl',relation.oid::regclass::text||'.'||attribute.attname,
|
|
720
|
+
pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
|
|
721
|
+
case when privilege.grantee=0 then 'PUBLIC'
|
|
722
|
+
else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
|
|
723
|
+
privilege.privilege_type||':'||privilege.is_grantable::text
|
|
724
|
+
from pg_catalog.pg_attribute attribute
|
|
725
|
+
join pg_catalog.pg_class relation on relation.oid=attribute.attrelid
|
|
726
|
+
cross join lateral pg_catalog.aclexplode(attribute.attacl) privilege
|
|
727
|
+
where relation.oid in (select oid from protected_relations)
|
|
728
|
+
and attribute.attnum>0 and not attribute.attisdropped
|
|
729
|
+
and attribute.attacl is not null and privilege.grantee<>relation.relowner
|
|
730
|
+
union all
|
|
731
|
+
select 'default_acl',owner.rolname||':'||coalesce(namespace.nspname,'')||':'||
|
|
732
|
+
keys.object_type::text,
|
|
733
|
+
pg_catalog.pg_get_userbyid(privilege.grantor)||':'||
|
|
734
|
+
case when privilege.grantee=0 then 'PUBLIC'
|
|
735
|
+
else pg_catalog.pg_get_userbyid(privilege.grantee) end||':'||
|
|
736
|
+
privilege.privilege_type||':'||privilege.is_grantable::text
|
|
737
|
+
from default_acl_keys keys
|
|
738
|
+
join pg_catalog.pg_roles owner on owner.oid=keys.owner_oid
|
|
739
|
+
left join pg_catalog.pg_namespace namespace on namespace.oid=keys.namespace_oid
|
|
740
|
+
left join pg_catalog.pg_default_acl default_acl
|
|
741
|
+
on default_acl.defaclrole=keys.owner_oid
|
|
742
|
+
and default_acl.defaclnamespace=keys.namespace_oid
|
|
743
|
+
and default_acl.defaclobjtype=keys.object_type
|
|
744
|
+
cross join lateral pg_catalog.aclexplode(case when keys.namespace_oid=0 then
|
|
745
|
+
coalesce(default_acl.defaclacl,pg_catalog.acldefault(keys.object_type,keys.owner_oid))
|
|
746
|
+
else coalesce(default_acl.defaclacl,'{}'::pg_catalog.aclitem[]) end) privilege
|
|
747
|
+
where privilege.grantee<>keys.owner_oid
|
|
748
|
+
union all
|
|
749
|
+
select 'attestation_function',procedure.oid::regprocedure::text,
|
|
750
|
+
pg_catalog.pg_get_userbyid(procedure.proowner)||':'||
|
|
751
|
+
pg_catalog.pg_get_functiondef(procedure.oid)
|
|
752
|
+
from pg_catalog.pg_proc procedure
|
|
753
|
+
where procedure.oid=
|
|
754
|
+
'agent_bridge.portable_archive_attestation_definition()'::regprocedure
|
|
755
|
+
)
|
|
756
|
+
select string_agg(kind||E'\x1f'||identity||E'\x1f'||definition,E'\x1e'
|
|
757
|
+
order by kind,identity,definition) from catalog_objects
|
|
758
|
+
$$;
|
|
759
|
+
|
|
760
|
+
revoke execute on function agent_bridge.owner_control_attestation_definition() from public;
|
|
761
|
+
revoke execute on function agent_bridge.portable_archive_attestation_definition() from public;
|
|
762
|
+
|
|
763
|
+
do $ownership$
|
|
764
|
+
declare
|
|
765
|
+
owner_role text := 'agent_bridge_control_owner_'||substr(md5(current_database()),1,16);
|
|
766
|
+
begin
|
|
767
|
+
execute format(
|
|
768
|
+
'alter function agent_bridge.owner_control_attestation_definition() owner to %I',
|
|
769
|
+
owner_role
|
|
770
|
+
);
|
|
771
|
+
end
|
|
772
|
+
$ownership$;
|
|
773
|
+
|
|
774
|
+
create or replace function agent_bridge.portable_archive_ready()
|
|
775
|
+
returns boolean language sql stable security definer set search_path = '' as $$
|
|
776
|
+
with names as (select
|
|
777
|
+
('agent_bridge_archive_operator_'||substr(md5(current_database()),1,16))::name archive_role,
|
|
778
|
+
(select pg_catalog.pg_get_userbyid(nspowner)::name from pg_catalog.pg_namespace
|
|
779
|
+
where nspname='agent_bridge') schema_owner
|
|
780
|
+
), latest_registry as (
|
|
781
|
+
select distinct on (event.member_role) event.member_role,event.action
|
|
782
|
+
from agent_bridge.archive_membership_events event order by event.member_role,event.sequence desc
|
|
783
|
+
), active_registry as (
|
|
784
|
+
select member_role from latest_registry where action='register'
|
|
785
|
+
), expected_memberships as (
|
|
786
|
+
select names.archive_role granted_role,names.schema_owner member_role,true admin_option from names
|
|
787
|
+
union all select names.archive_role,registry.member_role,false from active_registry registry cross join names
|
|
788
|
+
), actual_memberships as (
|
|
789
|
+
select granted.rolname::name granted_role,member.rolname::name member_role,
|
|
790
|
+
membership.admin_option,
|
|
791
|
+
coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
|
|
792
|
+
coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option
|
|
793
|
+
from pg_catalog.pg_auth_members membership
|
|
794
|
+
join pg_catalog.pg_roles granted on granted.oid=membership.roleid
|
|
795
|
+
join pg_catalog.pg_roles member on member.oid=membership.member
|
|
796
|
+
where granted.rolname=(select archive_role from names)
|
|
797
|
+
)
|
|
798
|
+
select current_setting('server_version_num')::integer/10000=any(array[15,16,17,18])
|
|
799
|
+
and exists(select 1 from pg_catalog.pg_roles role,names where role.rolname=names.archive_role
|
|
800
|
+
and not role.rolcanlogin and not role.rolsuper and not role.rolcreatedb
|
|
801
|
+
and not role.rolcreaterole and not role.rolreplication and not role.rolbypassrls)
|
|
802
|
+
and (select count(*)=1 and bool_and(
|
|
803
|
+
attestation.catalog_definition=agent_bridge.portable_archive_attestation_definition()
|
|
804
|
+
) from agent_bridge.portable_archive_attestations attestation
|
|
805
|
+
where attestation.name='portable-archive-v3')
|
|
806
|
+
and not exists(
|
|
807
|
+
(select granted_role,member_role,admin_option from actual_memberships
|
|
808
|
+
except select granted_role,member_role,admin_option from expected_memberships)
|
|
809
|
+
union all
|
|
810
|
+
(select granted_role,member_role,admin_option from expected_memberships
|
|
811
|
+
except select granted_role,member_role,admin_option from actual_memberships)
|
|
812
|
+
)
|
|
813
|
+
and not exists(select 1 from actual_memberships membership
|
|
814
|
+
where not membership.inherit_option or not membership.set_option)
|
|
815
|
+
and not exists(select 1 from active_registry registry
|
|
816
|
+
left join pg_catalog.pg_roles role on role.rolname=registry.member_role
|
|
817
|
+
where role.oid is null or not role.rolcanlogin or role.rolsuper or role.rolcreaterole
|
|
818
|
+
or role.rolcreatedb or role.rolreplication or role.rolbypassrls)
|
|
819
|
+
and not exists(select 1 from active_registry registry
|
|
820
|
+
join pg_catalog.pg_roles inherited on inherited.rolname<>registry.member_role
|
|
821
|
+
cross join names
|
|
822
|
+
where pg_catalog.pg_has_role(registry.member_role,inherited.rolname,'MEMBER')
|
|
823
|
+
and inherited.rolname<>names.archive_role)
|
|
824
|
+
and not exists(select 1 from active_registry registry cross join pg_catalog.pg_roles candidate,names
|
|
825
|
+
where candidate.rolname not in (registry.member_role,names.schema_owner)
|
|
826
|
+
and not candidate.rolsuper
|
|
827
|
+
and pg_catalog.pg_has_role(candidate.rolname,registry.member_role,'MEMBER'))
|
|
828
|
+
and has_schema_privilege((select archive_role from names),'agent_bridge','USAGE')
|
|
829
|
+
and not has_schema_privilege((select archive_role from names),'agent_bridge','CREATE')
|
|
830
|
+
and not exists(select 1 from pg_catalog.pg_class relation
|
|
831
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=relation.relnamespace
|
|
832
|
+
where namespace.nspname='agent_bridge' and relation.relkind in ('r','p','v','m','f')
|
|
833
|
+
and has_table_privilege((select archive_role from names),relation.oid,
|
|
834
|
+
'SELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER'))
|
|
835
|
+
and not exists(select 1 from pg_catalog.pg_class sequence_record
|
|
836
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=sequence_record.relnamespace
|
|
837
|
+
where namespace.nspname='agent_bridge'
|
|
838
|
+
and case when sequence_record.relkind='S' then has_sequence_privilege(
|
|
839
|
+
(select archive_role from names),sequence_record.oid,'USAGE,SELECT,UPDATE'
|
|
840
|
+
) else false end)
|
|
841
|
+
and not exists(select 1 from pg_catalog.pg_proc procedure
|
|
842
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
|
|
843
|
+
where namespace.nspname='agent_bridge' and has_function_privilege('public',procedure.oid,'EXECUTE'))
|
|
844
|
+
and not exists(select 1 from pg_catalog.pg_proc procedure
|
|
845
|
+
join pg_catalog.pg_namespace namespace on namespace.oid=procedure.pronamespace
|
|
846
|
+
where namespace.nspname='agent_bridge'
|
|
847
|
+
and has_function_privilege((select archive_role from names),procedure.oid,'EXECUTE')
|
|
848
|
+
and procedure.oid not in (
|
|
849
|
+
'agent_bridge.archive_begin_operation(uuid,text,text,character,bigint,bigint)'::regprocedure,
|
|
850
|
+
'agent_bridge.archive_authorize_transaction(uuid,text,text,character)'::regprocedure,
|
|
851
|
+
'agent_bridge.archive_close_transaction_authorization(uuid)'::regprocedure,
|
|
852
|
+
'agent_bridge.archive_export_messages(uuid,text,timestamp with time zone,uuid,integer)'::regprocedure,
|
|
853
|
+
'agent_bridge.archive_export_receipts(uuid,text,timestamp with time zone,uuid,text,integer)'::regprocedure,
|
|
854
|
+
'agent_bridge.archive_import_messages(uuid,text,character,bigint,jsonb)'::regprocedure,
|
|
855
|
+
'agent_bridge.archive_import_receipts(uuid,text,character,bigint,jsonb)'::regprocedure,
|
|
856
|
+
'agent_bridge.archive_complete_export(uuid,text,character,bigint,bigint,timestamp with time zone,text)'::regprocedure,
|
|
857
|
+
'agent_bridge.archive_reconcile_export(uuid,text,character,bigint,bigint,timestamp with time zone)'::regprocedure,
|
|
858
|
+
'agent_bridge.archive_complete_import(uuid,text,boolean)'::regprocedure,
|
|
859
|
+
'agent_bridge.archive_abandon_operation(uuid,text,text)'::regprocedure,
|
|
860
|
+
'agent_bridge.portable_archive_ready()'::regprocedure
|
|
861
|
+
))
|
|
862
|
+
and has_function_privilege((select archive_role from names),
|
|
863
|
+
'agent_bridge.archive_begin_operation(uuid,text,text,character,bigint,bigint)','EXECUTE')
|
|
864
|
+
and has_function_privilege((select archive_role from names),
|
|
865
|
+
'agent_bridge.archive_authorize_transaction(uuid,text,text,character)','EXECUTE')
|
|
866
|
+
and has_function_privilege((select archive_role from names),
|
|
867
|
+
'agent_bridge.archive_close_transaction_authorization(uuid)','EXECUTE')
|
|
868
|
+
and has_function_privilege((select archive_role from names),
|
|
869
|
+
'agent_bridge.archive_export_messages(uuid,text,timestamp with time zone,uuid,integer)','EXECUTE')
|
|
870
|
+
and has_function_privilege((select archive_role from names),
|
|
871
|
+
'agent_bridge.archive_export_receipts(uuid,text,timestamp with time zone,uuid,text,integer)','EXECUTE')
|
|
872
|
+
and has_function_privilege((select archive_role from names),
|
|
873
|
+
'agent_bridge.archive_import_messages(uuid,text,character,bigint,jsonb)','EXECUTE')
|
|
874
|
+
and has_function_privilege((select archive_role from names),
|
|
875
|
+
'agent_bridge.archive_import_receipts(uuid,text,character,bigint,jsonb)','EXECUTE')
|
|
876
|
+
and has_function_privilege((select archive_role from names),
|
|
877
|
+
'agent_bridge.archive_complete_export(uuid,text,character,bigint,bigint,timestamp with time zone,text)','EXECUTE')
|
|
878
|
+
and has_function_privilege((select archive_role from names),
|
|
879
|
+
'agent_bridge.archive_reconcile_export(uuid,text,character,bigint,bigint,timestamp with time zone)','EXECUTE')
|
|
880
|
+
and has_function_privilege((select archive_role from names),
|
|
881
|
+
'agent_bridge.archive_complete_import(uuid,text,boolean)','EXECUTE')
|
|
882
|
+
and has_function_privilege((select archive_role from names),
|
|
883
|
+
'agent_bridge.archive_abandon_operation(uuid,text,text)','EXECUTE')
|
|
884
|
+
and has_function_privilege((select archive_role from names),
|
|
885
|
+
'agent_bridge.portable_archive_ready()','EXECUTE')
|
|
886
|
+
$$;
|
|
887
|
+
|
|
888
|
+
create or replace function agent_bridge.owner_control_plane_ready()
|
|
889
|
+
returns boolean language sql stable security definer set search_path = '' as $$
|
|
890
|
+
with names as (select
|
|
891
|
+
('agent_bridge_control_owner_'||substr(md5(current_database()),1,16))::name owner_role,
|
|
892
|
+
('agent_bridge_control_operator_'||substr(md5(current_database()),1,16))::name operator_role,
|
|
893
|
+
('agent_bridge_control_auditor_'||substr(md5(current_database()),1,16))::name auditor_role,
|
|
894
|
+
(select pg_catalog.pg_get_userbyid(nspowner)::name from pg_catalog.pg_namespace
|
|
895
|
+
where nspname='agent_bridge') schema_owner
|
|
896
|
+
), latest_registry as (
|
|
897
|
+
select distinct on (event.member_role,event.control_role)
|
|
898
|
+
event.member_role,event.control_role,event.action
|
|
899
|
+
from agent_bridge.control_membership_events event
|
|
900
|
+
order by event.member_role,event.control_role,event.sequence desc
|
|
901
|
+
), active_registry as (
|
|
902
|
+
select member_role,control_role from latest_registry where action='register'
|
|
903
|
+
), expected_memberships as (
|
|
904
|
+
select names.owner_role granted_role,names.schema_owner member_role,true admin_option from names
|
|
905
|
+
union all select names.operator_role,names.schema_owner,true from names
|
|
906
|
+
union all select names.auditor_role,names.schema_owner,true from names
|
|
907
|
+
union all
|
|
908
|
+
select case registry.control_role when 'operator' then names.operator_role
|
|
909
|
+
else names.auditor_role end,registry.member_role,false
|
|
910
|
+
from active_registry registry cross join names
|
|
911
|
+
), actual_memberships as (
|
|
912
|
+
select granted.rolname::name granted_role,member.rolname::name member_role,
|
|
913
|
+
membership.admin_option,
|
|
914
|
+
coalesce((to_jsonb(membership)->>'inherit_option')::boolean,true) inherit_option,
|
|
915
|
+
coalesce((to_jsonb(membership)->>'set_option')::boolean,true) set_option
|
|
916
|
+
from pg_catalog.pg_auth_members membership
|
|
917
|
+
join pg_catalog.pg_roles granted on granted.oid=membership.roleid
|
|
918
|
+
join pg_catalog.pg_roles member on member.oid=membership.member
|
|
919
|
+
where granted.rolname in (
|
|
920
|
+
(select owner_role from names),(select operator_role from names),(select auditor_role from names)
|
|
921
|
+
)
|
|
922
|
+
), authority_closure as (
|
|
923
|
+
select control_role.granted_role,candidate.rolname::name member_role
|
|
924
|
+
from names
|
|
925
|
+
cross join lateral (values
|
|
926
|
+
(names.owner_role),(names.operator_role),(names.auditor_role)
|
|
927
|
+
) control_role(granted_role)
|
|
928
|
+
cross join pg_catalog.pg_roles candidate
|
|
929
|
+
where candidate.rolname not in (names.owner_role,names.operator_role,names.auditor_role)
|
|
930
|
+
and (not candidate.rolsuper or candidate.rolname=names.schema_owner)
|
|
931
|
+
and pg_catalog.pg_has_role(candidate.rolname,control_role.granted_role,'MEMBER')
|
|
932
|
+
)
|
|
933
|
+
select
|
|
934
|
+
current_setting('server_version_num')::integer/10000=any(array[15,16,17,18])
|
|
935
|
+
and (select count(*)=1 and bool_and(
|
|
936
|
+
attestation.catalog_definition=agent_bridge.owner_control_attestation_definition()
|
|
937
|
+
) from agent_bridge.owner_control_attestations attestation
|
|
938
|
+
where attestation.name='owner-control-v4')
|
|
939
|
+
and not exists (
|
|
940
|
+
(select granted_role,member_role,admin_option from actual_memberships
|
|
941
|
+
except select granted_role,member_role,admin_option from expected_memberships)
|
|
942
|
+
union all
|
|
943
|
+
(select granted_role,member_role,admin_option from expected_memberships
|
|
944
|
+
except select granted_role,member_role,admin_option from actual_memberships)
|
|
945
|
+
)
|
|
946
|
+
and not exists (select 1 from actual_memberships membership
|
|
947
|
+
where not membership.inherit_option or not membership.set_option)
|
|
948
|
+
and not exists (
|
|
949
|
+
select 1 from active_registry registry
|
|
950
|
+
left join pg_catalog.pg_roles role_record on role_record.rolname=registry.member_role
|
|
951
|
+
where role_record.oid is null or not role_record.rolcanlogin or role_record.rolsuper
|
|
952
|
+
or role_record.rolcreaterole or role_record.rolcreatedb
|
|
953
|
+
or role_record.rolreplication or role_record.rolbypassrls
|
|
954
|
+
)
|
|
955
|
+
and not exists (
|
|
956
|
+
(select granted_role,member_role from authority_closure
|
|
957
|
+
except select granted_role,member_role from expected_memberships)
|
|
958
|
+
union all
|
|
959
|
+
(select granted_role,member_role from expected_memberships
|
|
960
|
+
except select granted_role,member_role from authority_closure)
|
|
961
|
+
)
|
|
962
|
+
and not exists (
|
|
963
|
+
select 1 from active_registry registry
|
|
964
|
+
join pg_catalog.pg_roles inherited on inherited.rolname<>registry.member_role
|
|
965
|
+
where pg_catalog.pg_has_role(registry.member_role,inherited.rolname,'MEMBER')
|
|
966
|
+
and not exists (
|
|
967
|
+
select 1 from active_registry permitted
|
|
968
|
+
cross join names
|
|
969
|
+
where permitted.member_role=registry.member_role
|
|
970
|
+
and inherited.rolname=case permitted.control_role
|
|
971
|
+
when 'operator' then names.operator_role else names.auditor_role end
|
|
972
|
+
)
|
|
973
|
+
)
|
|
974
|
+
and not exists (
|
|
975
|
+
select 1 from active_registry registry
|
|
976
|
+
cross join pg_catalog.pg_roles candidate
|
|
977
|
+
cross join names
|
|
978
|
+
where candidate.rolname not in (registry.member_role,names.schema_owner)
|
|
979
|
+
and not candidate.rolsuper
|
|
980
|
+
and pg_catalog.pg_has_role(candidate.rolname,registry.member_role,'MEMBER')
|
|
981
|
+
)
|
|
982
|
+
$$;
|
|
983
|
+
|
|
984
|
+
insert into agent_bridge.owner_control_attestations(name,catalog_definition)
|
|
985
|
+
values('owner-control-v4',agent_bridge.owner_control_attestation_definition());
|
|
986
|
+
|
|
987
|
+
insert into agent_bridge.portable_archive_attestations(name,catalog_definition)
|
|
988
|
+
values('portable-archive-v3',agent_bridge.portable_archive_attestation_definition());
|
|
989
|
+
|
|
990
|
+
do $preflight$
|
|
991
|
+
begin
|
|
992
|
+
if not agent_bridge.security_schema_ready() then
|
|
993
|
+
raise exception 'native DR fence security readiness validation failed';
|
|
994
|
+
elseif not agent_bridge.owner_control_plane_ready() then
|
|
995
|
+
raise exception 'native DR fence owner readiness validation failed';
|
|
996
|
+
elseif not agent_bridge.portable_archive_ready() then
|
|
997
|
+
raise exception 'native DR fence portable archive readiness validation failed';
|
|
998
|
+
end if;
|
|
999
|
+
end
|
|
1000
|
+
$preflight$;
|
|
1001
|
+
|
|
1002
|
+
insert into agent_bridge.schema_migrations(version,name,checksum)
|
|
1003
|
+
values(16,'native_dr_fence','__AGENT_BRIDGE_MIGRATION_CHECKSUM__');
|
|
1004
|
+
|
|
1005
|
+
commit;
|