#!/usr/bin/env python3 """Create all Aegis wiki pages via Gitea API.""" import base64 import sys import time import requests from requests.auth import HTTPBasicAuth # Use internal Gitea URL (bypasses reverse proxy that blocks wiki API via HTTPS) # External HTTPS proxy blocks POST/PATCH to wiki endpoints (405 Method Not Allowed) # The correct Gitea wiki API endpoints are: # POST /api/v1/repos/{owner}/{repo}/wiki/new → create # PATCH /api/v1/repos/{owner}/{repo}/wiki/page/{pageName} → update # GET /api/v1/repos/{owner}/{repo}/wiki/pages → list GITEA_URL = "http://192.168.1.107:3000" OWNER = "kitos" REPO = "Aegis" USERNAME = "kitos" PASSWORD = "T'2JY%HLX\"Bp^6e" def create_or_update_page(title: str, content: str) -> bool: """Create a wiki page via Gitea API. Returns True on success.""" auth = HTTPBasicAuth(USERNAME, PASSWORD) encoded = base64.b64encode(content.encode("utf-8")).decode("ascii") payload = { "content": encoded, "message": f"Add wiki page: {title}", "title": title, } # Gitea wiki create endpoint is /wiki/new (not /wiki/pages) create_url = f"{GITEA_URL}/api/v1/repos/{OWNER}/{REPO}/wiki/new" r = requests.post(create_url, json=payload, auth=auth, timeout=30) if r.status_code in (201, 200): print(f" [OK] Created: {title}") return True elif r.status_code in (409, 422) or "already exist" in r.text.lower(): # Page already exists — update it slug = title.replace(" ", "-") update_url = f"{GITEA_URL}/api/v1/repos/{OWNER}/{REPO}/wiki/page/{slug}" r2 = requests.patch(update_url, json=payload, auth=auth, timeout=30) if r2.status_code in (200, 201): print(f" [OK] Updated: {title}") return True print(f" [FAIL-UPDATE] {title}: {r2.status_code} {r2.text[:200]}") return False print(f" [FAIL] Update {title}: {r2.status_code} {r2.text[:200]}") return False print(f" [FAIL] {title}: {r.status_code} {r.text[:200]}") return False # ───────────────────────────────────────────── # PAGE CONTENT DEFINITIONS # ───────────────────────────────────────────── PAGE_HOME = """\ # Aegis — Knowledge Base Home **Aegis** is a MITRE ATT&CK coverage management platform for Purple Team operations. It provides a structured workflow for Red and Blue teams to plan, execute, evaluate, and track security tests mapped to MITRE ATT&CK techniques, measure detection coverage, manage knowledge, and generate executive reporting. --- ## Table of Contents | Wiki Page | Description | |-----------|-------------| | [Home](Home) | This page — navigation hub and quick start | | [Architecture](Architecture) | Tech stack, Docker services, backend structure, data flow | | [Roles-and-Permissions](Roles-and-Permissions) | RBAC roles, permission matrix, admin bypass | | [Test-Lifecycle](Test-Lifecycle) | State machine: draft → validated, all transitions | | [MITRE-ATT-CK-Coverage](MITRE-ATT-CK-Coverage) | Coverage model, scoring, sync, heatmap | | [API-Reference](API-Reference) | All 28 routers with endpoints, methods, required roles | | [Authentication-and-Security](Authentication-and-Security) | JWT, cookies, API keys, SAML SSO, rate limiting | | [Campaigns](Campaigns) | Campaign types, lifecycle, threat-actor import | | [Knowledge-Management](Knowledge-Management) | Playbooks, lessons learned, versioning | | [Operational-Alerts](Operational-Alerts) | Alert rules, instances, evaluation, webhooks | | [Executive-Dashboard-and-Reports](Executive-Dashboard-and-Reports) | KPIs, snapshots, PDF/DOCX/HTML report generation | | [Detection-Lifecycle](Detection-Lifecycle) | Detection assets, validations, infrastructure changes | | [Deployment-Guide](Deployment-Guide) | Docker deployment, environment variables, migrations | | [QA-Testing-Guide](QA-Testing-Guide) | Automated QA runner, manual checklists per role | --- ## Quick Start ### 1. Log In ```http POST /api/v1/auth/login Content-Type: application/x-www-form-urlencoded username=admin&password= ``` The server sets an `aegis_token` HttpOnly cookie. All subsequent requests are authenticated automatically by the browser (or pass the token as `Authorization: Bearer `). On **first login**, if the admin account was freshly seeded, you will receive a `403 PASSWORD_CHANGE_REQUIRED` on every endpoint except `/api/v1/auth/me` and `/api/v1/auth/change-password`. Change your password immediately: ```http POST /api/v1/auth/change-password Content-Type: application/json {"current_password": "", "new_password": ""} ``` ### 2. Create Your First Test 1. **Create a test** (red_lead / blue_lead / admin): ```http POST /api/v1/tests {"title": "T1059.001 PowerShell execution", "technique_id": "T1059.001"} ``` 2. **Start execution** (red_tech / red_lead / admin): ```http POST /api/v1/tests/{id}/start-execution ``` 3. **Fill in red-side data** (red_tech / red_lead / admin): ```http PATCH /api/v1/tests/{id}/red {"tool_used": "Cobalt Strike", "command_executed": "powershell -nop -enc ..."} ``` 4. **Submit red** → moves to `blue_evaluating`. 5. **Fill in blue detection data** (blue_tech / blue_lead / admin): ```http PATCH /api/v1/tests/{id}/blue {"detection_result": "detected", "detection_notes": "SIEM alert fired"} ``` 6. **Submit blue** → moves to `in_review`. 7. **Validate red** (red_lead / admin) and **validate blue** (blue_lead / admin). 8. Test reaches `validated`. Technique coverage is updated automatically. ### 3. Explore Coverage Navigate to the heatmap to see your MITRE ATT&CK coverage: ```http GET /api/v1/heatmap ``` Or check overall scores: ```http GET /api/v1/scores/organization ``` --- ## Key Concepts Glossary | Term | Definition | |------|------------| | **Technique** | A MITRE ATT&CK technique (e.g. T1059.001). The atomic unit of coverage. | | **Test** | An execution of an attack technique against a target environment. Has a full state machine lifecycle. | | **Campaign** | A collection of tests grouped under a common objective (e.g. APT29 simulation). | | **Playbook** | Step-by-step procedure for attacking, defending, or detecting a technique. Versioned. | | **Lesson** | A "lessons learned" record linked to any entity. Captures what happened, root cause, and improvement actions. | | **Coverage** | The status of a technique: `not_covered`, `partial`, or `validated`. Determined by test outcomes. | | **Snapshot** | A point-in-time capture of overall coverage metrics. Used for trend analysis. | | **Detection Asset** | A defensive capability (SIEM rule, EDR policy, sensor) mapped to one or more techniques. | | **Alert Rule** | A condition that triggers an operational alert when met (e.g. coverage drops below threshold). | | **Score** | A weighted numeric representation of coverage quality (0–100) for a technique or the entire organization. | | **Threat Actor** | A known adversary profile linked to techniques. Campaigns can be auto-generated from threat actor profiles. | | **API Key** | A long-lived credential for machine-to-machine access. Scoped to `read`, `write`, or `admin`. | --- ## Where to Go Next - First-time deployers → [Deployment-Guide](Deployment-Guide) - Understand who can do what → [Roles-and-Permissions](Roles-and-Permissions) - Deep-dive into test states → [Test-Lifecycle](Test-Lifecycle) - All API endpoints → [API-Reference](API-Reference) - How coverage scoring works → [MITRE-ATT-CK-Coverage](MITRE-ATT-CK-Coverage) - QA and testing → [QA-Testing-Guide](QA-Testing-Guide) """ PAGE_ARCHITECTURE = """\ # Architecture This page describes the technical architecture of Aegis: its components, data flow, deployment model, and backend code structure. --- ## Tech Stack | Layer | Technology | Version | |-------|------------|---------| | Backend framework | FastAPI | Latest (Python 3.11) | | Database | PostgreSQL | 16 | | Object storage | MinIO | Latest | | Cache / token blacklist | Redis | 7 | | Frontend framework | React | 19 | | Language (frontend) | TypeScript | 5 | | Build tool | Vite | 7 | | CSS framework | Tailwind CSS | v4 | | Data fetching | TanStack Query | v5 | | ORM | SQLAlchemy | 2.x | | Migrations | Alembic | Latest | | Task scheduler | APScheduler | 3.x | | Linting | Ruff | Latest | | Testing | Pytest | 8.x | --- ## Docker Services All services are defined in `docker-compose.yml` (development) and `docker-compose.prod.yml` (production). | Service name | Image | Internal port | Dev exposed port | Role | |---|---|---|---|---| | `aegis-backend` | Custom (Dockerfile) | 8000 | 8000 | FastAPI application + APScheduler | | `aegis-frontend` | Custom (Dockerfile.prod / vite dev) | 5173 / 80 | 5173 | React SPA | | `aegis-postgres` | `postgres:16` | 5432 | 5433 | Primary relational database | | `aegis-redis` | `redis:7` | 6379 | 6379 | Token blacklist, rate limiting, cache | | `aegis-minio` | `minio/minio` | 9000 (API), 9001 (Console) | 9000 / 9001 | Evidence file storage (S3-compatible) | ### Health checks - Backend: `GET /health` returns `{"status": "ok"}` - Postgres: `pg_isready` via Docker healthcheck - Redis: `PING` via Docker healthcheck --- ## Backend Folder Structure ``` backend/ ├── app/ │ ├── domain/ │ │ ├── entities/ # Core domain entities (Test, Technique, User, ...) │ │ ├── errors/ # Custom domain exception classes │ │ ├── ports/ # Abstract interfaces (repositories, storage) │ │ └── value_objects/ # Immutable value types (CoverageStatus, Role, ...) │ │ │ ├── services/ # Business logic layer (~45 services) │ │ ├── test_service.py │ │ ├── coverage_service.py │ │ ├── campaign_service.py │ │ ├── alert_service.py │ │ ├── report_service.py │ │ └── ... │ │ │ ├── routers/ # FastAPI route handlers (~28 routers) │ │ ├── auth.py # /api/v1/auth │ │ ├── tests.py # /api/v1/tests │ │ ├── techniques.py # /api/v1/techniques │ │ ├── campaigns.py # /api/v1/campaigns │ │ └── ... # one file per module │ │ │ ├── models/ # SQLAlchemy ORM model classes │ │ ├── test.py │ │ ├── technique.py │ │ ├── user.py │ │ └── ... │ │ │ ├── schemas/ # Pydantic v2 request/response schemas │ │ ├── test_schemas.py │ │ ├── user_schemas.py │ │ └── ... │ │ │ ├── infrastructure/ # Adapters for external systems │ │ ├── redis_client.py # Redis connection + helpers │ │ ├── minio_client.py # MinIO / S3 evidence storage │ │ └── persistence/ # SQLAlchemy session management │ │ │ ├── jobs/ # APScheduler background jobs │ │ ├── mitre_sync.py # Hourly MITRE ATT&CK data sync │ │ ├── alert_eval.py # Hourly alert rule evaluation │ │ └── retention.py # Snapshot retention cleanup │ │ │ ├── core/ │ │ ├── config.py # Settings via pydantic-settings / .env │ │ ├── security.py # JWT creation/validation, password hashing │ │ └── dependencies.py # FastAPI dependency injection (get_db, get_current_user) │ │ │ └── main.py # App factory, router registration, CORS, middleware │ ├── alembic/ # Migration scripts │ └── versions/ ├── tests/ # Pytest test suite (367+ tests) │ ├── test_auth.py │ ├── test_tests.py │ └── ... ├── entrypoint.prod.sh # Production startup: run migrations then uvicorn ├── requirements.txt └── Dockerfile ``` --- ## API URL Structure Every API endpoint is prefixed with `/api/v1/`. Examples: ``` GET /api/v1/techniques POST /api/v1/tests GET /api/v1/dashboard/kpis POST /api/v1/campaigns/{id}/complete ``` The Swagger UI (development only) is available at `http://localhost:8000/docs`. ReDoc is at `http://localhost:8000/redoc`. In production (`AEGIS_ENV=production`) both are disabled. --- ## Authentication Flow ``` Client Backend Redis │ │ │ │ POST /auth/login │ │ │ (username + password) │ │ │──────────────────────────>│ │ │ │ verify password hash │ │ │ create JWT (jti claim) │ │ Set-Cookie: aegis_token │ │ │<──────────────────────────│ │ │ │ │ │ GET /api/v1/tests │ │ │ (Cookie: aegis_token) │ │ │──────────────────────────>│ │ │ │ decode JWT │ │ │ check blacklist (jti) ────>│ │ │<─────────────────── not found │ │ │ inject current_user │ │ 200 OK │ │ │<──────────────────────────│ │ │ │ │ │ POST /auth/logout │ │ │──────────────────────────>│ │ │ │ store jti in blacklist ───>│ │ │ (TTL = token exp) │ │ Clear cookie │ │ │<──────────────────────────│ │ ``` Alternative: pass token as `Authorization: Bearer ` header (e.g. for API clients or when cookies are not practical). --- ## Background Jobs (APScheduler) Aegis uses APScheduler (in-process) for recurring tasks: | Job ID | Schedule | Description | |--------|----------|-------------| | `mitre_sync` | Every hour | Fetches latest MITRE ATT&CK STIX data from GitHub, upserts techniques | | `alert_evaluation` | Every hour | Evaluates all enabled alert rules against current data | | `snapshot_retention` | Daily | Deletes old snapshots beyond the configured retention window | Jobs are registered in `app/jobs/` and started in `main.py` during application startup. Admin can check job status at `GET /api/v1/system/scheduler-status`. Manual MITRE sync: `POST /api/v1/system/sync-mitre` (admin only). --- ## Data Flow — Test Execution ``` red_tech Backend DB MinIO │ │ │ │ │ POST /tests/{id}/ │ │ │ │ start-execution │ │ │ │───────────────────────>│ check role (red_tech+) │ │ │ │ validate state = draft │ │ │ │ UPDATE state=red_executing│ │ │ │──────────────────────────>│ │ │ 200 OK │ │ │ │<───────────────────────│ │ │ │ │ │ │ │ POST /tests/{id}/ │ │ │ │ evidence │ │ │ │ (multipart file) │ │ │ │───────────────────────>│ check role + state │ │ │ │ upload file ──────────────────────────>│ │ │ store metadata ───────────>│ │ │ 201 Created │ │ │ │<───────────────────────│ │ │ ``` --- ## Production vs Development Differences | Feature | Development | Production | |---------|-------------|------------| | Swagger UI (`/docs`) | Enabled | Disabled | | ReDoc (`/redoc`) | Enabled | Disabled | | Cookie `secure` flag | False (HTTP allowed) | True (HTTPS required) | | `AEGIS_ENV` | `development` | `production` | | `SECURE_COOKIES` | `auto` or `false` | `auto` or `true` | | Debug logging | Verbose | Structured JSON | | CORS origins | Permissive | Restricted | | DB migrations | Manual or auto | Auto on startup via `entrypoint.prod.sh` | | Secret key | Can be default | **Must** be set via `SECRET_KEY` env var | See [Deployment-Guide](Deployment-Guide) for full configuration details. """ PAGE_ROLES = """\ # Roles and Permissions Aegis uses Role-Based Access Control (RBAC) with six roles organized in a hierarchy. Every authenticated request carries the user's role, which is checked against the required role for each endpoint. --- ## Role Hierarchy ``` admin └─ red_lead / blue_lead └─ red_tech / blue_tech └─ viewer ``` Higher roles generally include the capabilities of lower roles, with some team-specific restrictions (e.g. a `red_lead` cannot perform `blue_lead` actions). > **Admin bypass**: `admin` ALWAYS passes any role check, regardless of which role > is required. There is no endpoint an admin cannot call. --- ## Role Descriptions ### admin **Primary use case**: Platform administrator. Full system access. **Can do:** - Everything any other role can do - Create, update, deactivate, and delete users - View audit logs (`GET /api/v1/audit-logs`) - Delete snapshots (`DELETE /api/v1/snapshots/{id}`) - Delete alert rules - Trigger MITRE ATT&CK sync (`POST /api/v1/system/sync-mitre`) - Manage webhooks (`GET/POST/PATCH/DELETE /api/v1/webhooks`) — **admin only** - Set data classification on tests (`PATCH /api/v1/tests/{id}/classification`) - View scheduler status (`GET /api/v1/system/scheduler-status`) - Configure SSO (`PUT /api/v1/sso/config`) - Manage data sources (`POST/PATCH/DELETE /api/v1/data-sources`) - Complete campaigns (along with red_lead) **Cannot do:** - Be locked out — bypasses all role checks by design --- ### red_lead **Primary use case**: Red Team Lead. Manages the offensive side of operations. **Can do:** - Create, update, and delete tests (in `draft` or `rejected` state) - Create campaigns and complete them (`POST /campaigns/{id}/complete`) - Create test templates - Create and manage playbooks and lessons - Validate the red side of a test (`POST /tests/{id}/validate-red`) - Manage attack paths (including delete) - Create alert rules - Generate professional reports (PDF, DOCX, HTML) - Start test execution - Upload red evidence - Submit red assessment - All viewer capabilities **Cannot do:** - Access audit logs - Perform blue-side actions (validate-blue, submit-blue, update blue fields) - Delete snapshots - Sync MITRE data - Access or manage webhooks - Set data classification --- ### blue_lead **Primary use case**: Blue Team Lead. Manages the defensive side of operations. **Can do:** - Evaluate and validate the blue side of a test (`POST /tests/{id}/validate-blue`) - Validate blue side (`POST /tests/{id}/validate-blue`) - Manage detection assets and detection rules - Create playbooks and lessons - Generate a revalidation queue - Manage ownership of assets - Create alert rules - Generate professional reports (PDF, DOCX, HTML) - Upload blue evidence (in `blue_evaluating` state) - Submit blue assessment - All viewer capabilities **Cannot do:** - Complete campaigns (red_lead + admin only) - Start test execution (`POST /tests/{id}/start-execution`) - Validate the red side - Access audit logs - Delete snapshots - Sync MITRE data - Access or manage webhooks --- ### red_tech **Primary use case**: Red Team Technician. Executes attacks. **Can do:** - Start test execution (`POST /tests/{id}/start-execution`) — moves `draft` → `red_executing` - Update red-side fields (`PATCH /tests/{id}/red`): `tool_used`, `command_executed`, `output_observed`, `red_notes`, etc. - Upload red evidence files - Submit red assessment (`POST /tests/{id}/submit-red`) — moves `red_executing` → `blue_evaluating` - Pause and resume test timers - Create worklogs for tests - View all read-only data (same as viewer) **Cannot do:** - Create new tests - Create campaigns - Manage playbooks or lessons - Access webhooks, audit logs, or admin endpoints - Perform blue-side actions - Validate either side - Generate professional reports --- ### blue_tech **Primary use case**: Blue Team Technician. Evaluates detection. **Can do:** - Update blue-side fields (`PATCH /tests/{id}/blue`): `detection_result` (detected/not_detected/partial), `detection_notes`, `alert_fired`, `response_action`, etc. - Upload blue evidence files (only while test is in `blue_evaluating` state) - Submit blue assessment (`POST /tests/{id}/submit-blue`) — moves `blue_evaluating` → `in_review` - Evaluate detection rules (`POST /api/v1/detection-rules/evaluate`) - Create detection validations - Pause and resume test timers - Create worklogs for tests - View all read-only data (same as viewer) **Cannot do:** - Start test execution - Submit red assessment - Validate either side - Create tests, campaigns, playbooks, or lessons - Access webhooks, audit logs, or admin endpoints - Generate professional reports --- ### viewer **Primary use case**: Stakeholder, auditor, or report consumer. Read-only access plus report generation. **Can do:** - Read all entities: tests, techniques, campaigns, playbooks, lessons, coverage, snapshots, alerts, dashboards, detection lifecycle, scores, heatmap, threat actors, compliance, analytics - Generate professional reports: PDF, DOCX, HTML (all report types) - Mark own notifications as read **Cannot do:** - Create or modify any entity (no POST/PATCH/DELETE on data entities) - Upload evidence - Change test state - Manage users, webhooks, alert rules, playbooks, campaigns, or any configuration --- ## Access Matrix The table below shows key endpoints and which roles can call them. ✅ = allowed, ❌ = forbidden, * = with restrictions (see notes) | Endpoint | admin | red_lead | blue_lead | red_tech | blue_tech | viewer | |----------|-------|----------|-----------|----------|-----------|--------| | POST /tests | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | PATCH /tests/{id} (general) | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | POST /tests/{id}/start-execution | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | | PATCH /tests/{id}/red | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | | POST /tests/{id}/submit-red | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | | PATCH /tests/{id}/blue | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | | POST /tests/{id}/submit-blue | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | | POST /tests/{id}/validate-red | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | POST /tests/{id}/validate-blue | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | | POST /tests/{id}/reopen | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | POST /tests/{id}/pause-timer | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | | PATCH /tests/{id}/classification | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | POST /campaigns | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | POST /campaigns/{id}/complete | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | POST /campaigns/from-threat-actor/{id} | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | POST /knowledge/playbooks | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | GET /reports/generate/* | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | | GET /audit-logs | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | ALL /webhooks | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | DELETE /snapshots/{id} | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | POST /system/sync-mitre | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | PUT /sso/config | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | PATCH /data-sources | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | GET /users (list) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | POST /users | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | PATCH /alerts/rules | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | POST /alerts/evaluate | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | | GET /scores/organization | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | PATCH /scores/config | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | --- ## Notes on Role Enforcement 1. **JWT carries the role**: The user's role is embedded in the JWT payload and re-verified against the database on every request. 2. **Admin bypass is unconditional**: The `require_role()` dependency immediately returns if `current_user.role == "admin"`. 3. **Resource ownership**: Some endpoints additionally check that the requesting user owns the resource (e.g. API keys, worklogs, notifications). Admins bypass ownership checks too. 4. **must_change_password**: If this flag is set on the user record, all endpoints return `403 PASSWORD_CHANGE_REQUIRED` until the password is changed via `POST /api/v1/auth/change-password`. """ PAGE_TEST_LIFECYCLE = """\ # Test Lifecycle Every test in Aegis follows a structured state machine from creation to final validation. This page documents every state, transition, actor, and field update rule. --- ## State Diagram ``` ┌─────────┐ │ draft │◄──────────────────────────┐ └────┬────┘ │ │ POST /start-execution │ │ (red_tech, red_lead, admin) │ POST /reopen ▼ │ (leads, admin) ┌─────────────────┐ │ │ red_executing │ │ └────────┬────────┘ │ │ POST /submit-red │ │ (red_tech, red_lead, admin) │ ▼ │ ┌─────────────────────┐ │ │ blue_evaluating │ │ └────────┬────────────┘ │ │ POST /submit-blue │ │ (blue_tech, blue_lead, admin) │ ▼ │ ┌───────────┐ │ │ in_review │ │ └─────┬─────┘ │ │ Both validate (approved) │ │ │ ┌──────────┴──────────┐ │ │ Either rejects │ │ ▼ ▼ │ ┌──────────┐ ┌───────────┐ │ │ rejected │─────────┤ validated │ │ └──────────┘ └───────────┘ │ │ │ └───────────────────────────────────────────┘ ``` --- ## States Reference ### draft - **Description**: Initial state. Test has been defined but not yet executed. - **Who creates**: red_lead, blue_lead, admin (POST /api/v1/tests) - **Who can edit general fields**: red_lead, blue_lead, admin (PATCH /api/v1/tests/{id}) - **Available actions**: - Edit test metadata (title, description, technique_id, objective, target_environment, etc.) - Add to campaigns - Attach red evidence (even before execution, for pre-work files) - **Cannot do**: upload blue evidence, start timer **Create payload example:** ```json { "title": "T1059.001 PowerShell cradle download", "technique_id": "T1059.001", "objective": "Verify AMSI bypass detection", "target_environment": "Windows 10 workstation", "severity": "high", "campaign_id": "optional-uuid" } ``` --- ### red_executing - **Description**: Red team is actively executing the attack. - **Trigger**: POST /api/v1/tests/{id}/start-execution - **Who can trigger**: red_tech, red_lead, admin - **Timer starts** automatically when entering this state. - **Who updates red fields**: red_tech, red_lead, admin (PATCH /api/v1/tests/{id}/red) **Red fields (PATCH /tests/{id}/red):** ```json { "tool_used": "Cobalt Strike 4.8", "command_executed": "powershell -nop -w hidden -enc ...", "output_observed": "Beacon established on 10.0.0.5", "red_notes": "Used staged payload to bypass AV", "attacker_ip": "10.10.10.100", "target_ip": "10.0.0.5", "start_time": "2024-03-15T10:00:00Z", "end_time": "2024-03-15T10:15:00Z" } ``` **Evidence upload (team=red):** ```http POST /api/v1/tests/{id}/evidence Content-Type: multipart/form-data file= team=red description=PowerShell execution screenshot ``` --- ### blue_evaluating - **Description**: Blue team is evaluating what they detected (or didn't). - **Trigger**: POST /api/v1/tests/{id}/submit-red - **Who can trigger**: red_tech, red_lead, admin - **Who updates blue fields**: blue_tech, blue_lead, admin (PATCH /api/v1/tests/{id}/blue) **Blue fields (PATCH /tests/{id}/blue):** ```json { "detection_result": "detected", "detection_notes": "Windows Defender ATP alert fired: Suspicious PowerShell", "alert_fired": true, "alert_name": "ASR rule: Block Office apps from creating child processes", "response_action": "Endpoint isolated automatically", "time_to_detect_seconds": 47, "blue_notes": "AMSI telemetry captured full script content" } ``` `detection_result` values: - `detected` — blue team observed the attack in time - `not_detected` — attack was not detected - `partial` — partial detection (some indicators observed but not full alert) **Evidence upload (team=blue):** ```http POST /api/v1/tests/{id}/evidence Content-Type: multipart/form-data file= team=blue description=SIEM alert screenshot ``` --- ### in_review - **Description**: Both leads must independently validate their side. - **Trigger**: POST /api/v1/tests/{id}/submit-blue - **Who can trigger**: blue_tech, blue_lead, admin - **No field updates allowed** while in review. - **Evidence is locked** — no new evidence uploads. **Red validation (red_lead, admin):** ```http POST /api/v1/tests/{id}/validate-red {"red_validation_status": "approved", "red_validation_notes": "Execution was clean"} ``` **Blue validation (blue_lead, admin):** ```http POST /api/v1/tests/{id}/validate-blue {"blue_validation_status": "approved", "blue_validation_notes": "Detection confirmed"} ``` Both validations can be `approved` or `rejected`. --- ### validated - **Description**: Both leads approved. Test is complete and results are official. - **Trigger**: Both red_validation_status and blue_validation_status = `approved` - **Effects**: - Technique coverage status is recalculated - Webhook events fired (if configured): `test.validated` - Score may be updated - Snapshot data refreshed - **After validation**: Leads and admin can still edit the `remediation` fields: ```http PATCH /api/v1/tests/{id}/remediation { "remediation_status": "in_progress", "remediation_notes": "SIEM rule deployed to block this technique", "remediation_owner": "blue-lead-user-uuid" } ``` --- ### rejected - **Description**: At least one lead rejected their side. - **Trigger**: Either validate-red or validate-blue returns `rejected` - **Recovery**: POST /api/v1/tests/{id}/reopen (leads, admin) sends back to `draft` --- ## Evidence Rules Summary | State | Red evidence | Blue evidence | |-------|-------------|---------------| | draft | Upload allowed | Not allowed | | red_executing | Upload allowed | Not allowed | | blue_evaluating | Not allowed | Upload allowed | | in_review | **Locked** | **Locked** | | validated | **Locked** | **Locked** | | rejected | **Locked** | **Locked** | --- ## Timer The test timer measures elapsed execution time. | Action | Endpoint | Allowed roles | |--------|----------|---------------| | Pause timer | POST /tests/{id}/pause-timer | All except viewer | | Resume timer | POST /tests/{id}/resume-timer | All except viewer | The timer automatically starts when entering `red_executing`. --- ## Timeline and Audit Trail Every state change is recorded in the timeline: ```http GET /api/v1/tests/{id}/timeline ``` Response includes entries for: - State transitions (from_state, to_state, actor, timestamp) - Validation actions (approved/rejected, actor, timestamp) - Evidence uploads (team, filename, actor, timestamp) - Timer pauses/resumes - Worklog entries --- ## Retest Chain When a test is a retest of a previous (failed) test, it is linked via `retest_of`: ```json {"retest_of": "previous-test-uuid"} ``` Navigate the full chain: ```http GET /api/v1/tests/{id}/retest-chain ``` Returns an ordered list of all tests in the chain, oldest first. """ PAGE_MITRE = """\ # MITRE ATT&CK Coverage Aegis is built around the MITRE ATT&CK framework. This page explains how the coverage system works, how techniques are scored, and how the data is maintained. --- ## What is MITRE ATT&CK? MITRE ATT&CK is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. It provides a common language for describing attacker behavior, allowing Red and Blue teams to align their work. - Official reference: https://attack.mitre.org/ - Aegis uses the **Enterprise** matrix - Techniques are identified by IDs like `T1059` (parent) and `T1059.001` (sub-technique) --- ## Tactics (14) | # | Tactic ID | Tactic Name | Description | |---|-----------|-------------|-------------| | 1 | TA0043 | Reconnaissance | Gathering information before attack | | 2 | TA0042 | Resource Development | Preparing attack resources | | 3 | TA0001 | Initial Access | Getting into the target environment | | 4 | TA0002 | Execution | Running adversary-controlled code | | 5 | TA0003 | Persistence | Maintaining foothold | | 6 | TA0004 | Privilege Escalation | Gaining higher-level permissions | | 7 | TA0005 | Defense Evasion | Avoiding detection | | 8 | TA0006 | Credential Access | Stealing credentials | | 9 | TA0007 | Discovery | Exploring the environment | | 10 | TA0008 | Lateral Movement | Moving through the network | | 11 | TA0009 | Collection | Gathering data of interest | | 12 | TA0011 | Command and Control | Communicating with compromised systems | | 13 | TA0010 | Exfiltration | Stealing data out of the environment | | 14 | TA0040 | Impact | Manipulating, interrupting, or destroying systems | --- ## Coverage Statuses Each technique has a `coverage_status` field: | Status | Meaning | |--------|---------| | `not_covered` | No validated test exists for this technique | | `partial` | At least one test exists but not all are validated, OR detection was partial | | `validated` | At least one test is in `validated` state with both leads approved | Coverage is stored in the `techniques` table and recalculated automatically when: - A test reaches `validated` state - A test is reopened (back to draft from validated/rejected) - MITRE sync runs and adds/removes techniques --- ## Coverage Scoring Aegis calculates a **weighted coverage score** (0–100) for each technique and for the organization overall. The formula combines multiple signals: ### Default weights (configurable) | Signal | Default weight | Description | |--------|---------------|-------------| | Tests | 40% | Number and outcome of validated tests | | Detection rules | 30% | Detection rules linked to the technique | | D3FEND | 10% | Defensive countermeasures mapped via MITRE D3FEND | | Recency | 10% | How recently the technique was last tested | | Severity | 10% | Technique severity/impact factor | ### Configuring weights Only admins can change scoring weights: ```http PATCH /api/v1/scores/config { "test_weight": 0.40, "detection_rule_weight": 0.30, "d3fend_weight": 0.10, "recency_weight": 0.10, "severity_weight": 0.10 } ``` Weights must sum to 1.0. ### Score endpoints | Endpoint | Description | |----------|-------------| | GET /api/v1/scores/organization | Overall organization coverage score | | GET /api/v1/scores/techniques/{id} | Score for a specific technique | | GET /api/v1/scores/by-tactic | Scores aggregated by tactic | --- ## MITRE Data Sync Aegis maintains its own copy of the MITRE ATT&CK technique database. **Automatic sync**: Runs hourly via APScheduler. Fetches the latest STIX data from the official MITRE ATT&CK GitHub repository and upserts all techniques. **Manual sync** (admin only): ```http POST /api/v1/system/sync-mitre ``` **What syncs:** - Technique ID, name, description - Tactic associations - Sub-technique relationships - Deprecation/revocation status - Platform tags (Windows, Linux, macOS, Cloud, etc.) **New techniques** added by MITRE automatically appear as `not_covered` in Aegis. **Deprecated techniques** are marked accordingly but retained for historical test data. --- ## Technique Review When your infrastructure changes (new tools, new SIEM rules), techniques may need re-testing to confirm coverage is still accurate. **Mark a technique for review** (leads, admin): ```http PATCH /api/v1/techniques/{id}/review {"needs_review": true, "review_reason": "SIEM rules rebuilt after migration"} ``` This triggers the revalidation queue in the detection lifecycle module. --- ## Heatmap The heatmap visualizes coverage across all tactics and techniques: ```http GET /api/v1/heatmap ``` Returns a matrix organized by tactic, with each technique cell showing: - `coverage_status` - `score` - `test_count` - `last_tested` date --- ## D3FEND Mappings MITRE D3FEND is a knowledge graph of cybersecurity countermeasures. Aegis integrates D3FEND mappings to show which defensive techniques apply to each ATT&CK technique. ```http GET /api/v1/techniques/{mitre_id}/d3fend ``` Returns a list of D3FEND techniques (countermeasures) mapped to the ATT&CK technique, with descriptions and implementation guidance. --- ## Risk Profiles The risk module assesses how dangerous each uncovered technique is: | Endpoint | Description | |----------|-------------| | GET /api/v1/risk/profiles | Risk profile per technique | | GET /api/v1/risk/matrix | Risk matrix (severity × coverage) | | GET /api/v1/risk/summary | Aggregate risk summary | | GET /api/v1/risk/top | Top N highest-risk uncovered techniques | | GET /api/v1/risk/recommendations | Prioritized remediation recommendations | | POST /api/v1/risk/compute | Trigger manual risk recomputation | **Top uncovered techniques:** ```http GET /api/v1/risk/top?limit=10 ``` Returns the 10 highest-priority uncovered techniques based on: - MITRE technique severity/prevalence - Threat actor association (if the technique is used by tracked threat actors) - Current coverage gap size """ PAGE_API_REF = """\ # API Reference All endpoints are prefixed with `/api/v1/`. Authentication via cookie `aegis_token` or `Authorization: Bearer `. Role abbreviations: A=admin, RL=red_lead, BL=blue_lead, RT=red_tech, BT=blue_tech, V=viewer, Any=all authenticated. --- ## Auth — /api/v1/auth | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | /auth/login | Public | Login with username+password (form-encoded). Sets `aegis_token` cookie. | | POST | /auth/logout | Any | Revokes token (adds jti to Redis blacklist). Clears cookie. | | GET | /auth/me | Any | Returns current user profile + role. | | POST | /auth/change-password | Any | Change own password. Required if `must_change_password=true`. | --- ## Users — /api/v1/users | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /users | A | List all users (paginated). | | POST | /users | A | Create a new user. Sets `must_change_password=true`. | | GET | /users/{id} | A | Get user by ID. | | PATCH | /users/{id} | A | Update user (role, is_active, display_name, etc.). | | DELETE | /users/{id} | A | Deactivate (soft-delete) a user. | | GET | /users/me | Any | Get own profile. | | PATCH | /users/me/preferences | Any | Update own UI preferences (theme, notifications, etc.). | --- ## Techniques — /api/v1/techniques | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /techniques | Any | List techniques. Query: tactic_id, platform, coverage_status, search. | | GET | /techniques/{mitre_id} | Any | Get technique by MITRE ID (e.g. T1059.001). | | POST | /techniques | A | Create a custom technique (non-MITRE). | | PATCH | /techniques/{mitre_id} | A | Update technique metadata. | | PATCH | /techniques/{mitre_id}/review | RL,BL,A | Mark technique as needing review. | | GET | /techniques/{mitre_id}/d3fend | Any | D3FEND countermeasure mappings for this technique. | --- ## Tests — /api/v1/tests | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /tests | Any | List tests. Filters: state, technique_id, campaign_id, assigned_to. | | POST | /tests | RL,BL,A | Create a new test in draft state. | | POST | /tests/from-template | RL,BL,A | Create test(s) from a saved template. | | GET | /tests/{id} | Any | Get full test detail including all fields. | | PATCH | /tests/{id} | RL,BL,A | Update general fields (title, objective, technique, etc.). Draft/rejected only. | | PATCH | /tests/{id}/classification | A | Set data_classification (confidential, restricted, etc.). | | PATCH | /tests/{id}/red | RL,RT,A | Update red-side fields. State: red_executing. | | PATCH | /tests/{id}/blue | BL,BT,A | Update blue-side fields. State: blue_evaluating. | | POST | /tests/{id}/start-execution | RL,RT,A | Transition draft → red_executing. | | POST | /tests/{id}/submit-red | RL,RT,A | Transition red_executing → blue_evaluating. | | POST | /tests/{id}/submit-blue | BL,BT,A | Transition blue_evaluating → in_review. | | POST | /tests/{id}/validate-red | RL,A | Approve/reject red side. State: in_review. | | POST | /tests/{id}/validate-blue | BL,A | Approve/reject blue side. State: in_review. | | POST | /tests/{id}/reopen | RL,BL,A | Transition rejected → draft. | | POST | /tests/{id}/pause-timer | All except V | Pause elapsed time counter. | | POST | /tests/{id}/resume-timer | All except V | Resume elapsed time counter. | | PATCH | /tests/{id}/remediation | RL,BL,A | Update remediation status/notes (any state post-review). | | GET | /tests/{id}/timeline | Any | Full audit trail of state changes and actions. | | GET | /tests/{id}/retest-chain | Any | All tests in this retest lineage. | --- ## Evidence — /api/v1/tests/{id}/evidence & /api/v1/evidence | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | /tests/{id}/evidence | Role-dependent* | Upload evidence file. team=red or team=blue. | | GET | /tests/{id}/evidence | Any | List all evidence files for a test. | | GET | /evidence/{id} | Any | Get evidence metadata. | | GET | /evidence/{id}/download | Any | Download evidence file from MinIO (signed URL or stream). | | DELETE | /evidence/{id} | Own or RL,BL,A | Delete evidence file. | *red evidence: RL, RT, A | blue evidence: BL, BT, A. State restrictions apply (see [Test-Lifecycle](Test-Lifecycle)). --- ## Campaigns — /api/v1/campaigns | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /campaigns | Any | List campaigns. Filters: status, type, date range. | | POST | /campaigns | RL,BL,A | Create a new campaign. | | GET | /campaigns/{id} | Any | Get campaign detail. | | PATCH | /campaigns/{id} | RL,BL,A | Update campaign metadata. | | POST | /campaigns/{id}/tests | RL,BL,A | Add a test to a campaign. | | DELETE | /campaigns/{id}/tests/{ctid} | RL,BL,A | Remove a test from a campaign. | | POST | /campaigns/{id}/activate | RL,BL,A | Transition draft → active. | | POST | /campaigns/{id}/complete | RL,A | Transition active → completed. | | GET | /campaigns/{id}/progress | Any | Count validated/total tests in campaign. | | POST | /campaigns/from-threat-actor/{actor_id} | RL,BL,A | Auto-create campaign + tests from threat actor profile. | | PATCH | /campaigns/{id}/schedule | RL,BL,A | Set start_date / end_date. | | GET | /campaigns/{id}/history | Any | Campaign state change history. | --- ## Attack Paths — /api/v1/attack-paths | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /attack-paths | Any | List attack paths. | | POST | /attack-paths | All except V | Create an attack path (sequence of techniques). | | GET | /attack-paths/{id} | Any | Get attack path detail. | | PATCH | /attack-paths/{id} | All except V | Update attack path metadata. | | DELETE | /attack-paths/{id} | RL,BL,A | Delete attack path. | | POST | /attack-paths/{id}/steps | All except V | Add a step (technique) to attack path. | | DELETE | /attack-paths/{id}/steps/{sid} | All except V | Remove a step. | | POST | /attack-paths/{id}/executions | All except V | Start an execution of the attack path. | | POST | /executions/{id}/start | All except V | Begin execution. | | POST | /executions/{id}/steps/{sid} | All except V | Record step outcome. | | POST | /executions/{id}/complete | All except V | Mark execution complete. | | POST | /executions/{id}/abort | RL,BL,A | Abort execution. | --- ## Knowledge — /api/v1/knowledge | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /knowledge/playbooks | Any | List playbooks. Filter by technique, type. | | POST | /knowledge/playbooks | RL,BL,A | Create a playbook. | | GET | /knowledge/playbooks/{id} | Any | Get playbook + versions list. | | PATCH | /knowledge/playbooks/{id} | RL,BL,A | Update playbook (creates version snapshot). | | DELETE | /knowledge/playbooks/{id} | RL,BL,A | Delete playbook. | | GET | /knowledge/playbooks/{id}/versions | Any | List all versions of a playbook. | | POST | /knowledge/playbooks/{id}/restore/{version} | RL,BL,A | Restore a previous version. | | GET | /knowledge/lessons | Any | List lessons learned. Filter by severity, technique. | | POST | /knowledge/lessons | RL,BL,A | Create a lesson learned. | | GET | /knowledge/lessons/{id} | Any | Get lesson detail. | | PATCH | /knowledge/lessons/{id} | RL,BL,A | Update lesson. | | DELETE | /knowledge/lessons/{id} | RL,BL,A | Delete lesson. | | GET | /knowledge/stats | Any | Summary: playbooks by type, lessons by severity. | --- ## Ownership — /api/v1/ownership | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /ownership/assignments | Any | Technique-to-owner assignments. | | POST | /ownership/assignments | RL,BL,A | Assign technique ownership. | | PATCH | /ownership/assignments/{id} | RL,BL,A | Update assignment. | | DELETE | /ownership/assignments/{id} | RL,BL,A | Remove assignment. | | GET | /ownership/assets | Any | Asset ownership list. | | POST | /ownership/assets | BL,A | Create asset ownership record. | | GET | /ownership/revalidation-queue | BL,A | Techniques requiring revalidation. | | POST | /ownership/revalidation-queue/generate | BL,A | Generate revalidation queue from infrastructure changes. | --- ## Risk — /api/v1/risk | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | /risk/compute | RL,BL,A | Trigger risk recomputation. | | GET | /risk/profiles | Any | Risk profile per technique. | | GET | /risk/matrix | Any | Risk matrix (impact × coverage). | | GET | /risk/summary | Any | Aggregate risk numbers. | | GET | /risk/recommendations | Any | Prioritized list of techniques to test next. | | GET | /risk/top | Any | Top N highest-risk uncovered techniques. ?limit=N | --- ## Alerts — /api/v1/alerts | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | /alerts/evaluate | RL,BL,A | Manually trigger evaluation of all enabled rules. | | GET | /alerts/summary | Any | Alert counts by status, severity, rule type. | | GET | /alerts | Any | List alert instances. Filters: status, severity, rule_type. | | GET | /alerts/{id} | Any | Get alert instance detail. | | POST | /alerts/{id}/acknowledge | RL,BL,A | Acknowledge an alert. | | POST | /alerts/{id}/resolve | RL,BL,A | Resolve an alert. | | POST | /alerts/{id}/dismiss | RL,BL,A | Dismiss an alert. | | GET | /alerts/rules | Any | List alert rules. | | POST | /alerts/rules | RL,BL,A | Create an alert rule. | | GET | /alerts/rules/{id} | Any | Get rule detail. | | PATCH | /alerts/rules/{id} | RL,BL,A | Update rule. | | DELETE | /alerts/rules/{id} | A | Delete rule. | --- ## Dashboard — /api/v1/dashboard | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /dashboard/kpis | Any | Key metrics: total techniques, covered %, tests, campaigns. | | GET | /dashboard/executive | Any | Narrative executive summary. | | GET | /dashboard/coverage-by-tactic | Any | Coverage breakdown per tactic. | | GET | /dashboard/posture-history | Any | Historical coverage trend data. | | GET | /dashboard/activity | Any | Recent activity feed (tests, campaigns, validations). | | POST | /dashboard/posture-snapshot | RL,BL,A | Create an immediate posture snapshot. | --- ## Snapshots — /api/v1/snapshots | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /snapshots | Any | List snapshots (paginated). | | POST | /snapshots | RL,BL,A | Create a manual snapshot. | | GET | /snapshots/{id} | Any | Get snapshot detail. | | DELETE | /snapshots/{id} | A | Delete a snapshot. | | GET | /snapshots/evolution | Any | Trend data: coverage over time. | | GET | /snapshots/compare | Any | Diff two snapshots. Query: ?a=&b= | --- ## Reports — /api/v1/reports | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /reports/coverage-summary | Any | Coverage data as JSON. | | GET | /reports/coverage-csv | Any | Coverage data as CSV download. | | GET | /reports/test-results | Any | Test results data as JSON. | | GET | /reports/remediation-status | Any | Remediation tracking data. | | GET | /reports/generate/purple-campaign/{id} | RL,BL,A,V | Full campaign report. ?format=pdf|docx|html | | GET | /reports/generate/coverage-summary | RL,BL,A,V | Coverage status report. | | GET | /reports/generate/executive-summary | RL,BL,A,V | Executive briefing document. | | GET | /reports/generate/quarterly-summary | RL,BL,A,V | Quarterly review report. | | GET | /reports/generate/technique/{id} | RL,BL,A,V | Per-technique detail report. | --- ## Detection Lifecycle — /api/v1/detection-lifecycle | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /detection-lifecycle/assets | Any | List detection assets. | | POST | /detection-lifecycle/assets | BL,A | Create a detection asset. | | GET | /detection-lifecycle/assets/{id} | Any | Get asset detail. | | PATCH | /detection-lifecycle/assets/{id} | BL,A | Update asset. | | DELETE | /detection-lifecycle/assets/{id} | BL,A | Delete asset. | | POST | /detection-lifecycle/assets/{id}/techniques/{tid} | BL,A | Link asset to technique. | | DELETE | /detection-lifecycle/assets/{id}/techniques/{tid} | BL,A | Unlink asset from technique. | | GET | /detection-lifecycle/validations | Any | List detection validations. | | POST | /detection-lifecycle/validations | BL,A | Create a validation record. | | POST | /detection-lifecycle/validations/{id}/invalidate | BL,A | Invalidate (revoke) a validation. | | GET | /detection-lifecycle/infrastructure-changes | Any | List infrastructure change records. | | POST | /detection-lifecycle/infrastructure-changes | BL,A | Record an infrastructure change. | | GET | /detection-lifecycle/dashboard | Any | Detection confidence per technique. | --- ## Detection Rules — /api/v1/detection-rules | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /detection-rules | Any | List detection rules. | | POST | /detection-rules | BL,A | Create detection rule. | | GET | /detection-rules/{id} | Any | Get rule detail. | | PATCH | /detection-rules/{id} | BL,A | Update rule. | | DELETE | /detection-rules/{id} | BL,A | Delete rule. | | POST | /detection-rules/evaluate | BL,BT,A | Record rule triggered/not-triggered for a test. | --- ## Test Templates — /api/v1/test-templates | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /test-templates | Any | List templates. | | POST | /test-templates | RL,BL,A | Create template from existing test or from scratch. | | GET | /test-templates/{id} | Any | Get template detail. | | PATCH | /test-templates/{id} | RL,BL,A | Update template. | | DELETE | /test-templates/{id} | RL,BL,A | Delete template. | --- ## Webhooks — /api/v1/webhooks (admin only) | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /webhooks | A | List configured webhooks. | | POST | /webhooks | A | Create a webhook (URL, events, secret). | | GET | /webhooks/{id} | A | Get webhook detail. | | PATCH | /webhooks/{id} | A | Update webhook. | | DELETE | /webhooks/{id} | A | Delete webhook. | | POST | /webhooks/{id}/test | A | Send a test event to the webhook URL. | Webhook events: `test.validated`, `test.state_changed`, `campaign.completed`, `alert.fired`, `coverage.changed`. SSRF protection: webhook URLs are validated against private IP ranges before saving. --- ## API Keys — /api/v1/api-keys | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /api-keys | Any | List own API keys. | | POST | /api-keys | Any | Create a new API key. Scope: read/write/admin. | | DELETE | /api-keys/{id} | Own or A | Revoke an API key. | Keys are prefixed `aegis_` and passed as `Authorization: Bearer aegis_`. --- ## SSO — /api/v1/sso | Method | Path | Auth | Description | |--------|------|------|-------------| | GET | /sso/status | Any | Whether SSO is configured and enabled. | | PUT | /sso/config | A | Configure SAML 2.0 SSO settings. | | GET | /sso/login | Public | Initiate SAML login redirect. | | POST | /sso/callback | Public | SAML assertion callback. | | GET | /sso/metadata | Public | Service provider metadata XML. | --- ## Other Modules | Module | Path prefix | Auth | Notes | |--------|-------------|------|-------| | Notifications | /notifications | Any (own) | GET list, PATCH mark-read | | Audit Logs | /audit-logs | A | GET with filters | | Jira | /jira | RL,BL,A | sync, issues | | Threat Actors | /threat-actors | Any GET, RL,BL,A manage | | | Compliance | /compliance | Any | frameworks, controls, gaps | | Analytics | /analytics | Any | trend analytics | | Heatmap | /heatmap | Any | Coverage matrix | | Scores | /scores | Any GET; A for config PATCH | | | Worklogs | /worklogs | Any create; own or A to read | | | OSINT | /osint | RL,BL,A | | | Data Sources | /data-sources | Any GET; A manage | | | System | /system | A (scheduler, sync); Public (health) | | """ PAGE_AUTH_SECURITY = """\ # Authentication and Security This page covers the full authentication model, token management, API key usage, SSO configuration, and security controls in Aegis. --- ## Authentication Methods Aegis supports two authentication methods, checked in order: ### 1. HttpOnly Cookie (Primary — Browser Use) After a successful login, the server sets: ``` Set-Cookie: aegis_token=; HttpOnly; SameSite=Strict; Path=/; [Secure in prod] ``` The browser automatically sends this cookie on every request. **This is the recommended method for the frontend.** ### 2. Authorization: Bearer Header (API / Machine Use) ```http Authorization: Bearer ``` Use this for: - API integrations - Scripts and CI/CD pipelines - When cookies are not available (e.g. cross-origin API calls) --- ## JWT Token ### Structure The JWT payload contains: ```json { "sub": "user-uuid", "role": "red_lead", "jti": "unique-token-id", "exp": 1710000000, "iat": 1709996400 } ``` ### Expiry Configurable via environment variable: ``` ACCESS_TOKEN_EXPIRE_MINUTES=480 # 8 hours default ``` ### Token Revocation (Blacklist) On logout, the token's `jti` is stored in Redis with TTL = remaining token lifetime: ``` Redis key: blacklist:{jti} Value: "1" TTL: ``` Every authenticated request checks Redis for the `jti`. If found → 401 Unauthorized. --- ## Cookie Security | Setting | Development | Production | |---------|-------------|------------| | `HttpOnly` | True | True | | `SameSite` | Strict | Strict | | `Secure` | False | True | | `Path` | / | / | **`SECURE_COOKIES` environment variable:** - `auto` (default): `Secure=True` when `AEGIS_ENV=production` - `true`: Always set `Secure` flag - `false`: Never set `Secure` flag (development only) --- ## Password Policy - Minimum length: **12 characters** - Must contain: uppercase, lowercase, digit, and special character - Enforced at account creation and password change - Passwords are hashed with **bcrypt** (work factor 12) ### must_change_password When an admin creates a user, `must_change_password` is set to `True`. Until the user changes their password, **every endpoint** returns: ```json {"detail": "PASSWORD_CHANGE_REQUIRED"} ``` with status `403`, **except**: - `GET /api/v1/auth/me` - `POST /api/v1/auth/change-password` Change password: ```http POST /api/v1/auth/change-password Content-Type: application/json { "current_password": "admin-set-password", "new_password": "MyNewSecurePassword123!" } ``` --- ## API Keys API keys provide long-lived credentials for machine-to-machine access without session management. ### Creating a Key ```http POST /api/v1/api-keys Content-Type: application/json { "name": "CI Pipeline Key", "scope": "read", "expires_at": "2025-12-31T00:00:00Z" } ``` Response includes the key value (only shown once): ```json { "id": "uuid", "name": "CI Pipeline Key", "key": "aegis_a1b2c3d4e5f6...", "scope": "read" } ``` ### Using a Key ```http Authorization: Bearer aegis_a1b2c3d4e5f6... ``` ### Scopes | Scope | Allowed HTTP methods | |-------|---------------------| | `read` | GET, HEAD, OPTIONS only | | `write` | GET, POST, PATCH, PUT, DELETE | | `admin` | All + admin-only endpoints | A `read` scope key that attempts POST/PATCH/DELETE receives: ```json {"detail": "API key scope 'read' does not permit this operation"} ``` with status `403`. ### Revoking a Key ```http DELETE /api/v1/api-keys/{id} ``` --- ## SAML 2.0 SSO Aegis supports SAML 2.0 for enterprise single sign-on (e.g. Azure AD, Okta, Ping). ### Configuration (admin only) ```http PUT /api/v1/sso/config Content-Type: application/json { "enabled": true, "idp_entity_id": "https://idp.example.com", "idp_sso_url": "https://idp.example.com/sso/saml", "idp_certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----", "sp_entity_id": "https://aegis.example.com", "attribute_mapping": { "email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "role": "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups" } } ``` ### SSO Login Flow 1. Browser visits `GET /api/v1/sso/login` → redirect to IdP 2. User authenticates with IdP 3. IdP POSTs SAML assertion to `POST /api/v1/sso/callback` 4. Aegis validates assertion, finds/creates user, issues JWT cookie 5. Browser redirected to frontend ### Service Provider Metadata ```http GET /api/v1/sso/metadata ``` Returns XML metadata to register Aegis as a SAML service provider in your IdP. --- ## Rate Limiting - **Login endpoint**: 5 attempts per minute per IP - Exceeded: `429 Too Many Requests` - Implemented via Redis counters with TTL --- ## SSRF Protection (Webhooks) Webhook URLs are validated before saving. The following IP ranges are blocked: | Range | Description | |-------|-------------| | 10.0.0.0/8 | Private (Class A) | | 172.16.0.0/12 | Private (Class B) | | 192.168.0.0/16 | Private (Class C) | | 169.254.0.0/16 | Link-local / APIPA | | 127.0.0.0/8 | Loopback | | ::1/128 | IPv6 loopback | | fc00::/7 | IPv6 unique local | | 0.0.0.0/8 | Reserved | | 100.64.0.0/10 | Shared address space | Attempting to create a webhook pointing to a private IP returns: ```json {"detail": "Webhook URL points to a private or reserved IP address (SSRF prevention)"} ``` --- ## Audit Logging Every significant action is recorded in the `audit_logs` table (admin read-only): | Event type | Logged when | |------------|-------------| | `auth.login` | Successful login | | `auth.logout` | Logout | | `auth.login_failed` | Failed login attempt | | `auth.password_changed` | Password change | | `user.created` | New user created | | `user.updated` | User updated | | `user.deleted` | User deleted | | `test.created` | New test created | | `test.state_changed` | Test state transition | | `test.validated` | Both leads approved | | `campaign.completed` | Campaign completed | | `permission.denied` | 403 on a protected endpoint | Query audit logs: ```http GET /api/v1/audit-logs?user_id=&event_type=auth.login&from=2024-01-01&to=2024-12-31 ``` """ PAGE_CAMPAIGNS = """\ # Campaigns Campaigns group related tests under a common objective — for example, simulating a specific threat actor, testing a specific tactic, or running a quarterly purple team exercise. --- ## Campaign Types | Type | Description | |------|-------------| | `purple_team` | Collaborative red+blue exercise. Tests are executed with blue team full knowledge. | | `red_team` | Blind offensive assessment. Blue team may not know which techniques will be tested. | | `blue_team` | Detection-focused exercise. Blue team validates detection capabilities against known TTPs. | | `tabletop` | Scenario walkthrough. No live execution — tests are planned and reviewed conceptually. | --- ## Campaign Lifecycle ``` draft ──────────────> active ──────────────> completed │ │ │ │ POST /activate │ POST /complete │ │ (leads, admin) │ (red_lead, admin) │ │ │ │ └── at least one └── execution └── final state test required in progress (immutable) ``` ### Draft - Campaign is being planned - Tests can be added and removed - Metadata (name, type, objective, schedule) can be edited **Create a campaign:** ```http POST /api/v1/campaigns Content-Type: application/json { "name": "Q1 2024 Purple Team — APT29 Simulation", "description": "Full simulation of APT29 TTPs against corp environment", "campaign_type": "purple_team", "objective": "Measure detection coverage against Cozy Bear TTPs" } ``` **Add tests:** ```http POST /api/v1/campaigns/{id}/tests {"test_id": "test-uuid"} ``` **Set schedule:** ```http PATCH /api/v1/campaigns/{id}/schedule {"start_date": "2024-03-01T09:00:00Z", "end_date": "2024-03-15T18:00:00Z"} ``` ### Active - Triggered by POST /campaigns/{id}/activate (requires at least one linked test) - Tests within the campaign are executed following the normal [Test-Lifecycle](Test-Lifecycle) - Campaign-level progress is tracked in real time **Activate:** ```http POST /api/v1/campaigns/{id}/activate ``` ### Completed - Only red_lead or admin can complete a campaign - All remaining tests are frozen in their current state - Report generation becomes available for the full campaign **Complete:** ```http POST /api/v1/campaigns/{id}/complete {"completion_notes": "All priority techniques tested. 72% detection rate."} ``` --- ## Creating a Campaign from a Threat Actor Aegis maintains a database of known threat actor profiles with associated MITRE techniques. You can instantly scaffold a campaign for a threat actor: ```http POST /api/v1/campaigns/from-threat-actor/{actor_id} Content-Type: application/json { "campaign_name": "APT29 Full Simulation", "campaign_type": "purple_team", "include_sub_techniques": true } ``` This automatically: 1. Creates a new campaign 2. Creates one test draft per technique linked to the threat actor 3. Returns the campaign with all tests pre-populated --- ## Progress Tracking ```http GET /api/v1/campaigns/{id}/progress ``` Returns: ```json { "campaign_id": "uuid", "total_tests": 24, "validated": 18, "in_progress": 4, "not_started": 2, "detection_rate": 0.72, "progress_percent": 75.0 } ``` --- ## Campaign History Every state change in a campaign is logged: ```http GET /api/v1/campaigns/{id}/history ``` Returns list of: ```json { "timestamp": "2024-03-01T09:15:00Z", "from_state": "draft", "to_state": "active", "actor": "red_lead_user", "notes": null } ``` --- ## Reports for Campaigns Once a campaign is active or completed, a professional report can be generated: ```http GET /api/v1/reports/generate/purple-campaign/{id}?format=pdf ``` See [Executive-Dashboard-and-Reports](Executive-Dashboard-and-Reports) for all report options. """ PAGE_KNOWLEDGE = """\ # Knowledge Management Aegis includes a knowledge management module for capturing institutional expertise: **Playbooks** (procedural guides) and **Lessons Learned** (post-incident records). --- ## Playbooks A playbook is a step-by-step procedural guide for a specific MITRE ATT&CK technique. Each playbook is scoped to a `playbook_type` and a `technique_id`. ### Playbook Types | Type | Target audience | Purpose | |------|----------------|---------| | `attack` | Red team | How to execute this technique | | `defense` | Blue team | How to defend against this technique | | `detection` | Blue team | How to detect this technique in logs/alerts | ### Playbook Fields ```json { "title": "T1059.001 — PowerShell Attack Playbook", "technique_id": "T1059.001", "playbook_type": "attack", "content": "# Overview\n\nThis playbook covers...\n\n## Prerequisites\n...", "tools": ["Cobalt Strike", "PowerShell Empire", "Metasploit"], "prerequisites": ["Domain user credentials", "Access to workstation"], "is_active": true } ``` ### Versioning Every time a playbook is updated (PATCH), the system: 1. Creates a version snapshot with the previous content 2. Increments the version number 3. Records the author and timestamp **List versions:** ```http GET /api/v1/knowledge/playbooks/{id}/versions ``` **Restore a previous version:** ```http POST /api/v1/knowledge/playbooks/{id}/restore/{version_number} ``` This creates a new version from the restored content (non-destructive — the history is always preserved). ### Creating a Playbook ```http POST /api/v1/knowledge/playbooks Content-Type: application/json { "title": "T1078 — Valid Account Detection Playbook", "technique_id": "T1078", "playbook_type": "detection", "content": "## Indicators of Compromise\n\n1. Unusual login times...", "tools": ["Splunk", "Elastic SIEM"], "prerequisites": ["SIEM access", "AD event log forwarding configured"] } ``` ### Access Rules | Action | Required role | |--------|--------------| | Read any playbook | All roles (including viewer) | | Create playbook | red_lead, blue_lead, admin | | Update playbook | red_lead, blue_lead, admin | | Delete playbook | red_lead, blue_lead, admin | | Restore version | red_lead, blue_lead, admin | --- ## Lessons Learned Lessons Learned records capture what happened during an exercise, the root cause, and improvement actions. They can be linked to any entity in the system. ### Lesson Fields ```json { "title": "AMSI bypass succeeded due to outdated signatures", "what_happened": "Red team successfully bypassed AMSI on 3 of 5 endpoints...", "root_cause": "AMSI signature database had not been updated in 45 days.", "improvement": "Automate daily AMSI signature updates via WSUS. Set alert for stale updates.", "severity": "high", "tags": ["amsi", "evasion", "detection-gap"], "technique_ids": ["T1562.001"], "entity_type": "test", "entity_id": "test-uuid" } ``` ### Severity Levels | Level | Description | |-------|-------------| | `low` | Minor finding, low risk | | `medium` | Notable gap, moderate risk | | `high` | Significant detection failure | | `critical` | Systemic failure, immediate action required | ### Entity Linking Lessons can be linked to any entity: | `entity_type` | Example `entity_id` | |---------------|---------------------| | `test` | UUID of the test | | `campaign` | UUID of the campaign | | `technique` | MITRE technique ID (T1059.001) | | `detection_asset` | UUID of the detection asset | ### Access Rules | Action | Required role | |--------|--------------| | Read any lesson | All roles (including viewer) | | Create lesson | red_lead, blue_lead, admin | | Update lesson | red_lead, blue_lead, admin | | Delete lesson | red_lead, blue_lead, admin | --- ## Knowledge Stats ```http GET /api/v1/knowledge/stats ``` Returns: ```json { "playbooks": { "total": 87, "by_type": { "attack": 32, "defense": 28, "detection": 27 }, "techniques_covered": 61 }, "lessons": { "total": 43, "by_severity": { "critical": 3, "high": 12, "medium": 20, "low": 8 } } } ``` --- ## Best Practices 1. **Always link playbooks to tests**: When creating a test, reference the attack playbook in the test notes. 2. **Create lessons after every campaign**: Even successful tests yield lessons. Capture `what worked well`. 3. **Review playbooks quarterly**: Infrastructure changes may invalidate detection steps. 4. **Tag lessons**: Use consistent tags (`amsi`, `lateral-movement`, `cloud`) to make lessons searchable. 5. **One playbook per technique per type**: The system enforces uniqueness on `(technique_id, playbook_type)`. """ PAGE_ALERTS = """\ # Operational Alerts The operational alerts system monitors the state of your security coverage and notifies the team when conditions fall below defined thresholds. --- ## Alert Rules Alert rules define **what to check** and **when to fire**. Each rule has a type, severity, configuration thresholds, and notification preferences. ### Rule Types | Rule Type | What it checks | |-----------|---------------| | `coverage_drop` | Overall coverage score drops below a threshold | | `stale_test` | A test has been in `red_executing` or `blue_evaluating` for too long | | `unvalidated_test` | Tests stuck in `in_review` beyond a threshold duration | | `high_risk_uncovered` | High-severity techniques have no validated tests | | `detection_gap` | Technique has validated attack tests but no detection rule | ### Rule Fields ```json { "name": "Coverage below 70%", "description": "Alert when overall coverage drops below 70%", "rule_type": "coverage_drop", "severity": "high", "config": { "threshold": 70.0, "tactic_id": null }, "is_enabled": true, "cooldown_hours": 24, "notify_in_app": true, "notify_webhook": true, "webhook_id": "webhook-uuid-or-null" } ``` ### Severity Levels | Severity | Use case | |----------|---------| | `info` | Informational; no action needed immediately | | `low` | Worth noting but not urgent | | `medium` | Should be addressed in next sprint | | `high` | Requires prompt attention | | `critical` | Immediate action required | ### Rule Configuration Examples **Coverage drop:** ```json {"threshold": 75.0} ``` Fires when organization score drops below 75%. **Stale test:** ```json {"stale_days": 7} ``` Fires for any test in executing/evaluating state for more than 7 days. **High risk uncovered:** ```json {"min_severity": "high", "max_uncovered": 5} ``` Fires when more than 5 high-severity techniques have no validated test. **Detection gap:** ```json {"require_detection_rule": true} ``` Fires for every validated attack test that has no linked detection rule. --- ## Alert Instances When a rule's condition is met and the rule is not in cooldown, an alert instance is created. ### Instance Lifecycle ``` open ──────────────> acknowledged ──────────────> resolved │ │ └────────────────> dismissed │ │ │ └── suppressed until └── final state cooldown resets (immutable) ``` ### Instance Fields ```json { "id": "uuid", "rule_id": "uuid", "rule_name": "Coverage below 70%", "rule_type": "coverage_drop", "severity": "high", "status": "open", "details": {"current_score": 67.3, "threshold": 70.0}, "fired_at": "2024-03-15T10:00:00Z", "acknowledged_at": null, "acknowledged_by": null, "resolved_at": null, "dismissed_at": null } ``` --- ## Alert Lifecycle Actions ### Acknowledge Marks the alert as seen and being investigated. Does NOT suppress re-firing. ```http POST /api/v1/alerts/{id}/acknowledge {"notes": "Investigating coverage drop — two campaigns just completed"} ``` Required role: red_lead, blue_lead, admin ### Resolve Marks the underlying issue as fixed. Prevents re-evaluation from creating a duplicate alert (until cooldown expires and condition is met again). ```http POST /api/v1/alerts/{id}/resolve {"resolution_notes": "Coverage restored to 78% after campaign validation"} ``` Required role: red_lead, blue_lead, admin ### Dismiss Suppresses the alert for the rule's cooldown period. ```http POST /api/v1/alerts/{id}/dismiss {"reason": "Planned maintenance window — coverage drop expected"} ``` Required role: red_lead, blue_lead, admin --- ## Alert Evaluation ### Automatic (hourly) Aegis runs alert evaluation every hour via APScheduler: - Checks all `is_enabled=true` rules - For each rule, evaluates the condition against current data - Creates an instance if condition is met AND rule is not in cooldown - Sends in-app notifications and/or webhook calls per rule configuration ### Manual trigger ```http POST /api/v1/alerts/evaluate ``` Required role: red_lead, blue_lead, admin Useful when you've made changes and want to check immediately without waiting for the hourly job. --- ## In-App Notifications When `notify_in_app: true` on a rule, an in-app notification is sent to all users with role red_lead, blue_lead, or admin. View notifications: ```http GET /api/v1/notifications ``` Mark as read: ```http PATCH /api/v1/notifications/{id} {"is_read": true} ``` --- ## Webhook Notifications When `notify_webhook: true` and a `webhook_id` is set, Aegis POSTs to the configured webhook URL when the alert fires. Webhook payload: ```json { "event": "alert.fired", "alert_id": "uuid", "rule_name": "Coverage below 70%", "severity": "high", "details": {"current_score": 67.3, "threshold": 70.0}, "fired_at": "2024-03-15T10:00:00Z" } ``` --- ## Summary ```http GET /api/v1/alerts/summary ``` Returns: ```json { "total": 12, "by_status": {"open": 5, "acknowledged": 3, "resolved": 3, "dismissed": 1}, "by_severity": {"critical": 1, "high": 4, "medium": 5, "low": 2, "info": 0}, "by_type": { "coverage_drop": 2, "stale_test": 4, "unvalidated_test": 3, "high_risk_uncovered": 2, "detection_gap": 1 } } ``` """ PAGE_DASHBOARD_REPORTS = """\ # Executive Dashboard and Reports Aegis provides real-time dashboards, historical snapshots, and professional report generation for all stakeholders — from technical leads to C-suite executives. --- ## Dashboard Endpoints All dashboard endpoints require authentication but are accessible to all roles. ### KPIs ```http GET /api/v1/dashboard/kpis ``` Returns the core metrics at a glance: ```json { "total_techniques": 742, "covered_techniques": 387, "coverage_percent": 52.2, "validated_tests": 312, "active_campaigns": 3, "open_alerts": 7, "last_snapshot": "2024-03-15T08:00:00Z", "organization_score": 64.8 } ``` ### Executive Summary ```http GET /api/v1/dashboard/executive ``` Returns a narrative summary suitable for non-technical stakeholders: - Overall security posture statement - Coverage trend (improving/declining/stable) - Top 3 uncovered high-risk techniques - Recent key achievements (campaigns completed, techniques covered) - Open action items ### Coverage by Tactic ```http GET /api/v1/dashboard/coverage-by-tactic ``` Returns per-tactic breakdown: ```json [ { "tactic_id": "TA0002", "tactic_name": "Execution", "total_techniques": 13, "validated": 8, "partial": 3, "not_covered": 2, "coverage_percent": 84.6, "score": 76.2 }, ... ] ``` ### Posture History ```http GET /api/v1/dashboard/posture-history?days=90 ``` Time-series data for trend charts (default: last 90 days): ```json [ {"date": "2024-01-01", "score": 48.2, "covered_percent": 45.1}, {"date": "2024-02-01", "score": 52.7, "covered_percent": 48.8}, {"date": "2024-03-01", "score": 64.8, "covered_percent": 52.2} ] ``` ### Activity Feed ```http GET /api/v1/dashboard/activity?limit=20 ``` Recent actions across the platform: - Tests validated - Campaigns completed - New lessons learned added - Alerts fired - MITRE sync completed --- ## Snapshots Snapshots capture a complete point-in-time record of coverage metrics. They enable trend analysis and before/after comparison. ### Creating a Snapshot **Manual** (leads, admin): ```http POST /api/v1/snapshots {"notes": "Pre-campaign baseline — March 2024"} ``` **Automatic**: The system creates snapshots automatically: - After every campaign completion - After MITRE sync - On a scheduled basis (configurable) ### Listing Snapshots ```http GET /api/v1/snapshots?limit=10&offset=0 ``` ### Coverage Evolution (Trend) ```http GET /api/v1/snapshots/evolution?limit=50 ``` Returns time-ordered list of snapshots with key metrics — ideal for plotting a trend line of coverage improvement over time. ### Comparing Two Snapshots ```http GET /api/v1/snapshots/compare?a=&b= ``` Returns a diff showing: - Techniques newly covered since snapshot A - Techniques that lost coverage - Score delta - Test count delta ### Deleting a Snapshot Only admins can delete snapshots: ```http DELETE /api/v1/snapshots/{id} ``` --- ## Professional Report Generation Aegis can generate publication-ready reports in PDF, DOCX, or HTML format. These are available to: **admin, red_lead, blue_lead, and viewer**. ### Available Reports | Report | Endpoint | Description | |--------|----------|-------------| | Purple Team Campaign | GET /reports/generate/purple-campaign/{id} | Full campaign report with all tests | | Coverage Summary | GET /reports/generate/coverage-summary | Org-wide coverage status | | Executive Summary | GET /reports/generate/executive-summary | C-suite briefing, 2-3 pages | | Quarterly Summary | GET /reports/generate/quarterly-summary | Quarterly review with trends | | Technique Detail | GET /reports/generate/technique/{id} | Deep-dive on one technique | ### Format Selection Append `?format=pdf`, `?format=docx`, or `?format=html` (default: html): ```http GET /api/v1/reports/generate/executive-summary?format=pdf ``` Response headers: ``` Content-Type: application/pdf Content-Disposition: attachment; filename="executive-summary-2024-03-15.pdf" ``` ### Report Content — Purple Team Campaign Includes for each test in the campaign: - Test title, technique ID and name, objective - Execution timeline - Red team findings (tool, command, output) - Blue team detection result and response - Validation status - Evidence thumbnails - Remediation status Plus campaign-level summary: - Detection rate (detected / total tests) - Coverage improvement delta - Top findings and recommendations - Executive narrative ### Report Content — Executive Summary - Organization security posture score - Coverage percentage vs last quarter - Top 5 technique gaps by risk - Recent campaign outcomes - Key recommendations for next quarter - Glossary of terms --- ## Raw Data Exports | Endpoint | Format | Description | |----------|--------|-------------| | GET /reports/coverage-summary | JSON | Coverage status per technique | | GET /reports/coverage-csv | CSV download | Coverage matrix for Excel/BI tools | | GET /reports/test-results | JSON | All test results with outcomes | | GET /reports/remediation-status | JSON | Remediation tracking per technique | """ PAGE_DETECTION_LIFECYCLE = """\ # Detection Lifecycle The Detection Lifecycle module provides a structured system for the Blue Team to manage, validate, and track the health of detection capabilities mapped to MITRE ATT&CK techniques. --- ## Overview The detection lifecycle tracks three main concepts: 1. **Detection Assets** — What you use to detect (SIEM rules, EDR policies, sensors) 2. **Validations** — Proof that an asset correctly detected a technique 3. **Infrastructure Changes** — Events that may have broken your detections These three together provide a **confidence score** per technique: how certain are we that our detections for technique T still work today? --- ## Detection Assets A detection asset represents a defensive capability that can detect one or more techniques. ### Asset Types | Type | Examples | |------|---------| | `siem_rule` | Splunk SPL query, KQL rule in Sentinel | | `edr_policy` | CrowdStrike prevention policy, Defender ATP rule | | `sensor` | Network sensor, honeypot, canary token | | `ids_rule` | Snort/Suricata rule | | `manual_review` | Human review process | | `custom` | Any other detection mechanism | ### Creating an Asset ```http POST /api/v1/detection-lifecycle/assets Content-Type: application/json { "name": "Splunk: PowerShell AMSI Bypass Detection", "asset_type": "siem_rule", "description": "SPL search for AMSI bypass patterns in Windows PowerShell logs", "owner_id": "blue-lead-uuid", "team": "blue", "platform": "Windows", "rule_content": "index=wineventlog EventCode=4104 ScriptBlockText=*AmsiUtils*" } ``` ### Linking to Techniques ```http POST /api/v1/detection-lifecycle/assets/{id}/techniques/{technique_id} ``` One asset can be linked to multiple techniques. One technique can have multiple assets. --- ## Validations A validation record proves that an asset successfully detected a technique at a specific point in time. This is usually created after a successful test. ### Creating a Validation ```http POST /api/v1/detection-lifecycle/validations Content-Type: application/json { "asset_id": "asset-uuid", "technique_id": "T1059.001", "test_id": "test-uuid", "validated_at": "2024-03-15T10:30:00Z", "notes": "SIEM alert fired within 47 seconds of execution", "confidence": "high" } ``` ### Invalidating a Validation When a detection rule changes, is disabled, or the infrastructure shifts, you should invalidate the previous validation so the confidence score drops: ```http POST /api/v1/detection-lifecycle/validations/{id}/invalidate {"reason": "SIEM rule rewritten — new version not yet tested"} ``` ### Confidence Levels | Level | Meaning | |-------|---------| | `high` | Tested within 30 days, full detection | | `medium` | Tested within 90 days OR partial detection | | `low` | Tested more than 90 days ago | | `unknown` | No validation exists | --- ## Infrastructure Changes When infrastructure changes occur (SIEM migration, EDR update, log forwarding reconfigured), existing validations may no longer be accurate. Recording an infrastructure change automatically flags affected assets and adds techniques to the revalidation queue. ### Recording a Change ```http POST /api/v1/detection-lifecycle/infrastructure-changes Content-Type: application/json { "name": "Splunk → Microsoft Sentinel Migration", "description": "All SIEM rules migrated from Splunk to KQL in Sentinel", "change_type": "siem_migration", "affected_asset_ids": ["asset-uuid-1", "asset-uuid-2"], "occurred_at": "2024-03-01T00:00:00Z" } ``` **Effect**: All validations linked to affected assets are automatically marked as potentially stale. An alert rule of type `detection_gap` may fire. ### Revalidation Queue After an infrastructure change, use the ownership module to see which techniques need re-testing: ```http GET /api/v1/ownership/revalidation-queue ``` Generate an updated queue: ```http POST /api/v1/ownership/revalidation-queue/generate ``` --- ## Detection Dashboard ```http GET /api/v1/detection-lifecycle/dashboard ``` Returns per-technique detection confidence: ```json [ { "technique_id": "T1059.001", "technique_name": "PowerShell", "asset_count": 2, "latest_validation": "2024-03-15T10:30:00Z", "confidence": "high", "days_since_validation": 5, "pending_revalidation": false }, { "technique_id": "T1078", "technique_name": "Valid Accounts", "asset_count": 1, "latest_validation": "2023-11-10T08:00:00Z", "confidence": "low", "days_since_validation": 125, "pending_revalidation": true } ] ``` --- ## Detection Rules Detection rules are the concrete SIEM/EDR rule definitions linked to Aegis tests. They complement detection assets by providing the exact rule logic. ### Creating a Rule ```http POST /api/v1/detection-rules Content-Type: application/json { "name": "PowerShell Encoded Command Execution", "technique_ids": ["T1059.001"], "platform": "Windows", "rule_type": "siem", "rule_content": "EventCode=4104 ScriptBlockText=*-EncodedCommand*", "description": "Detects PowerShell execution with base64 encoded commands", "severity": "high", "is_enabled": true } ``` ### Evaluating a Rule Against a Test After running a test, record whether the rule fired: ```http POST /api/v1/detection-rules/evaluate { "rule_id": "rule-uuid", "test_id": "test-uuid", "result": "triggered", "time_to_detect_seconds": 23, "notes": "Rule fired on first PowerShell invocation" } ``` `result` options: `triggered`, `not_triggered`, `false_positive` """ PAGE_DEPLOYMENT = """\ # Deployment Guide This guide covers deploying Aegis in both development and production environments. --- ## Prerequisites | Requirement | Version | Notes | |-------------|---------|-------| | Docker | 24+ | Required | | Docker Compose | v2 (plugin) | `docker compose` (not `docker-compose`) | | Git | Any | For pulling updates | | 4GB RAM | Minimum | 8GB recommended for production | | 20GB disk | Minimum | For database + evidence files | --- ## Development Setup ### 1. Clone and configure ```bash git clone https://git.undiamagico.es/kitos/Aegis cd Aegis cp .env.example .env # Edit .env with your settings ``` ### 2. Start services ```bash docker compose up -d ``` This starts all services: - Backend API: http://localhost:8000 - Frontend: http://localhost:5173 - Swagger UI: http://localhost:8000/docs - ReDoc: http://localhost:8000/redoc - MinIO Console: http://localhost:9001 - PostgreSQL: localhost:5433 ### 3. Verify ```bash curl http://localhost:8000/health # {"status": "ok"} ``` ### 4. First login Default admin credentials are set via `ADMIN_USERNAME` and `ADMIN_PASSWORD` in `.env`. ```bash curl -X POST http://localhost:8000/api/v1/auth/login \\ -d "username=admin&password=yourpassword" \\ -c cookies.txt ``` --- ## Production Deployment ### 1. Prepare server ```bash # On the server mkdir -p /opt/aegis cd /opt/aegis git clone https://git.undiamagico.es/kitos/Aegis . cp .env.example .env # Edit .env — see Required Environment Variables section ``` ### 2. Deploy ```bash docker compose -f docker-compose.prod.yml up -d --build ``` ### 3. Verify ```bash docker compose -f docker-compose.prod.yml ps # All services should show "healthy" or "running" curl http://localhost:8000/health # {"status": "ok"} ``` ### 4. Update ```bash git pull docker compose -f docker-compose.prod.yml up -d --build ``` --- ## Required Environment Variables Create a `.env` file at the project root: ```env # ─── Database ───────────────────────────────────────────── DATABASE_URL=postgresql+asyncpg://aegis:strongpassword@aegis-postgres:5432/aegis # ─── Security ───────────────────────────────────────────── SECRET_KEY=your-256-bit-random-secret-key-here ACCESS_TOKEN_EXPIRE_MINUTES=480 # ─── Application ───────────────────────────────────────── AEGIS_ENV=production # or "development" SECURE_COOKIES=auto # auto | true | false # ─── First-run Admin ────────────────────────────────────── ADMIN_USERNAME=admin ADMIN_PASSWORD=ChangeMe123! # Must meet password policy # ─── MinIO (Object Storage) ─────────────────────────────── MINIO_ENDPOINT=aegis-minio:9000 MINIO_ACCESS_KEY=minioadmin MINIO_SECRET_KEY=minio-secret-key MINIO_BUCKET=aegis-evidence MINIO_USE_SSL=false # true if MinIO behind HTTPS # ─── Redis ──────────────────────────────────────────────── REDIS_URL=redis://aegis-redis:6379/0 # ─── Optional: Jira Integration ─────────────────────────── # JIRA_URL=https://yourorg.atlassian.net # JIRA_USERNAME=service-account@yourorg.com # JIRA_API_TOKEN=your-jira-api-token # JIRA_PROJECT_KEY=SEC # ─── Optional: CORS ─────────────────────────────────────── # CORS_ORIGINS=https://aegis.yourcompany.com ``` ### Generating SECRET_KEY ```bash python3 -c "import secrets; print(secrets.token_hex(32))" ``` --- ## Database Migrations Migrations are managed with **Alembic** and run automatically on container start in production via `entrypoint.prod.sh`: ```bash #!/bin/bash # entrypoint.prod.sh alembic upgrade head uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 ``` **Manual migration** (if needed): ```bash docker compose exec aegis-backend alembic upgrade head ``` **Create a new migration** (development): ```bash docker compose exec aegis-backend alembic revision --autogenerate -m "add_new_field" ``` --- ## Port Reference | Service | Development port | Production notes | |---------|-----------------|------------------| | Backend API | 8000 | Typically proxied via nginx/Traefik | | Frontend | 5173 | Served as static files via nginx in prod | | PostgreSQL | 5433 (host) | Not exposed in production | | MinIO API | 9000 | Not exposed in production | | MinIO Console | 9001 | Optional admin access | | Redis | 6379 | Not exposed in production | --- ## HTTPS / Reverse Proxy In production, place a reverse proxy (nginx, Traefik, Caddy) in front of Aegis: **nginx example:** ```nginx server { listen 443 ssl; server_name aegis.yourcompany.com; ssl_certificate /etc/ssl/certs/aegis.crt; ssl_certificate_key /etc/ssl/private/aegis.key; location /api/ { proxy_pass http://localhost:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto https; } location / { proxy_pass http://localhost:5173; proxy_set_header Host $host; } } ``` --- ## Running Tests ```bash # Full test suite cd backend && pytest tests/ -v # Specific module pytest tests/test_auth.py -v # With coverage report pytest tests/ --cov=app --cov-report=html # Fast (no DB — unit tests only) pytest tests/ -v -m "not integration" ``` The test suite has **367+ tests** covering all major modules. --- ## Linting ```bash # Check ruff check . # Fix auto-fixable issues ruff check . --fix # Format ruff format . ``` --- ## Logs ```bash # Follow backend logs docker compose logs -f aegis-backend # All services docker compose logs -f # Last 100 lines docker compose logs --tail=100 aegis-backend ``` --- ## Backup ### Database ```bash # Dump docker compose exec aegis-postgres pg_dump -U aegis aegis > backup_$(date +%Y%m%d).sql # Restore docker compose exec -T aegis-postgres psql -U aegis aegis < backup_20240315.sql ``` ### Evidence files (MinIO) ```bash # Using mc (MinIO client) mc mirror myminio/aegis-evidence ./evidence-backup/ ``` --- ## Troubleshooting | Issue | Solution | |-------|---------| | Backend fails to start | Check `SECRET_KEY` is set in production | | 401 on all requests | Check `SECURE_COOKIES` — may be forcing HTTPS on HTTP connection | | MinIO errors | Verify `MINIO_BUCKET` exists; check credentials | | Migration fails | Check `DATABASE_URL` is correct and DB is reachable | | Redis connection refused | Verify `REDIS_URL` and that redis container is running | | Swagger not loading | Expected in production (`AEGIS_ENV=production` disables it) | """ PAGE_QA_GUIDE = """\ # QA Testing Guide This page documents the automated QA runner and manual testing checklist for Aegis. The QA suite validates access control, test lifecycle, API key scoping, and security controls. --- ## Automated QA Runner The automated QA runner is located at `scripts/qa_runner.py`. ### What it Tests The runner executes **77 automated tests** organized as follows: 1. **Setup** — Creates test users for all 6 roles (admin, red_lead, blue_lead, red_tech, blue_tech, viewer). Creates a test technique. 2. **Authentication tests** - Login for each role - Verify `/auth/me` returns correct role - Verify `must_change_password` blocks all endpoints - Verify logout revokes token (subsequent request returns 401) 3. **Full test lifecycle** (happy path) - Create test as red_lead → draft - Start execution as red_tech → red_executing - Verify blue_tech cannot submit-red (403) - Submit red as red_tech → blue_evaluating - Verify red_tech cannot update blue fields (403) - Update blue fields as blue_tech - Submit blue as blue_tech → in_review - Validate red as red_lead → red approved - Validate blue as blue_lead → both approved → validated - Verify technique coverage updated 4. **Access control denials (403 checks)** For each protected endpoint, verify forbidden roles receive 403: - viewer cannot POST /tests - viewer cannot POST /tests/{id}/start-execution - blue_tech cannot PATCH /tests/{id}/red - red_tech cannot PATCH /tests/{id}/blue - blue_lead cannot POST /tests/{id}/complete (campaign) - red_tech cannot GET /audit-logs - non-admin cannot POST /webhooks - non-admin cannot DELETE /snapshots/{id} - non-admin cannot POST /system/sync-mitre 5. **API Key scope enforcement** - Create read-scope key as red_lead - Verify GET /tests returns 200 with read key - Verify POST /tests returns 403 with read key - Verify PATCH /tests/{id} returns 403 with read key - Create write-scope key - Verify POST /tests returns 201 with write key 6. **SSRF webhook protection** - Attempt to create webhook pointing to 192.168.1.1 → expect 400 (SSRF blocked) - Attempt to create webhook pointing to 127.0.0.1 → expect 400 - Attempt to create webhook pointing to 10.0.0.1 → expect 400 - Attempt with valid public URL → expect 201 7. **Campaign lifecycle** - Create campaign as red_lead - Add tests to campaign - Activate campaign - Verify viewer can read campaign progress - Verify blue_lead cannot complete campaign (403) - Complete campaign as red_lead 8. **Cleanup** - Delete all created tests - Delete all created campaigns - Delete all created users - Verify cleanup success ### Running the QA Suite ```bash # Against local dev python scripts/qa_runner.py --base-url http://localhost:8000 # Against production (use with caution) python scripts/qa_runner.py --base-url https://aegis.yourcompany.com # Verbose mode (shows each HTTP request) python scripts/qa_runner.py --base-url http://localhost:8000 --verbose # Run specific phase only python scripts/qa_runner.py --base-url http://localhost:8000 --phase lifecycle ``` ### Exit Codes | Code | Meaning | |------|---------| | 0 | All tests passed | | 1 | One or more tests failed | | 2 | Setup failed (cannot run further tests) | ### Sample Output ``` Creating Aegis QA Test Run... [SETUP] Creating test users... [OK] Created admin user: qa_admin_20240315 [OK] Created red_lead user: qa_rl_20240315 [OK] Created blue_lead user: qa_bl_20240315 [OK] Created red_tech user: qa_rt_20240315 [OK] Created blue_tech user: qa_bt_20240315 [OK] Created viewer user: qa_v_20240315 [PHASE 1] Authentication... [OK] Login as admin [OK] Login as red_lead ... [PHASE 2] Test Lifecycle... [OK] Create test (red_lead) [OK] Start execution (red_tech) [FAIL] 403 for viewer trying start-execution — expected 403, got 403 ✓ ... Results: 77/77 passed ``` --- ## Manual QA Checklist ### admin role - [ ] Login succeeds - [ ] Change password on first login - [ ] Can list all users (`GET /users`) - [ ] Can create a new user (POST /users) - [ ] Can delete a user (DELETE /users/{id}) - [ ] Can access audit logs (`GET /audit-logs`) - [ ] Can trigger MITRE sync (POST /system/sync-mitre) - [ ] Can create a webhook (POST /webhooks) - [ ] Can view scheduler status (`GET /system/scheduler-status`) - [ ] Can delete a snapshot (DELETE /snapshots/{id}) - [ ] Can set data classification on a test (PATCH /tests/{id}/classification) - [ ] Can complete a campaign (POST /campaigns/{id}/complete) - [ ] Can validate both red and blue sides of a test ### red_lead role - [ ] Login succeeds - [ ] Can create a test (POST /tests) - [ ] Can start test execution - [ ] Can validate red side of test in `in_review` - [ ] Can create a campaign and add tests - [ ] Can complete a campaign - [ ] Can create a playbook - [ ] Can create a lesson learned - [ ] Cannot access audit logs → 403 - [ ] Cannot access webhooks → 403 - [ ] Cannot validate blue side → 403 - [ ] Cannot delete snapshots → 403 - [ ] Can generate a PDF report ### blue_lead role - [ ] Login succeeds - [ ] Can validate blue side of test in `in_review` - [ ] Can create detection assets and link to techniques - [ ] Can create detection validations - [ ] Can generate revalidation queue - [ ] Cannot start test execution → 403 - [ ] Cannot complete a campaign → 403 - [ ] Cannot validate red side → 403 - [ ] Cannot access webhooks → 403 - [ ] Can generate a PDF report ### red_tech role - [ ] Login succeeds - [ ] Can start test execution (test must be in draft) - [ ] Can update red fields (PATCH /tests/{id}/red) in red_executing state - [ ] Can upload red evidence - [ ] Can submit red (POST /tests/{id}/submit-red) - [ ] Cannot create a test → 403 - [ ] Cannot update blue fields → 403 - [ ] Cannot validate either side → 403 - [ ] Cannot generate reports → 403 - [ ] Can pause/resume timer ### blue_tech role - [ ] Login succeeds - [ ] Can update blue fields (PATCH /tests/{id}/blue) in blue_evaluating state - [ ] Can upload blue evidence (only in blue_evaluating state) - [ ] Can submit blue (POST /tests/{id}/submit-blue) - [ ] Can evaluate detection rules (POST /detection-rules/evaluate) - [ ] Cannot start execution → 403 - [ ] Cannot submit red → 403 - [ ] Cannot validate either side → 403 - [ ] Cannot create tests → 403 - [ ] Can pause/resume timer ### viewer role - [ ] Login succeeds - [ ] Can read tests, campaigns, techniques, coverage, heatmap, dashboard - [ ] Can generate PDF/DOCX/HTML reports - [ ] Cannot create a test → 403 - [ ] Cannot start execution → 403 - [ ] Cannot update any test fields → 403 - [ ] Cannot upload evidence → 403 - [ ] Cannot validate → 403 - [ ] Cannot create campaigns → 403 - [ ] Cannot create playbooks or lessons → 403 - [ ] Cannot pause/resume timer → 403 --- ## Security QA Checklist ### SSRF Protection - [ ] POST /webhooks with URL `http://192.168.1.1/evil` → 400 Bad Request - [ ] POST /webhooks with URL `http://10.0.0.1/evil` → 400 Bad Request - [ ] POST /webhooks with URL `http://127.0.0.1/evil` → 400 Bad Request - [ ] POST /webhooks with URL `http://169.254.169.254/latest/meta-data` → 400 - [ ] POST /webhooks with URL `https://hooks.slack.com/services/valid` → 201 OK ### API Key Scopes - [ ] Read-scope key: GET requests succeed (200) - [ ] Read-scope key: POST requests fail (403) - [ ] Read-scope key: PATCH requests fail (403) - [ ] Read-scope key: DELETE requests fail (403) - [ ] Write-scope key: POST requests succeed (201) - [ ] Admin-scope key: admin-only endpoints succeed ### Token Blacklisting - [ ] Logout request succeeds - [ ] Using old token after logout returns 401 - [ ] New login after logout works correctly ### Rate Limiting - [ ] 6 rapid failed login attempts → 429 Too Many Requests on 6th > Note: This test cannot be run from loopback (127.0.0.1) if the rate limiter > is configured to skip localhost. Run from an external IP in production testing. --- ## Known Test Limitations | Limitation | Reason | Workaround | |------------|--------|------------| | Rate limit not testable from loopback | Redis rate limiter may skip 127.0.0.1 | Test from external network | | SSRF blocks may not apply to all DNS names | DNS resolves at request time, not save time | Use IP addresses in SSRF tests | | PDF generation requires wkhtmltopdf/weasyprint | Must be installed in container | Check container logs if PDF tests fail | | SAML SSO testing requires an IdP | Real IdP needed for full SSO test | Use mock IdP (e.g. samltest.id) | | Evidence download requires MinIO | MinIO must be running and bucket configured | Use dev docker-compose | | Cleanup may fail if tests are in validated state | Validated tests may be immutable | Admin override required for cleanup | --- ## Phase-by-Phase QA Scripts In addition to `qa_runner.py`, the `scripts/` directory contains phase-specific scripts for testing individual feature areas: | Script | Phase covered | |--------|--------------| | `qa_phases_6_7.py` | Attack paths, knowledge management | | `qa_phase8.py` | Ownership, risk, analytics | | `qa_phase9.py` | Alerts, detection lifecycle | | `qa_phase10.py` | Reports, snapshots, heatmap | | `qa_phase11.py` | Threat actors, compliance | | `qa_phase12.py` | SSO, API keys, webhooks | | `qa_phase13.py` | Operational alerts (Phase 13 gaps) | | `qa_phase14.py` | Final integration tests | | `qa_full.py` | Full end-to-end suite | Run any of these directly: ```bash python scripts/qa_phase13.py --base-url http://localhost:8000 ``` """ # ───────────────────────────────────────────── # PAGES DICT # ───────────────────────────────────────────── PAGES = { "Home": PAGE_HOME, "Architecture": PAGE_ARCHITECTURE, "Roles-and-Permissions": PAGE_ROLES, "Test-Lifecycle": PAGE_TEST_LIFECYCLE, "MITRE-ATT-CK-Coverage": PAGE_MITRE, "API-Reference": PAGE_API_REF, "Authentication-and-Security": PAGE_AUTH_SECURITY, "Campaigns": PAGE_CAMPAIGNS, "Knowledge-Management": PAGE_KNOWLEDGE, "Operational-Alerts": PAGE_ALERTS, "Executive-Dashboard-and-Reports": PAGE_DASHBOARD_REPORTS, "Detection-Lifecycle": PAGE_DETECTION_LIFECYCLE, "Deployment-Guide": PAGE_DEPLOYMENT, "QA-Testing-Guide": PAGE_QA_GUIDE, } def main(): print("Creating Aegis Wiki...") print(f"Target: {GITEA_URL}/api/v1/repos/{OWNER}/{REPO}/wiki/pages") print(f"Pages to create: {len(PAGES)}\n") results = {} for title, content in PAGES.items(): line_count = content.count("\n") print(f" Processing: {title} (~{line_count} lines)") ok = create_or_update_page(title, content) results[title] = ok time.sleep(0.5) # Be gentle with the API passed = sum(results.values()) total = len(results) print(f"\nDone: {passed}/{total} pages created successfully") if passed < total: print("\nFailed pages:") for t, ok in results.items(): if not ok: print(f" FAILED: {t}") sys.exit(1) else: print("\nAll wiki pages created successfully!") print(f"View at: {GITEA_URL}/{OWNER}/{REPO}/wiki") if __name__ == "__main__": main()