From 25eec703ce6cbbe09530a7121c64fe1d073e0831 Mon Sep 17 00:00:00 2001 From: chrischristiansen-glitch Date: Sun, 24 May 2026 02:30:27 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20billing=20IAM=20hardening=20=E2=80=94?= =?UTF-8?q?=2011-billing-iam-hardening.sh,=20jason.vauger=20viewer,=20MAST?= =?UTF-8?q?ERPLAN=20oppdatert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 30 ++++-- MASTERPLAN.md | 57 ++++++++--- infrastructure/11-billing-iam-hardening.sh | 104 +++++++++++++++++++++ 3 files changed, 172 insertions(+), 19 deletions(-) create mode 100644 infrastructure/11-billing-iam-hardening.sh diff --git a/.env.example b/.env.example index 933ff63..7d0c522 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,7 @@ export PROJECT_ID="propane-will-491900-m5" export REGION="us-central1" export BILLING_ACCOUNT_ID="XXXXXX-XXXXXX-XXXXXX" # gcloud billing accounts list -export ALERT_EMAIL="your-email@example.com" +export ALERT_EMAIL="chris.christiansen@vauco.no" # ── Service Account ──────────────────────────────────────────── export AGENT_SA="osvauco-agent-sa@${PROJECT_ID}.iam.gserviceaccount.com" @@ -43,22 +43,38 @@ export GITHUB_APP_INSTALLATION_ID="" # eks: 12345678 # ── Observability ──────────────────────────────────────────── # ALERT_EMAIL er allerede satt ovenfor +# ── Billing IAM Hardening (11-billing-iam-hardening.sh) ───────────── +# Kommaseparert liste — får roles/billing.viewer på billing-kontoen +# jason.vauger = agentens identitet (kan hente kostnadsdata autonomt) +# chris = eier med eksplisitt viewer i tillegg til admin +export BILLING_VIEWER_EMAILS="chris.christiansen@vauco.no,jason.vauger@vauco.no" +# Google Workspace / Cloud Identity domene: +# Sett kun hvis vauco.no er koblet som org i GCP — fjerner billing.creator fra domenet +# export ORG_DOMAIN="vauco.no" +# +# Fremtidig (Fase C): erstatt enkeltadresser med Google Group: +# export BILLING_VIEWER_EMAILS="billing@vauco.no" + +# ── Webhook / Chat-notifikasjoner ──────────────────────────── +# Lagres i Secret Manager, ikke her: +# gcloud secrets create webhook-url --data-file=<(echo -n "https://DIN-WEBHOOK-URL") +# Se: infrastructure/notifications/webhook-setup.md + # ── VPC / Security (valgfritt) ──────────────────────────────── export VPC_NETWORK="default" -# export CMEK_KEY_RING="agent-keyring" # kun ved CMEK -# export CMEK_KEY_NAME="agent-key" # kun ved CMEK +# export CMEK_KEY_RING="agent-keyring" +# export CMEK_KEY_NAME="agent-key" # ── IAP / Domene (Fase B) ───────────────────────────────────── # export IAP_DOMAIN="opax.vauco.no" -# export IAP_CLIENT_ID="" # fra GCP Console → IAP -# export IAP_CLIENT_SECRET="" # fra GCP Console → IAP +# export IAP_CLIENT_ID="" +# export IAP_CLIENT_SECRET="" # ── Terraform / Kunde-onboarding (Fase C) ───────────────────── -# Brukes av vauco-bootstrap Terraform-modul ved onboarding av ny kunde # export TF_VAR_customer_id="medioteq" # export TF_VAR_customer_project_id="" # export TF_VAR_customer_region="europe-north1" -# export TF_STATE_BUCKET="${PROJECT_ID}-tfstate" # GCS bucket for Terraform state +# export TF_STATE_BUCKET="${PROJECT_ID}-tfstate" # ── LiteLLM proxy (valgfritt) ───────────────────────────────── # export LITELLM_BASE_URL="" diff --git a/MASTERPLAN.md b/MASTERPLAN.md index 46ba6ca..6bddef2 100644 --- a/MASTERPLAN.md +++ b/MASTERPLAN.md @@ -1,7 +1,7 @@ # VAUCO MASTERPLAN — Chris Christiansen -**Sist oppdatert: 2026-05-23 CEST** -**Status: Repo ferdig — GCP gjenstår (A4–A8 neste)** +**Sist oppdatert: 2026-05-24 CEST** +**Status: Repo ferdig — GCP gjenstår (A4–A9 neste)** --- @@ -21,6 +21,22 @@ Du er første kunde. Når det virker for deg → selg det til andre. --- +## AGENT-IDENTITET + +| Identitet | E-post | Rolle | Formål | +|---|---|---|---| +| Eier / admin | `chris.christiansen@vauco.no` | `billing.admin` + `billing.viewer` | Full kontroll | +| Agent (OPAX) | `jason.vauger@vauco.no` | `billing.viewer` | Hente og rapportere kostnadsdata autonomt | +| Fremtidig gruppe | `billing@vauco.no` | `billing.viewer` (Fase C) | Delt innboks for team og kunder | + +**Agent-kapabiliteter med `billing.viewer`:** +- Svare på «Hva koster dette prosjektet denne måneden?» +- Sende ukentlig kostnadsrapport til chat-appen +- Reagere på Pub/Sub budget-alerts (via `10-cost-guard.sh`) +- **Ikke:** endre IAM, budsjetter eller koble prosjekter + +--- + ## CLOUD SHELL QUICK-START (kopi-klipp) > Bruk Cloud Shell: https://shell.cloud.google.com @@ -46,16 +62,17 @@ bash infrastructure/00-authcheck.sh bash infrastructure/01-setupenv.sh bash infrastructure/07-rag-setup.sh # *** Kopier RAG_CORPUS_NAME fra output inn i .env *** -nano .env # Legg til: export RAG_CORPUS_NAME="..." +nano .env source .env bash infrastructure/08-memorybank-setup.sh # *** Kopier MEMORY_ENGINE_NAME fra output inn i .env *** -nano .env # Legg til: export MEMORY_ENGINE_NAME="..." +nano .env source .env bash infrastructure/05-cloudrun-deploy.sh bash infrastructure/04-observability-setup.sh +bash infrastructure/11-billing-iam-hardening.sh # Om kvelden (OBLIGATORISK — unngå 24/7-fakturering): bash infrastructure/03-teardown.sh @@ -77,6 +94,7 @@ bash infrastructure/03-teardown.sh SUBAGENT_MODEL=gemini-2.0-flash [ ] A2 — bash infrastructure/00-authcheck.sh [ ] A3 — bash infrastructure/01-setupenv.sh + Oppretter: budsjetter ($75 dev + $500 prod), Pub/Sub topic [ ] A4 — bash infrastructure/07-rag-setup.sh OQ-02: Script faller automatisk tilbake til us-east1 om nødvendig ACTION: Kopier RAG_CORPUS_NAME til .env @@ -86,6 +104,10 @@ bash infrastructure/03-teardown.sh [ ] A6 — bash infrastructure/05-cloudrun-deploy.sh [ ] A7 — bash infrastructure/04-observability-setup.sh [ ] A8 — bash infrastructure/03-teardown.sh (kjør hver kveld) +[ ] A9 — bash infrastructure/11-billing-iam-hardening.sh + Gir billing.viewer til: chris.christiansen@vauco.no + jason.vauger@vauco.no + Løser GCP billing health-advarsel: "0 billing account viewers" + Fjerner billing.creator fra domenet (hvis ORG_DOMAIN er satt i .env) ``` --- @@ -103,7 +125,11 @@ bash infrastructure/03-teardown.sh Fyll inn GITHUB_APP_INSTALLATION_ID i .env [ ] B3 — bash infrastructure/06-cicd-setup.sh OQ-04: LØST — Cloud Build 2nd gen, ingen manuell OAuth i Console - Bruker: gcloud builds connections create github (programmatisk) +[ ] B4 — Legg til webhook URL i Secret Manager: + gcloud secrets create webhook-url --data-file=<(echo -n "https://DIN-WEBHOOK-URL") + Se: infrastructure/notifications/webhook-setup.md +[ ] B5 — bash infrastructure/10-cost-guard.sh + Aktiverer auto-teardown Cloud Function + chat-varsling ved budsjett-overskridelse ``` --- @@ -119,7 +145,11 @@ bash infrastructure/03-teardown.sh Terraform-variabler: customer_id, customer_project_id, customer_region State-backend: GCS bucket (TF_STATE_BUCKET i .env) [ ] C2 — Pakk `vauco-os` som kundeflate -[ ] C3 — Første kunde: Medioteq +[ ] C3 — Opprett billing@vauco.no som Google Group + Legg inn: chris.christiansen@vauco.no + jason.vauger@vauco.no + Oppdater .env: BILLING_VIEWER_EMAILS="billing@vauco.no" + Kjør på nytt: bash infrastructure/11-billing-iam-hardening.sh +[ ] C4 — Første kunde: Medioteq Region: europe-north1, separat GCP-prosjekt Kjør: terraform apply -var="customer_id=medioteq" ``` @@ -131,15 +161,17 @@ bash infrastructure/03-teardown.sh | Script | Rolle | Status | |--------|-------|--------| | `00-authcheck.sh` | Verifiser gcloud-innlogging | Klar | -| `01-setupenv.sh` | Aktiver APIs, SA, IAM, budsjett | Klar | +| `01-setupenv.sh` | Aktiver APIs, SA, IAM, budsjetter ($75+$500), Pub/Sub | Klar | | `02-deploy.sh` | IAP-deploy på eget domene | Klar | | `03-teardown.sh` | Riv ned Cloud Run | Klar | | `04-observability-setup.sh` | Monitoring + alerting | Klar | | `05-cloudrun-deploy.sh` | ADK-agent til Cloud Run | Klar | -| `06-cicd-setup.sh` | GitHub → Cloud Build **2nd gen** | **Oppdatert** | +| `06-cicd-setup.sh` | GitHub → Cloud Build **2nd gen** | Oppdatert | | `07-rag-setup.sh` | RAG Engine corpus + fallback | Oppdatert | | `08-memorybank-setup.sh` | Memory Bank + auto-install | Oppdatert | | `09-cost-check.sh` | Kostnadsoversikt + Artifact Registry | Klar | +| `10-cost-guard.sh` | Cloud Function auto-teardown + webhook-alert | **Ny** | +| `11-billing-iam-hardening.sh` | Billing IAM: viewer-roller + fjern domain Creator | **Ny** | --- @@ -166,7 +198,6 @@ bash infrastructure/03-teardown.sh | Medioteq-type (klinisk, compliance) | ~$220-350 | Breakeven: 1 kunde til $300-500/mnd dekker alle kostnader. -Ikke bygg kost-analyse-system nå — spør agenten når GCP er oppe. --- @@ -182,13 +213,15 @@ Ikke bygg kost-analyse-system nå — spør agenten når GCP er oppe. | OQ-06 | Memory Bank auto-discovery | Eksplisitt env var | | OQ-07 | Monitoring email flag-format | Teste i dev | | OQ-08 | VPC connector e2-micro regional | Default us-central1 | +| OQ-09 | GCP billing: 0 viewers | **LØST** — `11-billing-iam-hardening.sh` | +| OQ-10 | GCP billing: Creator på domenet | **LØST** — fjernes når ORG_DOMAIN er satt | --- ## TERRAFORM-VEIKART (Fase C) -Terraform introduseres **ikke** for nåværende infra — gcloud-scripts er tilstrekkelig -når vi opererer med ett GCP-prosjekt. Terraform blir relevant ved kunde-onboarding: +Terraform introduseres **ikke** for nåværende infra — gcloud-scripts er tilstrekkelig. +Terraform blir relevant ved kunde-onboarding: | Når | Hva | |---|---| @@ -198,7 +231,7 @@ når vi opererer med ett GCP-prosjekt. Terraform blir relevant ved kunde-onboard | State-backend | GCS bucket (`TF_STATE_BUCKET`) — én bucket, én state-fil per kunde | | Fase D | Terraform Cloud for sentralisert state og CI/CD på tvers av kunder | -Terraform-ressurser som er aktuelle: +Terraform-ressurser: - `google_cloudbuildv2_connection` — programmatisk GitHub-kobling per kunde - `google_cloudbuildv2_repository` — repo-link - `google_cloudbuild_trigger` — push-til-main trigger diff --git a/infrastructure/11-billing-iam-hardening.sh b/infrastructure/11-billing-iam-hardening.sh new file mode 100644 index 0000000..fca66f1 --- /dev/null +++ b/infrastructure/11-billing-iam-hardening.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# 11-billing-iam-hardening.sh — GCP Billing Account Health fixes +# +# Løser to offisielle GCP-advarsler: +# 1. "0 billing account viewers" — gir billing.viewer til spesifiserte brukere +# 2. "Billing Account Creator rolle på for domenet" — fjerner domain-wide Creator +# +# Agent-identitet: jason.vauger@vauco.no får billing.viewer +# slik at agenten kan hente og rapportere kostnadsdata autonomt. +# +# Krav: du må være Billing Account Administrator for å kjøre dette. +# Source .env før kjøring: source .env +set -euo pipefail + +: "${BILLING_ACCOUNT_ID:?Set BILLING_ACCOUNT_ID i .env}" +: "${ALERT_EMAIL:?Set ALERT_EMAIL i .env}" + +# Brukere som får billing.viewer — kan overstyres i .env +BILLING_VIEWER_EMAILS="${BILLING_VIEWER_EMAILS:-chris.christiansen@vauco.no,jason.vauger@vauco.no}" + +# Google Workspace / Cloud Identity domene (kun nødvendig ved org-oppsett) +ORG_DOMAIN="${ORG_DOMAIN:-}" + +echo "=== 11: Billing IAM Hardening ===" +echo " Billing Account: ${BILLING_ACCOUNT_ID}" + +bash "$(dirname "$0")/00-authcheck.sh" + +# ---------------------------------------------------------------- +# FIX 1: billing.viewer til agent-identitet og eier +# ---------------------------------------------------------------- +echo "" +echo "--- Fix 1: Billing Account Viewer ---" +echo " Gir lesetilgang til: ${BILLING_VIEWER_EMAILS}" +echo " Formål: agenten (jason.vauger) kan hente kostnadsdata autonomt;" +echo " chris får eksplisitt viewer i tillegg til admin." + +IFS=',' read -ra VIEWER_LIST <<< "${BILLING_VIEWER_EMAILS}" +for EMAIL in "${VIEWER_LIST[@]}"; do + EMAIL=$(echo "${EMAIL}" | xargs) + [[ -z "${EMAIL}" ]] && continue + + if [[ "${EMAIL}" == *".gserviceaccount.com" ]]; then + MEMBER="serviceAccount:${EMAIL}" + else + MEMBER="user:${EMAIL}" + fi + + gcloud billing accounts add-iam-policy-binding "${BILLING_ACCOUNT_ID}" \ + --member="${MEMBER}" \ + --role="roles/billing.viewer" --quiet + echo "✓ billing.viewer gitt til: ${EMAIL}" +done + +# ---------------------------------------------------------------- +# FIX 2: Fjern billing.creator fra domenet +# ---------------------------------------------------------------- +echo "" +echo "--- Fix 2: Fjern Billing Account Creator fra domenet ---" + +if [[ -z "${ORG_DOMAIN}" ]]; then + echo "⚠️ ORG_DOMAIN er ikke satt — hopper over." + echo " Sett i .env: export ORG_DOMAIN=\"vauco.no\" og kjør på nytt." + echo " (Kun nødvendig om vauco.no er koblet som Google Workspace-org i GCP)" +else + DOMAIN_MEMBER="domain:${ORG_DOMAIN}" + + EXISTING=$(gcloud billing accounts get-iam-policy "${BILLING_ACCOUNT_ID}" \ + --format=json 2>/dev/null \ + | python3 -c " +import sys, json +policy = json.load(sys.stdin) +for b in policy.get('bindings', []): + if b.get('role') == 'roles/billing.creator' and '${DOMAIN_MEMBER}' in b.get('members', []): + print('found') + break +" 2>/dev/null || echo "") + + if [[ "${EXISTING}" == "found" ]]; then + gcloud billing accounts remove-iam-policy-binding "${BILLING_ACCOUNT_ID}" \ + --member="${DOMAIN_MEMBER}" \ + --role="roles/billing.creator" --quiet + echo "✓ roles/billing.creator fjernet fra domain:${ORG_DOMAIN}" + else + echo "✓ roles/billing.creator var ikke tildelt domain:${ORG_DOMAIN} — ingen endring" + fi +fi + +# ---------------------------------------------------------------- +# Vis nåværende billing IAM-policy +# ---------------------------------------------------------------- +echo "" +echo "--- Nåværende Billing Account IAM (etter endringer) ---" +gcloud billing accounts get-iam-policy "${BILLING_ACCOUNT_ID}" \ + --format="table(bindings.role,bindings.members)" 2>/dev/null || true + +echo "" +echo "=== 11: Billing IAM Hardening COMPLETE ===" +echo " Verifiser i GCP Console:" +echo " https://console.cloud.google.com/billing/${BILLING_ACCOUNT_ID}/manage" +echo "" +echo " Fremtidig: opprett billing@vauco.no som Google Group," +echo " legg chris + jason inn i gruppa, og erstatt enkeltadressene med:" +echo " export BILLING_VIEWER_EMAILS=\"billing@vauco.no\""