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:
@@ -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 ""
|
||||
|
||||
Reference in New Issue
Block a user