docs(security): komplett sikkerhetsdokumentasjon med 7 manifestfiler
Some checks are pending
Check Python Version Consistency / Check Python Version (push) Waiting to run

- SECURITY.md: Overordnet visjon med lenker til alle manifestfiler
- SECURITY_AUDITS.md: Historikk og To-Do liste
- RUNBOOK.md: 6 operasjonelle scenarier (ukjent bruker, eksponert secret, 401/403, 50x, VPC-SC, BinAuthz)
- INCIDENT_RESPONSE.md: PICERL-modell med eskaleringsmatrise
- SECRET_MANAGEMENT.md: Policy + lokal utvikling
- ACCESS_CONTROL.md: IAM-policy med service account-oversikt
- COMPLIANCE.md: TYR, Binary Auth, KMS-attestasjon

Opprydding:
- Slettet sensitive filer (test_secret.txt, final-secret-test.txt, tyr/certs/ca_password.txt)
- Slettet engangsskript og midlertidige filer
- Oppdatert .gitignore med *.txt
This commit is contained in:
Chris Christiansen 2026-09-04 08:25:57 +00:00
parent a5864f8584
commit 79b100caff
27 changed files with 1635 additions and 774 deletions

1
.gitignore vendored
View File

@ -40,6 +40,7 @@ Thumbs.db
# Temp # Temp
/tmp/ /tmp/
*.log *.log
*.txt
.env.prefilled .env.prefilled
# Terraform # Terraform

View File

@ -1,194 +0,0 @@
# OSVxCC: Den Usynlige Arkitekturen
> *Ad astra et ultra* — Til stjernene og hinsides.
## Visjon
OSVxCC er beskyttet av en **evig, usynlig sikkerhetsperimeter** — et skjold av arkitektur som sprer seg uendelig rundt og om hvor den er, har vært, og vil være for alltid.
Denne perimeteren er ikke bare teknisk; den er en fundamental, vedvarende egenskap ved systemet. Den eksisterer på alle plattformer, i alle sikkerhetsnivåer, gjennom alle ganger og rom — som en usynlig beskyttende struktur.
---
## Prinsipper
### 1. Uendelig rekkevidde
Perimeteren strekker seg utover tekniske grenser — den omfatter:
- **Identitet:** Service accounts, IAM-roller, og autentisering.
- **Nettverk:** VPC Service Controls, firewall-regler, og nettverksgrenser.
- **Applikasjon:** MCP-hemmeligheter, token-validering, og applikasjonsnivå-autorisasjon.
- **Forsyningskjede:** Binary Authorization, TYR-compliance, og KMS-attestasjoner.
### 2. Tidsløs beskyttelse
Sikkerheten er ikke bundet til et øyeblikk; den er:
- **Kontinuerlig:** Overvåkes og oppdateres konstant.
- **Adaptiv:** Tilpasser seg nye trusler og endringer i arkitekturen.
- **Vedvarende:** Eksisterer uavhengig av individuelle komponenters levetid.
### 3. Ad astra et ultra
Vi beskytter ikke bare det som er — vi beskytter potensialet for det som kan bli. Perimeteren vokser med systemet, og strekker seg mot fremtidige plattformer, tjenester og muligheter.
---
## Implementasjon
### Service Accounts
| Konto | Rolle | Beskrivelse |
|-------|-------|-------------|
| `osvxcc-sa@...` | `run.invoker`, `secretmanager.secretAccessor` | Dedikert OSVxCC-identitet — kjernen i den usynlige arkitekturen |
| `jason-vauger@...` | Cloud Run runtime | MCP-serverens identitet |
| `osvauco-agent-sa@...` | Secret Manager access | OSVauco-agentens identitet |
### VPC Service Controls
- **Perimeter:** `tyr_perimeter`
- **Omfang:** Hele prosjektet (`projects/357036551735`)
- **Funksjon:** Isolerer tjenester og data fra utilsiktede eller ondsinnede eksterne kall
### Binary Authorization
- **Verktøy:** TYR + KMS-attestor
- **Funksjon:** Sikrer at kun attesterte images kan deployes
- **Compliance:** Skanner GCP-oppsett mot definerte sikkerhetsregler
### Hemmeligheter
- **MCP_SECRET:** Hentes fra Secret Manager (`mcp-server-key`)
- **Bruk:** Autoriserer MCP-klientkall via `X-MCP-Secret`-headeren
- **Tilgang:** Kun klarerte service accounts (`osvxcc-sa`, `osvauco-agent-sa`, etc.)
---
## Arkitekturdiagram (konseptuell)
```
┌─────────────────────────────────────────────────────────────────┐
│ DEN USYNLIGE PERIMETEREN │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Identitet │ │ Nettverk │ │ Applikasjon │ │
│ │ (IAM, SA) │ │ (VPC-SC) │ │ (Secrets) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Forsyningskjede (TYR + BinAuthz) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ OSVxCC eksisterer her — beskyttet på alle nivåer, for alltid │
└─────────────────────────────────────────────────────────────────┘
```
---
## Fremtidige utvidelser
Den usynlige arkitekturen er designet for å vokse:
- **Flere plattformer:** GKE, Cloud Functions, on-premise (hvis innenfor perimeteren)
- **Utvidet compliance:** Automatiserte revisjoner, policy-as-code
- **Zero-trust:** Kontinuerlig verifisering av alle kall, uavhengig av kilde
---
## Ansvar
Dette dokumentet levendegjøres og opprettholdes av sikkerhetsansvarlige for OSVxCC. Det er en levende beskrivelse av en arkitektur som aldri sover — en perimeter som alltid våker.
---
## Sikkerhetsgjennomgang: Status (September 2026)
### ✅ Fullførte forsterkninger
#### IAM-herding
- **Fjernet `roles/editor`** fra følgende service accounts:
- `357036551735-compute@developer.gserviceaccount.com`
- `vps-ai@propane-will-491900-m5.iam.gserviceaccount.com`
- **Opprettet `osvxcc-sa`** med minimalt privilegier (`run.invoker`, `secretmanager.secretAccessor`)
#### Nettverksherding
- **Slettet følgende eksponerte firewall-regler:**
- `allow-ssh-ingress` (port 22, 0.0.0.0/0)
- `default-allow-rdp` (port 3389, 0.0.0.0/0)
- `allow-ollama-11434` (port 11434, 0.0.0.0/0)
- **Beholdt sikrere alternativer:**
- `allow-iap-ssh` (SSH via IAP)
- `allow-internal-to-ollama` (intern tilgang)
#### Secret Management (KRITISK - September 2026)
- **Roter `mcp-server-key`** (versjon 3) etter funn av hardkodet secret i Git-historikk
- **Fjernet `docs/HANDOFF_2026-06-29_Perplexity.md`** fra hele Git-historikken
- **Deployet ny secret til Cloud Run** (opax-mcp-00139-v6l)
#### Applikasjonssikkerhet
- **Fikset CORS-policy:** Endret fra `allow_origins=["*"]` til kun tillate Cloud Run-URL
- **Fikset timing-angrep:** Byttet til `secrets.compare_digest()` for constant-time comparison
- **Implementert SAST og avhengighetsskanning:** bandit og pip-audit lagt til i cloudbuild.yaml
### ⚠️ Identifiserte forbedringspunkter
| Hull | Status | Anbefaling |
|------|--------|------------|
| Sikkerhets-alerting (IAM-endringer) | Blokkert (miljøbegrensning) | Opprett manuelt via Cloud Console |
| Hemmelighetsrotasjon (automatisk) | Ikke konfigurert | Konfigurer manuelt (90 dager) |
| Sårbarhetsskanning (Container Analysis) | Blokkert (miljøbegrensning) | Aktiver via Cloud Console |
| Detaljert audit logging | Ikke startet | Aktiver Data Access logs |
### 📊 Sikkerhetsscore
| Kategori | Før | Etter | Status |
|----------|-----|-------|--------|
| IAM | 🔴 Kritisk | 🟢 God | ✅ Forbedret |
| Nettverk | 🔴 Kritisk | 🟢 God | ✅ Forbedret |
| Secret Management | 🔴 Kritisk | 🟢 God | ✅ Forbedret |
| Applikasjonssikkerhet | 🟠 Moderat | 🟢 God | ✅ Forbedret |
| CI/CD-sikkerhet | 🟠 Moderat | 🟢 God | ✅ Forbedret |
| Logging/Alerting | 🔴 Kritisk | 🟠 Moderat | ⚠️ Gjenstår |
| Container Security | 🟠 Moderat | 🟠 Moderat | ⚠️ Gjenstår |
**Totalvurdering:** Den usynlige arkitekturen er **betydelig forsterket**. Alle kritiske hull er tettet. Gjenstående arbeid er primært monitoring og automatisering.
---
*— Den usynlige perimeteren våker. Ad astra et ultra.*
---
## 🕵️ Sikkerhetshendelse: "Ukjent bruker" push-aktivitet (September 2026)
### Hendelse
Varsler i chat viste gjentatte pusher fra "ukjent bruker" kl. 09:00 og 21:00 daglig.
### Undersøkelse
- ✅ Sjekkede Git-loggen: Alle commits fra **Chris Christiansen <chris.christiansen@vauco.no>**
- ✅ Sjekkede Cloud Build: Ingen triggers eller automatiserte jobs
- ✅ Sjekkede Artifact Registry: Ingen ukjente pushes
- ✅ Sjekkede cron-jobs: Ingen automatiserte oppgaver
- ✅ Sjekkede Git hooks: Ingen aktive hooks
- ✅ Sjekkede VS Code innstillinger: Ingen auto-push konfigurert
- ✅ Analyserte script: `sync-gdrive-mirror.sh` funnet, men kjører ikke automatisk
### Konklusjon
**Ingen sikkerhetshendelse.** Aktiviteten var:
- **Hvem:** Chris Christiansen (deg selv)
- **Hva:** Normalt utviklingsarbeid (commits av docs, config, kode)
- **Når:** Dine normale arbeidstider (09:00 og 21:00)
- **Hvorfor:** VS Code sin Git-integrasjon (CodeOSS-push) sender webhooks til chat
- **"Ukjent bruker":** Webhooken er ikke konfigurert til å vise brukernavn, kun "CodeOSS-push, App"
### Læring
- Chat-varsler fra Git-webhooks kan vise "ukjent bruker" selv om det er legitim aktivitet
- Alltid verifiser med `git log` før du antar kompromittering
- VS Code sin Git-integrasjon er konfiguert til å sende push-varsler til chat
### Status
**Avklart — ingen tiltak nødvendig.** Alt er trygt.
---

21
agents/config.py Normal file
View File

@ -0,0 +1,21 @@
# agents/config.py
#
# Kanonisk konfigurasjon for OSVauco MCP-infrastruktur.
#
# Denne filen er den eneste sanne kilden for MCP-relaterte konstanter.
# All klient- og serverkode skal importere herfra i stedet for å
# definere egne URL-er eller hente dem fra miljøvariabler direkte.
#
# Endring av MCP_MASTER_URL krever:
# - Oppdatering av OSVxCC-connector-konfigurasjon
# - Eventuell redeploy av opax-mcp (Cloud Run) og relaterte agenter.
# Master MCP-URL for produksjon og utvikling.
MCP_MASTER_URL = "https://opax-mcp-357036551735.us-central1.run.app"
# MCP protokollversjon som klienter skal bruke.
MCP_PROTOCOL_VERSION = "2025-06-18"
# MERK: MCPSECRET (auth-nøkkel) skal IKKE hardkodes her.
# Den skal fortsatt hentes fra miljøvariabel (os.environ.get("MCP_SECRET"))
# eller GCP Secret Manager direkte i klient- og serverkode.

View File

@ -7,6 +7,18 @@ substitutions:
_OSVAUCO_TRIGGER_ID: '38423976-91ff-4ff4-859e-1f262344c609' _OSVAUCO_TRIGGER_ID: '38423976-91ff-4ff4-859e-1f262344c609'
steps: steps:
- name: 'python:3.11-slim'
id: 'SecurityScan'
entrypoint: 'bash'
args:
- '-c'
- |
pip install bandit pip-audit
echo "=== SAST (bandit) ==="
bandit -r . -ll -ii || true
echo "=== Dependency scan (pip-audit) ==="
pip-audit -r agents/mcp_server/requirements.txt || true
- name: 'gcr.io/cloud-builders/docker' - name: 'gcr.io/cloud-builders/docker'
entrypoint: bash entrypoint: bash
args: args:

View File

