From 4f9bbc48af2bcc6e7068561c93171c86360fb516 Mon Sep 17 00:00:00 2001 From: cclohmar Date: Wed, 18 Mar 2026 22:03:54 +0000 Subject: [PATCH] chore: auto-commit 2026-03-18 22:03 --- wallarm-deploy-ct.sh | 1135 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 1058 insertions(+), 77 deletions(-) diff --git a/wallarm-deploy-ct.sh b/wallarm-deploy-ct.sh index 2f65ad5..3daa9dc 100644 --- a/wallarm-deploy-ct.sh +++ b/wallarm-deploy-ct.sh @@ -1,14 +1,20 @@ #!/bin/bash # ============================================================================== -# WALLARM BULLETPROOF STEALTH DEPLOYER - V1.9.1 (LXC & NETWORK DIAGNOSTIC) +# WALLARM BULLETPROOF DEPLOYMENT SCRIPT - V1.0 # ============================================================================== -# Recent Fixes: -# - Added Network Diagnostics (Phase 0) to verify manual host fixes -# - Relaxed connectivity checks to allow for manual /etc/hosts intervention -# - Improved CentOS 9 AppStream dependency resolution +# Features: +# - OS-agnostic deployment (Ubuntu, Debian, CentOS, RHEL, Alpine, etc.) +# - Docker/containerd installation from static binaries (no package managers) +# - Comprehensive pre-flight checks (sudo, OS, architecture, resources) +# - Resource verification (Wallarm cloud, Docker registry, application server) +# - DAU-friendly error handling with remediation instructions +# - Multiple-run detection and handling +# - Deployment logging with overwrite protection +# - Handshake verification with application server +# - Persistence across reboots (start.sh) # ============================================================================== -# Color definitions +# Color definitions for better UX RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' @@ -16,118 +22,1093 @@ BLUE='\033[0;34m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' BOLD='\033[1m' -NC='\033[0m' +NC='\033[0m' # No Color -# STEALTH TARGETS -BASE_DOMAIN="ct.sechpoint.app" -HUB_DOMAIN="hub.ct.sechpoint.app" -DOCKER_VERSION="29.2.1" +# Logging configuration LOG_FILE="/var/log/wallarm-deployment.log" +DEPLOYMENT_TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S') + +# Cloud endpoints (from Wallarm documentation) +EU_DATA_NODES=("api.wallarm.com" "node-data0.eu1.wallarm.com" "node-data1.eu1.wallarm.com") +US_DATA_NODES=("us1.api.wallarm.com" "node-data0.us1.wallarm.com" "node-data1.us1.wallarm.com") + +# Docker configuration +DOCKER_VERSION="27.0.0" # Current stable version +DOCKER_STATIC_BASE_URL="https://download.docker.com/linux/static/stable" + +# Resource reachability flags (set during verification) +REGISTRY_REACHABLE=false +DOWNLOAD_REACHABLE=false +CLOUD_REGION="" +API_HOST="" + +# ============================================================================== +# LOGGING & ERROR HANDLING FUNCTIONS +# ============================================================================== log_message() { local level="$1" local message="$2" - echo -e "$(date '+%H:%M:%S') [${level}] ${message}" | sudo tee -a "$LOG_FILE" > /dev/null + local timestamp=$(date '+%Y-%m-%d %H:%M:%S') + case "$level" in - "INFO") echo -e "${BLUE}${BOLD}[INFO]${NC} ${message}" ;; - "SUCCESS") echo -e "${GREEN}${BOLD}[SUCCESS]${NC} ${message}" ;; - "WARNING") echo -e "${YELLOW}${BOLD}[WARNING]${NC} ${message}" ;; - "ERROR") echo -e "${RED}${BOLD}[ERROR]${NC} ${message}" ;; - "DIAG") echo -e "${MAGENTA}${BOLD}[DIAG]${NC} ${message}" ;; + "INFO") color="${BLUE}" ;; + "SUCCESS") color="${GREEN}" ;; + "WARNING") color="${YELLOW}" ;; + "ERROR") color="${RED}" ;; + "DEBUG") color="${CYAN}" ;; + *) color="${NC}" ;; esac + + echo -e "${color}[${timestamp}] ${level}: ${message}${NC}" + echo "[${timestamp}] ${level}: ${message}" >> "$LOG_FILE" } -# --- PHASE 0: NETWORK DIAGNOSTICS --- -run_network_diagnostics() { - log_message "INFO" "=== PHASE 0: NETWORK DIAGNOSTICS ===" +fail_with_remediation() { + local error_msg="$1" + local remediation="$2" - local domains=("$BASE_DOMAIN" "$HUB_DOMAIN" "sechpoint.app") + log_message "ERROR" "$error_msg" + echo -e "\n${RED}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${RED}${BOLD}║ DEPLOYMENT FAILED ║${NC}" + echo -e "${RED}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}" + echo -e "\n${YELLOW}${BOLD}Root Cause:${NC} $error_msg" + echo -e "\n${YELLOW}${BOLD}How to Fix:${NC}" + echo -e "$remediation" + echo -e "\n${YELLOW}Check the full log for details:${NC} $LOG_FILE" + exit 1 +} + +validate_sudo_access() { + log_message "INFO" "Validating sudo access..." - for dom in "${domains[@]}"; do - local ip=$(getent hosts "$dom" | awk '{ print $1 }') - if [ -n "$ip" ]; then - log_message "DIAG" "$dom resolves to: ${CYAN}$ip${NC}" + # Check if user can run sudo + if ! command -v sudo >/dev/null 2>&1; then + fail_with_remediation "sudo command not found" \ + "1. Install sudo package for your OS: + - Ubuntu/Debian: apt-get update && apt-get install -y sudo + - CentOS/RHEL: yum install -y sudo + - Alpine: apk add sudo + +2. Add your user to sudoers: + usermod -aG sudo \$(whoami) # Ubuntu/Debian + usermod -aG wheel \$(whoami) # CentOS/RHEL + +3. Log out and log back in for changes to take effect." + fi + + # Test sudo with password prompt if needed + if ! sudo -v; then + fail_with_remediation "sudo authentication failed" \ + "1. Ensure you have sudo privileges: + - Run 'sudo -l' to check your sudo permissions + - Contact your system administrator + +2. If password is required: + - Make sure you know the correct password + - The script will prompt for password when needed + +3. For passwordless sudo: + - Add '\$(whoami) ALL=(ALL) NOPASSWD:ALL' to /etc/sudoers + - Use 'visudo' to edit safely + - WARNING: Only do this in secure environments" + fi + + log_message "SUCCESS" "Sudo access validated" +} + +detect_os_and_version() { + log_message "INFO" "Detecting OS and version..." + + local os_name="" + local os_version="" + + # Check for /etc/os-release first (modern systems) + if [ -f /etc/os-release ]; then + . /etc/os-release + os_name="$ID" + os_version="$VERSION_ID" + # Check for older RedHat/CentOS + elif [ -f /etc/redhat-release ]; then + os_name="rhel" + os_version=$(cat /etc/redhat-release | sed -e 's/.*release \([0-9]\+\)\..*/\1/') + # Check for Alpine + elif [ -f /etc/alpine-release ]; then + os_name="alpine" + os_version=$(cat /etc/alpine-release) + else + os_name=$(uname -s | tr '[:upper:]' '[:lower:]') + os_version=$(uname -r) + fi + + # Normalize OS names + case "$os_name" in + "ubuntu"|"debian"|"centos"|"rhel"|"alpine"|"amzn"|"ol"|"rocky"|"almalinux") + # Valid supported OS + ;; + *) + log_message "WARNING" "OS '$os_name' not explicitly tested but may work" + ;; + esac + + echo "$os_name:$os_version" +} + +detect_architecture() { + log_message "INFO" "Detecting system architecture..." + + local arch=$(uname -m) + local docker_arch="" + + case "$arch" in + x86_64|x64|amd64) + docker_arch="x86_64" + log_message "SUCCESS" "Architecture: x86_64 (Intel/AMD 64-bit)" + ;; + aarch64|arm64) + docker_arch="aarch64" + log_message "SUCCESS" "Architecture: aarch64 (ARM 64-bit)" + ;; + armv7l|armhf) + docker_arch="armhf" + log_message "SUCCESS" "Architecture: armhf (ARM 32-bit)" + ;; + *) + fail_with_remediation "Unsupported architecture: $arch" \ + "This script supports: +1. x86_64 (Intel/AMD 64-bit) - Most common +2. aarch64 (ARM 64-bit) - AWS Graviton, Raspberry Pi 4 +3. armhf (ARM 32-bit) - Older ARM devices + +Your architecture '$arch' is not supported for Docker static binaries. +Consider: +- Using a different machine with supported architecture +- Manual Docker installation from package manager +- Contact Wallarm support for alternative deployment options" + ;; + esac + + echo "$docker_arch" +} + +verify_resources() { + log_message "INFO" "=== PHASE 2: RESOURCE VERIFICATION ===" + + # Get cloud selection from user + log_message "INFO" "Select Wallarm Cloud region" + echo -e "\n${CYAN}${BOLD}Wallarm Cloud Region Selection:${NC}" + echo -e "1. ${YELLOW}US Cloud${NC} (us1.api.wallarm.com) - For US-based deployments" + echo -e "2. ${YELLOW}EU Cloud${NC} (api.wallarm.com) - For EU-based deployments" + + local cloud_choice="" + while [[ ! "$cloud_choice" =~ ^(1|2|US|EU)$ ]]; do + read -p "$(echo -e "${YELLOW}Enter choice [1/US or 2/EU]: ${NC}")" cloud_choice + cloud_choice=$(echo "$cloud_choice" | tr '[:lower:]' '[:upper:]') + + case "$cloud_choice" in + 1|"US") + CLOUD_REGION="US" + API_HOST="us1.api.wallarm.com" + NODES_TO_TEST=("${US_DATA_NODES[@]}") + log_message "INFO" "Selected US Cloud" + ;; + 2|"EU") + CLOUD_REGION="EU" + API_HOST="api.wallarm.com" + NODES_TO_TEST=("${EU_DATA_NODES[@]}") + log_message "INFO" "Selected EU Cloud" + ;; + *) + echo -e "${RED}Invalid choice. Enter 1/US for US Cloud or 2/EU for EU Cloud${NC}" + ;; + esac + done + + # Test Wallarm Cloud endpoints + log_message "INFO" "Testing connectivity to Wallarm Cloud endpoints..." + local all_endpoints_reachable=true + + for endpoint in "${NODES_TO_TEST[@]}"; do + if curl -skI --connect-timeout 10 "https://$endpoint" >/dev/null 2>&1; then + log_message "SUCCESS" "Wallarm endpoint $endpoint is reachable" else - log_message "WARNING" "$dom: ${RED}Unresolved${NC} (Check /etc/hosts)" + log_message "ERROR" "Wallarm endpoint $endpoint is NOT reachable" + all_endpoints_reachable=false fi done -} - -# --- PHASE 1: PRE-FLIGHT & DEPENDENCIES --- -check_pre_flight() { - log_message "INFO" "=== PHASE 1: PRE-FLIGHT CHECKS ===" - [[ $EUID -ne 0 ]] && { log_message "ERROR" "Run as sudo"; exit 1; } - - log_message "INFO" "Ensuring core tools (tar, iptables, curl)..." - # Ensure dnf is used for CentOS 9 - sudo dnf install -y tar iptables-legacy curl procps-ng > /dev/null 2>&1 - - # Final connectivity check before proceeding to downloads - if ! curl -IsL --connect-timeout 3 "https://$BASE_DOMAIN" > /dev/null 2>&1; then - echo -e "\n${RED}${BOLD}STOP:${NC} Cannot reach https://$BASE_DOMAIN" - echo -e "Please ensure your /etc/hosts contains:" - echo -e "${CYAN} $BASE_DOMAIN $HUB_DOMAIN${NC}\n" - exit 1 + if [ "$all_endpoints_reachable" = false ]; then + fail_with_remediation "Some Wallarm Cloud endpoints are not reachable" \ + "1. Check firewall rules: + - Allow outbound HTTPS (port 443) to Wallarm IPs + - US Cloud IPs: 34.96.64.17, 34.110.183.149, 35.235.66.155, 34.102.90.100, 34.94.156.115, 35.235.115.105 + - EU Cloud IPs: 34.160.38.183, 34.144.227.90, 34.90.110.226 + +2. Check network connectivity: + - Run: ping 8.8.8.8 (test general internet) + - Run: nslookup $API_HOST (test DNS) + - Run: curl -v https://$API_HOST (verbose test) + +3. Check proxy settings: + - If behind proxy, set http_proxy/https_proxy environment variables + - Or configure Docker to use proxy + +4. Contact your network administrator if issues persist" fi - log_message "SUCCESS" "Stealth Proxy connectivity verified." + + # Test Docker Registry (hub.docker.com) + log_message "INFO" "Testing connectivity to Docker Registry (hub.docker.com)..." + local registry_status=$(curl -skI --connect-timeout 10 -o /dev/null -w "%{http_code}" "https://hub.docker.com/v2/") + if [[ "$registry_status" == "200" || "$registry_status" == "401" || "$registry_status" == "302" ]]; then + log_message "SUCCESS" "Docker Registry is reachable (Status: $registry_status)" + REGISTRY_REACHABLE=true + else + log_message "WARNING" "Docker Registry may be blocked (Status: $registry_status)" + REGISTRY_REACHABLE=false + fi + + # Test Docker Download Server (download.docker.com) + log_message "INFO" "Testing connectivity to Docker Download Server..." + if curl -skI --connect-timeout 10 "https://download.docker.com" >/dev/null 2>&1; then + log_message "SUCCESS" "Docker Download Server is reachable" + DOWNLOAD_REACHABLE=true + else + log_message "WARNING" "Docker Download Server may be blocked" + DOWNLOAD_REACHABLE=false + fi + + # Check for local Docker binaries as fallback + if [ "$DOWNLOAD_REACHABLE" = false ]; then + log_message "INFO" "Checking for local Docker binary fallback..." + if ls docker-*.tgz 2>/dev/null | grep -q .; then + log_message "SUCCESS" "Found local Docker binary: $(ls docker-*.tgz | head -1)" + else + log_message "WARNING" "No local Docker binaries found. Manual download may be required." + fi + fi + + # Check for local Wallarm image as fallback + if [ "$REGISTRY_REACHABLE" = false ]; then + log_message "INFO" "Checking for local Wallarm image fallback..." + if ls wallarm-node-*.tar 2>/dev/null | grep -q .; then + log_message "SUCCESS" "Found local Wallarm image: $(ls wallarm-node-*.tar | head -1)" + else + log_message "WARNING" "No local Wallarm images found. Manual download may be required." + fi + fi + + # Final resource assessment + if [ "$REGISTRY_REACHABLE" = false ] && [ "$DOWNLOAD_REACHABLE" = false ]; then + log_message "ERROR" "Critical: Neither Docker Registry nor Download Server reachable" + echo -e "\n${YELLOW}${BOLD}Possible workarounds:${NC}" + echo -e "1. Download Docker binary manually:" + echo -e " curl -L '$DOCKER_STATIC_BASE_URL/$architecture/docker-$DOCKER_VERSION.tgz' -o docker.tgz" + echo -e "2. Download Wallarm image manually:" + echo -e " docker pull wallarm/node:latest" + echo -e " docker save wallarm/node:latest -o wallarm-node-latest.tar" + echo -e "3. Place files in current directory and re-run script" + echo -e "\n${RED}Without these resources, deployment cannot proceed.${NC}" + + read -p "$(echo -e "${YELLOW}Do you want to continue anyway? (y/N): ${NC}")" -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + fail_with_remediation "Insufficient resources for deployment" \ + "Please ensure at least one of these is available: +1. Internet access to Docker Registry (hub.docker.com) +2. Internet access to Docker Download Server (download.docker.com) +3. Local Docker binary (docker-*.tgz) in current directory +4. Local Wallarm image (wallarm-node-*.tar) in current directory" + fi + fi + + log_message "SUCCESS" "Resource verification completed" + echo "$CLOUD_REGION:$API_HOST:$REGISTRY_REACHABLE:$DOWNLOAD_REACHABLE" } -# --- PHASE 2: DOCKER ENGINE --- setup_docker_engine() { - log_message "INFO" "=== PHASE 2: DOCKER ENGINE SETUP ===" + local architecture="$1" + log_message "INFO" "Setting up Docker Engine..." - if command -v docker >/dev/null 2>&1; then - log_message "SUCCESS" "Docker already installed." + # Check if Docker is already installed and running + if command -v docker >/dev/null 2>&1 && sudo docker info >/dev/null 2>&1; then + local docker_version=$(docker --version | cut -d' ' -f3 | tr -d ',') + log_message "SUCCESS" "Docker is already installed and running (version $docker_version)" return 0 fi - - ARCH=$(uname -m) - [[ "$ARCH" == "x86_64" ]] && D_ARCH="x86_64" || D_ARCH="aarch64" + log_message "INFO" "Docker not found or not running. Proceeding with installation..." + + # Determine binary source local binary_file="docker-$DOCKER_VERSION.tgz" - local download_url="https://$BASE_DOMAIN/linux/static/stable/$D_ARCH/$binary_file" - - log_message "INFO" "Downloading binaries: $download_url" - curl -fL "$download_url" -o "/tmp/$binary_file" || { log_message "ERROR" "Download failed"; exit 1; } + local binary_path="" - tar xzvf "/tmp/$binary_file" -C /tmp/ > /dev/null - sudo cp /tmp/docker/* /usr/bin/ + if [ "$DOWNLOAD_REACHABLE" = true ]; then + # Download Docker static binary + log_message "INFO" "Downloading Docker static binary for $architecture..." + local download_url="$DOCKER_STATIC_BASE_URL/$architecture/docker-$DOCKER_VERSION.tgz" + + if curl -fL --connect-timeout 30 "$download_url" -o "$binary_file"; then + log_message "SUCCESS" "Downloaded Docker binary: $binary_file" + binary_path="$binary_file" + else + log_message "ERROR" "Failed to download Docker binary from $download_url" + binary_path="" + fi + fi - # LXC Optimization - sudo mkdir -p /etc/docker - echo '{"storage-driver":"vfs","iptables":false}' | sudo tee /etc/docker/daemon.json > /dev/null - + # Fallback: Check for local Docker binary + if [ -z "$binary_path" ]; then + log_message "INFO" "Checking for local Docker binary..." + local local_files=$(ls docker-*.tgz 2>/dev/null | head -1) + if [ -n "$local_files" ]; then + binary_path="$local_files" + log_message "SUCCESS" "Using local Docker binary: $binary_path" + else + fail_with_remediation "No Docker binary available" \ + "Please provide a Docker static binary: +1. Download manually: + curl -L '$DOCKER_STATIC_BASE_URL/$architecture/docker-$DOCKER_VERSION.tgz' -o docker.tgz +2. Or place an existing docker-*.tgz file in current directory +3. Re-run the script after downloading" + fi + fi + + # Extract and install + log_message "INFO" "Extracting Docker binary..." + if ! tar xzvf "$binary_path" >/dev/null 2>&1; then + fail_with_remediation "Failed to extract Docker binary" \ + "The Docker binary file may be corrupted: +1. Delete the corrupted file: rm -f docker-*.tgz +2. Download a fresh copy manually +3. Verify the file integrity: tar -tzf docker-*.tgz should list files" + fi + + log_message "INFO" "Installing Docker binaries to /usr/bin/" + sudo cp docker/* /usr/bin/ 2>/dev/null || { + fail_with_remediation "Failed to copy Docker binaries" \ + "Permission denied copying to /usr/bin/ +1. Ensure you have sudo privileges +2. Check disk space: df -h / +3. Manual installation: + sudo cp docker/* /usr/bin/" + } + + # Cleanup extracted directory + rm -rf docker + + # Configure systemd service + log_message "INFO" "Configuring Docker systemd service..." + + # Check if docker group exists, create if not + if ! getent group docker >/dev/null; then + sudo groupadd docker 2>/dev/null || log_message "WARNING" "Failed to create docker group (may already exist)" + fi + + # Create systemd service file sudo tee /etc/systemd/system/docker.service > /dev/null < /dev/null </dev/null 2>&1; then + log_message "SUCCESS" "Docker daemon started successfully (attempt $attempt/$max_attempts)" + break + fi + log_message "DEBUG" "Waiting for Docker daemon... ($attempt/$max_attempts)" + sleep 2 + attempt=$((attempt + 1)) + done + + if [ $attempt -gt $max_attempts ]; then + fail_with_remediation "Docker daemon failed to start" \ + "Troubleshooting steps: +1. Check Docker logs: sudo journalctl -u docker.service +2. Verify systemd configuration: sudo systemctl status docker +3. Check for port conflicts: sudo netstat -tulpn | grep :2375 +4. Manual start attempt: sudo dockerd --debug +5. Check kernel requirements: uname -r (should be 3.10+) +6. Ensure cgroups are mounted: mount | grep cgroup" + fi + + # Test Docker installation + log_message "INFO" "Testing Docker installation with hello-world..." + if sudo docker run --rm hello-world >/dev/null 2>&1; then + log_message "SUCCESS" "Docker installation verified successfully" + else + log_message "WARNING" "Docker hello-world test failed (network may be restricted)" + fi + + # Add current user to docker group (optional) + log_message "INFO" "Adding current user to docker group..." + if ! groups $(whoami) | grep -q docker; then + sudo usermod -aG docker $(whoami) 2>/dev/null && \ + log_message "SUCCESS" "Added $(whoami) to docker group. Log out and back in for changes to take effect." + fi + + log_message "SUCCESS" "Docker Engine setup completed" } -# --- PHASE 3: DEPLOY --- -deploy_wallarm() { - log_message "INFO" "=== PHASE 3: DEPLOYMENT ===" - log_message "INFO" "Pulling: $HUB_DOMAIN/wallarm/node:latest" - sudo docker pull "$HUB_DOMAIN/wallarm/node:latest" - sudo docker tag "$HUB_DOMAIN/wallarm/node:latest" wallarm/node:latest - log_message "SUCCESS" "Deployment verification successful." +collect_configuration() { + log_message "INFO" "Collecting deployment configuration..." + + # Get ingress port + local default_port=80 + local ingress_port="" + while [[ ! "$ingress_port" =~ ^[0-9]+$ ]] || [ "$ingress_port" -lt 1 ] || [ "$ingress_port" -gt 65535 ]; do + read -p "$(echo -e "${YELLOW}Enter inbound port [${default_port}]: ${NC}")" ingress_port + ingress_port="${ingress_port:-$default_port}" + + if [[ ! "$ingress_port" =~ ^[0-9]+$ ]]; then + echo -e "${RED}Port must be a number${NC}" + elif [ "$ingress_port" -lt 1 ] || [ "$ingress_port" -gt 65535 ]; then + echo -e "${RED}Port must be between 1 and 65535${NC}" + fi + done + + # Calculate monitoring port (ingress + 10) + local monitoring_port=$((ingress_port + 10)) + log_message "INFO" "Monitoring port will be: $monitoring_port" + + # Check for port conflicts + log_message "INFO" "Checking for port conflicts..." + if sudo netstat -tulpn 2>/dev/null | grep -E ":$ingress_port\s" >/dev/null 2>&1; then + fail_with_remediation "Port $ingress_port is already in use" \ + "1. Free up port $ingress_port: + - Stop the service using it: sudo lsof -ti:$ingress_port | xargs kill -9 + - Change your application to use a different port + +2. Choose a different inbound port + - Common alternatives: 8080, 8888, 3000 + - Avoid well-known ports (80, 443) if already used + +3. Check what's using the port: + sudo netstat -tulpn | grep :$ingress_port + sudo lsof -i :$ingress_port" + fi + + if sudo netstat -tulpn 2>/dev/null | grep -E ":$monitoring_port\s" >/dev/null 2>&1; then + log_message "WARNING" "Port $monitoring_port is in use, choosing alternative..." + monitoring_port=$((ingress_port + 100)) + log_message "INFO" "New monitoring port: $monitoring_port" + fi + + # Get application server details + local upstream_ip="" + local upstream_port="" + + echo -e "\n${CYAN}${BOLD}Application Server Configuration:${NC}" + echo -e "${YELLOW}Enter the IP/hostname and port of your backend application${NC}" + + while [[ -z "$upstream_ip" ]]; do + read -p "$(echo -e "${YELLOW}Upstream App IP/Hostname [127.0.0.1]: ${NC}")" upstream_ip + upstream_ip="${upstream_ip:-127.0.0.1}" + + # Validate IP/hostname format + if ! [[ "$upstream_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && \ + ! [[ "$upstream_ip" =~ ^[a-zA-Z0-9.-]+$ ]]; then + echo -e "${RED}Invalid IP/hostname format${NC}" + upstream_ip="" + fi + done + + while [[ ! "$upstream_port" =~ ^[0-9]+$ ]] || [ "$upstream_port" -lt 1 ] || [ "$upstream_port" -gt 65535 ]; do + read -p "$(echo -e "${YELLOW}Upstream App Port [8080]: ${NC}")" upstream_port + upstream_port="${upstream_port:-8080}" + + if [[ ! "$upstream_port" =~ ^[0-9]+$ ]]; then + echo -e "${RED}Port must be a number${NC}" + elif [ "$upstream_port" -lt 1 ] || [ "$upstream_port" -gt 65535 ]; then + echo -e "${RED}Port must be between 1 and 65535${NC}" + fi + done + + # Verify application server reachability + log_message "INFO" "Verifying application server reachability..." + if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$upstream_ip/$upstream_port" 2>/dev/null; then + log_message "SUCCESS" "Application server $upstream_ip:$upstream_port is reachable" + else + log_message "WARNING" "Application server $upstream_ip:$upstream_port is not reachable" + echo -e "${YELLOW}${BOLD}Warning:${NC} Cannot reach application server at $upstream_ip:$upstream_port" + echo -e "${YELLOW}This may cause the Wallarm node to fail. Possible reasons:${NC}" + echo -e "1. Application server is not running" + echo -e "2. Firewall blocking port $upstream_port" + echo -e "3. Wrong IP/hostname" + echo -e "4. Application server not listening on that port" + + read -p "$(echo -e "${YELLOW}Continue anyway? (y/N): ${NC}")" -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + fail_with_remediation "Application server unreachable" \ + "Ensure your application server is accessible: +1. Start your application server +2. Check it's listening: sudo netstat -tulpn | grep :$upstream_port +3. Verify firewall rules allow inbound connections +4. Test connectivity: telnet $upstream_ip $upstream_port +5. If using hostname, verify DNS resolution: nslookup $upstream_ip" + fi + fi + + # Get Wallarm node token + local wallarm_token="" + echo -e "\n${CYAN}${BOLD}Wallarm Node Token:${NC}" + echo -e "${YELLOW}Get your token from Wallarm Console:${NC}" + echo -e "US Cloud: https://us1.my.wallarm.com/nodes" + echo -e "EU Cloud: https://my.wallarm.com/nodes" + echo -e "Create a new 'Wallarm node' and copy the token" + + while [[ -z "$wallarm_token" ]]; do + read -p "$(echo -e "${YELLOW}Paste Wallarm Node Token: ${NC}")" wallarm_token + if [[ -z "$wallarm_token" ]]; then + echo -e "${RED}Token cannot be empty${NC}" + elif [[ ! "$wallarm_token" =~ ^[a-zA-Z0-9_-]+$ ]]; then + echo -e "${RED}Token contains invalid characters${NC}" + wallarm_token="" + fi + done + + # Generate instance names and paths + local instance_name="wallarm-node-$ingress_port" + local instance_dir="/opt/wallarm/$instance_name" + + # Check for existing container with same name + if sudo docker ps -a --format "{{.Names}}" | grep -q "^$instance_name$"; then + log_message "WARNING" "Container '$instance_name' already exists" + echo -e "${YELLOW}A container named '$instance_name' already exists.${NC}" + echo -e "Options:" + echo -e "1. Remove existing container and deploy new" + echo -e "2. Use different port to avoid conflict" + echo -e "3. Exit and manage manually" + + read -p "$(echo -e "${YELLOW}Choose option (1/2/3) [1]: ${NC}")" option + option="${option:-1}" + + case "$option" in + 1) + log_message "INFO" "Removing existing container '$instance_name'..." + sudo docker rm -f "$instance_name" 2>/dev/null || \ + log_message "WARNING" "Failed to remove container (may not exist)" + ;; + 2) + log_message "INFO" "Please restart script with different port" + exit 0 + ;; + 3) + log_message "INFO" "Exiting for manual management" + exit 0 + ;; + *) + log_message "ERROR" "Invalid option" + ;; + esac + fi + + # Create instance directory + log_message "INFO" "Creating instance directory: $instance_dir" + sudo mkdir -p "$instance_dir" + + # Output configuration summary + echo -e "\n${GREEN}${BOLD}✓ Configuration Complete${NC}" + echo -e "${CYAN}Summary:${NC}" + echo -e " Inbound Port: ${YELLOW}$ingress_port${NC}" + echo -e " Monitoring Port: ${YELLOW}$monitoring_port${NC}" + echo -e " Application Server: ${YELLOW}$upstream_ip:$upstream_port${NC}" + echo -e " Wallarm Cloud: ${YELLOW}$CLOUD_REGION ($API_HOST)${NC}" + echo -e " Instance Directory: ${YELLOW}$instance_dir${NC}" + + # Return configuration as colon-separated values + echo "$ingress_port:$monitoring_port:$upstream_ip:$upstream_port:$wallarm_token:$instance_name:$instance_dir" } +deploy_wallarm_node() { + log_message "INFO" "Deploying Wallarm filtering node..." + + # Pull or load Wallarm image + if [ "$REGISTRY_REACHABLE" = true ]; then + log_message "INFO" "Pulling Wallarm Docker image..." + if ! sudo docker pull wallarm/node:latest; then + fail_with_remediation "Failed to pull Wallarm image" \ + "Docker pull failed. Possible reasons: +1. Network connectivity to Docker Hub +2. Docker Hub rate limiting (login required) +3. Insufficient disk space + +Solutions: +1. Check network: docker pull hello-world +2. Login to Docker Hub: docker login +3. Use local image: docker save/load +4. Check disk: df -h /var/lib/docker" + fi + log_message "SUCCESS" "Wallarm image pulled successfully" + else + log_message "INFO" "Registry unreachable, checking for local image..." + local image_files=$(ls wallarm-node-*.tar 2>/dev/null | head -1) + if [ -n "$image_files" ]; then + log_message "INFO" "Loading local image: $image_files" + if ! sudo docker load < "$image_files"; then + fail_with_remediation "Failed to load local Wallarm image" \ + "The local image file may be corrupted: +1. Verify file integrity: tar -tzf $image_files +2. Download fresh image on another machine: + docker pull wallarm/node:latest + docker save wallarm/node:latest -o wallarm-node-latest.tar +3. Transfer file to this machine" + fi + log_message "SUCCESS" "Local Wallarm image loaded" + else + fail_with_remediation "No Wallarm image available" \ + "Please provide a Wallarm Docker image: +1. Download on another machine with internet: + docker pull wallarm/node:latest + docker save wallarm/node:latest -o wallarm-node-latest.tar +2. Transfer file to this machine +3. Place in current directory and re-run script" + fi + fi + + # Create nginx configuration + log_message "INFO" "Creating nginx configuration..." + sudo tee "$INSTANCE_DIR/nginx.conf" > /dev/null < /dev/null <> "\$LOG_FILE" + +# Stop and remove existing container if running +sudo docker stop "\$CONTAINER_NAME" 2>/dev/null || true +sudo docker rm "\$CONTAINER_NAME" 2>/dev/null || true + +# Start new container +sudo docker run -d \\ + --name "\$CONTAINER_NAME" \\ + --restart always \\ + --network host \\ + -p $INGRESS_PORT:80 \\ + -p $MONITORING_PORT:90 \\ + -e WALLARM_API_TOKEN="$WALLARM_TOKEN" \\ + -e WALLARM_API_HOST="$API_HOST" \\ + -e NGINX_BACKEND="$UPSTREAM_IP:$UPSTREAM_PORT" \\ + -e WALLARM_MODE="monitoring" \\ + -v "\$NGINX_CONFIG:/etc/nginx/http.d/default.conf:ro" \\ + wallarm/node:latest + +echo "\$(date) - Container started with ID: \$(sudo docker ps -q -f name=\$CONTAINER_NAME)" >> "\$LOG_FILE" + +# Verify container is running +sleep 3 +if sudo docker ps | grep -q "\$CONTAINER_NAME"; then + echo "\$(date) - Verification: Container is running" >> "\$LOG_FILE" + echo "Wallarm node \$CONTAINER_NAME started successfully" +else + echo "\$(date) - ERROR: Container failed to start" >> "\$LOG_FILE" + sudo docker logs "\$CONTAINER_NAME" >> "\$LOG_FILE" 2>&1 + exit 1 +fi +EOF + + sudo chmod +x "$INSTANCE_DIR/start.sh" + + # Deploy container using start.sh + log_message "INFO" "Deploying Wallarm container..." + if ! sudo "$INSTANCE_DIR/start.sh"; then + log_message "ERROR" "Container deployment failed, checking logs..." + sudo docker logs "$INSTANCE_NAME" 2>&1 | tail -20 | while read line; do + log_message "DEBUG" "Container log: $line" + done + + fail_with_remediation "Wallarm container deployment failed" \ + "Common issues: +1. Invalid Wallarm token + - Verify token in Wallarm Console + - Ensure token matches selected cloud region (US/EU) + +2. Port conflicts + - Check ports $INGRESS_PORT and $MONITORING_PORT are free + - sudo netstat -tulpn | grep -E ':$INGRESS_PORT|:$MONITORING_PORT' + +3. Docker permissions + - Ensure Docker daemon is running: sudo systemctl status docker + - Check user in docker group: groups + +4. Resource constraints + - Check memory: free -h + - Check disk: df -h + +5. View full logs: sudo docker logs $INSTANCE_NAME" + fi + + log_message "SUCCESS" "Wallarm node deployed successfully" + log_message "INFO" "Container Name: $INSTANCE_NAME" + log_message "INFO" "Instance Directory: $INSTANCE_DIR" + log_message "INFO" "Start Script: $INSTANCE_DIR/start.sh" + + # Create systemd service for auto-start on boot + log_message "INFO" "Creating systemd service for automatic startup..." + sudo tee "/etc/systemd/system/wallarm-$INSTANCE_NAME.service" > /dev/null </dev/null || \ + log_message "WARNING" "Failed to enable systemd service (may already exist)" + + log_message "SUCCESS" "Systemd service created: wallarm-$INSTANCE_NAME.service" +} + +verify_deployment() { + log_message "INFO" "Verifying deployment..." + + # Wait for container to be fully started + log_message "INFO" "Waiting for Wallarm node to initialize (30 seconds)..." + sleep 30 + + # Check container status + log_message "INFO" "Checking container status..." + if ! sudo docker ps --format "table {{.Names}}\t{{.Status}}" | grep -q "$INSTANCE_NAME"; then + fail_with_remediation "Container is not running" \ + "The Wallarm container failed to start: +1. Check container logs: sudo docker logs $INSTANCE_NAME +2. Verify Docker service: sudo systemctl status docker +3. Check resource constraints: sudo docker stats $INSTANCE_NAME +4. Review start script: $INSTANCE_DIR/start.sh" + fi + + local container_status=$(sudo docker ps --format "table {{.Names}}\t{{.Status}}" | grep "$INSTANCE_NAME") + log_message "SUCCESS" "Container status: $container_status" + + # Test monitoring endpoint + log_message "INFO" "Testing monitoring endpoint (port $MONITORING_PORT)..." + local max_attempts=10 + local attempt=1 + local monitoring_ok=false + + while [ $attempt -le $max_attempts ]; do + if curl -s "http://localhost:$MONITORING_PORT/wallarm-status" 2>/dev/null | grep -q "requests"; then + monitoring_ok=true + log_message "SUCCESS" "Monitoring endpoint responding (attempt $attempt/$max_attempts)" + break + fi + log_message "DEBUG" "Monitoring endpoint not ready yet ($attempt/$max_attempts)" + sleep 5 + attempt=$((attempt + 1)) + done + + if [ "$monitoring_ok" = false ]; then + log_message "WARNING" "Monitoring endpoint not responding as expected" + log_message "INFO" "Checking alternative..." + # Try direct container exec + if sudo docker exec "$INSTANCE_NAME" curl -s http://localhost:90/wallarm-status 2>/dev/null | grep -q "requests"; then + log_message "SUCCESS" "Monitoring endpoint works inside container" + else + log_message "ERROR" "Monitoring endpoint completely unavailable" + fi + fi + + # Test handshake through filtering node to application server + log_message "INFO" "Testing handshake through filtering node to application server..." + local handshake_ok=false + + for i in {1..5}; do + local response_code=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: test.local" "http://localhost:$INGRESS_PORT/health" --connect-timeout 10) + if [[ "$response_code" =~ ^(200|301|302|401|403)$ ]]; then + handshake_ok=true + log_message "SUCCESS" "Handshake successful: HTTP $response_code from application server (attempt $i/5)" + break + else + log_message "DEBUG" "Handshake attempt $i failed: HTTP $response_code" + sleep 3 + fi + done + + if [ "$handshake_ok" = false ]; then + log_message "WARNING" "Handshake to application server failed" + echo -e "${YELLOW}${BOLD}Warning:${NC} Cannot reach application server through filtering node" + echo -e "${YELLOW}Possible issues:${NC}" + echo -e "1. Application server not responding on $UPSTREAM_IP:$UPSTREAM_PORT" + echo -e "2. Network routing/firewall between container and application server" + echo -e "3. NGINX configuration issue" + echo -e "4. Application server requires specific Host header" + + # Test direct connection to application server + log_message "INFO" "Testing direct connection to application server..." + if timeout 5 bash -c "cat < /dev/null > /dev/tcp/$UPSTREAM_IP/$UPSTREAM_PORT" 2>/dev/null; then + log_message "SUCCESS" "Application server is directly reachable" + else + log_message "ERROR" "Application server is not reachable even directly" + fi + else + log_message "SUCCESS" "Handshake verification passed" + fi + + # Perform attack simulation test (safe) + log_message "INFO" "Performing attack simulation test (safe SQL injection probe)..." + local attack_test_url="http://localhost:$INGRESS_PORT/?id=%27OR+1%3D1--" + local attack_response=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: test.local" "$attack_test_url" --connect-timeout 10) + + if [[ "$attack_response" =~ ^(200|301|302|401|403|404)$ ]]; then + log_message "SUCCESS" "Attack simulation test passed: HTTP $attack_response" + log_message "INFO" "Note: Wallarm in monitoring mode logs attacks but doesn't block" + else + log_message "WARNING" "Attack simulation test returned unexpected code: $attack_response" + fi + + # Check Wallarm synchronization status + log_message "INFO" "Checking Wallarm cloud synchronization..." + local sync_attempts=0 + local sync_ok=false + + while [ $sync_attempts -lt 5 ]; do + if sudo docker logs "$INSTANCE_NAME" 2>&1 | tail -20 | grep -i "sync\|connected\|success" | grep -v "error\|fail" >/dev/null; then + sync_ok=true + log_message "SUCCESS" "Wallarm cloud synchronization detected" + break + fi + sync_attempts=$((sync_attempts + 1)) + sleep 10 + done + + if [ "$sync_ok" = false ]; then + log_message "WARNING" "Wallarm cloud synchronization not confirmed in logs" + log_message "INFO" "Check synchronization manually in Wallarm Console" + fi + + # Final deployment summary + echo -e "\n${GREEN}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}${BOLD}║ DEPLOYMENT VERIFICATION COMPLETE ║${NC}" + echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}" + echo -e "\n${CYAN}${BOLD}Deployment Summary:${NC}" + echo -e " ${YELLOW}✓${NC} Container: ${GREEN}$INSTANCE_NAME${NC} (running)" + echo -e " ${YELLOW}✓${NC} Inbound Port: ${GREEN}$INGRESS_PORT${NC}" + echo -e " ${YELLOW}✓${NC} Monitoring Port: ${GREEN}$MONITORING_PORT${NC} (http://localhost:$MONITORING_PORT/wallarm-status)" + echo -e " ${YELLOW}✓${NC} Application Server: ${GREEN}$UPSTREAM_IP:$UPSTREAM_PORT${NC}" + echo -e " ${YELLOW}✓${NC} Wallarm Cloud: ${GREEN}$CLOUD_REGION${NC}" + echo -e " ${YELLOW}✓${NC} Instance Directory: ${GREEN}$INSTANCE_DIR${NC}" + echo -e " ${YELLOW}✓${NC} Start Script: ${GREEN}$INSTANCE_DIR/start.sh${NC}" + echo -e " ${YELLOW}✓${NC} Systemd Service: ${GREEN}wallarm-$INSTANCE_NAME.service${NC}" + echo -e "\n${CYAN}${BOLD}Next Steps:${NC}" + echo -e " 1. Monitor attacks in ${YELLOW}Wallarm Console${NC}" + echo -e " 2. Switch to ${YELLOW}blocking mode${NC} when ready:" + echo -e " Edit ${GREEN}$INSTANCE_DIR/nginx.conf${NC} and change 'monitoring' to 'block'" + echo -e " 3. Restart container: ${GREEN}sudo $INSTANCE_DIR/start.sh${NC}" + echo -e " 4. Check logs: ${GREEN}sudo docker logs -f $INSTANCE_NAME${NC}" + echo -e "\n${YELLOW}Deployment log: $LOG_FILE${NC}" + + # Write success marker + sudo tee "$INSTANCE_DIR/DEPLOYMENT_SUCCESS" > /dev/null </dev/null || { + echo -e "${RED}Cannot create log file at $LOG_FILE${NC}" + echo -e "${YELLOW}Falling back to current directory...${NC}" + LOG_FILE="./wallarm-deployment.log" + truncate -s 0 "$LOG_FILE" 2>/dev/null || > "$LOG_FILE" + } + sudo chmod 644 "$LOG_FILE" 2>/dev/null || chmod 644 "$LOG_FILE" + exec > >(tee "$LOG_FILE") 2>&1 + + log_message "INFO" "=== Wallarm Bulletproof Deployment Started ===" + + # Phase 1: Pre-flight validation + log_message "INFO" "=== PHASE 1: PRE-FLIGHT VALIDATION ===" + validate_sudo_access + local os_info=$(detect_os_and_version) + local os_name=$(echo "$os_info" | cut -d: -f1) + local os_version=$(echo "$os_info" | cut -d: -f2) + log_message "SUCCESS" "OS detected: $os_name $os_version" + + local architecture=$(detect_architecture) + log_message "SUCCESS" "Architecture: $architecture" + + log_message "INFO" "Pre-flight validation completed successfully" + + # Phase 2: Resource verification + local resource_info=$(verify_resources) + CLOUD_REGION=$(echo "$resource_info" | cut -d: -f1) + API_HOST=$(echo "$resource_info" | cut -d: -f2) + REGISTRY_REACHABLE=$(echo "$resource_info" | cut -d: -f3) + DOWNLOAD_REACHABLE=$(echo "$resource_info" | cut -d: -f4) + + log_message "SUCCESS" "Resource verification completed:" + log_message "SUCCESS" " Cloud Region: $CLOUD_REGION" + log_message "SUCCESS" " API Host: $API_HOST" + log_message "SUCCESS" " Registry Reachable: $REGISTRY_REACHABLE" + log_message "SUCCESS" " Download Reachable: $DOWNLOAD_REACHABLE" + + # Phase 3: Docker engine setup + log_message "INFO" "=== PHASE 3: DOCKER ENGINE SETUP ===" + setup_docker_engine "$architecture" + + # Phase 4: Configuration collection + log_message "INFO" "=== PHASE 4: CONFIGURATION COLLECTION ===" + local config_info=$(collect_configuration) + INGRESS_PORT=$(echo "$config_info" | cut -d: -f1) + MONITORING_PORT=$(echo "$config_info" | cut -d: -f2) + UPSTREAM_IP=$(echo "$config_info" | cut -d: -f3) + UPSTREAM_PORT=$(echo "$config_info" | cut -d: -f4) + WALLARM_TOKEN=$(echo "$config_info" | cut -d: -f5) + INSTANCE_NAME=$(echo "$config_info" | cut -d: -f6) + INSTANCE_DIR=$(echo "$config_info" | cut -d: -f7) + + log_message "SUCCESS" "Configuration collected:" + log_message "SUCCESS" " Ingress Port: $INGRESS_PORT" + log_message "SUCCESS" " Monitoring Port: $MONITORING_PORT" + log_message "SUCCESS" " Upstream: $UPSTREAM_IP:$UPSTREAM_PORT" + log_message "SUCCESS" " Instance: $INSTANCE_NAME" + log_message "SUCCESS" " Directory: $INSTANCE_DIR" + + # Phase 5: Deployment + log_message "INFO" "=== PHASE 5: DEPLOYMENT ===" + deploy_wallarm_node + + # Phase 6: Verification + log_message "INFO" "=== PHASE 6: VERIFICATION ===" + verify_deployment + + log_message "SUCCESS" "=== WALLARM DEPLOYMENT COMPLETED SUCCESSFULLY ===" + echo -e "\n${GREEN}${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}" + echo -e "${GREEN}${BOLD}║ WALLARM FILTERING NODE DEPLOYMENT SUCCESSFUL ║${NC}" + echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}" + echo -e "\n${CYAN}The Wallarm filtering node is now active and protecting your application.${NC}" + echo -e "${YELLOW}Full deployment log: $LOG_FILE${NC}" + echo -e "${YELLOW}Instance directory: $INSTANCE_DIR${NC}" + echo -e "\n${GREEN}To stop the node:${NC} sudo docker stop $INSTANCE_NAME" + echo -e "${GREEN}To restart:${NC} sudo $INSTANCE_DIR/start.sh" + echo -e "${GREEN}To view logs:${NC} sudo docker logs -f $INSTANCE_NAME" + echo -e "\n${MAGENTA}${BOLD}Thank you for using Wallarm!${NC}" } +# Execute main function main "$@" \ No newline at end of file