feat: production deployment setup and hardcoded URL fixes

- Fix hardcoded localhost:8000 URLs in frontend to use relative /api/v1
  path (works with Nginx proxy in prod and VITE_API_URL in dev)
- Create production entrypoint (entrypoint.prod.sh) that runs migrations,
  seeds, and starts uvicorn with 4 workers (no --reload)
- Create comprehensive install.sh script for production deployment that
  generates secure .env, builds containers, waits for health, and
  optionally triggers initial MITRE sync
- Update docker-compose.prod.yml to use production entrypoint
- Update Dockerfile to make both entrypoints executable
- Remove init.ps1 (production will always be Linux)
- Update README with production deployment instructions
This commit is contained in:
2026-02-10 16:04:16 +01:00
parent a3f83c316a
commit 8aec3581a0
9 changed files with 310 additions and 168 deletions

View File

@@ -95,38 +95,40 @@ Both Red Lead and Blue Lead must independently vote:
- Docker and Docker Compose - Docker and Docker Compose
- Git - Git
- Linux / macOS (or WSL on Windows)
### Installation ### Production Deployment
The recommended way to deploy Aegis in production:
1. Clone the repository:
```bash ```bash
git clone <repository-url> git clone <repository-url>
cd Aegis cd Aegis
chmod +x scripts/install.sh
./scripts/install.sh
``` ```
2. Start all services: The install script will automatically:
- Generate a `.env` file with secure random secrets
- Build and start all containers (PostgreSQL, MinIO, Backend, Frontend)
- Run database migrations
- Seed the admin user and data sources
- Optionally run the initial MITRE ATT&CK sync
Access the application at **http://your-server:80**.
### Development Setup
For local development with hot-reload:
```bash ```bash
git clone <repository-url>
cd Aegis
docker-compose up -d docker-compose up -d
./scripts/init.sh
``` ```
3. Run database migrations: Access at **http://localhost:5173** (frontend dev server) and **http://localhost:8000/docs** (API docs).
```bash
docker exec aegis-backend alembic upgrade head
```
4. Seed the admin user:
```bash
docker exec aegis-backend python -m app.seed
```
5. Access the application:
```bash
# API health check
curl http://localhost:8000/health
# Expected: {"status":"ok"}
# Open http://localhost:5173 — Aegis login page
```
### Authentication ### Authentication
@@ -137,27 +139,32 @@ Username: admin
Password: admin123 Password: admin123
``` ```
> **Important:** Change the default `admin123` password and `SECRET_KEY` in production. > **Important:** Change the default `admin123` password immediately after first login.
### Importing Data Sources ### Importing Data Sources
After initial setup, the entrypoint script automatically seeds the initial data sources (Atomic Red Team, SigmaHQ, CALDERA, LOLBAS, GTFOBins, D3FEND). You can then sync each source from the UI: On startup, the backend automatically seeds the initial data sources (Atomic Red Team, SigmaHQ, CALDERA, LOLBAS, GTFOBins, D3FEND). You can then sync each source from the UI:
1. Navigate to **System > Data Sources** in the admin panel 1. Navigate to **Data Sources** in the sidebar
2. Click **Sync** on each data source to import its content 2. Click **Sync** on each data source to import its content
3. Trigger a **MITRE ATT&CK Sync** from the **System > MITRE Sync** page 3. Trigger a **MITRE ATT&CK Sync** from the **System** page
Alternatively, use the API: Alternatively, use the API:
```bash ```bash
# Sync MITRE ATT&CK techniques # Sync MITRE ATT&CK techniques
curl -X POST http://localhost:8000/api/v1/system/sync-mitre -H "Authorization: Bearer $TOKEN" curl -X POST http://your-server/api/v1/system/sync-mitre -H "Authorization: Bearer $TOKEN"
# Sync all data sources at once # Sync all data sources at once
curl -X POST http://localhost:8000/api/v1/data-sources/sync-all -H "Authorization: Bearer $TOKEN" curl -X POST http://your-server/api/v1/data-sources/sync-all -H "Authorization: Bearer $TOKEN"
``` ```
See [docs/DATA_SOURCES.md](docs/DATA_SOURCES.md) for detailed instructions on all data sources. ### Production Considerations
- **HTTPS/TLS:** For internet-facing deployments, place a reverse proxy with TLS in front (e.g., Traefik, Caddy, or Nginx with Let's Encrypt).
- **Backups:** Set up regular PostgreSQL backups: `docker exec aegis-postgres pg_dump -U postgres attackdb > backup.sql`
- **Updates:** To update, pull the latest code and run: `docker compose -f docker-compose.prod.yml up -d --build`
- **Firewall:** Only expose port 80/443. All other services (DB, MinIO, backend) are internal only.
### Configuring Scoring Weights ### Configuring Scoring Weights

View File

@@ -16,8 +16,8 @@ RUN pip install --no-cache-dir -r requirements.txt
# Copy application code # Copy application code
COPY . . COPY . .
# Make entrypoint executable # Make entrypoints executable
RUN chmod +x /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh /app/entrypoint.prod.sh
# Expose port # Expose port
EXPOSE 8000 EXPOSE 8000

View File

@@ -0,0 +1,14 @@
#!/bin/sh
set -e
echo "=== [PROD] Running Alembic migrations ==="
alembic upgrade head
echo "=== [PROD] Seeding admin user ==="
python -m app.seed
echo "=== [PROD] Seeding data sources ==="
python -m app.seed_data_sources
echo "=== [PROD] Starting uvicorn (production) ==="
exec uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4

View File

@@ -68,7 +68,7 @@ services:
condition: service_healthy condition: service_healthy
minio: minio:
condition: service_started condition: service_started
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 command: sh /app/entrypoint.prod.sh
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"] test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 10s interval: 10s

View File

@@ -1,7 +1,9 @@
import axios, { type AxiosError } from "axios"; import axios, { type AxiosError } from "axios";
const API_BASE_URL = import.meta.env.VITE_API_URL || "/api/v1";
const client = axios.create({ const client = axios.create({
baseURL: "http://localhost:8000/api/v1", baseURL: API_BASE_URL,
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
}); });

View File

@@ -19,7 +19,8 @@ export default function TestCreatePage() {
queryFn: async () => { queryFn: async () => {
// We need to find the mitre_id from the technique list // We need to find the mitre_id from the technique list
// This is a workaround since we get UUID but need mitre_id // This is a workaround since we get UUID but need mitre_id
const response = await fetch(`http://localhost:8000/api/v1/techniques`, { const apiBase = import.meta.env.VITE_API_URL || "/api/v1";
const response = await fetch(`${apiBase}/techniques`, {
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`, Authorization: `Bearer ${localStorage.getItem("token")}`,
}, },

View File

@@ -1,132 +0,0 @@
# =============================================================================
# Aegis Initialization Script (PowerShell)
# =============================================================================
# This script initializes the Aegis platform after starting Docker containers.
#
# Usage:
# .\scripts\init.ps1
# =============================================================================
$ErrorActionPreference = "Stop"
Write-Host "╔═══════════════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ Aegis - Platform Initialization ║" -ForegroundColor Cyan
Write-Host "╚═══════════════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""
function Write-Status {
param([string]$Message)
Write-Host "[✓] $Message" -ForegroundColor Green
}
function Write-Warning {
param([string]$Message)
Write-Host "[!] $Message" -ForegroundColor Yellow
}
function Write-Error {
param([string]$Message)
Write-Host "[✗] $Message" -ForegroundColor Red
}
# Check if Docker is running
try {
docker info | Out-Null
} catch {
Write-Error "Docker is not running. Please start Docker first."
exit 1
}
# Check if containers are running
$backendRunning = docker-compose ps | Select-String "aegis-backend"
if (-not $backendRunning) {
Write-Warning "Containers not running. Starting them now..."
docker-compose up -d
Write-Host ""
Write-Host "Waiting for services to be healthy..."
Start-Sleep -Seconds 10
}
# Wait for backend to be ready
Write-Host "Waiting for backend to be ready..."
$maxRetries = 30
$retryCount = 0
$ready = $false
while (-not $ready -and $retryCount -lt $maxRetries) {
try {
$response = Invoke-WebRequest -Uri "http://localhost:8000/health" -UseBasicParsing -TimeoutSec 2
if ($response.StatusCode -eq 200) {
$ready = $true
}
} catch {
$retryCount++
Write-Host " Waiting... ($retryCount/$maxRetries)"
Start-Sleep -Seconds 2
}
}
if (-not $ready) {
Write-Error "Backend failed to start after $maxRetries attempts"
exit 1
}
Write-Status "Backend is healthy"
# Run database migrations
Write-Host ""
Write-Host "Running database migrations..."
docker-compose exec -T backend alembic upgrade head
Write-Status "Migrations completed"
# Seed admin user
Write-Host ""
Write-Host "Seeding admin user..."
try {
docker-compose exec -T backend python -m app.seed 2>$null
Write-Status "Admin user ready"
} catch {
Write-Warning "Admin user may already exist"
}
# Ask about MITRE sync
Write-Host ""
$runSync = Read-Host "Do you want to run initial MITRE ATT&CK sync? (y/N)"
if ($runSync -eq "y" -or $runSync -eq "Y") {
Write-Host "Triggering MITRE sync (this may take a minute)..."
try {
# Get admin token
$loginBody = "username=admin&password=admin123"
$loginResponse = Invoke-RestMethod -Uri "http://localhost:8000/api/v1/auth/login" `
-Method Post -Body $loginBody -ContentType "application/x-www-form-urlencoded"
$token = $loginResponse.access_token
if ($token) {
$headers = @{ "Authorization" = "Bearer $token" }
Invoke-RestMethod -Uri "http://localhost:8000/api/v1/system/sync-mitre" `
-Method Post -Headers $headers | Out-Null
Write-Status "MITRE sync triggered"
}
} catch {
Write-Warning "Could not authenticate. Run sync manually from the System page."
}
}
# Print summary
Write-Host ""
Write-Host "╔═══════════════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
Write-Host "║ Aegis is ready! ║" -ForegroundColor Cyan
Write-Host "╠═══════════════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
Write-Host "║ ║" -ForegroundColor Cyan
Write-Host "║ Frontend: http://localhost:5173 ║" -ForegroundColor White
Write-Host "║ Backend API: http://localhost:8000 ║" -ForegroundColor White
Write-Host "║ Swagger UI: http://localhost:8000/docs ║" -ForegroundColor White
Write-Host "║ MinIO Console: http://localhost:9001 ║" -ForegroundColor White
Write-Host "║ ║" -ForegroundColor Cyan
Write-Host "║ Default login: admin / admin123 ║" -ForegroundColor Yellow
Write-Host "║ ║" -ForegroundColor Cyan
Write-Host "║ ⚠️ Change the default password in production! ║" -ForegroundColor Yellow
Write-Host "║ ║" -ForegroundColor Cyan
Write-Host "╚═══════════════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
Write-Host ""

View File

@@ -1,9 +1,10 @@
#!/bin/bash #!/bin/bash
# ============================================================================= # =============================================================================
# Aegis Initialization Script # Aegis Development Initialization Script
# ============================================================================= # =============================================================================
# This script initializes the Aegis platform after starting Docker containers. # This script initializes the Aegis platform for local development.
# # For production, use: ./scripts/install.sh
#
# Usage: # Usage:
# ./scripts/init.sh # ./scripts/init.sh
# ============================================================================= # =============================================================================

249
scripts/install.sh Normal file
View File

@@ -0,0 +1,249 @@
#!/bin/bash
# =============================================================================
# Aegis - Production Installation Script
# =============================================================================
# This script sets up the Aegis platform for production.
#
# Usage:
# chmod +x scripts/install.sh
# ./scripts/install.sh
#
# Prerequisites:
# - Docker and Docker Compose installed
# - Port 80 (or FRONTEND_PORT) available
# =============================================================================
set -e
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
print_ok() { echo -e "${GREEN}[✓]${NC} $1"; }
print_warn() { echo -e "${YELLOW}[!]${NC} $1"; }
print_error() { echo -e "${RED}[✗]${NC} $1"; }
print_info() { echo -e "${CYAN}[i]${NC} $1"; }
print_header() { echo -e "\n${BOLD}── $1 ──${NC}"; }
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ Aegis - Production Installation ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
# ── 1. Check prerequisites ──────────────────────────────────────────
print_header "Checking prerequisites"
if ! command -v docker &> /dev/null; then
print_error "Docker is not installed. Please install Docker first."
echo " -> https://docs.docker.com/engine/install/"
exit 1
fi
print_ok "Docker found: $(docker --version | head -1)"
if ! docker info > /dev/null 2>&1; then
print_error "Docker daemon is not running. Please start Docker."
exit 1
fi
print_ok "Docker daemon is running"
# Check for docker compose (v2 plugin or standalone)
if docker compose version > /dev/null 2>&1; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
else
print_error "Docker Compose is not installed."
echo " -> https://docs.docker.com/compose/install/"
exit 1
fi
print_ok "Docker Compose found ($COMPOSE_CMD)"
# ── 2. Setup .env file ──────────────────────────────────────────────
print_header "Environment configuration"
ENV_FILE=".env"
if [ -f "$ENV_FILE" ]; then
print_warn ".env file already exists"
read -p " Overwrite with new values? (y/N) " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_info "Keeping existing .env file"
SKIP_ENV=true
fi
fi
if [ "${SKIP_ENV}" != "true" ]; then
# Generate secure secrets
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))" 2>/dev/null || openssl rand -hex 32 2>/dev/null || head -c 64 /dev/urandom | od -An -tx1 | tr -d ' \n')
DB_PASSWORD=$(python3 -c "import secrets; print(secrets.token_urlsafe(24))" 2>/dev/null || openssl rand -base64 24 2>/dev/null || head -c 24 /dev/urandom | base64)
MINIO_SECRET=$(python3 -c "import secrets; print(secrets.token_urlsafe(24))" 2>/dev/null || openssl rand -base64 24 2>/dev/null || head -c 24 /dev/urandom | base64)
cat > "$ENV_FILE" <<EOF
# ── Aegis Production Environment ─────────────────────────────────
# Generated by install.sh on $(date -u +"%Y-%m-%d %H:%M:%S UTC")
# Database
DB_USER=postgres
DB_PASSWORD=${DB_PASSWORD}
DB_NAME=attackdb
# Security
SECRET_KEY=${SECRET_KEY}
TOKEN_EXPIRE_MINUTES=60
# MinIO Object Storage
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=${MINIO_SECRET}
MINIO_BUCKET=evidence
# Frontend
FRONTEND_PORT=80
EOF
print_ok ".env file created with secure random secrets"
print_info "Review and edit .env if needed before proceeding"
fi
# ── 3. Build and start containers ────────────────────────────────────
print_header "Building and starting containers"
print_info "This may take a few minutes on first run..."
$COMPOSE_CMD -f docker-compose.prod.yml up -d --build 2>&1 | while IFS= read -r line; do
echo " $line"
done
print_ok "Containers started"
# ── 4. Wait for services to be healthy ───────────────────────────────
print_header "Waiting for services"
# Wait for postgres
print_info "Waiting for PostgreSQL..."
MAX_RETRIES=30
RETRY=0
until docker exec aegis-postgres pg_isready -U postgres > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRIES ]; then
print_error "PostgreSQL failed to start after $MAX_RETRIES attempts"
echo " Check logs: docker logs aegis-postgres"
exit 1
fi
sleep 2
done
print_ok "PostgreSQL is ready"
# Wait for backend (which runs migrations + seed on startup)
print_info "Waiting for backend (running migrations and seeds)..."
RETRY=0
until curl -sf http://localhost:8000/health > /dev/null 2>&1 || \
docker exec aegis-backend curl -sf http://localhost:8000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge 60 ]; then
print_error "Backend failed to start after 120 seconds"
echo " Check logs: docker logs aegis-backend"
exit 1
fi
sleep 2
done
print_ok "Backend is ready (migrations and seeds completed)"
# Wait for frontend
print_info "Waiting for frontend..."
RETRY=0
FRONTEND_PORT=$(grep FRONTEND_PORT "$ENV_FILE" 2>/dev/null | cut -d= -f2 || echo "80")
FRONTEND_PORT=${FRONTEND_PORT:-80}
until curl -sf "http://localhost:${FRONTEND_PORT}" > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge 30 ]; then
print_error "Frontend failed to start after 60 seconds"
echo " Check logs: docker logs aegis-frontend"
exit 1
fi
sleep 2
done
print_ok "Frontend is ready"
# ── 5. Trigger MITRE ATT&CK sync ────────────────────────────────────
print_header "Initial data sync"
echo ""
read -p "Run initial MITRE ATT&CK sync? This imports ~700 techniques. (Y/n) " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
print_info "Authenticating..."
# Get admin token
TOKEN=$(curl -sf -X POST "http://localhost:${FRONTEND_PORT}/api/v1/auth/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=admin123" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || echo "")
if [ -n "$TOKEN" ] && [ "$TOKEN" != "" ]; then
print_info "Syncing MITRE ATT&CK data (this takes 1-2 minutes)..."
SYNC_RESULT=$(curl -sf -X POST "http://localhost:${FRONTEND_PORT}/api/v1/system/sync-mitre" \
-H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "error")
if [ "$SYNC_RESULT" != "error" ]; then
print_ok "MITRE ATT&CK sync completed"
else
print_warn "MITRE sync may have timed out. Check the System page in the UI."
fi
# Sync data sources
print_info "Syncing data sources (Atomic Red Team, SigmaHQ, etc.)..."
for source_id in $(curl -sf "http://localhost:${FRONTEND_PORT}/api/v1/data-sources" \
-H "Authorization: Bearer $TOKEN" 2>/dev/null | \
python3 -c "import sys,json; [print(s['id']) for s in json.load(sys.stdin)]" 2>/dev/null); do
curl -sf -X POST "http://localhost:${FRONTEND_PORT}/api/v1/data-sources/${source_id}/sync" \
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1 || true
done
print_ok "Data source sync triggered"
else
print_warn "Could not authenticate. Run MITRE sync manually from the System page."
print_info "Default credentials: admin / admin123"
fi
else
print_info "Skipping MITRE sync. You can do this later from the System page."
fi
# ── 6. Summary ───────────────────────────────────────────────────────
# Get the server's IP
SERVER_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost")
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ Aegis is ready! ║"
echo "╠═══════════════════════════════════════════════════════════════╣"
echo "║ ║"
echo "║ Application: http://${SERVER_IP}:${FRONTEND_PORT} "
echo "║ API Docs: http://${SERVER_IP}:${FRONTEND_PORT}/api/v1/docs "
echo "║ ║"
echo "║ Default login: admin / admin123 ║"
echo "║ ║"
echo "║ ⚠ IMPORTANT: ║"
echo "║ • Change the default password immediately ║"
echo "║ • Set up HTTPS/TLS for internet-facing deployments ║"
echo "║ • Configure firewall rules as needed ║"
echo "║ • Set up regular database backups ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
echo "Useful commands:"
echo " View logs: docker logs -f aegis-backend"
echo " Stop: $COMPOSE_CMD -f docker-compose.prod.yml down"
echo " Restart: $COMPOSE_CMD -f docker-compose.prod.yml restart"
echo " Update: $COMPOSE_CMD -f docker-compose.prod.yml up -d --build"
echo " DB backup: docker exec aegis-postgres pg_dump -U postgres attackdb > backup.sql"
echo ""