169 lines
4.8 KiB
Bash
Executable File
169 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Mission Control Security Audit
|
|
# Run: bash scripts/security-audit.sh [--env-file .env]
|
|
|
|
set -euo pipefail
|
|
|
|
SCORE=0
|
|
MAX_SCORE=0
|
|
ISSUES=()
|
|
|
|
pass() { echo " [PASS] $1"; ((SCORE++)); ((MAX_SCORE++)); }
|
|
fail() { echo " [FAIL] $1"; ISSUES+=("$1"); ((MAX_SCORE++)); }
|
|
warn() { echo " [WARN] $1"; ((MAX_SCORE++)); }
|
|
info() { echo " [INFO] $1"; }
|
|
|
|
# Load .env if exists
|
|
ENV_FILE="${1:-.env}"
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
while IFS='=' read -r key value; do
|
|
[[ "$key" =~ ^#.*$ ]] && continue
|
|
[[ -z "$key" ]] && continue
|
|
declare "$key=$value" 2>/dev/null || true
|
|
done < "$ENV_FILE"
|
|
fi
|
|
|
|
echo "=== Mission Control Security Audit ==="
|
|
echo ""
|
|
|
|
# 1. .env file permissions
|
|
echo "--- File Permissions ---"
|
|
if [[ -f "$ENV_FILE" ]]; then
|
|
perms=$(stat -f '%A' "$ENV_FILE" 2>/dev/null || stat -c '%a' "$ENV_FILE" 2>/dev/null)
|
|
if [[ "$perms" == "600" ]]; then
|
|
pass ".env permissions are 600 (owner read/write only)"
|
|
else
|
|
fail ".env permissions are $perms (should be 600). Run: chmod 600 $ENV_FILE"
|
|
fi
|
|
else
|
|
warn ".env file not found at $ENV_FILE"
|
|
fi
|
|
|
|
# 2. Default passwords check
|
|
echo ""
|
|
echo "--- Credentials ---"
|
|
INSECURE_PASSWORDS=("admin" "password" "change-me-on-first-login" "changeme" "testpass123" "testpass1234")
|
|
AUTH_PASS_VAL="${AUTH_PASS:-}"
|
|
if [[ -z "$AUTH_PASS_VAL" ]]; then
|
|
fail "AUTH_PASS is not set"
|
|
else
|
|
insecure=false
|
|
for bad in "${INSECURE_PASSWORDS[@]}"; do
|
|
if [[ "$AUTH_PASS_VAL" == "$bad" ]]; then
|
|
insecure=true; break
|
|
fi
|
|
done
|
|
if $insecure; then
|
|
fail "AUTH_PASS is set to a known insecure default"
|
|
elif [[ ${#AUTH_PASS_VAL} -lt 12 ]]; then
|
|
fail "AUTH_PASS is too short (${#AUTH_PASS_VAL} chars, minimum 12)"
|
|
else
|
|
pass "AUTH_PASS is set to a non-default value (${#AUTH_PASS_VAL} chars)"
|
|
fi
|
|
fi
|
|
|
|
API_KEY_VAL="${API_KEY:-}"
|
|
if [[ -z "$API_KEY_VAL" || "$API_KEY_VAL" == "generate-a-random-key" ]]; then
|
|
fail "API_KEY is not set or uses the default value"
|
|
else
|
|
pass "API_KEY is configured"
|
|
fi
|
|
|
|
# 3. Network config
|
|
echo ""
|
|
echo "--- Network Security ---"
|
|
MC_ALLOWED="${MC_ALLOWED_HOSTS:-}"
|
|
MC_ANY="${MC_ALLOW_ANY_HOST:-}"
|
|
if [[ "$MC_ANY" == "1" || "$MC_ANY" == "true" ]]; then
|
|
fail "MC_ALLOW_ANY_HOST is enabled (any host can connect)"
|
|
elif [[ -n "$MC_ALLOWED" ]]; then
|
|
pass "MC_ALLOWED_HOSTS is configured: $MC_ALLOWED"
|
|
else
|
|
warn "MC_ALLOWED_HOSTS is not set (defaults apply)"
|
|
fi
|
|
|
|
# 4. Cookie/HTTPS config
|
|
echo ""
|
|
echo "--- HTTPS & Cookies ---"
|
|
COOKIE_SECURE="${MC_COOKIE_SECURE:-}"
|
|
if [[ "$COOKIE_SECURE" == "1" || "$COOKIE_SECURE" == "true" ]]; then
|
|
pass "MC_COOKIE_SECURE is enabled"
|
|
else
|
|
warn "MC_COOKIE_SECURE is not enabled (cookies sent over HTTP)"
|
|
fi
|
|
|
|
SAMESITE="${MC_COOKIE_SAMESITE:-strict}"
|
|
if [[ "$SAMESITE" == "strict" ]]; then
|
|
pass "MC_COOKIE_SAMESITE is strict"
|
|
else
|
|
warn "MC_COOKIE_SAMESITE is '$SAMESITE' (strict recommended)"
|
|
fi
|
|
|
|
HSTS="${MC_ENABLE_HSTS:-}"
|
|
if [[ "$HSTS" == "1" ]]; then
|
|
pass "HSTS is enabled"
|
|
else
|
|
warn "HSTS is not enabled (set MC_ENABLE_HSTS=1 for HTTPS deployments)"
|
|
fi
|
|
|
|
# 5. Rate limiting
|
|
echo ""
|
|
echo "--- Rate Limiting ---"
|
|
RL_DISABLED="${MC_DISABLE_RATE_LIMIT:-}"
|
|
if [[ "$RL_DISABLED" == "1" ]]; then
|
|
fail "Rate limiting is disabled (MC_DISABLE_RATE_LIMIT=1)"
|
|
else
|
|
pass "Rate limiting is active"
|
|
fi
|
|
|
|
# 6. Docker security (if running in Docker)
|
|
echo ""
|
|
echo "--- Docker Security ---"
|
|
if command -v docker &>/dev/null; then
|
|
if docker ps --filter name=mission-control --format '{{.Names}}' 2>/dev/null | grep -q mission-control; then
|
|
ro=$(docker inspect mission-control --format '{{.HostConfig.ReadonlyRootfs}}' 2>/dev/null || echo "false")
|
|
if [[ "$ro" == "true" ]]; then
|
|
pass "Container filesystem is read-only"
|
|
else
|
|
warn "Container filesystem is writable (use read_only: true)"
|
|
fi
|
|
|
|
nnp=$(docker inspect mission-control --format '{{.HostConfig.SecurityOpt}}' 2>/dev/null || echo "[]")
|
|
if echo "$nnp" | grep -q "no-new-privileges"; then
|
|
pass "no-new-privileges is set"
|
|
else
|
|
warn "no-new-privileges not set"
|
|
fi
|
|
|
|
user=$(docker inspect mission-control --format '{{.Config.User}}' 2>/dev/null || echo "")
|
|
if [[ -n "$user" && "$user" != "root" && "$user" != "0" ]]; then
|
|
pass "Container runs as non-root user ($user)"
|
|
else
|
|
warn "Container may be running as root"
|
|
fi
|
|
else
|
|
info "Mission Control container not running"
|
|
fi
|
|
else
|
|
info "Docker not installed (skipping container checks)"
|
|
fi
|
|
|
|
# Summary
|
|
echo ""
|
|
echo "=== Security Score: $SCORE / $MAX_SCORE ==="
|
|
if [[ ${#ISSUES[@]} -gt 0 ]]; then
|
|
echo ""
|
|
echo "Issues to fix:"
|
|
for issue in "${ISSUES[@]}"; do
|
|
echo " - $issue"
|
|
done
|
|
fi
|
|
|
|
if [[ $SCORE -eq $MAX_SCORE ]]; then
|
|
echo "All checks passed!"
|
|
elif [[ $SCORE -ge $((MAX_SCORE * 7 / 10)) ]]; then
|
|
echo "Good security posture with minor improvements needed."
|
|
else
|
|
echo "Security improvements recommended before production use."
|
|
fi
|