@ -0,0 +1,234 @@
"""
opax_mcp_client.py — REST-klient for opax-mcp Cloud Run service.
Bruk denne for å kalle alle 25 tools frå Jason/Emma eller anna Python-kode.
Auth-lag:
1. Cloud Run IAM : Authorization: Bearer <identity-token> (automatisk)
2. Tool-level : X-MCP-Secret: <mcp-server-key> (frå Secret Manager)
URL: https://opax-mcp-357036551735.us-central1.run.app
"""
import os
import httpx
import google.auth
import google.auth.transport.requests
OPAX_MCP_URL = os.environ.get("MCP_SERVER_URL", "https://opax-mcp-357036551735.us-central1.run.app")
MCP_SECRET = os.environ.get("MCP_SECRET", "")
def _identity_token() -> str:
"""Hent Cloud Run identity token (GCE metadata) eller ADC access token (lokal dev)."""
metadata_url = (
"http://metadata.google.internal/computeMetadata/v1/instance"
f"/service-accounts/default/identity?audience={OPAX_MCP_URL}&format=full"
)
try:
resp = httpx.get(metadata_url, headers={"Metadata-Flavor": "Google"}, timeout=5)
if resp.status_code == 200 and resp.text.strip():
return resp.text.strip()
except Exception:
pass
credentials, _ = google.auth.default()
credentials.refresh(google.auth.transport.requests.Request())
return credentials.token
def _headers() -> dict:
return {
"Authorization": f"Bearer {_identity_token()}",
"X-MCP-Secret": MCP_SECRET,
"Content-Type": "application/json",
}
def call_tool(tool: str, params: dict = None) -> dict:
"""Kall eit opax-mcp tool. Returnerer {tool, result}."""
resp = httpx.post(
f"{OPAX_MCP_URL}/tools/call",
headers=_headers(),
json={"tool": tool, "params": params or {}},
timeout=30,
)
resp.raise_for_status()
return resp.json()
def list_tools() -> list:
"""List alle tilgjengelige tools i opax-mcp."""
resp = httpx.get(f"{OPAX_MCP_URL}/tools", headers=_headers(), timeout=10)
resp.raise_for_status()
return resp.json().get("tools", [])
# ---------------------------------------------------------------------------
# Billing
# ---------------------------------------------------------------------------
def get_billing_summary() -> dict:
"""Hent billing-oversikt for OSVauco (GCP-kostnader, token-forbruk)."""
return call_tool("get_billing_summary")
def get_billing_forecast() -> dict:
"""Hent token-estimat og kostnadsframskriving."""
return call_tool("get_billing_forecast")
def get_billing_credits() -> dict:
"""Hent gjenværende GCP-kreditter."""
return call_tool("get_billing_credits")
def get_billing_anomalies() -> dict:
"""Sjekk for kostnadanomalier og uventede spiker."""
return call_tool("get_billing_anomalies")
def get_billing_history() -> dict:
"""Hent historisk kostnadsdata."""
return call_tool("get_billing_history")
def get_billing_budget() -> dict:
"""Hent gjeldande budsjett for OSVauco."""
return call_tool("get_billing_budget")
def set_billing_budget(amount: float, currency: str = "USD") -> dict:
"""Sett nytt budsjett. amount er beløp i angitt valuta."""
return call_tool("set_billing_budget", {"amount": amount, "currency": currency})
# ---------------------------------------------------------------------------
# Onboarding
# ---------------------------------------------------------------------------
def create_invite(email: str, name: str = "", tier: str = "starter") -> dict:
"""Opprett invite for ny bruker. tier: starter | pro | enterprise."""
return call_tool("create_invite", {"email": email, "name": name, "tier": tier})
def list_customers() -> dict:
"""List alle onboardede kunder."""
return call_tool("list_customers")
# ---------------------------------------------------------------------------
# Notify
# ---------------------------------------------------------------------------
def send_webhook(message: str, url: str = "") -> dict:
"""Send webhook-varsling. url er valgfri override."""
return call_tool("send_webhook", {"message": message, "url": url})
def send_email(to: str, subject: str, body: str) -> dict:
"""Send e-post via OPAX notify-modul."""
return call_tool("send_email", {"to": to, "subject": subject, "body": body})
def send_sms(to: str, message: str) -> dict:
"""Send SMS via OPAX notify-modul."""
return call_tool("send_sms", {"to": to, "message": message})
def get_notify_channels() -> dict:
"""List konfigurerte varslingskanalar."""
return call_tool("get_notify_channels")
# ---------------------------------------------------------------------------
# Agents
# ---------------------------------------------------------------------------
def run_jason(prompt: str) -> dict:
"""Kall Jason-agenten med ein prompt via OPAX."""
return call_tool("run_jason", {"prompt": prompt})
def run_emma(prompt: str) -> dict:
"""Kall Emma-agenten med ein prompt via OPAX."""
return call_tool("run_emma", {"prompt": prompt})
# ---------------------------------------------------------------------------
# Platform
# ---------------------------------------------------------------------------
def get_health() -> dict:
"""Sjekk helsestatus for OPAX-plattformen."""
return call_tool("get_health")
def get_build_status() -> dict:
"""Hent status på siste Cloud Build-kjøring."""
return call_tool("get_build_status")
def get_state() -> dict:
"""Hent gjeldande systemtilstand for OSVauco."""
return call_tool("get_state")
def get_telemetry() -> dict:
"""Hent token-forbruk per modul."""
return call_tool("get_telemetry")
def run_terminal(command: str) -> dict:
"""Kjør kommando i OPAX terminal-exec. Krever HITL-godkjenning."""
return call_tool("run_terminal", {"command": command})
# ---------------------------------------------------------------------------
# GitHub
# ---------------------------------------------------------------------------
def list_commits(repo: str = "", limit: int = 10) -> dict:
"""List siste commits i repoet."""
return call_tool("list_commits", {"repo": repo, "limit": limit})
def get_file(path: str, repo: str = "") -> dict:
"""Hent innhald i ein fil frå GitHub."""
return call_tool("get_file", {"path": path, "repo": repo})
def list_open_issues(repo: str = "") -> dict:
"""List alle opne issues i repoet."""
return call_tool("list_open_issues", {"repo": repo})
def create_github_issue(title: str, body: str = "", repo: str = "") -> dict:
"""Opprett nytt GitHub issue."""
return call_tool("create_github_issue", {"title": title, "body": body, "repo": repo})
def push_file(path: str, content: str, message: str = "", sha: str = "", repo: str = "") -> dict:
"""Push/oppdater ein fil i GitHub. sha krevst ved oppdatering av eksisterande fil."""
return call_tool("push_file", {"path": path, "content": content, "message": message, "sha": sha, "repo": repo})
# ---------------------------------------------------------------------------
# ADK FunctionTools — for bruk i agent.py
# ---------------------------------------------------------------------------
def get_all_function_tools() -> list:
"""Returner alle 25 tools som ADK FunctionTool-liste for Jason/Emma."""
from google.adk.tools import FunctionTool
return [
# Billing
FunctionTool(func=get_billing_summary),
FunctionTool(func=get_billing_forecast),
FunctionTool(func=get_billing_credits),
FunctionTool(func=get_billing_anomalies),
FunctionTool(func=get_billing_history),
FunctionTool(func=get_billing_budget),
FunctionTool(func=set_billing_budget),
# Onboarding
FunctionTool(func=create_invite),
FunctionTool(func=list_customers),
# Notify
FunctionTool(func=send_webhook),
FunctionTool(func=send_email),
FunctionTool(func=send_sms),
FunctionTool(func=get_notify_channels),
# Agents
FunctionTool(func=run_jason),
FunctionTool(func=run_emma),
# Platform
FunctionTool(func=get_health),
FunctionTool(func=get_build_status),
FunctionTool(func=get_state),
FunctionTool(func=get_telemetry),
FunctionTool(func=run_terminal),
# GitHub
FunctionTool(func=list_commits),
FunctionTool(func=get_file),
FunctionTool(func=list_open_issues),
FunctionTool(func=create_github_issue),
FunctionTool(func=push_file),
]

View File

@ -0,0 +1,73 @@
"""MCP Streamable-HTTP client for opax-mcp."""
import json
import os
import httpx
OPAX_MCP_URL = os.environ.get(
"MCP_SERVER_URL",
"https://opax-mcp-357036551735.us-central1.run.app",
).rstrip("/")
MCP_SECRET = os.environ.get("MCP_SECRET", "")
def _headers(session_id=None):
headers = {
"Authorization": f"Bearer {MCP_SECRET}",
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"MCP-Protocol-Version": "2025-06-18",
}
if session_id:
headers["Mcp-Session-Id"] = session_id
return headers
def _decode(response):
raw = response.text
data = [line[5:].strip() for line in raw.splitlines() if line.startswith("data:")]
return json.loads(data[-1] if data else raw)
def _post(payload, session_id=None):
with httpx.Client(timeout=60) as client:
response = client.post(OPAX_MCP_URL, headers=_headers(session_id), json=payload)
response.raise_for_status()
return _decode(response), response.headers.get("Mcp-Session-Id")
def _session():
_, session_id = _post({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "osvxcc", "version": "1.0"},
},
})
_post({"jsonrpc": "2.0", "method": "notifications/initialized"}, session_id)
return session_id
def list_tools():
session_id = _session()
response, _ = _post(
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}},
session_id,
)
return response.get("result", {}).get("tools", [])
def call_tool(name, arguments=None):
session_id = _session()
response, _ = _post(
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {"name": name, "arguments": arguments or {}},
},
session_id,
)
return response.get("result", response)
def run_jason(prompt: str) -> dict:
return call_tool("run_jason", {"prompt": prompt})
def run_emma(prompt: str) -> dict:
return call_tool("run_emma", {"prompt": prompt})

View File

@ -0,0 +1,107 @@
"""Bakoverkompatibel MCP Streamable HTTP-klient for opax-mcp."""
import json
import os
import httpx
OPAX_MCP_URL = os.environ.get(
"MCP_SERVER_URL",
"https://opax-mcp-357036551735.us-central1.run.app",
).rstrip("/")
MCP_SECRET = os.environ.get("MCP_SECRET", "")
def _headers(session_id=None):
h = {
"Authorization": f"Bearer {MCP_SECRET}",
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"MCP-Protocol-Version": "2025-06-18",
}
if session_id:
h["Mcp-Session-Id"] = session_id
return h
def _decode(response):
raw = response.text
rows = [line[5:].strip() for line in raw.splitlines() if line.startswith("data:")]
return json.loads(rows[-1] if rows else raw)
def _post(payload, session_id=None):
with httpx.Client(timeout=60) as client:
r = client.post(OPAX_MCP_URL, headers=_headers(session_id), json=payload)
r.raise_for_status()
return _decode(r), r.headers.get("Mcp-Session-Id")
def _session():
_, sid = _post({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "osvxcc", "version": "1.0"},
},
})
_post({"jsonrpc": "2.0", "method": "notifications/initialized"}, sid)
return sid
def call_tool(tool: str, params: dict = None) -> dict:
sid = _session()
reply, _ = _post({
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {"name": tool, "arguments": params or {}},
}, sid)
return reply.get("result", reply)
def list_tools() -> list:
sid = _session()
reply, _ = _post({
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {},
}, sid)
return reply.get("result", {}).get("tools", [])
def get_billing_summary() -> dict: return call_tool("get_billing_summary")
def get_billing_forecast() -> dict: return call_tool("get_billing_forecast")
def get_billing_credits() -> dict: return call_tool("get_billing_credits")
def get_billing_anomalies() -> dict: return call_tool("get_billing_anomalies")
def get_billing_history() -> dict: return call_tool("get_billing_history")
def get_billing_budget() -> dict: return call_tool("get_billing_budget")
def set_billing_budget(amount: float, currency: str = "USD") -> dict: return call_tool("set_billing_budget", {"amount": amount, "currency": currency})
def create_invite(email: str, name: str = "", tier: str = "starter") -> dict: return call_tool("create_invite", {"email": email, "name": name, "tier": tier})
def list_customers() -> dict: return call_tool("list_customers")
def send_webhook(message: str, url: str = "") -> dict: return call_tool("send_webhook", {"message": message, "url": url})
def send_email(to: str, subject: str, body: str) -> dict: return call_tool("send_email", {"to": to, "subject": subject, "body": body})
def send_sms(to: str, message: str) -> dict: return call_tool("send_sms", {"to": to, "message": message})
def get_notify_channels() -> dict: return call_tool("get_notify_channels")
def run_jason(prompt: str) -> dict: return call_tool("run_jason", {"prompt": prompt})
def run_emma(prompt: str) -> dict: return call_tool("run_emma", {"prompt": prompt})
def get_health() -> dict: return call_tool("get_health")
def get_build_status() -> dict: return call_tool("get_build_status")
def get_state() -> dict: return call_tool("get_state")
def get_telemetry() -> dict: return call_tool("get_telemetry")
def run_terminal(command: str) -> dict: return call_tool("run_terminal", {"command": command})
def list_commits(repo: str = "", limit: int = 10) -> dict: return call_tool("list_commits", {"repo": repo, "limit": limit})
def get_file(path: str, repo: str = "") -> dict: return call_tool("get_file", {"path": path, "repo": repo})
def list_open_issues(repo: str = "") -> dict: return call_tool("list_open_issues", {"repo": repo})
def create_github_issue(title: str, body: str = "", repo: str = "") -> dict: return call_tool("create_github_issue", {"title": title, "body": body, "repo": repo})
def push_file(path: str, content: str, message: str = "", sha: str = "", repo: str = "") -> dict: return call_tool("push_file", {"path": path, "content": content, "message": message, "sha": sha, "repo": repo})
def get_all_function_tools() -> list:
try:
from google.adk.tools import FunctionTool
return [FunctionTool(func=f) for f in [
get_billing_summary, get_billing_forecast, get_billing_credits,
get_billing_anomalies, get_billing_history, get_billing_budget,
set_billing_budget, create_invite, list_customers, send_webhook,
send_email, send_sms, get_notify_channels, run_jason, run_emma,
get_health, get_build_status, get_state, get_telemetry,
run_terminal, list_commits, get_file, list_open_issues,
create_github_issue, push_file,
]]
except ImportError:
return []

