teleo-infrastructure/tests/test_apply_gcp_iap_operator_access.py
2026-07-13 05:24:25 +02:00

512 lines
22 KiB
Python

from __future__ import annotations
import copy
import hashlib
import importlib
import json
import subprocess
import sys
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
OPS = ROOT / "ops"
sys.path.insert(0, str(OPS))
apply = importlib.import_module("apply_gcp_iap_operator_access")
sys.path.pop(0)
def _binding(role: str, member: str, condition: tuple[str, str] | None = None) -> dict[str, Any]:
result: dict[str, Any] = {"role": role, "members": [member]}
if condition:
result["condition"] = {"title": condition[0], "expression": condition[1]}
return result
def _full_state() -> dict[str, Any]:
operator_members = [f"serviceAccount:{email}" for _, email, _ in apply.SERVICE_ACCOUNTS]
account_policies = {
email: {"bindings": [_binding("roles/iam.workloadIdentityUser", apply.WIF_PRINCIPAL)]}
for _, email, _ in apply.SERVICE_ACCOUNTS
}
project_bindings: list[dict[str, Any]] = []
for member in operator_members:
project_bindings.extend(
[
_binding(apply.CUSTOM_ROLE, member),
_binding(apply.IAP_ROLE, member, (apply.IAP_CONDITION_TITLE, apply.IAP_CONDITION)),
]
)
return {
"services": set(apply.REQUIRED_APIS),
"pool": {
"name": f"projects/123456789/locations/global/workloadIdentityPools/{apply.POOL}",
"state": "ACTIVE",
"disabled": False,
},
"provider": {
"name": (
f"projects/123456789/locations/global/workloadIdentityPools/{apply.POOL}"
f"/providers/{apply.PROVIDER}"
),
"state": "ACTIVE",
"disabled": False,
"displayName": apply.PROVIDER_DISPLAY_NAME,
"oidc": {"issuerUri": apply.PROVIDER_ISSUER},
"attributeMapping": apply.EXPECTED_ATTRIBUTE_MAPPING,
"attributeCondition": apply.PROVIDER_CONDITION,
},
"accounts": {
email: {"email": email, "displayName": display_name, "disabled": False}
for _, email, display_name in apply.SERVICE_ACCOUNTS
},
"account_policies": account_policies,
"custom_role": {
"title": apply.CUSTOM_ROLE_TITLE,
"description": apply.CUSTOM_ROLE_DESCRIPTION,
"includedPermissions": list(apply.CUSTOM_PERMISSIONS),
"stage": "GA",
},
"project_policy": {"bindings": project_bindings},
"vm_policy": {"bindings": [_binding("roles/iam.serviceAccountUser", member) for member in operator_members]},
"instance": {
"status": "RUNNING",
"networkInterfaces": [
{
"networkIP": apply.INSTANCE_INTERNAL_IP,
"network": f"projects/{apply.PROJECT}/global/networks/{apply.NETWORK}",
}
],
"serviceAccounts": [{"email": apply.VM_SERVICE_ACCOUNT}],
"metadata": {"items": [{"key": "enable-oslogin", "value": "TRUE"}]},
"tags": {"items": [apply.TARGET_TAG]},
},
"instance_policy": {
"bindings": [
_binding("roles/compute.osLogin", operator_members[0]),
_binding("roles/compute.osAdminLogin", operator_members[1]),
]
},
"iap_firewall": {
"direction": "INGRESS",
"network": f"projects/{apply.PROJECT}/global/networks/{apply.NETWORK}",
"allowed": [{"IPProtocol": "tcp", "ports": ["22"]}],
"sourceRanges": [apply.IAP_SOURCE_RANGE],
"targetTags": [apply.TARGET_TAG],
"disabled": False,
"logConfig": {"enable": True},
},
"public_firewall": {
"name": apply.CHANGING_PUBLIC_RULE,
"disabled": False,
"targetTags": ["teleo-public-ssh-bootstrap"],
},
"dispatcher_hash": hashlib.sha256(apply.DEFAULT_DISPATCHER.read_bytes()).hexdigest(),
"dispatcher_file_mode": "root:root:755",
"dispatcher_run_mode": "root:root:700",
}
def _clean_state() -> dict[str, Any]:
state = _full_state()
state.update(
{
"services": set(),
"pool": None,
"provider": None,
"accounts": {email: None for _, email, _ in apply.SERVICE_ACCOUNTS},
"account_policies": {email: None for _, email, _ in apply.SERVICE_ACCOUNTS},
"custom_role": None,
"project_policy": {"bindings": []},
"vm_policy": {"bindings": []},
"instance_policy": {"bindings": []},
"iap_firewall": None,
"dispatcher_hash": None,
"dispatcher_file_mode": None,
"dispatcher_run_mode": None,
}
)
state["instance"]["tags"] = {"items": []}
return state
class FakeGcloud:
def __init__(self, state: dict[str, Any]) -> None:
self.state = copy.deepcopy(state)
self.commands: list[list[str]] = []
self.mutations: list[list[str]] = []
@staticmethod
def _value(command: list[str], flag: str) -> str:
return command[command.index(flag) + 1]
@staticmethod
def _completed(command: list[str], payload: Any = None, *, returncode: int = 0, stderr: str = ""):
stdout = "" if payload is None else json.dumps(payload)
return subprocess.CompletedProcess(command, returncode, stdout, stderr)
@classmethod
def _missing(cls, command: list[str]):
return cls._completed(command, returncode=1, stderr="ERROR: NOT_FOUND: resource does not exist")
@staticmethod
def _add_binding(policy: dict[str, Any], role: str, member: str, condition: tuple[str, str] | None) -> None:
candidate = _binding(role, member, condition)
if candidate not in policy["bindings"]:
policy["bindings"].append(candidate)
def _read(self, command: list[str]):
words = command[1:]
if words[:2] == ["projects", "describe"]:
return self._completed(command, {"projectId": apply.PROJECT})
if words[:2] == ["services", "list"]:
return self._completed(command, [{"config": {"name": name}} for name in sorted(self.state["services"])])
if words[:3] == ["iam", "workload-identity-pools", "describe"]:
value = self.state["pool"]
return self._missing(command) if value is None or value.get("state") == "DELETED" else self._completed(command, value)
if words[:3] == ["iam", "workload-identity-pools", "list"]:
value = self.state["pool"]
return self._completed(command, [] if value is None else [value])
if words[:4] == ["iam", "workload-identity-pools", "providers", "describe"]:
value = self.state["provider"]
return self._missing(command) if value is None or value.get("state") == "DELETED" else self._completed(command, value)
if words[:4] == ["iam", "workload-identity-pools", "providers", "list"]:
value = self.state["provider"]
return self._completed(command, [] if value is None else [value])
if words[:3] == ["iam", "service-accounts", "describe"]:
email = words[3]
value = self.state["accounts"].get(email)
return self._missing(command) if value is None else self._completed(command, value)
if words[:3] == ["iam", "service-accounts", "get-iam-policy"]:
email = words[3]
value = (
self.state["vm_policy"] if email == apply.VM_SERVICE_ACCOUNT else self.state["account_policies"][email]
)
return self._completed(command, value)
if words[:3] == ["iam", "roles", "describe"]:
value = self.state["custom_role"]
return self._missing(command) if value is None else self._completed(command, value)
if words[:2] == ["projects", "get-iam-policy"]:
return self._completed(command, self.state["project_policy"])
if words[:3] == ["compute", "instances", "describe"]:
return self._completed(command, self.state["instance"])
if words[:3] == ["compute", "instances", "get-iam-policy"]:
return self._completed(command, self.state["instance_policy"])
if words[:3] == ["compute", "firewall-rules", "describe"]:
rule = words[3]
value = self.state["iap_firewall"] if rule == apply.IAP_FIREWALL_RULE else self.state["public_firewall"]
return self._missing(command) if value is None else self._completed(command, value)
return None
def _mutate(self, command: list[str]):
words = command[1:]
if words[:2] == ["services", "enable"]:
self.state["services"].update(words[2 : words.index("--project")])
elif words[:3] == ["iam", "workload-identity-pools", "create"]:
self.state["pool"] = _full_state()["pool"]
elif words[:3] == ["iam", "workload-identity-pools", "undelete"]:
self.state["pool"]["state"] = "ACTIVE"
elif words[:3] == ["iam", "workload-identity-pools", "update"]:
self.state["pool"]["disabled"] = False
elif words[:4] == ["iam", "workload-identity-pools", "providers", "create-oidc"]:
self.state["provider"] = _full_state()["provider"]
elif words[:4] == ["iam", "workload-identity-pools", "providers", "undelete"]:
self.state["provider"]["state"] = "ACTIVE"
elif words[:4] == ["iam", "workload-identity-pools", "providers", "update-oidc"]:
self.state["provider"].update(
{
"state": "ACTIVE",
"disabled": False,
"displayName": self._value(command, "--display-name"),
"oidc": {"issuerUri": self._value(command, "--issuer-uri")},
"attributeMapping": apply.EXPECTED_ATTRIBUTE_MAPPING,
"attributeCondition": self._value(command, "--attribute-condition"),
}
)
elif words[:3] == ["iam", "service-accounts", "create"]:
account_id = words[3]
email = f"{account_id}@{apply.PROJECT}.iam.gserviceaccount.com"
self.state["accounts"][email] = {
"email": email,
"displayName": self._value(command, "--display-name"),
"disabled": False,
}
self.state["account_policies"][email] = {"bindings": []}
elif words[:3] == ["iam", "service-accounts", "update"]:
self.state["accounts"][words[3]]["displayName"] = self._value(command, "--display-name")
elif words[:3] == ["iam", "roles", "create"] or words[:3] == ["iam", "roles", "update"]:
self.state["custom_role"] = {
"title": self._value(command, "--title"),
"description": self._value(command, "--description"),
"includedPermissions": self._value(command, "--permissions").split(","),
"stage": self._value(command, "--stage"),
}
elif words[:3] == ["iam", "service-accounts", "add-iam-policy-binding"]:
email = words[3]
policy = (
self.state["vm_policy"] if email == apply.VM_SERVICE_ACCOUNT else self.state["account_policies"][email]
)
self._add_binding(policy, self._value(command, "--role"), self._value(command, "--member"), None)
elif words[:2] == ["projects", "add-iam-policy-binding"]:
condition = None
if "--condition" in command:
expression, title = self._value(command, "--condition").rsplit(",title=", 1)
condition = (title, expression.removeprefix("expression="))
self._add_binding(
self.state["project_policy"],
self._value(command, "--role"),
self._value(command, "--member"),
condition,
)
elif words[:3] == ["compute", "instances", "add-iam-policy-binding"]:
self._add_binding(
self.state["instance_policy"],
self._value(command, "--role"),
self._value(command, "--member"),
None,
)
elif words[:3] == ["compute", "instances", "add-metadata"]:
self.state["instance"]["metadata"] = {"items": [{"key": "enable-oslogin", "value": "TRUE"}]}
elif words[:3] == ["compute", "instances", "add-tags"]:
self.state["instance"]["tags"] = {"items": [apply.TARGET_TAG]}
elif words[:3] == ["compute", "firewall-rules", "create"]:
self.state["iap_firewall"] = _full_state()["iap_firewall"]
elif words[:3] == ["compute", "firewall-rules", "update"]:
self.state["iap_firewall"].update(
{"targetTags": [apply.TARGET_TAG], "disabled": False, "logConfig": {"enable": True}}
)
elif words[:2] == ["compute", "scp"]:
pass
elif words[:2] == ["compute", "ssh"] and "sudo install -o root" in self._value(command, "--command"):
self.state["dispatcher_hash"] = hashlib.sha256(apply.DEFAULT_DISPATCHER.read_bytes()).hexdigest()
self.state["dispatcher_file_mode"] = "root:root:755"
self.state["dispatcher_run_mode"] = "root:root:700"
else:
return None
self.mutations.append(command)
return self._completed(command)
def __call__(self, command: list[str], **_: Any):
self.commands.append(command)
read = self._read(command)
if read is not None:
return read
if command[1:3] == ["compute", "ssh"]:
remote = self._value(command, "--command")
if "sudo install -o root" in remote:
return self._mutate(command)
file_mode = self.state["dispatcher_file_mode"] or "MISSING"
run_mode = self.state["dispatcher_run_mode"] or "MISSING"
lines = [f"FILE={file_mode}"]
if self.state["dispatcher_hash"]:
lines.append(f"SHA={self.state['dispatcher_hash']}")
lines.append(f"RUN={run_mode}")
return subprocess.CompletedProcess(command, 0, "\n".join(lines) + "\n", "")
mutated = self._mutate(command)
if mutated is not None:
return mutated
return subprocess.CompletedProcess(command, 2, "", "unsupported fake gcloud command")
def _run(tmp_path: Path, state: dict[str, Any]):
fake = FakeGcloud(state)
receipt, exitcode = apply.apply_operator_access(
execute=True,
output=tmp_path / "receipt.json",
runner=fake,
)
return fake, receipt, exitcode
def test_clean_create_converges_and_retains_sanitized_receipt(tmp_path: Path) -> None:
fake, receipt, exitcode = _run(tmp_path, _clean_state())
assert exitcode == 0
assert receipt["status"] == "applied"
assert receipt["mutation_count"] > 0
assert fake.state["provider"]["attributeCondition"] == apply.PROVIDER_CONDITION
assert fake.state["dispatcher_hash"] == receipt["dispatcher"]["sha256"]
assert receipt["credentials_logged"] is False
assert len(apply.PROVIDER_DISPLAY_NAME) <= 32
assert apply.PROVIDER_DISPLAY_NAME == "Teleo fixed IAP operator"
provider_lists = [
command
for command in fake.commands
if command[1:5] == ["iam", "workload-identity-pools", "providers", "list"]
]
assert provider_lists == []
assert json.loads((tmp_path / "receipt.json").read_text())["status"] == "applied"
def test_fully_existing_state_is_control_plane_noop(tmp_path: Path) -> None:
fake, receipt, exitcode = _run(tmp_path, _full_state())
assert exitcode == 0
assert receipt["status"] == "applied"
assert receipt["mutation_count"] == 0
assert fake.mutations == []
def test_exact_sixth_mutation_round_can_converge(tmp_path: Path, monkeypatch) -> None:
discovery_round = 0
def fake_discover(_client):
nonlocal discovery_round
state = {
"round": discovery_round,
"public_firewall": {
"name": apply.CHANGING_PUBLIC_RULE,
"disabled": False,
"targetTags": ["teleo-public-ssh-bootstrap"],
},
}
discovery_round += 1
return state
def fake_plan(state):
if state["round"] >= 6:
return []
return [apply.Mutation(f"round_{state['round']}", "update", ["gcloud", "noop"])]
monkeypatch.setattr(apply, "discover", fake_discover)
monkeypatch.setattr(apply, "plan_mutations", fake_plan)
monkeypatch.setattr(apply, "_run_mutations", lambda _client, _mutations: None)
monkeypatch.setattr(apply, "converge_dispatcher", lambda _client, _path: {"status": "unchanged"})
receipt, exitcode = apply.apply_operator_access(
execute=True,
output=tmp_path / "receipt.json",
runner=lambda command, **_: subprocess.CompletedProcess(command, 0, "", ""),
)
assert exitcode == 0
assert receipt["status"] == "applied"
assert discovery_round == 7
def test_partial_state_recovers_only_missing_clone_identity_and_bindings(tmp_path: Path) -> None:
state = _full_state()
clone_member = f"serviceAccount:{apply.CLONE_SERVICE_ACCOUNT}"
state["accounts"][apply.CLONE_SERVICE_ACCOUNT] = None
state["account_policies"][apply.CLONE_SERVICE_ACCOUNT] = None
for policy_name in ("project_policy", "vm_policy", "instance_policy"):
state[policy_name]["bindings"] = [
binding for binding in state[policy_name]["bindings"] if clone_member not in binding["members"]
]
fake, receipt, exitcode = _run(tmp_path, state)
mutation_names = [
operation["name"] for operation in receipt["operations"] if operation["action"] not in {"probe", "verify"}
]
assert exitcode == 0
assert f"create_service_account:{apply.CLONE_SERVICE_ACCOUNT}" in mutation_names
assert f"create_service_account:{apply.STATUS_SERVICE_ACCOUNT}" not in mutation_names
assert all(apply.STATUS_SERVICE_ACCOUNT not in " ".join(command) for command in fake.mutations)
def test_security_sensitive_provider_drift_fails_before_mutation(tmp_path: Path) -> None:
state = _full_state()
state["provider"]["attributeCondition"] = "assertion.repository == 'attacker/repository'"
fake, receipt, exitcode = _run(tmp_path, state)
assert exitcode == 1
assert receipt["status"] == "failed"
assert "security-sensitive drift" in receipt["error"]
assert fake.mutations == []
def test_disabled_pool_and_provider_are_narrowly_reenabled(tmp_path: Path) -> None:
state = _full_state()
state["pool"]["disabled"] = True
state["provider"]["disabled"] = True
fake, receipt, exitcode = _run(tmp_path, state)
mutation_names = [
operation["name"] for operation in receipt["operations"] if operation["action"] not in {"probe", "verify"}
]
assert exitcode == 0
assert "enable_wif_pool" in mutation_names
assert "update_wif_provider" in mutation_names
assert fake.state["pool"]["disabled"] is False
assert fake.state["provider"]["disabled"] is False
def test_soft_deleted_pool_and_provider_are_undeleted_and_converged(tmp_path: Path) -> None:
state = _full_state()
state["pool"]["state"] = "DELETED"
state["provider"]["state"] = "DELETED"
fake, receipt, exitcode = _run(tmp_path, state)
mutation_names = [
operation["name"] for operation in receipt["operations"] if operation["action"] not in {"probe", "verify"}
]
assert exitcode == 0
assert "undelete_wif_pool" in mutation_names
assert "undelete_wif_provider" in mutation_names
assert "create_wif_pool" not in mutation_names
assert "create_wif_provider" not in mutation_names
assert fake.state["pool"]["state"] == "ACTIVE"
assert fake.state["provider"]["state"] == "ACTIVE"
def test_wrong_or_stopped_instance_fails_before_mutation(tmp_path: Path) -> None:
state = _full_state()
state["instance"]["status"] = "TERMINATED"
fake, receipt, exitcode = _run(tmp_path, state)
assert exitcode == 1
assert receipt["status"] == "failed"
assert "not running" in receipt["error"]
assert fake.mutations == []
def test_missing_os_login_fails_before_mutation(tmp_path: Path) -> None:
state = _full_state()
state["instance"]["metadata"] = {"items": []}
fake, receipt, exitcode = _run(tmp_path, state)
assert exitcode == 1
assert receipt["status"] == "failed"
assert "refusing to change the SSH authentication mode" in receipt["error"]
assert fake.mutations == []
def test_bootstrap_never_disables_or_mutates_public_ssh_rule(tmp_path: Path) -> None:
fake, receipt, exitcode = _run(tmp_path, _clean_state())
assert exitcode == 0
assert receipt["public_ssh_rule"] == {
"name": apply.CHANGING_PUBLIC_RULE,
"observed_disabled_before": False,
"observed_disabled_after": False,
"target_tags_before": ["teleo-public-ssh-bootstrap"],
"target_tags_after": ["teleo-public-ssh-bootstrap"],
"effective_target_tag_expansion": False,
"mutated": False,
"disable_command_present": False,
}
assert not any("--disabled" in command for command in fake.commands)
assert not any(
apply.CHANGING_PUBLIC_RULE in command
and command[1:3] == ["compute", "firewall-rules"]
and command[3] != "describe"
for command in fake.commands
)
def test_public_rule_tag_collision_fails_before_mutation(tmp_path: Path) -> None:
state = _clean_state()
state["public_firewall"]["targetTags"] = [apply.TARGET_TAG]
fake, receipt, exitcode = _run(tmp_path, state)
assert exitcode == 1
assert receipt["status"] == "failed"
assert "would newly match the existing public SSH firewall rule" in receipt["error"]
assert fake.mutations == []