fix: resolve 20 security vulnerabilities from comprehensive audit

Critical (1-3):
- Replace hardcoded admin credentials with secure auto-generation (seed.py)
- Enforce SECRET_KEY configuration, fail in production if missing (config.py)
- Add Zip Slip and Zip Bomb protection to all ZIP import services

High/Medium (4-9):
- Add 50MB file size limit and extension whitelist to evidence uploads
- Configure CORS origins via environment variable instead of hardcoded
- Migrate JWT storage from localStorage to HttpOnly cookies (frontend+backend)
- Add rate limiting (5/min) on login endpoint via slowapi
- Replace generic dict payloads with Pydantic schemas (mass assignment)

Medium (10-17):
- Check is_active on login to prevent disabled users from authenticating
- Sanitize exception messages in API responses (system, data_sources)
- Escape LIKE wildcards in all ilike search filters across 8 routers
- Run Docker container as non-root user (appuser)
- Make MINIO_SECURE configurable via environment variable
- Add password complexity policy (12+ chars, upper/lower/digit/special)
- Implement JWT token revocation via in-memory blacklist + reduce TTL to 15min
- Replace xml.etree with defusedxml to prevent Billion Laughs attacks

Low (18-20):
- Add security headers to Nginx (CSP, X-Frame-Options, HSTS-ready, etc.)
- Disable Swagger UI/ReDoc/OpenAPI in production
- Restrict /health endpoint to internal networks via Nginx ACL

Also: rewrite install.sh as interactive wizard for guided deployment,
fix test-from-template validation error (technique_id UUID vs MITRE ID)
This commit is contained in:
2026-02-11 08:56:26 +01:00
parent e7e63161e8
commit 64d64080e0
36 changed files with 1154 additions and 311 deletions

View File

