fix: prove least-privilege GCP Leo runtime context

This commit is contained in:
twentyOne2x 2026-07-15 07:16:40 +02:00
parent d299f1cdf5
commit 7b16c6d3cc
18 changed files with 2840 additions and 536 deletions

File diff suppressed because it is too large Load diff

View file

@ -44,6 +44,11 @@ IAM or networking.
7. Deployment proves the attached VM service account from the metadata server, 7. Deployment proves the attached VM service account from the metadata server,
uses an empty phase-local Cloud SDK configuration, and rolls the complete uses an empty phase-local Cloud SDK configuration, and rolls the complete
prior runtime set back if installation or post-restart verification fails. prior runtime set back if installation or post-restart verification fails.
8. Post-restart status, permission, and administrator-secret denial probes join
both the actual service mount and network namespaces. Effective systemd
network, address-family, socket-bind, syscall-filter, and LSM properties must
match the reviewed unrestricted transport contract so a host-context helper
cannot produce a false runtime receipt.
## Safe Order Of Operations ## Safe Order Of Operations
@ -70,21 +75,33 @@ an artifact, or a command transcript.
### 4. Provision the scoped PostgreSQL role once ### 4. Provision the scoped PostgreSQL role once
Run `ops/gcp_leoclean_runtime_role.sql` as the Cloud SQL administrator from the Run `ops/provision_gcp_leoclean_runtime_role.sh` as the Cloud SQL administrator
private VM path. Supply the new runtime password through from the private VM path. Supply the new runtime password through
`TELEO_LEOCLEAN_DB_PASSWORD`; the SQL file does not contain it. The migration: `TELEO_LEOCLEAN_DB_PASSWORD` and the administrator password through
`PGPASSWORD`. The wrapper captures and unsets both fields before its first
external child, pins the private endpoint and reviewed server CA, detaches
`psql` from the controlling terminal, and feeds the runtime value only to
psql's client-side `\password` prompt. The SQL file and argv never contain the
cleartext runtime password. The migration:
- creates or rotates `leoclean_kb_runtime`; - creates or rotates `leoclean_kb_runtime`;
- keeps `leoclean_kb_runtime` `NOLOGIN` while both the `teleo_canonical` grant - changes any pre-existing `leoclean_kb_runtime` to `NOLOGIN`, removes its
phase and the legacy `teleo_kb` denial-verification phase run, and enables membership edges, and terminates its existing sessions before password
`LOGIN` only in the final transaction after both phases pass; handling; an interruption therefore leaves the old login disabled;
- enables `LOGIN` only as the last statement in the same transaction as the
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
preserved grants, gives the runtime `CONNECT` only to `teleo_canonical`, and
leaves the staging owner with no database connection target;
- inventories and preserves existing non-scoped application-routine and
large-object access while removing `PUBLIC` and both scoped roles from all
other application routines and every persistent large-object mutator;
- 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`;
- grants no direct table writes; - grants no direct table writes;
- removes all access to the legacy `teleo_restore` schema;
- aborts if either scoped role owns or can reach anything outside the explicit - aborts if either scoped role owns or can reach anything outside the explicit
allowlist. allowlist.
@ -99,11 +116,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` can still be connectable through its existing `PUBLIC CONNECT` `teleo_kb`, `template1`, and every other noncanonical database are actual
grant. That database-wide default is not treated as runtime authority. The negative connection checks. PostgreSQL's startup protocol does not expose a
migration denies the scoped role access to the `teleo_restore` schema, the SQLSTATE through `psql`, so the verifier requires the exact C-locale
runtime client is pinned to `teleo_canonical`, and audit fallback remains `permission denied for database` and `does not have CONNECT privilege`
disabled with `TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK=0`. diagnostics in addition to the catalog proof that the runtime's sole effective
target is `teleo_canonical`. Audit fallback remains disabled with
`TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK=0`.
### 5. Preflight and deploy the merged runtime ### 5. Preflight and deploy the merged runtime
@ -143,41 +162,71 @@ directory as a read-only bind at its existing
`/home/teleo/.hermes/profiles/leoclean/bin` path; verification compares the `/home/teleo/.hermes/profiles/leoclean/bin` path; verification compares the
source and service-namespace inode and hashes and proves the mount is read-only. source and service-namespace inode and hashes and proves the mount is read-only.
To prevent the `teleo` service user from renaming a writable ancestor and To prevent the `teleo` service user from renaming a writable ancestor and
recreating that absolute path, the service namespace mounts recreating that absolute path, the service namespace mounts the complete
`/home/teleo/.hermes` read-only, allows writes back only to the profile's `/home/teleo` tree read-only, allows writes back only to the profile's `state`
`state` and `workspace` subdirectories, and removes `CAP_SYS_ADMIN`. Live and `workspace` subdirectories, clears every bounding and ambient capability,
verification requires `.hermes` and `bin` to be read-only, both allowed and enforces `NoNewPrivileges`. Live verification requires the service home and
subdirectories to be writable, `/home` to be non-writable by `teleo`, and the bound `bin` to be read-only, both allowed subdirectories to be writable, the
effective capability set to exclude `CAP_SYS_ADMIN`. exact `teleo` group set, and every process capability field to be zero.
Preflight, post-restart, and `--verify-only` checks inspect the environment of the actual Preflight, post-restart, and `--verify-only` checks inspect the environment of the actual
systemd `MainPID` through `/proc/<MainPID>/environ`, rather than trusting the systemd `MainPID` through `/proc/<MainPID>/environ`, rather than trusting the
configured unit environment alone. A standalone fail-closed verifier requires configured unit environment alone. It also rejects effective credential
properties, auxiliary `Exec*` hooks, any `EnvironmentFile`, and any drop-in
ordered after the reviewed configuration. A standalone fail-closed verifier requires
the exact reviewed private target and runtime role, requires the exact reviewed private target and runtime role, requires
`TELEO_GCP_METADATA_ONLY=1`, and rejects all PostgreSQL, Cloud SDK, proxy, TLS `TELEO_GCP_METADATA_ONLY=1`, and rejects all PostgreSQL, Cloud SDK, proxy, TLS
trust, broader Google credential, shell-startup, dynamic-loader, Python-loader, trust, broader Google credential, shell-startup, dynamic-loader, Python-loader,
and administrator-secret overrides without printing their values. Effective administrator-secret, excerpt, credential-mode, claim-base, and retry-count
`EnvironmentFile` use and drop-ins ordered after the reviewed configuration are overrides without printing their values. The reviewed root-owned empty
rejected before restart. This blocks `CLOUDSDK_CONFIG`, pinned `PGSSLMODE=verify-ca`,
server CA, and Python isolation fields must match exactly. This blocks
pre-wrapper injection through fields such as `BASH_ENV`, any `LD_*`, pre-wrapper injection through fields such as `BASH_ENV`, any `LD_*`,
`PYTHONPATH`, `PYTHONHTTPSVERIFY`, or `SSLKEYLOGFILE`. The checks also require `PYTHONPATH`, `PYTHONHTTPSVERIFY`, or `SSLKEYLOGFILE`. The checks also require
the reviewed drop-in to appear in `DropInPaths`. The wrapper status probe then the reviewed drop-in to appear in `DropInPaths`. The wrapper status probe then
imports the validated environment from the actual `MainPID` with `nsenter imports the validated environment from the actual `MainPID` with `nsenter
--env`, enters its mount namespace, drops to `teleo`, and invokes the bound --env`, enters its mount and network namespaces, drops to `teleo`, and invokes the bound
wrapper without reinjecting any database identity setting. wrapper without reinjecting any database identity setting.
The effective unit must use the host network namespace (`PrivateNetwork=no`),
have no `NetworkNamespacePath`, `IPAddressDeny`, `IPAddressAllow`,
`RestrictAddressFamilies`, `SocketBindAllow`, `SocketBindDeny`,
`RestrictNetworkInterfaces`, `SystemCallFilter`, `AppArmorProfile`, or
`SELinuxContext` override, and load no
later drop-in. The executable regression harness injects each conflicting
property and requires the gate to fail. These checks cover restrictions that a
new `nsenter` process would not inherit merely by sharing the service network
namespace.
For cgroup/eBPF restrictions, post-restart probes do not rely on enumeration.
A root-only bounded runner verifies the service's single cgroup-v2 membership
against systemd's effective `ControlGroup`, forks a stopped child, moves only
that child into the service cgroup, confirms the move, and then resumes it to
join the service namespaces and drop to `teleo`. It propagates exit or signal
status and reaps the child on every path. The child installs a Linux
parent-death `SIGKILL` before stopping, with a parent-PID race check; the parent
blocks and handles `INT`, `TERM`, and `HUP` through cleanup before re-raising
the signal. Before resume, every supported Linux `RLIMIT_*` soft/hard pair is
copied from the live `MainPID` with a stable source and child readback. Live
`MainPID` verification also requires seccomp mode and filter count zero, the
exact `unconfined` LSM context, `PrivateUsers=no`, and the same user-namespace
inode as PID 1, preventing a per-process filter, profile, resource ceiling, or
user namespace from being mistaken for the reviewed service context.
The service runtime bypasses Cloud SDK configuration and credential caches The service runtime bypasses Cloud SDK configuration and credential caches
entirely. The helper obtains the attached service-account email and a bounded entirely. The helper obtains the attached service-account email and a bounded
access token from the fixed GCE metadata endpoint, requires the exact expected access token from the fixed GCE metadata endpoint, requires the exact expected
service account, and accesses only the named scoped Secret Manager version over service account, and accesses only the named scoped Secret Manager version over
the fixed HTTPS API. Its URL opener disables proxies. The scoped status read the fixed HTTPS API. Its URL opener disables proxies. The scoped status read
and administrator-secret IAM denial are rerun after restart. The denial probe and administrator-secret IAM denial are rerun after restart. The denial probe
imports the actual systemd `MainPID` environment and mount namespace, drops to imports the actual systemd `MainPID` environment and mount and network
namespaces, drops to
the service's `teleo` identity, and must observe the exact the service's `teleo` identity, and must observe the exact
`secretmanager.versions.access` permission denial. No token or `secretmanager.versions.access` permission denial. No token or
secret value is retained. The status read executes the bound `teleo-kb` wrapper secret value is retained. The status read and permission verifier execute from
from inside the service mount namespace, proving the wrapper and its adjacent inside the service mount and network namespaces; the status path invokes the
helper rather than bypassing them with a direct helper invocation. A failure bound `teleo-kb` wrapper and its adjacent helper rather than bypassing them with
a direct helper invocation. A failure
after mutation restores the prior runtime/drop-in directory existence and after mutation restores the prior runtime/drop-in directory existence and
metadata, wrapper, helper, both verifiers, drop-in, and revision as one set, metadata, wrapper, helper, both verifiers, drop-in, and revision as one set,
validates it against the root-only backup, and only then reloads systemd and validates it against the root-only backup, and only then reloads systemd and
@ -196,7 +245,7 @@ sudo chmod 0700 "$receipt_dir"
run_id="$(date -u +%Y%m%d%H%M%S)-manual" run_id="$(date -u +%Y%m%d%H%M%S)-manual"
sudo -n -u teleo env -i HOME=/home/teleo \ sudo -n -u teleo env -i HOME=/home/teleo \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
python3 /usr/local/libexec/livingip/leoclean-kb/verify_gcp_leoclean_runtime_permissions.py \ /usr/bin/python3 -I /usr/local/libexec/livingip/leoclean-kb/verify_gcp_leoclean_runtime_permissions.py \
--run-id "$run_id" \ --run-id "$run_id" \
--output "$receipt_dir/permission-receipt.json" --output "$receipt_dir/permission-receipt.json"
sudo stat -c '%U:%G:%a' "$receipt_dir/permission-receipt.json" sudo stat -c '%U:%G:%a' "$receipt_dir/permission-receipt.json"
@ -230,13 +279,17 @@ 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
large-object mutator execution, and zero scoped large-object ownership/ACLs;
- 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 access to `teleo_restore` even when `teleo_kb` remains connectable; - denied startup connections to both `teleo_kb` and `template1`, with the
catalog showing `teleo_canonical` as the sole runtime connection target and
zero remaining `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.

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/python3 -I
"""Cloud SQL memory and KB-proposal bridge for the GCP leoclean profile. """Cloud SQL memory and KB-proposal bridge for the GCP leoclean profile.
Canonical claim/strategy application remains review-gated. The production-like Canonical claim/strategy application remains review-gated. The production-like
@ -17,8 +17,10 @@ import hashlib
import json import json
import os import os
import re import re
import stat
import subprocess import subprocess
import urllib.request import urllib.request
from pathlib import Path
from typing import Any from typing import Any
STOPWORDS = { STOPWORDS = {
@ -84,6 +86,12 @@ RUNTIME_GCP_PROJECT = "teleo-501523"
RUNTIME_CLOUDSQL_HOST = "10.61.0.3" RUNTIME_CLOUDSQL_HOST = "10.61.0.3"
RUNTIME_CLOUDSQL_PORT = "5432" RUNTIME_CLOUDSQL_PORT = "5432"
RUNTIME_CLOUDSQL_DB = "teleo_canonical" RUNTIME_CLOUDSQL_DB = "teleo_canonical"
RUNTIME_SYSTEM_IDENTIFIER = "7659718422914359312"
RUNTIME_CLOUDSDK_CONFIG = "/usr/local/libexec/livingip/leoclean-kb/gcloud-config"
RUNTIME_PSQL_BIN = "/usr/bin/psql"
RUNTIME_SSL_MODE = "verify-ca"
RUNTIME_SSL_ROOT_CERT = "/usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem"
RUNTIME_SSL_ROOT_CERT_SHA256 = "80701e768f0e1f6b9d621aa0b53f6e851daaa276c6d9a8e51a300fbc015539cb"
RUNTIME_SERVICE_ACCOUNT = "sa-teleo-prod-vm@teleo-501523.iam.gserviceaccount.com" RUNTIME_SERVICE_ACCOUNT = "sa-teleo-prod-vm@teleo-501523.iam.gserviceaccount.com"
GCE_METADATA_EMAIL_URL = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email" GCE_METADATA_EMAIL_URL = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email"
GCE_METADATA_TOKEN_URL = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token" GCE_METADATA_TOKEN_URL = "http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token"
@ -105,13 +113,60 @@ RUNTIME_TLS_TRUST_ENVIRONMENT = frozenset(
"SSL_CERT_DIR", "SSL_CERT_DIR",
"SSL_CERT_FILE", "SSL_CERT_FILE",
"SSLKEYLOGFILE", "SSLKEYLOGFILE",
"OPENSSL_CONF",
"OPENSSL_ENGINES",
"OPENSSL_MODULES",
"GCONV_PATH",
} }
) )
RUNTIME_PSQL_ENV_BASE = { RUNTIME_PSQL_ENV_BASE = {
"PATH": "/usr/bin:/bin", "PATH": "/usr/bin:/bin",
"PGSSLMODE": "require", "PGSSLMODE": RUNTIME_SSL_MODE,
"PGCONNECT_TIMEOUT": "8", "PGCONNECT_TIMEOUT": "8",
} }
def runtime_ssl_root_cert() -> str:
"""Return the reviewed installed or ephemeral preflight CA path."""
raw = os.environ.get("TELEO_GCP_PREFLIGHT_SSL_ROOT_CERT", RUNTIME_SSL_ROOT_CERT)
candidate = Path(raw)
try:
if not candidate.is_absolute() or candidate.is_symlink():
raise OSError
resolved = candidate.resolve(strict=True)
if resolved != candidate or not resolved.is_file():
raise OSError
for part in (resolved, *resolved.parents):
metadata = part.stat()
if metadata.st_uid != 0 or metadata.st_mode & (stat.S_IWGRP | stat.S_IWOTH):
raise OSError
certificate = resolved.read_bytes()
except OSError:
raise SystemExit("Reviewed Cloud SQL server CA path is unavailable or untrusted.") from None
if hashlib.sha256(certificate).hexdigest() != RUNTIME_SSL_ROOT_CERT_SHA256:
raise SystemExit("Reviewed Cloud SQL server CA does not match the pinned certificate.")
return str(resolved)
def runtime_cloudsdk_config() -> str:
"""Return a root-owned empty SDK configuration directory."""
raw = os.environ.get("TELEO_GCP_PREFLIGHT_CLOUDSDK_CONFIG", RUNTIME_CLOUDSDK_CONFIG)
candidate = Path(raw)
try:
if not candidate.is_absolute() or candidate.is_symlink():
raise OSError
resolved = candidate.resolve(strict=True)
if resolved != candidate or not resolved.is_dir() or any(resolved.iterdir()):
raise OSError
for part in (resolved, *resolved.parents):
metadata = part.stat()
if metadata.st_uid != 0 or metadata.st_mode & (stat.S_IWGRP | stat.S_IWOTH):
raise OSError
except OSError:
raise SystemExit("Reviewed empty Cloud SDK configuration is unavailable or untrusted.") from None
return str(resolved)
CLONE_READ_ONLY_PGOPTIONS = "-c default_transaction_read_only=on" CLONE_READ_ONLY_PGOPTIONS = "-c default_transaction_read_only=on"
RECEIPTED_READ_COMMANDS = frozenset( RECEIPTED_READ_COMMANDS = frozenset(
{ {
@ -290,6 +345,8 @@ def validate_runtime_connection(args: argparse.Namespace) -> None:
password_secret = str(args.password_secret or "") password_secret = str(args.password_secret or "")
mode = str(getattr(args, "credential_mode", "runtime")) mode = str(getattr(args, "credential_mode", "runtime"))
if mode == "runtime": if mode == "runtime":
ssl_root_cert = runtime_ssl_root_cert()
cloudsdk_config = runtime_cloudsdk_config()
if user != RUNTIME_DB_USER: if user != RUNTIME_DB_USER:
raise SystemExit(f"Leo runtime requires database user {RUNTIME_DB_USER}.") raise SystemExit(f"Leo runtime requires database user {RUNTIME_DB_USER}.")
if password_secret != RUNTIME_PASSWORD_SECRET: if password_secret != RUNTIME_PASSWORD_SECRET:
@ -310,12 +367,31 @@ def validate_runtime_connection(args: argparse.Namespace) -> None:
if os.environ.get("TELEO_GCP_METADATA_ONLY") != "1": if os.environ.get("TELEO_GCP_METADATA_ONLY") != "1":
raise SystemExit("Leo runtime requires TELEO_GCP_METADATA_ONLY=1.") raise SystemExit("Leo runtime requires TELEO_GCP_METADATA_ONLY=1.")
forbidden_environment = any( forbidden_environment = any(
normalized_key.startswith("PG") (
or normalized_key.startswith("CLOUDSDK_") normalized_key.startswith("PG")
and not (
(normalized_key == "PGSSLMODE" and value == RUNTIME_SSL_MODE)
or (normalized_key == "PGSSLROOTCERT" and value == ssl_root_cert)
)
)
or (
normalized_key.startswith("CLOUDSDK_")
and not (
normalized_key == "CLOUDSDK_CONFIG"
and value == cloudsdk_config
)
)
or normalized_key == "GOOGLE_APPLICATION_CREDENTIALS" or normalized_key == "GOOGLE_APPLICATION_CREDENTIALS"
or normalized_key.startswith("LD_") or normalized_key.startswith("LD_")
or normalized_key.startswith("BASH_FUNC_") or normalized_key.startswith("BASH_FUNC_")
or (normalized_key.startswith("PYTHON") and normalized_key != "PYTHONUNBUFFERED") or (
normalized_key.startswith("PYTHON")
and not (
(normalized_key == "PYTHONNOUSERSITE" and value == "1")
or (normalized_key == "PYTHONSAFEPATH" and value == "1")
or normalized_key == "PYTHONUNBUFFERED"
)
)
or normalized_key in { or normalized_key in {
"BASH_ENV", "BASH_ENV",
"BASHOPTS", "BASHOPTS",
@ -329,7 +405,8 @@ def validate_runtime_connection(args: argparse.Namespace) -> None:
or normalized_key == "PROXY" or normalized_key == "PROXY"
or normalized_key.endswith("_PROXY") or normalized_key.endswith("_PROXY")
or normalized_key in RUNTIME_TLS_TRUST_ENVIRONMENT or normalized_key in RUNTIME_TLS_TRUST_ENVIRONMENT
for normalized_key in (key.upper() for key in os.environ) for key, value in os.environ.items()
for normalized_key in (key.upper(),)
) )
if forbidden_environment: if forbidden_environment:
raise SystemExit( raise SystemExit(
@ -515,6 +592,7 @@ def run_psql(args: argparse.Namespace, sql: str, db: str | None = None) -> str:
credential = password(args) credential = password(args)
if args.credential_mode == "runtime": if args.credential_mode == "runtime":
env = dict(RUNTIME_PSQL_ENV_BASE) env = dict(RUNTIME_PSQL_ENV_BASE)
env["PGSSLROOTCERT"] = runtime_ssl_root_cert()
else: else:
env = {key: value for key, value in os.environ.items() if not key.startswith("PG")} env = {key: value for key, value in os.environ.items() if not key.startswith("PG")}
env.update( env.update(
@ -529,7 +607,7 @@ def run_psql(args: argparse.Namespace, sql: str, db: str | None = None) -> str:
if args.credential_mode == "clone-readonly": if args.credential_mode == "clone-readonly":
env["PGOPTIONS"] = CLONE_READ_ONLY_PGOPTIONS env["PGOPTIONS"] = CLONE_READ_ONLY_PGOPTIONS
result = subprocess.run( result = subprocess.run(
["psql", "-X", "-At", "-q", "-v", "ON_ERROR_STOP=1"], [RUNTIME_PSQL_BIN, "-X", "-At", "-q", "-v", "ON_ERROR_STOP=1"],
text=True, text=True,
capture_output=True, capture_output=True,
input=sql, input=sql,
@ -583,6 +661,8 @@ select jsonb_build_object(
raise SystemExit("Canonical database read marker returned an unexpected server port.") raise SystemExit("Canonical database read marker returned an unexpected server port.")
if marker.get("ssl") is not True: if marker.get("ssl") is not True:
raise SystemExit("Canonical database read marker did not prove an encrypted connection.") raise SystemExit("Canonical database read marker did not prove an encrypted connection.")
if marker.get("system_identifier") != RUNTIME_SYSTEM_IDENTIFIER:
raise SystemExit("Canonical database read marker returned an unexpected system identifier.")
return marker return marker

View file

@ -0,0 +1,52 @@
#!/bin/bash
set -euo pipefail
# This wrapper is installed only into the GCP service's root-controlled bind.
# Ignore per-invocation mode and endpoint overrides so a service child cannot
# select the VPS/local backend or a different database credential context.
SCRIPT_PATH=${BASH_SOURCE[0]}
SCRIPT_DIR=${SCRIPT_PATH%/*}
if [ "$SCRIPT_DIR" = "$SCRIPT_PATH" ]; then
SCRIPT_DIR=.
fi
SCRIPT_DIR="$(cd "$SCRIPT_DIR" && pwd)"
CLOUDSQL_TOOL="$SCRIPT_DIR/cloudsql_memory_tool.py"
for argument in "$@"; do
case "$argument" in
--host|--host=*|--port|--port=*|--db|--db=*|--canonical-db|--canonical-db=*|\
--user|--user=*|--password-secret|--password-secret=*|--project|--project=*|\
--credential-mode|--credential-mode=*)
echo "teleo-kb: GCP runtime identity and endpoint overrides are forbidden" >&2
exit 64
;;
esac
done
if [ ! -x "$CLOUDSQL_TOOL" ]; then
echo "teleo-kb: reviewed GCP Cloud SQL tool is unavailable" >&2
exit 69
fi
if [ ! -x /usr/bin/python3 ] || [ ! -x /usr/bin/psql ]; then
echo "teleo-kb: required GCP runtime dependency is unavailable" >&2
exit 69
fi
exec /usr/bin/env -i \
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
LANG=C LC_ALL=C \
PYTHONNOUSERSITE=1 PYTHONSAFEPATH=1 \
CLOUDSDK_CONFIG=/usr/local/libexec/livingip/leoclean-kb/gcloud-config \
PGSSLMODE=verify-ca \
PGSSLROOTCERT=/usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem \
TELEO_KB_MODE=cloudsql \
TELEO_GCP_METADATA_ONLY=1 \
TELEO_GCP_PROJECT=teleo-501523 \
TELEO_CLOUDSQL_HOST=10.61.0.3 \
TELEO_CLOUDSQL_PORT=5432 \
TELEO_CLOUDSQL_DB=teleo_canonical \
TELEO_CANONICAL_CLOUDSQL_DB=teleo_canonical \
TELEO_CLOUDSQL_USER=leoclean_kb_runtime \
TELEO_CLOUDSQL_PASSWORD_SECRET=gcp-teleo-pgvector-standby-leoclean-kb-runtime-password \
TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK=0 \
/usr/bin/python3 -I "$CLOUDSQL_TOOL" "$@"

View file

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDcTCCAlmgAwIBAgIBADANBgkqhkiG9w0BAQsFADBwMS0wKwYDVQQuEyQzNTcw
NmEzOC03MGRjLTQ3YTQtOWExOS04NGMxYTk0YzQ3ZjMxHDAaBgNVBAMTE0Nsb3Vk
IFNRTCBTZXJ2ZXIgQ0ExFDASBgNVBAoTC0dvb2dsZSwgSW5jMQswCQYDVQQGEwJV
UzAeFw0yNjA3MDcwOTM3NTJaFw0zNjA3MDQwOTM4NTJaMHAxLTArBgNVBC4TJDM1
NzA2YTM4LTcwZGMtNDdhNC05YTE5LTg0YzFhOTRjNDdmMzEcMBoGA1UEAxMTQ2xv
dWQgU1FMIFNlcnZlciBDQTEUMBIGA1UEChMLR29vZ2xlLCBJbmMxCzAJBgNVBAYT
AlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0HwhrUzFIi9bqPbC
9FQjCalxEVYsu7tw+YzBtUaKOy+u/Y1e6TKJzFng/9wS8pXgY1Ul1/mn1S205yKR
5ydEESq7UL4pp7onSb2vGeu4NDTXDbcimZz++35dPbhFw7wtm1QSRMBHsONEDGaS
7/qIB3EN79SS9GxbSUZNsZOAn4DM9RSahtQrXa6NLlP7I9gPxvqEKRjFqUcBkWsE
Qzoz/yyxYQybZQo1UR9XCbGWFphi85cW15ITpfs7czoOVUk39bPNJKAVQFRqPUWK
xBC+1pHb7fe7xYk5j3jJbtoL6ixunQIFDCGRCy7/SEfIj5dWriDajVHwctilUmu1
Xy/A0QIDAQABoxYwFDASBgNVHRMBAf8ECDAGAQH/AgEAMA0GCSqGSIb3DQEBCwUA
A4IBAQBi8MoiT4kJAut4mLPibKldRB9s2Of7Jw8PmKNS50BIOZf8zrSrNMX5yC2i
/y9nmc8k9OCIYbzC6F/mTACmtHM1phoBtPYDjWYp+YS9cdo0HHGU2hwEP7PTfEOg
11ua2J1gjFbgVZVb7qNGV4n1QjCcAh1hWnJ2gLa55hiZBGZ0fuHTwBagWwRtom+u
YdD1d1NETInnCxnhzu7q+r0sjFUJABIj4qU/q/uVMHaAuy4Zy4PMctQofKSJ4ixf
vbU2/Sm7cbzgzcg2TJwpSqdDM/EdDMi5JtmMyZnRoKLZjH3CYI1091dyTBdk8RhK
MB4pqjJurXS8xKeA9dQjuvnIQvot
-----END CERTIFICATE-----

View file

@ -1,7 +1,8 @@
\set ON_ERROR_STOP on \set ON_ERROR_STOP on
-- One-time GCP staging provisioning for Leo's Cloud SQL identity. The password -- One-time GCP staging provisioning for Leo's Cloud SQL identity. Run this
-- is supplied only through the process environment and is never stored here. -- only through ops/provision_gcp_leoclean_runtime_role.sh; psql's \password
-- command encrypts the supplied value client-side, so cleartext is never SQL.
-- --
-- The login can read the canonical allowlist and can stage a pending proposal -- The login can read the canonical allowlist and can stage a pending proposal
-- through one narrowly-scoped SECURITY DEFINER function. It cannot inherit or -- through one narrowly-scoped SECURITY DEFINER function. It cannot inherit or
@ -24,19 +25,34 @@ alter role leoclean_kb_stage_owner
with nologin nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit -1 with nologin nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit -1
password null; password null;
-- Rotate the credential only after an idempotent rerun has disabled any -- Snapshot every non-superuser principal connected to either scoped role before
-- pre-existing LOGIN role. This ordering has an intentional autocommit -- removing the direct membership edges. Existing sessions can retain an
-- boundary: interruption can leave NOLOGIN, never a still-enabled old role. -- already-selected SET ROLE identity after NOLOGIN/revocation, so all other
\getenv runtime_password TELEO_LEOCLEAN_DB_PASSWORD -- connected sessions must be terminated before password or privilege work.
select format('alter role leoclean_kb_runtime password %L', :'runtime_password') create temporary table leoclean_fenced_roles (
\gexec role_oid oid primary key,
role_name name unique not null
) on commit preserve rows;
alter role leoclean_kb_runtime reset all; with recursive connected(role_oid) as (
alter role leoclean_kb_stage_owner reset all; values
('leoclean_kb_runtime'::pg_catalog.regrole::oid),
('leoclean_kb_stage_owner'::pg_catalog.regrole::oid)
union
select membership.member
from connected
join pg_catalog.pg_auth_members membership
on membership.roleid = connected.role_oid
)
insert into pg_temp.leoclean_fenced_roles (role_oid, role_name)
select role_row.oid, role_row.rolname
from connected
join pg_catalog.pg_roles role_row on role_row.oid = connected.role_oid
where not role_row.rolsuper;
-- NOINHERIT does not prevent SET ROLE. Remove every membership edge in either -- NOINHERIT does not prevent SET ROLE. Remove every direct membership edge in
-- direction so neither scoped principal can reach, or be reached through, a -- either direction before terminating the snapshotted sessions, closing the
-- broader role left behind by an earlier deployment. -- race for newly connected membership principals.
select format('revoke %I from %I', granted_role.rolname, member_role.rolname) select format('revoke %I from %I', granted_role.rolname, member_role.rolname)
from pg_catalog.pg_auth_members membership from pg_catalog.pg_auth_members membership
join pg_catalog.pg_roles granted_role on granted_role.oid = membership.roleid join pg_catalog.pg_roles granted_role on granted_role.oid = membership.roleid
@ -45,11 +61,62 @@ select format('revoke %I from %I', granted_role.rolname, member_role.rolname)
or member_role.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner') or member_role.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
\gexec \gexec
revoke all privileges on database teleo_canonical from leoclean_kb_runtime; do $session_fence$
revoke all privileges on database teleo_canonical from leoclean_kb_stage_owner; declare
revoke all privileges on database teleo_kb from leoclean_kb_runtime; target record;
revoke all privileges on database teleo_kb from leoclean_kb_stage_owner; remaining text;
grant connect on database teleo_canonical to leoclean_kb_runtime; begin
for target in
select activity.pid
from pg_catalog.pg_stat_activity activity
join pg_temp.leoclean_fenced_roles fenced on fenced.role_name = activity.usename
where activity.pid <> pg_catalog.pg_backend_pid()
and activity.backend_type = 'client backend'
loop
if not pg_catalog.pg_terminate_backend(target.pid, 5000) then
raise exception 'could not terminate a pre-existing scoped-role session';
end if;
end loop;
select pg_catalog.string_agg(activity.pid::text, ',' order by activity.pid)
into remaining
from pg_catalog.pg_stat_activity activity
join pg_temp.leoclean_fenced_roles fenced on fenced.role_name = activity.usename
where activity.pid <> pg_catalog.pg_backend_pid()
and activity.backend_type = 'client backend';
if remaining is not null then
raise exception 'pre-existing scoped-role sessions remain after termination';
end if;
select pg_catalog.string_agg(prepared.gid, ',' order by prepared.gid)
into remaining
from pg_catalog.pg_prepared_xacts prepared
join pg_temp.leoclean_fenced_roles fenced on fenced.role_name = prepared.owner;
if remaining is not null then
raise exception 'scoped-role prepared transactions must be resolved before provisioning';
end if;
end
$session_fence$;
-- This is deliberately after the autocommitted NOLOGIN/session/membership
-- fence. Interruption can leave a disabled role, never a still-enabled old
-- login or an old connected session. The wrapper supplies the two prompts.
\password leoclean_kb_runtime
alter role leoclean_kb_runtime reset all;
alter role leoclean_kb_stage_owner reset all;
drop table pg_temp.leoclean_fenced_roles;
select pg_catalog.format(
'alter role %I in database %I reset all',
role_row.rolname,
database_row.datname
)
from pg_catalog.pg_db_role_setting setting_row
join pg_catalog.pg_roles role_row on role_row.oid = setting_row.setrole
join pg_catalog.pg_database database_row on database_row.oid = setting_row.setdatabase
where role_row.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
\gexec
-- PostgreSQL normally grants TEMP to PUBLIC. A role-specific REVOKE cannot -- PostgreSQL normally grants TEMP to PUBLIC. A role-specific REVOKE cannot
-- override that inherited grant, while REVOKE TEMP FROM PUBLIC would be a -- override that inherited grant, while REVOKE TEMP FROM PUBLIC would be a
@ -77,6 +144,47 @@ alter role leoclean_kb_runtime in database teleo_canonical
begin; begin;
-- CONNECT granted to PUBLIC cannot be denied to one role with a role-specific
-- REVOKE. Preserve every existing non-scoped principal's effective CONNECT
-- set as explicit ACLs, remove the cluster-wide PUBLIC grant from every
-- database (including templates), and then grant the runtime its sole target.
-- This runs in the same transaction as every remaining privilege/topology
-- assertion, so a later failure restores the original ACLs atomically.
create temporary table leoclean_preserved_database_connect (
role_name name not null,
database_name name not null,
primary key (role_name, database_name)
) on commit drop;
insert into pg_temp.leoclean_preserved_database_connect (role_name, database_name)
select role_row.rolname, database_row.datname
from pg_catalog.pg_roles role_row
cross join pg_catalog.pg_database database_row
where role_row.rolname not in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
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)
from pg_catalog.pg_database database_row
\gexec
select pg_catalog.format(
'revoke all privileges on database %I from leoclean_kb_runtime, leoclean_kb_stage_owner',
database_row.datname
)
from pg_catalog.pg_database database_row
\gexec
select pg_catalog.format(
'grant connect on database %I to %I',
preserved.database_name,
preserved.role_name
)
from pg_temp.leoclean_preserved_database_connect preserved
order by preserved.database_name, preserved.role_name
\gexec
grant connect on database teleo_canonical to leoclean_kb_runtime;
-- The deploy principal needs temporary ownership authority to replace an -- The deploy principal needs temporary ownership authority to replace an
-- existing function on idempotent reruns. This membership is transaction-local -- existing function on idempotent reruns. This membership is transaction-local
-- and is revoked before verification/commit. -- and is revoked before verification/commit.
@ -95,6 +203,114 @@ revoke all privileges on all functions in schema public, kb_stage
revoke all privileges on all procedures in schema public, kb_stage revoke all privileges on all procedures in schema public, kb_stage
from leoclean_kb_runtime, leoclean_kb_stage_owner; from leoclean_kb_runtime, leoclean_kb_stage_owner;
-- Built-in defaults also grant PUBLIC EXECUTE on newly created application
-- routines. Preserve every existing non-scoped role's effective access,
-- remove PUBLIC/scoped execution from the complete application routine set,
-- and restore the inventory explicitly. The reviewed staging function is
-- normalized again after CREATE OR REPLACE below.
create temporary table leoclean_preserved_app_routine_execute (
role_name name not null,
function_oid oid not null,
primary key (role_name, function_oid)
) on commit drop;
insert into pg_temp.leoclean_preserved_app_routine_execute (role_name, function_oid)
select role_row.rolname, function_row.oid
from pg_catalog.pg_roles role_row
cross join pg_catalog.pg_proc function_row
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
where role_row.rolname not in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
and namespace.nspname in ('public', 'kb_stage')
and pg_catalog.has_function_privilege(role_row.oid, function_row.oid, 'EXECUTE');
select pg_catalog.format(
'revoke execute on %s %I.%I(%s) from public, leoclean_kb_runtime, leoclean_kb_stage_owner',
case when function_row.prokind = 'p' then 'procedure' else 'function' end,
namespace.nspname,
function_row.proname,
pg_catalog.pg_get_function_identity_arguments(function_row.oid)
)
from pg_catalog.pg_proc function_row
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
where namespace.nspname in ('public', 'kb_stage')
\gexec
select pg_catalog.format(
'grant execute on %s %I.%I(%s) to %I',
case when function_row.prokind = 'p' then 'procedure' else 'function' end,
namespace.nspname,
function_row.proname,
pg_catalog.pg_get_function_identity_arguments(function_row.oid),
preserved.role_name
)
from pg_temp.leoclean_preserved_app_routine_execute preserved
join pg_catalog.pg_proc function_row on function_row.oid = preserved.function_oid
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
order by namespace.nspname, function_row.proname, preserved.role_name
\gexec
select pg_catalog.format(
'revoke all privileges on large object %s from leoclean_kb_runtime, leoclean_kb_stage_owner',
metadata.oid
)
from pg_catalog.pg_largeobject_metadata metadata
\gexec
-- Default PostgreSQL grants EXECUTE on built-in large-object mutators to
-- PUBLIC, which would let the runtime persist data despite all relation ACLs.
-- Snapshot and explicitly preserve every existing non-scoped role's effective
-- access, then remove PUBLIC and both scoped roles from every mutator overload.
create temporary table leoclean_preserved_lo_execute (
role_name name not null,
function_oid oid not null,
primary key (role_name, function_oid)
) on commit drop;
insert into pg_temp.leoclean_preserved_lo_execute (role_name, function_oid)
select role_row.rolname, function_row.oid
from pg_catalog.pg_roles role_row
cross join pg_catalog.pg_proc function_row
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
where role_row.rolname not in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
and namespace.nspname = 'pg_catalog'
and function_row.proname in (
'lo_creat', 'lo_create', 'lo_export', 'lo_from_bytea', 'lo_import',
'lo_open', 'lo_put', 'lo_truncate', 'lo_truncate64', 'lo_unlink', 'lowrite'
)
and pg_catalog.has_function_privilege(role_row.oid, function_row.oid, 'EXECUTE');
select pg_catalog.format(
'revoke execute on function pg_catalog.%I(%s) from public, leoclean_kb_runtime, leoclean_kb_stage_owner',
function_row.proname,
pg_catalog.pg_get_function_identity_arguments(function_row.oid)
)
from pg_catalog.pg_proc function_row
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
where namespace.nspname = 'pg_catalog'
and function_row.proname in (
'lo_creat', 'lo_create', 'lo_export', 'lo_from_bytea', 'lo_import',
'lo_open', 'lo_put', 'lo_truncate', 'lo_truncate64', 'lo_unlink', 'lowrite'
)
\gexec
select pg_catalog.format(
'grant execute on function pg_catalog.%I(%s) to %I',
function_row.proname,
pg_catalog.pg_get_function_identity_arguments(function_row.oid),
preserved.role_name
)
from pg_temp.leoclean_preserved_lo_execute preserved
join pg_catalog.pg_proc function_row on function_row.oid = preserved.function_oid
order by function_row.proname, preserved.role_name
\gexec
select pg_catalog.format(
'revoke all privileges on parameter %I from leoclean_kb_runtime, leoclean_kb_stage_owner',
parameter_acl.parname
)
from pg_catalog.pg_parameter_acl parameter_acl
\gexec
-- Table-level REVOKE does not clear column ACLs. Clear every column ACL for both -- Table-level REVOKE does not clear column ACLs. Clear every column ACL for both
-- principals before rebuilding the exact allowlist below. -- principals before rebuilding the exact allowlist below.
select format( select format(
@ -310,6 +526,264 @@ begin
raise exception 'scoped Leo roles retain role memberships: %', unexpected; raise exception 'scoped Leo roles retain role memberships: %', unexpected;
end if; end if;
if not exists (
select 1
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
where namespace.nspname = 'kb_stage'
and relation.relname = 'kb_proposals'
and relation.relkind = 'r'
and relation.relpersistence = 'p'
and not relation.relrowsecurity
and not relation.relforcerowsecurity
) then
raise exception 'kb_stage.kb_proposals is not the reviewed permanent base table';
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I:%s', namespace.nspname, relation.relname, relation.relkind),
', '
order by namespace.nspname, relation.relname
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
where (namespace.nspname, relation.relname) in (
('public', 'agents'), ('public', 'claims'), ('public', 'claim_evidence'),
('public', 'claim_edges'), ('public', 'sources'), ('public', 'personas'),
('public', 'strategies'), ('public', 'beliefs'), ('public', 'blindspots'),
('public', 'agent_roles'), ('public', 'peer_models'), ('public', 'behavioral_rules'),
('public', 'contributor_rules'), ('public', 'reasoning_tools'),
('public', 'governance_gates'), ('kb_stage', 'kb_proposals')
)
and relation.relkind <> 'r';
if unexpected is not null then
raise exception 'Leo read allowlist contains a non-base relation: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%s->%s', inheritance.inhparent::pg_catalog.regclass, inheritance.inhrelid::pg_catalog.regclass),
', '
)
into unexpected
from pg_catalog.pg_inherits inheritance
where inheritance.inhparent in (
'public.agents'::pg_catalog.regclass, 'public.claims'::pg_catalog.regclass,
'public.claim_evidence'::pg_catalog.regclass, 'public.claim_edges'::pg_catalog.regclass,
'public.sources'::pg_catalog.regclass, 'public.personas'::pg_catalog.regclass,
'public.strategies'::pg_catalog.regclass, 'public.beliefs'::pg_catalog.regclass,
'public.blindspots'::pg_catalog.regclass, 'public.agent_roles'::pg_catalog.regclass,
'public.peer_models'::pg_catalog.regclass, 'public.behavioral_rules'::pg_catalog.regclass,
'public.contributor_rules'::pg_catalog.regclass, 'public.reasoning_tools'::pg_catalog.regclass,
'public.governance_gates'::pg_catalog.regclass, 'kb_stage.kb_proposals'::pg_catalog.regclass
)
or inheritance.inhrelid in (
'public.agents'::pg_catalog.regclass, 'public.claims'::pg_catalog.regclass,
'public.claim_evidence'::pg_catalog.regclass, 'public.claim_edges'::pg_catalog.regclass,
'public.sources'::pg_catalog.regclass, 'public.personas'::pg_catalog.regclass,
'public.strategies'::pg_catalog.regclass, 'public.beliefs'::pg_catalog.regclass,
'public.blindspots'::pg_catalog.regclass, 'public.agent_roles'::pg_catalog.regclass,
'public.peer_models'::pg_catalog.regclass, 'public.behavioral_rules'::pg_catalog.regclass,
'public.contributor_rules'::pg_catalog.regclass, 'public.reasoning_tools'::pg_catalog.regclass,
'public.governance_gates'::pg_catalog.regclass, 'kb_stage.kb_proposals'::pg_catalog.regclass
);
if unexpected is not null then
raise exception 'Leo read allowlist has inheritance edges: %', unexpected;
end if;
with expected(ordinal, attname, typname, not_null, has_default, collated) as (
values
(1, 'id', 'uuid', true, true, false),
(2, 'proposal_type', 'text', true, false, true),
(3, 'status', 'text', true, true, true),
(4, 'proposed_by_handle', 'text', false, false, true),
(5, 'proposed_by_agent_id', 'uuid', false, false, false),
(6, 'channel', 'text', true, true, true),
(7, 'source_ref', 'text', false, false, true),
(8, 'rationale', 'text', true, false, true),
(9, 'payload', 'jsonb', true, false, false),
(10, 'reviewed_by_handle', 'text', false, false, true),
(11, 'reviewed_by_agent_id', 'uuid', false, false, false),
(12, 'reviewed_at', 'timestamptz', false, false, false),
(13, 'review_note', 'text', false, false, true),
(14, 'applied_by_handle', 'text', false, false, true),
(15, 'applied_by_agent_id', 'uuid', false, false, false),
(16, 'applied_at', 'timestamptz', false, false, false),
(17, 'created_at', 'timestamptz', true, true, false),
(18, 'updated_at', 'timestamptz', true, true, false)
), actual as (
select attribute.attnum::int as ordinal,
attribute.attname,
type_row.typname,
attribute.attnotnull as not_null,
attribute.atthasdef as has_default,
attribute.attidentity,
attribute.attgenerated,
attribute.atttypmod,
type_namespace.nspname as type_namespace,
attribute.attcollation = 'pg_catalog.default'::pg_catalog.regcollation as collated
from pg_catalog.pg_attribute attribute
join pg_catalog.pg_type type_row on type_row.oid = attribute.atttypid
join pg_catalog.pg_namespace type_namespace on type_namespace.oid = type_row.typnamespace
where attribute.attrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
and attribute.attnum > 0
and not attribute.attisdropped
)
select pg_catalog.string_agg(coalesce(expected.attname, actual.attname), ', ' order by coalesce(expected.ordinal, actual.ordinal))
into unexpected
from expected
full join actual using (ordinal, attname)
where expected.ordinal is null
or actual.ordinal is null
or expected.typname is distinct from actual.typname
or expected.not_null is distinct from actual.not_null
or expected.has_default is distinct from actual.has_default
or expected.collated is distinct from actual.collated
or actual.attidentity <> ''
or actual.attgenerated <> ''
or actual.atttypmod <> -1
or actual.type_namespace <> 'pg_catalog';
if unexpected is not null then
raise exception 'kb_stage.kb_proposals column contract drifted: %', unexpected;
end if;
if exists (
select 1
from pg_catalog.pg_trigger trigger_row
where trigger_row.tgrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
and not trigger_row.tgisinternal
) or exists (
select 1
from pg_catalog.pg_rewrite rewrite_row
where rewrite_row.ev_class = 'kb_stage.kb_proposals'::pg_catalog.regclass
) or exists (
select 1
from pg_catalog.pg_policy policy_row
where policy_row.polrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
) or exists (
select 1
from pg_catalog.pg_attribute attribute
where attribute.attrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
and attribute.attnum > 0
and not attribute.attisdropped
and attribute.attgenerated <> ''
) then
raise exception 'kb_stage.kb_proposals has unsafe trigger/rule/RLS/generated topology';
end if;
with expected(attname, expression) as (
values
('id', 'gen_random_uuid()'),
('status', '''pending_review''::text'),
('channel', '''cli''::text'),
('created_at', 'now()'),
('updated_at', 'now()')
), actual as (
select attribute.attname,
pg_catalog.pg_get_expr(default_row.adbin, default_row.adrelid) as expression
from pg_catalog.pg_attribute attribute
join pg_catalog.pg_attrdef default_row
on default_row.adrelid = attribute.attrelid
and default_row.adnum = attribute.attnum
where attribute.attrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
and attribute.attnum > 0
and not attribute.attisdropped
)
select pg_catalog.string_agg(coalesce(expected.attname, actual.attname), ', ')
into unexpected
from expected
full join actual using (attname)
where expected.expression is distinct from actual.expression;
if unexpected is not null then
raise exception 'kb_stage.kb_proposals default topology drifted: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%s:%s', dependency.refclassid::pg_catalog.regclass, dependency.refobjid),
', '
)
into unexpected
from pg_catalog.pg_attrdef default_row
join pg_catalog.pg_depend dependency
on dependency.classid = 'pg_catalog.pg_attrdef'::pg_catalog.regclass
and dependency.objid = default_row.oid
where default_row.adrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
and (
dependency.refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass
and dependency.refobjid not in (
'pg_catalog.gen_random_uuid()'::pg_catalog.regprocedure,
'pg_catalog.now()'::pg_catalog.regprocedure
)
or dependency.refclassid = 'pg_catalog.pg_type'::pg_catalog.regclass
and not exists (
select 1
from pg_catalog.pg_type type_row
join pg_catalog.pg_namespace namespace on namespace.oid = type_row.typnamespace
where type_row.oid = dependency.refobjid
and namespace.nspname = 'pg_catalog'
)
or dependency.refclassid = 'pg_catalog.pg_operator'::pg_catalog.regclass
and not exists (
select 1
from pg_catalog.pg_operator operator_row
join pg_catalog.pg_namespace namespace on namespace.oid = operator_row.oprnamespace
where operator_row.oid = dependency.refobjid
and namespace.nspname = 'pg_catalog'
)
or dependency.refclassid = 'pg_catalog.pg_collation'::pg_catalog.regclass
and not exists (
select 1
from pg_catalog.pg_collation collation_row
join pg_catalog.pg_namespace namespace on namespace.oid = collation_row.collnamespace
where collation_row.oid = dependency.refobjid
and namespace.nspname = 'pg_catalog'
)
);
if unexpected is not null then
raise exception 'kb_stage.kb_proposals defaults depend on unreviewed objects: %', unexpected;
end if;
if (
select pg_catalog.count(*) <> 6
or pg_catalog.count(*) filter (
where (constraint_row.conname, constraint_row.contype) in (
('kb_proposals_pkey', 'p'),
('kb_proposals_proposal_type_check', 'c'),
('kb_proposals_status_check', 'c'),
('kb_proposals_applied_by_agent_id_fkey', 'f'),
('kb_proposals_proposed_by_agent_id_fkey', 'f'),
('kb_proposals_reviewed_by_agent_id_fkey', 'f')
)
) <> 6
from pg_catalog.pg_constraint constraint_row
where constraint_row.conrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
) or exists (
select 1
from pg_catalog.pg_constraint constraint_row
join pg_catalog.pg_depend dependency
on dependency.classid = 'pg_catalog.pg_constraint'::pg_catalog.regclass
and dependency.objid = constraint_row.oid
and dependency.refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass
join pg_catalog.pg_proc function_row on function_row.oid = dependency.refobjid
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
where constraint_row.conrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
and namespace.nspname <> 'pg_catalog'
) or exists (
select 1
from pg_catalog.pg_index index_row
where index_row.indrelid = 'kb_stage.kb_proposals'::pg_catalog.regclass
and (
index_row.indexprs is not null
or (
index_row.indpred is not null
and pg_catalog.pg_get_expr(index_row.indpred, index_row.indrelid)
<> '(proposed_by_handle IS NOT NULL)'
)
)
) then
raise exception 'kb_stage.kb_proposals constraint/index topology drifted';
end if;
select pg_catalog.string_agg( select pg_catalog.string_agg(
pg_catalog.format( pg_catalog.format(
'%I:%s:%s:grantable=%s', '%I:%s:%s:grantable=%s',
@ -330,8 +804,7 @@ begin
) )
) as acl ) as acl
join pg_catalog.pg_roles grantee on grantee.oid = acl.grantee join pg_catalog.pg_roles grantee on grantee.oid = acl.grantee
where database_row.datname in ('teleo_canonical', 'teleo_kb') where grantee.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
and grantee.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
and not ( and not (
database_row.datname = 'teleo_canonical' database_row.datname = 'teleo_canonical'
and grantee.rolname = 'leoclean_kb_runtime' and grantee.rolname = 'leoclean_kb_runtime'
@ -342,11 +815,86 @@ begin
raise exception 'scoped Leo roles retain unexpected direct database ACLs: %', unexpected; raise exception 'scoped Leo roles retain unexpected direct database ACLs: %', unexpected;
end if; end if;
if has_database_privilege('leoclean_kb_runtime', 'teleo_canonical', 'CREATE') select pg_catalog.string_agg(database_row.datname, ', ' order by database_row.datname)
or has_database_privilege('leoclean_kb_runtime', 'teleo_kb', 'CREATE') into unexpected
or has_database_privilege('leoclean_kb_stage_owner', 'teleo_canonical', 'CREATE') from pg_catalog.pg_database database_row
or has_database_privilege('leoclean_kb_stage_owner', 'teleo_kb', 'CREATE') then where database_row.datname <> 'teleo_canonical'
raise exception 'a scoped Leo role unexpectedly has database CREATE'; and has_database_privilege('leoclean_kb_runtime', database_row.oid, 'CONNECT');
if unexpected is not null then
raise exception 'runtime CONNECT reaches a noncanonical database: %', unexpected;
end if;
select pg_catalog.string_agg(database_row.datname, ', ' order by database_row.datname)
into unexpected
from pg_catalog.pg_database database_row
where has_database_privilege('leoclean_kb_stage_owner', database_row.oid, 'CONNECT');
if unexpected is not null then
raise exception 'stage owner CONNECT reaches a database: %', unexpected;
end if;
select pg_catalog.string_agg(database_row.datname, ', ' order by database_row.datname)
into unexpected
from pg_catalog.pg_database database_row
cross join lateral pg_catalog.aclexplode(
coalesce(database_row.datacl, pg_catalog.acldefault('d', database_row.datdba))
) acl
where acl.grantee = 0
and acl.privilege_type = 'CONNECT';
if unexpected is not null then
raise exception 'PUBLIC retains database CONNECT: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%s:%s', setting_row.setdatabase, setting_row.setconfig),
', '
)
into unexpected
from pg_catalog.pg_db_role_setting setting_row
where setting_row.setrole in (runtime_oid, owner_oid)
and not (
setting_row.setrole = runtime_oid
and setting_row.setdatabase = (
select database_row.oid
from pg_catalog.pg_database database_row
where database_row.datname = 'teleo_canonical'
)
and pg_catalog.cardinality(setting_row.setconfig) = 3
and setting_row.setconfig @> array[
'search_path=pg_catalog, public, kb_stage',
'statement_timeout=15s',
'lock_timeout=2s'
]::text[]
);
if unexpected is not null or not exists (
select 1
from pg_catalog.pg_db_role_setting setting_row
where setting_row.setrole = runtime_oid
and setting_row.setdatabase = (
select database_row.oid
from pg_catalog.pg_database database_row
where database_row.datname = 'teleo_canonical'
)
and pg_catalog.cardinality(setting_row.setconfig) = 3
and setting_row.setconfig @> array[
'search_path=pg_catalog, public, kb_stage',
'statement_timeout=15s',
'lock_timeout=2s'
]::text[]
) then
raise exception 'scoped Leo role settings differ from the reviewed contract';
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I:%s', database_row.datname, scoped_role.name),
', '
order by database_row.datname, scoped_role.name
)
into unexpected
from pg_catalog.pg_database database_row
cross join (values ('leoclean_kb_runtime'), ('leoclean_kb_stage_owner')) scoped_role(name)
where has_database_privilege(scoped_role.name, database_row.oid, 'CREATE');
if unexpected is not null then
raise exception 'a scoped Leo role unexpectedly has database CREATE: %', unexpected;
end if; end if;
select pg_catalog.string_agg( select pg_catalog.string_agg(
@ -382,6 +930,75 @@ begin
raise exception 'leoclean_kb_stage_owner owns unexpected database objects: %', unexpected; raise exception 'leoclean_kb_stage_owner owns unexpected database objects: %', unexpected;
end if; end if;
select pg_catalog.string_agg(metadata.oid::text, ', ' order by metadata.oid)
into unexpected
from pg_catalog.pg_largeobject_metadata metadata
where metadata.lomowner in (runtime_oid, owner_oid)
or exists (
select 1
from pg_catalog.aclexplode(
coalesce(metadata.lomacl, pg_catalog.acldefault('L', metadata.lomowner))
) acl
where acl.grantee in (0, runtime_oid, owner_oid)
and acl.privilege_type in ('SELECT', 'UPDATE')
);
if unexpected is not null then
raise exception 'scoped Leo roles retain large-object ownership or ACL reachability: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I(%s)', function_row.proname, pg_catalog.pg_get_function_identity_arguments(function_row.oid)),
', '
)
into unexpected
from pg_catalog.pg_proc function_row
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
where namespace.nspname = 'pg_catalog'
and function_row.proname in (
'lo_creat', 'lo_create', 'lo_export', 'lo_from_bytea', 'lo_import',
'lo_open', 'lo_put', 'lo_truncate', 'lo_truncate64', 'lo_unlink', 'lowrite'
)
and (
has_function_privilege('leoclean_kb_runtime', function_row.oid, 'EXECUTE')
or has_function_privilege('leoclean_kb_stage_owner', function_row.oid, 'EXECUTE')
);
if unexpected is not null then
raise exception 'PUBLIC or inherited large-object mutators remain executable: %', unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I(%s)', function_row.proname, pg_catalog.pg_get_function_identity_arguments(function_row.oid)),
', '
order by function_row.proname, pg_catalog.pg_get_function_identity_arguments(function_row.oid)
)
into unexpected
from pg_catalog.pg_proc function_row
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
cross join lateral pg_catalog.aclexplode(
coalesce(function_row.proacl, pg_catalog.acldefault('f', function_row.proowner))
) acl
where namespace.nspname = 'pg_catalog'
and function_row.proname in (
'lo_creat', 'lo_create', 'lo_export', 'lo_from_bytea', 'lo_import',
'lo_open', 'lo_put', 'lo_truncate', 'lo_truncate64', 'lo_unlink', 'lowrite'
)
and acl.grantee = 0
and acl.privilege_type = 'EXECUTE';
if unexpected is not null then
raise exception 'PUBLIC retains large-object mutator EXECUTE: %', unexpected;
end if;
select pg_catalog.string_agg(parameter_acl.parname, ', ' order by parameter_acl.parname)
into unexpected
from pg_catalog.pg_parameter_acl parameter_acl
where has_parameter_privilege('leoclean_kb_runtime', parameter_acl.parname, 'SET')
or has_parameter_privilege('leoclean_kb_runtime', parameter_acl.parname, 'ALTER SYSTEM')
or has_parameter_privilege('leoclean_kb_stage_owner', parameter_acl.parname, 'SET')
or has_parameter_privilege('leoclean_kb_stage_owner', parameter_acl.parname, 'ALTER SYSTEM');
if unexpected is not null then
raise exception 'scoped Leo roles retain parameter privileges: %', unexpected;
end if;
if has_schema_privilege('leoclean_kb_runtime', 'public', 'CREATE') if has_schema_privilege('leoclean_kb_runtime', 'public', 'CREATE')
or has_schema_privilege('leoclean_kb_runtime', 'kb_stage', 'CREATE') or has_schema_privilege('leoclean_kb_runtime', 'kb_stage', 'CREATE')
or has_schema_privilege('leoclean_kb_stage_owner', 'public', 'CREATE') or has_schema_privilege('leoclean_kb_stage_owner', 'public', 'CREATE')
@ -698,137 +1315,9 @@ begin
end end
$verification$; $verification$;
commit; -- LOGIN is the last state change in the same transaction as every database ACL,
-- canonical privilege, large-object, and topology assertion. Any failure rolls
-- The legacy restore database is not an audit fallback for the scoped runtime. -- back the complete privilege migration and leaves the role NOLOGIN.
-- Strip direct ACLs from both scoped roles and fail if PUBLIC/group grants still
-- make any teleo_restore relation reachable.
\connect teleo_kb
begin;
select format('revoke all on schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format('revoke all privileges on all tables in schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format('revoke all privileges on all sequences in schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format('revoke all privileges on all functions in schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format('revoke all privileges on all procedures in schema %I from leoclean_kb_runtime, leoclean_kb_stage_owner', namespace.nspname)
from pg_catalog.pg_namespace namespace
where namespace.nspname = 'teleo_restore'
\gexec
select format(
'revoke all privileges (%s) on table %I.%I from %I',
pg_catalog.string_agg(pg_catalog.quote_ident(attribute.attname), ', ' order by attribute.attnum),
namespace.nspname,
relation.relname,
target.rolname
)
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
cross join (
values ('leoclean_kb_runtime'), ('leoclean_kb_stage_owner')
) as target(rolname)
where namespace.nspname = 'teleo_restore'
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
group by namespace.nspname, relation.relname, target.rolname
\gexec
do $audit_verification$
declare
unexpected text;
scoped_role text;
begin
foreach scoped_role in array array['leoclean_kb_runtime', 'leoclean_kb_stage_owner'] loop
if to_regnamespace('teleo_restore') is not null
and (has_schema_privilege(scoped_role, 'teleo_restore', 'USAGE')
or has_schema_privilege(scoped_role, 'teleo_restore', 'CREATE')) then
raise exception '% unexpectedly has teleo_restore schema access', scoped_role;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I:%s', namespace.nspname, relation.relname, privilege.name),
', '
order by relation.relname, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
cross join (
values ('SELECT'), ('INSERT'), ('UPDATE'), ('DELETE'), ('TRUNCATE'), ('REFERENCES'), ('TRIGGER')
) privilege(name)
where namespace.nspname = 'teleo_restore'
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and has_table_privilege(scoped_role, relation.oid, privilege.name);
if unexpected is not null then
raise exception '% unexpectedly has teleo_restore table access: %', scoped_role, unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I.%I:%s', namespace.nspname, relation.relname, attribute.attname, privilege.name),
', '
order by relation.relname, attribute.attnum, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
cross join (values ('SELECT'), ('INSERT'), ('UPDATE'), ('REFERENCES')) privilege(name)
where namespace.nspname = 'teleo_restore'
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
and has_column_privilege(scoped_role, relation.oid, attribute.attnum, privilege.name);
if unexpected is not null then
raise exception '% unexpectedly has teleo_restore column access: %', scoped_role, unexpected;
end if;
select pg_catalog.string_agg(
pg_catalog.format('%I.%I:%s', namespace.nspname, relation.relname, privilege.name),
', '
order by relation.relname, privilege.name
)
into unexpected
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
cross join (values ('USAGE'), ('SELECT'), ('UPDATE')) privilege(name)
where namespace.nspname = 'teleo_restore'
and relation.relkind = 'S'
and has_sequence_privilege(scoped_role, relation.oid, privilege.name);
if unexpected is not null then
raise exception '% unexpectedly has teleo_restore sequence access: %', scoped_role, unexpected;
end if;
end loop;
end
$audit_verification$;
commit;
\connect teleo_canonical
-- LOGIN is the last state change. If this verification fails, the transaction
-- rolls back and the role remains NOLOGIN while prior least-privilege grants
-- stay inert for direct authentication.
begin;
alter role leoclean_kb_runtime alter role leoclean_kb_runtime
with login nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit 8; with login nosuperuser nocreatedb nocreaterole noinherit noreplication nobypassrls connection limit 8;
@ -838,14 +1327,16 @@ begin
select 1 select 1
from pg_catalog.pg_roles role_row from pg_catalog.pg_roles role_row
where role_row.rolname = 'leoclean_kb_runtime' where role_row.rolname = 'leoclean_kb_runtime'
and (not role_row.rolcanlogin and (
or role_row.rolsuper not role_row.rolcanlogin
or role_row.rolcreatedb or role_row.rolsuper
or role_row.rolcreaterole or role_row.rolcreatedb
or role_row.rolinherit or role_row.rolcreaterole
or role_row.rolreplication or role_row.rolinherit
or role_row.rolbypassrls or role_row.rolreplication
or role_row.rolconnlimit <> 8) or role_row.rolbypassrls
or role_row.rolconnlimit <> 8
)
) then ) then
raise exception 'leoclean_kb_runtime final login attributes are unsafe'; raise exception 'leoclean_kb_runtime final login attributes are unsafe';
end if; end if;
@ -853,32 +1344,3 @@ end
$login_verification$; $login_verification$;
commit; commit;
select jsonb_build_object(
'role', rolname,
'superuser', rolsuper,
'create_db', rolcreatedb,
'create_role', rolcreaterole,
'inherit', rolinherit,
'replication', rolreplication,
'bypass_rls', rolbypassrls,
'has_role_memberships', exists (
select 1
from pg_catalog.pg_auth_members membership
where membership.roleid = pg_catalog.pg_roles.oid
or membership.member = pg_catalog.pg_roles.oid
),
'can_select_claims', has_table_privilege(rolname, 'public.claims', 'SELECT'),
'can_select_proposals', has_table_privilege(rolname, 'kb_stage.kb_proposals', 'SELECT'),
'can_insert_proposals_directly', has_table_privilege(rolname, 'kb_stage.kb_proposals', 'INSERT'),
'can_connect_legacy_database', has_database_privilege(rolname, 'teleo_kb', 'CONNECT'),
'can_create_database_objects', has_database_privilege(rolname, 'teleo_canonical', 'CREATE'),
'can_create_temp_objects', has_database_privilege(rolname, 'teleo_canonical', 'TEMP'),
'can_stage_proposal', has_function_privilege(
rolname,
'kb_stage.stage_leoclean_proposal(text,text,text,text,jsonb)',
'EXECUTE'
)
)::text
from pg_catalog.pg_roles
where rolname = 'leoclean_kb_runtime';

View file

@ -0,0 +1,150 @@
#!/bin/bash
# Provision the scoped GCP staging role without placing its cleartext password
# in SQL text, argv, command history, or PostgreSQL statement logs.
set +x
set -euo pipefail
umask 077
ulimit -c 0
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
runtime_password=${TELEO_LEOCLEAN_DB_PASSWORD-}
administrator_password=${PGPASSWORD-}
unset TELEO_LEOCLEAN_DB_PASSWORD PGPASSWORD
script_path=${BASH_SOURCE[0]}
case "$script_path" in
*/*) script_directory=${script_path%/*} ;;
*) script_directory=. ;;
esac
SCRIPT_DIR="$(cd -- "$script_directory" && pwd -P)"
SQL_FILE="$SCRIPT_DIR/gcp_leoclean_runtime_role.sql"
SERVER_CA_FILE="$SCRIPT_DIR/gcp-teleo-pgvector-standby-server-ca.pem"
PSQL_BIN=/usr/bin/psql
SETSID_BIN=/usr/bin/setsid
ENV_BIN=/usr/bin/env
SHA256SUM_BIN=/usr/bin/sha256sum
SERVER_CA_SHA256=80701e768f0e1f6b9d621aa0b53f6e851daaa276c6d9a8e51a300fbc015539cb
assert_root_owned_nonwritable() {
local target="$1" state uid mode
state="$(stat -Lc '%u:%a' -- "$target")"
uid=${state%%:*}
mode=${state#*:}
if [ "$uid" != 0 ] || (( (8#$mode & 8#022) != 0 )); then
echo "Refusing an untrusted PostgreSQL executable path." >&2
return 1
fi
}
assert_trusted_executable() {
local requested="$1" resolved current
[ -x "$requested" ] || {
echo "Required PostgreSQL client is unavailable." >&2
return 1
}
resolved="$(readlink -f -- "$requested")"
[ -n "$resolved" ] && [ -f "$resolved" ] && [ -x "$resolved" ] || {
echo "Required PostgreSQL client did not resolve to an executable file." >&2
return 1
}
assert_root_owned_nonwritable "$requested"
current="$(dirname "$requested")"
while :; do
assert_root_owned_nonwritable "$current"
[ "$current" = / ] && break
current="$(dirname "$current")"
done
current="$resolved"
while :; do
assert_root_owned_nonwritable "$current"
[ "$current" = / ] && break
current="$(dirname "$current")"
done
}
if [ ! -f "$SQL_FILE" ] || [ -L "$SQL_FILE" ] || [ ! -f "$SERVER_CA_FILE" ] || [ -L "$SERVER_CA_FILE" ]; then
echo "Reviewed GCP runtime-role SQL is unavailable." >&2
exit 66
fi
assert_trusted_executable "$PSQL_BIN"
assert_trusted_executable "$SETSID_BIN"
assert_trusted_executable "$ENV_BIN"
assert_trusted_executable "$SHA256SUM_BIN"
if [ "$("$SHA256SUM_BIN" "$SERVER_CA_FILE")" != "$SERVER_CA_SHA256 $SERVER_CA_FILE" ]; then
echo "Reviewed Cloud SQL server CA does not match the pinned certificate." >&2
exit 66
fi
if [ -z "$runtime_password" ] || [ "${#runtime_password}" -gt 4096 ]; then
echo "TELEO_LEOCLEAN_DB_PASSWORD must contain a bounded nonempty value." >&2
exit 64
fi
case "$runtime_password" in
*$'\n'*|*$'\r'*)
echo "TELEO_LEOCLEAN_DB_PASSWORD must not contain line breaks." >&2
exit 64
;;
esac
if [ -z "$administrator_password" ] || [ "${#administrator_password}" -gt 4096 ]; then
echo "PGPASSWORD must contain a bounded nonempty administrator credential." >&2
exit 64
fi
case "$administrator_password" in
*$'\n'*|*$'\r'*)
echo "PGPASSWORD must not contain line breaks." >&2
exit 64
;;
esac
cleanup_password() {
runtime_password=
administrator_password=
unset runtime_password administrator_password
}
provision_pid=
terminate_provisioning() {
local exit_status="$1"
trap - INT TERM
if [[ "$provision_pid" =~ ^[1-9][0-9]*$ ]]; then
kill -TERM -- "-$provision_pid" 2>/dev/null || kill -TERM -- "$provision_pid" 2>/dev/null || true
wait "$provision_pid" 2>/dev/null || true
provision_pid=
fi
cleanup_password
exit "$exit_status"
}
trap cleanup_password EXIT
trap 'terminate_provisioning 130' INT
trap 'terminate_provisioning 143' TERM
# \password encrypts client-side. --no-password ensures an administrator-login
# prompt cannot consume either of the two runtime-password records from stdin.
# setsid detaches psql from any controlling terminal without --fork, keeping
# its PID as the session/process-group leader so an interruption can terminate
# and reap the complete credential-bearing child before this wrapper returns.
{
printf '%s\n' "$runtime_password"
printf '%s\n' "$runtime_password"
} | "$ENV_BIN" -i \
PATH="$PATH" \
LANG=C LC_ALL=C \
PYTHONNOUSERSITE=1 PYTHONSAFEPATH=1 \
PGHOST=10.61.0.3 \
PGPORT=5432 \
PGDATABASE=teleo_canonical \
PGUSER=postgres \
PGPASSWORD="$administrator_password" \
PGSSLMODE=verify-ca \
PGSSLROOTCERT="$SERVER_CA_FILE" \
"$SETSID_BIN" -- "$PSQL_BIN" \
--no-psqlrc \
--no-password \
--set=ON_ERROR_STOP=1 \
--file="$SQL_FILE" &
provision_pid=$!
set +e
wait "$provision_pid"
status=$?
set -e
provision_pid=
exit "$status"

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/python3 -I
"""Retain a sanitized least-privilege receipt for the GCP Leo runtime role. """Retain a sanitized least-privilege receipt for the GCP Leo runtime role.
This verifier is intentionally service-independent. It must run on the private This verifier is intentionally service-independent. It must run on the private
@ -19,8 +19,8 @@ import re
import stat import stat
import subprocess import subprocess
import sys import sys
import tempfile
from collections.abc import Callable, Mapping from collections.abc import Callable, Mapping
from contextlib import nullcontext
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
@ -29,8 +29,10 @@ from typing import Any
PROJECT_ID = "teleo-501523" PROJECT_ID = "teleo-501523"
PRIVATE_CLOUDSQL_HOST = "10.61.0.3" PRIVATE_CLOUDSQL_HOST = "10.61.0.3"
PRIVATE_CLOUDSQL_PORT = 5432 PRIVATE_CLOUDSQL_PORT = 5432
EXPECTED_SYSTEM_IDENTIFIER = "7659718422914359312"
CANONICAL_DATABASE = "teleo_canonical" CANONICAL_DATABASE = "teleo_canonical"
LEGACY_DATABASE = "teleo_kb" LEGACY_DATABASE = "teleo_kb"
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"
RUNTIME_UNIX_USER = "teleo" RUNTIME_UNIX_USER = "teleo"
@ -38,6 +40,9 @@ SCOPED_PASSWORD_SECRET = "gcp-teleo-pgvector-standby-leoclean-kb-runtime-passwor
ADMINISTRATOR_PASSWORD_SECRET = "gcp-teleo-pgvector-standby-postgres-password" ADMINISTRATOR_PASSWORD_SECRET = "gcp-teleo-pgvector-standby-postgres-password"
GCLOUD_BIN = "/usr/bin/gcloud" GCLOUD_BIN = "/usr/bin/gcloud"
PSQL_BIN = "/usr/bin/psql" PSQL_BIN = "/usr/bin/psql"
SERVER_CA_PATH = Path("/usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem")
RUNTIME_CLOUDSDK_CONFIG = Path("/usr/local/libexec/livingip/leoclean-kb/gcloud-config")
SERVER_CA_SHA256 = "80701e768f0e1f6b9d621aa0b53f6e851daaa276c6d9a8e51a300fbc015539cb"
COMMAND_TIMEOUT_SECONDS = 30 COMMAND_TIMEOUT_SECONDS = 30
CHILD_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" CHILD_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
RUN_ID_RE = re.compile(r"[a-z0-9][a-z0-9-]{6,62}[a-z0-9]\Z") RUN_ID_RE = re.compile(r"[a-z0-9][a-z0-9-]{6,62}[a-z0-9]\Z")
@ -195,8 +200,14 @@ CATALOG_ZERO_COUNT_FIELDS: tuple[str, ...] = (
"column_dml_grants", "column_dml_grants",
"database_create_grants", "database_create_grants",
"missing_allowed_table_selects", "missing_allowed_table_selects",
"large_object_acl_privileges",
"large_object_mutation_routine_execute",
"public_large_object_mutation_routine_execute",
"other_scoped_backends",
"owned_database_objects", "owned_database_objects",
"owned_large_objects",
"owner_database_create_grants", "owner_database_create_grants",
"owner_database_connect_grants",
"owner_direct_database_acl_entries", "owner_direct_database_acl_entries",
"owner_missing_agent_select_columns", "owner_missing_agent_select_columns",
"owner_missing_proposal_insert_columns", "owner_missing_proposal_insert_columns",
@ -210,38 +221,44 @@ CATALOG_ZERO_COUNT_FIELDS: tuple[str, ...] = (
"owner_unexpected_schema_usage", "owner_unexpected_schema_usage",
"owner_unexpected_table_selects", "owner_unexpected_table_selects",
"schema_create_grants", "schema_create_grants",
"parameter_privileges",
"public_database_connect_grants",
"scoped_prepared_xacts",
"sequence_grants", "sequence_grants",
"table_dml_grants", "table_dml_grants",
"unexpected_column_selects", "unexpected_column_selects",
"unexpected_direct_database_acl_entries", "unexpected_direct_database_acl_entries",
"unexpected_connectable_databases",
"unexpected_routine_execute", "unexpected_routine_execute",
"unexpected_role_settings",
"unexpected_schema_usage", "unexpected_schema_usage",
"unexpected_security_definer_execute", "unexpected_security_definer_execute",
"unexpected_table_selects", "unexpected_table_selects",
"unsafe_allowed_relation_kinds",
"unsafe_allowed_relation_inheritance",
"unsafe_proposal_column_contract",
"unsafe_proposal_default_dependencies",
"unsafe_proposal_defaults",
"unsafe_proposal_constraint_dependencies",
"unsafe_proposal_index_expressions",
"unsafe_proposal_policies",
"unsafe_proposal_rewrite_rules",
"unsafe_proposal_triggers",
"unsafe_proposal_generated_columns",
) )
CATALOG_TRUE_FIELDS: tuple[str, ...] = ( CATALOG_TRUE_FIELDS: tuple[str, ...] = (
"canonical_connect", "canonical_connect",
"owner_grant_contract", "owner_grant_contract",
"owner_schema_usage", "owner_schema_usage",
"proposal_table_contract",
"proposal_constraint_shape",
"runtime_connect_acl_exact", "runtime_connect_acl_exact",
"runtime_schema_usage", "runtime_schema_usage",
"runtime_setting_contract",
"effective_setting_contract",
) )
LEGACY_CATALOG_ZERO_COUNT_FIELDS: tuple[str, ...] = (
"owner_column_privileges",
"owner_routine_privileges",
"owner_schema_privileges",
"owner_sequence_privileges",
"owner_table_privileges",
"runtime_column_privileges",
"runtime_routine_privileges",
"runtime_schema_privileges",
"runtime_sequence_privileges",
"runtime_table_privileges",
)
@dataclass(frozen=True) @dataclass(frozen=True)
class CommandResult: class CommandResult:
returncode: int returncode: int
@ -250,6 +267,7 @@ class CommandResult:
Runner = Callable[[list[str], Mapping[str, str], int, bool], CommandResult] Runner = Callable[[list[str], Mapping[str, str], int, bool], CommandResult]
DependencyValidator = Callable[[], None]
@dataclass(frozen=True) @dataclass(frozen=True)
@ -258,6 +276,7 @@ class NegativeCheck:
database: str database: str
sql: str sql: str
expected_sqlstate: str expected_sqlstate: str
expected_connection_denial: bool = False
class VerificationError(RuntimeError): class VerificationError(RuntimeError):
@ -328,14 +347,110 @@ def _base_child_environment(_source: Mapping[str, str]) -> dict[str, str]:
"LANG": "C", "LANG": "C",
"LC_ALL": "C", "LC_ALL": "C",
"PATH": CHILD_PATH, "PATH": CHILD_PATH,
"PYTHONNOUSERSITE": "1",
"PYTHONSAFEPATH": "1",
} }
def _path_and_parents(path: Path) -> tuple[Path, ...]:
resolved = path if path.is_absolute() else path.absolute()
return (resolved, *resolved.parents)
def _assert_root_owned_nonwritable(path: Path, *, follow_symlinks: bool = True) -> None:
try:
metadata = path.stat() if follow_symlinks else path.lstat()
except OSError:
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies") from None
if metadata.st_uid != 0 or metadata.st_mode & (stat.S_IWGRP | stat.S_IWOTH):
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies")
if os.geteuid() != 0 and os.access(path, os.W_OK):
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies")
def _assert_trusted_executable(raw_path: str) -> None:
requested = Path(raw_path)
if not requested.is_absolute():
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies")
try:
resolved = requested.resolve(strict=True)
metadata = resolved.stat()
except OSError:
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies") from None
if not stat.S_ISREG(metadata.st_mode) or not os.access(resolved, os.X_OK):
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies")
try:
requested_metadata = requested.lstat()
except OSError:
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies") from None
if requested_metadata.st_uid != 0 or (
not stat.S_ISLNK(requested_metadata.st_mode)
and requested_metadata.st_mode & (stat.S_IWGRP | stat.S_IWOTH)
):
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies")
for candidate in (*_path_and_parents(requested.parent), *_path_and_parents(resolved)):
_assert_root_owned_nonwritable(candidate)
def validate_runtime_dependencies() -> None:
_assert_trusted_executable(GCLOUD_BIN)
_assert_trusted_executable(PSQL_BIN)
runtime_cloudsdk_config_path()
server_ca = runtime_server_ca_path()
for candidate in _path_and_parents(server_ca):
_assert_root_owned_nonwritable(candidate)
try:
certificate = server_ca.read_bytes()
except OSError:
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies") from None
if hashlib.sha256(certificate).hexdigest() != SERVER_CA_SHA256:
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies")
def runtime_server_ca_path() -> Path:
raw = os.environ.get("TELEO_GCP_PREFLIGHT_SSL_ROOT_CERT", str(SERVER_CA_PATH))
candidate = Path(raw)
try:
if not candidate.is_absolute() or candidate.is_symlink():
raise OSError
resolved = candidate.resolve(strict=True)
except OSError:
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies") from None
if resolved != candidate or not resolved.is_file():
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies")
return resolved
def selected_server_ca_path() -> Path:
return Path(os.environ.get("TELEO_GCP_PREFLIGHT_SSL_ROOT_CERT", str(SERVER_CA_PATH)))
def runtime_cloudsdk_config_path() -> Path:
raw = os.environ.get("TELEO_GCP_PREFLIGHT_CLOUDSDK_CONFIG", str(RUNTIME_CLOUDSDK_CONFIG))
candidate = Path(raw)
try:
if not candidate.is_absolute() or candidate.is_symlink():
raise OSError
resolved = candidate.resolve(strict=True)
if resolved != candidate or not resolved.is_dir() or any(resolved.iterdir()):
raise OSError
except OSError:
raise VerificationError("runtime_dependency_untrusted", "runtime_dependencies") from None
for part in _path_and_parents(resolved):
_assert_root_owned_nonwritable(part)
return resolved
def selected_cloudsdk_config_path() -> Path:
return Path(os.environ.get("TELEO_GCP_PREFLIGHT_CLOUDSDK_CONFIG", str(RUNTIME_CLOUDSDK_CONFIG)))
def _gcloud_environment(source: Mapping[str, str], config_dir: Path) -> dict[str, str]: def _gcloud_environment(source: Mapping[str, str], config_dir: Path) -> dict[str, str]:
env = _base_child_environment(source) env = _base_child_environment(source)
env.update( env.update(
{ {
"CLOUDSDK_CONFIG": str(config_dir), "CLOUDSDK_CONFIG": str(config_dir),
"CLOUDSDK_CORE_DISABLE_FILE_LOGGING": "true",
"CLOUDSDK_CORE_DISABLE_PROMPTS": "1", "CLOUDSDK_CORE_DISABLE_PROMPTS": "1",
"CLOUDSDK_CORE_PROJECT": PROJECT_ID, "CLOUDSDK_CORE_PROJECT": PROJECT_ID,
} }
@ -345,7 +460,13 @@ def _gcloud_environment(source: Mapping[str, str], config_dir: Path) -> dict[str
def _psql_environment(source: Mapping[str, str], password: str) -> dict[str, str]: def _psql_environment(source: Mapping[str, str], password: str) -> dict[str, str]:
env = _base_child_environment(source) env = _base_child_environment(source)
env["PGPASSWORD"] = password env.update(
{
"PGPASSWORD": password,
"PGSSLMODE": "verify-ca",
"PGSSLROOTCERT": str(selected_server_ca_path()),
}
)
return env return env
@ -424,7 +545,7 @@ def _assert_administrator_secret_denied(*, env: Mapping[str, str], runner: Runne
def _conninfo(database: str) -> str: def _conninfo(database: str) -> str:
return ( return (
f"host={PRIVATE_CLOUDSQL_HOST} port={PRIVATE_CLOUDSQL_PORT} dbname={database} " f"host={PRIVATE_CLOUDSQL_HOST} port={PRIVATE_CLOUDSQL_PORT} dbname={database} "
f"user={RUNTIME_DATABASE_ROLE} sslmode=require connect_timeout=10 " f"user={RUNTIME_DATABASE_ROLE} sslmode=verify-ca sslrootcert={selected_server_ca_path()} connect_timeout=10 "
"application_name=leoclean_runtime_permission_verifier" "application_name=leoclean_runtime_permission_verifier"
) )
@ -515,7 +636,8 @@ select pg_catalog.jsonb_build_object(
'ssl_version', coalesce( 'ssl_version', coalesce(
(select version from pg_catalog.pg_stat_ssl where pid = pg_catalog.pg_backend_pid()), (select version from pg_catalog.pg_stat_ssl where pid = pg_catalog.pg_backend_pid()),
'' ''
) ),
'system_identifier', (select system_identifier::text from pg_catalog.pg_control_system())
)::text; )::text;
""".strip() """.strip()
@ -689,17 +811,60 @@ with runtime_role as (
), allowed_relation as ( ), allowed_relation as (
select allowed_select.nspname, select allowed_select.nspname,
allowed_select.relname, allowed_select.relname,
relation.oid relation.oid,
relation.relkind
from allowed_select from allowed_select
left join pg_catalog.pg_namespace namespace left join pg_catalog.pg_namespace namespace
on namespace.nspname = allowed_select.nspname on namespace.nspname = allowed_select.nspname
left join pg_catalog.pg_class relation left join pg_catalog.pg_class relation
on relation.relnamespace = namespace.oid on relation.relnamespace = namespace.oid
and relation.relname = allowed_select.relname and relation.relname = allowed_select.relname
and relation.relkind in ('r', 'p', 'v', 'm', 'f')
), owner_insert_column(attname) as ( ), owner_insert_column(attname) as (
values values
{owner_insert_values} {owner_insert_values}
), expected_proposal_column(ordinal, attname, typname, not_null, has_default, collated) as (
values
(1, 'id', 'uuid', true, true, false),
(2, 'proposal_type', 'text', true, false, true),
(3, 'status', 'text', true, true, true),
(4, 'proposed_by_handle', 'text', false, false, true),
(5, 'proposed_by_agent_id', 'uuid', false, false, false),
(6, 'channel', 'text', true, true, true),
(7, 'source_ref', 'text', false, false, true),
(8, 'rationale', 'text', true, false, true),
(9, 'payload', 'jsonb', true, false, false),
(10, 'reviewed_by_handle', 'text', false, false, true),
(11, 'reviewed_by_agent_id', 'uuid', false, false, false),
(12, 'reviewed_at', 'timestamptz', false, false, false),
(13, 'review_note', 'text', false, false, true),
(14, 'applied_by_handle', 'text', false, false, true),
(15, 'applied_by_agent_id', 'uuid', false, false, false),
(16, 'applied_at', 'timestamptz', false, false, false),
(17, 'created_at', 'timestamptz', true, true, false),
(18, 'updated_at', 'timestamptz', true, true, false)
), proposal_table as (
select relation.*
from pg_catalog.pg_class relation
join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
where namespace.nspname = 'kb_stage'
and relation.relname = 'kb_proposals'
), expected_proposal_default(attname, expression) as (
values
('id', 'gen_random_uuid()'),
('status', '''pending_review''::text'),
('channel', '''cli''::text'),
('created_at', 'now()'),
('updated_at', 'now()')
), actual_proposal_default as (
select attribute.attname,
pg_catalog.pg_get_expr(default_row.adbin, default_row.adrelid) as expression
from proposal_table
join pg_catalog.pg_attribute attribute on attribute.attrelid = proposal_table.oid
join pg_catalog.pg_attrdef default_row
on default_row.adrelid = attribute.attrelid
and default_row.adnum = attribute.attnum
where attribute.attnum > 0
and not attribute.attisdropped
) )
select pg_catalog.jsonb_build_object( select pg_catalog.jsonb_build_object(
'canonical_connect', coalesce( 'canonical_connect', coalesce(
@ -720,6 +885,75 @@ select pg_catalog.jsonb_build_object(
where database_row.datname in ({_sql_literal(CANONICAL_DATABASE)}, {_sql_literal(LEGACY_DATABASE)}) where database_row.datname in ({_sql_literal(CANONICAL_DATABASE)}, {_sql_literal(LEGACY_DATABASE)})
and acl.grantee = runtime_role.oid and acl.grantee = runtime_role.oid
), false), ), false),
'runtime_setting_contract', coalesce((
select pg_catalog.count(*) = 1
and pg_catalog.bool_and(
setting_row.setdatabase = (
select database_row.oid
from pg_catalog.pg_database database_row
where database_row.datname = {_sql_literal(CANONICAL_DATABASE)}
)
and pg_catalog.cardinality(setting_row.setconfig) = 3
and setting_row.setconfig @> array[
'search_path=pg_catalog, public, kb_stage',
'statement_timeout=15s',
'lock_timeout=2s'
]::text[]
)
from pg_catalog.pg_db_role_setting setting_row
join runtime_role on runtime_role.oid = setting_row.setrole
), false),
'unexpected_role_settings', (
select pg_catalog.count(*)::int
from pg_catalog.pg_db_role_setting setting_row
where setting_row.setrole in ((select oid from runtime_role), (select oid from owner_role))
and not (
setting_row.setrole = (select oid from runtime_role)
and setting_row.setdatabase = (
select database_row.oid
from pg_catalog.pg_database database_row
where database_row.datname = {_sql_literal(CANONICAL_DATABASE)}
)
and pg_catalog.cardinality(setting_row.setconfig) = 3
and setting_row.setconfig @> array[
'search_path=pg_catalog, public, kb_stage',
'statement_timeout=15s',
'lock_timeout=2s'
]::text[]
)
),
'effective_setting_contract', (
pg_catalog.current_setting('search_path') = 'pg_catalog, public, kb_stage'
and pg_catalog.current_setting('statement_timeout') = '15s'
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('role') = 'none'
),
'unexpected_connectable_databases', (
select pg_catalog.count(*)::int
from pg_catalog.pg_database database_row
where database_row.datname <> {_sql_literal(CANONICAL_DATABASE)}
and pg_catalog.has_database_privilege(current_user, database_row.oid, 'CONNECT')
),
'owner_database_connect_grants', (
select pg_catalog.count(*)::int
from pg_catalog.pg_database database_row
where pg_catalog.has_database_privilege(
{_sql_literal(STAGE_OWNER_DATABASE_ROLE)},
database_row.oid,
'CONNECT'
)
),
'public_database_connect_grants', (
select pg_catalog.count(*)::int
from pg_catalog.pg_database database_row
cross join lateral pg_catalog.aclexplode(
coalesce(database_row.datacl, pg_catalog.acldefault('d', database_row.datdba))
) acl
where acl.grantee = 0
and acl.privilege_type = 'CONNECT'
),
'unexpected_direct_database_acl_entries', ( 'unexpected_direct_database_acl_entries', (
select pg_catalog.count(*)::int select pg_catalog.count(*)::int
from pg_catalog.pg_database database_row from pg_catalog.pg_database database_row
@ -727,8 +961,7 @@ select pg_catalog.jsonb_build_object(
cross join lateral pg_catalog.aclexplode( cross join lateral pg_catalog.aclexplode(
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 database_row.datname in ({_sql_literal(CANONICAL_DATABASE)}, {_sql_literal(LEGACY_DATABASE)}) where acl.grantee = runtime_role.oid
and acl.grantee = runtime_role.oid
and not ( and not (
database_row.datname = {_sql_literal(CANONICAL_DATABASE)} database_row.datname = {_sql_literal(CANONICAL_DATABASE)}
and acl.privilege_type = 'CONNECT' and acl.privilege_type = 'CONNECT'
@ -742,20 +975,19 @@ select pg_catalog.jsonb_build_object(
cross join lateral pg_catalog.aclexplode( cross join lateral pg_catalog.aclexplode(
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 database_row.datname in ({_sql_literal(CANONICAL_DATABASE)}, {_sql_literal(LEGACY_DATABASE)}) where acl.grantee = owner_role.oid
and acl.grantee = owner_role.oid
), ),
'database_create_grants', ( 'database_create_grants', (
select pg_catalog.count(*)::int select pg_catalog.count(*)::int
from (values ({_sql_literal(CANONICAL_DATABASE)}), ({_sql_literal(LEGACY_DATABASE)})) database_name(name) from pg_catalog.pg_database database_row
where pg_catalog.has_database_privilege(current_user, database_name.name, 'CREATE') where pg_catalog.has_database_privilege(current_user, database_row.oid, 'CREATE')
), ),
'owner_database_create_grants', ( 'owner_database_create_grants', (
select pg_catalog.count(*)::int select pg_catalog.count(*)::int
from (values ({_sql_literal(CANONICAL_DATABASE)}), ({_sql_literal(LEGACY_DATABASE)})) database_name(name) from pg_catalog.pg_database database_row
where pg_catalog.has_database_privilege( where pg_catalog.has_database_privilege(
{_sql_literal(STAGE_OWNER_DATABASE_ROLE)}, {_sql_literal(STAGE_OWNER_DATABASE_ROLE)},
database_name.name, database_row.oid,
'CREATE' 'CREATE'
) )
), ),
@ -837,6 +1069,241 @@ select pg_catalog.jsonb_build_object(
false false
) )
), ),
'unsafe_allowed_relation_kinds', (
select pg_catalog.count(*)::int
from allowed_relation
where allowed_relation.relkind is distinct from 'r'::"char"
),
'unsafe_allowed_relation_inheritance', (
select pg_catalog.count(*)::int
from pg_catalog.pg_inherits inheritance
where inheritance.inhparent in (select oid from allowed_relation where oid is not null)
or inheritance.inhrelid in (select oid from allowed_relation where oid is not null)
),
'unsafe_proposal_column_contract', (
select pg_catalog.count(*)::int
from expected_proposal_column expected
full join (
select attribute.attnum::int as ordinal,
attribute.attname,
type_row.typname,
attribute.attnotnull as not_null,
attribute.atthasdef as has_default,
attribute.attidentity,
attribute.attgenerated,
attribute.atttypmod,
type_namespace.nspname as type_namespace,
attribute.attcollation = 'pg_catalog.default'::pg_catalog.regcollation as collated
from proposal_table
join pg_catalog.pg_attribute attribute on attribute.attrelid = proposal_table.oid
join pg_catalog.pg_type type_row on type_row.oid = attribute.atttypid
join pg_catalog.pg_namespace type_namespace on type_namespace.oid = type_row.typnamespace
where attribute.attnum > 0
and not attribute.attisdropped
) actual using (ordinal, attname)
where expected.ordinal is null
or actual.ordinal is null
or expected.typname is distinct from actual.typname
or expected.not_null is distinct from actual.not_null
or expected.has_default is distinct from actual.has_default
or expected.collated is distinct from actual.collated
or actual.attidentity <> ''
or actual.attgenerated <> ''
or actual.atttypmod <> -1
or actual.type_namespace <> 'pg_catalog'
),
'proposal_table_contract', coalesce((
select pg_catalog.count(*) = 1
and pg_catalog.bool_and(
proposal_table.relkind = 'r'
and proposal_table.relpersistence = 'p'
and not proposal_table.relrowsecurity
and not proposal_table.relforcerowsecurity
)
from proposal_table
), false),
'unsafe_proposal_triggers', (
select pg_catalog.count(*)::int
from pg_catalog.pg_trigger trigger_row
join proposal_table on proposal_table.oid = trigger_row.tgrelid
where not trigger_row.tgisinternal
),
'unsafe_proposal_rewrite_rules', (
select pg_catalog.count(*)::int
from pg_catalog.pg_rewrite rewrite_row
join proposal_table on proposal_table.oid = rewrite_row.ev_class
),
'unsafe_proposal_policies', (
select pg_catalog.count(*)::int
from pg_catalog.pg_policy policy_row
join proposal_table on proposal_table.oid = policy_row.polrelid
),
'unsafe_proposal_generated_columns', (
select pg_catalog.count(*)::int
from pg_catalog.pg_attribute attribute
join proposal_table on proposal_table.oid = attribute.attrelid
where attribute.attnum > 0
and not attribute.attisdropped
and attribute.attgenerated <> ''
),
'unsafe_proposal_defaults', (
select pg_catalog.count(*)::int
from expected_proposal_default expected
full join actual_proposal_default actual using (attname)
where expected.expression is distinct from actual.expression
),
'unsafe_proposal_default_dependencies', (
select pg_catalog.count(*)::int
from (
select dependency.objid, dependency.refobjid
from proposal_table
join pg_catalog.pg_attrdef default_row on default_row.adrelid = proposal_table.oid
join pg_catalog.pg_depend dependency
on dependency.classid = 'pg_catalog.pg_attrdef'::pg_catalog.regclass
and dependency.objid = default_row.oid
and dependency.refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass
join pg_catalog.pg_proc function_row on function_row.oid = dependency.refobjid
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
where namespace.nspname <> 'pg_catalog'
or function_row.oid not in (
'pg_catalog.gen_random_uuid()'::pg_catalog.regprocedure,
'pg_catalog.now()'::pg_catalog.regprocedure
)
union all
select dependency.objid, dependency.refobjid
from proposal_table
join pg_catalog.pg_attrdef default_row on default_row.adrelid = proposal_table.oid
join pg_catalog.pg_depend dependency
on dependency.classid = 'pg_catalog.pg_attrdef'::pg_catalog.regclass
and dependency.objid = default_row.oid
and dependency.refclassid = 'pg_catalog.pg_type'::pg_catalog.regclass
join pg_catalog.pg_type type_row on type_row.oid = dependency.refobjid
join pg_catalog.pg_namespace namespace on namespace.oid = type_row.typnamespace
where namespace.nspname <> 'pg_catalog'
union all
select dependency.objid, dependency.refobjid
from proposal_table
join pg_catalog.pg_attrdef default_row on default_row.adrelid = proposal_table.oid
join pg_catalog.pg_depend dependency
on dependency.classid = 'pg_catalog.pg_attrdef'::pg_catalog.regclass
and dependency.objid = default_row.oid
and dependency.refclassid = 'pg_catalog.pg_operator'::pg_catalog.regclass
join pg_catalog.pg_operator operator_row on operator_row.oid = dependency.refobjid
join pg_catalog.pg_namespace namespace on namespace.oid = operator_row.oprnamespace
where namespace.nspname <> 'pg_catalog'
union all
select dependency.objid, dependency.refobjid
from proposal_table
join pg_catalog.pg_attrdef default_row on default_row.adrelid = proposal_table.oid
join pg_catalog.pg_depend dependency
on dependency.classid = 'pg_catalog.pg_attrdef'::pg_catalog.regclass
and dependency.objid = default_row.oid
and dependency.refclassid = 'pg_catalog.pg_collation'::pg_catalog.regclass
join pg_catalog.pg_collation collation_row on collation_row.oid = dependency.refobjid
join pg_catalog.pg_namespace namespace on namespace.oid = collation_row.collnamespace
where namespace.nspname <> 'pg_catalog'
) unsafe_dependency
),
'owned_large_objects', (
select pg_catalog.count(*)::int
from pg_catalog.pg_largeobject_metadata metadata
where metadata.lomowner in ((select oid from runtime_role), (select oid from owner_role))
),
'large_object_acl_privileges', (
select pg_catalog.count(*)::int
from pg_catalog.pg_largeobject_metadata metadata
where exists (
select 1
from pg_catalog.aclexplode(
coalesce(metadata.lomacl, pg_catalog.acldefault('L', metadata.lomowner))
) acl
where acl.grantee in (0, (select oid from runtime_role), (select oid from owner_role))
and acl.privilege_type in ('SELECT', 'UPDATE')
)
),
'large_object_mutation_routine_execute', (
select pg_catalog.count(*)::int
from pg_catalog.pg_proc function_row
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
cross join (values ({_sql_literal(RUNTIME_DATABASE_ROLE)}), ({_sql_literal(STAGE_OWNER_DATABASE_ROLE)})) scoped_role(name)
where namespace.nspname = 'pg_catalog'
and function_row.proname in (
'lo_creat', 'lo_create', 'lo_export', 'lo_from_bytea', 'lo_import',
'lo_open', 'lo_put', 'lo_truncate', 'lo_truncate64', 'lo_unlink', 'lowrite'
)
and pg_catalog.has_function_privilege(scoped_role.name, function_row.oid, 'EXECUTE')
),
'public_large_object_mutation_routine_execute', (
select pg_catalog.count(*)::int
from pg_catalog.pg_proc function_row
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
cross join lateral pg_catalog.aclexplode(
coalesce(function_row.proacl, pg_catalog.acldefault('f', function_row.proowner))
) acl
where namespace.nspname = 'pg_catalog'
and function_row.proname in (
'lo_creat', 'lo_create', 'lo_export', 'lo_from_bytea', 'lo_import',
'lo_open', 'lo_put', 'lo_truncate', 'lo_truncate64', 'lo_unlink', 'lowrite'
)
and acl.grantee = 0
and acl.privilege_type = 'EXECUTE'
),
'parameter_privileges', (
select pg_catalog.count(*)::int
from pg_catalog.pg_parameter_acl parameter_acl
cross join (values ({_sql_literal(RUNTIME_DATABASE_ROLE)}), ({_sql_literal(STAGE_OWNER_DATABASE_ROLE)})) scoped_role(name)
where pg_catalog.has_parameter_privilege(scoped_role.name, parameter_acl.parname, 'SET')
or pg_catalog.has_parameter_privilege(scoped_role.name, parameter_acl.parname, 'ALTER SYSTEM')
),
'proposal_constraint_shape', coalesce((
select pg_catalog.count(*) = 6
and pg_catalog.count(*) filter (
where (constraint_row.conname, constraint_row.contype) in (
('kb_proposals_pkey', 'p'),
('kb_proposals_proposal_type_check', 'c'),
('kb_proposals_status_check', 'c'),
('kb_proposals_applied_by_agent_id_fkey', 'f'),
('kb_proposals_proposed_by_agent_id_fkey', 'f'),
('kb_proposals_reviewed_by_agent_id_fkey', 'f')
)
) = 6
from pg_catalog.pg_constraint constraint_row
join proposal_table on proposal_table.oid = constraint_row.conrelid
), false),
'unsafe_proposal_constraint_dependencies', (
select pg_catalog.count(*)::int
from pg_catalog.pg_constraint constraint_row
join proposal_table on proposal_table.oid = constraint_row.conrelid
join pg_catalog.pg_depend dependency
on dependency.classid = 'pg_catalog.pg_constraint'::pg_catalog.regclass
and dependency.objid = constraint_row.oid
and dependency.refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass
join pg_catalog.pg_proc function_row on function_row.oid = dependency.refobjid
join pg_catalog.pg_namespace namespace on namespace.oid = function_row.pronamespace
where namespace.nspname <> 'pg_catalog'
),
'unsafe_proposal_index_expressions', (
select pg_catalog.count(*)::int
from pg_catalog.pg_index index_row
join proposal_table on proposal_table.oid = index_row.indrelid
where index_row.indexprs is not null
or (
index_row.indpred is not null
and pg_catalog.pg_get_expr(index_row.indpred, index_row.indrelid)
<> '(proposed_by_handle IS NOT NULL)'
)
),
'other_scoped_backends', (
select pg_catalog.count(*)::int
from pg_catalog.pg_stat_activity activity
where activity.pid <> pg_catalog.pg_backend_pid()
and activity.usename in ({_sql_literal(RUNTIME_DATABASE_ROLE)}, {_sql_literal(STAGE_OWNER_DATABASE_ROLE)})
),
'scoped_prepared_xacts', (
select pg_catalog.count(*)::int
from pg_catalog.pg_prepared_xacts prepared
where prepared.owner in ({_sql_literal(RUNTIME_DATABASE_ROLE)}, {_sql_literal(STAGE_OWNER_DATABASE_ROLE)})
),
'table_dml_grants', ( 'table_dml_grants', (
select pg_catalog.count(*)::int select pg_catalog.count(*)::int
from pg_catalog.pg_class relation from pg_catalog.pg_class relation
@ -1083,99 +1550,6 @@ select pg_catalog.jsonb_build_object(
""".strip() """.strip()
def _legacy_catalog_privilege_posture_sql() -> str:
return f"""
with scoped_role(role_name, field_prefix) as (
values
({_sql_literal(RUNTIME_DATABASE_ROLE)}, 'runtime'),
({_sql_literal(STAGE_OWNER_DATABASE_ROLE)}, 'owner')
), restore_schema as (
select oid
from pg_catalog.pg_namespace
where nspname = 'teleo_restore'
), posture as (
select scoped_role.field_prefix,
(
select pg_catalog.count(*)::int
from restore_schema
cross join (values ('USAGE'), ('CREATE')) privilege(name)
where pg_catalog.has_schema_privilege(
scoped_role.role_name,
restore_schema.oid,
privilege.name
)
) as schema_privileges,
(
select pg_catalog.count(*)::int
from pg_catalog.pg_class relation
join restore_schema on restore_schema.oid = relation.relnamespace
cross join (
values ('SELECT'), ('INSERT'), ('UPDATE'), ('DELETE'),
('TRUNCATE'), ('REFERENCES'), ('TRIGGER')
) privilege(name)
where relation.relkind in ('r', 'p', 'v', 'm', 'f')
and pg_catalog.has_table_privilege(
scoped_role.role_name,
relation.oid,
privilege.name
)
) as table_privileges,
(
select pg_catalog.count(*)::int
from pg_catalog.pg_class relation
join restore_schema on restore_schema.oid = relation.relnamespace
join pg_catalog.pg_attribute attribute on attribute.attrelid = relation.oid
cross join (values ('SELECT'), ('INSERT'), ('UPDATE'), ('REFERENCES')) privilege(name)
where relation.relkind in ('r', 'p', 'v', 'm', 'f')
and attribute.attnum > 0
and not attribute.attisdropped
and pg_catalog.has_column_privilege(
scoped_role.role_name,
relation.oid,
attribute.attnum,
privilege.name
)
) as column_privileges,
(
select pg_catalog.count(*)::int
from pg_catalog.pg_class relation
join restore_schema on restore_schema.oid = relation.relnamespace
cross join (values ('USAGE'), ('SELECT'), ('UPDATE')) privilege(name)
where relation.relkind = 'S'
and pg_catalog.has_sequence_privilege(
scoped_role.role_name,
relation.oid,
privilege.name
)
) as sequence_privileges,
(
select pg_catalog.count(*)::int
from pg_catalog.pg_proc function_row
join restore_schema on restore_schema.oid = function_row.pronamespace
where pg_catalog.has_function_privilege(
scoped_role.role_name,
function_row.oid,
'EXECUTE'
)
) as routine_privileges
from scoped_role
)
select pg_catalog.jsonb_build_object(
'runtime_schema_privileges', max(schema_privileges) filter (where field_prefix = 'runtime'),
'runtime_table_privileges', max(table_privileges) filter (where field_prefix = 'runtime'),
'runtime_column_privileges', max(column_privileges) filter (where field_prefix = 'runtime'),
'runtime_sequence_privileges', max(sequence_privileges) filter (where field_prefix = 'runtime'),
'runtime_routine_privileges', max(routine_privileges) filter (where field_prefix = 'runtime'),
'owner_schema_privileges', max(schema_privileges) filter (where field_prefix = 'owner'),
'owner_table_privileges', max(table_privileges) filter (where field_prefix = 'owner'),
'owner_column_privileges', max(column_privileges) filter (where field_prefix = 'owner'),
'owner_sequence_privileges', max(sequence_privileges) filter (where field_prefix = 'owner'),
'owner_routine_privileges', max(routine_privileges) filter (where field_prefix = 'owner')
)::text
from posture;
""".strip()
def _canary_count_sql(source_ref: str) -> str: def _canary_count_sql(source_ref: str) -> str:
return f"select pg_catalog.count(*)::text from kb_stage.kb_proposals where source_ref = {_sql_literal(source_ref)};" return f"select pg_catalog.count(*)::text from kb_stage.kb_proposals where source_ref = {_sql_literal(source_ref)};"
@ -1202,6 +1576,7 @@ select pg_catalog.jsonb_build_object(
'proposal', proposal 'proposal', proposal
)::text )::text
from staged; from staged;
set constraints all immediate;
rollback; rollback;
""".strip() """.strip()
@ -1249,6 +1624,24 @@ def _negative_checks(run_id: str, source_ref: str) -> tuple[NegativeCheck, ...]:
_transactional("delete from public.claims where false"), _transactional("delete from public.claims where false"),
"42501", "42501",
), ),
NegativeCheck(
"large_object_creat",
CANONICAL_DATABASE,
_transactional("select pg_catalog.lo_creat(0)"),
"42501",
),
NegativeCheck(
"large_object_create",
CANONICAL_DATABASE,
_transactional("select pg_catalog.lo_create(0)"),
"42501",
),
NegativeCheck(
"large_object_from_bytea",
CANONICAL_DATABASE,
_transactional("select pg_catalog.lo_from_bytea(0, ''::bytea)"),
"42501",
),
NegativeCheck( NegativeCheck(
"forged_proposer_overload", "forged_proposer_overload",
CANONICAL_DATABASE, CANONICAL_DATABASE,
@ -1313,10 +1706,18 @@ def _negative_checks(run_id: str, source_ref: str) -> tuple[NegativeCheck, ...]:
"42501", "42501",
), ),
NegativeCheck( NegativeCheck(
"legacy_teleo_restore_read", "legacy_database_connect",
LEGACY_DATABASE, LEGACY_DATABASE,
_transactional("select 1 from teleo_restore.response_audit limit 1"), "select 1 /* legacy_database_connect */;",
"42501", "42501",
expected_connection_denial=True,
),
NegativeCheck(
"template_database_connect",
TEMPLATE_DATABASE,
"select 1 /* template_database_connect */;",
"42501",
expected_connection_denial=True,
), ),
) )
@ -1341,6 +1742,24 @@ def _expect_sqlstate(
observed_sqlstate="success", observed_sqlstate="success",
) )
if check.expected_connection_denial:
normalized = result.stderr.casefold()
expected_database = f'permission denied for database "{check.database}"'.casefold()
if expected_database not in normalized or "does not have connect privilege" not in normalized:
raise VerificationError(
"negative_connection_denial_mismatch",
check.name,
expected_sqlstate=check.expected_sqlstate,
observed_sqlstate="startup_error_unclassified",
)
return {
"check": check.name,
"expected_sqlstate": check.expected_sqlstate,
"observed_error_class": "connect_privilege_denied",
"observed_sqlstate": "not_exposed_by_libpq_startup",
"result": "denied",
}
observed = SQLSTATE_RE.findall(result.stderr) observed = SQLSTATE_RE.findall(result.stderr)
unique_observed = sorted(set(observed)) unique_observed = sorted(set(observed))
if not observed: if not observed:
@ -1373,6 +1792,7 @@ def _assert_identity(identity: dict[str, Any]) -> dict[str, Any]:
"server_addr": PRIVATE_CLOUDSQL_HOST, "server_addr": PRIVATE_CLOUDSQL_HOST,
"server_port": PRIVATE_CLOUDSQL_PORT, "server_port": PRIVATE_CLOUDSQL_PORT,
"ssl": True, "ssl": True,
"system_identifier": EXPECTED_SYSTEM_IDENTIFIER,
} }
if any(identity.get(key) != value for key, value in expected.items()): if any(identity.get(key) != value for key, value in expected.items()):
raise VerificationError("private_ssl_identity_mismatch", "database_identity") raise VerificationError("private_ssl_identity_mismatch", "database_identity")
@ -1510,16 +1930,6 @@ def _assert_catalog_privilege_posture(posture: dict[str, Any]) -> dict[str, int
return sanitized return sanitized
def _assert_legacy_catalog_privilege_posture(posture: dict[str, Any]) -> dict[str, int]:
sanitized: dict[str, int] = {}
for field in LEGACY_CATALOG_ZERO_COUNT_FIELDS:
value = posture.get(field)
if isinstance(value, bool) or not isinstance(value, int) or value != 0:
raise VerificationError("legacy_catalog_privilege_posture_mismatch", "legacy_catalog_privileges")
sanitized[field] = 0
return sanitized
def _assert_allowed_reads(readback: dict[str, Any]) -> dict[str, int]: def _assert_allowed_reads(readback: dict[str, Any]) -> dict[str, int]:
sanitized: dict[str, int] = {} sanitized: dict[str, int] = {}
for key in ("claims_rows_sampled", "proposal_rows_sampled"): for key in ("claims_rows_sampled", "proposal_rows_sampled"):
@ -1553,6 +1963,7 @@ def verify_runtime_permissions(
runner: Runner = subprocess_runner, runner: Runner = subprocess_runner,
base_env: Mapping[str, str] | None = None, base_env: Mapping[str, str] | None = None,
effective_user: str | None = None, effective_user: str | None = None,
dependency_validator: DependencyValidator = validate_runtime_dependencies,
) -> dict[str, Any]: ) -> dict[str, Any]:
try: try:
run_id = validate_run_id(run_id) run_id = validate_run_id(run_id)
@ -1566,14 +1977,13 @@ def verify_runtime_permissions(
raise VerificationError("unix_user_unknown", "runtime_user") from None raise VerificationError("unix_user_unknown", "runtime_user") from None
if effective_user != RUNTIME_UNIX_USER: if effective_user != RUNTIME_UNIX_USER:
raise VerificationError("unexpected_unix_user", "runtime_user") raise VerificationError("unexpected_unix_user", "runtime_user")
dependency_validator()
source = os.environ if base_env is None else base_env source = os.environ if base_env is None else base_env
source_ref = f"leo-runtime-permission:{run_id}" source_ref = f"leo-runtime-permission:{run_id}"
generated_at = datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z") generated_at = datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z")
with tempfile.TemporaryDirectory(prefix="leoclean-runtime-permission-gcloud-") as config_name: with nullcontext(selected_cloudsdk_config_path()) as config_dir:
config_dir = Path(config_name)
config_dir.chmod(0o700)
gcloud_env = _gcloud_environment(source, config_dir) gcloud_env = _gcloud_environment(source, config_dir)
password = _read_scoped_password(env=gcloud_env, runner=runner) password = _read_scoped_password(env=gcloud_env, runner=runner)
psql_env = _psql_environment(source, password) psql_env = _psql_environment(source, password)
@ -1650,18 +2060,6 @@ def verify_runtime_permissions(
"catalog_privilege_posture", "catalog_privilege_posture",
) )
) )
legacy_catalog_privileges = _assert_legacy_catalog_privilege_posture(
_parse_json_object(
_run_psql_success(
check="legacy_catalog_privileges",
database=LEGACY_DATABASE,
sql=_legacy_catalog_privilege_posture_sql(),
env=psql_env,
runner=runner,
),
"legacy_catalog_privileges",
)
)
allowed_reads = _assert_allowed_reads( allowed_reads = _assert_allowed_reads(
_parse_json_object( _parse_json_object(
_run_psql_success( _run_psql_success(
@ -1721,7 +2119,6 @@ def verify_runtime_permissions(
"canary_rows_after": rows_after, "canary_rows_after": rows_after,
"canary_rows_before": rows_before, "canary_rows_before": rows_before,
"function_privileges": function_privileges, "function_privileges": function_privileges,
"legacy_catalog_privileges": legacy_catalog_privileges,
"negative_permissions": negative_permissions, "negative_permissions": negative_permissions,
"role_posture": role_posture, "role_posture": role_posture,
"stage_function_definition": stage_function_definition, "stage_function_definition": stage_function_definition,
@ -1768,7 +2165,7 @@ def verify_runtime_permissions(
"port": PRIVATE_CLOUDSQL_PORT, "port": PRIVATE_CLOUDSQL_PORT,
"project": PROJECT_ID, "project": PROJECT_ID,
"role": RUNTIME_DATABASE_ROLE, "role": RUNTIME_DATABASE_ROLE,
"sslmode": "require", "sslmode": "verify-ca",
}, },
} }

View file

@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/python3 -I
"""Verify the effective Leo service environment without exposing its values.""" """Verify the effective Leo service environment without exposing its values."""
from __future__ import annotations from __future__ import annotations
@ -18,6 +18,9 @@ RUNTIME_DATABASE_ROLE = "leoclean_kb_runtime"
SCOPED_PASSWORD_SECRET = "gcp-teleo-pgvector-standby-leoclean-kb-runtime-password" SCOPED_PASSWORD_SECRET = "gcp-teleo-pgvector-standby-leoclean-kb-runtime-password"
ADMINISTRATOR_PASSWORD_SECRET = "gcp-teleo-pgvector-standby-postgres-password" ADMINISTRATOR_PASSWORD_SECRET = "gcp-teleo-pgvector-standby-postgres-password"
RUNTIME_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" RUNTIME_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
RUNTIME_CLOUDSDK_CONFIG = "/usr/local/libexec/livingip/leoclean-kb/gcloud-config"
RUNTIME_SSL_MODE = "verify-ca"
RUNTIME_SSL_ROOT_CERT = "/usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem"
FORBIDDEN_EXACT_FIELDS = frozenset( FORBIDDEN_EXACT_FIELDS = frozenset(
{ {
@ -26,6 +29,7 @@ FORBIDDEN_EXACT_FIELDS = frozenset(
"BASHOPTS", "BASHOPTS",
"CDPATH", "CDPATH",
"CURL_CA_BUNDLE", "CURL_CA_BUNDLE",
"CREDENTIALS_DIRECTORY",
"ENV", "ENV",
"GLOBIGNORE", "GLOBIGNORE",
"GOOGLE_APPLICATION_CREDENTIALS", "GOOGLE_APPLICATION_CREDENTIALS",
@ -33,12 +37,22 @@ FORBIDDEN_EXACT_FIELDS = frozenset(
"HTTP_PROXY", "HTTP_PROXY",
"IFS", "IFS",
"NO_PROXY", "NO_PROXY",
"OPENSSL_CONF",
"OPENSSL_ENGINES",
"OPENSSL_MODULES",
"PS4", "PS4",
"REQUESTS_CA_BUNDLE", "REQUESTS_CA_BUNDLE",
"SHELLOPTS", "SHELLOPTS",
"SSL_CERT_DIR", "SSL_CERT_DIR",
"SSL_CERT_FILE", "SSL_CERT_FILE",
"SSLKEYLOGFILE", "SSLKEYLOGFILE",
"GCONV_PATH",
"TELEO_CLOUDSQL_CREDENTIAL_MODE",
"TELEO_KB_CLAIM_BASE_URL",
"TELEO_KB_READ_ATTEMPTS",
"TELEO_MEMORY_INCLUDE_EXCERPTS",
"TELEO_MEMORY_REDACT",
"XDG_CONFIG_HOME",
} }
) )
@ -83,7 +97,12 @@ def expected_environment() -> dict[str, str]:
"""Build the exact reviewed environment contract.""" """Build the exact reviewed environment contract."""
return { return {
"CLOUDSDK_CONFIG": RUNTIME_CLOUDSDK_CONFIG,
"PATH": RUNTIME_PATH, "PATH": RUNTIME_PATH,
"PGSSLMODE": RUNTIME_SSL_MODE,
"PGSSLROOTCERT": RUNTIME_SSL_ROOT_CERT,
"PYTHONNOUSERSITE": "1",
"PYTHONSAFEPATH": "1",
"TELEO_CANONICAL_CLOUDSQL_DB": CANONICAL_DATABASE, "TELEO_CANONICAL_CLOUDSQL_DB": CANONICAL_DATABASE,
"TELEO_CLOUDSQL_DB": CANONICAL_DATABASE, "TELEO_CLOUDSQL_DB": CANONICAL_DATABASE,
"TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK": "0", "TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK": "0",
@ -127,22 +146,31 @@ def _forbidden_field_labels(environment: Mapping[str, str]) -> tuple[str, ...]:
labels: set[str] = set() labels: set[str] = set()
for key in environment: for key in environment:
normalized = key.upper() normalized = key.upper()
if normalized.startswith("PG"): if normalized.startswith("PG") and normalized not in {"PGSSLMODE", "PGSSLROOTCERT"}:
labels.add("PG*") labels.add("PG*")
if normalized.startswith("CLOUDSDK_"): if normalized.startswith("CLOUDSDK_") and normalized != "CLOUDSDK_CONFIG":
labels.add("CLOUDSDK_*") labels.add("CLOUDSDK_*")
if normalized.startswith("LD_"): if normalized.startswith("LD_"):
labels.add("LD_*") labels.add("LD_*")
if normalized.startswith("BASH_FUNC_"): if normalized.startswith("BASH_FUNC_"):
labels.add("BASH_FUNC_*") labels.add("BASH_FUNC_*")
if normalized.startswith("PYTHON") and normalized != "PYTHONUNBUFFERED": if normalized.startswith("PYTHON") and normalized not in {
"PYTHONNOUSERSITE",
"PYTHONSAFEPATH",
"PYTHONUNBUFFERED",
}:
labels.add("PYTHON*") labels.add("PYTHON*")
if normalized in FORBIDDEN_EXACT_FIELDS: if normalized in FORBIDDEN_EXACT_FIELDS:
labels.add(normalized) labels.add(normalized)
return tuple(sorted(labels)) return tuple(sorted(labels))
def validate_environment(environment: Mapping[str, str]) -> tuple[str, ...]: def validate_environment(
environment: Mapping[str, str],
*,
allow_missing_cloudsdk_config: bool = False,
allow_missing_runtime_security_fields: bool = False,
) -> tuple[str, ...]:
"""Validate the effective environment and return only reviewed field names.""" """Validate the effective environment and return only reviewed field names."""
if not isinstance(environment, Mapping) or any( if not isinstance(environment, Mapping) or any(
@ -151,6 +179,18 @@ def validate_environment(environment: Mapping[str, str]) -> tuple[str, ...]:
raise VerificationError("environment_malformed") raise VerificationError("environment_malformed")
expected = expected_environment() expected = expected_environment()
if allow_missing_runtime_security_fields:
for field in (
"CLOUDSDK_CONFIG",
"PGSSLMODE",
"PGSSLROOTCERT",
"PYTHONNOUSERSITE",
"PYTHONSAFEPATH",
):
if field not in environment:
expected.pop(field)
if allow_missing_cloudsdk_config and "CLOUDSDK_CONFIG" not in environment:
expected.pop("CLOUDSDK_CONFIG")
administrator_secret = ADMINISTRATOR_PASSWORD_SECRET.casefold() administrator_secret = ADMINISTRATOR_PASSWORD_SECRET.casefold()
if any(administrator_secret in value.casefold() for value in environment.values()): if any(administrator_secret in value.casefold() for value in environment.values()):
raise VerificationError("administrator_secret_reference") raise VerificationError("administrator_secret_reference")
@ -172,12 +212,18 @@ def verify_process_environment(
pid: int, pid: int,
*, *,
proc_root: Path = Path("/proc"), proc_root: Path = Path("/proc"),
allow_missing_cloudsdk_config: bool = False,
allow_missing_runtime_security_fields: bool = False,
) -> dict[str, object]: ) -> dict[str, object]:
"""Read and verify one process environment, returning a sanitized receipt.""" """Read and verify one process environment, returning a sanitized receipt."""
validated_pid = validate_pid(pid) validated_pid = validate_pid(pid)
raw = (proc_root / str(validated_pid) / "environ").read_bytes() raw = (proc_root / str(validated_pid) / "environ").read_bytes()
validated_fields = validate_environment(parse_environment(raw)) validated_fields = validate_environment(
parse_environment(raw),
allow_missing_cloudsdk_config=allow_missing_cloudsdk_config,
allow_missing_runtime_security_fields=allow_missing_runtime_security_fields,
)
return { return {
"runtime_environment": "scoped", "runtime_environment": "scoped",
"status": "pass", "status": "pass",
@ -196,13 +242,19 @@ def canonical_json(payload: Mapping[str, Any]) -> str:
def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = _SanitizedArgumentParser(description=__doc__, add_help=False) parser = _SanitizedArgumentParser(description=__doc__, add_help=False)
parser.add_argument("--pid", required=True) parser.add_argument("--pid", required=True)
parser.add_argument("--allow-missing-cloudsdk-config", action="store_true")
parser.add_argument("--allow-missing-runtime-security-fields", action="store_true")
return parser.parse_args(argv) return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int: def main(argv: list[str] | None = None) -> int:
try: try:
args = parse_args(argv) args = parse_args(argv)
receipt = verify_process_environment(validate_pid(args.pid)) receipt = verify_process_environment(
validate_pid(args.pid),
allow_missing_cloudsdk_config=args.allow_missing_cloudsdk_config,
allow_missing_runtime_security_fields=args.allow_missing_runtime_security_fields,
)
returncode = 0 returncode = 0
except VerificationError as exc: except VerificationError as exc:
receipt = failure_receipt(exc) receipt = failure_receipt(exc)

View file

@ -46,8 +46,10 @@ DEFAULT_SERVICE = "leoclean-gcp-prod-parallel.service"
DEFAULT_HOST = "10.61.0.3" DEFAULT_HOST = "10.61.0.3"
DEFAULT_PROJECT = "teleo-501523" DEFAULT_PROJECT = "teleo-501523"
DEFAULT_SECRET = "gcp-teleo-pgvector-standby-postgres-password" DEFAULT_SECRET = "gcp-teleo-pgvector-standby-postgres-password"
DEFAULT_SSL_ROOT_CERT = Path("/usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem")
DEFAULT_SYSTEM_IDENTIFIER = "7659718422914359312"
DEFAULT_MANIFEST_SQL = HERE.parent / "ops" / "postgres_parity_manifest.sql" DEFAULT_MANIFEST_SQL = HERE.parent / "ops" / "postgres_parity_manifest.sql"
REVIEWED_CLOUDSQL_TOOL_SHA256 = "66fa84b3bd6fe67e102fbce4509ea28c3537ba5bb1091e1a1a457b7425366064" REVIEWED_CLOUDSQL_TOOL_SHA256 = "17d5213756ea6492e4232784a04b7f584afee5dce5184b33a2227e58847472c1"
REVIEWED_MANIFEST_SQL_SHA256 = "8b8cdc25d54fdd8de05eb38c6e4423d2836953eb6012d4545f5c9c71b5f0150a" REVIEWED_MANIFEST_SQL_SHA256 = "8b8cdc25d54fdd8de05eb38c6e4423d2836953eb6012d4545f5c9c71b5f0150a"
COUNT_READBACK_RE = re.compile( COUNT_READBACK_RE = re.compile(
r"DB readback:\s*claims:\s*`?(?P<claims>\d+)`?;\s*" r"DB readback:\s*claims:\s*`?(?P<claims>\d+)`?;\s*"
@ -192,10 +194,13 @@ def run_target_psql(
**os.environ, **os.environ,
"PGPASSWORD": password, "PGPASSWORD": password,
"PGOPTIONS": "-c default_transaction_read_only=on", "PGOPTIONS": "-c default_transaction_read_only=on",
"PGSSLMODE": "verify-ca",
"PGSSLROOTCERT": str(args.ssl_root_cert),
} }
command = [ command = [
"psql", "psql",
f"host={args.host} port=5432 dbname={args.target_db} user=postgres sslmode=require connect_timeout=8", f"host={args.host} port=5432 dbname={args.target_db} user=postgres "
f"sslmode=verify-ca sslrootcert={args.ssl_root_cert} connect_timeout=8",
"-X", "-X",
"-Atq", "-Atq",
"-v", "-v",
@ -232,7 +237,11 @@ rollback;
return json.loads(rows[-1]) return json.loads(rows[-1])
def validate_database_identity(identity: dict[str, Any], target_db: str) -> None: def validate_database_identity(
identity: dict[str, Any],
target_db: str,
expected_system_identifier: str = DEFAULT_SYSTEM_IDENTIFIER,
) -> None:
if identity.get("current_database") != target_db: if identity.get("current_database") != target_db:
raise bound.CheckpointError("Cloud SQL identity does not match the generated target database") raise bound.CheckpointError("Cloud SQL identity does not match the generated target database")
if identity.get("ssl") is not True: if identity.get("ssl") is not True:
@ -252,8 +261,8 @@ def validate_database_identity(identity: dict[str, Any], target_db: str) -> None
) )
if not any(address in network for network in private_networks): if not any(address in network for network in private_networks):
raise bound.CheckpointError("Cloud SQL target address is not RFC1918-private") raise bound.CheckpointError("Cloud SQL target address is not RFC1918-private")
if not identity.get("system_identifier"): if identity.get("system_identifier") != expected_system_identifier:
raise bound.CheckpointError("Cloud SQL target system identifier is missing") raise bound.CheckpointError("Cloud SQL target system identifier is not the reviewed instance")
def canonical_status(args: argparse.Namespace) -> dict[str, Any]: def canonical_status(args: argparse.Namespace) -> dict[str, Any]:
@ -347,6 +356,7 @@ def build_cloudsql_wrapper(
host: str, host: str,
project: str, project: str,
password_secret: str, password_secret: str,
ssl_root_cert: Path,
run_nonce: str, run_nonce: str,
system_exec_path: str = bound.SYSTEM_EXEC_PATH, system_exec_path: str = bound.SYSTEM_EXEC_PATH,
) -> str: ) -> str:
@ -361,18 +371,21 @@ TARGET_HOST={shlex.quote(host)}
SOURCE_COMPUTE={shlex.quote(SOURCE_COMPUTE)} SOURCE_COMPUTE={shlex.quote(SOURCE_COMPUTE)}
PROJECT={shlex.quote(project)} PROJECT={shlex.quote(project)}
PASSWORD_SECRET={shlex.quote(password_secret)} PASSWORD_SECRET={shlex.quote(password_secret)}
SSL_ROOT_CERT={shlex.quote(str(ssl_root_cert))}
RUN_NONCE={shlex.quote(run_nonce)} RUN_NONCE={shlex.quote(run_nonce)}
PYTHON={shlex.quote(python)} PYTHON={shlex.quote(python)}
export PATH={shlex.quote(system_exec_path)} export PATH={shlex.quote(system_exec_path)}
export CLOUDSDK_CONFIG=/home/teleo/.config/gcloud export CLOUDSDK_CONFIG=/home/teleo/.config/gcloud
export PGOPTIONS="-c default_transaction_read_only=on" export PGOPTIONS="-c default_transaction_read_only=on"
export PGSSLMODE=verify-ca
export PGSSLROOTCERT="$SSL_ROOT_CERT"
INVOCATION_ID="$($PYTHON -c 'import uuid; print(uuid.uuid4())')" INVOCATION_ID="$($PYTHON -c 'import uuid; print(uuid.uuid4())')"
if ! PGPASSWORD="$(gcloud secrets versions access latest --secret="$PASSWORD_SECRET" --project="$PROJECT")"; then if ! PGPASSWORD="$(gcloud secrets versions access latest --secret="$PASSWORD_SECRET" --project="$PROJECT")"; then
exit 124 exit 124
fi fi
export PGPASSWORD export PGPASSWORD
trap 'unset PGPASSWORD PGOPTIONS' EXIT trap 'unset PGPASSWORD PGOPTIONS' EXIT
DB_RECEIPT="$(psql "host=$TARGET_HOST port=5432 dbname=$TARGET_DATABASE user=postgres sslmode=require connect_timeout=8" -X -Atq -v ON_ERROR_STOP=1 <<'SQL' DB_RECEIPT="$(psql "host=$TARGET_HOST port=5432 dbname=$TARGET_DATABASE user=postgres sslmode=verify-ca sslrootcert=$SSL_ROOT_CERT connect_timeout=8" -X -Atq -v ON_ERROR_STOP=1 <<'SQL'
begin transaction read only; begin transaction read only;
select jsonb_build_object( select jsonb_build_object(
'current_database', current_database(), 'current_database', current_database(),
@ -468,6 +481,7 @@ def patch_temp_bridge(args: argparse.Namespace, temp_profile: Path) -> dict[str,
project=args.project, project=args.project,
password_secret=args.password_secret, password_secret=args.password_secret,
run_nonce=run_nonce, run_nonce=run_nonce,
ssl_root_cert=args.ssl_root_cert,
) )
binding = f""" binding = f"""
## Generated GCP Database Checkpoint ## Generated GCP Database Checkpoint
@ -788,6 +802,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser.add_argument("--host", default=DEFAULT_HOST) parser.add_argument("--host", default=DEFAULT_HOST)
parser.add_argument("--project", default=DEFAULT_PROJECT) parser.add_argument("--project", default=DEFAULT_PROJECT)
parser.add_argument("--password-secret", default=DEFAULT_SECRET) parser.add_argument("--password-secret", default=DEFAULT_SECRET)
parser.add_argument("--ssl-root-cert", default=DEFAULT_SSL_ROOT_CERT, type=Path)
parser.add_argument("--service", default=DEFAULT_SERVICE) parser.add_argument("--service", default=DEFAULT_SERVICE)
parser.add_argument("--live-profile", default=bound.LIVE_PROFILE, type=Path) parser.add_argument("--live-profile", default=bound.LIVE_PROFILE, type=Path)
parser.add_argument("--chat-id", default=bound.DEFAULT_CHAT_ID) parser.add_argument("--chat-id", default=bound.DEFAULT_CHAT_ID)
@ -804,6 +819,8 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser.error("--target-db must start with teleo_clone_ and cannot name a live database") parser.error("--target-db must start with teleo_clone_ and cannot name a live database")
if not args.cloudsql_tool.is_file(): if not args.cloudsql_tool.is_file():
parser.error("--cloudsql-tool must exist") parser.error("--cloudsql-tool must exist")
if not args.ssl_root_cert.is_file():
parser.error("--ssl-root-cert must exist")
if not args.manifest_sql.is_file(): if not args.manifest_sql.is_file():
parser.error("--manifest-sql must exist") parser.error("--manifest-sql must exist")
if not args.parity_receipt.is_file(): if not args.parity_receipt.is_file():

View file

@ -452,7 +452,8 @@ select current_database() = :'expected_database' as exact_database_bound \\gset
"psql", "psql",
( (
f"host={self.args.host} port=5432 dbname={self.args.target_db} " f"host={self.args.host} port=5432 dbname={self.args.target_db} "
f"user={role} sslmode=require connect_timeout=8" f"user={role} sslmode=verify-ca "
f"sslrootcert={self.args.ssl_root_cert} connect_timeout=8"
), ),
"-X", "-X",
"-Atq", "-Atq",
@ -461,7 +462,13 @@ select current_database() = :'expected_database' as exact_database_bound \\gset
"-v", "-v",
f"expected_database={self.args.target_db}", f"expected_database={self.args.target_db}",
] ]
env = {**os.environ, "PGPASSWORD": password, "PGOPTIONS": pgoptions} env = {
**os.environ,
"PGPASSWORD": password,
"PGOPTIONS": pgoptions,
"PGSSLMODE": "verify-ca",
"PGSSLROOTCERT": str(self.args.ssl_root_cert),
}
try: try:
try: try:
output = gcp.run( output = gcp.run(
@ -474,6 +481,8 @@ select current_database() = :'expected_database' as exact_database_bound \\gset
finally: finally:
env.pop("PGPASSWORD", None) env.pop("PGPASSWORD", None)
env.pop("PGOPTIONS", None) env.pop("PGOPTIONS", None)
env.pop("PGSSLMODE", None)
env.pop("PGSSLROOTCERT", None)
password = "" password = ""
lines = output.splitlines() lines = output.splitlines()
receipt_lines = [line for line in lines if line.startswith(ROLE_RECEIPT_PREFIX)] receipt_lines = [line for line in lines if line.startswith(ROLE_RECEIPT_PREFIX)]
@ -692,6 +701,7 @@ def _build_gcp_read_wrapper(
host=args.host, host=args.host,
project=args.project, project=args.project,
password_secret=args.password_secret, password_secret=args.password_secret,
ssl_root_cert=getattr(args, "ssl_root_cert", gcp.DEFAULT_SSL_ROOT_CERT),
run_nonce=run_nonce, run_nonce=run_nonce,
) )
wrapper = wrapper.replace( wrapper = wrapper.replace(
@ -699,7 +709,11 @@ def _build_gcp_read_wrapper(
f"PASSWORD_SECRET={shlex.quote(args.password_secret)}\nREAD_ROLE={shlex.quote(args.read_role)}\n", f"PASSWORD_SECRET={shlex.quote(args.password_secret)}\nREAD_ROLE={shlex.quote(args.read_role)}\n",
1, 1,
) )
wrapper = wrapper.replace("user=postgres sslmode=require", "user=$READ_ROLE sslmode=require", 1) wrapper = wrapper.replace(
"user=postgres sslmode=verify-ca",
"user=$READ_ROLE sslmode=verify-ca",
1,
)
wrapper = wrapper.replace( wrapper = wrapper.replace(
" 'default_transaction_read_only', current_setting('default_transaction_read_only')\n", " 'default_transaction_read_only', current_setting('default_transaction_read_only')\n",
" 'default_transaction_read_only', current_setting('default_transaction_read_only'),\n" " 'default_transaction_read_only', current_setting('default_transaction_read_only'),\n"
@ -755,6 +769,8 @@ def _cloudsql_lifecycle_wrapper(
args.instance, args.instance,
"--password-secret", "--password-secret",
args.password_secret, args.password_secret,
"--ssl-root-cert",
str(args.ssl_root_cert),
"--read-role", "--read-role",
args.read_role, args.read_role,
"--stage-password-secret", "--stage-password-secret",
@ -1600,6 +1616,7 @@ def parse_internal_stage_args(argv: list[str]) -> argparse.Namespace:
parser.add_argument("--project", required=True) parser.add_argument("--project", required=True)
parser.add_argument("--instance", required=True) parser.add_argument("--instance", required=True)
parser.add_argument("--password-secret", required=True) parser.add_argument("--password-secret", required=True)
parser.add_argument("--ssl-root-cert", required=True, type=Path)
parser.add_argument("--read-role", required=True) parser.add_argument("--read-role", required=True)
parser.add_argument("--stage-password-secret", required=True) parser.add_argument("--stage-password-secret", required=True)
parser.add_argument("--stage-role", required=True) parser.add_argument("--stage-role", required=True)
@ -1609,6 +1626,8 @@ def parse_internal_stage_args(argv: list[str]) -> argparse.Namespace:
parser.add_argument("--target-identity-receipt", required=True, type=Path) parser.add_argument("--target-identity-receipt", required=True, type=Path)
parser.add_argument("--target-identity-sha256", required=True) parser.add_argument("--target-identity-sha256", required=True)
args = parser.parse_args(argv) args = parser.parse_args(argv)
if not args.ssl_root_cert.is_file():
parser.error("--ssl-root-cert must exist")
args.review_role = "kb_review" args.review_role = "kb_review"
args.apply_role = "kb_apply" args.apply_role = "kb_apply"
args.allow_stage = True args.allow_stage = True
@ -1680,6 +1699,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser.add_argument("--project", default=gcp.DEFAULT_PROJECT) parser.add_argument("--project", default=gcp.DEFAULT_PROJECT)
parser.add_argument("--instance") parser.add_argument("--instance")
parser.add_argument("--password-secret", default=gcp.DEFAULT_SECRET) parser.add_argument("--password-secret", default=gcp.DEFAULT_SECRET)
parser.add_argument("--ssl-root-cert", default=gcp.DEFAULT_SSL_ROOT_CERT, type=Path)
parser.add_argument("--read-role", default="kb_read") parser.add_argument("--read-role", default="kb_read")
parser.add_argument("--stage-role", default=DEFAULT_STAGE_ROLE) parser.add_argument("--stage-role", default=DEFAULT_STAGE_ROLE)
parser.add_argument("--review-role", default=DEFAULT_REVIEW_ROLE) parser.add_argument("--review-role", default=DEFAULT_REVIEW_ROLE)
@ -1713,6 +1733,8 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
if args.turn_timeout <= 0: if args.turn_timeout <= 0:
parser.error("--turn-timeout must be positive") parser.error("--turn-timeout must be positive")
if args.execute: if args.execute:
if not args.ssl_root_cert.is_file():
parser.error("--ssl-root-cert must exist for execution")
receipt_inputs = ( receipt_inputs = (
args.target_identity_receipt, args.target_identity_receipt,
args.target_identity_sha256, args.target_identity_sha256,

View file

@ -1,17 +1,36 @@
[Service] [Service]
UnsetEnvironment=PGPASSWORD UnsetEnvironment=PGPASSWORD
UnsetEnvironment=PGPASSFILE PGSERVICE PGHOST PGPORT PGDATABASE PGUSER PGOPTIONS UnsetEnvironment=PGPASSFILE PGSERVICE PGHOST PGPORT PGDATABASE PGUSER PGOPTIONS
UnsetEnvironment=GOOGLE_APPLICATION_CREDENTIALS CLOUDSDK_CONFIG CLOUDSDK_CORE_PROJECT CLOUDSDK_AUTH_ACCESS_TOKEN UnsetEnvironment=GOOGLE_APPLICATION_CREDENTIALS CLOUDSDK_CORE_PROJECT CLOUDSDK_AUTH_ACCESS_TOKEN
UnsetEnvironment=BASH_ENV ENV BASHOPTS SHELLOPTS CDPATH GLOBIGNORE IFS PS4 UnsetEnvironment=BASH_ENV ENV BASHOPTS SHELLOPTS CDPATH GLOBIGNORE IFS PS4
UnsetEnvironment=LD_PRELOAD LD_LIBRARY_PATH LD_AUDIT LD_DEBUG LD_DEBUG_OUTPUT LD_DYNAMIC_WEAK LD_HWCAP_MASK LD_ORIGIN_PATH LD_PROFILE LD_SHOW_AUXV LD_TRACE_LOADED_OBJECTS LD_USE_LOAD_BIAS LD_VERBOSE LD_WARN UnsetEnvironment=LD_PRELOAD LD_LIBRARY_PATH LD_AUDIT LD_DEBUG LD_DEBUG_OUTPUT LD_DYNAMIC_WEAK LD_HWCAP_MASK LD_ORIGIN_PATH LD_PROFILE LD_SHOW_AUXV LD_TRACE_LOADED_OBJECTS LD_USE_LOAD_BIAS LD_VERBOSE LD_WARN
UnsetEnvironment=PYTHONHOME PYTHONPATH PYTHONSTARTUP PYTHONINSPECT PYTHONUSERBASE PYTHONHTTPSVERIFY UnsetEnvironment=PYTHONHOME PYTHONPATH PYTHONSTARTUP PYTHONINSPECT PYTHONUSERBASE PYTHONHTTPSVERIFY
UnsetEnvironment=HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy UnsetEnvironment=HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY http_proxy https_proxy all_proxy no_proxy
UnsetEnvironment=SSL_CERT_FILE SSL_CERT_DIR REQUESTS_CA_BUNDLE CURL_CA_BUNDLE SSLKEYLOGFILE UnsetEnvironment=SSL_CERT_FILE SSL_CERT_DIR REQUESTS_CA_BUNDLE CURL_CA_BUNDLE SSLKEYLOGFILE
ReadOnlyPaths=/home/teleo/.hermes UnsetEnvironment=OPENSSL_CONF OPENSSL_MODULES OPENSSL_ENGINES GCONV_PATH CREDENTIALS_DIRECTORY
UnsetEnvironment=XDG_CONFIG_HOME
UnsetEnvironment=TELEO_CLOUDSQL_CREDENTIAL_MODE TELEO_KB_CLAIM_BASE_URL TELEO_KB_READ_ATTEMPTS TELEO_MEMORY_INCLUDE_EXCERPTS TELEO_MEMORY_REDACT
ReadOnlyPaths=
ReadWritePaths=
InaccessiblePaths=
BindPaths=
BindReadOnlyPaths=
SupplementaryGroups=
ReadOnlyPaths=/home/teleo
ReadWritePaths=/home/teleo/.hermes/profiles/leoclean/state /home/teleo/.hermes/profiles/leoclean/workspace ReadWritePaths=/home/teleo/.hermes/profiles/leoclean/state /home/teleo/.hermes/profiles/leoclean/workspace
InaccessiblePaths=-/home/teleo/.config/gcloud -/home/teleo/.pgpass -/home/teleo/.pg_service.conf -/home/teleo/.postgresql
BindReadOnlyPaths=/usr/local/libexec/livingip/leoclean-kb:/home/teleo/.hermes/profiles/leoclean/bin BindReadOnlyPaths=/usr/local/libexec/livingip/leoclean-kb:/home/teleo/.hermes/profiles/leoclean/bin
CapabilityBoundingSet=~CAP_SYS_ADMIN CapabilityBoundingSet=
AmbientCapabilities=
NoNewPrivileges=yes
Group=teleo
SupplementaryGroups=teleo
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment=CLOUDSDK_CONFIG=/usr/local/libexec/livingip/leoclean-kb/gcloud-config
Environment=PGSSLMODE=verify-ca
Environment=PGSSLROOTCERT=/usr/local/libexec/livingip/leoclean-kb/cloudsql-server-ca.pem
Environment=PYTHONNOUSERSITE=1
Environment=PYTHONSAFEPATH=1
Environment=TELEO_KB_MODE=cloudsql Environment=TELEO_KB_MODE=cloudsql
Environment=TELEO_GCP_METADATA_ONLY=1 Environment=TELEO_GCP_METADATA_ONLY=1
Environment=TELEO_GCP_PROJECT=teleo-501523 Environment=TELEO_GCP_PROJECT=teleo-501523

View file

@ -109,6 +109,7 @@ def test_wrapper_binds_private_read_only_database_and_records_calls(tmp_path: Pa
host="10.61.0.3", host="10.61.0.3",
project="teleo-501523", project="teleo-501523",
password_secret="test-secret-name", password_secret="test-secret-name",
ssl_root_cert=tmp_path / "cloudsql-server-ca.pem",
run_nonce="nonce", run_nonce="nonce",
) )
@ -117,7 +118,8 @@ def test_wrapper_binds_private_read_only_database_and_records_calls(tmp_path: Pa
assert '--canonical-db "$TARGET_DATABASE"' in wrapper assert '--canonical-db "$TARGET_DATABASE"' in wrapper
assert "--credential-mode clone-readonly" in wrapper assert "--credential-mode clone-readonly" in wrapper
assert "--user postgres" in wrapper assert "--user postgres" in wrapper
assert "sslmode=require" in wrapper assert "sslmode=verify-ca" in wrapper
assert "sslrootcert=$SSL_ROOT_CERT" in wrapper
assert "begin transaction read only" in wrapper assert "begin transaction read only" in wrapper
assert 'export PGOPTIONS="-c default_transaction_read_only=on"' in wrapper assert 'export PGOPTIONS="-c default_transaction_read_only=on"' in wrapper
assert "default_transaction_read_only" in wrapper assert "default_transaction_read_only" in wrapper
@ -140,6 +142,7 @@ def test_database_identity_requires_private_tls_and_read_only_target() -> None:
"default_transaction_read_only": "on", "default_transaction_read_only": "on",
}, },
"teleo_clone_test", "teleo_clone_test",
"1234",
) )
with pytest.raises(RuntimeError, match="not RFC1918-private"): with pytest.raises(RuntimeError, match="not RFC1918-private"):
@ -153,6 +156,7 @@ def test_database_identity_requires_private_tls_and_read_only_target() -> None:
"default_transaction_read_only": "on", "default_transaction_read_only": "on",
}, },
"teleo_clone_test", "teleo_clone_test",
"1234",
) )
@ -184,6 +188,7 @@ def test_database_fingerprint_uses_full_normalized_manifest(monkeypatch: pytest.
"host": "10.61.0.3", "host": "10.61.0.3",
"target_db": "teleo_clone_test", "target_db": "teleo_clone_test",
"manifest_sql": manifest_sql, "manifest_sql": manifest_sql,
"ssl_root_cert": tmp_path / "cloudsql-server-ca.pem",
}, },
)() )()
output = "\n".join( output = "\n".join(
@ -282,6 +287,7 @@ def test_generated_wrapper_executes_only_clone_bound_default_read_only_tool(tmp_
host="10.61.0.3", host="10.61.0.3",
project="teleo-501523", project="teleo-501523",
password_secret="test-secret", password_secret="test-secret",
ssl_root_cert=tmp_path / "cloudsql-server-ca.pem",
run_nonce="test-nonce", run_nonce="test-nonce",
system_exec_path=f"{fake_bin}:/usr/bin:/bin", system_exec_path=f"{fake_bin}:/usr/bin:/bin",
) )

View file

@ -131,6 +131,7 @@ def executor_args(**overrides: object) -> argparse.Namespace:
"project": "teleo-501523", "project": "teleo-501523",
"instance": "teleo-pgvector-standby", "instance": "teleo-pgvector-standby",
"password_secret": "read-secret", "password_secret": "read-secret",
"ssl_root_cert": Path("ops/gcp-teleo-pgvector-standby-server-ca.pem").resolve(),
"read_role": "kb_read", "read_role": "kb_read",
"stage_role": "kb_stage_writer", "stage_role": "kb_stage_writer",
"review_role": "kb_review", "review_role": "kb_review",
@ -229,6 +230,10 @@ def test_executor_binds_every_call_and_defaults_read_only_except_controller_phas
assert len(calls) == 4 assert len(calls) == 4
assert all("dbname=teleo_clone_working_leo" in call["command"][1] for call in calls) assert all("dbname=teleo_clone_working_leo" in call["command"][1] for call in calls)
assert all("sslmode=verify-ca" in call["command"][1] for call in calls)
assert all("sslrootcert=" in call["command"][1] for call in calls)
assert all(call["env"]["PGSSLMODE"] == "verify-ca" for call in calls)
assert all(call["env"]["PGSSLROOTCERT"].endswith("gcp-teleo-pgvector-standby-server-ca.pem") for call in calls)
assert all("expected_database=teleo_clone_working_leo" in call["command"] for call in calls) assert all("expected_database=teleo_clone_working_leo" in call["command"] for call in calls)
assert calls[0]["env"]["PGOPTIONS"] == "-c default_transaction_read_only=on" assert calls[0]["env"]["PGOPTIONS"] == "-c default_transaction_read_only=on"
assert [call["env"]["PGOPTIONS"] for call in calls[1:]] == [ assert [call["env"]["PGOPTIONS"] for call in calls[1:]] == [
@ -535,6 +540,9 @@ def test_lifecycle_wrapper_exposes_only_read_or_exact_stage_and_never_delivery(t
) )
assert catalog_wrapper.count('"prompt_id": "DC-01"') == 2 assert catalog_wrapper.count('"prompt_id": "DC-01"') == 2
assert '--user "$READ_ROLE"' in catalog_wrapper assert '--user "$READ_ROLE"' in catalog_wrapper
assert "sslmode=verify-ca" in catalog_wrapper
assert "sslrootcert=$SSL_ROOT_CERT" in catalog_wrapper
assert "sslmode=require" not in catalog_wrapper
def test_capability_receipt_rejects_superuser_or_wrong_phase_acl() -> None: def test_capability_receipt_rejects_superuser_or_wrong_phase_acl() -> None:

View file

@ -31,7 +31,6 @@ class FakeRunner:
function_posture_override: Mapping[str, Mapping[str, object]] | None = None, function_posture_override: Mapping[str, Mapping[str, object]] | None = None,
stage_definition_override: Mapping[str, object] | None = None, stage_definition_override: Mapping[str, object] | None = None,
catalog_posture_override: Mapping[str, object] | None = None, catalog_posture_override: Mapping[str, object] | None = None,
legacy_catalog_posture_override: Mapping[str, object] | None = None,
) -> None: ) -> None:
self.sqlstate_override = sqlstate_override self.sqlstate_override = sqlstate_override
self.sqlstate_missing_for = sqlstate_missing_for self.sqlstate_missing_for = sqlstate_missing_for
@ -46,7 +45,6 @@ class FakeRunner:
} }
self.stage_definition_override = dict(stage_definition_override or {}) self.stage_definition_override = dict(stage_definition_override or {})
self.catalog_posture_override = dict(catalog_posture_override or {}) self.catalog_posture_override = dict(catalog_posture_override or {})
self.legacy_catalog_posture_override = dict(legacy_catalog_posture_override or {})
self.calls: list[tuple[list[str], dict[str, str], int, bool]] = [] self.calls: list[tuple[list[str], dict[str, str], int, bool]] = []
self.negative = {check.sql: check for check in verifier._negative_checks(RUN_ID, SOURCE_REF)} self.negative = {check.sql: check for check in verifier._negative_checks(RUN_ID, SOURCE_REF)}
@ -94,6 +92,7 @@ class FakeRunner:
"session_user": verifier.RUNTIME_DATABASE_ROLE, "session_user": verifier.RUNTIME_DATABASE_ROLE,
"ssl": True, "ssl": True,
"ssl_version": "TLSv1.3", "ssl_version": "TLSv1.3",
"system_identifier": verifier.EXPECTED_SYSTEM_IDENTIFIER,
} }
identity.update(self.identity_override) identity.update(self.identity_override)
return verifier.CommandResult(0, json.dumps(identity), f"ignored {SECRET_VALUE}") return verifier.CommandResult(0, json.dumps(identity), f"ignored {SECRET_VALUE}")
@ -179,13 +178,6 @@ class FakeRunner:
) )
catalog_posture.update(self.catalog_posture_override) catalog_posture.update(self.catalog_posture_override)
return verifier.CommandResult(0, json.dumps(catalog_posture), f"ignored {SECRET_VALUE}") return verifier.CommandResult(0, json.dumps(catalog_posture), f"ignored {SECRET_VALUE}")
if sql == verifier._legacy_catalog_privilege_posture_sql():
legacy_posture: dict[str, object] = {
field: 0 for field in verifier.LEGACY_CATALOG_ZERO_COUNT_FIELDS
}
legacy_posture["leak"] = SECRET_VALUE
legacy_posture.update(self.legacy_catalog_posture_override)
return verifier.CommandResult(0, json.dumps(legacy_posture), f"ignored {SECRET_VALUE}")
if sql == verifier._canary_count_sql(SOURCE_REF): if sql == verifier._canary_count_sql(SOURCE_REF):
return verifier.CommandResult(0, "0\n", f"ignored {SECRET_VALUE}") return verifier.CommandResult(0, "0\n", f"ignored {SECRET_VALUE}")
if sql == verifier._stage_sql(RUN_ID, SOURCE_REF): if sql == verifier._stage_sql(RUN_ID, SOURCE_REF):
@ -211,6 +203,15 @@ class FakeRunner:
return verifier.CommandResult(0, SECRET_VALUE, f"unexpected success {SECRET_VALUE}") return verifier.CommandResult(0, SECRET_VALUE, f"unexpected success {SECRET_VALUE}")
if check.name == self.sqlstate_missing_for: if check.name == self.sqlstate_missing_for:
return verifier.CommandResult(1, SECRET_VALUE, f"denied without state {SECRET_VALUE}") return verifier.CommandResult(1, SECRET_VALUE, f"denied without state {SECRET_VALUE}")
if check.expected_connection_denial:
return verifier.CommandResult(
2,
SECRET_VALUE,
(
f'connection failed: FATAL: permission denied for database "{check.database}"\n'
f"DETAIL: User does not have CONNECT privilege. {SECRET_VALUE}"
),
)
observed = check.expected_sqlstate observed = check.expected_sqlstate
if self.sqlstate_override is not None and check.name == self.sqlstate_override[0]: if self.sqlstate_override is not None and check.name == self.sqlstate_override[0]:
observed = self.sqlstate_override[1] observed = self.sqlstate_override[1]
@ -243,6 +244,7 @@ def run_success(runner: FakeRunner) -> dict[str, object]:
runner=runner, runner=runner,
base_env=poisoned_environment(), base_env=poisoned_environment(),
effective_user="teleo", effective_user="teleo",
dependency_validator=lambda: None,
) )
@ -253,8 +255,10 @@ def assert_child_boundaries(runner: FakeRunner) -> None:
assert timeout == verifier.COMMAND_TIMEOUT_SECONDS assert timeout == verifier.COMMAND_TIMEOUT_SECONDS
pg_keys = sorted(key for key in env if key.upper().startswith("PG")) pg_keys = sorted(key for key in env if key.upper().startswith("PG"))
if command[0] == verifier.PSQL_BIN: if command[0] == verifier.PSQL_BIN:
assert pg_keys == ["PGPASSWORD"] assert pg_keys == ["PGPASSWORD", "PGSSLMODE", "PGSSLROOTCERT"]
assert env["PGPASSWORD"] == SECRET_VALUE assert env["PGPASSWORD"] == SECRET_VALUE
assert env["PGSSLMODE"] == "verify-ca"
assert env["PGSSLROOTCERT"] == str(verifier.SERVER_CA_PATH)
assert discard_stdout is False assert discard_stdout is False
else: else:
assert pg_keys == [] assert pg_keys == []
@ -284,6 +288,7 @@ def test_live_receipt_contract_is_sanitized_and_uses_exact_child_environments()
"session_user": verifier.RUNTIME_DATABASE_ROLE, "session_user": verifier.RUNTIME_DATABASE_ROLE,
"ssl": True, "ssl": True,
"ssl_version": "TLSv1.3", "ssl_version": "TLSv1.3",
"system_identifier": verifier.EXPECTED_SYSTEM_IDENTIFIER,
} }
assert receipt["checks"]["rolled_back_proposal"] == { assert receipt["checks"]["rolled_back_proposal"] == {
"agent_id_matches": True, "agent_id_matches": True,
@ -331,9 +336,6 @@ def test_live_receipt_contract_is_sanitized_and_uses_exact_child_environments()
**{field: 0 for field in verifier.CATALOG_ZERO_COUNT_FIELDS}, **{field: 0 for field in verifier.CATALOG_ZERO_COUNT_FIELDS},
**{field: True for field in verifier.CATALOG_TRUE_FIELDS}, **{field: True for field in verifier.CATALOG_TRUE_FIELDS},
} }
assert receipt["checks"]["legacy_catalog_privileges"] == {
field: 0 for field in verifier.LEGACY_CATALOG_ZERO_COUNT_FIELDS
}
assert receipt["checks"]["stage_function_definition"] == { assert receipt["checks"]["stage_function_definition"] == {
"acl_exact": True, "acl_exact": True,
"argument_modes": None, "argument_modes": None,
@ -570,7 +572,6 @@ def test_catalog_queries_use_exact_regprocedure_signatures_and_both_membership_d
owner_role_sql = verifier._stage_owner_role_posture_sql() owner_role_sql = verifier._stage_owner_role_posture_sql()
stage_definition_sql = verifier._stage_function_definition_sql() stage_definition_sql = verifier._stage_function_definition_sql()
catalog_sql = verifier._catalog_privilege_posture_sql() catalog_sql = verifier._catalog_privilege_posture_sql()
legacy_sql = verifier._legacy_catalog_privilege_posture_sql()
assert "pg_catalog.to_regprocedure(signature)" in function_sql assert "pg_catalog.to_regprocedure(signature)" in function_sql
for _name, signature, _exists, _execute in verifier.FUNCTION_PRIVILEGE_EXPECTATIONS: for _name, signature, _exists, _execute in verifier.FUNCTION_PRIVILEGE_EXPECTATIONS:
@ -597,12 +598,10 @@ def test_catalog_queries_use_exact_regprocedure_signatures_and_both_membership_d
assert "nspname !~ '^pg_'" in catalog_sql assert "nspname !~ '^pg_'" in catalog_sql
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 "teleo_restore" in legacy_sql assert "public_database_connect_grants" in catalog_sql
assert "pg_catalog.has_schema_privilege" in legacy_sql assert "public_large_object_mutation_routine_execute" in catalog_sql
assert "pg_catalog.has_table_privilege" in legacy_sql assert "'lo_creat'" in catalog_sql
assert "pg_catalog.has_column_privilege" in legacy_sql assert "'lo_open'" in catalog_sql
assert "pg_catalog.has_sequence_privilege" in legacy_sql
assert "pg_catalog.has_function_privilege" in legacy_sql
for schema, relation in verifier.READ_TABLE_ALLOWLIST: for schema, relation in verifier.READ_TABLE_ALLOWLIST:
assert schema in catalog_sql assert schema in catalog_sql
assert relation in catalog_sql assert relation in catalog_sql
@ -630,38 +629,27 @@ def test_any_missing_required_catalog_privilege_fails_closed(field: str) -> None
assert caught.value.code == "catalog_privilege_posture_mismatch" assert caught.value.code == "catalog_privilege_posture_mismatch"
@pytest.mark.parametrize("field", verifier.LEGACY_CATALOG_ZERO_COUNT_FIELDS) def test_legacy_and_template_database_connect_are_both_behaviorally_denied() -> None:
def test_any_teleo_restore_catalog_privilege_fails_closed(field: str) -> None:
runner = FakeRunner(legacy_catalog_posture_override={field: 1})
with pytest.raises(verifier.VerificationError) as caught:
run_success(runner)
assert caught.value.code == "legacy_catalog_privilege_posture_mismatch"
assert caught.value.check == "legacy_catalog_privileges"
assert SECRET_VALUE not in str(caught.value)
def test_teleo_restore_catalog_and_behavioral_denial_are_both_exercised_on_legacy_database() -> None:
runner = FakeRunner() runner = FakeRunner()
receipt = run_success(runner) receipt = run_success(runner)
legacy_catalog_call = next( denials = {check["check"]: check for check in receipt["checks"]["negative_permissions"]}
command for name in ("legacy_database_connect", "template_database_connect"):
assert denials[name] == {
"check": name,
"expected_sqlstate": "42501",
"observed_error_class": "connect_privilege_denied",
"observed_sqlstate": "not_exposed_by_libpq_startup",
"result": "denied",
}
database_calls = [
" ".join(command)
for command, _env, _timeout, _discard in runner.calls for command, _env, _timeout, _discard in runner.calls
if any(part == f"--command={verifier._legacy_catalog_privilege_posture_sql()}" for part in command) if command[0] == verifier.PSQL_BIN
) ]
assert any(f"dbname={verifier.LEGACY_DATABASE}" in part for part in legacy_catalog_call) assert any(f"dbname={verifier.LEGACY_DATABASE}" in command for command in database_calls)
legacy_denial = next( assert any(f"dbname={verifier.TEMPLATE_DATABASE}" in command for command in database_calls)
check for check in receipt["checks"]["negative_permissions"] if check["check"] == "legacy_teleo_restore_read"
)
assert legacy_denial == {
"check": "legacy_teleo_restore_read",
"expected_sqlstate": "42501",
"observed_sqlstate": "42501",
"result": "denied",
}
def test_required_sqlstate_mismatch_fails_closed_without_secret_in_error() -> None: def test_required_sqlstate_mismatch_fails_closed_without_secret_in_error() -> None:
@ -693,6 +681,19 @@ def test_missing_verbose_sqlstate_fails_closed_without_raw_stderr() -> None:
assert SECRET_VALUE not in json.dumps(error.as_dict()) assert SECRET_VALUE not in json.dumps(error.as_dict())
def test_unclassified_database_startup_failure_cannot_masquerade_as_connect_denial() -> None:
runner = FakeRunner(sqlstate_missing_for="legacy_database_connect")
with pytest.raises(verifier.VerificationError) as caught:
run_success(runner)
error = caught.value
assert error.code == "negative_connection_denial_mismatch"
assert error.check == "legacy_database_connect"
assert error.observed_sqlstate == "startup_error_unclassified"
assert SECRET_VALUE not in json.dumps(error.as_dict())
def test_unexpected_permission_success_fails_closed_and_transaction_sql_has_rollback() -> None: def test_unexpected_permission_success_fails_closed_and_transaction_sql_has_rollback() -> None:
runner = FakeRunner(unexpected_success_for="proposal_update") runner = FakeRunner(unexpected_success_for="proposal_update")

View file

@ -78,7 +78,6 @@ def test_later_dropin_effective_override_fails_without_outputting_value(
[ [
("PGPASSWORD", "PG*"), ("PGPASSWORD", "PG*"),
("pgservice", "PG*"), ("pgservice", "PG*"),
("CLOUDSDK_CONFIG", "CLOUDSDK_*"),
("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_*"), ("CLOUDSDK_CORE_PROJECT", "CLOUDSDK_*"),
("CLOUDSDK_AUTH_ACCESS_TOKEN", "CLOUDSDK_*"), ("CLOUDSDK_AUTH_ACCESS_TOKEN", "CLOUDSDK_*"),
("cloudsdk_auth_refresh_token", "CLOUDSDK_*"), ("cloudsdk_auth_refresh_token", "CLOUDSDK_*"),
@ -102,6 +101,11 @@ def test_later_dropin_effective_override_fails_without_outputting_value(
("REQUESTS_CA_BUNDLE", "REQUESTS_CA_BUNDLE"), ("REQUESTS_CA_BUNDLE", "REQUESTS_CA_BUNDLE"),
("curl_ca_bundle", "CURL_CA_BUNDLE"), ("curl_ca_bundle", "CURL_CA_BUNDLE"),
("SSLKEYLOGFILE", "SSLKEYLOGFILE"), ("SSLKEYLOGFILE", "SSLKEYLOGFILE"),
("TELEO_CLOUDSQL_CREDENTIAL_MODE", "TELEO_CLOUDSQL_CREDENTIAL_MODE"),
("TELEO_KB_CLAIM_BASE_URL", "TELEO_KB_CLAIM_BASE_URL"),
("TELEO_KB_READ_ATTEMPTS", "TELEO_KB_READ_ATTEMPTS"),
("TELEO_MEMORY_INCLUDE_EXCERPTS", "TELEO_MEMORY_INCLUDE_EXCERPTS"),
("TELEO_MEMORY_REDACT", "TELEO_MEMORY_REDACT"),
], ],
) )
def test_environment_file_broader_credentials_and_forbidden_fields_fail_closed( def test_environment_file_broader_credentials_and_forbidden_fields_fail_closed(

View file

@ -26,6 +26,8 @@ ROOT = Path(__file__).resolve().parents[1]
BRIDGE_DIR = ROOT / "hermes-agent" / "leoclean-bin" BRIDGE_DIR = ROOT / "hermes-agent" / "leoclean-bin"
DB_CONTEXT_PLUGIN = ROOT / "hermes-agent" / "leoclean-plugins" / "vps" / "leo-db-context" / "__init__.py" DB_CONTEXT_PLUGIN = ROOT / "hermes-agent" / "leoclean-plugins" / "vps" / "leo-db-context" / "__init__.py"
GCP_RUNTIME_ROLE_SQL = ROOT / "ops" / "gcp_leoclean_runtime_role.sql" GCP_RUNTIME_ROLE_SQL = ROOT / "ops" / "gcp_leoclean_runtime_role.sql"
GCP_RUNTIME_WRAPPER = BRIDGE_DIR / "teleo-kb-gcp"
GCP_RUNTIME_PROVISIONER = ROOT / "ops" / "provision_gcp_leoclean_runtime_role.sh"
def test_leoclean_bridge_files_are_present_and_parseable() -> None: def test_leoclean_bridge_files_are_present_and_parseable() -> None:
@ -42,6 +44,71 @@ def test_leoclean_bridge_files_are_present_and_parseable() -> None:
subprocess.run(["bash", "-n", str(wrapper)], check=True) subprocess.run(["bash", "-n", str(wrapper)], check=True)
@pytest.mark.parametrize(
"argument",
[
"--host",
"--host=127.0.0.1",
"--port",
"--port=6543",
"--db",
"--db=postgres",
"--canonical-db",
"--canonical-db=postgres",
"--user",
"--user=postgres",
"--password-secret",
"--password-secret=administrator-secret",
"--project",
"--project=other-project",
"--credential-mode",
"--credential-mode=clone-readonly",
],
)
def test_deployed_gcp_wrapper_executable_rejects_every_identity_override(argument: str) -> None:
injected = "sensitive-override-value"
completed = subprocess.run(
[str(GCP_RUNTIME_WRAPPER), argument, injected],
capture_output=True,
check=False,
text=True,
)
assert completed.returncode == 64
assert "identity and endpoint overrides are forbidden" in completed.stderr
assert injected not in completed.stderr
assert completed.stdout == ""
def test_gcp_wrapper_and_password_provisioner_are_executable_and_syntax_valid() -> None:
assert os.access(GCP_RUNTIME_WRAPPER, os.X_OK)
assert os.access(GCP_RUNTIME_PROVISIONER, os.X_OK)
subprocess.run(["bash", "-n", str(GCP_RUNTIME_WRAPPER)], check=True)
subprocess.run(["bash", "-n", str(GCP_RUNTIME_PROVISIONER)], check=True)
wrapper = GCP_RUNTIME_WRAPPER.read_text(encoding="utf-8")
assert "exec /usr/bin/env -i" in wrapper
assert '/usr/bin/python3 -I "$CLOUDSQL_TOOL" "$@"' in wrapper
assert "TELEO_CLOUDSQL_USER=leoclean_kb_runtime" in wrapper
assert "PGSSLMODE=verify-ca" in wrapper
assert "TELEO_CLOUDSQL_ENABLE_AUDIT_FALLBACK=0" in wrapper
provisioner = GCP_RUNTIME_PROVISIONER.read_text(encoding="utf-8")
capture_runtime = "runtime_password=${TELEO_LEOCLEAN_DB_PASSWORD-}"
capture_admin = "administrator_password=${PGPASSWORD-}"
clear_ambient = "unset TELEO_LEOCLEAN_DB_PASSWORD PGPASSWORD"
first_external_child = 'SCRIPT_DIR="$(cd -- "$script_directory" && pwd -P)"'
assert provisioner.index(capture_runtime) < provisioner.index(first_external_child)
assert provisioner.index(capture_admin) < provisioner.index(first_external_child)
assert provisioner.index(clear_ambient) < provisioner.index(first_external_child)
assert '"$SETSID_BIN" -- "$PSQL_BIN"' in provisioner
assert 'kill -TERM -- "-$provision_pid"' in provisioner
assert 'wait "$provision_pid"' in provisioner
assert "SERVER_CA_SHA256=80701e" in provisioner
assert "--no-password" in provisioner
assert "PGSSLMODE=verify-ca" in provisioner
def test_cloudsql_wrapper_supports_expected_review_gated_commands() -> None: def test_cloudsql_wrapper_supports_expected_review_gated_commands() -> None:
wrapper_text = (BRIDGE_DIR / "teleo-kb").read_text() wrapper_text = (BRIDGE_DIR / "teleo-kb").read_text()
cloudsql_text = (BRIDGE_DIR / "cloudsql_memory_tool.py").read_text() cloudsql_text = (BRIDGE_DIR / "cloudsql_memory_tool.py").read_text()
@ -502,7 +569,7 @@ def test_cloudsql_psql_uses_only_explicit_pg_environment_fields(monkeypatch: pyt
monkeypatch.setattr(module.subprocess, "run", fake_run) monkeypatch.setattr(module.subprocess, "run", fake_run)
assert module.run_psql(args, "select 1;") == "ok\n" assert module.run_psql(args, "select 1;") == "ok\n"
assert captured["command"] == ["psql", "-X", "-At", "-q", "-v", "ON_ERROR_STOP=1"] assert captured["command"] == ["/usr/bin/psql", "-X", "-At", "-q", "-v", "ON_ERROR_STOP=1"]
kwargs = captured["kwargs"] kwargs = captured["kwargs"]
assert isinstance(kwargs, dict) assert isinstance(kwargs, dict)
env = kwargs["env"] env = kwargs["env"]
@ -512,7 +579,8 @@ def test_cloudsql_psql_uses_only_explicit_pg_environment_fields(monkeypatch: pyt
"PGPORT": "5432", "PGPORT": "5432",
"PGDATABASE": "teleo_canonical", "PGDATABASE": "teleo_canonical",
"PGUSER": module.RUNTIME_DB_USER, "PGUSER": module.RUNTIME_DB_USER,
"PGSSLMODE": "require", "PGSSLMODE": "verify-ca",
"PGSSLROOTCERT": module.RUNTIME_SSL_ROOT_CERT,
"PGCONNECT_TIMEOUT": "8", "PGCONNECT_TIMEOUT": "8",
"PGPASSWORD": "scoped-runtime-password", "PGPASSWORD": "scoped-runtime-password",
} }
@ -728,7 +796,7 @@ def test_cloudsql_runtime_read_marker_binds_database_user_private_server_and_ssl
"server_port": int(module.RUNTIME_CLOUDSQL_PORT), "server_port": int(module.RUNTIME_CLOUDSQL_PORT),
"ssl": True, "ssl": True,
"ssl_version": "TLSv1.3", "ssl_version": "TLSv1.3",
"system_identifier": "system-id", "system_identifier": module.RUNTIME_SYSTEM_IDENTIFIER,
"wal_lsn": "0/1", "wal_lsn": "0/1",
} }
captured: dict[str, object] = {} captured: dict[str, object] = {}
@ -771,7 +839,7 @@ def test_cloudsql_runtime_read_marker_rejects_wrong_live_identity(
"server_port": int(module.RUNTIME_CLOUDSQL_PORT), "server_port": int(module.RUNTIME_CLOUDSQL_PORT),
"ssl": True, "ssl": True,
"ssl_version": "TLSv1.3", "ssl_version": "TLSv1.3",
"system_identifier": "system-id", "system_identifier": module.RUNTIME_SYSTEM_IDENTIFIER,
"wal_lsn": "0/1", "wal_lsn": "0/1",
} }
marker[field] = value marker[field] = value
@ -832,14 +900,16 @@ def test_gcp_runtime_role_uses_one_narrow_staging_function() -> None:
assert "stage_leoclean_proposal" in sql assert "stage_leoclean_proposal" in sql
assert "'pending_review'" in sql assert "'pending_review'" in sql
assert "revoke all privileges on all tables in schema public, kb_stage" in sql assert "revoke all privileges on all tables in schema public, kb_stage" in sql
assert "can_insert_proposals_directly" in sql assert "grant insert" in sql.lower()
assert "can_create_database_objects" in sql
assert "can_create_temp_objects" in sql
assert "can_connect_legacy_database" in sql
assert "unexpectedly has database CREATE" in sql assert "unexpectedly has database CREATE" in sql
assert "unexpectedly has schema CREATE" in sql
assert "runtime CONNECT reaches a noncanonical database" in sql
assert "PUBLIC retains database CONNECT" in sql
assert "PUBLIC retains large-object mutator EXECUTE" in sql
assert "REVOKE TEMP FROM PUBLIC would be a" in sql assert "REVOKE TEMP FROM PUBLIC would be a" in sql
assert "can execute unexpected SECURITY DEFINER functions" in sql assert "can execute unexpected SECURITY DEFINER functions" in sql
assert "revoke all privileges (%s) on table %I.%I from %I" in sql assert "revoke connect on database %I from public" in sql
assert "revoke execute on function pg_catalog.%I(%s) from public" in sql
assert re.search( assert re.search(
r"grant\s+insert\s*\([^)]*proposal_type[^)]*payload[^)]*\)\s+" r"grant\s+insert\s*\([^)]*proposal_type[^)]*payload[^)]*\)\s+"
r"on\s+kb_stage\.kb_proposals\s+to\s+leoclean_kb_stage_owner", r"on\s+kb_stage\.kb_proposals\s+to\s+leoclean_kb_stage_owner",
@ -855,12 +925,13 @@ def test_gcp_runtime_role_uses_one_narrow_staging_function() -> None:
assert "grant delete" not in sql.lower() assert "grant delete" not in sql.lower()
assert "create role leoclean_kb_runtime nologin" in sql.lower() assert "create role leoclean_kb_runtime nologin" in sql.lower()
disable_statement = "alter role leoclean_kb_runtime\n with nologin nosuperuser" disable_statement = "alter role leoclean_kb_runtime\n with nologin nosuperuser"
password_statement = "alter role leoclean_kb_runtime password" password_statement = "\\password leoclean_kb_runtime"
assert sql.lower().index(disable_statement) < sql.lower().index(password_statement) assert sql.lower().index(disable_statement) < sql.index(password_statement)
assert sql.lower().index(disable_statement) < sql.index("\\getenv runtime_password") assert "\\getenv runtime_password" not in sql
assert sql.index("\\getenv runtime_password") < sql.lower().index(password_statement) assert "password :'runtime_password'" not in sql.lower()
assert sql.lower().index("with nologin nosuperuser") < sql.index("\\connect teleo_kb") assert sql.lower().index("with nologin nosuperuser") < sql.index("\\connect teleo_canonical")
assert sql.index("\\connect teleo_kb") < sql.lower().rindex("with login nosuperuser") assert sql.index("\\connect teleo_canonical") < sql.lower().rindex("with login nosuperuser")
assert sql.lower().rindex("with login nosuperuser") < sql.rindex("commit;")
assert "leoclean_kb_runtime final login attributes are unsafe" in sql assert "leoclean_kb_runtime final login attributes are unsafe" in sql
@ -869,6 +940,9 @@ def _load_module(path: Path):
assert spec and spec.loader assert spec and spec.loader
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) spec.loader.exec_module(module)
if path.name == "cloudsql_memory_tool.py":
module.runtime_ssl_root_cert = lambda: module.RUNTIME_SSL_ROOT_CERT
module.runtime_cloudsdk_config = lambda: module.RUNTIME_CLOUDSDK_CONFIG
return module return module

View file

@ -1,9 +1,33 @@
import os
import resource
import signal
import subprocess import subprocess
import sys
import time
from pathlib import Path from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1] REPO_ROOT = Path(__file__).resolve().parents[1]
def embedded_service_context_runner_source() -> str:
script = (REPO_ROOT / "deploy" / "sync-gcp-leoclean-runtime.sh").read_text()
marker = "remote_helpers=$(cat <<'REMOTE'\n"
start = script.index(marker) + len(marker)
end = script.index("\nREMOTE\n)", start)
helpers = script[start:end]
blocks = [part.split("\nPY", maxsplit=1)[0] for part in helpers.split("<<'PY'\n")[1:]]
return next(block for block in blocks if "def run_stopped_child(" in block)
def load_service_context_runner() -> dict[str, object]:
namespace: dict[str, object] = {"__name__": "embedded_service_context_runner_test"}
source = embedded_service_context_runner_source()
exec(compile(source, "<embedded-service-context-runner>", "exec"), namespace)
return namespace
def test_teleo_agent_template_supports_optional_per_agent_env_file(): def test_teleo_agent_template_supports_optional_per_agent_env_file():
unit = (REPO_ROOT / "systemd" / "teleo-agent@.service").read_text() unit = (REPO_ROOT / "systemd" / "teleo-agent@.service").read_text()
@ -189,7 +213,7 @@ def test_gcp_runtime_sync_is_source_bound_and_scoped():
assert "sha256sum" in script assert "sha256sum" in script
assert "--preflight-only" in script assert "--preflight-only" in script
assert "--restart" in script assert "--restart" in script
assert '/usr/bin/python3 "$tool_path" status --format json --redacted' in script assert '/usr/bin/python3 -I "$tool_path" status --format json --redacted' in script
assert "sa-teleo-prod-vm@teleo-501523.iam.gserviceaccount.com" in script assert "sa-teleo-prod-vm@teleo-501523.iam.gserviceaccount.com" in script
assert "169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email" in script assert "169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email" in script
assert "ProxyHandler({})" in script assert "ProxyHandler({})" in script
@ -204,7 +228,8 @@ def test_gcp_runtime_sync_is_source_bound_and_scoped():
wrapper_probe = script.split("run_service_wrapper_status() {", maxsplit=1)[1].split( wrapper_probe = script.split("run_service_wrapper_status() {", maxsplit=1)[1].split(
"\n}\n\nservice_fingerprint", maxsplit=1 "\n}\n\nservice_fingerprint", maxsplit=1
)[0] )[0]
assert 'nsenter -t "$main_pid" -m --env' in wrapper_probe assert 'run_in_service_cgroup "$main_pid"' in wrapper_probe
assert 'nsenter -t "$main_pid" -m -n --env' in wrapper_probe
assert "/usr/bin/setpriv" in wrapper_probe assert "/usr/bin/setpriv" in wrapper_probe
assert "--reuid=teleo" in wrapper_probe assert "--reuid=teleo" in wrapper_probe
assert "TELEO_CLOUDSQL_" not in wrapper_probe assert "TELEO_CLOUDSQL_" not in wrapper_probe
@ -218,15 +243,54 @@ def test_gcp_runtime_sync_is_source_bound_and_scoped():
assert "--property=EnvironmentFiles" in script assert "--property=EnvironmentFiles" in script
assert "Service has an effective EnvironmentFile" in script assert "Service has an effective EnvironmentFile" in script
assert "Service has a later drop-in" in script assert "Service has a later drop-in" in script
assert "--property=PrivateNetwork" in script
assert "--property=PrivateUsers" in script
assert "NetworkNamespacePath" in script
assert "IPAddressDeny" in script
assert "IPAddressAllow" in script
assert "RestrictAddressFamilies" in script
assert "RestrictNetworkInterfaces" in script
assert "SystemCallFilter" in script
assert "AppArmorProfile" in script
assert "SELinuxContext" in script
assert '--property="$network_property"' in script
assert "assert_administrator_secret_denied" in script assert "assert_administrator_secret_denied" in script
assert '"stdout_discarded": True' in script assert '"stdout_discarded": True' in script
assert '"credential_context": "systemd_main_pid_environment"' in script assert '"credential_context": "systemd_main_pid_environment"' in script
administrator_probe = script.split("assert_administrator_secret_denied() {", maxsplit=1)[1].split( administrator_probe = script.split("assert_administrator_secret_denied() {", maxsplit=1)[1].split(
"\n}\n\nrun_scoped_status", maxsplit=1 "\n}\n\nrun_scoped_status", maxsplit=1
)[0] )[0]
assert 'nsenter -t "$main_pid" -m --env' in administrator_probe assert 'run_in_service_cgroup "$main_pid"' in administrator_probe
assert 'nsenter -t "$main_pid" -m -n --env' in administrator_probe
assert "/usr/bin/setpriv" in administrator_probe assert "/usr/bin/setpriv" in administrator_probe
assert "env -i" not in administrator_probe assert "env -i" not in administrator_probe
permission_probe = script.split("run_permission_verifier() {", maxsplit=1)[1].split(
"\n}\n\nassert_trusted_executable", maxsplit=1
)[0]
assert 'if [ "$phase" = pre ]; then' in permission_probe
assert 'run_in_service_cgroup "$main_pid"' in permission_probe
assert 'nsenter -t "$main_pid" -m -n' in permission_probe
assert "/usr/bin/env -i" in permission_probe
assert 'verification_tmp="$(sudo mktemp -d /run/' in script
assert "run_in_service_cgroup() {" in script
assert "--property=ControlGroup" in script
assert 'Path(f"/proc/{pid}/cgroup")' in script
assert 'Path("/sys/fs/cgroup").resolve(strict=True)' in script
assert 'cgroup_dir / "cgroup.procs"' in script
assert "os.WUNTRACED" in script
assert "signal.SIGSTOP" in script
assert "signal.SIGCONT" in script
assert "os.waitpid(child, 0)" in script
assert "PR_SET_PDEATHSIG" in script
assert "signal.pthread_sigmask" in script
assert "ParentInterrupted" in script
assert "kill_and_reap(child)" in script
assert "Seccomp_filters" in script
assert '"/proc/$main_pid/attr/current"' in script
assert "/proc/1/ns/user" in script
assert "resource.prlimit" in script
assert "copy_process_limits" in script
assert "configure_stopped_child" in script
assert 'preflight_service_before="$(service_fingerprint)"' in script assert 'preflight_service_before="$(service_fingerprint)"' in script
assert 'assert_service_fingerprint "$preflight_service_before"' in script assert 'assert_service_fingerprint "$preflight_service_before"' in script
assert "backup_path" in script assert "backup_path" in script
@ -253,7 +317,7 @@ def test_gcp_runtime_sync_is_source_bound_and_scoped():
assert 'REMOTE_RUNTIME_BIN="${REMOTE_RUNTIME_BIN:-/usr/local/libexec/livingip/leoclean-kb}"' in script assert 'REMOTE_RUNTIME_BIN="${REMOTE_RUNTIME_BIN:-/usr/local/libexec/livingip/leoclean-kb}"' in script
assert 'sudo install -d -o root -g root -m 0755 "$runtime_parent"' in script assert 'sudo install -d -o root -g root -m 0755 "$runtime_parent"' in script
assert 'sudo install -d -o root -g root -m 0755 "$runtime_bin"' in script assert 'sudo install -d -o root -g root -m 0755 "$runtime_bin"' in script
assert 'remote_tmp="$(sudo mktemp -d /var/tmp/teleo-gcp-leoclean-runtime.XXXXXX)"' in script assert 'remote_tmp="$(sudo mktemp -d /run/teleo-gcp-leoclean-runtime.XXXXXX)"' in script
assert 'sudo install -d -o root -g root -m 0755 "$payload_dir"' in script assert 'sudo install -d -o root -g root -m 0755 "$payload_dir"' in script
assert 'sudo install -d -o root -g root -m 0700 "$backup_dir"' in script assert 'sudo install -d -o root -g root -m 0700 "$backup_dir"' in script
assert 'sudo install -d -o teleo -g teleo -m 0700 "$work_dir"' in script assert 'sudo install -d -o teleo -g teleo -m 0700 "$work_dir"' in script
@ -288,7 +352,10 @@ def test_gcp_runtime_sync_is_source_bound_and_scoped():
assert 'for executable_dir in \\\n /usr/local/sbin \\' in script assert 'for executable_dir in \\\n /usr/local/sbin \\' in script
assert 'sudo stat -Lc \'%U:%G\' "$executable_dir"' in script assert 'sudo stat -Lc \'%U:%G\' "$executable_dir"' in script
assert 'sudo -n -u teleo test ! -w "$executable_dir"' in script assert 'sudo -n -u teleo test ! -w "$executable_dir"' in script
assert 'for protected_file in /bin/bash /usr/bin/curl /usr/bin/python3' in script assert (
'for protected_file in /bin/bash /usr/bin/curl /usr/bin/env /usr/bin/gcloud '
'/usr/bin/psql /usr/bin/python3 /usr/bin/setpriv' in script
)
assert '/usr/bin/curl -q --noproxy' in script assert '/usr/bin/curl -q --noproxy' in script
assert 'test "$(sudo cat "$revision")" = "$SOURCE_REVISION"' in script assert 'test "$(sudo cat "$revision")" = "$SOURCE_REVISION"' in script
assert 'assert_existing_root_directory "$existing_directory"' in script assert 'assert_existing_root_directory "$existing_directory"' in script
@ -314,30 +381,39 @@ def test_gcp_runtime_sync_is_source_bound_and_scoped():
assert 'sudo install -o root -g root -m 0755 "$work_dir/' not in script assert 'sudo install -o root -g root -m 0755 "$work_dir/' not in script
assert 'nsenter -t "$main_pid" -m -- sha256sum "$SERVICE_PROFILE_BIN/teleo-kb"' in script assert 'nsenter -t "$main_pid" -m -- sha256sum "$SERVICE_PROFILE_BIN/teleo-kb"' in script
assert 'findmnt -T "$SERVICE_PROFILE_BIN"' in script assert 'findmnt -T "$SERVICE_PROFILE_BIN"' in script
assert 'findmnt -M "$SERVICE_HERMES_ROOT"' in script assert "findmnt -M /home/teleo" in script
assert 'findmnt -M "$SERVICE_PROFILE_ROOT/state"' in script assert 'findmnt -M "$SERVICE_PROFILE_ROOT/state"' in script
assert 'findmnt -M "$SERVICE_PROFILE_ROOT/workspace"' in script assert 'findmnt -M "$SERVICE_PROFILE_ROOT/workspace"' in script
assert "cap_sys_admin" in script assert "for capability_field in CapInh CapPrm CapEff CapBnd CapAmb" in script
assert "0000000000000000" in script
assert '"current_tier": "T3_live_readonly"' in script assert '"current_tier": "T3_live_readonly"' in script
assert '"status": "pass"' in script assert '"status": "pass"' in script
assert "UnsetEnvironment=PGPASSWORD" in dropin assert "UnsetEnvironment=PGPASSWORD" in dropin
assert "UnsetEnvironment=GOOGLE_APPLICATION_CREDENTIALS CLOUDSDK_CONFIG" in dropin assert (
"UnsetEnvironment=GOOGLE_APPLICATION_CREDENTIALS CLOUDSDK_CORE_PROJECT "
"CLOUDSDK_AUTH_ACCESS_TOKEN" in dropin
)
assert "UnsetEnvironment=BASH_ENV ENV BASHOPTS SHELLOPTS" in dropin assert "UnsetEnvironment=BASH_ENV ENV BASHOPTS SHELLOPTS" in dropin
assert "UnsetEnvironment=LD_PRELOAD LD_LIBRARY_PATH LD_AUDIT" in dropin assert "UnsetEnvironment=LD_PRELOAD LD_LIBRARY_PATH LD_AUDIT" in dropin
assert "UnsetEnvironment=PYTHONHOME PYTHONPATH PYTHONSTARTUP PYTHONINSPECT" in dropin assert "UnsetEnvironment=PYTHONHOME PYTHONPATH PYTHONSTARTUP PYTHONINSPECT" in dropin
assert "UnsetEnvironment=HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY" in dropin assert "UnsetEnvironment=HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY" in dropin
assert "UnsetEnvironment=SSL_CERT_FILE SSL_CERT_DIR REQUESTS_CA_BUNDLE CURL_CA_BUNDLE SSLKEYLOGFILE" in dropin assert "UnsetEnvironment=SSL_CERT_FILE SSL_CERT_DIR REQUESTS_CA_BUNDLE CURL_CA_BUNDLE SSLKEYLOGFILE" in dropin
assert (
"UnsetEnvironment=TELEO_CLOUDSQL_CREDENTIAL_MODE TELEO_KB_CLAIM_BASE_URL "
"TELEO_KB_READ_ATTEMPTS TELEO_MEMORY_INCLUDE_EXCERPTS TELEO_MEMORY_REDACT" in dropin
)
assert ( assert (
"BindReadOnlyPaths=/usr/local/libexec/livingip/leoclean-kb:/home/teleo/.hermes/profiles/leoclean/bin" in dropin "BindReadOnlyPaths=/usr/local/libexec/livingip/leoclean-kb:/home/teleo/.hermes/profiles/leoclean/bin" in dropin
) )
assert "ReadOnlyPaths=/home/teleo/.hermes" in dropin assert "ReadOnlyPaths=/home/teleo" in dropin
assert ( assert (
"ReadWritePaths=/home/teleo/.hermes/profiles/leoclean/state " "ReadWritePaths=/home/teleo/.hermes/profiles/leoclean/state "
"/home/teleo/.hermes/profiles/leoclean/workspace" in dropin "/home/teleo/.hermes/profiles/leoclean/workspace" in dropin
) )
assert "CapabilityBoundingSet=~CAP_SYS_ADMIN" in dropin assert "CapabilityBoundingSet=" in dropin
assert "AmbientCapabilities=" in dropin
assert "RuntimeDirectory=" not in dropin assert "RuntimeDirectory=" not in dropin
assert "Environment=CLOUDSDK_" not in dropin assert "Environment=CLOUDSDK_CONFIG=/usr/local/libexec/livingip/leoclean-kb/gcloud-config" in dropin
assert "Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" in dropin assert "Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" in dropin
assert "TELEO_KB_MODE=cloudsql" in dropin assert "TELEO_KB_MODE=cloudsql" in dropin
assert "TELEO_GCP_METADATA_ONLY=1" in dropin assert "TELEO_GCP_METADATA_ONLY=1" in dropin
@ -374,7 +450,7 @@ def test_gcp_runtime_embedded_remote_programs_are_bash_syntax_valid() -> None:
assert completed.returncode == 0, f"{name}: {completed.stderr}" assert completed.returncode == 0, f"{name}: {completed.stderr}"
embedded_python = [part.split("\nPY", maxsplit=1)[0] for part in blocks["remote_helpers"].split("<<'PY'\n")[1:]] embedded_python = [part.split("\nPY", maxsplit=1)[0] for part in blocks["remote_helpers"].split("<<'PY'\n")[1:]]
assert len(embedded_python) == 2 assert len(embedded_python) == 4
for index, body in enumerate(embedded_python): for index, body in enumerate(embedded_python):
completed = subprocess.run( completed = subprocess.run(
["python3", "-c", "import sys; compile(sys.stdin.read(), '<embedded>', 'exec')"], ["python3", "-c", "import sys; compile(sys.stdin.read(), '<embedded>', 'exec')"],
@ -386,6 +462,162 @@ def test_gcp_runtime_embedded_remote_programs_are_bash_syntax_valid() -> None:
assert completed.returncode == 0, f"embedded_python_{index}: {completed.stderr}" assert completed.returncode == 0, f"embedded_python_{index}: {completed.stderr}"
@pytest.mark.parametrize(
("command", "expected_returncode"),
[
(["/bin/sh", "-c", "exit 0"], 0),
(["/bin/sh", "-c", "exit 7"], 7),
(["/bin/sh", "-c", "kill -TERM $$"], 128 + signal.SIGTERM),
],
)
def test_service_context_runner_propagates_result_and_reaps_child(
command: list[str],
expected_returncode: int,
) -> None:
run_stopped_child = load_service_context_runner()["run_stopped_child"]
moved_children: list[int] = []
result = run_stopped_child( # type: ignore[operator]
command,
"/test.service",
move_child=moved_children.append,
read_child_cgroup=lambda _pid: "/test.service",
prepare_child=lambda _parent: None,
)
assert result == expected_returncode
assert len(moved_children) == 1
with pytest.raises(ProcessLookupError):
os.kill(moved_children[0], 0)
def test_service_context_runner_reaps_stopped_child_when_move_fails() -> None:
run_stopped_child = load_service_context_runner()["run_stopped_child"]
stopped_children: list[int] = []
def fail_move(child: int) -> None:
stopped_children.append(child)
raise RuntimeError("synthetic move failure")
with pytest.raises(RuntimeError, match="synthetic move failure"):
run_stopped_child( # type: ignore[operator]
["/bin/sh", "-c", "exit 0"],
"/test.service",
move_child=fail_move,
read_child_cgroup=lambda _pid: "/test.service",
prepare_child=lambda _parent: None,
)
assert len(stopped_children) == 1
with pytest.raises(ProcessLookupError):
os.kill(stopped_children[0], 0)
@pytest.mark.skipif(not hasattr(resource, "prlimit"), reason="requires Linux prlimit")
def test_service_context_runner_copies_mainpid_resource_limits(tmp_path: Path) -> None:
namespace = load_service_context_runner()
run_stopped_child = namespace["run_stopped_child"]
copy_process_limits = namespace["copy_process_limits"]
limit_readback = tmp_path / "limits.txt"
child_program = (
"import resource,sys; from pathlib import Path; "
"limits=resource.getrlimit(resource.RLIMIT_NOFILE); "
"Path(sys.argv[1]).write_text(f'{limits[0]}:{limits[1]}', encoding='ascii')"
)
def lower_nofile_limit() -> None:
resource.setrlimit(resource.RLIMIT_NOFILE, (64, 64))
source = subprocess.Popen(
[sys.executable, "-c", "import time; time.sleep(30)"],
preexec_fn=lower_nofile_limit,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
try:
result = run_stopped_child( # type: ignore[operator]
[sys.executable, "-c", child_program, str(limit_readback)],
"/test.service",
move_child=lambda _child: None,
read_child_cgroup=lambda _pid: "/test.service",
prepare_child=lambda _parent: None,
configure_stopped_child=lambda child: copy_process_limits(source.pid, child), # type: ignore[operator]
)
finally:
source.terminate()
source.wait(timeout=5)
assert result == 0
assert limit_readback.read_text(encoding="ascii") == "64:64"
@pytest.mark.parametrize("signum", [signal.SIGTERM, signal.SIGHUP])
def test_service_context_runner_interruption_kills_and_reaps_probe(
tmp_path: Path,
signum: signal.Signals,
) -> None:
run_stopped_child = load_service_context_runner()["run_stopped_child"]
child_pid_path = tmp_path / f"probe-{signum.name}.pid"
child_program = (
"import os,sys,time; from pathlib import Path; "
"Path(sys.argv[1]).write_text(str(os.getpid()), encoding='ascii'); "
"time.sleep(30)"
)
runner_pid = os.fork()
if runner_pid == 0:
result = run_stopped_child( # type: ignore[operator]
[sys.executable, "-c", child_program, str(child_pid_path)],
"/test.service",
move_child=lambda _child: None,
read_child_cgroup=lambda _pid: "/test.service",
prepare_child=lambda _parent: None,
)
os._exit(result)
runner_reaped = False
probe_pid: int | None = None
try:
deadline = time.monotonic() + 5
probe_pid_text = ""
while time.monotonic() < deadline:
if child_pid_path.is_file():
probe_pid_text = child_pid_path.read_text(encoding="ascii")
if probe_pid_text.isdecimal():
break
waited, _status = os.waitpid(runner_pid, os.WNOHANG)
if waited == runner_pid:
runner_reaped = True
pytest.fail("service-context runner exited before probe start")
time.sleep(0.01)
assert probe_pid_text.isdecimal()
probe_pid = int(probe_pid_text)
os.kill(runner_pid, signum)
waited, status = os.waitpid(runner_pid, 0)
runner_reaped = True
assert waited == runner_pid
assert os.WIFSIGNALED(status)
assert os.WTERMSIG(status) == signum
with pytest.raises(ProcessLookupError):
os.kill(probe_pid, 0)
finally:
if not runner_reaped:
try:
os.kill(runner_pid, signal.SIGKILL)
except ProcessLookupError:
pass
try:
os.waitpid(runner_pid, 0)
except ChildProcessError:
pass
if probe_pid is not None:
try:
os.kill(probe_pid, signal.SIGKILL)
except ProcessLookupError:
pass
def test_gcp_runtime_rejects_preexisting_symlink_before_mutation(tmp_path: Path) -> None: def test_gcp_runtime_rejects_preexisting_symlink_before_mutation(tmp_path: Path) -> None:
script = (REPO_ROOT / "deploy" / "sync-gcp-leoclean-runtime.sh").read_text() script = (REPO_ROOT / "deploy" / "sync-gcp-leoclean-runtime.sh").read_text()
function_start = script.index("assert_existing_root_directory() {") function_start = script.index("assert_existing_root_directory() {")
@ -428,10 +660,43 @@ set -euo pipefail
SERVICE=leoclean.service SERVICE=leoclean.service
REMOTE_DROPIN_DIR=/etc/systemd/system/leoclean.service.d REMOTE_DROPIN_DIR=/etc/systemd/system/leoclean.service.d
DROPIN_NAME=10-cloudsql-memory.conf DROPIN_NAME=10-cloudsql-memory.conf
REMOTE_RUNTIME_BIN=/usr/local/libexec/livingip/leoclean-kb
SERVICE_PROFILE_ROOT=/home/teleo/.hermes/profiles/leoclean
SERVICE_PROFILE_BIN=$SERVICE_PROFILE_ROOT/bin
assert_exact_word_set() {{
local actual="$1" expected="$2" label="$3" actual_sorted expected_sorted word
actual_sorted="$(for word in $actual; do printf '%s\\n' "${{word#-}}"; done | sort)"
expected_sorted="$(for word in $expected; do printf '%s\\n' "${{word#-}}"; done | sort)"
[ "$actual_sorted" = "$expected_sorted" ] || {{
echo "Service effective $label differs from the reviewed exact allowlist." >&2
return 1
}}
}}
systemctl() {{ systemctl() {{
case "$*" in local argument property=
*--property=DropInPaths*) printf '%s\\n' "${{FAKE_DROPINS:-}}" ;; for argument in "$@"; do
*--property=EnvironmentFiles*) printf '%s\\n' "${{FAKE_ENVIRONMENT_FILES:-}}" ;; case "$argument" in --property=*) property=${{argument#--property=}} ;; esac
done
case "$property" in
DropInPaths) printf '%s\\n' "${{FAKE_DROPINS:-}}" ;;
EnvironmentFiles) printf '%s\\n' "${{FAKE_ENVIRONMENT_FILES:-}}" ;;
LoadCredential|LoadCredentialEncrypted|SetCredential|SetCredentialEncrypted|ImportCredential|ExecCondition|ExecStartPre|ExecStartPost|ExecReload|ExecStop|ExecStopPost)
local variable="FAKE_$property"
printf '%s\\n' "${{!variable:-}}"
;;
CapabilityBoundingSet|AmbientCapabilities|BindPaths|TemporaryFileSystem) printf '\\n' ;;
NoNewPrivileges) printf 'yes\\n' ;;
PrivateNetwork) printf '%s\\n' "${{FAKE_PrivateNetwork:-no}}" ;;
PrivateUsers) printf '%s\\n' "${{FAKE_PrivateUsers:-no}}" ;;
NetworkNamespacePath|IPAddressDeny|IPAddressAllow|RestrictAddressFamilies|RestrictNetworkInterfaces|SocketBindAllow|SocketBindDeny|SystemCallFilter|AppArmorProfile|SELinuxContext)
local variable="FAKE_$property"
printf '%s\\n' "${{!variable:-}}"
;;
Group|SupplementaryGroups) printf 'teleo\\n' ;;
ReadOnlyPaths) printf '%s\\n' "${{FAKE_ReadOnlyPaths:-/home/teleo}}" ;;
ReadWritePaths) printf '%s\\n' "$SERVICE_PROFILE_ROOT/state $SERVICE_PROFILE_ROOT/workspace" ;;
BindReadOnlyPaths) printf '%s\\n' "$REMOTE_RUNTIME_BIN:$SERVICE_PROFILE_BIN" ;;
InaccessiblePaths) printf '%s\\n' '/home/teleo/.config/gcloud /home/teleo/.pgpass /home/teleo/.pg_service.conf /home/teleo/.postgresql' ;;
*) return 90 ;; *) return 90 ;;
esac esac
}} }}
@ -445,6 +710,19 @@ assert_unit_configuration
"FAKE_ENVIRONMENT_FILES": "", "FAKE_ENVIRONMENT_FILES": "",
}, },
{"FAKE_DROPINS": reviewed, "FAKE_ENVIRONMENT_FILES": "/etc/leoclean-runtime.env"}, {"FAKE_DROPINS": reviewed, "FAKE_ENVIRONMENT_FILES": "/etc/leoclean-runtime.env"},
{"FAKE_DROPINS": reviewed, "FAKE_ExecStartPost": "/usr/local/bin/conflicting-hook"},
{"FAKE_DROPINS": reviewed, "FAKE_ReadOnlyPaths": "/home/teleo /tmp"},
{"FAKE_DROPINS": reviewed, "FAKE_PrivateNetwork": "yes"},
{"FAKE_DROPINS": reviewed, "FAKE_PrivateUsers": "yes"},
{"FAKE_DROPINS": reviewed, "FAKE_NetworkNamespacePath": "/run/netns/restricted"},
{"FAKE_DROPINS": reviewed, "FAKE_IPAddressDeny": "any"},
{"FAKE_DROPINS": reviewed, "FAKE_IPAddressAllow": "10.61.0.4/32"},
{"FAKE_DROPINS": reviewed, "FAKE_RestrictAddressFamilies": "AF_UNIX"},
{"FAKE_DROPINS": reviewed, "FAKE_RestrictNetworkInterfaces": "~ens4"},
{"FAKE_DROPINS": reviewed, "FAKE_SocketBindDeny": "any"},
{"FAKE_DROPINS": reviewed, "FAKE_SystemCallFilter": "@system-service ~@network-io"},
{"FAKE_DROPINS": reviewed, "FAKE_AppArmorProfile": "restricted-profile"},
{"FAKE_DROPINS": reviewed, "FAKE_SELinuxContext": "system_u:system_r:restricted_t:s0"},
) )
for environment in cases: for environment in cases:
completed = subprocess.run( completed = subprocess.run(
@ -462,12 +740,53 @@ assert_unit_configuration
input=harness, input=harness,
capture_output=True, capture_output=True,
check=False, check=False,
env={"FAKE_DROPINS": reviewed, "FAKE_ENVIRONMENT_FILES": ""}, env={"FAKE_DROPINS": reviewed},
text=True, text=True,
) )
assert allowed.returncode == 0, allowed.stderr assert allowed.returncode == 0, allowed.stderr
def test_gcp_runtime_effective_execstart_override_is_rejected_before_process_probe() -> None:
script = (REPO_ROOT / "deploy" / "sync-gcp-leoclean-runtime.sh").read_text()
function_start = script.index("assert_service_running() {")
function_end = script.index("\n\nassert_unit_configuration() {", function_start)
service_function = script[function_start:function_end]
harness = f"""
set -euo pipefail
SERVICE=leoclean.service
EXPECTED_HERMES_EXECUTABLE=/home/teleo/.hermes/hermes-agent/hermes.py
EXPECTED_HERMES_PYTHON=/home/teleo/.hermes/hermes-agent/venv/bin/python
systemctl() {{
local argument property=
for argument in "$@"; do
case "$argument" in --property=*) property=${{argument#--property=}} ;; esac
done
case "$property" in
ActiveState) printf 'active\\n' ;;
SubState) printf 'running\\n' ;;
MainPID) printf '1234\\n' ;;
User) printf 'teleo\\n' ;;
ExecStart) printf '{{ path=/usr/bin/python3 ; argv[]=/usr/bin/python3 /tmp/conflicting.py ; }}\\n' ;;
*) return 90 ;;
esac
}}
ps() {{ printf 'teleo\\n'; }}
{service_function}
assert_service_running false
"""
completed = subprocess.run(
["bash", "-s"],
input=harness,
capture_output=True,
check=False,
text=True,
)
assert completed.returncode != 0
assert "effective ExecStart is not the reviewed" in completed.stderr
def test_gcp_runtime_absence_rollback_removes_every_created_directory(tmp_path: Path) -> None: def test_gcp_runtime_absence_rollback_removes_every_created_directory(tmp_path: Path) -> None:
script = (REPO_ROOT / "deploy" / "sync-gcp-leoclean-runtime.sh").read_text() script = (REPO_ROOT / "deploy" / "sync-gcp-leoclean-runtime.sh").read_text()
function_start = script.index("backup_directory_state() {") function_start = script.index("backup_directory_state() {")