feat: Add Alateya, Clan, Eonarch agents + fix gateway-router connection
## Agents Added - Alateya: R&D, biotech, innovations - Clan (Spirit): Community spirit agent - Eonarch: Consciousness evolution agent ## Changes - docker-compose.node1.yml: Added tokens for all 3 new agents - gateway-bot/http_api.py: Added configs and webhook endpoints - gateway-bot/clan_prompt.txt: New prompt file - gateway-bot/eonarch_prompt.txt: New prompt file ## Fixes - Fixed ROUTER_URL from :9102 to :8000 (internal container port) - All 9 Telegram agents now working ## Documentation - Created PROJECT-MASTER-INDEX.md - single entry point - Added various status documents and scripts Tokens configured: - Helion, NUTRA, Agromatrix (existing) - Alateya, Clan, Eonarch (new) - Druid, GreenFood, DAARWIZZ (configured)
This commit is contained in:
195
ops/hardening/apply-node1-firewall.sh
Normal file
195
ops/hardening/apply-node1-firewall.sh
Normal file
@@ -0,0 +1,195 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# NODE1 Firewall Hardening Script
|
||||
# Version: 1.0
|
||||
# Last Updated: 2026-01-26
|
||||
#
|
||||
# Usage: ./apply-node1-firewall.sh [--apply|--dry-run|--rollback]
|
||||
# --dry-run Show what would be done (default)
|
||||
# --apply Apply firewall rules
|
||||
# --rollback Restore previous rules
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Admin IPs that should have full access (add your IPs here)
|
||||
ADMIN_IPS=(
|
||||
# "YOUR_OFFICE_IP/32"
|
||||
# "YOUR_VPN_IP/32"
|
||||
)
|
||||
|
||||
# Ports to DENY from public (will only be accessible locally)
|
||||
DENY_PORTS=(
|
||||
"9102" # Router
|
||||
"9300" # Gateway (will be proxied via nginx)
|
||||
"6333" # Qdrant
|
||||
"30633" # Qdrant NodePort
|
||||
"9090" # Prometheus
|
||||
"3030" # Grafana
|
||||
"8890" # Swapper
|
||||
"8000" # Memory Service
|
||||
"9500" # RAG Service
|
||||
"8001" # Vision Encoder
|
||||
"8101" # Parser Pipeline
|
||||
)
|
||||
|
||||
# Ports to ALLOW from public
|
||||
ALLOW_PORTS=(
|
||||
"22" # SSH
|
||||
"80" # HTTP (redirect to HTTPS)
|
||||
"443" # HTTPS (nginx proxy)
|
||||
)
|
||||
|
||||
# Parse arguments
|
||||
MODE="dry-run"
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--apply) MODE="apply" ;;
|
||||
--dry-run) MODE="dry-run" ;;
|
||||
--rollback) MODE="rollback" ;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--apply|--dry-run|--rollback]"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "========================================"
|
||||
echo " NODE1 Firewall Hardening"
|
||||
echo " Mode: $MODE"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# Backup current rules
|
||||
backup_rules() {
|
||||
echo "Backing up current UFW rules..."
|
||||
sudo cp /etc/ufw/user.rules /etc/ufw/user.rules.backup.$(date +%Y%m%d_%H%M%S) 2>/dev/null || true
|
||||
sudo cp /etc/ufw/user6.rules /etc/ufw/user6.rules.backup.$(date +%Y%m%d_%H%M%S) 2>/dev/null || true
|
||||
echo "Backup saved to /etc/ufw/user.rules.backup.*"
|
||||
}
|
||||
|
||||
# Apply deny rules
|
||||
apply_deny_rules() {
|
||||
for port in "${DENY_PORTS[@]}"; do
|
||||
if [ "$MODE" = "apply" ]; then
|
||||
echo -e "${YELLOW}Denying${NC} port $port from public..."
|
||||
sudo ufw deny $port/tcp comment "Hardening: internal only" 2>/dev/null || true
|
||||
else
|
||||
echo "[DRY-RUN] Would deny port $port/tcp"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Apply allow rules for admin IPs
|
||||
apply_admin_allowlist() {
|
||||
if [ ${#ADMIN_IPS[@]} -eq 0 ]; then
|
||||
echo -e "${YELLOW}Warning:${NC} No admin IPs configured in ADMIN_IPS array"
|
||||
echo "Add your IPs to enable remote admin access to internal ports"
|
||||
return
|
||||
fi
|
||||
|
||||
for ip in "${ADMIN_IPS[@]}"; do
|
||||
for port in "${DENY_PORTS[@]}"; do
|
||||
if [ "$MODE" = "apply" ]; then
|
||||
echo -e "${GREEN}Allowing${NC} $ip to port $port..."
|
||||
sudo ufw allow from $ip to any port $port proto tcp comment "Admin access" 2>/dev/null || true
|
||||
else
|
||||
echo "[DRY-RUN] Would allow $ip to port $port/tcp"
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
# Ensure public ports are allowed
|
||||
apply_allow_rules() {
|
||||
for port in "${ALLOW_PORTS[@]}"; do
|
||||
if [ "$MODE" = "apply" ]; then
|
||||
echo -e "${GREEN}Ensuring${NC} port $port is allowed..."
|
||||
sudo ufw allow $port/tcp 2>/dev/null || true
|
||||
else
|
||||
echo "[DRY-RUN] Would ensure port $port/tcp is allowed"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Rollback to previous rules
|
||||
rollback_rules() {
|
||||
echo "Looking for backup files..."
|
||||
LATEST_BACKUP=$(ls -t /etc/ufw/user.rules.backup.* 2>/dev/null | head -1)
|
||||
|
||||
if [ -z "$LATEST_BACKUP" ]; then
|
||||
echo -e "${RED}No backup files found!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Restoring from: $LATEST_BACKUP"
|
||||
sudo cp "$LATEST_BACKUP" /etc/ufw/user.rules
|
||||
|
||||
LATEST_BACKUP6=$(ls -t /etc/ufw/user6.rules.backup.* 2>/dev/null | head -1)
|
||||
if [ -n "$LATEST_BACKUP6" ]; then
|
||||
sudo cp "$LATEST_BACKUP6" /etc/ufw/user6.rules
|
||||
fi
|
||||
|
||||
sudo ufw reload
|
||||
echo -e "${GREEN}Rollback complete${NC}"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
case $MODE in
|
||||
"apply")
|
||||
echo "=== Applying firewall hardening ==="
|
||||
backup_rules
|
||||
echo ""
|
||||
apply_deny_rules
|
||||
echo ""
|
||||
apply_admin_allowlist
|
||||
echo ""
|
||||
apply_allow_rules
|
||||
echo ""
|
||||
echo "Reloading UFW..."
|
||||
sudo ufw reload
|
||||
echo ""
|
||||
echo -e "${GREEN}Hardening applied!${NC}"
|
||||
echo ""
|
||||
echo "=== Current UFW Status ==="
|
||||
sudo ufw status numbered | head -30
|
||||
;;
|
||||
"rollback")
|
||||
rollback_rules
|
||||
;;
|
||||
"dry-run")
|
||||
echo "=== DRY RUN - No changes will be made ==="
|
||||
echo ""
|
||||
echo "Would backup current rules..."
|
||||
echo ""
|
||||
echo "Ports to DENY from public:"
|
||||
for port in "${DENY_PORTS[@]}"; do
|
||||
echo " - $port/tcp"
|
||||
done
|
||||
echo ""
|
||||
echo "Ports to ALLOW from public:"
|
||||
for port in "${ALLOW_PORTS[@]}"; do
|
||||
echo " - $port/tcp"
|
||||
done
|
||||
echo ""
|
||||
if [ ${#ADMIN_IPS[@]} -gt 0 ]; then
|
||||
echo "Admin IPs with full access:"
|
||||
for ip in "${ADMIN_IPS[@]}"; do
|
||||
echo " - $ip"
|
||||
done
|
||||
else
|
||||
echo -e "${YELLOW}Note: No admin IPs configured${NC}"
|
||||
fi
|
||||
echo ""
|
||||
echo "Run with --apply to execute these changes"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
35
ops/hardening/fail2ban-nginx.conf
Normal file
35
ops/hardening/fail2ban-nginx.conf
Normal file
@@ -0,0 +1,35 @@
|
||||
#
|
||||
# Fail2ban configuration for NODE1 Nginx
|
||||
# Install: apt-get install fail2ban
|
||||
# Copy to: /etc/fail2ban/jail.d/nginx-node1.conf
|
||||
#
|
||||
|
||||
[nginx-waf]
|
||||
enabled = true
|
||||
port = http,https
|
||||
filter = nginx-waf
|
||||
logpath = /var/log/nginx/waf-blocks.log
|
||||
maxretry = 5
|
||||
findtime = 300
|
||||
bantime = 1800
|
||||
action = iptables-multiport[name=nginx-waf, port="http,https", protocol=tcp]
|
||||
|
||||
[nginx-auth]
|
||||
enabled = true
|
||||
port = http,https
|
||||
filter = nginx-auth
|
||||
logpath = /var/log/nginx/auth-fails.log
|
||||
maxretry = 10
|
||||
findtime = 600
|
||||
bantime = 3600
|
||||
action = iptables-multiport[name=nginx-auth, port="http,https", protocol=tcp]
|
||||
|
||||
[nginx-ratelimit]
|
||||
enabled = true
|
||||
port = http,https
|
||||
filter = nginx-limit-req
|
||||
logpath = /var/log/nginx/api-error.log
|
||||
maxretry = 20
|
||||
findtime = 60
|
||||
bantime = 600
|
||||
action = iptables-multiport[name=nginx-ratelimit, port="http,https", protocol=tcp]
|
||||
213
ops/hardening/security-regression-test.sh
Normal file
213
ops/hardening/security-regression-test.sh
Normal file
@@ -0,0 +1,213 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# NODE1 Security Regression Test
|
||||
# Version: 1.0
|
||||
# Last Updated: 2026-01-26
|
||||
#
|
||||
# Run after each deploy to verify security posture
|
||||
#
|
||||
# Usage: ./security-regression-test.sh [--remote]
|
||||
#
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Config
|
||||
HOST="${TEST_HOST:-https://gateway.daarion.city}"
|
||||
HEALTH_TOKEN="${HEALTH_TOKEN:-dg-health-2026-secret-change-me}"
|
||||
|
||||
passed=0
|
||||
failed=0
|
||||
warnings=0
|
||||
|
||||
# Test helper
|
||||
test_check() {
|
||||
local name="$1"
|
||||
local result="$2"
|
||||
local expected="$3"
|
||||
|
||||
if [ "$result" = "$expected" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}: $name"
|
||||
((passed++))
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC}: $name (got: $result, expected: $expected)"
|
||||
((failed++))
|
||||
fi
|
||||
}
|
||||
|
||||
test_contains() {
|
||||
local name="$1"
|
||||
local haystack="$2"
|
||||
local needle="$3"
|
||||
|
||||
if echo "$haystack" | grep -qi "$needle"; then
|
||||
echo -e "${GREEN}✅ PASS${NC}: $name"
|
||||
((passed++))
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC}: $name (missing: $needle)"
|
||||
((failed++))
|
||||
fi
|
||||
}
|
||||
|
||||
echo "========================================"
|
||||
echo " NODE1 Security Regression Test"
|
||||
echo " $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
echo " Target: $HOST"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# === 1. TLS/HSTS ===
|
||||
echo "=== 1. TLS & Security Headers ==="
|
||||
|
||||
headers=$(curl -sS -k -I "$HOST/ping" 2>&1)
|
||||
|
||||
test_contains "HSTS header present" "$headers" "strict-transport-security"
|
||||
test_contains "X-Frame-Options present" "$headers" "x-frame-options"
|
||||
test_contains "X-Content-Type-Options present" "$headers" "x-content-type-options"
|
||||
test_contains "X-XSS-Protection present" "$headers" "x-xss-protection"
|
||||
test_contains "Content-Security-Policy present" "$headers" "content-security-policy"
|
||||
|
||||
echo ""
|
||||
|
||||
# === 2. HTTP→HTTPS Redirect ===
|
||||
echo "=== 2. HTTP→HTTPS Redirect ==="
|
||||
|
||||
http_host="${HOST/https:/http:}"
|
||||
redirect_code=$(curl -sS -o /dev/null -w "%{http_code}" "$http_host/ping" 2>/dev/null || echo "000")
|
||||
test_check "HTTP redirects to HTTPS" "$redirect_code" "301"
|
||||
|
||||
echo ""
|
||||
|
||||
# === 3. /health Protection ===
|
||||
echo "=== 3. /health Endpoint Protection ==="
|
||||
|
||||
# Without token (should be 401 or blocked)
|
||||
health_no_token=$(curl -sS -k -o /dev/null -w "%{http_code}" "$HOST/health" 2>/dev/null || echo "000")
|
||||
if [ "$health_no_token" = "401" ] || [ "$health_no_token" = "403" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}: /health without token blocked ($health_no_token)"
|
||||
((passed++))
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC}: /health without token NOT blocked ($health_no_token)"
|
||||
((failed++))
|
||||
fi
|
||||
|
||||
# /ping should work without auth
|
||||
ping_code=$(curl -sS -k -o /dev/null -w "%{http_code}" "$HOST/ping" 2>/dev/null || echo "000")
|
||||
test_check "/ping accessible without auth" "$ping_code" "200"
|
||||
|
||||
echo ""
|
||||
|
||||
# === 4. API Auth Gate ===
|
||||
echo "=== 4. API Auth Gate (/v1/*) ==="
|
||||
|
||||
# Without key (should be 401)
|
||||
v1_no_key=$(curl -sS -k -o /dev/null -w "%{http_code}" "$HOST/v1/test" 2>/dev/null || echo "000")
|
||||
test_check "/v1/* without API key returns 401" "$v1_no_key" "401"
|
||||
|
||||
# With invalid key format
|
||||
v1_bad_key=$(curl -sS -k -o /dev/null -w "%{http_code}" -H "Authorization: Bearer invalid" "$HOST/v1/test" 2>/dev/null || echo "000")
|
||||
test_check "/v1/* with invalid key format returns 401" "$v1_bad_key" "401"
|
||||
|
||||
echo ""
|
||||
|
||||
# === 5. WAF Rules ===
|
||||
echo "=== 5. WAF Rules ==="
|
||||
|
||||
# .env should be blocked (444 = connection closed, shows as 000 in curl)
|
||||
env_code=$(curl -sS -k -o /dev/null -w "%{http_code}" --max-time 5 "$HOST/.env" 2>&1 | grep -oE '[0-9]{3}$' | tail -1 || echo "000")
|
||||
# 000 means connection was closed (444), which is blocked
|
||||
if [[ "$env_code" =~ ^0+$ ]] || [ "$env_code" = "444" ] || [ "$env_code" = "403" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}: /.env blocked (connection closed)"
|
||||
((passed++)) || true
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC}: /.env NOT blocked ($env_code)"
|
||||
((failed++)) || true
|
||||
fi
|
||||
|
||||
# .git should be blocked
|
||||
git_code=$(curl -sS -k -o /dev/null -w "%{http_code}" --max-time 5 "$HOST/.git/config" 2>&1 | grep -oE '[0-9]{3}$' | tail -1 || echo "000")
|
||||
if [[ "$git_code" =~ ^0+$ ]] || [ "$git_code" = "444" ] || [ "$git_code" = "403" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}: /.git blocked (connection closed)"
|
||||
((passed++)) || true
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC}: /.git NOT blocked ($git_code)"
|
||||
((failed++)) || true
|
||||
fi
|
||||
|
||||
# SQL injection attempt
|
||||
sql_code=$(curl -sS -k -o /dev/null -w "%{http_code}" "$HOST/?q=select+*+from+users" 2>/dev/null || echo "000")
|
||||
test_check "SQL injection blocked" "$sql_code" "403"
|
||||
|
||||
# wp-admin blocked
|
||||
wp_code=$(curl -sS -k -o /dev/null -w "%{http_code}" --max-time 5 "$HOST/wp-admin/" 2>&1 | grep -oE '[0-9]{3}$' | tail -1 || echo "000")
|
||||
if [[ "$wp_code" =~ ^0+$ ]] || [ "$wp_code" = "444" ] || [ "$wp_code" = "403" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}: /wp-admin blocked (connection closed)"
|
||||
((passed++)) || true
|
||||
else
|
||||
echo -e "${RED}❌ FAIL${NC}: /wp-admin NOT blocked ($wp_code)"
|
||||
((failed++)) || true
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# === 6. Rate Limiting ===
|
||||
echo "=== 6. Rate Limiting ==="
|
||||
|
||||
echo -n "Sending 30 rapid requests to /ping... "
|
||||
got_429=false
|
||||
for i in $(seq 1 30); do
|
||||
code=$(curl -sS -k -o /dev/null -w "%{http_code}" "$HOST/ping" 2>/dev/null || echo "000")
|
||||
if [ "$code" = "429" ]; then
|
||||
got_429=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$got_429" = true ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}: Rate limit (429) triggered"
|
||||
((passed++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠ WARN${NC}: Rate limit (429) not triggered (may need more requests or higher rate)"
|
||||
((warnings++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# === 7. Default Route ===
|
||||
echo "=== 7. Default Route Security ==="
|
||||
|
||||
# Unknown endpoint should not expose info
|
||||
unknown_code=$(curl -sS -k -o /dev/null -w "%{http_code}" "$HOST/unknown-endpoint-xyz" 2>/dev/null || echo "000")
|
||||
if [ "$unknown_code" = "404" ] || [ "$unknown_code" = "401" ] || [ "$unknown_code" = "403" ]; then
|
||||
echo -e "${GREEN}✅ PASS${NC}: Unknown endpoint returns safe code ($unknown_code)"
|
||||
((passed++))
|
||||
else
|
||||
echo -e "${YELLOW}⚠ WARN${NC}: Unknown endpoint returns $unknown_code"
|
||||
((warnings++))
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# === Summary ===
|
||||
echo "========================================"
|
||||
echo " Security Regression Test Summary"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo -e " ${GREEN}Passed:${NC} $passed"
|
||||
echo -e " ${RED}Failed:${NC} $failed"
|
||||
echo -e " ${YELLOW}Warnings:${NC} $warnings"
|
||||
echo ""
|
||||
|
||||
if [ "$failed" -gt 0 ]; then
|
||||
echo -e "${RED}SECURITY REGRESSION DETECTED${NC}"
|
||||
exit 1
|
||||
elif [ "$warnings" -gt 0 ]; then
|
||||
echo -e "${YELLOW}Tests passed with warnings${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${GREEN}All security tests passed${NC}"
|
||||
exit 0
|
||||
fi
|
||||
Reference in New Issue
Block a user