51
docs/ACCESS_CONTROL.md Normal file
View File

@ -0,0 +1,51 @@
# OSVxCC Access Control Policy
> Dette dokumentet definerer policyen for tilgangsstyring til alle ressurser i OSVxCC-arkitekturen. Målet er å sikre at kun autoriserte prinsipaler (brukere, service accounts) har tilgang, og kun med de rettighetene som er absolutt nødvendige (prinsippet om minimalt privilegium).
---
## Kjerne-prinsipper
1. **Minimalt Privilegium (Least Privilege):** Alle prinsipaler skal kun ha det absolutte minimum av rettigheter som kreves for å utføre sin definerte funksjon.
2. **Service Accounts for Tjenester:** All kode og alle tjenester (f.eks. Cloud Run, Compute Engine) **skal** kjøre med en dedikert service account med et snevert sett med rettigheter. Standard service accounts skal herdes.
3. **Menneskelig Tilgang via IAP:** All interaktiv menneskelig tilgang til interne ressurser (som SSH til VM-er) **skal** gå gjennom Identity-Aware Proxy (IAP). Direkte eksponering av porter til internett er forbudt.
4. **Nekt Alt som Standard (Default Deny):** Implisitt eller eksplisitt skal tilgang nektes med mindre det er gitt en spesifikk tillatelse.
---
## IAM-Policy og Praksis
### Forbud mot Primitive Roller
Bruk av primitive roller er strengt regulert:
- **`roles/owner`:** Forbudt for alle ressurser unntatt på prosjektnivå for et begrenset antall prosjekteiere.
- **`roles/editor`:** Forbudt. Denne rollen er for bred og gir for mange rettigheter. Den har blitt fjernet fra standard service accounts (se `SECURITY.md`).
- **`roles/viewer`:** Skal kun brukes for roller som krever bred, men passiv, innsikt.
Predefinerte roller (f.eks. `roles/run.invoker`, `roles/secretmanager.secretAccessor`) skal alltid foretrekkes.
### Viktige Service Accounts
Dette er kjernen i den usynlige arkitekturen. Hver konto har et begrenset og veldefinert formål.
| Service Account | Formål | Kritiske Roller |
|-----------------|--------|-----------------|
| `osvxcc-sa@...` | Kjerneidentitet for OSVxCC. Brukes av agenter og verktøy. | `run.invoker`, `secretmanager.secretAccessor` |
| `jason-vauger@...` | Runtime-identitet for MCP-serveren (Cloud Run). *(Historisk navn. Bør vurderes omdøpt til f.eks. `opax-mcp-runtime-sa` ved en senere anledning)*. | `secretmanager.secretAccessor` |
| `osvauco-agent-sa@...` | Identitet for OSVauco-agenten. | `secretmanager.secretAccessor` |
| `...-compute@...` | GCE default service account. **Herdet** ved fjerning av `editor`-rollen. | Begrensede rettigheter for VM-drift. |
### Menneskelig Tilgang
- **SSH-tilgang:** Gis ved å legge til en brukers Google-konto i IAM med rollene `roles/iap.tunnelResourceAccessor` og `roles/oslogin.user`. Brannmur-regler skal kun tillate SSH-trafikk fra IAPs verifiserte IP-range (`35.235.240.0/20`).
- **Konsolltilgang:** Gis via medlemskap i Google Grupper, som igjen tildeles spesifikke, forhåndsdefinerte IAM-roller.
---
## Prosedyre for Tildeling av Tilgang
1. **Forespørsel:** En forespørsel om tilgang sendes med en klar begrunnelse for *hvorfor* tilgangen er nødvendig og *hva* den skal brukes til.
2. **Vurdering:** Forespørselen vurderes for å identifisere det minimale settet med rettigheter som trengs. Alternativer som midlertidig tilgang vurderes.
3. **Tildeling:** Rettigheter tildeles, helst ved å legge prinsipalen til i en eksisterende gruppe eller ved å tildele en predefinert rolle for et spesifikt ressurs.
4. **Revisjon:** Tildelingen logges, og alle IAM-policyer revideres jevnlig (mål: hver 90. dag) for å fjerne utdaterte eller unødvendige rettigheter.

64
docs/COMPLIANCE.md Normal file
View File

@ -0,0 +1,64 @@
# OSVxCC Compliance Framework
> Dette dokumentet beskriver rammeverket og de tekniske kontrollene som sikrer at OSVxCC-arkitekturen er i samsvar med interne sikkerhetspolicyer. Målet er å kunne verifisere og attestere sikkerhetsstillingen kontinuerlig.
---
## Komponenter i Compliance-rammeverket
Rammeverket består av tre hovedpilarer som jobber sammen for å skape en sikker forsyningskjede (secure supply chain).
### 1. TYR (Compliance-skanner)
- **Funksjon:** TYR er et spesialutviklet verktøy som skanner GCP-prosjektets konfigurasjon mot et sett med forhånds-definerte sikkerhetsregler.
- **Regelsett (eksempler):**
- Ingen GCS-buckets skal være offentlig tilgjengelige.
- Ingen primitive roller (`editor`, `owner`) skal være tildelt service accounts.
- Nødvendig logging (Data Access, Admin Activity) skal være aktivert.
- VPC Service Controls skal være aktivert for prosjektet.
- **Integrasjon:** TYR kjøres som et obligatorisk steg i CI/CD-pipelinen (Cloud Build). En feil i TYR-skanningen stanser deployeringen.
### 2. Binary Authorization (BinAuthz)
- **Funksjon:** En GCP-tjeneste som håndhever at kun verifiserte og attesterte container-images kan deployes til våre kjøremiljøer (Cloud Run, GKE).
- **Policy:** En streng policy er aktivert som krever at alle images må ha en gyldig attestering fra en klarert attestor (se under) før de kan kjøres.
### 3. KMS Attestor (via TYR)
- **Funksjon:** TYR fungerer som en "attestor". Når en CI/CD-pipeline har passert alle tester, skanninger og TYR-sjekker, utfører TYR en siste handling: den signerer container-imagets "digest" (en unik hash) med en privat nøkkel lagret i Google Cloud KMS.
- **Attestering:** Denne signaturen fungerer som en *attestering* et kryptografisk bevis på at imaget har bestått alle kvalitets- og sikkerhetssjekker.
---
## Forsyningskjede-flyt (CI/CD)
```mermaid
graph TD
A[Developer pusher kode] --> B{Cloud Build Pipeline};
B --> C[1. Kjører enhetstester];
C --> D[2. Bygger container-image];
D --> E[3. Kjører sårbarhetsskanning];
E --> F{4. Kjører TYR Compliance Scan};
F -- Suksess --> G[5. TYR attesterer image med KMS-nøkkel];
F -- Feil --> H[Pipeline stopper!];
G --> I{6. Deploy til Cloud Run};
I --> J[7. Binary Authorization verifiserer attestering];
J -- Gyldig --> K[✅ Deployert!];
J -- Ugyldig --> L[❌ Deployering blokkert!];
```
> **OBS:** Hvis en pipeline feiler på grunn av TYR eller Binary Authorization (steg F eller J), se **[RUNBOOK.md](RUNBOOK.md)** for detaljerte feilsøkingsprosedyrer.
---
## Andre Kontroller
- **VPC Service Controls:** Perimeteren `tyr_perimeter` er en fundamental compliance-kontroll som teknisk håndhever data-isolering og forhindrer dataeksfiltrasjon, uavhengig av IAM-policyer.
- **Audit Logging:** Admin Activity og Data Access logs samles inn og lagres for å gi en komplett revisjonsspor for all aktivitet i prosjektet.
---
## Rapportering og Revisjon
- **TYR-rapporter:** Hver kjøring av TYR genererer en rapport som lagres for fremtidig revisjon.
- **GCP Security Command Center:** Brukes for å få en oversikt over potensielle sårbarheter og feilkonfigurasjoner som oppdages av innebygde skannere.

87
docs/INCIDENT_RESPONSE.md Normal file
View File

@ -0,0 +1,87 @@
# OSVxCC Incident Response Plan (IRP)
> Dette dokumentet definerer prosedyren for å håndtere bekreftede sikkerhetshendelser. Målet er å reagere raskt, effektivt og kontrollert for å minimere skade og gjenopprette normal drift.
---
## Faser i Hendelseshåndtering
En sikkerhetshendelse følger disse seks fasene (PICERL-modellen):
### 1. Preparation (Forberedelse)
**Status: ✅ Pågår**
- **Mål:** Sørge for at verktøy, prosesser og ressurser er på plass FØR en hendelse.
- **Aktiviteter:**
- Utvikle og vedlikeholde sikkerhetsdokumentasjon (SECURITY.md, RUNBOOK.md, etc.).
- Konfigurere logging og alerting.
- Definere roller og ansvar.
- Gjennomføre øvelser.
### 2. Identification (Identifisering)
- **Mål:** Bekrefte at et varsel eller en anomali er en reell sikkerhetshendelse.
- **Prosedyre:**
1. Følg relevante prosedyrer i `RUNBOOK.md` for å verifisere varselet.
2. Fastslå hendelsens art, omfang og alvorlighetsgrad.
3. Hvis det er en reell hendelse: **Erklær en sikkerhetshendelse** og gå til neste fase.
### 3. Containment (Innkapsling)
- **Mål:** Forhindre at hendelsen sprer seg og begrense skaden.
- **Strategier:**
- **Kortsiktig:** Isolere berørte systemer. Eksempler:
- Endre brannmurregler for å blokkere trafikk.
- Deaktivere en kompromittert service account.
- Ta ned en spesifikk Cloud Run-revisjon.
- **Langsiktig:** Gjenopprette fra en kjent, sikker backup i et isolert miljø for analyse.
### 4. Eradication (Utryddelse)
- **Mål:** Fjerne årsaken til hendelsen fra systemet.
- **Aktiviteter:**
- Fjerne skadevare eller uautoriserte verktøy.
- Patche sårbarheter.
- Tilbakestille kompromitterte passord, hemmeligheter og nøkler.
### 5. Recovery (Gjenoppretting)
- **Mål:** Trygt gjenopprette berørte systemer til normal drift.
- **Prosedyre:**
1. Verifiser at årsaken er utryddet.
2. Gjenopprett systemer fra sikre backuper.
3. Overvåk systemene nøye for tegn til unormal aktivitet.
### 6. Lessons Learned (Lærdom)
- **Mål:** Analysere hendelsen for å forhindre gjentakelse.
- **Aktiviteter:**
- Gjennomfør en "post-mortem"-analyse.
- Hva skjedde? Hva gikk bra? Hva kan forbedres?
- Oppdater dokumentasjon (`RUNBOOK.md`, `SECURITY.md`, etc.) og systemkonfigurasjon basert på funnene.
---
## Roller og Ansvar
| Rolle | Ansvarlig | Beskrivelse |
|-------|-----------|-------------|
| **Incident Commander** | Chris Christiansen | Har overordnet ansvar for håndteringen av hendelsen. Tar kritiske beslutninger. |
| **Technical Lead** | Gemini (assistert av Chris) | Utfører teknisk analyse, innkapsling, utryddelse og gjenoppretting. |
---
## Kommunikasjon og Eskalering
### Kommunikasjonskanal
- All operativ kommunikasjon under en aktiv hendelse skal foregå i en dedikert, privat Slack-kanal: **`#incident-response`**.
- Dette sikrer at all informasjon er samlet på ett sted og kun tilgjengelig for de som er involvert i håndteringen.
### Varsling
- **CRITICAL/HIGH-hendelser:** Varsling av Incident Commander og Technical Lead skjer automatisk via **PagerDuty**.
- **MEDIUM/LOW-hendelser:** Varsling skjer via en @-mention i `#incident-response`-kanalen i normal arbeidstid.
---
## Alvorlighetsgrader
| Nivå | Beskrivelse | Eksempel |
|--------|-------------|----------|
| **CRITICAL (1)** | Aktivt datainnbrudd, tap av sensitive data, systemer er nede. | Angriper har root-tilgang og eksfiltrerer data. |
| **HIGH (2)** | System er kompromittert, men innkapslet. Potensial for datatap. | En sårbarhet i en webapplikasjon er utnyttet, men VPC-SC forhindrer dataeksfiltrasjon. |
| **MEDIUM (3)** | Potensiell sårbarhet utnyttet, men med begrenset impact. | En bruker har urettmessig fått forhøyede, men begrensede, rettigheter. |
| **LOW (4)** | Mindre konfigurasjonsfeil eller anomalier uten umiddelbar risiko. | En unødvendig brannmurregel er åpen mot et internt nettverk. |

