Handle Cloud SQL provider database residuals
This commit is contained in:
parent
487c2a6599
commit
508e1bdb28
6 changed files with 634 additions and 26 deletions
|
|
@ -92,12 +92,22 @@ cleartext runtime password. The migration:
|
||||||
database ACL, canonical grant, routine, large-object, and topology checks;
|
database ACL, canonical grant, routine, large-object, and topology checks;
|
||||||
- creates a dedicated `NOLOGIN` function owner with no role memberships;
|
- creates a dedicated `NOLOGIN` function owner with no role memberships;
|
||||||
- removes stale table, column, sequence, function, and role-membership grants;
|
- removes stale table, column, sequence, function, and role-membership grants;
|
||||||
- inventories existing principals, replaces `PUBLIC CONNECT` with explicit
|
- inventories existing principals, replaces operator-controlled `PUBLIC
|
||||||
preserved grants, gives the runtime `CONNECT` only to `teleo_canonical`, and
|
CONNECT` with explicit preserved grants, gives the runtime a sole direct
|
||||||
leaves the staging owner with no database connection target;
|
`CONNECT` grant on `teleo_canonical`, and leaves the staging owner with no
|
||||||
|
direct database grant;
|
||||||
|
- validates and reports the unavoidable Cloud SQL system connection residual:
|
||||||
|
`cloudsqladmin` and `template0`, both owned by `cloudsqladmin`, retain their
|
||||||
|
provider-owned `PUBLIC CONNECT`; owner, connection/template flags, public
|
||||||
|
connection semantics, unexpected grantees, or extra-database drift fails
|
||||||
|
closed, while the raw provider-owned `template0` ACL is preserved rather than
|
||||||
|
reconstructed;
|
||||||
- inventories and preserves existing non-scoped application-routine and
|
- inventories and preserves existing non-scoped application-routine and
|
||||||
large-object access while removing `PUBLIC` and both scoped roles from all
|
large-object access while removing both scoped roles from unexpected direct
|
||||||
other application routines and every persistent large-object mutator;
|
routine ACLs and explicitly reporting the provider-owned public
|
||||||
|
large-object-mutator residual;
|
||||||
|
- rejects nonempty session/local preload-library settings before enabling the
|
||||||
|
scoped runtime login;
|
||||||
- grants exact canonical reads;
|
- grants exact canonical reads;
|
||||||
- creates a locked security-definer staging function that hard-codes both
|
- creates a locked security-definer staging function that hard-codes both
|
||||||
`pending_review` and canonical proposer `leo`;
|
`pending_review` and canonical proposer `leo`;
|
||||||
|
|
@ -116,12 +126,13 @@ by a `pg_catalog, pg_temp` search path, schema-qualified relations, a fixed
|
||||||
The administrator password is used only for this bounded bootstrap. Retain no
|
The administrator password is used only for this bounded bootstrap. Retain no
|
||||||
password output.
|
password output.
|
||||||
|
|
||||||
`teleo_kb`, `template1`, and every other noncanonical database are actual
|
`postgres`, `teleo_kb`, `template1`, and every other operator-controlled
|
||||||
negative connection checks. PostgreSQL's startup protocol does not expose a
|
noncanonical database are denied. PostgreSQL's startup protocol does not expose
|
||||||
SQLSTATE through `psql`, so the verifier requires the exact C-locale
|
a SQLSTATE through `psql`, so the verifier requires the exact C-locale
|
||||||
`permission denied for database` and `does not have CONNECT privilege`
|
`permission denied for database` and `does not have CONNECT privilege`
|
||||||
diagnostics in addition to the catalog proof that the runtime's sole effective
|
diagnostics in addition to the catalog proof. The receipt separately exposes
|
||||||
target is `teleo_canonical`. Audit fallback remains disabled with
|
the exact provider-owned `cloudsqladmin`/`template0` residual; it never labels
|
||||||
|
the runtime production-eligible. Audit fallback remains disabled with
|
||||||
`TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK=0`.
|
`TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK=0`.
|
||||||
|
|
||||||
### 5. Preflight and deploy the merged runtime
|
### 5. Preflight and deploy the merged runtime
|
||||||
|
|
@ -279,17 +290,20 @@ Retain a redacted receipt containing:
|
||||||
both before and after the transaction;
|
both before and after the transaction;
|
||||||
- denied direct insert, update, and delete on `kb_stage.kb_proposals`;
|
- denied direct insert, update, and delete on `kb_stage.kb_proposals`;
|
||||||
- denied insert, update, and delete on canonical `public.claims`;
|
- denied insert, update, and delete on canonical `public.claims`;
|
||||||
- denied `lo_creat`, `lo_create`, and `lo_from_bytea`, zero effective
|
- zero direct scoped large-object mutator ACLs and zero scoped large-object
|
||||||
large-object mutator execution, and zero scoped large-object ownership/ACLs;
|
ownership/ACLs; the known provider-owned public mutator residual is reported
|
||||||
|
explicitly and exercised only inside a rolled-back probe, so this receipt
|
||||||
|
does not claim arbitrary database writes are categorically denied;
|
||||||
- denied reviewer/apply security-definer functions;
|
- denied reviewer/apply security-definer functions;
|
||||||
- exact function-catalog posture: only the five-argument staging function is
|
- exact function-catalog posture: only the five-argument staging function is
|
||||||
executable, the forged six-argument overload is absent, and each expected
|
executable, the forged six-argument overload is absent, and each expected
|
||||||
reviewer/apply function exists but is not executable by the runtime role;
|
reviewer/apply function exists but is not executable by the runtime role;
|
||||||
- unavailable forged-proposer overload and denied `SET ROLE` escalation into
|
- unavailable forged-proposer overload and denied `SET ROLE` escalation into
|
||||||
the staging owner, reviewer, apply, or administrator roles;
|
the staging owner, reviewer, apply, or administrator roles;
|
||||||
- denied startup connections to both `teleo_kb` and `template1`, with the
|
- denied startup connections to `postgres`, `teleo_kb`, and `template1`, with
|
||||||
catalog showing `teleo_canonical` as the sole runtime connection target and
|
the catalog showing a sole direct runtime grant on `teleo_canonical`, the
|
||||||
zero remaining `PUBLIC CONNECT` grants;
|
exact two-row Cloud SQL system residual, and zero unexpected `PUBLIC CONNECT`
|
||||||
|
grants;
|
||||||
- readable scoped runtime secret and an IAM permission denial for the
|
- readable scoped runtime secret and an IAM permission denial for the
|
||||||
administrator secret, with neither secret value retained;
|
administrator secret, with neither secret value retained;
|
||||||
- zero Telegram messages and zero committed canary rows.
|
- zero Telegram messages and zero committed canary rows.
|
||||||
|
|
|
||||||
|
|
@ -153,8 +153,181 @@ begin;
|
||||||
-- REVOKE. Preserve every existing non-scoped principal's effective CONNECT
|
-- REVOKE. Preserve every existing non-scoped principal's effective CONNECT
|
||||||
-- set as explicit ACLs, remove the cluster-wide PUBLIC grant from every
|
-- set as explicit ACLs, remove the cluster-wide PUBLIC grant from every
|
||||||
-- database (including templates), and then grant the runtime its sole target.
|
-- database (including templates), and then grant the runtime its sole target.
|
||||||
|
-- Cloud SQL reserves cloudsqladmin and template0 under the immutable
|
||||||
|
-- cloudsqladmin owner. Their exact provider-owned PUBLIC CONNECT residual
|
||||||
|
-- cannot be changed by the customer administrator, so validate that pair
|
||||||
|
-- field-for-field and exclude only those two rows from ACL normalization.
|
||||||
-- This runs in the same transaction as every remaining privilege/topology
|
-- This runs in the same transaction as every remaining privilege/topology
|
||||||
-- assertion, so a later failure restores the original ACLs atomically.
|
-- assertion, so a later failure restores the original ACLs atomically.
|
||||||
|
create temporary table leoclean_expected_provider_database_connect_residual (
|
||||||
|
database_name name primary key,
|
||||||
|
owner_name name not null,
|
||||||
|
allow_connections boolean not null,
|
||||||
|
is_template boolean not null,
|
||||||
|
database_acl_is_default boolean,
|
||||||
|
public_connect_acl_entries integer not null,
|
||||||
|
public_connect_grantable boolean not null
|
||||||
|
) on commit drop;
|
||||||
|
|
||||||
|
insert into pg_temp.leoclean_expected_provider_database_connect_residual (
|
||||||
|
database_name,
|
||||||
|
owner_name,
|
||||||
|
allow_connections,
|
||||||
|
is_template,
|
||||||
|
database_acl_is_default,
|
||||||
|
public_connect_acl_entries,
|
||||||
|
public_connect_grantable
|
||||||
|
)
|
||||||
|
select expected.*
|
||||||
|
from (values
|
||||||
|
('cloudsqladmin'::name, 'cloudsqladmin'::name, true, false, true, 1, false),
|
||||||
|
('template0'::name, 'cloudsqladmin'::name, false, true, null::boolean, 1, false)
|
||||||
|
) expected(
|
||||||
|
database_name,
|
||||||
|
owner_name,
|
||||||
|
allow_connections,
|
||||||
|
is_template,
|
||||||
|
database_acl_is_default,
|
||||||
|
public_connect_acl_entries,
|
||||||
|
public_connect_grantable
|
||||||
|
)
|
||||||
|
where pg_catalog.to_regrole('cloudsqladmin') is not null;
|
||||||
|
|
||||||
|
do $$
|
||||||
|
declare
|
||||||
|
unexpected text;
|
||||||
|
begin
|
||||||
|
with actual as (
|
||||||
|
select database_row.datname as database_name,
|
||||||
|
owner_role.rolname as owner_name,
|
||||||
|
database_row.datallowconn as allow_connections,
|
||||||
|
database_row.datistemplate as is_template,
|
||||||
|
database_row.datacl is null as database_acl_is_default,
|
||||||
|
pg_catalog.count(*) filter (
|
||||||
|
where acl.grantee = 0
|
||||||
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
)::integer as public_connect_acl_entries,
|
||||||
|
coalesce(
|
||||||
|
pg_catalog.bool_or(acl.is_grantable) filter (
|
||||||
|
where acl.grantee = 0
|
||||||
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
),
|
||||||
|
false
|
||||||
|
) as public_connect_grantable
|
||||||
|
from pg_catalog.pg_database database_row
|
||||||
|
join pg_catalog.pg_roles owner_role on owner_role.oid = database_row.datdba
|
||||||
|
cross join lateral pg_catalog.aclexplode(
|
||||||
|
coalesce(
|
||||||
|
database_row.datacl,
|
||||||
|
pg_catalog.acldefault('d', database_row.datdba)
|
||||||
|
)
|
||||||
|
) acl
|
||||||
|
where pg_catalog.to_regrole('cloudsqladmin') is not null
|
||||||
|
and database_row.datname in ('cloudsqladmin', 'template0')
|
||||||
|
group by database_row.datname,
|
||||||
|
owner_role.rolname,
|
||||||
|
database_row.datallowconn,
|
||||||
|
database_row.datistemplate,
|
||||||
|
database_row.datacl
|
||||||
|
)
|
||||||
|
select pg_catalog.string_agg(
|
||||||
|
pg_catalog.format(
|
||||||
|
'%s:expected=%s/%s/%s/%s/%s/%s:actual=%s/%s/%s/%s/%s/%s',
|
||||||
|
coalesce(expected.database_name, actual.database_name),
|
||||||
|
expected.owner_name,
|
||||||
|
expected.allow_connections,
|
||||||
|
expected.is_template,
|
||||||
|
expected.database_acl_is_default,
|
||||||
|
expected.public_connect_acl_entries,
|
||||||
|
expected.public_connect_grantable,
|
||||||
|
actual.owner_name,
|
||||||
|
actual.allow_connections,
|
||||||
|
actual.is_template,
|
||||||
|
actual.database_acl_is_default,
|
||||||
|
actual.public_connect_acl_entries,
|
||||||
|
actual.public_connect_grantable
|
||||||
|
),
|
||||||
|
', '
|
||||||
|
order by coalesce(expected.database_name, actual.database_name)
|
||||||
|
)
|
||||||
|
into unexpected
|
||||||
|
from pg_temp.leoclean_expected_provider_database_connect_residual expected
|
||||||
|
full join actual using (database_name)
|
||||||
|
where expected.database_name is null
|
||||||
|
or actual.database_name is null
|
||||||
|
or expected.owner_name is distinct from actual.owner_name
|
||||||
|
or expected.allow_connections is distinct from actual.allow_connections
|
||||||
|
or expected.is_template is distinct from actual.is_template
|
||||||
|
or (
|
||||||
|
expected.database_acl_is_default is not null
|
||||||
|
and expected.database_acl_is_default is distinct from actual.database_acl_is_default
|
||||||
|
)
|
||||||
|
or expected.public_connect_acl_entries is distinct from actual.public_connect_acl_entries
|
||||||
|
or expected.public_connect_grantable is distinct from actual.public_connect_grantable;
|
||||||
|
if unexpected is not null then
|
||||||
|
raise exception 'Cloud SQL provider database CONNECT residual drifted: %', unexpected;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select pg_catalog.string_agg(
|
||||||
|
pg_catalog.format(
|
||||||
|
'%I:%I:%s:grantable=%s',
|
||||||
|
database_row.datname,
|
||||||
|
grantee.rolname,
|
||||||
|
acl.privilege_type,
|
||||||
|
acl.is_grantable
|
||||||
|
),
|
||||||
|
', '
|
||||||
|
order by database_row.datname, grantee.rolname, acl.privilege_type
|
||||||
|
)
|
||||||
|
into unexpected
|
||||||
|
from pg_catalog.pg_database database_row
|
||||||
|
join pg_temp.leoclean_expected_provider_database_connect_residual expected
|
||||||
|
on expected.database_name = database_row.datname
|
||||||
|
cross join lateral pg_catalog.aclexplode(database_row.datacl) acl
|
||||||
|
join pg_catalog.pg_roles grantee on grantee.oid = acl.grantee
|
||||||
|
where grantee.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner');
|
||||||
|
if unexpected is not null then
|
||||||
|
raise exception 'scoped Leo roles retain direct ACLs on provider databases: %', unexpected;
|
||||||
|
end if;
|
||||||
|
|
||||||
|
select pg_catalog.string_agg(
|
||||||
|
pg_catalog.format(
|
||||||
|
'%I:%s:%s:grantable=%s',
|
||||||
|
database_row.datname,
|
||||||
|
case when acl.grantee = 0 then 'PUBLIC' else grantee.rolname end,
|
||||||
|
acl.privilege_type,
|
||||||
|
acl.is_grantable
|
||||||
|
),
|
||||||
|
', '
|
||||||
|
order by database_row.datname, acl.grantee, acl.privilege_type
|
||||||
|
)
|
||||||
|
into unexpected
|
||||||
|
from pg_catalog.pg_database database_row
|
||||||
|
join pg_temp.leoclean_expected_provider_database_connect_residual expected
|
||||||
|
on expected.database_name = database_row.datname
|
||||||
|
cross join lateral pg_catalog.aclexplode(
|
||||||
|
coalesce(
|
||||||
|
database_row.datacl,
|
||||||
|
pg_catalog.acldefault('d', database_row.datdba)
|
||||||
|
)
|
||||||
|
) acl
|
||||||
|
left join pg_catalog.pg_roles grantee on grantee.oid = acl.grantee
|
||||||
|
where not (
|
||||||
|
(
|
||||||
|
acl.grantee = 0
|
||||||
|
and acl.privilege_type in ('CONNECT', 'TEMPORARY')
|
||||||
|
and not acl.is_grantable
|
||||||
|
) or (
|
||||||
|
acl.grantee = database_row.datdba
|
||||||
|
and acl.privilege_type in ('CREATE', 'CONNECT', 'TEMPORARY')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if unexpected is not null then
|
||||||
|
raise exception 'provider databases retain unexpected ACL entries: %', unexpected;
|
||||||
|
end if;
|
||||||
|
end
|
||||||
|
$$;
|
||||||
|
|
||||||
create temporary table leoclean_preserved_database_connect (
|
create temporary table leoclean_preserved_database_connect (
|
||||||
role_name name not null,
|
role_name name not null,
|
||||||
database_name name not null,
|
database_name name not null,
|
||||||
|
|
@ -166,10 +339,20 @@ select role_row.rolname, database_row.datname
|
||||||
from pg_catalog.pg_roles role_row
|
from pg_catalog.pg_roles role_row
|
||||||
cross join pg_catalog.pg_database database_row
|
cross join pg_catalog.pg_database database_row
|
||||||
where role_row.rolname not in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
|
where role_row.rolname not in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
|
||||||
|
and not exists (
|
||||||
|
select 1
|
||||||
|
from pg_temp.leoclean_expected_provider_database_connect_residual expected
|
||||||
|
where expected.database_name = database_row.datname
|
||||||
|
)
|
||||||
and pg_catalog.has_database_privilege(role_row.oid, database_row.oid, 'CONNECT');
|
and pg_catalog.has_database_privilege(role_row.oid, database_row.oid, 'CONNECT');
|
||||||
|
|
||||||
select pg_catalog.format('revoke connect on database %I from public', database_row.datname)
|
select pg_catalog.format('revoke connect on database %I from public', database_row.datname)
|
||||||
from pg_catalog.pg_database database_row
|
from pg_catalog.pg_database database_row
|
||||||
|
where not exists (
|
||||||
|
select 1
|
||||||
|
from pg_temp.leoclean_expected_provider_database_connect_residual expected
|
||||||
|
where expected.database_name = database_row.datname
|
||||||
|
)
|
||||||
\gexec
|
\gexec
|
||||||
|
|
||||||
select pg_catalog.format(
|
select pg_catalog.format(
|
||||||
|
|
@ -177,6 +360,11 @@ select pg_catalog.format(
|
||||||
database_row.datname
|
database_row.datname
|
||||||
)
|
)
|
||||||
from pg_catalog.pg_database database_row
|
from pg_catalog.pg_database database_row
|
||||||
|
where not exists (
|
||||||
|
select 1
|
||||||
|
from pg_temp.leoclean_expected_provider_database_connect_residual expected
|
||||||
|
where expected.database_name = database_row.datname
|
||||||
|
)
|
||||||
\gexec
|
\gexec
|
||||||
|
|
||||||
select pg_catalog.format(
|
select pg_catalog.format(
|
||||||
|
|
@ -988,6 +1176,11 @@ begin
|
||||||
into unexpected
|
into unexpected
|
||||||
from pg_catalog.pg_database database_row
|
from pg_catalog.pg_database database_row
|
||||||
where database_row.datname <> 'teleo_canonical'
|
where database_row.datname <> 'teleo_canonical'
|
||||||
|
and not exists (
|
||||||
|
select 1
|
||||||
|
from pg_temp.leoclean_expected_provider_database_connect_residual expected
|
||||||
|
where expected.database_name = database_row.datname
|
||||||
|
)
|
||||||
and has_database_privilege('leoclean_kb_runtime', database_row.oid, 'CONNECT');
|
and has_database_privilege('leoclean_kb_runtime', database_row.oid, 'CONNECT');
|
||||||
if unexpected is not null then
|
if unexpected is not null then
|
||||||
raise exception 'runtime CONNECT reaches a noncanonical database: %', unexpected;
|
raise exception 'runtime CONNECT reaches a noncanonical database: %', unexpected;
|
||||||
|
|
@ -996,7 +1189,12 @@ begin
|
||||||
select pg_catalog.string_agg(database_row.datname, ', ' order by database_row.datname)
|
select pg_catalog.string_agg(database_row.datname, ', ' order by database_row.datname)
|
||||||
into unexpected
|
into unexpected
|
||||||
from pg_catalog.pg_database database_row
|
from pg_catalog.pg_database database_row
|
||||||
where has_database_privilege('leoclean_kb_stage_owner', database_row.oid, 'CONNECT');
|
where not exists (
|
||||||
|
select 1
|
||||||
|
from pg_temp.leoclean_expected_provider_database_connect_residual expected
|
||||||
|
where expected.database_name = database_row.datname
|
||||||
|
)
|
||||||
|
and has_database_privilege('leoclean_kb_stage_owner', database_row.oid, 'CONNECT');
|
||||||
if unexpected is not null then
|
if unexpected is not null then
|
||||||
raise exception 'stage owner CONNECT reaches a database: %', unexpected;
|
raise exception 'stage owner CONNECT reaches a database: %', unexpected;
|
||||||
end if;
|
end if;
|
||||||
|
|
@ -1008,9 +1206,14 @@ begin
|
||||||
coalesce(database_row.datacl, pg_catalog.acldefault('d', database_row.datdba))
|
coalesce(database_row.datacl, pg_catalog.acldefault('d', database_row.datdba))
|
||||||
) acl
|
) acl
|
||||||
where acl.grantee = 0
|
where acl.grantee = 0
|
||||||
and acl.privilege_type = 'CONNECT';
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
and not exists (
|
||||||
|
select 1
|
||||||
|
from pg_temp.leoclean_expected_provider_database_connect_residual expected
|
||||||
|
where expected.database_name = database_row.datname
|
||||||
|
);
|
||||||
if unexpected is not null then
|
if unexpected is not null then
|
||||||
raise exception 'PUBLIC retains database CONNECT: %', unexpected;
|
raise exception 'PUBLIC retains unexpected database CONNECT: %', unexpected;
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
select pg_catalog.string_agg(
|
select pg_catalog.string_agg(
|
||||||
|
|
@ -1187,6 +1390,11 @@ begin
|
||||||
raise exception 'lo_compat_privileges must remain off for scoped large-object ACL enforcement';
|
raise exception 'lo_compat_privileges must remain off for scoped large-object ACL enforcement';
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
|
if pg_catalog.current_setting('session_preload_libraries') <> ''
|
||||||
|
or pg_catalog.current_setting('local_preload_libraries') <> '' then
|
||||||
|
raise exception 'session and local preload libraries must remain empty for the scoped runtime';
|
||||||
|
end if;
|
||||||
|
|
||||||
select pg_catalog.string_agg(parameter_acl.parname, ', ' order by parameter_acl.parname)
|
select pg_catalog.string_agg(parameter_acl.parname, ', ' order by parameter_acl.parname)
|
||||||
into unexpected
|
into unexpected
|
||||||
from pg_catalog.pg_parameter_acl parameter_acl
|
from pg_catalog.pg_parameter_acl parameter_acl
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ PRIVATE_CLOUDSQL_PORT = 5432
|
||||||
EXPECTED_SYSTEM_IDENTIFIER = "7659718422914359312"
|
EXPECTED_SYSTEM_IDENTIFIER = "7659718422914359312"
|
||||||
CANONICAL_DATABASE = "teleo_canonical"
|
CANONICAL_DATABASE = "teleo_canonical"
|
||||||
LEGACY_DATABASE = "teleo_kb"
|
LEGACY_DATABASE = "teleo_kb"
|
||||||
|
OPERATOR_DATABASE = "postgres"
|
||||||
TEMPLATE_DATABASE = "template1"
|
TEMPLATE_DATABASE = "template1"
|
||||||
RUNTIME_DATABASE_ROLE = "leoclean_kb_runtime"
|
RUNTIME_DATABASE_ROLE = "leoclean_kb_runtime"
|
||||||
STAGE_OWNER_DATABASE_ROLE = "leoclean_kb_stage_owner"
|
STAGE_OWNER_DATABASE_ROLE = "leoclean_kb_stage_owner"
|
||||||
|
|
@ -248,6 +249,31 @@ LARGE_OBJECT_MUTATION_ROUTINE_RESIDUAL: tuple[tuple[str, bool], ...] = (
|
||||||
("lowrite(integer, bytea)", True),
|
("lowrite(integer, bytea)", True),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PROVIDER_DATABASE_CONNECT_RESIDUAL: tuple[dict[str, object], ...] = (
|
||||||
|
{
|
||||||
|
"allow_connections": True,
|
||||||
|
"database": "cloudsqladmin",
|
||||||
|
"is_template": False,
|
||||||
|
"owner": "cloudsqladmin",
|
||||||
|
"public_connect": True,
|
||||||
|
"public_connect_acl_entries": 1,
|
||||||
|
"public_connect_grantable": False,
|
||||||
|
"runtime_connect": True,
|
||||||
|
"stage_owner_connect": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_connections": False,
|
||||||
|
"database": "template0",
|
||||||
|
"is_template": True,
|
||||||
|
"owner": "cloudsqladmin",
|
||||||
|
"public_connect": True,
|
||||||
|
"public_connect_acl_entries": 1,
|
||||||
|
"public_connect_grantable": False,
|
||||||
|
"runtime_connect": True,
|
||||||
|
"stage_owner_connect": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
CATALOG_ZERO_COUNT_FIELDS: tuple[str, ...] = (
|
CATALOG_ZERO_COUNT_FIELDS: tuple[str, ...] = (
|
||||||
"column_dml_grants",
|
"column_dml_grants",
|
||||||
"database_create_grants",
|
"database_create_grants",
|
||||||
|
|
@ -300,6 +326,7 @@ CATALOG_ZERO_COUNT_FIELDS: tuple[str, ...] = (
|
||||||
|
|
||||||
CATALOG_TRUE_FIELDS: tuple[str, ...] = (
|
CATALOG_TRUE_FIELDS: tuple[str, ...] = (
|
||||||
"canonical_connect",
|
"canonical_connect",
|
||||||
|
"cloudsqladmin_database_acl_is_default",
|
||||||
"owner_grant_contract",
|
"owner_grant_contract",
|
||||||
"owner_schema_usage",
|
"owner_schema_usage",
|
||||||
"proposal_table_contract",
|
"proposal_table_contract",
|
||||||
|
|
@ -864,6 +891,82 @@ with runtime_role as (
|
||||||
from pg_catalog.pg_namespace
|
from pg_catalog.pg_namespace
|
||||||
where nspname <> 'information_schema'
|
where nspname <> 'information_schema'
|
||||||
and nspname !~ '^pg_'
|
and nspname !~ '^pg_'
|
||||||
|
), provider_database_connect_observed as (
|
||||||
|
select database_row.oid,
|
||||||
|
database_row.datname as database_name,
|
||||||
|
owner_role.rolname as owner_name,
|
||||||
|
database_row.datallowconn as allow_connections,
|
||||||
|
database_row.datistemplate as is_template,
|
||||||
|
pg_catalog.count(*) filter (
|
||||||
|
where acl.grantee = 0
|
||||||
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
)::integer as public_connect_acl_entries,
|
||||||
|
coalesce(
|
||||||
|
pg_catalog.bool_or(true) filter (
|
||||||
|
where acl.grantee = 0
|
||||||
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
),
|
||||||
|
false
|
||||||
|
) as public_connect,
|
||||||
|
coalesce(
|
||||||
|
pg_catalog.bool_or(acl.is_grantable) filter (
|
||||||
|
where acl.grantee = 0
|
||||||
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
),
|
||||||
|
false
|
||||||
|
) as public_connect_grantable,
|
||||||
|
coalesce(
|
||||||
|
pg_catalog.has_database_privilege(
|
||||||
|
(select oid from runtime_role),
|
||||||
|
database_row.oid,
|
||||||
|
'CONNECT'
|
||||||
|
),
|
||||||
|
false
|
||||||
|
) as runtime_connect,
|
||||||
|
coalesce(
|
||||||
|
pg_catalog.has_database_privilege(
|
||||||
|
(select oid from owner_role),
|
||||||
|
database_row.oid,
|
||||||
|
'CONNECT'
|
||||||
|
),
|
||||||
|
false
|
||||||
|
) as stage_owner_connect
|
||||||
|
from pg_catalog.pg_database database_row
|
||||||
|
join pg_catalog.pg_roles owner_role on owner_role.oid = database_row.datdba
|
||||||
|
cross join lateral pg_catalog.aclexplode(
|
||||||
|
coalesce(
|
||||||
|
database_row.datacl,
|
||||||
|
pg_catalog.acldefault('d', database_row.datdba)
|
||||||
|
)
|
||||||
|
) acl
|
||||||
|
where pg_catalog.to_regrole('cloudsqladmin') is not null
|
||||||
|
and database_row.datname in ('cloudsqladmin', 'template0')
|
||||||
|
group by database_row.oid,
|
||||||
|
database_row.datname,
|
||||||
|
owner_role.rolname,
|
||||||
|
database_row.datallowconn,
|
||||||
|
database_row.datistemplate
|
||||||
|
), allowed_provider_database_connect_residual as (
|
||||||
|
select observed.oid
|
||||||
|
from provider_database_connect_observed observed
|
||||||
|
where (
|
||||||
|
(
|
||||||
|
observed.database_name = 'cloudsqladmin'
|
||||||
|
and observed.owner_name = 'cloudsqladmin'
|
||||||
|
and observed.allow_connections
|
||||||
|
and not observed.is_template
|
||||||
|
) or (
|
||||||
|
observed.database_name = 'template0'
|
||||||
|
and observed.owner_name = 'cloudsqladmin'
|
||||||
|
and not observed.allow_connections
|
||||||
|
and observed.is_template
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and observed.public_connect
|
||||||
|
and observed.public_connect_acl_entries = 1
|
||||||
|
and not observed.public_connect_grantable
|
||||||
|
and observed.runtime_connect
|
||||||
|
and observed.stage_owner_connect
|
||||||
), allowed_select(nspname, relname) as (
|
), allowed_select(nspname, relname) as (
|
||||||
values
|
values
|
||||||
{allowlist_values}
|
{allowlist_values}
|
||||||
|
|
@ -928,7 +1031,33 @@ with runtime_role as (
|
||||||
where attribute.attnum > 0
|
where attribute.attnum > 0
|
||||||
and not attribute.attisdropped
|
and not attribute.attisdropped
|
||||||
)
|
)
|
||||||
select pg_catalog.jsonb_build_object(
|
select (pg_catalog.jsonb_build_object(
|
||||||
|
'provider_database_connect_residual', coalesce((
|
||||||
|
select pg_catalog.jsonb_agg(
|
||||||
|
pg_catalog.jsonb_build_object(
|
||||||
|
'allow_connections', observed.allow_connections,
|
||||||
|
'database', observed.database_name,
|
||||||
|
'is_template', observed.is_template,
|
||||||
|
'owner', observed.owner_name,
|
||||||
|
'public_connect', observed.public_connect,
|
||||||
|
'public_connect_acl_entries', observed.public_connect_acl_entries,
|
||||||
|
'public_connect_grantable', observed.public_connect_grantable,
|
||||||
|
'runtime_connect', observed.runtime_connect,
|
||||||
|
'stage_owner_connect', observed.stage_owner_connect
|
||||||
|
)
|
||||||
|
order by observed.database_name
|
||||||
|
)
|
||||||
|
from provider_database_connect_observed observed
|
||||||
|
), '[]'::pg_catalog.jsonb),
|
||||||
|
'cloudsqladmin_database_acl_is_default', coalesce((
|
||||||
|
select database_row.datacl is null
|
||||||
|
from pg_catalog.pg_database database_row
|
||||||
|
join pg_catalog.pg_roles owner_role on owner_role.oid = database_row.datdba
|
||||||
|
where database_row.datname = 'cloudsqladmin'
|
||||||
|
and owner_role.rolname = 'cloudsqladmin'
|
||||||
|
), false)
|
||||||
|
)
|
||||||
|
|| pg_catalog.jsonb_build_object(
|
||||||
'canonical_connect', coalesce(
|
'canonical_connect', coalesce(
|
||||||
pg_catalog.has_database_privilege(current_user, pg_catalog.current_database(), 'CONNECT'),
|
pg_catalog.has_database_privilege(current_user, pg_catalog.current_database(), 'CONNECT'),
|
||||||
false
|
false
|
||||||
|
|
@ -985,11 +1114,12 @@ select pg_catalog.jsonb_build_object(
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
'effective_setting_contract', (
|
'effective_setting_contract', (
|
||||||
|
-- Do not grant pg_read_all_settings merely to inspect restricted preload
|
||||||
|
-- settings. The exact pg_db_role_setting contract above rejects scoped
|
||||||
|
-- role overrides; provider/global settings remain a separate admin check.
|
||||||
pg_catalog.current_setting('search_path') = 'pg_catalog, public, kb_stage'
|
pg_catalog.current_setting('search_path') = 'pg_catalog, public, kb_stage'
|
||||||
and pg_catalog.current_setting('statement_timeout') = '15s'
|
and pg_catalog.current_setting('statement_timeout') = '15s'
|
||||||
and pg_catalog.current_setting('lock_timeout') = '2s'
|
and pg_catalog.current_setting('lock_timeout') = '2s'
|
||||||
and pg_catalog.current_setting('session_preload_libraries') = ''
|
|
||||||
and pg_catalog.current_setting('local_preload_libraries') = ''
|
|
||||||
and pg_catalog.current_setting('lo_compat_privileges') = 'off'
|
and pg_catalog.current_setting('lo_compat_privileges') = 'off'
|
||||||
and pg_catalog.current_setting('role') = 'none'
|
and pg_catalog.current_setting('role') = 'none'
|
||||||
),
|
),
|
||||||
|
|
@ -997,6 +1127,11 @@ select pg_catalog.jsonb_build_object(
|
||||||
select pg_catalog.count(*)::int
|
select pg_catalog.count(*)::int
|
||||||
from pg_catalog.pg_database database_row
|
from pg_catalog.pg_database database_row
|
||||||
where database_row.datname <> {_sql_literal(CANONICAL_DATABASE)}
|
where database_row.datname <> {_sql_literal(CANONICAL_DATABASE)}
|
||||||
|
and not exists (
|
||||||
|
select 1
|
||||||
|
from allowed_provider_database_connect_residual allowed
|
||||||
|
where allowed.oid = database_row.oid
|
||||||
|
)
|
||||||
and pg_catalog.has_database_privilege(current_user, database_row.oid, 'CONNECT')
|
and pg_catalog.has_database_privilege(current_user, database_row.oid, 'CONNECT')
|
||||||
),
|
),
|
||||||
'owner_database_connect_grants', (
|
'owner_database_connect_grants', (
|
||||||
|
|
@ -1007,6 +1142,11 @@ select pg_catalog.jsonb_build_object(
|
||||||
database_row.oid,
|
database_row.oid,
|
||||||
'CONNECT'
|
'CONNECT'
|
||||||
)
|
)
|
||||||
|
and not exists (
|
||||||
|
select 1
|
||||||
|
from allowed_provider_database_connect_residual allowed
|
||||||
|
where allowed.oid = database_row.oid
|
||||||
|
)
|
||||||
),
|
),
|
||||||
'public_database_connect_grants', (
|
'public_database_connect_grants', (
|
||||||
select pg_catalog.count(*)::int
|
select pg_catalog.count(*)::int
|
||||||
|
|
@ -1016,6 +1156,11 @@ select pg_catalog.jsonb_build_object(
|
||||||
) acl
|
) acl
|
||||||
where acl.grantee = 0
|
where acl.grantee = 0
|
||||||
and acl.privilege_type = 'CONNECT'
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
and not exists (
|
||||||
|
select 1
|
||||||
|
from allowed_provider_database_connect_residual allowed
|
||||||
|
where allowed.oid = database_row.oid
|
||||||
|
)
|
||||||
),
|
),
|
||||||
'unexpected_direct_database_acl_entries', (
|
'unexpected_direct_database_acl_entries', (
|
||||||
select pg_catalog.count(*)::int
|
select pg_catalog.count(*)::int
|
||||||
|
|
@ -1184,7 +1329,9 @@ select pg_catalog.jsonb_build_object(
|
||||||
or actual.attgenerated <> ''
|
or actual.attgenerated <> ''
|
||||||
or actual.atttypmod <> -1
|
or actual.atttypmod <> -1
|
||||||
or actual.type_namespace <> 'pg_catalog'
|
or actual.type_namespace <> 'pg_catalog'
|
||||||
),
|
)
|
||||||
|
)
|
||||||
|
|| pg_catalog.jsonb_build_object(
|
||||||
'proposal_table_contract', coalesce((
|
'proposal_table_contract', coalesce((
|
||||||
select pg_catalog.count(*) = 1
|
select pg_catalog.count(*) = 1
|
||||||
and pg_catalog.bool_and(
|
and pg_catalog.bool_and(
|
||||||
|
|
@ -1625,7 +1772,7 @@ select pg_catalog.jsonb_build_object(
|
||||||
'EXECUTE'
|
'EXECUTE'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)::text;
|
))::text;
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1833,6 +1980,13 @@ def _negative_checks(run_id: str, source_ref: str) -> tuple[NegativeCheck, ...]:
|
||||||
"42501",
|
"42501",
|
||||||
expected_connection_denial=True,
|
expected_connection_denial=True,
|
||||||
),
|
),
|
||||||
|
NegativeCheck(
|
||||||
|
"operator_database_connect",
|
||||||
|
OPERATOR_DATABASE,
|
||||||
|
"select 1 /* operator_database_connect */;",
|
||||||
|
"42501",
|
||||||
|
expected_connection_denial=True,
|
||||||
|
),
|
||||||
NegativeCheck(
|
NegativeCheck(
|
||||||
"template_database_connect",
|
"template_database_connect",
|
||||||
TEMPLATE_DATABASE,
|
TEMPLATE_DATABASE,
|
||||||
|
|
@ -2066,6 +2220,10 @@ def _assert_catalog_privilege_posture(posture: dict[str, Any]) -> dict[str, Any]
|
||||||
if posture.get("large_object_mutation_routine_residual") != expected_residual:
|
if posture.get("large_object_mutation_routine_residual") != expected_residual:
|
||||||
raise VerificationError("large_object_residual_mismatch", "catalog_privilege_posture")
|
raise VerificationError("large_object_residual_mismatch", "catalog_privilege_posture")
|
||||||
sanitized["large_object_mutation_routine_residual"] = expected_residual
|
sanitized["large_object_mutation_routine_residual"] = expected_residual
|
||||||
|
expected_provider_residual = [dict(row) for row in PROVIDER_DATABASE_CONNECT_RESIDUAL]
|
||||||
|
if posture.get("provider_database_connect_residual") != expected_provider_residual:
|
||||||
|
raise VerificationError("provider_database_residual_mismatch", "catalog_privilege_posture")
|
||||||
|
sanitized["provider_database_connect_residual"] = expected_provider_residual
|
||||||
return sanitized
|
return sanitized
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2337,6 +2495,7 @@ def verify_runtime_permissions(
|
||||||
"direct_stage_table_writes_denied": True,
|
"direct_stage_table_writes_denied": True,
|
||||||
"production_eligible": False,
|
"production_eligible": False,
|
||||||
"production_promotion_attempted": False,
|
"production_promotion_attempted": False,
|
||||||
|
"provider_database_connect_residual": "exact_cloud_sql_system_pair",
|
||||||
"proposal_staging": "function_only",
|
"proposal_staging": "function_only",
|
||||||
"proposal_write_committed": False,
|
"proposal_write_committed": False,
|
||||||
"public_large_object_mutation_residual": "present",
|
"public_large_object_mutation_residual": "present",
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,9 @@ class FakeRunner:
|
||||||
}
|
}
|
||||||
for signature, public_execute in verifier.LARGE_OBJECT_MUTATION_ROUTINE_RESIDUAL
|
for signature, public_execute in verifier.LARGE_OBJECT_MUTATION_ROUTINE_RESIDUAL
|
||||||
],
|
],
|
||||||
|
"provider_database_connect_residual": [
|
||||||
|
dict(row) for row in verifier.PROVIDER_DATABASE_CONNECT_RESIDUAL
|
||||||
|
],
|
||||||
**{field: True for field in verifier.CATALOG_TRUE_FIELDS},
|
**{field: True for field in verifier.CATALOG_TRUE_FIELDS},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -372,6 +375,9 @@ def test_live_receipt_contract_is_sanitized_and_uses_exact_child_environments()
|
||||||
}
|
}
|
||||||
for signature, public_execute in verifier.LARGE_OBJECT_MUTATION_ROUTINE_RESIDUAL
|
for signature, public_execute in verifier.LARGE_OBJECT_MUTATION_ROUTINE_RESIDUAL
|
||||||
],
|
],
|
||||||
|
"provider_database_connect_residual": [
|
||||||
|
dict(row) for row in verifier.PROVIDER_DATABASE_CONNECT_RESIDUAL
|
||||||
|
],
|
||||||
}
|
}
|
||||||
assert receipt["checks"]["stage_function_definition"] == {
|
assert receipt["checks"]["stage_function_definition"] == {
|
||||||
"acl_exact": True,
|
"acl_exact": True,
|
||||||
|
|
@ -400,6 +406,7 @@ def test_live_receipt_contract_is_sanitized_and_uses_exact_child_environments()
|
||||||
"direct_stage_table_writes_denied": True,
|
"direct_stage_table_writes_denied": True,
|
||||||
"production_eligible": False,
|
"production_eligible": False,
|
||||||
"production_promotion_attempted": False,
|
"production_promotion_attempted": False,
|
||||||
|
"provider_database_connect_residual": "exact_cloud_sql_system_pair",
|
||||||
"proposal_staging": "function_only",
|
"proposal_staging": "function_only",
|
||||||
"proposal_write_committed": False,
|
"proposal_write_committed": False,
|
||||||
"public_large_object_mutation_residual": "present",
|
"public_large_object_mutation_residual": "present",
|
||||||
|
|
@ -1146,6 +1153,9 @@ def test_catalog_queries_use_exact_regprocedure_signatures_and_both_membership_d
|
||||||
assert "nspname <> 'information_schema'" in catalog_sql
|
assert "nspname <> 'information_schema'" in catalog_sql
|
||||||
assert verifier.STAGE_OWNER_DATABASE_ROLE in catalog_sql
|
assert verifier.STAGE_OWNER_DATABASE_ROLE in catalog_sql
|
||||||
assert "public_database_connect_grants" in catalog_sql
|
assert "public_database_connect_grants" in catalog_sql
|
||||||
|
assert "provider_database_connect_residual" in catalog_sql
|
||||||
|
assert "cloudsqladmin" in catalog_sql
|
||||||
|
assert "template0" in catalog_sql
|
||||||
assert "direct_large_object_mutation_routine_acl_entries" in catalog_sql
|
assert "direct_large_object_mutation_routine_acl_entries" in catalog_sql
|
||||||
assert "large_object_mutation_routine_residual" in catalog_sql
|
assert "large_object_mutation_routine_residual" in catalog_sql
|
||||||
assert "current_setting('lo_compat_privileges') = 'off'" in catalog_sql
|
assert "current_setting('lo_compat_privileges') = 'off'" in catalog_sql
|
||||||
|
|
@ -1191,6 +1201,30 @@ def test_large_object_provider_residual_drift_fails_closed() -> None:
|
||||||
assert caught.value.check == "catalog_privilege_posture"
|
assert caught.value.check == "catalog_privilege_posture"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("field", "drifted_value"),
|
||||||
|
(
|
||||||
|
("allow_connections", False),
|
||||||
|
("database", "cloudsqladmin-shadow"),
|
||||||
|
("is_template", True),
|
||||||
|
("owner", "postgres"),
|
||||||
|
("public_connect", False),
|
||||||
|
("public_connect_acl_entries", 2),
|
||||||
|
("public_connect_grantable", True),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_provider_database_connect_residual_drift_fails_closed(field: str, drifted_value: object) -> None:
|
||||||
|
drifted = [dict(row) for row in verifier.PROVIDER_DATABASE_CONNECT_RESIDUAL]
|
||||||
|
drifted[0][field] = drifted_value
|
||||||
|
runner = FakeRunner(catalog_posture_override={"provider_database_connect_residual": drifted})
|
||||||
|
|
||||||
|
with pytest.raises(verifier.VerificationError) as caught:
|
||||||
|
run_success(runner)
|
||||||
|
|
||||||
|
assert caught.value.code == "provider_database_residual_mismatch"
|
||||||
|
assert caught.value.check == "catalog_privilege_posture"
|
||||||
|
|
||||||
|
|
||||||
def test_large_object_residual_probe_must_be_rollback_only() -> None:
|
def test_large_object_residual_probe_must_be_rollback_only() -> None:
|
||||||
with pytest.raises(verifier.VerificationError) as caught:
|
with pytest.raises(verifier.VerificationError) as caught:
|
||||||
verifier._assert_large_object_residual_probe({"created": True, "owned_inside": False})
|
verifier._assert_large_object_residual_probe({"created": True, "owned_inside": False})
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ POSTGRES_IMAGE = "postgres@sha256:eb4759788a2182f08257135e61a34f2cfc3c2914079f34
|
||||||
POSTGRES_VERSION_NUM = "160014"
|
POSTGRES_VERSION_NUM = "160014"
|
||||||
DATABASE = "teleo_canonical"
|
DATABASE = "teleo_canonical"
|
||||||
RUNTIME_ROLE = "leoclean_kb_runtime"
|
RUNTIME_ROLE = "leoclean_kb_runtime"
|
||||||
|
CONNECT_SENTINEL_DATABASE = "leoclean_connect_sentinel"
|
||||||
LOCAL_RUNTIME_PASSWORD = "disposable-r2-runtime-password"
|
LOCAL_RUNTIME_PASSWORD = "disposable-r2-runtime-password"
|
||||||
IDENTIFIER_RE = re.compile(r"[a-z_][a-z0-9_]*\Z")
|
IDENTIFIER_RE = re.compile(r"[a-z_][a-z0-9_]*\Z")
|
||||||
|
|
||||||
|
|
@ -126,6 +127,97 @@ def _assert_denied(container: str, sql: str, sqlstate: str = "42501") -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _assert_database_connect_denied(container: str, database: str) -> None:
|
||||||
|
completed = _psql(container, "select 1;", role=RUNTIME_ROLE, database=database)
|
||||||
|
assert completed.returncode != 0, f"unexpected database connection success: {database}"
|
||||||
|
assert f'permission denied for database "{database}"' in completed.stderr
|
||||||
|
assert "does not have CONNECT privilege" in completed.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def _bootstrap_provider_database_connect_residual(container: str) -> list[dict[str, object]]:
|
||||||
|
_require_success(_psql(container, "create role cloudsqladmin nologin;"), "provider owner fixture")
|
||||||
|
_require_success(
|
||||||
|
_psql(container, "alter database template0 owner to cloudsqladmin;"),
|
||||||
|
"provider template owner fixture",
|
||||||
|
)
|
||||||
|
_require_success(
|
||||||
|
_psql(container, "create database cloudsqladmin owner cloudsqladmin template template0;"),
|
||||||
|
"provider system database fixture",
|
||||||
|
)
|
||||||
|
_require_success(
|
||||||
|
_psql(container, f"create database {CONNECT_SENTINEL_DATABASE};"),
|
||||||
|
"operator-owned connect sentinel fixture",
|
||||||
|
)
|
||||||
|
return _read_provider_database_connect_residual(container)
|
||||||
|
|
||||||
|
|
||||||
|
def _read_provider_database_connect_residual(container: str) -> list[dict[str, object]]:
|
||||||
|
output = _require_success(
|
||||||
|
_psql(
|
||||||
|
container,
|
||||||
|
"""
|
||||||
|
select coalesce(
|
||||||
|
pg_catalog.jsonb_agg(
|
||||||
|
pg_catalog.jsonb_build_object(
|
||||||
|
'allow_connections', observed.allow_connections,
|
||||||
|
'database', observed.database_name,
|
||||||
|
'database_acl', observed.database_acl,
|
||||||
|
'is_template', observed.is_template,
|
||||||
|
'owner', observed.owner_name,
|
||||||
|
'public_connect', observed.public_connect,
|
||||||
|
'public_connect_acl_entries', observed.public_connect_acl_entries,
|
||||||
|
'public_connect_grantable', observed.public_connect_grantable
|
||||||
|
)
|
||||||
|
order by observed.database_name
|
||||||
|
),
|
||||||
|
'[]'::pg_catalog.jsonb
|
||||||
|
)::text
|
||||||
|
from (
|
||||||
|
select database_row.datname as database_name,
|
||||||
|
owner_role.rolname as owner_name,
|
||||||
|
database_row.datallowconn as allow_connections,
|
||||||
|
database_row.datistemplate as is_template,
|
||||||
|
database_row.datacl::text as database_acl,
|
||||||
|
pg_catalog.count(*) filter (
|
||||||
|
where acl.grantee = 0
|
||||||
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
)::integer as public_connect_acl_entries,
|
||||||
|
coalesce(
|
||||||
|
pg_catalog.bool_or(true) filter (
|
||||||
|
where acl.grantee = 0
|
||||||
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
),
|
||||||
|
false
|
||||||
|
) as public_connect,
|
||||||
|
coalesce(
|
||||||
|
pg_catalog.bool_or(acl.is_grantable) filter (
|
||||||
|
where acl.grantee = 0
|
||||||
|
and acl.privilege_type = 'CONNECT'
|
||||||
|
),
|
||||||
|
false
|
||||||
|
) as public_connect_grantable
|
||||||
|
from pg_catalog.pg_database database_row
|
||||||
|
join pg_catalog.pg_roles owner_role on owner_role.oid = database_row.datdba
|
||||||
|
cross join lateral pg_catalog.aclexplode(
|
||||||
|
coalesce(
|
||||||
|
database_row.datacl,
|
||||||
|
pg_catalog.acldefault('d', database_row.datdba)
|
||||||
|
)
|
||||||
|
) acl
|
||||||
|
where database_row.datname in ('cloudsqladmin', 'template0')
|
||||||
|
group by database_row.datname,
|
||||||
|
owner_role.rolname,
|
||||||
|
database_row.datallowconn,
|
||||||
|
database_row.datistemplate,
|
||||||
|
database_row.datacl
|
||||||
|
) observed;
|
||||||
|
""",
|
||||||
|
),
|
||||||
|
"provider database-connect residual inventory",
|
||||||
|
)
|
||||||
|
return json.loads(output)
|
||||||
|
|
||||||
|
|
||||||
def _generated_read_tables_sql() -> str:
|
def _generated_read_tables_sql() -> str:
|
||||||
statements: list[str] = []
|
statements: list[str] = []
|
||||||
for schema, relation, columns in verifier.READ_COLUMN_ALLOWLIST:
|
for schema, relation, columns in verifier.READ_COLUMN_ALLOWLIST:
|
||||||
|
|
@ -897,6 +989,16 @@ def test_digest_pinned_network_disabled_postgres_16_14_permission_contract(tmp_p
|
||||||
f"{probe_output}\ncontainer logs:\n{logs.stdout}\n{logs.stderr}"
|
f"{probe_output}\ncontainer logs:\n{logs.stdout}\n{logs.stderr}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
provider_residual_before = _bootstrap_provider_database_connect_residual(container)
|
||||||
|
expected_provider_core = [
|
||||||
|
{key: value for key, value in row.items() if key not in {"runtime_connect", "stage_owner_connect"}}
|
||||||
|
for row in verifier.PROVIDER_DATABASE_CONNECT_RESIDUAL
|
||||||
|
]
|
||||||
|
assert [
|
||||||
|
{key: value for key, value in row.items() if key != "database_acl"}
|
||||||
|
for row in provider_residual_before
|
||||||
|
] == expected_provider_core
|
||||||
|
assert provider_residual_before[0]["database_acl"] is None
|
||||||
_require_success(_psql(container, "create database teleo_kb;"), "legacy database fixture")
|
_require_success(_psql(container, "create database teleo_kb;"), "legacy database fixture")
|
||||||
_require_success(_psql(container, _bootstrap_sql()), "permission fixture bootstrap")
|
_require_success(_psql(container, _bootstrap_sql()), "permission fixture bootstrap")
|
||||||
_assert_cloudsql_admin_attribute_compatibility(container)
|
_assert_cloudsql_admin_attribute_compatibility(container)
|
||||||
|
|
@ -953,6 +1055,62 @@ def test_digest_pinned_network_disabled_postgres_16_14_permission_contract(tmp_p
|
||||||
"direct large-object ACL recovery",
|
"direct large-object ACL recovery",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_require_success(
|
||||||
|
_psql(
|
||||||
|
container,
|
||||||
|
"create role provider_acl_probe nologin; "
|
||||||
|
"grant connect on database template0 to provider_acl_probe;",
|
||||||
|
),
|
||||||
|
"provider extra-principal ACL fixture",
|
||||||
|
)
|
||||||
|
provider_acl_drift = _provision(container, "/tmp/gcp_leoclean_runtime_role.sql")
|
||||||
|
assert provider_acl_drift.returncode != 0
|
||||||
|
assert "provider databases retain unexpected ACL entries" in provider_acl_drift.stderr
|
||||||
|
assert _require_success(
|
||||||
|
_psql(container, f"select rolcanlogin from pg_catalog.pg_roles where rolname = '{RUNTIME_ROLE}';"),
|
||||||
|
"provider ACL drift role fence",
|
||||||
|
) == "f"
|
||||||
|
_require_success(
|
||||||
|
_psql(
|
||||||
|
container,
|
||||||
|
"revoke connect on database template0 from provider_acl_probe; "
|
||||||
|
"drop role provider_acl_probe;",
|
||||||
|
),
|
||||||
|
"provider extra-principal ACL recovery",
|
||||||
|
)
|
||||||
|
|
||||||
|
_require_success(
|
||||||
|
_psql(container, "alter database teleo_canonical set session_preload_libraries = 'auto_explain';"),
|
||||||
|
"preload-library drift fixture",
|
||||||
|
)
|
||||||
|
preload_drift = _provision(container, "/tmp/gcp_leoclean_runtime_role.sql")
|
||||||
|
assert preload_drift.returncode != 0
|
||||||
|
assert "session and local preload libraries must remain empty" in preload_drift.stderr
|
||||||
|
assert _require_success(
|
||||||
|
_psql(container, f"select rolcanlogin from pg_catalog.pg_roles where rolname = '{RUNTIME_ROLE}';"),
|
||||||
|
"preload-library drift role fence",
|
||||||
|
) == "f"
|
||||||
|
_require_success(
|
||||||
|
_psql(container, "alter database teleo_canonical reset session_preload_libraries;"),
|
||||||
|
"preload-library drift recovery",
|
||||||
|
)
|
||||||
|
|
||||||
|
_require_success(
|
||||||
|
_psql(container, "alter database cloudsqladmin allow_connections false;"),
|
||||||
|
"provider residual drift fixture",
|
||||||
|
)
|
||||||
|
provider_drift = _provision(container, "/tmp/gcp_leoclean_runtime_role.sql")
|
||||||
|
assert provider_drift.returncode != 0
|
||||||
|
assert "Cloud SQL provider database CONNECT residual drifted" in provider_drift.stderr
|
||||||
|
assert _require_success(
|
||||||
|
_psql(container, f"select rolcanlogin from pg_catalog.pg_roles where rolname = '{RUNTIME_ROLE}';"),
|
||||||
|
"provider residual drift role fence",
|
||||||
|
) == "f"
|
||||||
|
_require_success(
|
||||||
|
_psql(container, "alter database cloudsqladmin allow_connections true;"),
|
||||||
|
"provider residual drift recovery",
|
||||||
|
)
|
||||||
|
|
||||||
successful_states: list[dict[str, object]] = []
|
successful_states: list[dict[str, object]] = []
|
||||||
for provision_number in range(1, 4):
|
for provision_number in range(1, 4):
|
||||||
_require_success(
|
_require_success(
|
||||||
|
|
@ -972,15 +1130,22 @@ def test_digest_pinned_network_disabled_postgres_16_14_permission_contract(tmp_p
|
||||||
assert role_state["leoclean_kb_stage_owner"]["password_is_scram"] is False
|
assert role_state["leoclean_kb_stage_owner"]["password_is_scram"] is False
|
||||||
assert role_state["leoclean_kb_stage_owner"]["password_is_null"] is True
|
assert role_state["leoclean_kb_stage_owner"]["password_is_null"] is True
|
||||||
_assert_large_object_provider_residual_rolls_back(container)
|
_assert_large_object_provider_residual_rolls_back(container)
|
||||||
|
assert _read_provider_database_connect_residual(container) == provider_residual_before
|
||||||
successful_states.append(state)
|
successful_states.append(state)
|
||||||
|
|
||||||
assert successful_states[1:] == successful_states[:1] * 2
|
assert successful_states[1:] == successful_states[:1] * 2
|
||||||
stable_state = successful_states[0]
|
stable_state = successful_states[0]
|
||||||
|
|
||||||
|
_require_success(
|
||||||
|
_psql(container, f"grant connect on database {CONNECT_SENTINEL_DATABASE} to public;"),
|
||||||
|
"rollback database ACL fixture",
|
||||||
|
)
|
||||||
|
pre_failure_state = _read_provisioning_state(container)
|
||||||
failed = _provision(container, "/tmp/gcp_leoclean_runtime_role_failure.sql")
|
failed = _provision(container, "/tmp/gcp_leoclean_runtime_role_failure.sql")
|
||||||
assert failed.returncode != 0
|
assert failed.returncode != 0
|
||||||
assert re.search(r"ERROR:\s+22012:", failed.stderr)
|
assert re.search(r"ERROR:\s+22012:", failed.stderr)
|
||||||
assert _read_provisioning_state(container) == _state_with_runtime_login(stable_state, can_login=False)
|
assert _read_provisioning_state(container) == _state_with_runtime_login(pre_failure_state, can_login=False)
|
||||||
|
assert _read_provider_database_connect_residual(container) == provider_residual_before
|
||||||
disabled_login = _psql(container, "select 1;", role=RUNTIME_ROLE)
|
disabled_login = _psql(container, "select 1;", role=RUNTIME_ROLE)
|
||||||
assert disabled_login.returncode != 0
|
assert disabled_login.returncode != 0
|
||||||
assert f'role "{RUNTIME_ROLE}" is not permitted to log in' in disabled_login.stderr
|
assert f'role "{RUNTIME_ROLE}" is not permitted to log in' in disabled_login.stderr
|
||||||
|
|
@ -997,6 +1162,14 @@ def test_digest_pinned_network_disabled_postgres_16_14_permission_contract(tmp_p
|
||||||
)
|
)
|
||||||
assert identity == f"{RUNTIME_ROLE}|{DATABASE}"
|
assert identity == f"{RUNTIME_ROLE}|{DATABASE}"
|
||||||
|
|
||||||
|
for denied_database in ("postgres", "teleo_kb", "template1", CONNECT_SENTINEL_DATABASE):
|
||||||
|
_assert_database_connect_denied(container, denied_database)
|
||||||
|
provider_identity = _require_success(
|
||||||
|
_psql(container, "select current_user || '|' || current_database();", role=RUNTIME_ROLE, database="cloudsqladmin"),
|
||||||
|
"provider-owned residual connection",
|
||||||
|
)
|
||||||
|
assert provider_identity == f"{RUNTIME_ROLE}|cloudsqladmin"
|
||||||
|
|
||||||
stage_definition = json.loads(
|
stage_definition = json.loads(
|
||||||
_require_success(
|
_require_success(
|
||||||
_psql(container, verifier._stage_function_definition_sql(), role=RUNTIME_ROLE),
|
_psql(container, verifier._stage_function_definition_sql(), role=RUNTIME_ROLE),
|
||||||
|
|
@ -1005,6 +1178,19 @@ def test_digest_pinned_network_disabled_postgres_16_14_permission_contract(tmp_p
|
||||||
)
|
)
|
||||||
assert verifier._assert_stage_function_definition(stage_definition)["owner_execute"] is False
|
assert verifier._assert_stage_function_definition(stage_definition)["owner_execute"] is False
|
||||||
|
|
||||||
|
catalog_output = _require_success(
|
||||||
|
_psql(container, verifier._catalog_privilege_posture_sql(), role=RUNTIME_ROLE),
|
||||||
|
"external verifier catalog query",
|
||||||
|
)
|
||||||
|
assert len(catalog_output.splitlines()) == 1, repr(catalog_output)
|
||||||
|
_parsed, parsed_end = json.JSONDecoder().raw_decode(catalog_output)
|
||||||
|
assert not catalog_output[parsed_end:].strip(), repr(catalog_output[parsed_end : parsed_end + 200])
|
||||||
|
catalog_posture = json.loads(catalog_output)
|
||||||
|
verified_catalog = verifier._assert_catalog_privilege_posture(catalog_posture)
|
||||||
|
assert verified_catalog["provider_database_connect_residual"] == [
|
||||||
|
dict(row) for row in verifier.PROVIDER_DATABASE_CONNECT_RESIDUAL
|
||||||
|
]
|
||||||
|
|
||||||
before = _read_fingerprint(container)
|
before = _read_fingerprint(container)
|
||||||
assert before == {
|
assert before == {
|
||||||
f"{schema}.{relation}": (1 if (schema, relation) == ("public", "agents") else 0)
|
f"{schema}.{relation}": (1 if (schema, relation) == ("public", "agents") else 0)
|
||||||
|
|
|
||||||
|
|
@ -1172,7 +1172,14 @@ def test_gcp_runtime_role_uses_one_narrow_staging_function() -> None:
|
||||||
assert "unexpectedly has database CREATE" in sql
|
assert "unexpectedly has database CREATE" in sql
|
||||||
assert "unexpectedly has schema CREATE" in sql
|
assert "unexpectedly has schema CREATE" in sql
|
||||||
assert "runtime CONNECT reaches a noncanonical database" in sql
|
assert "runtime CONNECT reaches a noncanonical database" in sql
|
||||||
assert "PUBLIC retains database CONNECT" in sql
|
assert "PUBLIC retains unexpected database CONNECT" in sql
|
||||||
|
assert "Cloud SQL provider database CONNECT residual drifted" in sql
|
||||||
|
assert "provider databases retain unexpected ACL entries" in sql
|
||||||
|
assert "('cloudsqladmin'::name, 'cloudsqladmin'::name, true, false, true, 1, false)" in sql
|
||||||
|
assert "('template0'::name, 'cloudsqladmin'::name, false, true, null::boolean, 1, false)" in sql
|
||||||
|
assert "cloudsql%" not in sql
|
||||||
|
assert "current_setting('session_preload_libraries') <> ''" in sql
|
||||||
|
assert "current_setting('local_preload_libraries') <> ''" in sql
|
||||||
assert "provider-owned large-object routine residual drifted" in sql
|
assert "provider-owned large-object routine residual drifted" in sql
|
||||||
assert "scoped Leo roles have direct large-object routine ACLs" in sql
|
assert "scoped Leo roles have direct large-object routine ACLs" in sql
|
||||||
assert "lo_compat_privileges must remain off" in sql
|
assert "lo_compat_privileges must remain off" in sql
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue