From cf796fea7656026150838ef5da8e22c96fbed20e Mon Sep 17 00:00:00 2001 From: twentyOne2x Date: Mon, 13 Jul 2026 01:31:26 +0200 Subject: [PATCH] feat: add secure GCP operator reauth helper (#90) * feat: add secure GCP operator reauth helper * fix: keep secure GCP prompt open on empty input * fix: enable paste in secure GCP password dialog --- docs/gcp-operator-reauth.md | 37 +++ scripts/gcp-operator-reauth.sh | 403 ++++++++++++++++++++++++++++++ tests/test_gcp_operator_reauth.py | 250 ++++++++++++++++++ 3 files changed, 690 insertions(+) create mode 100644 docs/gcp-operator-reauth.md create mode 100755 scripts/gcp-operator-reauth.sh create mode 100644 tests/test_gcp_operator_reauth.py diff --git a/docs/gcp-operator-reauth.md b/docs/gcp-operator-reauth.md new file mode 100644 index 0000000..12ba203 --- /dev/null +++ b/docs/gcp-operator-reauth.md @@ -0,0 +1,37 @@ +# GCP Operator Reauthentication + +From the `teleo-infrastructure` repository root, inspect the current local +operator state without opening a dialog: + +```bash +scripts/gcp-operator-reauth.sh --status +``` + +Store or update the current Google password through a native secure prompt: + +```bash +scripts/gcp-operator-reauth.sh --store-password +``` + +With no option, the script asks before opening that prompt. It uses +`pinentry-mac` when installed. Otherwise it compiles a temporary AppKit helper +that uses `NSAlert` and `NSSecureTextField`. The helper stores the value as a +non-synchronizing generic password in this Mac's encrypted Keychain. The +password is never printed, copied to the clipboard, submitted to Google, or +placed in a process argument or plaintext file. + +The stored password is only an emergency operator convenience. A Google +password cannot refresh or satisfy gcloud OAuth by itself, and this script does +not attempt to automate Google login. When OAuth is stale, follow the one +`clear_CTA` printed by the script and rerun `--status`. + +`--status` verifies the selected account and project, checks token refresh +without printing the token, reads the expected VM identity, and asks gcloud to +construct the IAP SSH command with `--dry-run`. It does not create a tunnel or +start SSH. `iap_ssh_preflight=ready_dry_run_only` must not be reported as a live +IAP connection. + +The durable unattended operator is +`.github/workflows/gcp-iap-operator.yml`. It uses GitHub OIDC, Workload Identity +Federation, fixed reviewed operations, and IAP after the one-time authenticated +bootstrap. No stored Google password is an alternative to that route. diff --git a/scripts/gcp-operator-reauth.sh b/scripts/gcp-operator-reauth.sh new file mode 100755 index 0000000..c14b8e4 --- /dev/null +++ b/scripts/gcp-operator-reauth.sh @@ -0,0 +1,403 @@ +#!/usr/bin/env bash +set +x +set -Eeuo pipefail +umask 077 + +readonly EXPECTED_ACCOUNT="billy@livingip.xyz" +readonly PROJECT_ID="teleo-501523" +readonly INSTANCE="teleo-prod-1" +readonly ZONE="europe-west6-a" +readonly EXPECTED_INTERNAL_IP="10.60.0.3" +readonly KEYCHAIN_SERVICE="com.livingip.teleo.gcp-operator-reauth" +readonly KEYCHAIN_LABEL="Teleo GCP operator emergency password" +readonly WORKFLOW_PATH=".github/workflows/gcp-iap-operator.yml" +readonly RERUN_COMMAND="scripts/gcp-operator-reauth.sh --status" + +HELPER_DIR="" +KEYCHAIN_HELPER="" + +usage() { + cat <<'EOF' +Usage: scripts/gcp-operator-reauth.sh [--status | --store-password] + + --status Report Keychain presence and local gcloud/IAP prerequisites. + --store-password Securely store or update the emergency Google password, then report status. + +With no option, the script asks before opening the secure password prompt. +EOF +} + +die() { + printf 'gcp-operator-reauth: %s\n' "$1" >&2 + exit 2 +} + +cleanup() { + if [[ -n "$HELPER_DIR" && -d "$HELPER_DIR" ]]; then + rm -rf -- "$HELPER_DIR" + fi +} +trap cleanup EXIT + +validate_test_overrides() { + if [[ -n "${TELEO_GCP_REAUTH_KEYCHAIN_HELPER:-}${TELEO_GCP_REAUTH_PINENTRY:-}" && \ + "${TELEO_GCP_REAUTH_TESTING:-0}" != "1" ]]; then + die "helper overrides are accepted only when TELEO_GCP_REAUTH_TESTING=1" + fi +} + +build_keychain_helper() { + if [[ -n "${TELEO_GCP_REAUTH_KEYCHAIN_HELPER:-}" ]]; then + [[ -x "$TELEO_GCP_REAUTH_KEYCHAIN_HELPER" ]] || die "test Keychain helper is not executable" + KEYCHAIN_HELPER="$TELEO_GCP_REAUTH_KEYCHAIN_HELPER" + return + fi + + command -v swiftc >/dev/null 2>&1 || die "swiftc is required for secure macOS Keychain access" + HELPER_DIR="$(mktemp -d "${TMPDIR:-/tmp}/teleo-gcp-keychain.XXXXXX")" + chmod 0700 "$HELPER_DIR" + local source="$HELPER_DIR/GcpOperatorKeychain.swift" + KEYCHAIN_HELPER="$HELPER_DIR/gcp-operator-keychain" + + cat >"$source" <<'SWIFT' +import AppKit +import Foundation +import Security + +func fail(_ message: String, _ code: Int32 = 2) -> Never { + FileHandle.standardError.write(Data("gcp-operator-keychain: \(message)\n".utf8)) + exit(code) +} + +guard CommandLine.arguments.count == 5 else { + fail("expected action, service, account, and label") +} + +let action = CommandLine.arguments[1] +let service = CommandLine.arguments[2] +let account = CommandLine.arguments[3] +let label = CommandLine.arguments[4] + +func itemQuery() -> [String: Any] { + [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrService as String: service, + kSecAttrAccount as String: account, + kSecAttrSynchronizable as String: kCFBooleanFalse as Any, + ] +} + +func itemExists() -> Bool { + var query = itemQuery() + query[kSecMatchLimit as String] = kSecMatchLimitOne + let status = SecItemCopyMatching(query as CFDictionary, nil) + if status == errSecSuccess { + return true + } + if status == errSecItemNotFound { + return false + } + fail("Keychain status failed with OSStatus \(status)") +} + +func store(_ password: Data) -> String { + guard !password.isEmpty else { + fail("empty password was not stored") + } + guard password.count <= 4096 else { + fail("password exceeds the 4096-byte limit") + } + + let query = itemQuery() + if itemExists() { + let attributes: [String: Any] = [ + kSecValueData as String: password, + kSecAttrLabel as String: label, + kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, + ] + let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary) + guard status == errSecSuccess else { + fail("Keychain update failed with OSStatus \(status)") + } + return "updated" + } + + var item = query + item[kSecValueData as String] = password + item[kSecAttrLabel as String] = label + item[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly + let status = SecItemAdd(item as CFDictionary, nil) + guard status == errSecSuccess else { + fail("Keychain add failed with OSStatus \(status)") + } + return "stored" +} + +func hexValue(_ byte: UInt8) -> UInt8? { + switch byte { + case 48...57: return byte - 48 + case 65...70: return byte - 55 + case 97...102: return byte - 87 + default: return nil + } +} + +func decodeAssuan(_ value: Substring) -> Data? { + let input = Array(value.utf8) + var output = Data() + var index = 0 + while index < input.count { + if input[index] == 37 { + guard index + 2 < input.count, + let high = hexValue(input[index + 1]), + let low = hexValue(input[index + 2]) else { + return nil + } + output.append((high << 4) | low) + index += 3 + } else { + output.append(input[index]) + index += 1 + } + } + return output +} + +func passwordFromPinentry(_ response: Data) -> Data { + guard let text = String(data: response, encoding: .utf8) else { + fail("pinentry returned non-UTF-8 protocol data") + } + for line in text.split(separator: "\n", omittingEmptySubsequences: false) { + if line.hasPrefix("D ") { + guard let decoded = decodeAssuan(line.dropFirst(2)) else { + fail("pinentry returned invalid escaped data") + } + return decoded + } + } + fail("pinentry did not return a password") +} + +switch action { +case "status": + print(itemExists() ? "present" : "absent") +case "store-pinentry": + let response = FileHandle.standardInput.readDataToEndOfFile() + print(store(passwordFromPinentry(response))) +case "prompt-store": + let app = NSApplication.shared + app.setActivationPolicy(.accessory) + app.activate(ignoringOtherApps: true) + let alert = NSAlert() + alert.alertStyle = .informational + alert.messageText = "Store Teleo GCP emergency password" + alert.informativeText = "This password is stored only in this Mac's encrypted Keychain. It cannot refresh gcloud OAuth by itself." + alert.addButton(withTitle: "Store in Keychain") + alert.addButton(withTitle: "Cancel") + let field = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: 360, height: 24)) + field.placeholderString = "Current Google password" + let mainMenu = NSMenu() + let editMenuItem = NSMenuItem(title: "Edit", action: nil, keyEquivalent: "") + let editMenu = NSMenu(title: "Edit") + editMenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v") + editMenuItem.submenu = editMenu + mainMenu.addItem(editMenuItem) + app.mainMenu = mainMenu + let fieldMenu = NSMenu(title: "Edit") + fieldMenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "") + field.menu = fieldMenu + alert.accessoryView = field + alert.window.initialFirstResponder = field + while true { + app.activate(ignoringOtherApps: true) + guard alert.runModal() == .alertFirstButtonReturn else { + fail("password entry cancelled", 3) + } + if !field.stringValue.isEmpty { + print(store(Data(field.stringValue.utf8))) + break + } + NSSound.beep() + alert.informativeText = "Enter the current Google password. Empty values are never stored." + alert.window.initialFirstResponder = field + } +default: + fail("unsupported action") +} +SWIFT + + swiftc -O -framework AppKit -framework Security "$source" -o "$KEYCHAIN_HELPER" + chmod 0700 "$KEYCHAIN_HELPER" +} + +find_pinentry() { + if [[ -n "${TELEO_GCP_REAUTH_PINENTRY:-}" ]]; then + [[ -x "$TELEO_GCP_REAUTH_PINENTRY" ]] || die "test pinentry helper is not executable" + printf '%s\n' "$TELEO_GCP_REAUTH_PINENTRY" + return + fi + if command -v pinentry-mac >/dev/null 2>&1; then + command -v pinentry-mac + return + fi + for candidate in /opt/homebrew/bin/pinentry-mac /usr/local/bin/pinentry-mac; do + if [[ -x "$candidate" ]]; then + printf '%s\n' "$candidate" + return + fi + done + return 1 +} + +store_password() { + local pinentry result + if pinentry="$(find_pinentry)"; then + result="$({ + printf 'SETTITLE Teleo GCP operator\n' + printf 'SETDESC Store the current Google password in macOS Keychain. This does not authenticate gcloud.\n' + printf 'SETPROMPT Google password:\n' + printf 'GETPIN\n' + printf 'BYE\n' + } | "$pinentry" | "$KEYCHAIN_HELPER" store-pinentry "$KEYCHAIN_SERVICE" "$EXPECTED_ACCOUNT" "$KEYCHAIN_LABEL")" || { + die "secure password entry was cancelled or failed" + } + printf 'secure_prompt=pinentry-mac\n' + else + result="$("$KEYCHAIN_HELPER" prompt-store "$KEYCHAIN_SERVICE" "$EXPECTED_ACCOUNT" "$KEYCHAIN_LABEL")" || { + die "secure password entry was cancelled or failed" + } + printf 'secure_prompt=AppKit NSSecureTextField\n' + fi + case "$result" in + stored | updated) printf 'keychain_password=%s\n' "$result" ;; + *) die "Keychain helper returned an invalid result" ;; + esac +} + +emit_security_boundary() { + printf '%s\n' \ + 'security_truth=The stored Google password is emergency convenience only; it cannot satisfy or automate gcloud OAuth.' \ + 'password_handling=The script never prints, copies, or submits the password and never places it in a command argument.' \ + "durable_unattended_route=$WORKFLOW_PATH uses GitHub OIDC/Workload Identity Federation and IAP after one-time bootstrap." +} + +emit_reauth_cta() { + printf '%s\n' \ + 'oauth_state=stale_or_unavailable' \ + "clear_CTA=Run 'gcloud auth login $EXPECTED_ACCOUNT --force --no-launch-browser' in a terminal, paste the displayed URL into the dedicated Chrome GCP session, complete reauthentication, return the authorization code to gcloud, then rerun '$RERUN_COMMAND'." +} + +report_status() { + local keychain_state account project instance_state gcloud_bin + keychain_state="$("$KEYCHAIN_HELPER" status "$KEYCHAIN_SERVICE" "$EXPECTED_ACCOUNT" "$KEYCHAIN_LABEL")" + case "$keychain_state" in + present | absent) ;; + *) die "Keychain helper returned an invalid status" ;; + esac + + emit_security_boundary + printf 'keychain_password=%s\n' "$keychain_state" + + if ! gcloud_bin="$(command -v gcloud)"; then + printf '%s\n' 'gcloud_state=missing' 'operator_readiness=not_ready' + return 1 + fi + printf 'gcloud_binary=%s\n' "$gcloud_bin" + + account="$(gcloud config get-value account --quiet 2>/dev/null || true)" + project="$(gcloud config get-value project --quiet 2>/dev/null || true)" + printf 'gcloud_account=%s\n' "${account:-unset}" + printf 'gcloud_project=%s\n' "${project:-unset}" + if [[ "$account" != "$EXPECTED_ACCOUNT" || "$project" != "$PROJECT_ID" ]]; then + printf '%s\n' \ + 'gcloud_config_state=mismatch' \ + "clear_CTA=Run 'gcloud config set account $EXPECTED_ACCOUNT && gcloud config set project $PROJECT_ID', then rerun '$RERUN_COMMAND'." \ + 'operator_readiness=not_ready' + return 1 + fi + printf 'gcloud_config_state=ready\n' + + if ! gcloud auth print-access-token --account="$EXPECTED_ACCOUNT" >/dev/null 2>&1; then + emit_reauth_cta + printf 'operator_readiness=not_ready\n' + return 1 + fi + printf 'oauth_state=ready\n' + + if ! command -v ssh >/dev/null 2>&1; then + printf '%s\n' 'ssh_binary=missing' 'operator_readiness=not_ready' + return 1 + fi + printf 'ssh_binary=ready\n' + + instance_state="$( + gcloud compute instances describe "$INSTANCE" \ + --project="$PROJECT_ID" \ + --zone="$ZONE" \ + --format='value(name,status,networkInterfaces[0].networkIP)' \ + --quiet 2>/dev/null || true + )" + if [[ "$instance_state" != "$INSTANCE"$'\t'"RUNNING"$'\t'"$EXPECTED_INTERNAL_IP" ]]; then + printf '%s\n' \ + 'instance_readback=not_ready' \ + "clear_CTA=Confirm $INSTANCE is RUNNING at $EXPECTED_INTERNAL_IP in $PROJECT_ID/$ZONE, then rerun '$RERUN_COMMAND'." \ + 'operator_readiness=not_ready' + return 1 + fi + printf 'instance_readback=%s RUNNING %s\n' "$INSTANCE" "$EXPECTED_INTERNAL_IP" + + if ! gcloud compute ssh "$INSTANCE" \ + --project="$PROJECT_ID" \ + --zone="$ZONE" \ + --tunnel-through-iap \ + --ssh-key-expire-after=5m \ + --dry-run \ + --quiet >/dev/null 2>&1; then + printf '%s\n' \ + 'iap_ssh_preflight=not_ready' \ + "clear_CTA=Repair the local gcloud IAP/SSH prerequisite reported by 'gcloud compute ssh $INSTANCE --project=$PROJECT_ID --zone=$ZONE --tunnel-through-iap --ssh-key-expire-after=5m --dry-run', then rerun '$RERUN_COMMAND'." \ + 'operator_readiness=not_ready' + return 1 + fi + printf '%s\n' \ + 'iap_ssh_preflight=ready_dry_run_only' \ + 'iap_connection=not_attempted' \ + 'operator_readiness=ready_for_explicit_operator_action' +} + +main() { + local mode="interactive" answer + [[ $# -le 1 ]] || { + usage >&2 + exit 2 + } + if [[ $# -eq 1 ]]; then + case "$1" in + --status) mode="status" ;; + --store-password) mode="store" ;; + -h | --help) usage; exit 0 ;; + *) usage >&2; exit 2 ;; + esac + fi + + validate_test_overrides + build_keychain_helper + + if [[ "$mode" == "interactive" ]]; then + if [[ ! -t 0 && "${TELEO_GCP_REAUTH_TESTING:-0}" != "1" ]]; then + die "interactive mode requires a terminal; use --status or --store-password" + fi + printf 'Store or update the emergency Google password in macOS Keychain? [y/N] ' >&2 + IFS= read -r answer || answer="" + case "$answer" in + y | Y | yes | YES) store_password ;; + *) printf 'keychain_password=unchanged\n' ;; + esac + elif [[ "$mode" == "store" ]]; then + store_password + fi + + report_status +} + +main "$@" diff --git a/tests/test_gcp_operator_reauth.py b/tests/test_gcp_operator_reauth.py new file mode 100644 index 0000000..823102c --- /dev/null +++ b/tests/test_gcp_operator_reauth.py @@ -0,0 +1,250 @@ +import os +import re +import shutil +import subprocess +import sys +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[1] +SCRIPT = REPO_ROOT / "scripts" / "gcp-operator-reauth.sh" +DOC = REPO_ROOT / "docs" / "gcp-operator-reauth.md" +FAKE_SECRET = "fake password%value" + + +def write_executable(path: Path, source: str) -> None: + path.write_text(source, encoding="utf-8") + path.chmod(0o755) + + +def fake_operator_env(tmp_path: Path, *, token_ready: bool = True) -> tuple[dict[str, str], Path, Path, Path]: + bin_dir = tmp_path / "bin" + bin_dir.mkdir() + calls = tmp_path / "gcloud-calls.txt" + helper_calls = tmp_path / "keychain-helper-calls.txt" + keychain_state = tmp_path / "keychain-state" + + write_executable( + bin_dir / "gcloud", + """#!/usr/bin/env bash +set -eu +printf '%s\\n' "$*" >> "$FAKE_GCLOUD_CALLS" +case "$1:$2:${3:-}" in + config:get-value:account) printf '%s\\n' 'billy@livingip.xyz' ;; + config:get-value:project) printf '%s\\n' 'teleo-501523' ;; + auth:print-access-token:*) + [[ "${FAKE_GCLOUD_TOKEN_READY:-0}" == "1" ]] || exit 1 + printf '%s\\n' 'fake-token-that-must-be-discarded' + ;; + compute:instances:describe) printf 'teleo-prod-1\\tRUNNING\\t10.60.0.3\\n' ;; + compute:ssh:teleo-prod-1) exit 0 ;; + *) exit 90 ;; +esac +""", + ) + write_executable(bin_dir / "ssh", "#!/usr/bin/env bash\nexit 0\n") + write_executable( + bin_dir / "pinentry-mac", + """#!/usr/bin/env bash +set -eu +cat >/dev/null +printf '%s\\n' 'OK fake-pinentry' 'D fake%20password%25value' 'OK' +""", + ) + write_executable( + bin_dir / "keychain-helper", + """#!/usr/bin/env bash +set -eu +printf '%s\\n' "$*" >> "$FAKE_KEYCHAIN_HELPER_CALLS" +action="$1" +case "$action" in + status) + if [[ -s "$FAKE_KEYCHAIN_STATE" ]]; then printf 'present\\n'; else printf 'absent\\n'; fi + ;; + store-pinentry) + response="$(cat)" + [[ "$response" == *'D fake%20password%25value'* ]] + printf '%s' 'fake password%value' > "$FAKE_KEYCHAIN_STATE" + printf 'stored\\n' + ;; + prompt-store) + printf '%s' 'fake AppKit password' > "$FAKE_KEYCHAIN_STATE" + printf 'stored\\n' + ;; + *) exit 91 ;; +esac +""", + ) + + env = { + **os.environ, + "PATH": f"{bin_dir}:{os.environ['PATH']}", + "FAKE_GCLOUD_CALLS": str(calls), + "FAKE_GCLOUD_TOKEN_READY": "1" if token_ready else "0", + "FAKE_KEYCHAIN_HELPER_CALLS": str(helper_calls), + "FAKE_KEYCHAIN_STATE": str(keychain_state), + "TELEO_GCP_REAUTH_TESTING": "1", + "TELEO_GCP_REAUTH_KEYCHAIN_HELPER": str(bin_dir / "keychain-helper"), + "TELEO_GCP_REAUTH_PINENTRY": str(bin_dir / "pinentry-mac"), + } + return env, calls, helper_calls, keychain_state + + +def run_script(env: dict[str, str], *args: str, input_text: str | None = None) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["bash", str(SCRIPT), *args], + cwd=REPO_ROOT, + env=env, + input=input_text, + text=True, + capture_output=True, + check=False, + ) + + +def test_fake_pinentry_to_keychain_to_ready_status_lifecycle(tmp_path: Path) -> None: + env, calls_path, helper_calls_path, state_path = fake_operator_env(tmp_path) + + completed = run_script(env, "--store-password") + + assert completed.returncode == 0, completed.stderr + assert completed.stdout.count("security_truth=") == 1 + assert "secure_prompt=pinentry-mac" in completed.stdout + assert "keychain_password=stored" in completed.stdout + assert "keychain_password=present" in completed.stdout + assert "oauth_state=ready" in completed.stdout + assert "instance_readback=teleo-prod-1 RUNNING 10.60.0.3" in completed.stdout + assert "iap_ssh_preflight=ready_dry_run_only" in completed.stdout + assert "iap_connection=not_attempted" in completed.stdout + assert "operator_readiness=ready_for_explicit_operator_action" in completed.stdout + assert state_path.read_text(encoding="utf-8") == FAKE_SECRET + + combined_output = completed.stdout + completed.stderr + calls = calls_path.read_text(encoding="utf-8") + helper_calls = helper_calls_path.read_text(encoding="utf-8") + assert FAKE_SECRET not in combined_output + assert "fake-token-that-must-be-discarded" not in combined_output + assert FAKE_SECRET not in calls + assert FAKE_SECRET not in helper_calls + assert "auth print-access-token --account=billy@livingip.xyz" in calls + assert "compute instances describe teleo-prod-1" in calls + assert "compute ssh teleo-prod-1" in calls + assert "--tunnel-through-iap" in calls + assert "--ssh-key-expire-after=5m" in calls + assert "--dry-run" in calls + + +def test_default_interactive_mode_uses_the_secure_popup_path(tmp_path: Path) -> None: + env, _, _, state_path = fake_operator_env(tmp_path) + + completed = run_script(env, input_text="yes\n") + + assert completed.returncode == 0, completed.stderr + assert "Store or update" in completed.stderr + assert "secure_prompt=pinentry-mac" in completed.stdout + assert state_path.read_text(encoding="utf-8") == FAKE_SECRET + assert FAKE_SECRET not in completed.stdout + completed.stderr + + +def test_appkit_secure_field_fallback_runs_when_pinentry_is_absent(tmp_path: Path) -> None: + env, _, _, state_path = fake_operator_env(tmp_path) + pinentry = Path(env.pop("TELEO_GCP_REAUTH_PINENTRY")) + pinentry.unlink() + env["PATH"] = f"{pinentry.parent}:/usr/bin:/bin" + + completed = run_script(env, "--store-password") + + assert completed.returncode == 0, completed.stderr + assert "secure_prompt=AppKit NSSecureTextField" in completed.stdout + assert state_path.read_text(encoding="utf-8") == "fake AppKit password" + assert "fake AppKit password" not in completed.stdout + completed.stderr + + +def test_status_does_not_open_a_popup_or_read_a_password(tmp_path: Path) -> None: + env, _, helper_calls_path, state_path = fake_operator_env(tmp_path) + state_path.write_text(FAKE_SECRET, encoding="utf-8") + + completed = run_script(env, "--status") + + assert completed.returncode == 0, completed.stderr + assert "keychain_password=present" in completed.stdout + assert "secure_prompt=" not in completed.stdout + assert helper_calls_path.read_text(encoding="utf-8").splitlines() == [ + "status com.livingip.teleo.gcp-operator-reauth billy@livingip.xyz Teleo GCP operator emergency password" + ] + assert FAKE_SECRET not in completed.stdout + completed.stderr + + +def test_stale_oauth_emits_one_exact_nonlaunching_cta_and_stops(tmp_path: Path) -> None: + env, calls_path, _, _ = fake_operator_env(tmp_path, token_ready=False) + + completed = run_script(env, "--status") + + assert completed.returncode == 1 + assert "oauth_state=stale_or_unavailable" in completed.stdout + assert completed.stdout.count("clear_CTA=") == 1 + assert "gcloud auth login billy@livingip.xyz --force --no-launch-browser" in completed.stdout + assert "dedicated Chrome GCP session" in completed.stdout + assert "scripts/gcp-operator-reauth.sh --status" in completed.stdout + assert "operator_readiness=not_ready" in completed.stdout + calls = calls_path.read_text(encoding="utf-8") + assert "compute instances describe" not in calls + assert "compute ssh" not in calls + + +def test_source_enforces_secret_and_gui_boundaries() -> None: + source = SCRIPT.read_text(encoding="utf-8") + + assert "NSAlert" in source + assert "NSSecureTextField" in source + assert "app.activate(ignoringOtherApps: true)" in source + assert 'action: #selector(NSText.paste(_:)), keyEquivalent: "v"' in source + assert "field.menu = fieldMenu" in source + assert "Empty values are never stored." in source + assert "SecItemAdd" in source + assert "SecItemUpdate" in source + assert "kSecAttrAccessibleWhenUnlockedThisDeviceOnly" in source + assert "kSecAttrSynchronizable" in source + assert "store-pinentry" in source + assert "FileHandle.standardInput.readDataToEndOfFile()" in source + assert "cannot satisfy or automate gcloud OAuth" in source + assert "ready_dry_run_only" in source + assert "iap_connection=not_attempted" in source + for forbidden in ( + "osascript", + "AppleScript", + "System Events", + "NSPasteboard", + "pbcopy", + "open -a", + "open -n", + "security add-generic-password", + ): + assert forbidden not in source + assert not re.search(r"gcloud auth login.*(?:^|\n)\s*gcloud auth login", source) + + +def test_shell_syntax_and_operator_documentation() -> None: + subprocess.run(["bash", "-n", str(SCRIPT)], cwd=REPO_ROOT, check=True) + documentation = DOC.read_text(encoding="utf-8") + assert "scripts/gcp-operator-reauth.sh --status" in documentation + assert "scripts/gcp-operator-reauth.sh --store-password" in documentation + assert "cannot refresh or satisfy gcloud OAuth by itself" in documentation + assert ".github/workflows/gcp-iap-operator.yml" in documentation + assert "ready_dry_run_only" in documentation + + +@pytest.mark.skipif(sys.platform != "darwin" or shutil.which("swiftc") is None, reason="macOS Swift toolchain required") +def test_embedded_swift_helper_typechecks(tmp_path: Path) -> None: + source = SCRIPT.read_text(encoding="utf-8") + match = re.search(r"<<'SWIFT'\n(?P.*?)\nSWIFT", source, flags=re.DOTALL) + assert match is not None + swift_source = tmp_path / "GcpOperatorKeychain.swift" + swift_source.write_text(match.group("swift"), encoding="utf-8") + + subprocess.run( + ["swiftc", "-typecheck", "-framework", "AppKit", "-framework", "Security", str(swift_source)], + cwd=REPO_ROOT, + check=True, + )