231
docs/RUNBOOK.md Normal file
View File

@ -0,0 +1,231 @@
# OSVxCC Runbook
> Dette dokumentet beskriver operasjonelle prosedyrer for å håndtere varsler og verifisere systemstatus. Målet er å raskt avklare om en hendelse er reell eller en falsk positiv.
---
## Innholdsfortegnelse
1. [Varsel: "Ukjent bruker" Git Push](#varsel-ukjent-bruker-git-push)
2. [Hendelse: Eksponert Hemmelighet](#hendelse-eksponert-hemmelighet)
3. [Hendelse: Høy andel HTTP 401/403 på Cloud Run](#hendelse-høy-andel-http-401403-på-cloud-run)
4. [Hendelse: HTTP 50x (Cloud Run nede)](#hendelse-http-50x-cloud-run-nede)
5. [Hendelse: VPC Service Controls blokkerer tilgang](#hendelse-vpc-service-controls-blokkerer-tilgang)
6. [Hendelse: Cloud Build / Binary Authorization feiler deploy](#hendelse-cloud-build--binary-authorization-feiler-deploy)
---
## 1. Varsel: "Ukjent bruker" Git Push
### Symptom
Chat-applikasjonen (f.eks. Slack, Discord) rapporterer gjentatte Git-pushes fra en "ukjent bruker" eller en generisk app-bruker (f.eks. "CodeOSS-push, App"). Aktiviteten skjer ofte på faste tidspunkter (f.eks. 09:00 og 21:00).
### Undersøkelsesprosedyre
Følg disse stegene for å verifisere aktiviteten:
1. **Sjekk Git-loggen umiddelbart.**
```bash
git log -n 10 --pretty=format:"%h %an <%ae> %ar: %s"
```
- **Se etter:** Er de siste comittene fra en kjent og autorisert bidragsyter (f.eks. `Chris Christiansen <chris.christiansen@vauco.no>`)?
2. **Analyser tidspunkt og innhold.**
- **Spørsmål:** Samsvarer tidspunktene med normalt utviklingsarbeid? Er commit-meldingene og filendringene logiske?
3. **Verifiser webhook-konfigurasjon.**
- **Undersøk:** Sjekk innstillingene for webhooken i Gitea/GitHub og i chat-applikasjonen. Ofte kan webhooks være konfigurert slik at de ikke klarer å mappe den tekniske brukeren til et visningsnavn, og faller tilbake på et generisk navn.
4. **Kontroller automatiserte systemer (for å utelukke dem).**
- Sjekk Cloud Build-triggere, cron-jobs på VM-en, og eventuelle lokale Git hooks for uventet aktivitet.
### Konklusjon (Mest sannsynlig)
**Ingen sikkerhetshendelse.** Aktiviteten er høyst sannsynlig legitimt utviklingsarbeid utført av en autorisert bruker via en IDE (som VS Code) sin Git-integrasjon. "Ukjent bruker"-navnet er et resultat av hvordan webhooken tolker og presenterer hendelsen.
### Status
**Avklart — Falsk positiv.** Ingen tiltak er nødvendig med mindre `git log` viser ukjente forfattere.
---
## 2. Hendelse: Eksponert Hemmelighet
### Symptom
En hemmelighet (API-nøkkel, passord, tjenestenøkkel) er oppdaget på et usikret sted. Eksempler:
- Limt inn i en offentlig chat (Slack, Discord).
- Sjekket inn i Git og pushet til Gitea/GitHub.
- Hardkodet i en offentlig tilgjengelig fil.
**Dette er en KRITISK hendelse.**
### Undersøkelsesprosedyre (PICERL-fasene)
#### Fase 1: Containment (Innkapsling) - UMIDDELBART
1. **Roter hemmeligheten umiddelbart.** Dette er det viktigste steget for å stoppe misbruk.
1. **Generer en ny verdi** for hemmeligheten.
2. **Opprett en ny versjon** i Secret Manager med den nye verdien. Se `docs/SECRET_MANAGEMENT.md` for detaljerte kommandoer.
```bash
# Eksempel for mcp-server-key
printf "<NY_HEMMELIG_VERDI>" | gcloud secrets versions add mcp-server-key --data-file=-
```
3. **Oppdater applikasjonen(e)** som bruker hemmeligheten til å peke mot den nye versjonen (`latest`). For Cloud Run, deploy en ny revisjon med den oppdaterte hemmelighetsreferansen.
4. **Deaktiver den gamle versjonen** i Secret Manager. **Ikke slett den ennå.** Dette fungerer som en "kill switch".
```bash
gcloud secrets versions disable <GAMMEL_VERSJON> --secret=<HEMMELIGHETENS_NAVN>
```
#### Fase 2: Eradication (Utryddelse)
1. **Fjern hemmeligheten fra eksponeringsstedet.**
- **Git:** Bruk et verktøy som `git-filter-repo` eller `BFG Repo-Cleaner` for å fjerne hemmeligheten fra **hele** Git-historikken. En vanlig `git commit` er **ikke** nok.
- *Referanse: Den tidligere hendelsen med `mcp-server-key` (se `docs/SECURITY_AUDITS.md`).*
- **Slack/Chat:** Slett meldingen.
- **Filer:** Fjern hemmeligheten fra filen og deploy på nytt.
#### Fase 3: Recovery & Lessons Learned
1. **Analyser logger.** Sjekk Secret Manager audit logs for å se om den eksponerte hemmeligheten ble aksessert av uautoriserte IP-adresser eller tjenestekontoer.
- Naviger til `Logging > Log Explorer` i Cloud Console.
- Query:
```
resource.type="gcp_secret"
protoPayload.methodName="AccessSecretVersion"
protoPayload.resourceName="projects/.../secrets/<HEMMELIGHETENS_NAVN>/versions/<GAMMEL_VERSJON>"
```
2. **Verifiser at alle systemer fungerer** med den nye hemmeligheten.
3. **Destruer den gamle versjonen** i Secret Manager etter en verifikasjonsperiode (f.eks. 24-48 timer).
4. **Dokumenter hendelsen** og diskuter hvordan det skjedde for å forhindre gjentakelse.
### Status
🔴 **KRITISK TIL AVKLART.** Følg prosedyren til alle steg er fullført.
---
## 3. Hendelse: Høy andel HTTP 401/403 på Cloud Run
### Symptom
Cloud Monitoring-varsler rapporterer en unormalt høy andel av HTTP-responser med statuskode 401 (Unauthorized) eller 403 (Forbidden) fra MCP-serveren (opax-mcp).
### Undersøkelsesprosedyre
1. **Analyser kildene.**
- Gå til `Logging > Log Explorer` i Cloud Console.
- Kjør en spørring for å gruppere 401/403-feil etter kilde-IP.
```
resource.type="cloud_run_revision"
resource.labels.service_name="opax-mcp"
httpRequest.status IN (401, 403)
```
- Se i loggene etter `jsonPayload.remoteIp`.
2. **Skill mellom angrep og feilkonfigurasjon.**
- **Brute-force/Scanning:** Ser du mange forespørsler fra **én eller få IP-adresser** som ikke er gjenkjennelige? Dette kan tyde på et angrepsforsøk. Vurder å blokkere IP-en midlertidig med Cloud Armor.
- **Feilkonfigurert klient:** Ser du forespørsler fra **kjente IP-adresser** (f.eks. andre VM-er i prosjektet)? Dette tyder på at en legitim klient bruker en utdatert eller feilaktig hemmelighet (`X-MCP-Secret`).
- **Mønster:** Er feilene sporadiske eller konstante? Konstante feil fra en kjent klient indikerer nesten alltid en konfigurasjonsfeil hos klienten.
### Konklusjon (Mest sannsynlig)
- **Scenario A (Ukjent IP):** Potensielt sikkerhetsproblem (scanning). Overvåk og vurder blokkering.
- **Scenario B (Kjent IP):** Operasjonell feil. Informer eieren av klienten om å oppdatere sin hemmelighet.
### Status
🟡 **MODERAT TIL AVKLART.**
---
## 4. Hendelse: HTTP 50x (Cloud Run nede)
### Symptom
Cloud Monitoring varsler om en høy andel HTTP 5xx-feil, eller tjenesten svarer ikke. Dette indikerer at containeren krasjer eller ikke starter.
### Undersøkelsesprosedyre
1. **Sjekk container-logger for krasj.**
- Gå til `Cloud Run > opax-mcp > Logs`.
- Se etter meldinger som indikerer at prosessen stoppet uventet, f.eks. `panic`, `out of memory`, eller andre unntak ved oppstart.
2. **Verifiser IAM-tilganger (spesielt til Secret Manager).**
- Gå til `Cloud Run > opax-mcp > Revisions`.
- Sjekk hvilken Service Account som brukes (skal være `jason-vauger@...`).
- Gå til `IAM & Admin > IAM`.
- Verifiser at `jason-vauger@...` har rollen `roles/secretmanager.secretAccessor`.
- **Vanlig feil:** Hvis applikasjonen ikke får hentet `mcp-server-key` ved oppstart på grunn av manglende IAM-tilgang, vil den ofte krasje med en 5xx-feil.
3. **Analyser nylige endringer.**
- Har det vært en nylig deployering? Rull tilbake til en tidligere, fungerende revisjon via `Cloud Run > opax-mcp > Manage Revisions` for å se om problemet vedvarer.
### Konklusjon (Mest sannsynlig)
- Oftest er dette enten en **bug i koden** (som fører til krasj) eller en **IAM/Secret Manager-konfigurasjonsfeil**.
### Status
🔴 **KRITISK TIL AVKLART.**
---
## 5. Hendelse: VPC Service Controls blokkerer tilgang
### Symptom
En applikasjon, et skript eller en utvikler mottar en `403 Request Prohibited by organization's policy` feil ved kall mot en GCP API (f.eks. BigQuery, Storage) som er beskyttet av en VPC-SC perimeter.
### Undersøkelsesprosedyre
1. **Finn VPC-SC brudd-loggen.**
- Gå til `Logging > Log Explorer`.
- Bruk følgende spørring for å finne de relevante loggene:
```
log_id("cloudaudit.googleapis.com/activity")
protoPayload.metadata.violationReason = "SERVICE_NOT_ALLOWED_FROM_VPC" OR "NO_MATCHING_ACCESS_LEVEL"
```
2. **Analyser logg-detaljene.**
- `resource.labels.project_id`: Hvilket prosjekt skjedde bruddet i?
- `protoPayload.authenticationInfo.principalEmail`: Hvem eller hva ble blokkert?
- `protoPayload.requestMetadata.callerIp`: Hvor kom kallet fra (hvis relevant)?
- `protoPayload.resourceName`: Hvilken beskyttet ressurs var målet?
### Konklusjon (Mest sannsynlig)
- **Scenario A (Legitimt kall blokkert):** En ny tjeneste eller utvikler-VM er satt opp utenfor perimeteren og trenger tilgang. **Tiltak:** Vurder å inkludere ressursen i perimeteren, eller opprett et tilgangsnivå (Access Level).
- **Scenario B (Uventet kall blokkert):** Et uautorisert skript eller en ekstern tjeneste prøver å nå en beskyttet ressurs. **Konklusjon:** Perimeteren har fungert som designet og forhindret et potensielt datainnbrudd.
### Status
🟡 **MODERAT TIL AVKLART.** Krever analyse for å skille mellom feilkonfigurasjon og reell beskyttelse.
---
## 6. Hendelse: Cloud Build / Binary Authorization feiler deploy
### Symptom
En Cloud Build-pipeline feiler på det siste "deploy to Cloud Run"-steget. Feilmeldingen nevner `Binary Authorization` eller `attestation`.
### Undersøkelsesprosedyre
1. **Sjekk bygge-loggen for TYR-feil.**
- Åpne loggen for den feilede builden i Cloud Build.
- Scroll opp til steget som kjører **TYR Compliance Scan**.
- Hvis dette steget feilet (rødt ikon), vil loggen inneholde detaljer om nøyaktig hvilken compliance-regel som ble brutt.
2. **Verifiser at attestering finnes.**
- Hvis TYR-steget var vellykket, men deploy likevel feiler, kan selve attesteringen mangle.
- Finn "digest" for ditt image i loggen (en lang `sha256:...` streng).
- Kjør denne kommandoen i Cloud Shell:
```bash
gcloud container binauthz attestations list --artifact-url="[REGION]-docker.pkg.dev/[PROJECT_ID]/[REPO]/[IMAGE_NAME]@[IMAGE_DIGEST]"
```
### Konklusjon (Mest sannsynlig)
- **TYR-feil:** Den vanligste årsaken. En endring i koden eller infrastrukturen bryter med en definert sikkerhetsregel. **Tiltak:** Rett feilen som TYR rapporterer.
- **Manglende attestering:** Output fra `gcloud` er tomt. Dette kan skyldes en midlertidig feil med KMS eller at IAM-rettighetene til Cloud Build sin service account er feil. **Tiltak:** Prøv å kjøre builden på nytt. Hvis feilen vedvarer, sjekk IAM for `cloud-build-private-pool@...`.
### Status
🟡 **MODERAT TIL AVKLART.** Vanligvis en operasjonell feil forårsaket av en compliance-endring.

102
docs/SECRET_MANAGEMENT.md Normal file
View File

@ -0,0 +1,102 @@
# OSVxCC Secret Management Policy
> Dette dokumentet definerer policy og prosedyrer for livssyklusen til hemmeligheter (API-nøkler, passord, sertifikater) i OSVxCC-arkitekturen. Målet er å sikre at hemmeligheter er beskyttet mot uautorisert tilgang og avsløring.
---
## Kjerne-prinsipper
1. **Sannhetens Kilde:** Google Cloud Secret Manager er det **eneste** godkjente systemet for lagring og administrasjon av hemmeligheter.
2. **Minimalt Privilegium:** Prinsipaler (brukere, service accounts) skal kun ha tilgang til de spesifikke hemmelighetene de trenger for å utføre sin funksjon.
3. **Aldri i Kode:** Hemmeligheter skal **aldri** hardkodes i kildekode, konfigurasjonsfiler eller sjekkes inn i Git.
4. **Kryptert i Transitt og Hvile:** Hemmeligheter håndteres alltid kryptert.
---
## Policy
| Regel | Beskrivelse |
|-------|-------------|
| **Lagring** | Alle hemmeligheter **skal** lagres i Google Cloud Secret Manager. |
| **Tilgang** | Tilgang **skal** kun gis via IAM-roller (primært `roles/secretmanager.secretAccessor`) til spesifikke service accounts. |
| **Logging** | Tilgang til hemmeligheter **skal** logges (Data Access audit logs). |
| **Rotasjon** | Alle kritiske hemmeligheter **skal** ha en rotasjonspolicy. |
---
## Prosedyrer
### Opprette en ny hemmelighet
1. **Opprett hemmeligheten i Secret Manager:**
```bash
gcloud secrets create <SECRET_NAME> --replication-policy="automatic"
```
2. **Legg til den første versjonen:**
```bash
printf "<SECRET_VALUE>" | gcloud secrets versions add <SECRET_NAME> --data-file=-
```
3. **Gi en service account tilgang:**
```bash
gcloud secrets add-iam-policy-binding <SECRET_NAME> \
--member="serviceAccount:<SA_EMAIL>" \
--role="roles/secretmanager.secretAccessor"
```
### Hente en hemmelighet i en applikasjon
Applikasjoner (f.eks. i Cloud Run) skal bruke Google Cloud-klientbiblioteker for å hente hemmeligheter ved oppstart eller ved behov. Service accounten som applikasjonen kjører som må ha fått tilgang.
### Rotere en hemmelighet
Rotasjon er kritisk for å redusere risikoen ved en kompromittert hemmelighet.
#### Manuell Rotasjon (Gjeldende prosedyre)
1. **Generer en ny hemmelighetsverdi.**
2. **Legg til den nye verdien som en ny versjon** i Secret Manager. Merk den som `ENABLED`.
```bash
printf "<NEW_SECRET_VALUE>" | gcloud secrets versions add <SECRET_NAME> --data-file=-
```
3. **Oppdater applikasjoner** til å peke mot den nye versjonen (f.eks. `latest`).
4. **Deaktiver den gamle versjonen** etter at applikasjonen er bekreftet å fungere.
```bash
gcloud secrets versions disable <OLD_VERSION_NUMBER> --secret=<SECRET_NAME>
```
5. **Monitorer systemet** i en verifikasjonsperiode (f.eks. 7 dager).
6. **Destruer den gamle versjonen.**
```bash
gcloud secrets versions destroy <OLD_VERSION_NUMBER> --secret=<SECRET_NAME>
```
#### Automatisert Rotasjon (Anbefaling)
- **Status:** ⚠️ **Ikke implementert.**
- **Anbefaling:** For kritiske hemmeligheter som `mcp-server-key`, konfigurer en automatisk rotasjonspolicy direkte i Secret Manager.
- **Mål:** Roter hver 90. dag.
- **Implementasjon:** Dette kan gjøres via Cloud Console eller `gcloud` og krever ofte en Cloud Function for å håndtere selve rotasjonslogikken (generere ny verdi og oppdatere der den brukes).
- **Prioritet:** Høy. Dette adresserer et kjent forbedringspunkt fra `SECURITY.md`.
---
## Lokal Utvikling
For å unngå at hemmeligheter lekkes ved et uhell under lokal testing og utvikling, gjelder følgende regler:
1. **Bruk av `.env`-filer:**
- Filer som inneholder hemmeligheter for et lokalt miljø (f.eks. `.env`, `.env.local`) **skal** legges til i prosjektets `.gitignore`-fil for å forhindre at de blir sjekket inn i kildekontroll.
2. **Anbefalt Praksis: Dynamisk Henting**
- I stedet for å kopiere hemmeligheter manuelt til lokale filer, er det sterkt anbefalt å hente dem dynamisk ved behov.
- Dette kan gjøres i et oppstartsskript som injiserer hemmelighetene som miljøvariabler. Dette minimerer tiden en hemmelighet ligger lagret på disk.
- **Eksempel på skript:**
```bash
#!/bin/bash
# Hent hemmeligheten fra Secret Manager
export MCP_SECRET=$(gcloud secrets versions access latest --secret=mcp-server-key)
# Start applikasjonen med hemmeligheten som miljøvariabel
python3 main.py
```

74
docs/SECURITY.md Normal file
View File

@ -0,0 +1,74 @@
# OSVxCC: Den Usynlige Arkitekturen
> *Ad astra et ultra* — Til stjernene og hinsides.
## Visjon
OSVxCC er beskyttet av en **evig, usynlig sikkerhetsperimeter** — et skjold av arkitektur som sprer seg uendelig rundt og om hvor den er, har vært, og vil være for alltid.
Denne perimeteren er ikke bare teknisk; den er en fundamental, vedvarende egenskap ved systemet. Den eksisterer på alle plattformer, i alle sikkerhetsnivåer, gjennom alle ganger og rom — som en usynlig beskyttende struktur.
---
## Prinsipper
### 1. Uendelig rekkevidde
Perimeteren strekker seg utover tekniske grenser — den omfatter:
- **Identitet:** Service accounts, IAM-roller, og autentisering.
- **Nettverk:** VPC Service Controls, firewall-regler, og nettverksgrenser.
- **Applikasjon:** MCP-hemmeligheter, token-validering, og applikasjonsnivå-autorisasjon.
- **Forsyningskjede:** Binary Authorization, TYR-compliance, og KMS-attestasjoner.
### 2. Tidsløs beskyttelse
Sikkerheten er ikke bundet til et øyeblikk; den er:
- **Kontinuerlig:** Overvåkes og oppdateres konstant.
- **Adaptiv:** Tilpasser seg nye trusler og endringer i arkitekturen.
- **Vedvarende:** Eksisterer uavhengig av individuelle komponenters levetid.
### 3. Ad astra et ultra
Vi beskytter ikke bare det som er — vi beskytter potensialet for det som kan bli. Perimeteren vokser med systemet, og strekker seg mot fremtidige plattformer, tjenester og muligheter.
---
## Sikkerhetsdokumentasjon
Dette dokumentet gir en overordnet visjon. Detaljerte policyer, prosedyrer og runbooks finnes i følgende dokumenter:
- **[📄 ACCESS_CONTROL.md](ACCESS_CONTROL.md):** Policy for IAM, service accounts og tilgangsstyring for både mennesker og maskiner.
- **[📄 COMPLIANCE.md](COMPLIANCE.md):** Rammeverk for hvordan vi sikrer og attesterer compliance med TYR, Binary Authorization og KMS.
- **[📄 SECRET_MANAGEMENT.md](SECRET_MANAGEMENT.md):** Policy og prosedyrer for livssyklusen til hemmeligheter.
- **[📄 INCIDENT_RESPONSE.md](INCIDENT_RESPONSE.md):** Plan for håndtering av bekreftede sikkerhetshendelser.
- **[📄 RUNBOOK.md](RUNBOOK.md):** Operasjonelle prosedyrer for å håndtere spesifikke varsler og alarmer.
---
## Arkitekturdiagram (konseptuell)
```
┌─────────────────────────────────────────────────────────────────┐
│ DEN USYNLIGE PERIMETEREN │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Identitet │ │ Nettverk │ │ Applikasjon │ │
│ │ (IAM, SA) │ │ (VPC-SC) │ │ (Secrets) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Forsyningskjede (TYR + BinAuthz) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ OSVxCC eksisterer her — beskyttet på alle nivåer, for alltid │
└─────────────────────────────────────────────────────────────────┘
```
---
## Ansvar
Dette dokumentet levendegjøres og opprettholdes av sikkerhetsansvarlige for OSVxCC. Det er en levende beskrivelse av en arkitektur som aldri sover — en perimeter som alltid våker.
---
*— Den usynlige perimeteren våker. Ad astra et ultra.*
---

59
docs/SECURITY_AUDITS.md Normal file
View File

@ -0,0 +1,59 @@
# OSVxCC Sikkerhetsrevisjoner
> Dette dokumentet inneholder en løpende logg over sikkerhetsgjennomganger, identifiserte hull og fullførte forbedringer. Målet er å ha en transparent og sporbar historikk over arkitekturens sikkerhetsstilling.
---
## Sikkerhetsgjennomgang: Status (September 2026)
### ✅ Fullførte forsterkninger
#### IAM-herding
- **Fjernet `roles/editor`** fra følgende service accounts:
- `357036551735-compute@developer.gserviceaccount.com`
- `vps-ai@propane-will-491900-m5.iam.gserviceaccount.com`
- **Opprettet `osvxcc-sa`** med minimalt privilegier (`run.invoker`, `secretmanager.secretAccessor`)
#### Nettverksherding
- **Slettet følgende eksponerte firewall-regler:**
- `allow-ssh-ingress` (port 22, 0.0.0.0/0)
- `default-allow-rdp` (port 3389, 0.0.0.0/0)
- `allow-ollama-11434` (port 11434, 0.0.0.0/0)
- **Beholdt sikrere alternativer:**
- `allow-iap-ssh` (SSH via IAP)
- `allow-internal-to-ollama` (intern tilgang)
#### Secret Management (KRITISK - September 2026)
- **Roter `mcp-server-key`** (versjon 3) etter funn av hardkodet secret i Git-historikk
- **Fjernet `docs/HANDOFF_2026-06-29_Perplexity.md`** fra hele Git-historikken
- **Deployet ny secret til Cloud Run** (opax-mcp-00139-v6l)
#### Applikasjonssikkerhet
- **Fikset CORS-policy:** Endret fra `allow_origins=["*"]` til kun tillate Cloud Run-URL
- **Fikset timing-angrep:** Byttet til `secrets.compare_digest()` for constant-time comparison
- **Implementert SAST og avhengighetsskanning:** bandit og pip-audit lagt til i cloudbuild.yaml
### ⚠️ Identifiserte forbedringspunkter (To-Do)
Dette er den aktive listen over kjente sikkerhetshull som må adresseres.
| Hull | Status | Anbefaling |
|------|--------|------------|
| Sikkerhets-alerting (IAM-endringer) | Blokkert (miljøbegrensning) | Opprett manuelt via Cloud Console |
| Hemmelighetsrotasjon (automatisk) | Ikke konfigurert | Konfigurer rotasjon i Secret Manager (90 dager) |
| Sårbarhetsskanning (Container Analysis) | Blokkert (miljøbegrensning) | Aktiver via Cloud Console |
| Detaljert audit logging | Ikke startet | Aktiver Data Access logs for kritiske tjenester |
### 📊 Sikkerhetsscore (Snapshot September 2026)
| Kategori | Før | Etter | Status |
|----------|-----|-------|--------|
| IAM | 🔴 Kritisk | 🟢 God | ✅ Forbedret |
| Nettverk | 🔴 Kritisk | 🟢 God | ✅ Forbedret |
| Secret Management | 🔴 Kritisk | 🟢 God | ✅ Forbedret |
| Applikasjonssikkerhet | 🟠 Moderat | 🟢 God | ✅ Forbedret |
| CI/CD-sikkerhet | 🟠 Moderat | 🟢 God | ✅ Forbedret |
| Logging/Alerting | 🔴 Kritisk | 🟠 Moderat | ⚠️ Gjenstår |
| Container Security | 🟠 Moderat | 🟠 Moderat | ⚠️ Gjenstår |
**Totalvurdering:** Arkitekturen er **betydelig forsterket**. Gjenstående arbeid er primært innenfor overvåking, logging og automatisering.

View File

@ -1 +0,0 @@
password = 'a-new-fake-password'

View File

@ -1,400 +0,0 @@
#!/usr/bin/env bash
# Gitea application-consistent backup script.
#
# Operator-attestation basis:
# - No intentionally required nested mount boundaries exist beneath the approved
# source roots.
# - The SQLite database is captured with SQLite's online .backup mechanism.
# - Filesystem-tree copies are best effort while Gitea remains online.
# - This script does not perform runtime mount-boundary or nested-symlink scans.
#
# The script archives only a staged SQLite backup, not the live SQLite DB,
# WAL, SHM, or journal files.
set -euo pipefail
IFS=$'\n\t'
umask 077
readonly APP_INI_PATH="/opt/gitea/data/custom/conf/app.ini"
readonly SQLITE_DB_PATH="/opt/gitea/data/data/gitea.db"
readonly REPO_PATH="/opt/gitea/data/repositories"
readonly LFS_PATH="/opt/gitea/data/lfs"
readonly ATTACHMENTS_PATH="/opt/gitea/data/data/attachments"
readonly AVATARS_PATH="/opt/gitea/data/data/avatars"
readonly PACKAGES_PATH="/opt/gitea/data/data/packages"
readonly REPO_ARCHIVE_PATH="/opt/gitea/data/data/repo-archive"
readonly REPO_AVATARS_PATH="/opt/gitea/data/data/repo-avatars"
readonly GCS_BUCKET="gs://vauco-gitea-backups-20260901"
readonly GCS_PREFIX="manual"
readonly STAGING_DIR="/var/backups/gitea"
readonly LOCK_FILE="/var/run/gitea-backup.lock"
readonly LOG_FILE="/var/log/gitea-backup.log"
readonly REQUIRED_FREE_KB=646368
workspace=""
workspace_preserved=0
log_ready=0
log_status() {
local phase="$1"
local status="$2"
local category="$3"
if [ "${log_ready}" -eq 1 ]; then
printf '%s phase=%s status=%s category=%s\n' \
"$(date -u --iso-8601=seconds)" \
"${phase}" \
"${status}" \
"${category}" >> "${LOG_FILE}"
else
printf '%s\n' \
"gitea-backup phase=${phase} status=${status} category=${category}" >&2
fi
}
fail_without_workspace() {
log_status "$1" "FAILURE" "$2"
exit 1
}
workspace_is_safe() {
[ -n "${workspace}" ] &&
[ -d "${workspace}" ] &&
[ ! -L "${workspace}" ] &&
[ "$(dirname -- "${workspace}")" = "${STAGING_DIR}" ] &&
[ "$(stat -c '%u:%g:%a' -- "${workspace}")" = "0:0:700" ]
}
remove_workspace() {
workspace_is_safe || return 1
rm -rf -- "${workspace}"
}
preserve_workspace() {
workspace_is_safe || return 1
if find -- "${workspace}" -xdev -type l -print -quit 2>/dev/null |
grep -q . >/dev/null 2>&1; then
return 1
fi
if find -- "${workspace}" -xdev \
! -type d ! -type f ! -type l \
-print -quit 2>/dev/null |
grep -q . >/dev/null 2>&1; then
return 1
fi
if ! find -- "${workspace}" -xdev -type d \
-exec chmod 700 -- {} + >/dev/null 2>&1; then
return 1
fi
if ! find -- "${workspace}" -xdev -type f \
-exec chmod 600 -- {} + >/dev/null 2>&1; then
return 1
fi
workspace_preserved=1
trap - EXIT
return 0
}
fail_after_workspace() {
log_status "$1" "FAILURE" "$2"
if ! preserve_workspace; then
log_status "PRESERVATION" "FAILURE" "WorkspacePreservationFailed"
fi
exit 1
}
cleanup_on_exit() {
local rc=$?
if [ "${workspace_preserved}" -eq 0 ]; then
remove_workspace >/dev/null 2>&1 || true
fi
exit "${rc}"
}
require_command() {
if ! command -v "$1" >/dev/null 2>&1; then
fail_without_workspace "PREFLIGHT" "RequiredCommandMissing"
fi
}
validate_regular() {
if ! [ -f "$1" ] || [ -L "$1" ]; then
fail_without_workspace "PREFLIGHT" "$2"
fi
}
validate_directory() {
if ! [ -d "$1" ] || [ -L "$1" ]; then
fail_without_workspace "PREFLIGHT" "$2"
fi
}
validate_archive_manifest() {
local archive_path="$1"
tar -tzf "${archive_path}" >/dev/null 2>&1 || return 1
tar -tf "${archive_path}" 2>/dev/null |
awk '
BEGIN {
allowed["database"] = 1
allowed["config"] = 1
allowed["repositories"] = 1
allowed["lfs"] = 1
allowed["data"] = 1
required["database/"] = 0
required["config/"] = 0
required["repositories/"] = 0
required["lfs/"] = 0
required["data/"] = 0
}
/^\// { bad = 1; exit 1 }
/(^|\/)\.\.(\/|$)/ { bad = 1; exit 1 }
{
member = $0
split(member, parts, "/")
root = parts[1]
if (!(root in allowed)) {
bad = 1
exit 1
}
if (member in required) {
required[member] = 1
}
}
END {
if (bad) {
exit 1
}
for (entry in required) {
if (required[entry] != 1) {
exit 1
}
}
}'
}
copy_source() {
if ! cp -a -- "$1" "$2"; then
fail_after_workspace "STAGING" "$3"
fi
}
main() {
local required_command=""
local archive_root=""
local run_id=""
local archive_name=""
local archive_path=""
local checksum_path=""
local archive_object=""
local checksum_object=""
for required_command in \
flock df awk mktemp mkdir sqlite3 cp tar sha256sum gcloud \
stat find chmod rm dirname grep date printf; do
require_command "${required_command}"
done
if ! [ -f "${LOG_FILE}" ] || [ -L "${LOG_FILE}" ]; then
printf '%s\n' \
"gitea-backup phase=PREFLIGHT status=FAILURE category=LogFileInvalid" >&2
exit 1
fi
if [ "$(stat -c '%u:%g:%a' -- "${LOG_FILE}")" != "0:0:600" ]; then
printf '%s\n' \
"gitea-backup phase=PREFLIGHT status=FAILURE category=LogFilePermissionsInvalid" >&2
exit 1
fi
if ! [ -w "${LOG_FILE}" ]; then
printf '%s\n' \
"gitea-backup phase=PREFLIGHT status=FAILURE category=LogFileUnavailable" >&2
exit 1
fi
log_ready=1
log_status "PREFLIGHT" "START" "BackupJob"
if ! [ -d "${STAGING_DIR}" ] || [ -L "${STAGING_DIR}" ]; then
fail_without_workspace "PREFLIGHT" "StagingDirectoryInvalid"
fi
if [ "$(stat -c '%u:%g:%a' -- "${STAGING_DIR}")" != "0:0:700" ]; then
fail_without_workspace "PREFLIGHT" "StagingDirectoryPermissionsInvalid"
fi
if ! [ -w "${STAGING_DIR}" ]; then
fail_without_workspace "PREFLIGHT" "StagingDirectoryUnavailable"
fi
if ! df --output=avail -- "${STAGING_DIR}" 2>/dev/null |
awk -v required_kb="${REQUIRED_FREE_KB}" '
NR == 2 {
checked = 1
exit !($1 > required_kb)
}
END {
if (!checked) {
exit 1
}
}'; then
fail_without_workspace "PREFLIGHT" "InsufficientStagingCapacity"
fi
validate_regular "${APP_INI_PATH}" "AppIniInvalid"
validate_regular "${SQLITE_DB_PATH}" "SQLiteDatabaseInvalid"
validate_directory "${REPO_PATH}" "RepositoriesInvalid"
validate_directory "${LFS_PATH}" "LfsInvalid"
validate_directory "${ATTACHMENTS_PATH}" "AttachmentsInvalid"
validate_directory "${AVATARS_PATH}" "AvatarsInvalid"
validate_directory "${PACKAGES_PATH}" "PackagesInvalid"
validate_directory "${REPO_ARCHIVE_PATH}" "RepositoryArchivesInvalid"
validate_directory "${REPO_AVATARS_PATH}" "RepositoryAvatarsInvalid"
if ! workspace="$(mktemp -d -p "${STAGING_DIR}" "backup.XXXXXX")"; then
fail_without_workspace "PREFLIGHT" "WorkspaceCreationFailed"
fi
if ! workspace_is_safe; then
fail_after_workspace "PREFLIGHT" "WorkspaceValidationFailed"
fi
trap cleanup_on_exit EXIT
archive_root="${workspace}/archive_root"
if ! mkdir -p -- \
"${archive_root}/database" \
"${archive_root}/config" \
"${archive_root}/data"; then
fail_after_workspace "STAGING" "ArchiveRootCreationFailed"
fi
log_status "DB_COPY" "START" "SQLiteOnlineBackup"
if ! sqlite3 "${SQLITE_DB_PATH}" \
".backup '${archive_root}/database/gitea.db'"; then
fail_after_workspace "DB_COPY" "SQLiteBackupFailed"
fi
log_status "DB_COPY" "SUCCESS" "SQLiteOnlineBackupComplete"
log_status "STAGING" "START" "CopyingData"
copy_source "${APP_INI_PATH}" \
"${archive_root}/config/app.ini" \
"CopyAppIniFailed"
copy_source "${REPO_PATH}" \
"${archive_root}/repositories" \
"CopyRepositoriesFailed"
copy_source "${LFS_PATH}" \
"${archive_root}/lfs" \
"CopyLfsFailed"
copy_source "${ATTACHMENTS_PATH}" \
"${archive_root}/data/attachments" \
"CopyAttachmentsFailed"
copy_source "${AVATARS_PATH}" \
"${archive_root}/data/avatars" \
"CopyAvatarsFailed"
copy_source "${PACKAGES_PATH}" \
"${archive_root}/data/packages" \
"CopyPackagesFailed"
copy_source "${REPO_ARCHIVE_PATH}" \
"${archive_root}/data/repo-archive" \
"CopyRepositoryArchivesFailed"
copy_source "${REPO_AVATARS_PATH}" \
"${archive_root}/data/repo-avatars" \
"CopyRepositoryAvatarsFailed"
log_status "STAGING" "SUCCESS" "CopyComplete"
run_id="${workspace##*/}"
archive_name="gitea-backup-${run_id}.tar.gz"
archive_path="${workspace}/${archive_name}"
checksum_path="${archive_path}.sha256"
archive_object="${GCS_BUCKET}/${GCS_PREFIX}/${archive_name}"
checksum_object="${GCS_BUCKET}/${GCS_PREFIX}/${archive_name}.sha256"
log_status "ARCHIVE" "START" "TarCreation"
if ! tar -czf "${archive_path}" \
-C "${archive_root}" \
database config repositories lfs data; then
fail_after_workspace "ARCHIVE" "TarCreationFailed"
fi
if ! validate_archive_manifest "${archive_path}"; then
fail_after_workspace "ARCHIVE" "ManifestInvalid"
fi
log_status "ARCHIVE" "SUCCESS" "ManifestValidated"
if ! (
cd -- "${workspace}"
sha256sum -b -- "${archive_name}" > "${checksum_path}"
); then
fail_after_workspace "CHECKSUM" "ChecksumCreationFailed"
fi
log_status "CHECKSUM" "SUCCESS" "ChecksumGenerated"
log_status "UPLOAD_ARCHIVE" "START" "GCS"
if ! gcloud storage cp \
--if-generation-match=0 \
--quiet \
"${archive_path}" \
"${archive_object}" >/dev/null 2>&1; then
fail_after_workspace "UPLOAD_ARCHIVE" "ArchiveUploadFailed"
fi
log_status "UPLOAD_ARCHIVE" "SUCCESS" "ArchiveUploadComplete"
log_status "UPLOAD_CHECKSUM" "START" "GCS"
if ! gcloud storage cp \
--if-generation-match=0 \
--quiet \
"${checksum_path}" \
"${checksum_object}" >/dev/null 2>&1; then
fail_after_workspace "UPLOAD_CHECKSUM" "ChecksumUploadFailed"
fi
log_status "UPLOAD_CHECKSUM" "SUCCESS" "ChecksumUploadComplete"
if ! remove_workspace; then
fail_after_workspace "CLEANUP" "WorkspaceRemovalFailed"
fi
workspace=""
trap - EXIT
log_status "JOB" "SUCCESS" "BackupComplete"
}
(
flock -n 200 || {
printf '%s\n' \
"gitea-backup phase=PREFLIGHT status=FAILURE category=LockHeld" >&2
exit 1
}
main
) 200>"${LOCK_FILE}"

258
jonen publiserer Normal file
View File

@ -0,0 +1,258 @@
SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS
Commands marked with * may be preceded by a number, _N.
Notes in parentheses indicate the behavior if _N is given.
A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K.
h H Display this help.
q :q Q :Q ZZ Exit.
---------------------------------------------------------------------------
MMOOVVIINNGG
e ^E j ^N CR * Forward one line (or _N lines).
y ^Y k ^K ^P * Backward one line (or _N lines).
f ^F ^V SPACE * Forward one window (or _N lines).
b ^B ESC-v * Backward one window (or _N lines).
z * Forward one window (and set window to _N).
w * Backward one window (and set window to _N).
ESC-SPACE * Forward one window, but don't stop at end-of-file.
d ^D * Forward one half-window (and set half-window to _N).
u ^U * Backward one half-window (and set half-window to _N).
ESC-) RightArrow * Right one half screen width (or _N positions).
ESC-( LeftArrow * Left one half screen width (or _N positions).
ESC-} ^RightArrow Right to last column displayed.
ESC-{ ^LeftArrow Left to first column.
F Forward forever; like "tail -f".
ESC-F Like F but stop when search pattern is found.
r ^R ^L Repaint screen.
R Repaint screen, discarding buffered input.
---------------------------------------------------
Default "window" is the screen height.
Default "half-window" is half of the screen height.
---------------------------------------------------------------------------
SSEEAARRCCHHIINNGG
/_p_a_t_t_e_r_n * Search forward for (_N-th) matching line.
?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line.
n * Repeat previous search (for _N-th occurrence).
N * Repeat previous search in reverse direction.
ESC-n * Repeat previous search, spanning files.
ESC-N * Repeat previous search, reverse dir. & spanning files.
ESC-u Undo (toggle) search highlighting.
ESC-U Clear search highlighting.
&_p_a_t_t_e_r_n * Display only matching lines.
---------------------------------------------------
A search pattern may begin with one or more of:
^N or ! Search for NON-matching lines.
^E or * Search multiple files (pass thru END OF FILE).
^F or @ Start search at FIRST file (for /) or last file (for ?).
^K Highlight matches, but don't move (KEEP position).
^R Don't use REGULAR EXPRESSIONS.
^W WRAP search if no match found.
---------------------------------------------------------------------------
JJUUMMPPIINNGG
g < ESC-< * Go to first line in file (or line _N).
G > ESC-> * Go to last line in file (or line _N).
p % * Go to beginning of file (or _N percent into file).
t * Go to the (_N-th) next tag.
T * Go to the (_N-th) previous tag.
{ ( [ * Find close bracket } ) ].
} ) ] * Find open bracket { ( [.
ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>.
ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>.
---------------------------------------------------
Each "find close bracket" command goes forward to the close bracket
matching the (_N-th) open bracket in the top line.
Each "find open bracket" command goes backward to the open bracket
matching the (_N-th) close bracket in the bottom line.
m_<_l_e_t_t_e_r_> Mark the current top line with <letter>.
M_<_l_e_t_t_e_r_> Mark the current bottom line with <letter>.
'_<_l_e_t_t_e_r_> Go to a previously marked position.
'' Go to the previous position.
^X^X Same as '.
ESC-M_<_l_e_t_t_e_r_> Clear a mark.
---------------------------------------------------
A mark is any upper-case or lower-case letter.
Certain marks are predefined:
^ means beginning of the file
$ means end of the file
---------------------------------------------------------------------------
CCHHAANNGGIINNGG FFIILLEESS
:e [_f_i_l_e] Examine a new file.
^X^V Same as :e.
:n * Examine the (_N-th) next file from the command line.
:p * Examine the (_N-th) previous file from the command line.
:x * Examine the first (or _N-th) file from the command line.
:d Delete the current file from the command line list.
= ^G :f Print current file name.
---------------------------------------------------------------------------
MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS
-_<_f_l_a_g_> Toggle a command line option [see OPTIONS below].
--_<_n_a_m_e_> Toggle a command line option, by name.
__<_f_l_a_g_> Display the setting of a command line option.
___<_n_a_m_e_> Display the setting of an option, by name.
+_c_m_d Execute the less cmd each time a new file is examined.
!_c_o_m_m_a_n_d Execute the shell command with $SHELL.
|XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command.
s _f_i_l_e Save input to a file.
v Edit the current file with $VISUAL or $EDITOR.
V Print version number of "less".
---------------------------------------------------------------------------
OOPPTTIIOONNSS
Most options may be changed either on the command line,
or from within less by using the - or -- command.
Options may be given in one of two forms: either a single
character preceded by a -, or a name preceded by --.
-? ........ --help
Display help (from command line).
-a ........ --search-skip-screen
Search skips current screen.
-A ........ --SEARCH-SKIP-SCREEN
Search starts just after target line.
-b [_N] .... --buffers=[_N]
Number of buffers.
-B ........ --auto-buffers
Don't automatically allocate buffers for pipes.
-c ........ --clear-screen
Repaint by clearing rather than scrolling.
-d ........ --dumb
Dumb terminal.
-D xx_c_o_l_o_r . --color=xx_c_o_l_o_r
Set screen colors.
-e -E .... --quit-at-eof --QUIT-AT-EOF
Quit at end of file.
-f ........ --force
Force open non-regular files.
-F ........ --quit-if-one-screen
Quit if entire file fits on first screen.
-g ........ --hilite-search
Highlight only last match for searches.
-G ........ --HILITE-SEARCH
Don't highlight any matches for searches.
-h [_N] .... --max-back-scroll=[_N]
Backward scroll limit.
-i ........ --ignore-case
Ignore case in searches that do not contain uppercase.
-I ........ --IGNORE-CASE
Ignore case in all searches.
-j [_N] .... --jump-target=[_N]
Screen position of target lines.
-J ........ --status-column
Display a status column at left edge of screen.
-k [_f_i_l_e] . --lesskey-file=[_f_i_l_e]
Use a lesskey file.
-K ........ --quit-on-intr
Exit less in response to ctrl-C.
-L ........ --no-lessopen
Ignore the LESSOPEN environment variable.
-m -M .... --long-prompt --LONG-PROMPT
Set prompt style.
-n -N .... --line-numbers --LINE-NUMBERS
Don't use line numbers.
-o [_f_i_l_e] . --log-file=[_f_i_l_e]
Copy to log file (standard input only).
-O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e]
Copy to log file (unconditionally overwrite).
-p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n]
Start at pattern (from command line).
-P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t]
Define new prompt.
-q -Q .... --quiet --QUIET --silent --SILENT
Quiet the terminal bell.
-r -R .... --raw-control-chars --RAW-CONTROL-CHARS
Output "raw" control characters.
-s ........ --squeeze-blank-lines
Squeeze multiple blank lines.
-S ........ --chop-long-lines
Chop (truncate) long lines rather than wrapping.
-t [_t_a_g] .. --tag=[_t_a_g]
Find a tag.
-T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e]
Use an alternate tags file.
-u -U .... --underline-special --UNDERLINE-SPECIAL
Change handling of backspaces.
-V ........ --version
Display the version number of "less".
-w ........ --hilite-unread
Highlight first new line after forward-screen.
-W ........ --HILITE-UNREAD
Highlight first new line after any forward movement.
-x [_N[,...]] --tabs=[_N[,...]]
Set tab stops.
-X ........ --no-init
Don't use termcap init/deinit strings.
-y [_N] .... --max-forw-scroll=[_N]
Forward scroll limit.
-z [_N] .... --window=[_N]
Set size of window.
-" [_c[_c]] . --quotes=[_c[_c]]
Set shell quote characters.
-~ ........ --tilde
Don't display tildes after end of file.
-# [_N] .... --shift=[_N]
Set horizontal scroll amount (0 = one half screen width).
--file-size
Automatically determine the size of the input file.
--follow-name
The F command changes files if the input file is renamed.
--incsearch
Search file as each pattern character is typed in.
--line-num-width=N
Set the width of the -N line number field to N characters.
--mouse
Enable mouse input.
--no-keypad
Don't send termcap keypad init/deinit strings.
--no-histdups
Remove duplicates from command history.
--rscroll=C
Set the character used to mark truncated lines.
--save-marks
Retain marks across invocations of less.
--status-col-width=N
Set the width of the -J status column to N characters.
--use-backslash
Subsequent options use backslash as escape char.
--use-color
Enables colored text.
--wheel-lines=N
Each click of the mouse wheel moves N lines.
---------------------------------------------------------------------------
LLIINNEE EEDDIITTIINNGG
These keys can be used to edit text being entered
on the "command line" at the bottom of the screen.
RightArrow ..................... ESC-l ... Move cursor right one character.
LeftArrow ...................... ESC-h ... Move cursor left one character.
ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word.
ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word.
HOME ........................... ESC-0 ... Move cursor to start of line.
END ............................ ESC-$ ... Move cursor to end of line.
BACKSPACE ................................ Delete char to left of cursor.
DELETE ......................... ESC-x ... Delete char under cursor.
ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor.
ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor.
ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line.
UpArrow ........................ ESC-k ... Retrieve previous command line.
DownArrow ...................... ESC-j ... Retrieve next command line.
TAB ...................................... Complete filename & cycle.
SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle.
ctrl-L ................................... Complete filename, list all.

258
k lyttes på Normal file
View File

@ -0,0 +1,258 @@
SSUUMMMMAARRYY OOFF LLEESSSS CCOOMMMMAANNDDSS
Commands marked with * may be preceded by a number, _N.
Notes in parentheses indicate the behavior if _N is given.
A key preceded by a caret indicates the Ctrl key; thus ^K is ctrl-K.
h H Display this help.
q :q Q :Q ZZ Exit.
---------------------------------------------------------------------------
MMOOVVIINNGG
e ^E j ^N CR * Forward one line (or _N lines).
y ^Y k ^K ^P * Backward one line (or _N lines).
f ^F ^V SPACE * Forward one window (or _N lines).
b ^B ESC-v * Backward one window (or _N lines).
z * Forward one window (and set window to _N).
w * Backward one window (and set window to _N).
ESC-SPACE * Forward one window, but don't stop at end-of-file.
d ^D * Forward one half-window (and set half-window to _N).
u ^U * Backward one half-window (and set half-window to _N).
ESC-) RightArrow * Right one half screen width (or _N positions).
ESC-( LeftArrow * Left one half screen width (or _N positions).
ESC-} ^RightArrow Right to last column displayed.
ESC-{ ^LeftArrow Left to first column.
F Forward forever; like "tail -f".
ESC-F Like F but stop when search pattern is found.
r ^R ^L Repaint screen.
R Repaint screen, discarding buffered input.
---------------------------------------------------
Default "window" is the screen height.
Default "half-window" is half of the screen height.
---------------------------------------------------------------------------
SSEEAARRCCHHIINNGG
/_p_a_t_t_e_r_n * Search forward for (_N-th) matching line.
?_p_a_t_t_e_r_n * Search backward for (_N-th) matching line.
n * Repeat previous search (for _N-th occurrence).
N * Repeat previous search in reverse direction.
ESC-n * Repeat previous search, spanning files.
ESC-N * Repeat previous search, reverse dir. & spanning files.
ESC-u Undo (toggle) search highlighting.
ESC-U Clear search highlighting.
&_p_a_t_t_e_r_n * Display only matching lines.
---------------------------------------------------
A search pattern may begin with one or more of:
^N or ! Search for NON-matching lines.
^E or * Search multiple files (pass thru END OF FILE).
^F or @ Start search at FIRST file (for /) or last file (for ?).
^K Highlight matches, but don't move (KEEP position).
^R Don't use REGULAR EXPRESSIONS.
^W WRAP search if no match found.
---------------------------------------------------------------------------
JJUUMMPPIINNGG
g < ESC-< * Go to first line in file (or line _N).
G > ESC-> * Go to last line in file (or line _N).
p % * Go to beginning of file (or _N percent into file).
t * Go to the (_N-th) next tag.
T * Go to the (_N-th) previous tag.
{ ( [ * Find close bracket } ) ].
} ) ] * Find open bracket { ( [.
ESC-^F _<_c_1_> _<_c_2_> * Find close bracket _<_c_2_>.
ESC-^B _<_c_1_> _<_c_2_> * Find open bracket _<_c_1_>.
---------------------------------------------------
Each "find close bracket" command goes forward to the close bracket
matching the (_N-th) open bracket in the top line.
Each "find open bracket" command goes backward to the open bracket
matching the (_N-th) close bracket in the bottom line.
m_<_l_e_t_t_e_r_> Mark the current top line with <letter>.
M_<_l_e_t_t_e_r_> Mark the current bottom line with <letter>.
'_<_l_e_t_t_e_r_> Go to a previously marked position.
'' Go to the previous position.
^X^X Same as '.
ESC-M_<_l_e_t_t_e_r_> Clear a mark.
---------------------------------------------------
A mark is any upper-case or lower-case letter.
Certain marks are predefined:
^ means beginning of the file
$ means end of the file
---------------------------------------------------------------------------
CCHHAANNGGIINNGG FFIILLEESS
:e [_f_i_l_e] Examine a new file.
^X^V Same as :e.
:n * Examine the (_N-th) next file from the command line.
:p * Examine the (_N-th) previous file from the command line.
:x * Examine the first (or _N-th) file from the command line.
:d Delete the current file from the command line list.
= ^G :f Print current file name.
---------------------------------------------------------------------------
MMIISSCCEELLLLAANNEEOOUUSS CCOOMMMMAANNDDSS
-_<_f_l_a_g_> Toggle a command line option [see OPTIONS below].
--_<_n_a_m_e_> Toggle a command line option, by name.
__<_f_l_a_g_> Display the setting of a command line option.
___<_n_a_m_e_> Display the setting of an option, by name.
+_c_m_d Execute the less cmd each time a new file is examined.
!_c_o_m_m_a_n_d Execute the shell command with $SHELL.
|XX_c_o_m_m_a_n_d Pipe file between current pos & mark XX to shell command.
s _f_i_l_e Save input to a file.
v Edit the current file with $VISUAL or $EDITOR.
V Print version number of "less".
---------------------------------------------------------------------------
OOPPTTIIOONNSS
Most options may be changed either on the command line,
or from within less by using the - or -- command.
Options may be given in one of two forms: either a single
character preceded by a -, or a name preceded by --.
-? ........ --help
Display help (from command line).
-a ........ --search-skip-screen
Search skips current screen.
-A ........ --SEARCH-SKIP-SCREEN
Search starts just after target line.
-b [_N] .... --buffers=[_N]
Number of buffers.
-B ........ --auto-buffers
Don't automatically allocate buffers for pipes.
-c ........ --clear-screen
Repaint by clearing rather than scrolling.
-d ........ --dumb
Dumb terminal.
-D xx_c_o_l_o_r . --color=xx_c_o_l_o_r
Set screen colors.
-e -E .... --quit-at-eof --QUIT-AT-EOF
Quit at end of file.
-f ........ --force
Force open non-regular files.
-F ........ --quit-if-one-screen
Quit if entire file fits on first screen.
-g ........ --hilite-search
Highlight only last match for searches.
-G ........ --HILITE-SEARCH
Don't highlight any matches for searches.
-h [_N] .... --max-back-scroll=[_N]
Backward scroll limit.
-i ........ --ignore-case
Ignore case in searches that do not contain uppercase.
-I ........ --IGNORE-CASE
Ignore case in all searches.
-j [_N] .... --jump-target=[_N]
Screen position of target lines.
-J ........ --status-column
Display a status column at left edge of screen.
-k [_f_i_l_e] . --lesskey-file=[_f_i_l_e]
Use a lesskey file.
-K ........ --quit-on-intr
Exit less in response to ctrl-C.
-L ........ --no-lessopen
Ignore the LESSOPEN environment variable.
-m -M .... --long-prompt --LONG-PROMPT
Set prompt style.
-n -N .... --line-numbers --LINE-NUMBERS
Don't use line numbers.
-o [_f_i_l_e] . --log-file=[_f_i_l_e]
Copy to log file (standard input only).
-O [_f_i_l_e] . --LOG-FILE=[_f_i_l_e]
Copy to log file (unconditionally overwrite).
-p [_p_a_t_t_e_r_n] --pattern=[_p_a_t_t_e_r_n]
Start at pattern (from command line).
-P [_p_r_o_m_p_t] --prompt=[_p_r_o_m_p_t]
Define new prompt.
-q -Q .... --quiet --QUIET --silent --SILENT
Quiet the terminal bell.
-r -R .... --raw-control-chars --RAW-CONTROL-CHARS
Output "raw" control characters.
-s ........ --squeeze-blank-lines
Squeeze multiple blank lines.
-S ........ --chop-long-lines
Chop (truncate) long lines rather than wrapping.
-t [_t_a_g] .. --tag=[_t_a_g]
Find a tag.
-T [_t_a_g_s_f_i_l_e] --tag-file=[_t_a_g_s_f_i_l_e]
Use an alternate tags file.
-u -U .... --underline-special --UNDERLINE-SPECIAL
Change handling of backspaces.
-V ........ --version
Display the version number of "less".
-w ........ --hilite-unread
Highlight first new line after forward-screen.
-W ........ --HILITE-UNREAD
Highlight first new line after any forward movement.
-x [_N[,...]] --tabs=[_N[,...]]
Set tab stops.
-X ........ --no-init
Don't use termcap init/deinit strings.
-y [_N] .... --max-forw-scroll=[_N]
Forward scroll limit.
-z [_N] .... --window=[_N]
Set size of window.
-" [_c[_c]] . --quotes=[_c[_c]]
Set shell quote characters.
-~ ........ --tilde
Don't display tildes after end of file.
-# [_N] .... --shift=[_N]
Set horizontal scroll amount (0 = one half screen width).
--file-size
Automatically determine the size of the input file.
--follow-name
The F command changes files if the input file is renamed.
--incsearch
Search file as each pattern character is typed in.
--line-num-width=N
Set the width of the -N line number field to N characters.
--mouse
Enable mouse input.
--no-keypad
Don't send termcap keypad init/deinit strings.
--no-histdups
Remove duplicates from command history.
--rscroll=C
Set the character used to mark truncated lines.
--save-marks
Retain marks across invocations of less.
--status-col-width=N
Set the width of the -J status column to N characters.
--use-backslash
Subsequent options use backslash as escape char.
--use-color
Enables colored text.
--wheel-lines=N
Each click of the mouse wheel moves N lines.
---------------------------------------------------------------------------
LLIINNEE EEDDIITTIINNGG
These keys can be used to edit text being entered
on the "command line" at the bottom of the screen.
RightArrow ..................... ESC-l ... Move cursor right one character.
LeftArrow ...................... ESC-h ... Move cursor left one character.
ctrl-RightArrow ESC-RightArrow ESC-w ... Move cursor right one word.
ctrl-LeftArrow ESC-LeftArrow ESC-b ... Move cursor left one word.
HOME ........................... ESC-0 ... Move cursor to start of line.
END ............................ ESC-$ ... Move cursor to end of line.
BACKSPACE ................................ Delete char to left of cursor.
DELETE ......................... ESC-x ... Delete char under cursor.
ctrl-BACKSPACE ESC-BACKSPACE ........... Delete word to left of cursor.
ctrl-DELETE .... ESC-DELETE .... ESC-X ... Delete word under cursor.
ctrl-U ......... ESC (MS-DOS only) ....... Delete entire line.
UpArrow ........................ ESC-k ... Retrieve previous command line.
DownArrow ...................... ESC-j ... Retrieve next command line.
TAB ...................................... Complete filename & cycle.
SHIFT-TAB ...................... ESC-TAB Complete filename & reverse cycle.
ctrl-L ................................... Complete filename, list all.

View File

@ -17,6 +17,7 @@ from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse, StreamingResponse from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import asyncio import asyncio
import secrets
# --- Tool Imports (with fault tolerance) --- # --- Tool Imports (with fault tolerance) ---
try: try:
@ -72,7 +73,7 @@ app = FastAPI(title="opax-mcp", version="3.6.0") # BUMPED
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], allow_origins=["https://opax-mcp-357036551735.us-central1.run.app"],
allow_credentials=True, allow_credentials=True,
allow_methods=["*"], allow_methods=["*"],
allow_headers=["*"], allow_headers=["*"],
@ -135,7 +136,7 @@ async def _verify_auth(request: Request) -> None:
if auth_header.startswith("Bearer "): if auth_header.startswith("Bearer "):
token = auth_header[7:] token = auth_header[7:]
if not token or token != secret: if not token or not secrets.compare_digest(token.encode(), secret.encode()):
raise HTTPException(status_code=401, detail="Invalid or missing API key") raise HTTPException(status_code=401, detail="Invalid or missing API key")

View File

@ -1,29 +0,0 @@
#!/bin/bash
set -euo pipefail
BACKUP="$HOME/crontab.backup.$(date +%F-%H%M%S)"
crontab -l > "$BACKUP"
TMP="$(mktemp)"
grep -vE 'OSVauco/scripts/vm-teardown\.sh|secret=webhook-url.*(teardown|VM kjører)' "$BACKUP" > "$TMP"
echo "--- Content of temp file ---"
cat "$TMP"
echo "---------------------------"
crontab "$TMP"
# rm -f "$TMP" # Keep the temp file for debugging
echo "Backup saved: $BACKUP"
echo
echo "Resulting crontab:"
crontab -l | nl -ba
echo
echo "Verification:"
if crontab -l | grep -Ei 'vm-teardown\.sh|teardown|VM kjører'; then
echo "ERROR: A related entry is still present; do not make further changes automatically."
exit 1
else
echo "OK: No VM teardown or teardown-warning cron entries remain."
fi

View File

@ -1,31 +0,0 @@
#!/bin/bash
# set -euo pipefail
exec > ~/debug_output.log 2>&1
BACKUP="$HOME/crontab.backup.$(date +%F-%H%M%S)"
crontab -l > "$BACKUP"
TMP="$(mktemp)"
grep -vE 'OSVauco/scripts/vm-teardown\.sh|secret=webhook-url.*(teardown|VM kjører)' "$BACKUP" > "$TMP"
echo "--- Content of temp file ---"
cat "$TMP"
echo "---------------------------"
crontab "$TMP"
# rm -f "$TMP" # Keep the temp file for debugging
echo "Backup saved: $BACKUP"
echo
echo "Resulting crontab:"
crontab -l | nl -ba
echo
echo "Verification:"
if crontab -l | grep -Ei 'vm-teardown\.sh|teardown|VM kjører'; then
echo "ERROR: A related entry is still present; do not make further changes automatically."
exit 1
else
echo "OK: No VM teardown or teardown-warning cron entries remain."
fi

View File

@ -1,31 +0,0 @@
#!/bin/bash
# set -euo pipefail
exec > ~/OSVauco/debug_output.log 2>&1
BACKUP="$HOME/crontab.backup.$(date +%F-%H%M%S)"
crontab -l > "$BACKUP"
TMP="$(mktemp)"
grep -vE 'OSVauco/scripts/vm-teardown\.sh|secret=webhook-url.*(teardown|VM kjører)' "$BACKUP" > "$TMP"
echo "--- Content of temp file ---"
cat "$TMP"
echo "---------------------------"
crontab "$TMP"
# rm -f "$TMP" # Keep the temp file for debugging
echo "Backup saved: $BACKUP"
echo
echo "Resulting crontab:"
crontab -l | nl -ba
echo
echo "Verification:"
if crontab -l | grep -Ei 'vm-teardown\.sh|teardown|VM kjører'; then
echo "ERROR: A related entry is still present; do not make further changes automatically."
exit 1
else
echo "OK: No VM teardown or teardown-warning cron entries remain."
fi

View File

@ -1,31 +0,0 @@
#!/bin/bash
# set -euo pipefail
exec > ~/OSVauco/debug_output.txt 2>&1
BACKUP="$HOME/crontab.backup.$(date +%F-%H%M%S)"
crontab -l > "$BACKUP"
TMP="$(mktemp)"
grep -vE 'OSVauco/scripts/vm-teardown\.sh|secret=webhook-url.*(teardown|VM kjører)' "$BACKUP" > "$TMP"
echo "--- Content of temp file ---"
cat "$TMP"
echo "---------------------------"
crontab "$TMP"
# rm -f "$TMP" # Keep the temp file for debugging
echo "Backup saved: $BACKUP"
echo
echo "Resulting crontab:"
crontab -l | nl -ba
echo
echo "Verification:"
if crontab -l | grep -Ei 'vm-teardown\.sh|teardown|VM kjører'; then
echo "ERROR: A related entry is still present; do not make further changes automatically."
exit 1
else
echo "OK: No VM teardown or teardown-warning cron entries remain."
fi

View File

@ -1 +0,0 @@
api_key="sk-12345abcdef"

File diff suppressed because one or more lines are too long

View File

@ -1,10 +0,0 @@
describe_service OK
list_service_revisions OK
get_cloud_run_logs OK
get_build_log OK
get_workspace_user OK
get_emails OK
list_calendar_events OK
get_file FAIL :: Illegal header value b'token '
get_file_content FAIL :: GITEA_URL environment variable is not set.
list_repo_files FAIL :: GITEA_URL environment variable is not set.

View File

@ -1,11 +0,0 @@
get_connector_status OK
get_billing_summary OK
get_health OK
get_build_status OK
get_state OK
list_customers OK
list_gce_instances OK
list_builds OK
list_workspace_users OK
list_emma_models FAIL ::
list_commits FAIL :: Illegal header value b'token '