989 lines
No EOL
38 KiB
Bash
989 lines
No EOL
38 KiB
Bash
#!/bin/bash
|
|
# ==============================================================================
|
|
# WALLARM NODE DEPLOYMENT SCRIPT - V1.0
|
|
# ==============================================================================
|
|
# 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 for better UX
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
MAGENTA='\033[0;35m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# 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"
|
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
case "$level" in
|
|
"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"
|
|
}
|
|
|
|
fail_with_remediation() {
|
|
local error_msg="$1"
|
|
local remediation="$2"
|
|
|
|
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..."
|
|
|
|
# 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 "ERROR" "Wallarm endpoint $endpoint is NOT reachable"
|
|
all_endpoints_reachable=false
|
|
fi
|
|
done
|
|
|
|
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
|
|
|
|
# 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"
|
|
}
|
|
|
|
setup_docker_engine() {
|
|
local architecture="$1"
|
|
log_message "INFO" "Setting up Docker Engine..."
|
|
|
|
# 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
|
|
|
|
log_message "INFO" "Docker not found or not running. Proceeding with installation..."
|
|
|
|
# Determine binary source
|
|
local binary_file="docker-$DOCKER_VERSION.tgz"
|
|
local binary_path=""
|
|
|
|
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
|
|
|
|
# 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 <<EOF
|
|
[Unit]
|
|
Description=Docker Engine
|
|
After=network-online.target firewalld.service containerd.service
|
|
Wants=network-online.target
|
|
Requires=docker.socket
|
|
|
|
[Service]
|
|
Type=notify
|
|
ExecStart=/usr/bin/dockerd --group docker
|
|
ExecReload=/bin/kill -s HUP \$MAINPID
|
|
TimeoutSec=0
|
|
RestartSec=2
|
|
Restart=always
|
|
StartLimitBurst=3
|
|
StartLimitInterval=60s
|
|
LimitNOFILE=infinity
|
|
LimitNPROC=infinity
|
|
LimitCORE=infinity
|
|
TasksMax=infinity
|
|
Delegate=yes
|
|
KillMode=process
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Create docker socket
|
|
sudo tee /etc/systemd/system/docker.socket > /dev/null <<EOF
|
|
[Unit]
|
|
Description=Docker Socket for the API
|
|
|
|
[Socket]
|
|
ListenStream=/var/run/docker.sock
|
|
SocketMode=0660
|
|
SocketUser=root
|
|
SocketGroup=docker
|
|
|
|
[Install]
|
|
WantedBy=sockets.target
|
|
EOF
|
|
|
|
# Reload systemd and enable service
|
|
log_message "INFO" "Starting Docker daemon..."
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable docker.service docker.socket
|
|
sudo systemctl start docker
|
|
|
|
# Wait for Docker to start
|
|
local max_attempts=30
|
|
local attempt=1
|
|
while [ $attempt -le $max_attempts ]; do
|
|
if sudo docker info >/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"
|
|
}
|
|
|
|
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 <<EOF
|
|
server {
|
|
listen 80;
|
|
server_name _;
|
|
wallarm_mode monitoring;
|
|
|
|
location / {
|
|
proxy_pass http://$UPSTREAM_IP:$UPSTREAM_PORT;
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
|
|
# Timeouts
|
|
proxy_connect_timeout 60s;
|
|
proxy_send_timeout 60s;
|
|
proxy_read_timeout 60s;
|
|
}
|
|
|
|
# Health check endpoint
|
|
location /health {
|
|
access_log off;
|
|
return 200 "healthy\n";
|
|
add_header Content-Type text/plain;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 90;
|
|
server_name _;
|
|
|
|
location /wallarm-status {
|
|
wallarm_status on;
|
|
allow all;
|
|
access_log off;
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Create start.sh script for persistence across reboots
|
|
log_message "INFO" "Creating start.sh script for persistence..."
|
|
sudo tee "$INSTANCE_DIR/start.sh" > /dev/null <<EOF
|
|
#!/bin/bash
|
|
# Wallarm Node start script - Auto-generated
|
|
# This script ensures the Wallarm node starts on system boot
|
|
|
|
set -e
|
|
|
|
CONTAINER_NAME="$INSTANCE_NAME"
|
|
NGINX_CONFIG="$INSTANCE_DIR/nginx.conf"
|
|
LOG_FILE="$INSTANCE_DIR/container.log"
|
|
|
|
echo "\$(date) - Starting Wallarm node \$CONTAINER_NAME" >> "\$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 <<EOF
|
|
[Unit]
|
|
Description=Wallarm Filtering Node $INSTANCE_NAME
|
|
After=docker.service
|
|
Requires=docker.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|
|
WorkingDirectory=$INSTANCE_DIR
|
|
ExecStart=$INSTANCE_DIR/start.sh
|
|
TimeoutStartSec=0
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable "wallarm-$INSTANCE_NAME.service" 2>/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}"
|
|
}
|
|
|
|
# Execute main function
|
|
main "$@" |