docs(security): komplett sikkerhetsdokumentasjon med 7 manifestfiler
Some checks are pending
Check Python Version Consistency / Check Python Version (push) Waiting to run
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:
parent
a5864f8584
commit
79b100caff
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -40,6 +40,7 @@ Thumbs.db
|
|||
# Temp
|
||||
/tmp/
|
||||
*.log
|
||||
*.txt
|
||||
.env.prefilled
|
||||
|
||||
# Terraform
|
||||
|
|
|
|||
194
SECURITY.md
194
SECURITY.md
|
|
@ -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
21
agents/config.py
Normal 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.
|
||||
|
|
@ -7,6 +7,18 @@ substitutions:
|
|||
_OSVAUCO_TRIGGER_ID: '38423976-91ff-4ff4-859e-1f262344c609'
|
||||
|
||||
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'
|
||||
entrypoint: bash
|
||||
args:
|
||||
|
|
|
|||
234
agents/tools/opax_mcp_client.py.bak.20260903103701
Normal file
234
agents/tools/opax_mcp_client.py.bak.20260903103701
Normal 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),
|
||||
]
|
||||
73
agents/tools/opax_mcp_client.py.mcp-short.20260903104302
Normal file
73
agents/tools/opax_mcp_client.py.mcp-short.20260903104302
Normal 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})
|
||||
107
agents/tools/opax_mcp_client.py.mcp-short.20260903104320
Normal file
107
agents/tools/opax_mcp_client.py.mcp-short.20260903104320
Normal 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
51
docs/ACCESS_CONTROL.md
Normal 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
64
docs/COMPLIANCE.md
Normal 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
87
docs/INCIDENT_RESPONSE.md
Normal 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
231
docs/RUNBOOK.md
Normal 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
102
docs/SECRET_MANAGEMENT.md
Normal 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
74
docs/SECURITY.md
Normal 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
59
docs/SECURITY_AUDITS.md
Normal 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.
|
||||
|
|
@ -1 +0,0 @@
|
|||
password = 'a-new-fake-password'
|
||||
|
|
@ -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
258
jonen publiserer
Normal 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
258
k lyttes på
Normal 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.
|
||||
|
|
@ -17,6 +17,7 @@ from fastapi import FastAPI, Request, HTTPException
|
|||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import asyncio
|
||||
import secrets
|
||||
|
||||
# --- Tool Imports (with fault tolerance) ---
|
||||
try:
|
||||
|
|
@ -72,7 +73,7 @@ app = FastAPI(title="opax-mcp", version="3.6.0") # BUMPED
|
|||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=["https://opax-mcp-357036551735.us-central1.run.app"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
|
|
@ -135,7 +136,7 @@ async def _verify_auth(request: Request) -> None:
|
|||
if auth_header.startswith("Bearer "):
|
||||
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")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1 +0,0 @@
|
|||
api_key="sk-12345abcdef"
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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.
|
||||
|
|
@ -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 '
|
||||
Loading…
Reference in New Issue
Block a user