Fail closed on cross-provider WIF mappings

This commit is contained in:
twentyOne2x 2026-07-15 22:45:24 +02:00
parent 1a3c7fa9f4
commit 0296176355
4 changed files with 151 additions and 2 deletions

View file

@ -87,7 +87,9 @@ they do not route production traffic.
The deployer binding uses the provider-unique
`attribute.observatory_workflow` principal set. A pool-wide subject binding
or the general `living-ip-github` provider is diagnostic-only and must not
authenticate the preflight or deploy jobs.
authenticate the preflight or deploy jobs. The preflight lists every
provider in `github-actions`, including soft-deleted providers, and blocks
if any non-dedicated provider maps that attribute.
3. Add the dedicated runtime service account as a Cloud SQL IAM
service-account user on `teleo-pgvector-standby`.
4. From the existing private GCP VM database-admin path, run

View file

@ -54,6 +54,7 @@ PREFLIGHT_PERMISSIONS = {
"iam.serviceAccounts.getIamPolicy",
"iam.roles.get",
"iam.workloadIdentityPoolProviders.get",
"iam.workloadIdentityPoolProviders.list",
"resourcemanager.projects.get",
"resourcemanager.projects.getIamPolicy",
"run.operations.get",
@ -307,6 +308,35 @@ def wif_provider_is_exact(value: str) -> bool:
)
def wif_provider_inventory_is_isolated(value: str) -> bool:
providers = json.loads(value)
if not isinstance(providers, list) or not providers:
return False
dedicated: list[dict[str, object]] = []
provider_names: set[str] = set()
for provider in providers:
if not isinstance(provider, dict):
return False
name = provider.get("name")
if not isinstance(name, str) or not name:
return False
provider_id = name.rsplit("/", 1)[-1]
if provider_id in provider_names:
return False
provider_names.add(provider_id)
mapping = provider.get("attributeMapping", {})
if not isinstance(mapping, dict):
return False
if provider_id == DEPLOY_WIF_PROVIDER:
dedicated.append(provider)
elif "attribute.observatory_workflow" in mapping:
return False
return len(dedicated) == 1 and wif_provider_is_exact(json.dumps(dedicated[0]))
def deployer_service_account_policy_is_exact(value: str) -> bool:
policy = json.loads(value)
return role_has_exact_unconditional_members(
@ -482,6 +512,26 @@ def build_checks(image_ref: str) -> list[Check]:
wif_provider_is_exact,
"main_and_exact_workflow_only",
),
command_check(
"wif_pool_provider_attribute_isolation",
[
"gcloud",
"iam",
"workload-identity-pools",
"providers",
"list",
"--project",
PROJECT,
"--location",
"global",
"--workload-identity-pool",
WIF_POOL,
"--show-deleted",
"--format=json",
],
wif_provider_inventory_is_isolated,
"dedicated_provider_is_sole_observatory_workflow_mapper",
),
command_check(
"preflight_custom_role",
[

View file

@ -55,6 +55,7 @@ PREFLIGHT_PERMISSIONS = (
"iam.serviceAccounts.getIamPolicy",
"iam.roles.get",
"iam.workloadIdentityPoolProviders.get",
"iam.workloadIdentityPoolProviders.list",
"resourcemanager.projects.get",
"resourcemanager.projects.getIamPolicy",
"run.operations.get",
@ -448,6 +449,13 @@ def build_plan() -> dict[str, object]:
"fallback_provider_allowed": False,
"required_condition": DEPLOY_WIF_ATTRIBUTE_CONDITION,
},
"provider_isolation": {
"pool": WIF_POOL,
"dedicated_provider": DEPLOY_WIF_PROVIDER,
"exclusive_attribute": "attribute.observatory_workflow",
"all_providers_must_be_enumerated": True,
"non_dedicated_provider_mapping_allowed": False,
},
"required_apis": list(REQUIRED_APIS),
"preflight_custom_role": {
"name": PREFLIGHT_ROLE,
@ -482,7 +490,8 @@ def build_plan() -> dict[str, object]:
"post_apply_checks": [
f"gcloud projects describe {PROJECT} --format=value(projectId)",
"python3 ops/check_gcp_infra_readiness.py",
"Require the adapter preflight to verify the dedicated WIF provider, custom roles, IAM bindings, private Cloud SQL, secret, and immutable image.",
"Require the adapter preflight to enumerate every provider in the pool and prove only the dedicated provider maps attribute.observatory_workflow.",
"Require the adapter preflight to verify custom roles, IAM bindings, private Cloud SQL, secret, and immutable image.",
(
"gh workflow run gcp-observatory-read-adapter.yml --repo "
f"{GITHUB_REPOSITORY} --ref main -f action=preflight_staging"

View file

@ -54,6 +54,14 @@ def test_plan_splits_build_deploy_and_runtime_identities() -> None:
"fallback_provider_allowed": False,
"required_condition": plan["conditions"]["deployer_wif"],
}
assert plan["provider_isolation"] == {
"pool": "github-actions",
"dedicated_provider": "observatory-read-adapter-main",
"exclusive_attribute": "attribute.observatory_workflow",
"all_providers_must_be_enumerated": True,
"non_dedicated_provider_mapping_allowed": False,
}
assert "iam.workloadIdentityPoolProviders.list" in plan["preflight_custom_role"]["permissions"]
assert "remove-iam-policy-binding" in commands
assert plan["legacy_pool_wide_github_wif_member_removed"] in commands
assert "roles/run.serviceAgent" in commands
@ -269,6 +277,10 @@ def test_preflight_end_to_end_snapshot_retains_no_secret(monkeypatch) -> None:
]
}
provider = {
"name": (
"projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/"
"observatory-read-adapter-main"
),
"state": "ACTIVE",
"attributeCondition": preflight.DEPLOY_WIF_ATTRIBUTE_CONDITION,
"attributeMapping": {
@ -293,6 +305,24 @@ def test_preflight_end_to_end_snapshot_retains_no_secret(monkeypatch) -> None:
stdout = "ENABLED\n"
elif "workload-identity-pools providers describe" in joined:
stdout = json.dumps(provider)
elif "workload-identity-pools providers list" in joined:
stdout = json.dumps(
[
provider,
{
"name": (
"projects/785938879453/locations/global/workloadIdentityPools/"
"github-actions/providers/living-ip-github"
),
"state": "ACTIVE",
"attributeMapping": {
"google.subject": "assertion.sub",
"attribute.repository": "assertion.repository",
"attribute.ref": "assertion.ref",
},
},
]
)
elif "iam roles describe observatoryStagingPreflight" in joined:
stdout = json.dumps({"includedPermissions": sorted(preflight.PREFLIGHT_PERMISSIONS)})
elif "iam roles describe observatoryStagingDeployer" in joined:
@ -375,6 +405,10 @@ def test_deployer_wif_binding_rejects_pool_wide_or_additional_impersonators() ->
def test_dedicated_provider_requires_provider_unique_workflow_mapping() -> None:
provider = {
"name": (
"projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/"
"observatory-read-adapter-main"
),
"state": "ACTIVE",
"attributeCondition": preflight.DEPLOY_WIF_ATTRIBUTE_CONDITION,
"attributeMapping": {
@ -389,6 +423,60 @@ def test_dedicated_provider_requires_provider_unique_workflow_mapping() -> None:
assert preflight.wif_provider_is_exact(json.dumps(provider)) is True
@pytest.mark.parametrize(
"malicious_mapping",
[
"assertion.workflow_ref",
"'living-ip/teleo-infrastructure/.github/workflows/"
"gcp-observatory-read-adapter.yml@refs/heads/main'",
],
)
def test_provider_inventory_rejects_any_second_observatory_mapper(malicious_mapping: str) -> None:
dedicated = {
"name": (
"projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/"
"observatory-read-adapter-main"
),
"state": "ACTIVE",
"attributeCondition": preflight.DEPLOY_WIF_ATTRIBUTE_CONDITION,
"attributeMapping": {
"google.subject": "assertion.sub",
"attribute.observatory_workflow": "assertion.workflow_ref",
"attribute.repository": "assertion.repository",
"attribute.ref": "assertion.ref",
"attribute.workflow_ref": "assertion.workflow_ref",
},
}
general = {
"name": (
"projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/"
"living-ip-github"
),
"state": "ACTIVE",
"attributeMapping": {
"google.subject": "assertion.sub",
"attribute.repository": "assertion.repository",
},
}
assert preflight.wif_provider_inventory_is_isolated(json.dumps([dedicated, general])) is True
general["attributeMapping"]["attribute.observatory_workflow"] = malicious_mapping
assert preflight.wif_provider_inventory_is_isolated(json.dumps([dedicated, general])) is False
def test_provider_inventory_fails_closed_on_missing_or_duplicate_dedicated_provider() -> None:
general = {
"name": (
"projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/"
"living-ip-github"
),
"state": "ACTIVE",
"attributeMapping": {"google.subject": "assertion.sub"},
}
assert preflight.wif_provider_inventory_is_isolated(json.dumps([general])) is False
assert preflight.wif_provider_inventory_is_isolated(json.dumps([general, general])) is False
def test_malformed_successful_readback_becomes_blocked_check(monkeypatch) -> None:
monkeypatch.setattr(
preflight,