diff --git a/install.sh b/install.sh index c70d338..6a1ce22 100644 --- a/install.sh +++ b/install.sh @@ -1,55 +1,168 @@ #!/bin/bash set -e +# --- Configuration --- GREEN="\033[1;32m" RED="\033[1;31m" +YELLOW="\033[1;33m" NC="\033[0m" # No Color +REPO_URL="https://github.com/fabriziosalmi/caddy-waf.git" +REPO_DIR="caddy-waf" +GO_VERSION_REQUIRED="1.22.3" +GO_VERSION_TARGET="1.23.4" +XCADDY_VERSION="latest" # or specify a version like "v0.3.7" +GEOLITE2_DB_URL="https://git.io/GeoLite2-Country.mmdb" +GEOLITE2_DB_FILE="GeoLite2-Country.mmdb" -# Helper functions +# --- Helper Functions --- print_success() { - echo -e "${GREEN}✅ $1${NC}" + echo -e "${GREEN}✅ Success: $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠️ Warning: $1${NC}" +} + +print_info() { + echo -e "ℹ️ Info: $1${NC}" } print_error() { - echo -e "${RED}❌ $1${NC}" + echo -e "${RED}❌ Error: $1${NC}" + echo -e "${RED} $1${NC}" >&2 # Print error to stderr as well exit 1 } -# Detect OS -OS=$(uname -s) -GO_VERSION_REQUIRED="1.22.3" -GO_VERSION_TARGET="1.23.4" +check_command_exists() { + if ! command -v "$1" &> /dev/null; then + return 1 # Command not found + else + return 0 # Command found + fi +} + +ensure_go_installed() { + if ! check_command_exists go; then + print_info "Go not found. Installing Go $GO_VERSION_TARGET..." + install_go + else + check_go_version + fi +} + +ensure_xcaddy_installed() { + if ! check_command_exists xcaddy; then + print_info "xcaddy not found. Installing xcaddy..." + install_xcaddy + else + print_info "xcaddy is already installed." + fi +} + +clone_or_update_repo() { + if [ -d "$REPO_DIR" ]; then + print_info "$REPO_DIR directory already exists. Pulling latest changes..." + cd "$REPO_DIR" || print_error "Could not change directory to $REPO_DIR" + git pull origin main || print_error "Failed to pull $REPO_DIR updates." # Specify origin and branch + cd .. # Go back to the script's directory + else + print_info "Cloning $REPO_DIR repository..." + git clone "$REPO_URL" "$REPO_DIR" || print_error "Failed to clone repository from $REPO_URL." + fi + cd "$REPO_DIR" || print_error "Could not change directory to $REPO_DIR after clone/pull" +} + +update_dependencies() { + print_info "Running go mod tidy..." + go mod tidy || print_error "Failed to run go mod tidy." + print_success "Dependencies updated (go mod tidy)." + + print_info "Fetching Go modules..." + go get -v github.com/caddyserver/caddy/v2 github.com/caddyserver/caddy/v2/caddyconfig/caddyfile github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile github.com/caddyserver/caddy/v2/modules/caddyhttp github.com/oschwald/maxminddb-golang github.com/fsnotify/fsnotify github.com/fabriziosalmi/caddy-waf || print_error "Failed to fetch Go modules." # Combined go get + print_success "Go modules fetched successfully." +} + +download_geolite2_db() { + if [ ! -f "$GEOLITE2_DB_FILE" ]; then + print_info "Downloading GeoLite2 Country database..." + wget -q "$GEOLITE2_DB_URL" -O "$GEOLITE2_DB_FILE" || print_error "Failed to download GeoLite2 database from $GEOLITE2_DB_URL." # Specify output file + print_success "GeoLite2 database downloaded." + else + print_info "GeoLite2 database already exists." + fi +} + +build_caddy() { + print_info "Building Caddy with caddy-waf module..." + xcaddy build --with github.com/fabriziosalmi/caddy-waf=./ || print_error "Failed to build Caddy." + print_success "Caddy built successfully." +} + +format_caddyfile() { + print_info "Formatting Caddyfile..." + ./caddy fmt --overwrite || print_warning "Failed to format Caddyfile (This is a warning, Caddy might still run)." # Warning instead of error + print_success "Caddyfile formatted." +} + +run_caddy() { + print_info "Starting Caddy server..." + ./caddy run || print_error "Failed to start Caddy." # Keep as error, Caddy run failure is critical + print_success "Caddy is running." # This line will likely not be reached in a typical run +} + + +# --- OS Specific Functions --- install_go_linux() { - echo -e "⚙️ Installing Go $GO_VERSION_TARGET for Linux..." - wget https://go.dev/dl/go${GO_VERSION_TARGET}.linux-amd64.tar.gz && \ - sudo rm -rf /usr/local/go && \ - sudo tar -C /usr/local -xzf go${GO_VERSION_TARGET}.linux-amd64.tar.gz && \ - rm go${GO_VERSION_TARGET}.linux-amd64.tar.gz && \ - export PATH=$PATH:/usr/local/go/bin && \ - print_success "Go $GO_VERSION_TARGET installed successfully." || print_error "Failed to install Go." + print_info "Installing Go $GO_VERSION_TARGET for Linux..." + GOBIN="/usr/local/go/bin" # Define GOBIN for clarity + wget "https://go.dev/dl/go${GO_VERSION_TARGET}.linux-amd64.tar.gz" || print_error "Failed to download Go for Linux." + sudo rm -rf /usr/local/go + sudo tar -C /usr/local -xzf "go${GO_VERSION_TARGET}.linux-amd64.tar.gz" || print_error "Failed to extract Go for Linux." + rm "go${GO_VERSION_TARGET}.linux-amd64.tar.gz" + export PATH="$PATH:$GOBIN" # Ensure PATH is updated in current shell + print_success "Go $GO_VERSION_TARGET installed successfully on Linux." } install_go_macos() { - echo -e "⚙️ Installing Go $GO_VERSION_TARGET for macOS..." - curl -LO https://go.dev/dl/go${GO_VERSION_TARGET}.darwin-amd64.pkg && \ - sudo installer -pkg go${GO_VERSION_TARGET}.darwin-amd64.pkg -target / && \ - rm go${GO_VERSION_TARGET}.darwin-amd64.pkg && \ - export PATH=$PATH:/usr/local/go/bin && \ - print_success "Go $GO_VERSION_TARGET installed successfully." || print_error "Failed to install Go." + print_info "Installing Go $GO_VERSION_TARGET for macOS..." + curl -LO "https://go.dev/dl/go${GO_VERSION_TARGET}.darwin-amd64.pkg" || print_error "Failed to download Go for macOS." + sudo installer -pkg "go${GO_VERSION_TARGET}.darwin-amd64.pkg" -target / || print_error "Failed to install Go package on macOS." + rm "go${GO_VERSION_TARGET}.darwin-amd64.pkg" + export PATH="$PATH:/usr/local/go/bin" # Ensure PATH is updated in current shell + print_success "Go $GO_VERSION_TARGET installed successfully on macOS." +} + +install_xcaddy() { + print_info "Installing xcaddy $XCADDY_VERSION..." + GOBIN="$(go env GOBIN)" + if [ -z "$GOBIN" ]; then + GOBIN="$HOME/go/bin" # Default GOBIN if not set + fi + go install "github.com/caddyserver/xcaddy/cmd/xcaddy@$XCADDY_VERSION" || print_error "Failed to install xcaddy." + export PATH="$PATH:$GOBIN" # Ensure PATH is updated in current shell + print_success "xcaddy $XCADDY_VERSION installed successfully." } check_go_version() { local version - version=$(go version | awk '{print $3}' | sed 's/go//') + version=$(go version 2>&1 | awk '{print $3}' | sed 's/go//') # Redirect stderr to stdout for error handling + if [[ "$version" == *"error"* ]]; then # Check for "error" string in output + print_warning "Failed to get Go version. Assuming Go is outdated or not properly installed. Attempting to install Go $GO_VERSION_TARGET." + install_go # Attempt reinstall if version check fails + return + fi + if [[ $(printf '%s\n' "$GO_VERSION_REQUIRED" "$version" | sort -V | head -n1) != "$GO_VERSION_REQUIRED" ]]; then - print_error "Go version must be $GO_VERSION_REQUIRED or newer. Current version: $version" + print_warning "Go version $version is older than required $GO_VERSION_REQUIRED. Attempting to install Go $GO_VERSION_TARGET." + install_go # Attempt reinstall if version is too old else - print_success "Go version $version meets requirements." + print_info "Go version $version meets requirements ($GO_VERSION_REQUIRED or newer)." fi } install_go() { + OS=$(uname -s) if [ "$OS" == "Linux" ]; then install_go_linux elif [ "$OS" == "Darwin" ]; then @@ -57,67 +170,21 @@ install_go() { else print_error "Unsupported OS: $OS" fi + check_go_version # Re-check version after install to confirm success } -# Install Go if not present or outdated -if ! command -v go &> /dev/null; then - echo -e "⚙️ Go not found. Installing Go $GO_VERSION_TARGET..." - install_go -else - echo -e "🔍 Checking Go version..." - check_go_version -fi -# Install xcaddy if not present -if ! command -v xcaddy &> /dev/null; then - echo -e "⚙️ xcaddy not found. Installing xcaddy..." - go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest && \ - export PATH=$PATH:$(go env GOPATH)/bin && \ - print_success "xcaddy installed successfully." || print_error "Failed to install xcaddy." -else - print_success "xcaddy is already installed." -fi +# --- Main Script Execution --- -# Clone the repository -if [ -d "caddy-waf" ]; then - echo -e "🔄 caddy-waf directory already exists. Pulling latest changes..." - cd caddy-waf && git pull || print_error "Failed to pull caddy-waf updates." -else - echo -e "📥 Cloning caddy-waf repository..." - git clone https://github.com/fabriziosalmi/caddy-waf.git && \ - cd caddy-waf && \ - print_success "caddy-waf repository cloned successfully." || print_error "Failed to clone repository." -fi +print_info "Starting setup for caddy-waf..." -# Clean and update dependencies -echo -e "📦 Running go mod tidy..." -go mod tidy && print_success "Dependencies updated (go mod tidy)." || print_error "Failed to run go mod tidy." - -echo -e "🔍 Fetching Go modules..." -go get -v github.com/caddyserver/caddy/v2 github.com/caddyserver/caddy/v2/caddyconfig/caddyfile github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile github.com/caddyserver/caddy/v2 github.com/caddyserver/caddy/v2/modules/caddyhttp github.com/oschwald/maxminddb-golang github.com/fsnotify/fsnotify github.com/fabriziosalmi/caddy-waf && \ -print_success "Go modules fetched successfully." || print_error "Failed to fetch Go modules." - -# Download GeoLite2 Country database -if [ ! -f "GeoLite2-Country.mmdb" ]; then - echo -e "🌍 Downloading GeoLite2 Country database..." - wget -q https://git.io/GeoLite2-Country.mmdb && \ - print_success "GeoLite2 database downloaded." || print_error "Failed to download GeoLite2 database." -else - print_success "GeoLite2 database already exists." -fi - -# Build with xcaddy -echo -e "⚙️ Building Caddy with caddy-waf module..." -xcaddy build --with github.com/fabriziosalmi/caddy-waf=./ && \ -print_success "Caddy built successfully." || print_error "Failed to build Caddy." - -# Format Caddyfile -echo -e "🧹 Formatting Caddyfile..." -./caddy fmt --overwrite && \ -print_success "Caddyfile formatted." || print_error "Failed to format Caddyfile." - -# Run Caddy -echo -e "🚀 Starting Caddy server..." -./caddy run && \ -print_success "Caddy is running." || print_error "Failed to start Caddy." +ensure_go_installed +ensure_xcaddy_installed +clone_or_update_repo +update_dependencies +download_geolite2_db +build_caddy +format_caddyfile +run_caddy +print_success "caddy-waf setup completed!" \ No newline at end of file diff --git a/rules.go b/rules.go index ef53f64..52791f0 100644 --- a/rules.go +++ b/rules.go @@ -23,6 +23,8 @@ func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, ru zap.String("value", value), zap.String("description", rule.Description), zap.Int("score", rule.Score), + zap.Int("anomaly_threshold_config", m.AnomalyThreshold), // ADDED: Log configured anomaly threshold + zap.Int("current_anomaly_score", state.TotalScore), // ADDED: Log current anomaly score before increment ) // Rule Hit Counter - Refactored for clarity @@ -52,16 +54,19 @@ func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, ru } } - m.logRequest(zapcore.DebugLevel, "Anomaly score increased", r, // 'r' is now the 3rd argument + m.logRequest(zapcore.DebugLevel, "Determining Block Action", r, // More descriptive log message zap.String("action", rule.Action), zap.Bool("should_block", shouldBlock), zap.String("block_reason", blockReason), + zap.Int("total_score", state.TotalScore), // ADDED: Log total score in block decision log + zap.Int("anomaly_threshold", m.AnomalyThreshold), // ADDED: Log anomaly threshold in block decision log ) if shouldBlock { m.blockRequest(w, r, state, http.StatusForbidden, blockReason, string(rule.ID), value, zap.Int("total_score", state.TotalScore), zap.Int("anomaly_threshold", m.AnomalyThreshold), + zap.String("final_block_reason", blockReason), // ADDED: Clarify block reason in blockRequest log ) return false } @@ -70,6 +75,8 @@ func (m *Middleware) processRuleMatch(w http.ResponseWriter, r *http.Request, ru m.logRequest(zapcore.InfoLevel, "Rule action: Log", r, zap.String("log_id", logID), zap.String("rule_id", string(rule.ID)), + zap.Int("total_score", state.TotalScore), // ADDED: Log total score for log action + zap.Int("anomaly_threshold", m.AnomalyThreshold), // ADDED: Log anomaly threshold for log action ) } else if !shouldBlock && !state.ResponseWritten { m.logRequest(zapcore.DebugLevel, "Rule action: No Block", r,