@@ -1,8 +1,9 @@
#!/bin/bash
# =============================================================================
# Aegis - Production Installation Script
# Aegis - Interactive Production Installer
# =============================================================================
# This script sets up the Aegis platform for production.
# Sets up the Aegis platform for production with an interactive wizard
# that configures all environment variables.
#
# Usage:
# chmod +x scripts/install.sh
@@ -10,7 +11,7 @@
#
# Prerequisites:
# - Docker and Docker Compose installed
# - Port 80 (or FRONTEND_PORT) available
# - Port 80 (or chosen port) available
# =============================================================================
set -e
@@ -20,33 +21,58 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
cd "$PROJECT_ROOT"
# Colors
# ── Colors & helpers ──────────────────────────────────────────────────────
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
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}"; }
print_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
print_warn() { echo -e "${YELLOW}[!]${NC} $1"; }
print_error() { echo -e "${RED}[X]${NC} $1"; }
print_info() { echo -e "${CYAN}[i]${NC} $1"; }
print_header() { echo -e "\n${BOLD}── $1 ──${NC}\n"; }
print_prompt() { echo -en "${CYAN}>>>${NC} $1"; }
# Generate a cryptographically secure random string
gen_secret() {
python3 -c "import secrets; print(secrets.token_hex($1))" 2>/dev/null \
|| openssl rand -hex "$1" 2>/dev/null \
|| head -c "$1" /dev/urandom | od -An -tx1 | tr -d ' \n'
}
gen_password() {
python3 -c "import secrets; print(secrets.token_urlsafe($1))" 2>/dev/null \
|| openssl rand -base64 "$1" 2>/dev/null \
|| head -c "$1" /dev/urandom | base64 | tr -d '=/+' | head -c "$1"
}
# ── Banner ────────────────────────────────────────────────────────────────
clear 2>/dev/null || true
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo " Aegis - Production Installation ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
echo -e "${BOLD}"
echo " ╔══════════════════════════════════════════════════════════╗"
echo " ║ ║"
echo " ║ Aegis - Installation Wizard ║"
echo " ║ MITRE ATT&CK Coverage Platform ║"
echo " ║ ║"
echo " ╚══════════════════════════════════════════════════════════╝"
echo -e "${NC}"
# ── 1. Check prerequisites ──────────────────────────────────────────
# ═════════════════════════════════════════════════════════════════════════
# STEP 1: Check prerequisites
# ═════════════════════════════════════════════════════════════════════════
print_header "Checking prerequisites"
print_header "Step 1/5 - 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/"
echo " https://docs.docker.com/engine/install/"
exit 1
fi
print_ok "Docker found: $(docker --version | head -1)"
@@ -57,236 +83,444 @@ if ! docker info > /dev/null 2>&1; then
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/"
echo " https://docs.docker.com/compose/install/"
exit 1
fi
print_ok "Docker Compose found ($COMPOSE_CMD)"
# Auto-detect Docker API version to avoid client/server mismatch
# Auto-detect Docker API version
DOCKER_SERVER_API=$(docker version --format '{{.Server.APIVersion}}' 2>/dev/null || echo "")
if [ -n "$DOCKER_SERVER_API" ]; then
export DOCKER_API_VERSION="$DOCKER_SERVER_API"
print_info "Docker API version: $DOCKER_SERVER_API"
fi
# ── 2. Setup .env file ──────────────────────────────────────────────
# ═════════════════════════════════════════════════════════════════════════
# STEP 2: Interactive configuration
# ═════════════════════════════════════════════════════════════════════════
print_header "Environment configuration"
print_header "Step 2/5 - Configuration"
ENV_FILE=".env"
SKIP_CONFIG=false
if [ -f "$ENV_FILE" ]; then
print_warn ".env file already exists"
read -p " Overwrite with new values? (y/N) " -n 1 -r
print_warn "An existing .env file was found."
echo ""
print_prompt "Do you want to reconfigure? (y/N): "
read -r REPLY
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_info "Keeping existing .env file"
SKIP_ENV=true
print_info "Keeping existing configuration."
SKIP_CONFIG=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)
if [ "$SKIP_CONFIG" = false ]; then
cat > "$ENV_FILE" <<EOF
# ── Aegis Production Environment ─────────────────────────────────
echo -e " ${DIM}Answer the following questions to configure Aegis."
echo -e " Press Enter to accept the default value shown in [brackets].${NC}"
echo ""
# ── Domain / URL ──────────────────────────────────────────────────
echo -e " ${BOLD}1. Domain Configuration${NC}"
echo -e " ${DIM}The domain where Aegis will be accessible."
echo -e " Examples: aegis.example.com, 192.168.1.100, localhost${NC}"
echo ""
print_prompt "Domain or IP [localhost]: "
read -r INPUT_DOMAIN
DOMAIN="${INPUT_DOMAIN:-localhost}"
# ── Protocol ──────────────────────────────────────────────────────
if [ "$DOMAIN" = "localhost" ] || [ "$DOMAIN" = "127.0.0.1" ]; then
PROTOCOL="http"
print_info "Using HTTP for local deployment"
else
echo ""
print_prompt "Are you using HTTPS/SSL? (Y/n): "
read -r REPLY
if [[ $REPLY =~ ^[Nn]$ ]]; then
PROTOCOL="http"
else
PROTOCOL="https"
fi
fi
# ── Port ──────────────────────────────────────────────────────────
if [ "$PROTOCOL" = "https" ]; then
DEFAULT_PORT=443
else
DEFAULT_PORT=80
fi
echo ""
echo -e " ${BOLD}2. Port${NC}"
print_prompt "Frontend port [$DEFAULT_PORT]: "
read -r INPUT_PORT
FRONTEND_PORT="${INPUT_PORT:-$DEFAULT_PORT}"
# Build the full origin URL for CORS
if { [ "$PROTOCOL" = "https" ] && [ "$FRONTEND_PORT" = "443" ]; } || \
{ [ "$PROTOCOL" = "http" ] && [ "$FRONTEND_PORT" = "80" ]; }; then
ORIGIN_URL="${PROTOCOL}://${DOMAIN}"
else
ORIGIN_URL="${PROTOCOL}://${DOMAIN}:${FRONTEND_PORT}"
fi
# ── Admin account ─────────────────────────────────────────────────
echo ""
echo -e " ${BOLD}3. Admin Account${NC}"
echo -e " ${DIM}The initial administrator account for Aegis.${NC}"
echo ""
print_prompt "Admin username [admin]: "
read -r INPUT_ADMIN_USER
ADMIN_USERNAME="${INPUT_ADMIN_USER:-admin}"
echo ""
echo -e " ${DIM}Leave empty to auto-generate a secure password."
echo -e " The password will be shown in the installation summary.${NC}"
print_prompt "Admin password [auto-generate]: "
read -rs INPUT_ADMIN_PASS
echo ""
ADMIN_PASSWORD="${INPUT_ADMIN_PASS}"
if [ -z "$ADMIN_PASSWORD" ]; then
ADMIN_PASSWORD=$(gen_password 18)
ADMIN_PW_GENERATED=true
print_info "Password will be auto-generated"
else
ADMIN_PW_GENERATED=false
print_ok "Password set"
fi
# ── Database ──────────────────────────────────────────────────────
echo ""
echo -e " ${BOLD}4. Database${NC}"
print_prompt "Database name [attackdb]: "
read -r INPUT_DB_NAME
DB_NAME="${INPUT_DB_NAME:-attackdb}"
print_prompt "Database user [postgres]: "
read -r INPUT_DB_USER
DB_USER="${INPUT_DB_USER:-postgres}"
echo -e " ${DIM}Leave empty to auto-generate a secure password.${NC}"
print_prompt "Database password [auto-generate]: "
read -rs INPUT_DB_PASS
echo ""
if [ -z "$INPUT_DB_PASS" ]; then
DB_PASSWORD=$(gen_password 24)
print_info "Database password auto-generated"
else
DB_PASSWORD="$INPUT_DB_PASS"
print_ok "Database password set"
fi
# ── Token expiry ──────────────────────────────────────────────────
echo ""
echo -e " ${BOLD}5. Session Duration${NC}"
print_prompt "Token expiry in minutes [60]: "
read -r INPUT_TOKEN_EXP
TOKEN_EXPIRE_MINUTES="${INPUT_TOKEN_EXP:-60}"
# ── MITRE sync ────────────────────────────────────────────────────
echo ""
echo -e " ${BOLD}6. Initial Data${NC}"
print_prompt "Run MITRE ATT&CK sync after install? (Y/n): "
read -r INPUT_SYNC
if [[ $INPUT_SYNC =~ ^[Nn]$ ]]; then
RUN_MITRE_SYNC=false
else
RUN_MITRE_SYNC=true
fi
# ── Generate secrets ──────────────────────────────────────────────
SECRET_KEY=$(gen_secret 32)
MINIO_SECRET=$(gen_password 24)
# ── Show summary before writing ──────────────────────────────────
echo ""
echo -e "${BOLD} ┌──────────────────────────────────────────────────────┐${NC}"
echo -e "${BOLD} │ Configuration Summary │${NC}"
echo -e "${BOLD} ├──────────────────────────────────────────────────────┤${NC}"
echo -e " │ URL: ${CYAN}${ORIGIN_URL}${NC}"
echo -e " │ Admin user: ${CYAN}${ADMIN_USERNAME}${NC}"
if [ "$ADMIN_PW_GENERATED" = true ]; then
echo -e " │ Admin pass: ${CYAN}(auto-generated)${NC}"
else
echo -e " │ Admin pass: ${CYAN}(custom)${NC}"
fi
echo -e " │ Database: ${CYAN}${DB_USER}@${DB_NAME}${NC}"
echo -e " │ Port: ${CYAN}${FRONTEND_PORT}${NC}"
echo -e " │ Session TTL: ${CYAN}${TOKEN_EXPIRE_MINUTES} min${NC}"
echo -e " │ MITRE sync: ${CYAN}$([ "$RUN_MITRE_SYNC" = true ] && echo "yes" || echo "no")${NC}"
echo -e "${BOLD} └──────────────────────────────────────────────────────┘${NC}"
echo ""
print_prompt "Proceed with these settings? (Y/n): "
read -r CONFIRM
if [[ $CONFIRM =~ ^[Nn]$ ]]; then
print_warn "Installation cancelled. Run the script again to reconfigure."
exit 0
fi
# ── Write .env ────────────────────────────────────────────────────
cat > "$ENV_FILE" <<ENVEOF
# =============================================================================
# Aegis Production Environment
# Generated by install.sh on $(date -u +"%Y-%m-%d %H:%M:%S UTC")
# =============================================================================
# Database
DB_USER=postgres
# ── Database ─────────────────────────────────────────────────────────────────
DB_USER=${DB_USER}
DB_PASSWORD=${DB_PASSWORD}
DB_NAME=attackdb
DB_NAME=${DB_NAME}
# Security
# ── Security ─────────────────────────────────────────────────────────────────
SECRET_KEY=${SECRET_KEY}
TOKEN_EXPIRE_MINUTES=60
TOKEN_EXPIRE_MINUTES=${TOKEN_EXPIRE_MINUTES}
# MinIO Object Storage
# ── Initial Admin Account ────────────────────────────────────────────────────
ADMIN_USERNAME=${ADMIN_USERNAME}
ADMIN_PASSWORD=${ADMIN_PASSWORD}
# ── MinIO Object Storage ─────────────────────────────────────────────────────
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=${MINIO_SECRET}
MINIO_BUCKET=evidence
# Frontend
FRONTEND_PORT=80
EOF
# ── CORS (allowed frontend origins) ─────────────────────────────────────────
CORS_ORIGINS=${ORIGIN_URL}
print_ok ".env file created with secure random secrets"
print_info "Review and edit .env if needed before proceeding"
fi
# ── Frontend ─────────────────────────────────────────────────────────────────
FRONTEND_PORT=${FRONTEND_PORT}
# ── 3. Build and start containers ────────────────────────────────────
# ── Environment ──────────────────────────────────────────────────────────────
AEGIS_ENV=production
ENVEOF
print_header "Building and starting containers"
print_ok ".env file created with secure configuration"
print_info "This may take a few minutes on first run..."
print_info "Project root: $PROJECT_ROOT"
fi # end SKIP_CONFIG
if ! $COMPOSE_CMD -f docker-compose.prod.yml up -d --build; then
# ═════════════════════════════════════════════════════════════════════════
# STEP 3: Build and start
# ═════════════════════════════════════════════════════════════════════════
print_header "Step 3/5 - Building and starting containers"
print_info "This may take several minutes on first run..."
if ! $COMPOSE_CMD -f docker-compose.prod.yml up -d --build 2>&1; then
print_error "Failed to build/start containers. Check the output above."
exit 1
fi
print_ok "Containers started"
# ── 4. Wait for services to be healthy ───────────────────────────────
# ═════════════════════════════════════════════════════════════════════════
# STEP 4: Wait for services
# ═════════════════════════════════════════════════════════════════════════
print_header "Waiting for services"
print_header "Step 4/5 - Waiting for services to be ready"
# Wait for postgres
print_info "Waiting for PostgreSQL..."
# Wait for PostgreSQL
echo -en " 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"
echo ""
print_error "PostgreSQL failed to start. Check: docker logs aegis-postgres"
exit 1
fi
echo -n "."
sleep 2
done
print_ok "PostgreSQL is ready"
echo -e " ${GREEN}ready${NC}"
# Wait for backend (which runs migrations + seed on startup)
print_info "Waiting for backend (running migrations and seeds)..."
# Wait for backend (runs migrations + seed)
echo -en " Backend (migrations + seed) ..."
RETRY=0
until docker exec aegis-backend curl -sf http://localhost:8000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge 90 ]; then
print_error "Backend failed to start after 180 seconds"
echo " Check logs: docker logs aegis-backend"
echo ""
print_error "Backend failed to start after 3 minutes."
echo " Check: docker logs aegis-backend"
exit 1
fi
# Show progress every 10 attempts
if [ $((RETRY % 5)) -eq 0 ]; then
print_info " Still waiting... ($RETRY attempts, checking logs)"
docker logs aegis-backend --tail 3 2>/dev/null | while IFS= read -r line; do echo " $line"; done
fi
echo -n "."
sleep 2
done
print_ok "Backend is ready (migrations and seeds completed)"
echo -e " ${GREEN}ready${NC}"
# 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}
echo -en " Frontend ..."
RETRY=0
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"
echo ""
print_error "Frontend failed to start. Check: docker logs aegis-frontend"
exit 1
fi
echo -n "."
sleep 2
done
print_ok "Frontend is ready"
echo -e " ${GREEN}ready${NC}"
# ── 5. Trigger MITRE ATT&CK sync ────────────────────────────────────
print_ok "All services are running"
print_header "Initial data sync"
# ── Extract admin credentials from backend logs ──────────────────────
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..."
ADMIN_CREDS_USER=""
ADMIN_CREDS_PASS=""
# Get admin token (try via nginx first, then directly to backend container)
API_URL="http://localhost:${FRONTEND_PORT}/api/v1"
TOKEN=$(curl -sf --max-time 10 -X POST "${API_URL}/auth/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=admin123" 2>/dev/null | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || echo "")
# Try to extract the credentials from the backend startup logs
LOG_OUTPUT=$(docker logs aegis-backend 2>&1 | tail -20)
# Fallback: try directly via backend container
if [ -z "$TOKEN" ] || [ "$TOKEN" = "" ]; then
TOKEN=$(docker exec aegis-backend curl -sf -X POST "http://localhost:8000/api/v1/auth/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=admin123" 2>/dev/null | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || echo "")
API_URL="http://localhost:8000/api/v1"
API_VIA_DOCKER=true
fi
if [ -n "$TOKEN" ] && [ "$TOKEN" != "" ]; then
print_info "Syncing MITRE ATT&CK data (this takes 1-2 minutes)..."
if [ "$API_VIA_DOCKER" = true ]; then
SYNC_RESULT=$(docker exec aegis-backend curl -sf --max-time 300 -X POST "${API_URL}/system/sync-mitre" \
-H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "error")
else
SYNC_RESULT=$(curl -sf --max-time 300 -X POST "${API_URL}/system/sync-mitre" \
-H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "error")
fi
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.)..."
if [ "$API_VIA_DOCKER" = true ]; then
CURL_PREFIX="docker exec aegis-backend curl"
else
CURL_PREFIX="curl"
fi
for source_id in $($CURL_PREFIX -sf "${API_URL}/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_PREFIX -sf --max-time 120 -X POST "${API_URL}/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."
if echo "$LOG_OUTPUT" | grep -q "Initial Admin User Created"; then
ADMIN_CREDS_USER=$(echo "$LOG_OUTPUT" | grep "Username :" | sed 's/.*Username : //')
ADMIN_CREDS_PASS=$(echo "$LOG_OUTPUT" | grep "Password :" | sed 's/.*Password : //')
fi
# ── 6. Summary ───────────────────────────────────────────────────────
# Fallback: if we set it via env, use those values
if [ -z "$ADMIN_CREDS_USER" ]; then
ADMIN_CREDS_USER=$(grep ADMIN_USERNAME "$ENV_FILE" 2>/dev/null | cut -d= -f2 || echo "admin")
ADMIN_CREDS_USER="${ADMIN_CREDS_USER:-admin}"
fi
# Get the server's IP
if [ -z "$ADMIN_CREDS_PASS" ] || [ "$ADMIN_CREDS_PASS" = "(set via ADMIN_PASSWORD env var)" ]; then
ADMIN_CREDS_PASS=$(grep ADMIN_PASSWORD "$ENV_FILE" 2>/dev/null | cut -d= -f2 || echo "")
fi
# ═════════════════════════════════════════════════════════════════════════
# STEP 5: Initial data sync (optional)
# ═════════════════════════════════════════════════════════════════════════
# Re-read RUN_MITRE_SYNC if we skipped config
if [ "$SKIP_CONFIG" = true ]; then
echo ""
print_prompt "Run initial MITRE ATT&CK sync? (~700 techniques, 1-2 min) (Y/n): "
read -r INPUT_SYNC
if [[ $INPUT_SYNC =~ ^[Nn]$ ]]; then
RUN_MITRE_SYNC=false
else
RUN_MITRE_SYNC=true
fi
fi
if [ "$RUN_MITRE_SYNC" = true ]; then
print_header "Step 5/5 - Initial data sync"
print_info "Authenticating with backend..."
# Authenticate via backend container (most reliable)
TOKEN=$(docker exec aegis-backend curl -sf -X POST "http://localhost:8000/api/v1/auth/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=${ADMIN_CREDS_USER}&password=${ADMIN_CREDS_PASS}" 2>/dev/null | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || echo "")
if [ -n "$TOKEN" ] && [ "$TOKEN" != "" ]; then
# MITRE ATT&CK sync
print_info "Syncing MITRE ATT&CK techniques (1-2 minutes)..."
SYNC_RESULT=$(docker exec aegis-backend curl -sf --max-time 300 \
-X POST "http://localhost:8000/api/v1/system/sync-mitre" \
-H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "error")
if [ "$SYNC_RESULT" != "error" ]; then
NEW_TECHNIQUES=$(echo "$SYNC_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('new',0))" 2>/dev/null || echo "?")
print_ok "MITRE sync completed ($NEW_TECHNIQUES techniques imported)"
else
print_warn "MITRE sync timed out. You can retry from the System page."
fi
# Data sources sync
print_info "Syncing data sources (Atomic Red Team, SigmaHQ, etc.)..."
SOURCES=$(docker exec aegis-backend curl -sf "http://localhost:8000/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 || echo "")
SYNC_COUNT=0
for source_id in $SOURCES; do
docker exec aegis-backend curl -sf --max-time 120 \
-X POST "http://localhost:8000/api/v1/data-sources/${source_id}/sync" \
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1 && SYNC_COUNT=$((SYNC_COUNT + 1)) || true
done
if [ "$SYNC_COUNT" -gt 0 ]; then
print_ok "Data sources synced ($SYNC_COUNT sources)"
fi
else
print_warn "Could not authenticate. Run MITRE sync from the System page."
fi
else
print_header "Step 5/5 - Skipping data sync"
print_info "You can import data later from the System page."
fi
# ═════════════════════════════════════════════════════════════════════════
# FINAL SUMMARY
# ═════════════════════════════════════════════════════════════════════════
# Build the access URL
ORIGIN_URL=$(grep CORS_ORIGINS "$ENV_FILE" 2>/dev/null | cut -d= -f2 || echo "http://localhost")
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 -e "${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║ ║${NC}"
echo -e "${BOLD}${GREEN}Aegis is ready!${NC}${BOLD}${NC}"
echo -e "${BOLD}║ ║${NC}"
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
echo -e "${BOLD}${NC} ${BOLD}${NC}"
echo -e "${BOLD}${NC} ${CYAN}Application${NC} ${ORIGIN_URL}"
echo -e "${BOLD}${NC} ${CYAN}Local IP${NC} http://${SERVER_IP}:${FRONTEND_PORT}"
echo -e "${BOLD}${NC} ${BOLD}${NC}"
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
echo -e "${BOLD}${NC} ${BOLD}${NC}"
echo -e "${BOLD}${NC} ${CYAN}Admin Login${NC} ${BOLD}${NC}"
echo -e "${BOLD}${NC} Username: ${GREEN}${ADMIN_CREDS_USER}${NC}"
if [ -n "$ADMIN_CREDS_PASS" ]; then
echo -e "${BOLD}${NC} Password: ${GREEN}${ADMIN_CREDS_PASS}${NC}"
else
echo -e "${BOLD}${NC} Password: ${YELLOW}(check: docker logs aegis-backend | grep Password)${NC}"
fi
echo -e "${BOLD}${NC} ${BOLD}${NC}"
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
echo -e "${BOLD}${NC} ${BOLD}${NC}"
echo -e "${BOLD}${NC} ${YELLOW}Important:${NC} ${BOLD}${NC}"
echo -e "${BOLD}${NC} - Save the admin password now if auto-generated ${BOLD}${NC}"
echo -e "${BOLD}${NC} - Set up HTTPS/TLS for internet-facing deployments ${BOLD}${NC}"
echo -e "${BOLD}${NC} - Configure firewall rules as needed ${BOLD}${NC}"
echo -e "${BOLD}${NC} - Set up regular database backups ${BOLD}${NC}"
echo -e "${BOLD}${NC} ${BOLD}${NC}"
echo -e "${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
echo ""
echo -e "${BOLD}Useful commands:${NC}"
echo -e " ${DIM}View logs${NC} docker logs -f aegis-backend"
echo -e " ${DIM}Stop${NC} $COMPOSE_CMD -f docker-compose.prod.yml down"
echo -e " ${DIM}Restart${NC} $COMPOSE_CMD -f docker-compose.prod.yml restart"
echo -e " ${DIM}Update${NC} $COMPOSE_CMD -f docker-compose.prod.yml up -d --build"
echo -e " ${DIM}DB backup${NC} docker exec aegis-postgres pg_dump -U postgres ${DB_NAME:-attackdb} > backup.sql"
echo -e " ${DIM}Reconfigure${NC} ./scripts/install.sh"
echo ""