Add GitHub WIF GCP readiness probe (#45)
Some checks are pending
CI / lint-and-test (push) Waiting to run

This commit is contained in:
twentyOne2x 2026-07-07 12:41:09 +02:00 committed by GitHub
parent 6dd7f9fe8c
commit 0c9dde3f36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 149 additions and 0 deletions

98
.github/workflows/gcp-readiness.yml vendored Normal file
View file

@ -0,0 +1,98 @@
name: gcp-readiness
on:
workflow_dispatch:
permissions:
contents: read
id-token: write
concurrency:
group: gcp-readiness-${{ github.ref }}
cancel-in-progress: true
env:
PROJECT_ID: teleo-501523
WORKLOAD_IDENTITY_PROVIDER: projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/living-ip-github
ARTIFACT_SERVICE_ACCOUNT: sa-artifact-builder@teleo-501523.iam.gserviceaccount.com
jobs:
readiness:
name: Read-only GCP readiness probe
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ env.ARTIFACT_SERVICE_ACCOUNT }}
- uses: google-github-actions/setup-gcloud@v2
- name: Capture gcloud identity
shell: bash
run: |
set +e
mkdir -p .gcp-readiness
{
echo "project=${PROJECT_ID}"
echo "service_account=${ARTIFACT_SERVICE_ACCOUNT}"
echo "workflow_ref=${GITHUB_REF}"
echo "revision=${GITHUB_SHA}"
} > .gcp-readiness/context.txt
gcloud auth list --format=json > .gcp-readiness/gcloud-auth-list.json 2> .gcp-readiness/gcloud-auth-list.stderr
echo "$?" > .gcp-readiness/gcloud-auth-list.exitcode
gcloud config list --format=json > .gcp-readiness/gcloud-config-list.json 2> .gcp-readiness/gcloud-config-list.stderr
echo "$?" > .gcp-readiness/gcloud-config-list.exitcode
- name: Run readiness checker
shell: bash
run: |
set +e
python3 ops/check_gcp_infra_readiness.py \
> .gcp-readiness/gcp-infra-readiness.json \
2> .gcp-readiness/gcp-infra-readiness.stderr
echo "$?" > .gcp-readiness/gcp-infra-readiness.exitcode
python3 - <<'PY'
import json
from pathlib import Path
base = Path(".gcp-readiness")
report = {
"artifact": "github_wif_gcp_readiness_probe",
"checker_exitcode": (base / "gcp-infra-readiness.exitcode").read_text().strip(),
"stderr_tail": (base / "gcp-infra-readiness.stderr").read_text()[-2000:],
}
try:
payload = json.loads((base / "gcp-infra-readiness.json").read_text() or "{}")
except json.JSONDecodeError as exc:
report["json_parse_error"] = str(exc)
payload = {}
if payload:
report["pass_count"] = payload.get("pass_count")
report["blocked_count"] = payload.get("blocked_count")
report["fail_count"] = payload.get("fail_count")
report["checks"] = [
{
"name": check.get("name"),
"status": check.get("status"),
"required_tier": check.get("required_tier"),
"current_tier": check.get("current_tier"),
"detail": (check.get("detail") or "")[:500],
}
for check in payload.get("checks", [])
]
(base / "gcp-readiness-summary.json").write_text(json.dumps(report, indent=2, sort_keys=True) + "\n")
print(json.dumps(report, indent=2, sort_keys=True))
PY
- name: Upload readiness artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: gcp-readiness
path: .gcp-readiness/
if-no-files-found: error

View file

@ -54,6 +54,17 @@ gh workflow run gcp-artifact.yml --repo living-ip/teleo-infrastructure --ref mai
The workflow authenticates to GCP with Workload Identity Federation, builds `Dockerfile.gcp-staging`, runs the image smoke test, pushes the image, and uploads `gcp-artifact-image.txt` as a run artifact.
For a read-only GCP posture probe through the same Workload Identity path:
```bash
gh workflow run gcp-readiness.yml --repo living-ip/teleo-infrastructure --ref main
```
This workflow runs `ops/check_gcp_infra_readiness.py` from GitHub Actions and
uploads stdout, stderr, exit code, and a summary as the `gcp-readiness` artifact.
It is intentionally non-mutating. If the Artifact Registry service account lacks
broader read permissions, the artifact is still the proof of the exact IAM gap.
For a local/manual Cloud Build proof:
```bash
@ -97,6 +108,7 @@ The check is read-only and prints no secret values. It verifies:
- Source SQLite/KB backup/export repeatability.
- Whether an approved source KB/Postgres dump or replication credential exists.
- Whether source data has actually been restored or replicated into GCP and queried.
- Whether the GitHub WIF readiness workflow exists for non-local readback.
## Current Boundaries

View file

@ -253,6 +253,44 @@ def check_github_actions_artifact_ci() -> Check:
)
def check_github_actions_readiness_ci() -> Check:
workflow = Path(".github/workflows/gcp-readiness.yml")
if not workflow.exists():
return Check(
"github_actions_readiness_ci",
"blocked",
"missing .github/workflows/gcp-readiness.yml",
required_tier="T3_live_readonly",
current_tier="T0_spec",
)
text = workflow.read_text()
required = [
"workflow_dispatch",
"google-github-actions/auth@v2",
"ops/check_gcp_infra_readiness.py",
GITHUB_ARTIFACT_SERVICE_ACCOUNT,
"gcp-readiness-summary.json",
"actions/upload-artifact@v4",
]
absent = [item for item in required if item not in text]
if absent:
return Check(
"github_actions_readiness_ci",
"fail",
f"workflow_missing={absent}",
required_tier="T3_live_readonly",
current_tier="T1_foundation",
)
return Check(
"github_actions_readiness_ci",
"pass",
"workflow=.github/workflows/gcp-readiness.yml captures readiness stdout/stderr/exitcode through GitHub WIF",
required_tier="T3_live_readonly",
current_tier="T2_runtime",
)
def _rule_allows_port(rule: dict, port: str) -> bool:
for allowed in rule.get("allowed", []):
if allowed.get("IPProtocol") in ("tcp", "all"):
@ -615,6 +653,7 @@ def main() -> int:
safe_check("cloud_build_contract", check_cloud_build_contract),
safe_check("cloud_build_service_account", check_cloud_build_service_account),
safe_check("github_actions_artifact_ci", check_github_actions_artifact_ci),
safe_check("github_actions_readiness_ci", check_github_actions_readiness_ci),
safe_check("network_ingress", check_network_ingress),
safe_check("compute_runtime_service_accounts", check_compute_runtime_service_accounts),
safe_check("compute_disk_snapshots", check_snapshot_policy),