#!/usr/bin/env python3 """Run the adapter-specific, non-mutating GCP staging preflight.""" from __future__ import annotations import argparse import json import re import subprocess from collections.abc import Callable from dataclasses import asdict, dataclass from pathlib import Path PROJECT = "teleo-501523" PROJECT_NUMBER = "785938879453" REGION = "europe-west6" NETWORK = "teleo-staging-net" SUBNET = "teleo-staging-europe-west6" INSTANCE = "teleo-pgvector-standby" SERVICE = "observatory-read-adapter-staging" DEPLOY_SERVICE_ACCOUNT = f"sa-observatory-deployer@{PROJECT}.iam.gserviceaccount.com" RUNTIME_SERVICE_ACCOUNT = f"sa-observatory-read-adapter@{PROJECT}.iam.gserviceaccount.com" BUILD_SERVICE_ACCOUNT = f"sa-artifact-builder@{PROJECT}.iam.gserviceaccount.com" DB_IAM_USER = f"sa-observatory-read-adapter@{PROJECT}.iam" SECRET = "observatory-read-api-key-staging" REPOSITORY = "teleo" WIF_POOL = "github-actions" DEPLOY_WIF_PROVIDER = "observatory-read-adapter-main" DEPLOY_WORKFLOW_REF = ( "living-ip/teleo-infrastructure/.github/workflows/" "gcp-observatory-read-adapter.yml@refs/heads/main" ) DEPLOY_WIF_ATTRIBUTE_CONDITION = ( "assertion.repository=='living-ip/teleo-infrastructure' && " "assertion.ref=='refs/heads/main' && " f"assertion.workflow_ref=='{DEPLOY_WORKFLOW_REF}'" ) DEPLOY_WIF_MEMBER = ( f"principal://iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/" f"workloadIdentityPools/{WIF_POOL}/subject/" "repo:living-ip/teleo-infrastructure:ref:refs/heads/main" ) PREFLIGHT_ROLE = f"projects/{PROJECT}/roles/observatoryStagingPreflight" DEPLOY_ROLE = f"projects/{PROJECT}/roles/observatoryStagingDeployer" PREFLIGHT_PERMISSIONS = { "artifactregistry.repositories.getIamPolicy", "cloudsql.instances.get", "cloudsql.users.list", "compute.networks.get", "compute.subnetworks.get", "iam.serviceAccounts.get", "iam.serviceAccounts.getIamPolicy", "iam.roles.get", "iam.workloadIdentityPoolProviders.get", "resourcemanager.projects.get", "resourcemanager.projects.getIamPolicy", "run.operations.get", "run.services.get", "run.services.getIamPolicy", "secretmanager.secrets.getIamPolicy", "serviceusage.services.get", "serviceusage.services.use", } DEPLOY_PERMISSIONS = { "run.operations.get", "run.services.create", "run.services.get", "run.services.setIamPolicy", "run.services.update", } REQUIRED_APIS = ( "artifactregistry.googleapis.com", "compute.googleapis.com", "iamcredentials.googleapis.com", "run.googleapis.com", "secretmanager.googleapis.com", "sqladmin.googleapis.com", "sts.googleapis.com", ) IMAGE_RE = re.compile( rf"^europe-west6-docker[.]pkg[.]dev/{PROJECT}/teleo/observatory-read-adapter:" r"[0-9a-f]{40}-[0-9]+-[1-9][0-9]*@sha256:[0-9a-f]{64}$" ) @dataclass(frozen=True) class Check: name: str status: str detail: str def sanitize_error(value: str) -> str: value = re.sub(r"/[^\s]*/gha-creds-[^\s]+[.]json", "", value) return " ".join(value.strip().split())[-600:] or "command_failed_without_stderr" def run(command: list[str]) -> subprocess.CompletedProcess[str]: try: return subprocess.run(command, text=True, capture_output=True, timeout=30, check=False) except subprocess.TimeoutExpired: return subprocess.CompletedProcess(command, 124, stdout="", stderr="command_timed_out_after_30_seconds") except OSError as exc: return subprocess.CompletedProcess(command, 127, stdout="", stderr=f"command_unavailable:{exc}") def command_check( name: str, command: list[str], validate: Callable[[str], bool], success_detail: str, ) -> Check: result = run(command) if result.returncode != 0: return Check(name, "blocked", sanitize_error(result.stderr)) try: valid = validate(result.stdout) except (AttributeError, IndexError, KeyError, TypeError, ValueError, json.JSONDecodeError) as exc: return Check(name, "blocked", f"invalid_readback:{type(exc).__name__}") return Check(name, "pass" if valid else "blocked", success_detail if valid else "unexpected_readback") def cloud_sql_is_private_and_iam_enabled(value: str) -> bool: payload = json.loads(value) flags = { item.get("name"): str(item.get("value", "")).lower() for item in payload.get("settings", {}).get("databaseFlags", []) } ip_configuration = payload.get("settings", {}).get("ipConfiguration", {}) address_types = {item.get("type") for item in payload.get("ipAddresses", [])} return ( payload.get("name") == INSTANCE and payload.get("region") == REGION and str(payload.get("databaseVersion", "")).startswith("POSTGRES_") and ip_configuration.get("ipv4Enabled") is False and str(ip_configuration.get("privateNetwork", "")).endswith(f"/networks/{NETWORK}") and "PRIVATE" in address_types and "PRIMARY" not in address_types and flags.get("cloudsql.iam_authentication") == "on" ) def cloud_sql_iam_user_exists(value: str) -> bool: users = json.loads(value) return any( user.get("name") == DB_IAM_USER and user.get("type") == "CLOUD_IAM_SERVICE_ACCOUNT" for user in users ) def subnet_is_exact(value: str) -> bool: payload = json.loads(value) return ( payload.get("name") == SUBNET and str(payload.get("region", "")).endswith(f"/regions/{REGION}") and str(payload.get("network", "")).endswith(f"/networks/{NETWORK}") ) def secret_is_valid(value: str) -> bool: try: encoded = value.encode("ascii") except UnicodeEncodeError: return False return value == value.strip() and 32 <= len(encoded) <= 512 def normalized(value: str) -> str: return " ".join(value.split()) def policy_has_binding( policy: dict[str, object], role: str, member: str, *, condition_title: str | None = None, condition_expression: str | None = None, ) -> bool: bindings = policy.get("bindings", []) if not isinstance(bindings, list): return False for binding in bindings: if not isinstance(binding, dict) or binding.get("role") != role: continue members = binding.get("members", []) if not isinstance(members, list) or member not in members: continue condition = binding.get("condition") if condition_title is None and condition_expression is None: return condition in (None, {}) if not isinstance(condition, dict): continue if condition_title is not None and condition.get("title") != condition_title: continue if condition_expression is not None and normalized(str(condition.get("expression", ""))) != normalized( condition_expression ): continue return True return False def member_roles(policy: dict[str, object], member: str) -> set[str]: roles: set[str] = set() bindings = policy.get("bindings", []) if not isinstance(bindings, list): return roles for binding in bindings: if not isinstance(binding, dict): continue members = binding.get("members", []) role = binding.get("role") if isinstance(members, list) and member in members and isinstance(role, str): roles.add(role) return roles def project_iam_is_exact(value: str) -> bool: policy = json.loads(value) deployer = f"serviceAccount:{DEPLOY_SERVICE_ACCOUNT}" runtime = f"serviceAccount:{RUNTIME_SERVICE_ACCOUNT}" builder = f"serviceAccount:{BUILD_SERVICE_ACCOUNT}" service_agent = ( f"serviceAccount:service-{PROJECT_NUMBER}@serverless-robot-prod.iam.gserviceaccount.com" ) sql_expression = ( f"resource.name == 'projects/{PROJECT}/instances/{INSTANCE}' && " "resource.service == 'sqladmin.googleapis.com'" ) forbidden_deployer = { "roles/artifactregistry.writer", "roles/cloudsql.admin", "roles/compute.admin", "roles/editor", "roles/owner", "roles/run.admin", "roles/run.developer", "roles/secretmanager.admin", } forbidden_builder = forbidden_deployer | { DEPLOY_ROLE, PREFLIGHT_ROLE, "roles/iam.serviceAccountUser", "roles/secretmanager.secretAccessor", } return ( policy_has_binding(policy, PREFLIGHT_ROLE, deployer) and policy_has_binding(policy, DEPLOY_ROLE, deployer) and policy_has_binding( policy, "roles/cloudsql.client", runtime, condition_title="observatory-exact-cloudsql-instance", condition_expression=sql_expression, ) and policy_has_binding( policy, "roles/cloudsql.instanceUser", runtime, condition_title="observatory-exact-cloudsql-instance", condition_expression=sql_expression, ) and policy_has_binding(policy, "roles/run.serviceAgent", service_agent) and not (member_roles(policy, deployer) & forbidden_deployer) and not (member_roles(policy, builder) & forbidden_builder) ) def role_has_exact_permissions(value: str, expected: set[str]) -> bool: payload = json.loads(value) permissions = payload.get("includedPermissions", []) return isinstance(permissions, list) and set(permissions) == expected and payload.get("deleted") is not True def wif_provider_is_exact(value: str) -> bool: payload = json.loads(value) mapping = payload.get("attributeMapping", {}) expected_mapping = { "google.subject": "assertion.sub", "attribute.repository": "assertion.repository", "attribute.ref": "assertion.ref", "attribute.workflow_ref": "assertion.workflow_ref", } return ( payload.get("state") == "ACTIVE" and mapping == expected_mapping and normalized(str(payload.get("attributeCondition", ""))) == normalized(DEPLOY_WIF_ATTRIBUTE_CONDITION) ) def deployer_service_account_policy_is_exact(value: str) -> bool: policy = json.loads(value) return policy_has_binding(policy, "roles/iam.workloadIdentityUser", DEPLOY_WIF_MEMBER) def runtime_service_account_policy_is_exact(value: str) -> bool: policy = json.loads(value) deployer = f"serviceAccount:{DEPLOY_SERVICE_ACCOUNT}" builder = f"serviceAccount:{BUILD_SERVICE_ACCOUNT}" return policy_has_binding(policy, "roles/iam.serviceAccountUser", deployer) and not member_roles(policy, builder) def artifact_policy_is_exact(value: str) -> bool: policy = json.loads(value) deployer = f"serviceAccount:{DEPLOY_SERVICE_ACCOUNT}" return policy_has_binding(policy, "roles/artifactregistry.reader", deployer) def secret_policy_is_exact(value: str) -> bool: policy = json.loads(value) deployer = f"serviceAccount:{DEPLOY_SERVICE_ACCOUNT}" runtime = f"serviceAccount:{RUNTIME_SERVICE_ACCOUNT}" builder = f"serviceAccount:{BUILD_SERVICE_ACCOUNT}" return ( policy_has_binding(policy, "roles/secretmanager.secretAccessor", deployer) and policy_has_binding(policy, "roles/secretmanager.secretAccessor", runtime) and policy_has_binding(policy, "roles/secretmanager.viewer", deployer) and not member_roles(policy, builder) ) def api_key_secret_check() -> Check: version_result = run( [ "gcloud", "secrets", "versions", "list", SECRET, "--project", PROJECT, "--filter=state=ENABLED", "--sort-by=~createTime", "--limit=1", "--format=value(name)", ] ) if version_result.returncode != 0: return Check("api_key_secret", "blocked", sanitize_error(version_result.stderr)) version = version_result.stdout.strip().rsplit("/", 1)[-1] if not version.isdigit(): return Check("api_key_secret", "blocked", "enabled_secret_version_missing") secret_result = run( [ "gcloud", "secrets", "versions", "access", version, "--secret", SECRET, "--project", PROJECT, ] ) if secret_result.returncode != 0: return Check("api_key_secret", "blocked", sanitize_error(secret_result.stderr)) valid = secret_is_valid(secret_result.stdout) detail = f"enabled_version_{version}_valid_length_value_redacted" if valid else "invalid_secret_value" return Check("api_key_secret", "pass" if valid else "blocked", detail) def existing_service_policy_check() -> Check: describe = run( [ "gcloud", "run", "services", "describe", SERVICE, "--project", PROJECT, "--region", REGION, "--format=value(metadata.name)", ] ) if describe.returncode != 0: error = sanitize_error(describe.stderr) if "not found" in error.lower() or "not_found" in error.lower(): return Check("existing_service_policy", "pass", "service_not_created_yet") return Check("existing_service_policy", "blocked", error) if describe.stdout.strip() != SERVICE: return Check("existing_service_policy", "blocked", "unexpected_service_readback") policy_result = run( [ "gcloud", "run", "services", "get-iam-policy", SERVICE, "--project", PROJECT, "--region", REGION, "--format=json", ] ) if policy_result.returncode != 0: return Check("existing_service_policy", "blocked", sanitize_error(policy_result.stderr)) try: policy = json.loads(policy_result.stdout) builder = f"serviceAccount:{BUILD_SERVICE_ACCOUNT}" valid = not member_roles(policy, builder) except (AttributeError, TypeError, ValueError, json.JSONDecodeError) as exc: return Check("existing_service_policy", "blocked", f"invalid_readback:{type(exc).__name__}") return Check( "existing_service_policy", "pass" if valid else "blocked", "artifact_builder_absent" if valid else "artifact_builder_has_service_binding", ) def build_checks(image_ref: str) -> list[Check]: image_name, image_digest = image_ref.rsplit("@", 1) if "@" in image_ref else (image_ref, "") digest_ref = f"{image_name.rsplit(':', 1)[0]}@{image_digest}" if image_digest else image_ref checks = [ command_check( "active_identity", ["gcloud", "auth", "list", "--filter=status:ACTIVE", "--format=value(account)"], lambda value: value.strip() == DEPLOY_SERVICE_ACCOUNT, "dedicated_observatory_deployer", ), command_check( "project", ["gcloud", "projects", "describe", PROJECT, "--format=value(projectId)"], lambda value: value.strip() == PROJECT, PROJECT, ), ] checks.extend( command_check( f"api_{api.removesuffix('.googleapis.com')}", ["gcloud", "services", "describe", api, "--project", PROJECT, "--format=value(state)"], lambda value: value.strip() == "ENABLED", "enabled", ) for api in REQUIRED_APIS ) checks.extend( [ command_check( "deployer_wif_provider", [ "gcloud", "iam", "workload-identity-pools", "providers", "describe", DEPLOY_WIF_PROVIDER, "--project", PROJECT, "--location", "global", "--workload-identity-pool", WIF_POOL, "--format=json", ], wif_provider_is_exact, "main_and_exact_workflow_only", ), command_check( "preflight_custom_role", [ "gcloud", "iam", "roles", "describe", "observatoryStagingPreflight", "--project", PROJECT, "--format=json", ], lambda value: role_has_exact_permissions(value, PREFLIGHT_PERMISSIONS), "exact_read_only_permissions", ), command_check( "deploy_custom_role", [ "gcloud", "iam", "roles", "describe", "observatoryStagingDeployer", "--project", PROJECT, "--format=json", ], lambda value: role_has_exact_permissions(value, DEPLOY_PERMISSIONS), "five_permissions_no_delete", ), command_check( "project_iam", ["gcloud", "projects", "get-iam-policy", PROJECT, "--format=json"], project_iam_is_exact, "required_bindings_present_broad_roles_absent", ), command_check( "deployer_wif_binding", [ "gcloud", "iam", "service-accounts", "get-iam-policy", DEPLOY_SERVICE_ACCOUNT, "--project", PROJECT, "--format=json", ], deployer_service_account_policy_is_exact, "exact_main_subject_workload_identity_user", ), command_check( "runtime_act_as_policy", [ "gcloud", "iam", "service-accounts", "get-iam-policy", RUNTIME_SERVICE_ACCOUNT, "--project", PROJECT, "--format=json", ], runtime_service_account_policy_is_exact, "deployer_only_artifact_builder_absent", ), command_check( "artifact_repository_policy", [ "gcloud", "artifacts", "repositories", "get-iam-policy", REPOSITORY, "--project", PROJECT, "--location", REGION, "--format=json", ], artifact_policy_is_exact, "deployer_reader", ), command_check( "secret_policy", [ "gcloud", "secrets", "get-iam-policy", SECRET, "--project", PROJECT, "--format=json", ], secret_policy_is_exact, "runtime_and_deployer_only_artifact_builder_absent", ), existing_service_policy_check(), ] ) checks.extend( [ command_check( "runtime_service_account", [ "gcloud", "iam", "service-accounts", "describe", RUNTIME_SERVICE_ACCOUNT, "--project", PROJECT, "--format=value(email)", ], lambda value: value.strip() == RUNTIME_SERVICE_ACCOUNT, RUNTIME_SERVICE_ACCOUNT, ), command_check( "vpc_network", [ "gcloud", "compute", "networks", "describe", NETWORK, "--project", PROJECT, "--format=value(name)", ], lambda value: value.strip() == NETWORK, NETWORK, ), command_check( "vpc_subnet", [ "gcloud", "compute", "networks", "subnets", "describe", SUBNET, "--project", PROJECT, "--region", REGION, "--format=json", ], subnet_is_exact, f"{NETWORK}/{REGION}/{SUBNET}", ), command_check( "cloud_sql_private_iam", ["gcloud", "sql", "instances", "describe", INSTANCE, "--project", PROJECT, "--format=json"], cloud_sql_is_private_and_iam_enabled, f"{PROJECT}:{REGION}:{INSTANCE}:private_ip_only:iam_authentication_on", ), command_check( "cloud_sql_iam_user", ["gcloud", "sql", "users", "list", "--instance", INSTANCE, "--project", PROJECT, "--format=json"], cloud_sql_iam_user_exists, DB_IAM_USER, ), api_key_secret_check(), command_check( "immutable_image", ["gcloud", "artifacts", "docker", "images", "describe", digest_ref, "--project", PROJECT, "--format=json"], lambda _value: bool(IMAGE_RE.fullmatch(image_ref)), image_digest or "missing_digest", ), ] ) return checks def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--image-ref", required=True) parser.add_argument("--output", type=Path) args = parser.parse_args() checks = build_checks(args.image_ref) pass_count = sum(check.status == "pass" for check in checks) blocked_count = len(checks) - pass_count receipt = { "schema": "livingip.observatory-read-adapter-gcp-preflight.v1", "project": PROJECT, "required_tier": "T3_live_readonly", "current_tier": "T2_authenticated_gcp_preflight", "ready_for_staging_deploy": blocked_count == 0, "claim_ceiling": "Preflight only; T3 requires the live private Cloud SQL claim and negative receipts.", "identity": DEPLOY_SERVICE_ACCOUNT, "checks": [asdict(check) for check in checks], "pass_count": pass_count, "blocked_count": blocked_count, "secret_values_retained": False, "database_role_live_verification_deferred_to_fail_closed_canary": True, "production_repoint_executed": False, } rendered = json.dumps(receipt, indent=2, sort_keys=True) + "\n" if args.output: args.output.write_text(rendered, encoding="utf-8") else: print(rendered, end="") return 0 if blocked_count == 0 else 1 if __name__ == "__main__": raise SystemExit(main())