From b9a9bee0b929d8f3022d1534d997a6bf25ff2b1c Mon Sep 17 00:00:00 2001 From: Joao Gilberto Magalhaes Date: Thu, 4 Dec 2025 10:07:17 -0500 Subject: [PATCH] Revamp Swarm example documentation and streamline deployment instructions - Consolidated and simplified Swarm example README to focus on YAML header comments for documentation. - Removed redundant and extended sections, replacing with concise steps for getting started. - Standardized YAML headers across `easyhaproxy.yml`, `services.yml`, and `portainer.yml` with a clearer and more readable format. - Enhanced user guidance for deployment, setup requirements, verification, and cleanup. --- examples/docker/README.md | 608 +---------- examples/docker/docker-compose-acme.yml | 62 +- .../docker/docker-compose-changed-label.yml | 40 +- examples/docker/docker-compose-cloudflare.yml | 56 +- .../docker/docker-compose-ip-whitelist.yml | 54 +- .../docker/docker-compose-jwt-validator.yml | 67 +- .../docker-compose-multi-containers.yml | 53 +- examples/docker/docker-compose-php-fpm.yml | 60 +- .../docker-compose-plugins-combined.yml | 78 +- .../docker-compose-portainer-app-example.yml | 43 + examples/docker/docker-compose-portainer.yml | 53 + examples/docker/docker-compose.yml | 58 +- examples/kubernetes/README.md | 719 +------------ examples/kubernetes/cloudflare.yml | 79 +- examples/kubernetes/ip-whitelist.yml | 51 +- examples/kubernetes/jwt-validator.yml | 88 +- examples/kubernetes/plugins-combined.yml | 81 +- examples/kubernetes/service.yml | 54 + examples/kubernetes/service_tls.yml | 55 + examples/static/README.md | 463 ++------- examples/static/docker-compose.yml | 59 +- examples/swarm/README.md | 941 +----------------- examples/swarm/cloudflare.yml | 75 +- examples/swarm/easyhaproxy.yml | 53 +- examples/swarm/ip-whitelist.yml | 70 +- examples/swarm/jwt-validator.yml | 91 +- examples/swarm/plugins-combined.yml | 104 +- examples/swarm/portainer.yml | 43 +- examples/swarm/services.yml | 62 +- 29 files changed, 1459 insertions(+), 2861 deletions(-) diff --git a/examples/docker/README.md b/examples/docker/README.md index 04085ff..ecf146f 100644 --- a/examples/docker/README.md +++ b/examples/docker/README.md @@ -1,590 +1,52 @@ # Docker Compose Examples -This directory contains various Docker Compose examples demonstrating different EasyHAProxy configurations. +Self-contained examples for EasyHAProxy. **All documentation is in the docker-compose files as header comments.** -## Prerequisites: Generate SSL Certificates +## Quick Start -**IMPORTANT:** Before running any examples, you must generate the required SSL certificates and JWT keys: +1. Pick an example below +2. Open the docker-compose file +3. Read the header comments for complete instructions +4. Run the commands step-by-step -```bash -# From the repository root -./examples/generate-keys.sh -``` +## Basic Examples -This script automatically generates: -- SSL certificates for host1.local and host2.local -- JWT keys (jwt_private.pem and jwt_pubkey.pem) for JWT validation examples -- All other .pem files needed for testing +| File | Description | +|----------------------------------------------------------------------------|----------------------------------------------------------------| +| [docker-compose.yml](docker-compose.yml) | Basic SSL setup with two virtual hosts and stats interface | +| [docker-compose-acme.yml](docker-compose-acme.yml) | Let's Encrypt SSL with automatic certificate generation | +| [docker-compose-multi-containers.yml](docker-compose-multi-containers.yml) | Load balancing across multiple container replicas | +| [docker-compose-changed-label.yml](docker-compose-changed-label.yml) | Using custom label prefix (for multiple EasyHAProxy instances) | -**Note:** These are self-signed certificates for testing only. Do not use in production. +## Real-World Application Examples ---- - -## Examples Overview - -### 1. Basic Configuration (`docker-compose.yml`) - -**What it demonstrates:** -- Basic SSL setup with two virtual hosts -- SSL redirect (HTTP → HTTPS) -- Custom SSL certificates (embedded and file-based) -- HAProxy stats interface - -**Features:** -- `host1.local`: SSL certificate embedded as base64 in labels -- `host2.local`: SSL certificate loaded from file (`host2.local.pem`) -- Automatic HTTP to HTTPS redirect -- Stats available at port 1936 - -**Usage:** -```bash -docker compose up -d -``` - -**Test:** -```bash -# Test HTTPS -curl -k -H "Host: host1.local" https://127.0.0.1/ -curl -k -H "Host: host2.local" https://127.0.0.1/ - -# Test HTTP redirect -curl -I -H "Host: host1.local" http://127.0.0.1 -# Should return: HTTP/1.1 301 Moved Permanently - -# View SSL certificate -openssl s_client -showcerts -connect 127.0.0.1:443 -servername host1.local -``` - -**Access stats:** -- URL: http://localhost:1936 -- Username: `admin` -- Password: `password` - ---- - -### 2. ACME/Let's Encrypt (`docker-compose-acme.yml`) - -**What it demonstrates:** -- Automatic SSL certificate generation using Let's Encrypt -- HTTP-01 ACME challenge -- Certificate persistence - -**Requirements:** -- Public IP address pointing to your machine -- Open ports 80 and 443 in firewall -- Valid domain name - -**Configuration:** -```yaml -EASYHAPROXY_CERTBOT_EMAIL: user@example.com # Change this! -easyhaproxy.http.certbot: true # Enable certbot -``` - -**Usage:** -```bash -# Edit docker-compose-acme.yml and set: -# - EASYHAPROXY_CERTBOT_EMAIL to your email -# - easyhaproxy.http.host to your domain - -docker compose -f docker-compose-acme.yml up -d -``` - -**Certificate storage:** -Certificates are persisted in `./certs/certbot/` to avoid re-challenges on restart. - ---- - -### 3. Multiple Containers with Load Balancing (`docker-compose-multi-containers.yml`) - -**What it demonstrates:** -- Multiple containers behind single domain -- Load balancing with round-robin -- Domain redirect functionality - -**Features:** -- 2 replicas of nginx container -- Load balancing across replicas -- Domain redirect: `google.helloworld.com` → `www.google.com` - -**Usage:** -```bash -docker compose -f docker-compose-multi-containers.yml up -d -``` - -**Test:** -```bash -# Test load balancing (hostname changes between containers) -curl -H "Host: www.helloworld.com" localhost:19901 -# Response: f6d8d45b7411 -curl -H "Host: www.helloworld.com" localhost:19901 -# Response: 59b213cb8592 - -# Test redirect -curl -I -H "Host: google.helloworld.com" localhost:19901 -# Should redirect to: www.google.com/ -``` - ---- - -### 4. Changed Label Prefix (`docker-compose-changed-label.yml`) - -**What it demonstrates:** -- Using custom label prefix instead of default `easyhaproxy` -- Useful for running multiple EasyHAProxy instances - -**Configuration:** -```yaml -environment: - EASYHAPROXY_LABEL_PREFIX: myproxy -``` - -**Container labels:** -```yaml -labels: - myproxy.http.host: example.com - myproxy.http.port: 80 -``` - - -**Usage:** -```bash -docker compose -f docker-compose-changed-label.yml up -d -``` - -**Test:** -```bash -# Test load balancing (hostname changes between containers) -curl -H "Host: www.helloworld.com" localhost:19901 -# Response: f6d8d45b7411 -curl -H "Host: www.helloworld.com" localhost:19901 -# Response: 59b213cb8592 - -# Test redirect -curl -I -H "Host: google.helloworld.com" localhost:19901 -# Should redirect to: www.google.com/ -``` - ---- - -### 5. Portainer Integration (`docker-compose-portainer.yml`) - -**What it demonstrates:** -- Running Portainer behind EasyHAProxy -- Real-world application example - -**Usage:** -```bash -docker compose -f docker-compose-portainer.yml up -d -``` - -**Access Portainer:** -- URL: http://portainer.local (add to `/etc/hosts` or use real DNS) -- First time: Create admin user - ---- - -### 6. Portainer + App Example (`docker-compose-portainer-app-example.yml`) - -**What it demonstrates:** -- Multiple applications behind EasyHAProxy -- Portainer + custom app setup - -**Usage:** -```bash -docker compose -f docker-compose-portainer-app-example.yml up -d -``` - ---- +| File | Description | +|--------------------------------------------------------------------------------------|-----------------------------------------------------| +| [docker-compose-portainer.yml](docker-compose-portainer.yml) | Portainer behind EasyHAProxy with Let's Encrypt | +| [docker-compose-portainer-app-example.yml](docker-compose-portainer-app-example.yml) | Additional app alongside Portainer (shared network) | ## Plugin Examples -### FastCGI Plugin with PHP-FPM +| File | Description | +|----------------------------------------------------------------------------|-----------------------------------------------------| +| [docker-compose-php-fpm.yml](docker-compose-php-fpm.yml) | FastCGI plugin with PHP-FPM and PATH_INFO routing | +| [docker-compose-jwt-validator.yml](docker-compose-jwt-validator.yml) | JWT token validation for API protection | +| [docker-compose-ip-whitelist.yml](docker-compose-ip-whitelist.yml) | IP whitelist for admin panels or sensitive services | +| [docker-compose-cloudflare.yml](docker-compose-cloudflare.yml) | Restore real client IPs when behind Cloudflare CDN | +| [docker-compose-plugins-combined.yml](docker-compose-plugins-combined.yml) | Multiple plugins combined for layered security | -Run PHP applications with FastCGI protocol support: +## Documentation Structure -**File:** `docker-compose-php-fpm.yml` +Each docker-compose file contains: +- **WHAT THIS DEMONSTRATES** - Key features and concepts +- **REQUIREMENTS** - Idempotent setup commands (safe to run multiple times) +- **HOW TO START** - Command to launch the stack +- **HOW TO VERIFY IT'S WORKING** - Test commands with expected outputs +- **CLEAN UP** - Commands to stop and remove resources -**What it demonstrates:** -- PHP-FPM 8.5 with TCP connection on port 9000 -- FastCGI protocol support (`proto: fcgi`) -- FastCGI plugin for PHP environment configuration -- Custom document root and index file -- PATH_INFO support for RESTful routing +## Additional Documentation -**Features:** -- HAProxy forwards requests to PHP-FPM via TCP (port 9000) -- FastCGI plugin generates `fcgi-app` configuration that defines CGI parameters: - - `SCRIPT_FILENAME`, `DOCUMENT_ROOT`, `REQUEST_URI` - - `QUERY_STRING`, `REQUEST_METHOD`, `CONTENT_TYPE` - - `SERVER_NAME`, `SERVER_PORT`, `HTTPS` - - `PATH_INFO` (for routing support) -- Sample PHP application included in `php-app/` directory - -**Configuration:** -```yaml -services: - haproxy: - image: byjg/easy-haproxy:4.6.0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - environment: - EASYHAPROXY_DISCOVER: docker - ports: - - "80:80/tcp" - - php-fpm: - image: byjg/php:8.5-fpm - volumes: - - ./php-app:/var/www/html:ro - labels: - easyhaproxy.http.host: phpapp.local - easyhaproxy.http.port: 80 - # PHP-FPM listens on port 9000 - easyhaproxy.http.localport: 9000 - easyhaproxy.http.proto: fcgi - # Enable FastCGI plugin - easyhaproxy.http.plugins: fastcgi - easyhaproxy.http.plugin.fastcgi.document_root: /var/www/html - easyhaproxy.http.plugin.fastcgi.index_file: index.php - easyhaproxy.http.plugin.fastcgi.path_info: "true" -``` - -**Usage:** -```bash -# Add to /etc/hosts -echo "127.0.0.1 phpapp.local" | sudo tee -a /etc/hosts - -# Start the stack -docker compose -f docker-compose-php-fpm.yml up -d - -# Test PHP application -curl http://phpapp.local/ -curl http://phpapp.local/info.php -curl http://phpapp.local/test-path-info.php/users/123 -``` - -**Alternative: Unix Socket Connection** - -For PHP-FPM images that support Unix sockets, you can use socket connection: - -```yaml -services: - haproxy: - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - php-fpm-socket:/run/php - - php-fpm: - image: php:8.2-fpm # Official PHP image supports sockets - volumes: - - php-fpm-socket:/run/php - - ./php-app:/var/www/html:ro - labels: - easyhaproxy.http.host: phpapp.local - easyhaproxy.http.port: 80 - easyhaproxy.http.socket: /run/php/php-fpm.sock - easyhaproxy.http.proto: fcgi - easyhaproxy.http.plugins: fastcgi - # ... plugin configuration - -volumes: - php-fpm-socket: -``` - -**Sample Application:** - -The `php-app/` directory contains: -- `index.php` - Main page showing FastCGI environment -- `info.php` - PHP configuration info (phpinfo) -- `test-path-info.php` - PATH_INFO routing demonstration - -**What the FastCGI plugin does:** -1. Sets `SCRIPT_FILENAME` with proper document root path -2. Handles directory requests (appends `index.php`) -3. Sets all standard CGI environment variables -4. Enables `PATH_INFO` for RESTful URL routing -5. Supports custom FastCGI parameters - ---- - -### JWT Validator Plugin - -Protect your API with JWT token validation: - -```yaml -services: - haproxy: - image: byjg/easy-haproxy:4.6.0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./jwt_pubkey.pem:/etc/haproxy/jwt_keys/api_pubkey.pem:ro - environment: - EASYHAPROXY_DISCOVER: docker - HAPROXY_USERNAME: admin - HAPROXY_PASSWORD: password - HAPROXY_STATS_PORT: 1936 - ports: - - "80:80/tcp" - - "443:443/tcp" - - "1936:1936/tcp" - - api: - image: my-api:latest - labels: - easyhaproxy.http.host: api.example.com - easyhaproxy.http.port: 80 - easyhaproxy.http.localport: 8080 - # Enable JWT validation - easyhaproxy.http.plugins: jwt_validator - easyhaproxy.http.plugin.jwt_validator.algorithm: RS256 - easyhaproxy.http.plugin.jwt_validator.issuer: https://auth.example.com/ - easyhaproxy.http.plugin.jwt_validator.audience: https://api.example.com - easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/haproxy/jwt_keys/api_pubkey.pem -``` - -**What it validates:** -- Authorization header presence -- JWT signing algorithm -- JWT issuer and audience -- JWT signature using public key -- JWT expiration time - -**Test:** -```bash -# Without token - should fail -curl http://api.example.com/endpoint -# Response: Missing Authorization HTTP header - -# With valid JWT token -curl -H "Authorization: Bearer eyJhbGc..." http://api.example.com/endpoint -# Response: Success -``` - -**Generate test public key:** -```bash -# Generate private key -openssl genrsa -out jwt_private.pem 2048 - -# Extract public key -openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem -``` - ---- - -### Cloudflare IP Restoration Plugin - -Restore original visitor IPs when using Cloudflare CDN: - -```yaml -services: - haproxy: - image: byjg/easy-haproxy:4.6.0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./cloudflare_ips.lst:/etc/haproxy/cloudflare_ips.lst:ro - environment: - EASYHAPROXY_DISCOVER: docker - ports: - - "80:80/tcp" - - "443:443/tcp" - - webapp: - image: my-webapp:latest - labels: - easyhaproxy.http.host: myapp.com - easyhaproxy.http.port: 80 - easyhaproxy.http.localport: 3000 - # Enable Cloudflare plugin - easyhaproxy.http.plugins: cloudflare -``` - -**Setup Cloudflare IP list:** -```bash -# Download Cloudflare IP ranges -curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst -curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst -``` - -**What it does:** -- Detects requests from Cloudflare IPs -- Restores original visitor IP from `CF-Connecting-IP` header -- Your application logs show real visitor IPs, not Cloudflare IPs - ---- - -### IP Whitelist Plugin - -Restrict access to specific IP addresses: - -```yaml -services: - haproxy: - image: byjg/easy-haproxy:4.6.0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - environment: - EASYHAPROXY_DISCOVER: docker - ports: - - "80:80/tcp" - - admin_panel: - image: admin-panel:latest - labels: - easyhaproxy.http.host: admin.example.com - easyhaproxy.http.port: 80 - easyhaproxy.http.localport: 8080 - # Enable IP whitelist - easyhaproxy.http.plugins: ip_whitelist - easyhaproxy.http.plugin.ip_whitelist.allowed_ips: 192.168.1.0/24,10.0.0.5 - easyhaproxy.http.plugin.ip_whitelist.status_code: 403 -``` - -**Allowed IP formats:** -- Single IP: `10.0.0.5` -- CIDR range: `192.168.1.0/24` -- Multiple (comma-separated): `192.168.1.0/24,10.0.0.5,172.16.0.100` - -**Test:** -```bash -# From allowed IP -curl http://admin.example.com -# Response: Success - -# From blocked IP -curl http://admin.example.com -# Response: HTTP 403 Forbidden -``` - ---- - -### Multiple Plugins Combined - -Combine multiple plugins for enhanced security: - -```yaml -services: - haproxy: - image: byjg/easy-haproxy:4.6.0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./cloudflare_ips.lst:/etc/haproxy/cloudflare_ips.lst:ro - environment: - EASYHAPROXY_DISCOVER: docker - ports: - - "80:80/tcp" - - "443:443/tcp" - - webapp: - image: webapp:latest - labels: - easyhaproxy.http.host: myapp.example.com - easyhaproxy.http.port: 80 - easyhaproxy.http.localport: 8080 - # Enable multiple plugins - easyhaproxy.http.plugins: cloudflare,deny_pages - # Block specific paths - easyhaproxy.http.plugin.deny_pages.paths: /admin,/wp-admin,/wp-login.php,/.env - easyhaproxy.http.plugin.deny_pages.status_code: 404 - - api: - image: api:latest - labels: - easyhaproxy.http.host: api.example.com - easyhaproxy.http.port: 80 - easyhaproxy.http.localport: 3000 - # Combine JWT + IP whitelist + path blocking - easyhaproxy.http.plugins: jwt_validator,ip_whitelist,deny_pages - easyhaproxy.http.plugin.jwt_validator.algorithm: RS256 - easyhaproxy.http.plugin.jwt_validator.issuer: https://auth.example.com/ - easyhaproxy.http.plugin.jwt_validator.pubkey_path: /etc/haproxy/jwt_keys/api.pem - easyhaproxy.http.plugin.ip_whitelist.allowed_ips: 192.168.0.0/16,10.0.0.0/8 - easyhaproxy.http.plugin.deny_pages.paths: /internal,/debug -``` - -**Plugin execution order:** -1. IP Whitelist (blocks non-whitelisted IPs) -2. Deny Pages (blocks specific paths) -3. JWT Validator (validates authentication) - ---- - -## Common Configuration Options - -### Environment Variables (HAProxy Container) - -| Variable | Description | Default | -|-----------------------------|---------------------------|----------| -| `EASYHAPROXY_DISCOVER` | Discovery mode | `docker` | -| `EASYHAPROXY_SSL_MODE` | SSL mode (loose/strict) | `strict` | -| `EASYHAPROXY_CERTBOT_EMAIL` | Email for Let's Encrypt | - | -| `HAPROXY_CUSTOMERRORS` | Enable custom error pages | `false` | -| `HAPROXY_USERNAME` | Stats username | - | -| `HAPROXY_PASSWORD` | Stats password | - | -| `HAPROXY_STATS_PORT` | Stats port | `1936` | - -### Container Labels - -| Label | Description | Example | -|---------------------------------|----------------------|---------------| -| `easyhaproxy.http.host` | Virtual host domain | `example.com` | -| `easyhaproxy.http.port` | External port | `80` | -| `easyhaproxy.http.localport` | Container port | `8080` | -| `easyhaproxy.http.redirect_ssl` | Force HTTPS redirect | `true` | -| `easyhaproxy.http.certbot` | Enable Let's Encrypt | `true` | -| `easyhaproxy.https.ssl` | Enable SSL | `true` | -| `easyhaproxy.https.sslcert` | Base64 SSL cert | `LS0t...` | - -For complete documentation, see [Container Labels](../../docs/container-labels.md). - -## Tips - -1. **Local Testing with Fake Domains:** - Add entries to `/etc/hosts`: - ``` - 127.0.0.1 host1.local host2.local portainer.local - ``` - -2. **Viewing Logs:** - ```bash - docker compose logs -f haproxy - ``` - -3. **Reloading Configuration:** - EasyHAProxy automatically detects changes. Watch logs for reload events. - -4. **Generating Test SSL Certificates:** - ```bash - openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ - -keyout host.key -out host.crt \ - -subj "/CN=host1.local" - cat host.crt host.key > host.pem - ``` - -5. **Base64 Encoding SSL Certificate:** - ```bash - base64 -w 0 host.pem - ``` - -## Troubleshooting - -**Issue:** Container not detected -- Check labels are correct (prefix, syntax) -- Verify Docker socket is mounted -- Check logs: `docker compose logs haproxy` - -**Issue:** SSL not working -- Verify certificate format (cert + key in same PEM file) -- Check certificate matches domain -- Verify SSL mode (`loose` vs `strict`) - -**Issue:** Let's Encrypt fails -- Ensure ports 80/443 are publicly accessible -- Verify domain DNS points to your IP -- Check certbot logs in HAProxy container - -## Further Reading - -- [Docker Configuration Guide](../../docs/docker.md) - [Container Labels Reference](../../docs/container-labels.md) -- [ACME/Let's Encrypt Guide](../../docs/acme.md) +- [Docker Configuration Guide](../../docs/docker.md) - [Environment Variables](../../docs/environment-variable.md) +- [Plugin Documentation](../../docs/plugins/) diff --git a/examples/docker/docker-compose-acme.yml b/examples/docker/docker-compose-acme.yml index 495ac45..a7bb4c6 100644 --- a/examples/docker/docker-compose-acme.yml +++ b/examples/docker/docker-compose-acme.yml @@ -1,8 +1,62 @@ -# This example shows how to setup HTTP-01 ACME CA Challenge +# ============================================================================== +# EXAMPLE: Let's Encrypt SSL with ACME/Certbot +# ============================================================================== # -# You need -# - public IP pointing your machine -# - open ports 80 and 443 in your firewall +# WHAT THIS DEMONSTRATES: +# - Automatic SSL certificate generation using Let's Encrypt +# - HTTP-01 ACME challenge protocol +# - Certificate persistence across container restarts +# - Auto-renewal of certificates +# +# REQUIREMENTS (run these first): +# ```bash +# # You MUST have: +# # - A public IP address pointing to your machine +# # - Ports 80 and 443 open in your firewall +# # - A valid domain name with DNS configured +# +# # Edit this file and change: +# # - Line 21: EASYHAPROXY_CERTBOT_EMAIL to your email +# # - Line 36: easyhaproxy.http.host to your real domain +# +# # Create certs directory +# mkdir -p ./certs/certbot +# ``` +# +# HOW TO START: +# ```bash +# docker compose -f docker-compose-acme.yml up -d +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check logs for certificate issuance +# docker compose -f docker-compose-acme.yml logs -f haproxy +# # Look for: "Successfully received certificate" +# +# # Test HTTPS with real domain (replace test.xpto.us with your domain) +# curl https://test.xpto.us/ +# # Expected: 200 OK with valid SSL certificate +# +# # Verify certificate +# openssl s_client -showcerts -connect test.xpto.us:443 < /dev/null | grep "Issuer:" +# # Expected: Issuer: C = US, O = Let's Encrypt +# +# # Check certificate files +# ls -la ./certs/certbot/ +# # Expected: Your domain certificate files +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-acme.yml down +# # Keep certificates: +# # docker compose -f docker-compose-acme.yml down +# # Remove certificates too: +# # docker compose -f docker-compose-acme.yml down && rm -rf ./certs/certbot +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/docker/docker-compose-changed-label.yml b/examples/docker/docker-compose-changed-label.yml index 94d1253..70af8c3 100644 --- a/examples/docker/docker-compose-changed-label.yml +++ b/examples/docker/docker-compose-changed-label.yml @@ -1,8 +1,40 @@ -# To test: -# curl -k -H "Host: host1.local" https://127.0.0.1/ +# ============================================================================== +# EXAMPLE: Custom Label Prefix +# ============================================================================== # -# or add to /etc/hosts -# 127.0.0.1 host1.local +# WHAT THIS DEMONSTRATES: +# - Using a custom label prefix instead of default "easyhaproxy" +# - Useful for running multiple EasyHAProxy instances +# - Custom label configuration (haproxy.* instead of easyhaproxy.*) +# +# REQUIREMENTS (run these first): +# ```bash +# # Add to /etc/hosts (idempotent) +# grep -q "host1.local" /etc/hosts || echo "127.0.0.1 host1.local" | sudo tee -a /etc/hosts +# ``` +# +# HOW TO START: +# ```bash +# docker compose -f docker-compose-changed-label.yml up -d +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Test HTTPS +# curl -k -H "Host: host1.local" https://127.0.0.1/ +# # Expected: 200 OK with hostname in response +# +# # Verify custom label prefix is working +# docker inspect $(docker ps -q -f "ancestor=byjg/static-httpserver") | grep "haproxy.http" +# # Expected: Labels starting with "haproxy." instead of "easyhaproxy." +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-changed-label.yml down +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/docker/docker-compose-cloudflare.yml b/examples/docker/docker-compose-cloudflare.yml index 79c7518..49bfe50 100644 --- a/examples/docker/docker-compose-cloudflare.yml +++ b/examples/docker/docker-compose-cloudflare.yml @@ -1,28 +1,48 @@ -# Cloudflare IP Restoration Plugin Example +# ============================================================================== +# EXAMPLE: Cloudflare IP Restoration Plugin +# ============================================================================== # -# This example demonstrates restoring original visitor IPs when using Cloudflare CDN +# WHAT THIS DEMONSTRATES: +# - Restoring original visitor IPs when behind Cloudflare CDN +# - Detecting requests from Cloudflare IP ranges +# - Using CF-Connecting-IP header for real client IP +# - Accurate IP logging for applications behind Cloudflare # -# Prerequisites: -# 1. Download Cloudflare IP ranges: -# curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst -# curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst +# REQUIREMENTS (run these first): +# ```bash +# # Download Cloudflare IP ranges (idempotent - overwrites if exists) +# curl -s https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst +# curl -s https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst # -# 2. Add to /etc/hosts: -# 127.0.0.1 myapp.local +# # Add to /etc/hosts (idempotent) +# grep -q "myapp.local" /etc/hosts || echo "127.0.0.1 myapp.local" | sudo tee -a /etc/hosts +# ``` # -# 3. Start the stack: -# docker compose -f docker-compose-cloudflare.yml up -d +# HOW TO START: +# ```bash +# docker compose -f docker-compose-cloudflare.yml up -d +# ``` # -# 4. Test (simulating Cloudflare request): -# # Without CF-Connecting-IP header: -# curl -H "Host: myapp.local" http://127.0.0.1/ +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Test normal request +# curl -H "Host: myapp.local" http://127.0.0.1/ +# # Expected: 200 OK # -# # With CF-Connecting-IP header (simulating Cloudflare): -# curl -H "Host: myapp.local" -H "CF-Connecting-IP: 203.0.113.50" http://127.0.0.1/ +# # Test with CF-Connecting-IP header (simulating Cloudflare) +# curl -H "Host: myapp.local" -H "CF-Connecting-IP: 203.0.113.50" http://127.0.0.1/ +# # Expected: 200 OK (backend sees 203.0.113.50 as client IP) # -# Note: This plugin is most useful when your site is actually behind Cloudflare. -# Without Cloudflare, the request won't come from Cloudflare IPs, so the plugin -# won't activate. This example is for demonstration and testing purposes. +# # Note: This plugin is most useful when your site is actually behind Cloudflare +# # In production, requests come from Cloudflare IPs and the plugin restores real client IPs +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-cloudflare.yml down +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/docker/docker-compose-ip-whitelist.yml b/examples/docker/docker-compose-ip-whitelist.yml index 87be787..02d7970 100644 --- a/examples/docker/docker-compose-ip-whitelist.yml +++ b/examples/docker/docker-compose-ip-whitelist.yml @@ -1,23 +1,49 @@ -# IP Whitelist Plugin Example +# ============================================================================== +# EXAMPLE: IP Whitelist Plugin +# ============================================================================== # -# This example demonstrates restricting access to specific IP addresses +# WHAT THIS DEMONSTRATES: +# - Restricting access to specific IP addresses or CIDR ranges +# - Single IP, CIDR notation, and multiple IP support +# - Custom HTTP status code for blocked requests +# - Admin panel or sensitive application protection # -# Prerequisites: -# 1. Add to /etc/hosts: -# 127.0.0.1 admin.local +# REQUIREMENTS (run these first): +# ```bash +# # Add to /etc/hosts (idempotent) +# grep -q "admin.local" /etc/hosts || echo "127.0.0.1 admin.local" | sudo tee -a /etc/hosts # -# 2. Start the stack: -# docker compose -f docker-compose-ip-whitelist.yml up -d +# # IMPORTANT: Update the allowed_ips in this file (line 52) with your actual IPs! +# # Default allows localhost and private networks for testing +# ``` # -# 3. Test from localhost (127.0.0.1 is whitelisted): -# curl http://admin.local/ -# # Response: Success (200 OK) +# HOW TO START: +# ```bash +# docker compose -f docker-compose-ip-whitelist.yml up -d +# ``` # -# 4. Test from non-whitelisted IP: -# # You'll need to test from another machine or configure the example -# # with your actual IP address in the allowed_ips label +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Test from localhost (127.0.0.1 is whitelisted) +# curl http://admin.local/ +# # Expected: 200 OK - Access granted # -# Note: Update the allowed_ips label with your actual IP addresses/networks +# # Test from non-whitelisted IP +# # You'll need to test from another machine or temporarily remove your IP +# # from the allowed_ips list to see the 403 Forbidden response +# +# # View HAProxy stats to see blocked requests +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-ip-whitelist.yml down +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/docker/docker-compose-jwt-validator.yml b/examples/docker/docker-compose-jwt-validator.yml index b5e4b70..f3f49a1 100644 --- a/examples/docker/docker-compose-jwt-validator.yml +++ b/examples/docker/docker-compose-jwt-validator.yml @@ -1,31 +1,56 @@ -# JWT Validator Plugin Example +# ============================================================================== +# EXAMPLE: JWT Validator Plugin +# ============================================================================== # -# This example demonstrates JWT token validation for API protection +# WHAT THIS DEMONSTRATES: +# - JWT token validation for API protection +# - RS256 algorithm signature verification +# - Issuer and audience validation +# - Public key-based JWT verification # -# Prerequisites: -# 1. Generate RSA key pair: -# openssl genrsa -out jwt_private.pem 2048 -# openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem +# REQUIREMENTS (run these first): +# ```bash +# # Generate RSA key pair (idempotent - skips if exists) +# [ -f jwt_private.pem ] || openssl genrsa -out jwt_private.pem 2048 +# [ -f jwt_pubkey.pem ] || openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem # -# 2. Add to /etc/hosts: -# 127.0.0.1 api.local +# # Add to /etc/hosts (idempotent) +# grep -q "api.local" /etc/hosts || echo "127.0.0.1 api.local" | sudo tee -a /etc/hosts +# ``` # -# 3. Start the stack: -# docker compose -f docker-compose-jwt-validator.yml up -d +# HOW TO START: +# ```bash +# docker compose -f docker-compose-jwt-validator.yml up -d +# ``` # -# 4. Test without token (should fail): -# curl http://api.local/ -# # Response: Missing Authorization HTTP header +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Test without token (should fail) +# curl http://api.local/ +# # Expected: HTTP 403 - Missing Authorization HTTP header # -# 5. Generate test JWT at https://jwt.io with: -# - Algorithm: RS256 -# - Payload: {"iss":"https://auth.example.com/","aud":"https://api.example.com","exp":9999999999} -# - Use your jwt_private.pem for signing +# # Generate test JWT at https://jwt.io with: +# # - Algorithm: RS256 +# # - Payload: {"iss":"https://auth.example.com/","aud":"https://api.example.com","exp":9999999999} +# # - Paste contents of jwt_private.pem in private key field # -# 6. Test with token: -# TOKEN="eyJhbGc..." -# curl -H "Authorization: Bearer $TOKEN" http://api.local/ -# # Response: Success +# # Test with valid token +# TOKEN="eyJhbGc..." # Replace with your generated token +# curl -H "Authorization: Bearer $TOKEN" http://api.local/ +# # Expected: 200 OK with API response +# +# # View HAProxy stats +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-jwt-validator.yml down +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/docker/docker-compose-multi-containers.yml b/examples/docker/docker-compose-multi-containers.yml index 92181bf..aaf8c90 100644 --- a/examples/docker/docker-compose-multi-containers.yml +++ b/examples/docker/docker-compose-multi-containers.yml @@ -1,11 +1,48 @@ -# curl -H Host:www.helloworld.com localhost:19901 -# f6d8d45b7411 -# 59b213cb8592 - -# curl -I -H Host:google.helloworld.com localhost:19901 -# HTTP/1.1 301 Moved Permanently -# content-length: 0 -# location: www.google.com/ +# ============================================================================== +# EXAMPLE: Load Balancing with Multiple Container Replicas +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - Multiple container replicas behind a single domain +# - Round-robin load balancing across replicas +# - Domain redirect functionality +# - Custom port configuration +# +# REQUIREMENTS (run these first): +# ```bash +# # No special requirements - this example runs on localhost:19901 +# ``` +# +# HOW TO START: +# ```bash +# docker compose -f docker-compose-multi-containers.yml up -d +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Test load balancing - hostname should alternate between containers +# curl -H "Host: www.helloworld.com" localhost:19901 +# # Expected: Container ID (e.g., f6d8d45b7411) +# curl -H "Host: www.helloworld.com" localhost:19901 +# # Expected: Different container ID (e.g., 59b213cb8592) +# +# # Test domain redirect +# curl -I -H "Host: google.helloworld.com" localhost:19901 +# # Expected: HTTP/1.1 301 Moved Permanently, Location: www.google.com/ +# +# # View HAProxy stats +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# # You should see 2 backend servers +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-multi-containers.yml down +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/docker/docker-compose-php-fpm.yml b/examples/docker/docker-compose-php-fpm.yml index c8e8737..35355a0 100644 --- a/examples/docker/docker-compose-php-fpm.yml +++ b/examples/docker/docker-compose-php-fpm.yml @@ -1,25 +1,51 @@ -# FastCGI Plugin Example with PHP-FPM +# ============================================================================== +# EXAMPLE: FastCGI Plugin with PHP-FPM +# ============================================================================== # -# This example demonstrates PHP-FPM configuration with FastCGI protocol support -# using HAProxy as a reverse proxy and the FastCGI plugin for PHP environment setup. +# WHAT THIS DEMONSTRATES: +# - PHP-FPM configuration with FastCGI protocol (proto: fcgi) +# - FastCGI plugin for PHP environment variable configuration +# - TCP connection to PHP-FPM on port 9000 +# - PATH_INFO support for RESTful routing +# - Custom document root and index file configuration # -# Prerequisites: -# 1. Add to /etc/hosts: -# 127.0.0.1 phpapp.local +# REQUIREMENTS (run these first): +# ```bash +# # Add to /etc/hosts (idempotent) +# grep -q "phpapp.local" /etc/hosts || echo "127.0.0.1 phpapp.local" | sudo tee -a /etc/hosts +# ``` # -# 2. Start the stack: -# docker compose -f docker-compose-php-fpm.yml up -d +# HOW TO START: +# ```bash +# docker compose -f docker-compose-php-fpm.yml up -d +# ``` # -# 3. Test PHP application: -# curl http://phpapp.local/ -# curl http://phpapp.local/info.php +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Test main page +# curl http://phpapp.local/ +# # Expected: 200 OK with PHP environment info # -# Features: -# - PHP-FPM 8.5 with TCP connection on port 9000 -# - FastCGI protocol support -# - Custom document root -# - PATH_INFO support for routing -# - Custom FastCGI parameters +# # Test PHP info page +# curl http://phpapp.local/info.php +# # Expected: phpinfo() output +# +# # Test PATH_INFO routing +# curl http://phpapp.local/test-path-info.php/users/123 +# # Expected: PATH_INFO=/users/123 +# +# # View HAProxy stats +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-php-fpm.yml down +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/docker/docker-compose-plugins-combined.yml b/examples/docker/docker-compose-plugins-combined.yml index 7fd26f1..a2165bf 100644 --- a/examples/docker/docker-compose-plugins-combined.yml +++ b/examples/docker/docker-compose-plugins-combined.yml @@ -1,33 +1,67 @@ -# Multiple Plugins Combined Example +# ============================================================================== +# EXAMPLE: Multiple Plugins Combined +# ============================================================================== # -# This example demonstrates using multiple plugins together for enhanced security +# WHAT THIS DEMONSTRATES: +# - Using multiple security plugins together +# - Different plugin combinations for different services +# - Cloudflare + path blocking for public sites +# - JWT validation + path blocking for APIs +# - IP whitelist for admin panels +# - Layered security approach # -# Prerequisites: -# 1. Generate JWT keys: -# openssl genrsa -out jwt_private.pem 2048 -# openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem +# REQUIREMENTS (run these first): +# ```bash +# # Generate JWT keys (idempotent - skips if exists) +# [ -f jwt_private.pem ] || openssl genrsa -out jwt_private.pem 2048 +# [ -f jwt_pubkey.pem ] || openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem # -# 2. Download Cloudflare IPs: -# curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst -# curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst +# # Download Cloudflare IPs (idempotent - overwrites if exists) +# curl -s https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst +# curl -s https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst # -# 3. Add to /etc/hosts: -# 127.0.0.1 website.local api.local admin.local +# # Add to /etc/hosts (idempotent) +# grep -q "website.local" /etc/hosts || echo "127.0.0.1 website.local api.local admin.local" | sudo tee -a /etc/hosts +# ``` # -# 4. Start the stack: -# docker compose -f docker-compose-plugins-combined.yml up -d +# HOW TO START: +# ```bash +# docker compose -f docker-compose-plugins-combined.yml up -d +# ``` # -# 5. Test each service: -# # Public website (Cloudflare + path blocking) -# curl http://website.local/ -# curl http://website.local/admin # Should be blocked (404) +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Test public website (Cloudflare + path blocking) +# curl http://website.local/ +# # Expected: 200 OK +# curl http://website.local/admin +# # Expected: HTTP 404 - Path blocked # -# # Protected API (JWT required) -# curl http://api.local/ # Should fail - no JWT -# curl -H "Authorization: Bearer " http://api.local/ # Success +# # Test protected API (JWT required) +# curl http://api.local/ +# # Expected: HTTP 403 - Missing Authorization header +# # Generate JWT at https://jwt.io (see jwt-validator example for details) +# TOKEN="eyJhbGc..." # Replace with your token +# curl -H "Authorization: Bearer $TOKEN" http://api.local/ +# # Expected: 200 OK # -# # Admin panel (IP whitelist only) -# curl http://admin.local/ # Success from localhost +# # Test admin panel (IP whitelist) +# curl http://admin.local/ +# # Expected: 200 OK from localhost +# +# # View HAProxy stats +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# # You should see 3 backends with different security configurations +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-plugins-combined.yml down +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/docker/docker-compose-portainer-app-example.yml b/examples/docker/docker-compose-portainer-app-example.yml index 2c2f105..ffbd963 100644 --- a/examples/docker/docker-compose-portainer-app-example.yml +++ b/examples/docker/docker-compose-portainer-app-example.yml @@ -1,3 +1,46 @@ +# ============================================================================== +# EXAMPLE: Additional Application with Portainer +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - Adding more applications to an existing EasyHAProxy setup +# - Using the shared "easyhaproxy" network +# - Multiple applications behind the same HAProxy instance +# +# REQUIREMENTS (run these first): +# ```bash +# # 1. First start the Portainer stack (creates network and HAProxy) +# docker compose -f docker-compose-portainer.yml up -d +# +# # 2. Edit this file and change: +# # - Line 6: easyhaproxy.http.host to your real domain +# ``` +# +# HOW TO START: +# ```bash +# docker compose -f docker-compose-portainer-app-example.yml up -d +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check container is running +# docker compose -f docker-compose-portainer-app-example.yml ps +# +# # Test the application +# curl http://test.xpto.us +# # OR with /etc/hosts: echo "127.0.0.1 test.xpto.us" | sudo tee -a /etc/hosts +# +# # Verify both apps in HAProxy stats (port 1936) +# # You should see backends for both portainer.xpto.us and test.xpto.us +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-portainer-app-example.yml down +# ``` +# +# ============================================================================== + services: container: image: byjg/static-httpserver diff --git a/examples/docker/docker-compose-portainer.yml b/examples/docker/docker-compose-portainer.yml index 5f23d90..39c4917 100644 --- a/examples/docker/docker-compose-portainer.yml +++ b/examples/docker/docker-compose-portainer.yml @@ -1,7 +1,60 @@ +# ============================================================================== +# EXAMPLE: Portainer Behind EasyHAProxy +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - Running Portainer (Docker management UI) behind EasyHAProxy +# - Using external volumes and networks for shared infrastructure +# - Real-world application example with Let's Encrypt +# - HTTP to HTTPS redirect with Certbot +# +# REQUIREMENTS (run these first): +# ```bash +# # Create required volumes (idempotent) # docker volume create certs_certbot # docker volume create certs_haproxy # docker volume create portainer_data +# +# # Create shared network (idempotent) # docker network create easyhaproxy +# +# # Edit this file and change: +# # - Line 18: EASYHAPROXY_CERTBOT_EMAIL to your email +# # - Line 38: easyhaproxy.http.host to your real domain +# ``` +# +# HOW TO START: +# ```bash +# docker compose -f docker-compose-portainer.yml up -d +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check containers are running +# docker compose -f docker-compose-portainer.yml ps +# # Expected: Both easyhaproxy and portainer containers running +# +# # Access Portainer (replace with your domain or use /etc/hosts) +# # First time: Create admin user +# curl http://portainer.xpto.us +# # OR with /etc/hosts: echo "127.0.0.1 portainer.xpto.us" | sudo tee -a /etc/hosts +# +# # View HAProxy stats +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# ``` +# +# CLEAN UP: +# ```bash +# docker compose -f docker-compose-portainer.yml down +# # To also remove volumes: +# # docker compose -f docker-compose-portainer.yml down -v +# # docker volume rm certs_certbot certs_haproxy portainer_data +# # docker network rm easyhaproxy +# ``` +# +# ============================================================================== services: diff --git a/examples/docker/docker-compose.yml b/examples/docker/docker-compose.yml index ad82ed4..db59b38 100644 --- a/examples/docker/docker-compose.yml +++ b/examples/docker/docker-compose.yml @@ -1,19 +1,53 @@ -# To test: +# ============================================================================== +# EXAMPLE: Basic SSL Setup with Two Virtual Hosts +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - Basic SSL setup with custom certificates (embedded base64 and file-based) +# - Automatic HTTP to HTTPS redirect +# - Two virtual hosts (host1.local and host2.local) +# - HAProxy stats interface +# +# REQUIREMENTS (run these first): +# ```bash +# # Add to /etc/hosts (idempotent) +# grep -q "host1.local" /etc/hosts || echo "127.0.0.1 host1.local host2.local" | sudo tee -a /etc/hosts +# +# # Generate SSL certificates +# cd ../.. && ./examples/generate-keys.sh && cd examples/docker +# ``` +# +# HOW TO START: +# ```bash +# docker compose up -d +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Test HTTPS # curl -k -H "Host: host1.local" https://127.0.0.1/ # curl -k -H "Host: host2.local" https://127.0.0.1/ -# -# curl -I -H Host:host1.local http://127.0.0.1 -# HTTP/1.1 301 Moved Permanently -# content-length: 0 -# location: https://host1.local/ +# # Expected: 200 OK with hostname in response # -# curl -I -H Host:host2.local http://127.0.0.1 -# HTTP/1.1 301 Moved Permanently -# content-length: 0 -# location: https://host1.local/ +# # Test HTTP redirect +# curl -I -H "Host: host1.local" http://127.0.0.1 +# # Expected: HTTP/1.1 301 Moved Permanently, Location: https://host1.local/ # -# Test SSL: -# openssl s_client -showcerts -connect 127.0.0.1:443 -servername host1.local +# # View SSL certificate +# openssl s_client -showcerts -connect 127.0.0.1:443 -servername host1.local < /dev/null +# +# # Access stats interface +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# ``` +# +# CLEAN UP: +# ```bash +# docker compose down +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/kubernetes/README.md b/examples/kubernetes/README.md index 4a867cb..2ab7e02 100644 --- a/examples/kubernetes/README.md +++ b/examples/kubernetes/README.md @@ -1,705 +1,50 @@ # Kubernetes Examples -This directory contains Kubernetes manifest examples demonstrating EasyHAProxy ingress configurations. +Self-contained examples for EasyHAProxy ingress controller. **All documentation is in the YAML files as header comments.** + +## Quick Start + +1. Pick an example below +2. Open the YAML file +3. Read the header comments for complete instructions +4. Run the commands step-by-step ## Prerequisites -1. **Generate SSL Certificates (Required for TLS examples):** - ```bash - # From the repository root - ./examples/generate-keys.sh - ``` +All examples require: +- EasyHAProxy installed in your Kubernetes cluster +- Node labeled for EasyHAProxy deployment - This script automatically generates: - - SSL certificates for testing (host1.local, host2.local) - - JWT keys for authentication examples - - All other .pem files needed for examples +See header comments in each file for detailed setup instructions. - **Note:** These are self-signed certificates for testing only. For production, use Let's Encrypt or your own certificates. +## Basic Examples -2. **EasyHAProxy installed in your cluster:** - ```bash - kubectl create namespace easyhaproxy - kubectl apply -f https://raw.githubusercontent.com/byjg/docker-easy-haproxy/4.6.0/deploy/kubernetes/easyhaproxy-daemonset.yml - ``` - -3. **Label the node where EasyHAProxy will run:** - ```bash - kubectl label nodes "easyhaproxy/node=master" - ``` - -See the [Kubernetes Guide](../../docs/kubernetes.md) for complete installation instructions. - ---- - -## Examples Overview - -### 1. Basic Ingress (`service.yml`) - -**What it demonstrates:** -- Basic ingress configuration -- Multiple domains pointing to same service -- Complete deployment + service + ingress setup - -**Components:** -- **Deployment**: `byjg/static-httpserver` container -- **Service**: ClusterIP exposing port 8080 -- **Ingress**: Routes for `example.org` and `www.example.org` - -**Apply:** -```bash -kubectl apply -f service.yml -``` - -**Test:** -```bash -# If using NodePort or port-forward: -curl -H "Host: example.org" http://:31080 - -# Or port-forward for testing: -kubectl port-forward -n easyhaproxy deployment/easyhaproxy 8080:80 -curl -H "Host: example.org" http://localhost:8080 -``` - -**Manifest breakdown:** -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: easyhaproxy-ingress # Required! - name: container-example -spec: - rules: - - host: example.org # First domain - http: - paths: - - backend: - service: - name: container-example - port: - number: 8080 - - host: www.example.org # Second domain (same service) - ... -``` - ---- - -### 2. TLS/SSL Ingress (`service_tls.yml`) - -**What it demonstrates:** -- HTTPS/TLS configuration -- Custom SSL certificates via Kubernetes secrets -- SSL redirect (HTTP → HTTPS) -- Certbot/Let's Encrypt integration - -**Components:** -- **Secret**: Custom SSL certificate for `host2.local` -- **Ingress**: TLS configuration + certbot annotation - -**Apply:** -```bash -kubectl apply -f service_tls.yml -``` - -**Features:** - -1. **Custom SSL Certificate:** - ```yaml - apiVersion: v1 - kind: Secret - metadata: - name: host2-tls - data: - tls.crt: - tls.key: - type: kubernetes.io/tls - ``` - -2. **Ingress TLS Configuration:** - ```yaml - spec: - tls: - - hosts: - - host2.local - secretName: host2-tls # References the secret above - ``` - -3. **Certbot/Let's Encrypt:** - ```yaml - metadata: - annotations: - easyhaproxy.certbot: 'true' - easyhaproxy.redirect_ssl: 'true' - ``` - -**Test:** -```bash -# Test HTTPS (if host2.local in /etc/hosts) -curl -k https://host2.local - -# Test HTTP redirect -curl -I http://host2.local -# Should return: HTTP/1.1 301 Moved Permanently -``` - ---- - -## Kubernetes Annotations Reference - -All annotations are applied at the **Ingress** level and affect all hosts in that ingress. - -### Required Annotation - -| Annotation | Description | Example | -|-------------------------------|-----------------------|-----------------------| -| `kubernetes.io/ingress.class` | Activates EasyHAProxy | `easyhaproxy-ingress` | - -### Optional Annotations - -| Annotation | Description | Default | Example | -|----------------------------|----------------------|---------|-------------------------| -| `easyhaproxy.redirect_ssl` | Force HTTPS redirect | `false` | `'true'` | -| `easyhaproxy.certbot` | Enable Let's Encrypt | `false` | `'true'` | -| `easyhaproxy.mode` | Protocol mode | `http` | `http` or `tcp` | -| `easyhaproxy.listen_port` | Override listen port | `80` | `8080` | -| `easyhaproxy.plugins` | Enable plugins | - | `cloudflare,deny_pages` | - -See [Kubernetes Guide](../../docs/kubernetes.md#kubernetes-annotations) for complete reference. - ---- - -## Common Use Cases - -### Use Case 1: Simple HTTP Application - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: easyhaproxy-ingress - name: my-app -spec: - rules: - - host: myapp.example.com - http: - paths: - - backend: - service: - name: my-app-service - port: - number: 8080 - pathType: ImplementationSpecific -``` - -### Use Case 2: HTTPS with Let's Encrypt - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: easyhaproxy-ingress - easyhaproxy.certbot: 'true' - easyhaproxy.redirect_ssl: 'true' - name: secure-app -spec: - rules: - - host: secure.example.com - http: - paths: - - backend: - service: - name: secure-app-service - port: - number: 8080 - pathType: ImplementationSpecific -``` - -**Requirements for Let's Encrypt:** -- Cluster must be publicly accessible on ports 80 and 443 -- DNS must point to cluster IP -- Configure certbot email: - ```bash - # Via Helm: - helm upgrade ingress byjg/easyhaproxy \ - --set easyhaproxy.certbot.email=your-email@example.com - - # Or via environment variable in manifest - ``` - -### Use Case 3: Custom SSL Certificate - -```yaml ---- -apiVersion: v1 -kind: Secret -metadata: - name: my-tls-secret -type: kubernetes.io/tls -data: - tls.crt: LS0tLS1CRUdJTi... # base64 encoded certificate - tls.key: LS0tLS1CRUdJTi... # base64 encoded private key - ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: easyhaproxy-ingress - name: custom-ssl-app -spec: - tls: - - hosts: - - myapp.example.com - secretName: my-tls-secret - rules: - - host: myapp.example.com - http: - paths: - - backend: - service: - name: my-app-service - port: - number: 8080 - pathType: ImplementationSpecific -``` - -### Use Case 4: Using Plugins (JWT, IP Whitelist, etc.) - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: easyhaproxy-ingress - # Enable plugins - easyhaproxy.plugins: "jwt_validator,deny_pages" - # Configure JWT validator - easyhaproxy.plugin.jwt_validator.algorithm: "RS256" - easyhaproxy.plugin.jwt_validator.issuer: "https://auth.example.com/" - easyhaproxy.plugin.jwt_validator.audience: "https://api.example.com" - easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api_pubkey.pem" - # Configure deny_pages - easyhaproxy.plugin.deny_pages.paths: "/admin,/private" - name: secure-api -spec: - rules: - - host: api.example.com - http: - paths: - - backend: - service: - name: api-service - port: - number: 8080 - pathType: ImplementationSpecific -``` - -See [Using Plugins with Kubernetes](../../docs/kubernetes.md#using-plugins-with-kubernetes) for more examples. - ---- +| File | Description | +|------------------------------------|--------------------------------------------| +| [service.yml](service.yml) | Basic HTTP ingress with multiple domains | +| [service_tls.yml](service_tls.yml) | HTTPS/TLS ingress with custom certificates | ## Plugin Examples -### JWT Validator Plugin +| File | Description | +|----------------------------------------------|-----------------------------------------------------| +| [jwt-validator.yml](jwt-validator.yml) | JWT token validation for API protection | +| [ip-whitelist.yml](ip-whitelist.yml) | IP whitelist for admin panels or sensitive services | +| [cloudflare.yml](cloudflare.yml) | Restore real client IPs when behind Cloudflare CDN | +| [plugins-combined.yml](plugins-combined.yml) | Multiple plugins combined for layered security | -Complete example with JWT validation for API protection: +## Documentation Structure -```yaml ---- -# Create ConfigMap with public key -apiVersion: v1 -kind: ConfigMap -metadata: - name: jwt-keys - namespace: default -data: - api_pubkey.pem: | - -----BEGIN PUBLIC KEY----- - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... - -----END PUBLIC KEY----- +Each YAML file contains: +- **WHAT THIS DEMONSTRATES** - Key features and concepts +- **REQUIREMENTS** - Idempotent setup commands (safe to run multiple times) +- **HOW TO START** - Command to apply the manifest +- **HOW TO VERIFY IT'S WORKING** - Test commands with expected outputs +- **CLEAN UP** - Commands to remove resources ---- -# Mount public key into EasyHAProxy pod -# Add this to your EasyHAProxy deployment: -# volumeMounts: -# - name: jwt-keys -# mountPath: /etc/haproxy/jwt_keys -# volumes: -# - name: jwt-keys -# configMap: -# name: jwt-keys - ---- -apiVersion: v1 -kind: Service -metadata: - name: api-service - namespace: default -spec: - ports: - - port: 8080 - selector: - app: api - type: ClusterIP - ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: api - namespace: default -spec: - replicas: 3 - selector: - matchLabels: - app: api - template: - metadata: - labels: - app: api - spec: - containers: - - name: api - image: my-api:latest - ports: - - containerPort: 8080 - ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: easyhaproxy-ingress - # Enable JWT validator - easyhaproxy.plugins: "jwt_validator" - easyhaproxy.plugin.jwt_validator.algorithm: "RS256" - easyhaproxy.plugin.jwt_validator.issuer: "https://auth.example.com/" - easyhaproxy.plugin.jwt_validator.audience: "https://api.example.com" - easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api_pubkey.pem" - name: api-ingress - namespace: default -spec: - rules: - - host: api.example.com - http: - paths: - - backend: - service: - name: api-service - port: - number: 8080 - pathType: ImplementationSpecific -``` - -**Test:** -```bash -# Without JWT token - should fail -curl http://api.example.com/users -# Response: Missing Authorization HTTP header - -# With valid JWT token -TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." -curl -H "Authorization: Bearer $TOKEN" http://api.example.com/users -# Response: Success -``` - ---- - -### Cloudflare IP Restoration Plugin - -Restore original visitor IPs when behind Cloudflare: - -```yaml ---- -# Create ConfigMap with Cloudflare IP ranges -apiVersion: v1 -kind: ConfigMap -metadata: - name: cloudflare-ips - namespace: easyhaproxy -data: - cloudflare_ips.lst: | - 173.245.48.0/20 - 103.21.244.0/22 - 103.22.200.0/22 - 103.31.4.0/22 - 141.101.64.0/18 - 108.162.192.0/18 - 190.93.240.0/20 - 188.114.96.0/20 - 197.234.240.0/22 - 198.41.128.0/17 - 162.158.0.0/15 - 104.16.0.0/13 - 104.24.0.0/14 - 172.64.0.0/13 - 131.0.72.0/22 - ---- -# Mount ConfigMap into EasyHAProxy pod -# Add this to your EasyHAProxy deployment: -# volumeMounts: -# - name: cloudflare-ips -# mountPath: /etc/haproxy/cloudflare_ips.lst -# subPath: cloudflare_ips.lst -# volumes: -# - name: cloudflare-ips -# configMap: -# name: cloudflare-ips - ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: easyhaproxy-ingress - # Enable Cloudflare plugin - easyhaproxy.plugins: "cloudflare" - name: webapp-ingress - namespace: default -spec: - rules: - - host: myapp.example.com - http: - paths: - - backend: - service: - name: webapp-service - port: - number: 8080 - pathType: ImplementationSpecific -``` - -**Download latest Cloudflare IPs:** -```bash -curl https://www.cloudflare.com/ips-v4 -curl https://www.cloudflare.com/ips-v6 -``` - ---- - -### IP Whitelist Plugin - -Restrict admin panel to office IPs only: - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: easyhaproxy-ingress - # Enable IP whitelist - easyhaproxy.plugins: "ip_whitelist" - easyhaproxy.plugin.ip_whitelist.allowed_ips: "203.0.113.0/24,198.51.100.42" - easyhaproxy.plugin.ip_whitelist.status_code: "403" - name: admin-ingress - namespace: default -spec: - rules: - - host: admin.example.com - http: - paths: - - backend: - service: - name: admin-service - port: - number: 8080 - pathType: ImplementationSpecific -``` - -**Test:** -```bash -# From allowed IP (203.0.113.50) -curl http://admin.example.com -# Response: Success - -# From blocked IP -curl http://admin.example.com -# Response: HTTP 403 Forbidden -``` - ---- - -### Multiple Plugins Combined - -Combine Cloudflare + JWT + Path Blocking for maximum security: - -```yaml -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubernetes.io/ingress.class: easyhaproxy-ingress - # Enable multiple plugins - easyhaproxy.plugins: "cloudflare,jwt_validator,deny_pages" - - # Cloudflare - restore real IPs - # (no config needed if using default path) - - # JWT Validator - validate tokens - easyhaproxy.plugin.jwt_validator.algorithm: "RS256" - easyhaproxy.plugin.jwt_validator.issuer: "https://auth.example.com/" - easyhaproxy.plugin.jwt_validator.audience: "https://api.example.com" - easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api_pubkey.pem" - - # Deny Pages - block sensitive paths - easyhaproxy.plugin.deny_pages.paths: "/internal,/debug,/admin" - easyhaproxy.plugin.deny_pages.status_code: "404" - name: secure-api-ingress - namespace: production -spec: - rules: - - host: api.example.com - http: - paths: - - backend: - service: - name: api-service - port: - number: 8080 - pathType: ImplementationSpecific -``` - -**Plugin execution order:** -1. Cloudflare IP restoration (sets correct visitor IP) -2. Deny Pages (blocks blacklisted paths) -3. JWT Validator (validates authentication) - ---- - -## Creating SSL Secrets - -### From Certificate Files - -```bash -kubectl create secret tls my-tls-secret \ - --cert=path/to/cert.crt \ - --key=path/to/cert.key \ - -n default -``` - -### From PEM File - -```bash -# Extract certificate and key -openssl x509 -in cert.pem -out cert.crt -openssl rsa -in cert.pem -out cert.key - -# Create secret -kubectl create secret tls my-tls-secret \ - --cert=cert.crt \ - --key=cert.key \ - -n default -``` - -### Generate Self-Signed Certificate for Testing - -```bash -openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ - -keyout tls.key -out tls.crt \ - -subj "/CN=myapp.example.com" - -kubectl create secret tls my-tls-secret \ - --cert=tls.crt \ - --key=tls.key -``` - ---- - -## Troubleshooting - -### Ingress Not Detected - -**Check annotation:** -```bash -kubectl get ingress -o yaml | grep annotations -A 5 -``` - -Ensure `kubernetes.io/ingress.class: easyhaproxy-ingress` is present. - -**Check EasyHAProxy logs:** -```bash -kubectl logs -n easyhaproxy deployment/easyhaproxy -f -``` - -### SSL Certificate Not Loading - -**Verify secret exists:** -```bash -kubectl get secret -o yaml -``` - -**Check secret has correct fields:** -- `tls.crt`: base64-encoded certificate -- `tls.key`: base64-encoded private key - -**Check EasyHAProxy logs** for certificate loading errors. - -### Let's Encrypt Fails - -**Requirements:** -- Ports 80 and 443 must be publicly accessible -- DNS must resolve to cluster IP -- Certbot email must be configured - -**Check certbot logs:** -```bash -kubectl logs -n easyhaproxy deployment/easyhaproxy | grep certbot -``` - -### Changes Not Applied - -EasyHAProxy watches ingress changes automatically. If changes aren't applied: - -1. **Check discovery interval:** - ```bash - # Default is 10 seconds, increase if needed - EASYHAPROXY_REFRESH: "30" - ``` - -2. **Force reload:** - ```bash - kubectl rollout restart -n easyhaproxy deployment/easyhaproxy - ``` - ---- - -## Tips - -1. **Local Testing:** - Add entries to `/etc/hosts`: - ``` - example.org www.example.org host2.local - ``` - -2. **View HAProxy Config:** - ```bash - kubectl exec -n easyhaproxy deployment/easyhaproxy -- cat /etc/haproxy/haproxy.cfg - ``` - -3. **Access Stats Interface:** - ```bash - kubectl port-forward -n easyhaproxy deployment/easyhaproxy 1936:1936 - # Open: http://localhost:1936 - ``` - -4. **Debug Mode:** - Enable debug logging: - ```yaml - env: - - name: EASYHAPROXY_LOG_LEVEL - value: DEBUG - ``` - ---- - -## Further Reading +## Additional Documentation - [Kubernetes Installation Guide](../../docs/kubernetes.md) - [Helm Installation](../../docs/helm.md) +- [Kubernetes Annotations Reference](../../docs/kubernetes.md#kubernetes-annotations) - [Using Plugins with Kubernetes](../../docs/kubernetes.md#using-plugins-with-kubernetes) -- [ACME/Let's Encrypt](../../docs/acme.md) -- [Environment Variables](../../docs/environment-variable.md) diff --git a/examples/kubernetes/cloudflare.yml b/examples/kubernetes/cloudflare.yml index 734d228..b4e037a 100644 --- a/examples/kubernetes/cloudflare.yml +++ b/examples/kubernetes/cloudflare.yml @@ -1,34 +1,65 @@ -# Cloudflare IP Restoration Plugin Example for Kubernetes +# ============================================================================== +# EXAMPLE: Cloudflare IP Restoration Plugin for Kubernetes +# ============================================================================== # -# This example demonstrates restoring original visitor IPs when using Cloudflare CDN +# WHAT THIS DEMONSTRATES: +# - Restoring original visitor IPs when behind Cloudflare CDN +# - Using ConfigMaps to mount Cloudflare IP ranges +# - Detecting requests from Cloudflare IP ranges +# - Accurate client IP logging for applications behind Cloudflare # -# Prerequisites: -# 1. EasyHAProxy installed in your cluster +# REQUIREMENTS (run these first): +# ```bash +# # 1. Ensure EasyHAProxy is installed in your cluster +# kubectl create namespace easyhaproxy +# kubectl apply -f https://raw.githubusercontent.com/byjg/docker-easy-haproxy/4.6.0/deploy/kubernetes/easyhaproxy-daemonset.yml # -# 2. Download Cloudflare IP ranges and create ConfigMap: -# curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst -# curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst -# kubectl create configmap cloudflare-ips \ -# --from-file=cloudflare_ips.lst=cloudflare_ips.lst \ -# -n easyhaproxy +# # 2. Download Cloudflare IP ranges +# curl -s https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst +# curl -s https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst # -# 3. Mount the ConfigMap in EasyHAProxy deployment (add to volumeMounts and volumes): -# volumeMounts: -# - name: cloudflare-ips -# mountPath: /etc/haproxy/cloudflare_ips.lst -# subPath: cloudflare_ips.lst -# volumes: -# - name: cloudflare-ips -# configMap: -# name: cloudflare-ips +# # 3. Create ConfigMap with Cloudflare IPs +# kubectl create configmap cloudflare-ips \ +# --from-file=cloudflare_ips.lst=cloudflare_ips.lst \ +# -n easyhaproxy # -# 4. Apply this manifest: -# kubectl apply -f cloudflare.yml +# # 4. Mount the ConfigMap in EasyHAProxy deployment: +# # Edit your EasyHAProxy deployment and add: +# # volumeMounts: +# # - name: cloudflare-ips +# # mountPath: /etc/haproxy/cloudflare_ips.lst +# # subPath: cloudflare_ips.lst +# # volumes: +# # - name: cloudflare-ips +# # configMap: +# # name: cloudflare-ips +# ``` # -# 5. Test: -# curl http://myapp.example.local/ +# HOW TO START: +# ```bash +# kubectl apply -f cloudflare.yml +# ``` # -# Note: This plugin is most useful when your site is actually behind Cloudflare. +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check resources are created +# kubectl get deployment,service,ingress -l app=webapp +# +# # Test via port-forward +# kubectl port-forward -n easyhaproxy deployment/easyhaproxy 8080:80 +# curl -H "Host: myapp.example.local" http://localhost:8080 +# # Expected: 200 OK with "App Behind Cloudflare" +# +# # In production behind Cloudflare, the plugin will restore real client IPs +# # from the CF-Connecting-IP header +# ``` +# +# CLEAN UP: +# ```bash +# kubectl delete -f cloudflare.yml +# ``` +# +# ============================================================================== --- apiVersion: v1 diff --git a/examples/kubernetes/ip-whitelist.yml b/examples/kubernetes/ip-whitelist.yml index 4603d43..c1d5cba 100644 --- a/examples/kubernetes/ip-whitelist.yml +++ b/examples/kubernetes/ip-whitelist.yml @@ -1,23 +1,48 @@ -# IP Whitelist Plugin Example for Kubernetes +# ============================================================================== +# EXAMPLE: IP Whitelist Plugin for Kubernetes +# ============================================================================== # -# This example demonstrates restricting access to specific IP addresses +# WHAT THIS DEMONSTRATES: +# - Restricting access to specific IP addresses or CIDR ranges +# - Using annotations for IP-based access control +# - Protecting admin panels or sensitive services in Kubernetes +# - Custom HTTP status code for blocked requests # -# Prerequisites: -# 1. EasyHAProxy installed in your cluster +# REQUIREMENTS (run these first): +# ```bash +# # 1. Ensure EasyHAProxy is installed in your cluster +# kubectl create namespace easyhaproxy +# kubectl apply -f https://raw.githubusercontent.com/byjg/docker-easy-haproxy/4.6.0/deploy/kubernetes/easyhaproxy-daemonset.yml # -# 2. Update the allowed_ips annotation with your actual IP addresses/networks +# # 2. IMPORTANT: Edit this file (line 80) and update allowed_ips +# # with your actual office/VPN IP addresses or networks +# ``` # -# 3. Apply this manifest: -# kubectl apply -f ip-whitelist.yml +# HOW TO START: +# ```bash +# kubectl apply -f ip-whitelist.yml +# ``` # -# 4. Test from allowed IP: -# curl http://admin.example.local/ -# # Response: Success (200 OK) +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check resources are created +# kubectl get deployment,service,ingress -l app=admin # -# 5. Test from non-allowed IP: -# # Response: HTTP 403 Forbidden +# # Test from allowed IP +# kubectl port-forward -n easyhaproxy deployment/easyhaproxy 8080:80 +# curl -H "Host: admin.example.local" http://localhost:8080 +# # Expected: 200 OK with "Admin Panel - IP Restricted" (if your IP is in allowed_ips) # -# Note: Update the allowed_ips annotation with your actual office/VPN IPs +# # Test from non-allowed IP +# # Expected: HTTP 403 Forbidden +# ``` +# +# CLEAN UP: +# ```bash +# kubectl delete -f ip-whitelist.yml +# ``` +# +# ============================================================================== --- apiVersion: v1 diff --git a/examples/kubernetes/jwt-validator.yml b/examples/kubernetes/jwt-validator.yml index f3d7b90..1a393bd 100644 --- a/examples/kubernetes/jwt-validator.yml +++ b/examples/kubernetes/jwt-validator.yml @@ -1,41 +1,69 @@ -# JWT Validator Plugin Example for Kubernetes +# ============================================================================== +# EXAMPLE: JWT Validator Plugin for Kubernetes +# ============================================================================== # -# This example demonstrates JWT token validation for API protection in Kubernetes +# WHAT THIS DEMONSTRATES: +# - JWT token validation for API protection in Kubernetes +# - RS256 algorithm signature verification +# - Using ConfigMaps to mount JWT public keys +# - Issuer and audience validation # -# Prerequisites: -# 1. EasyHAProxy installed in your cluster -# 2. Generate RSA key pair: -# openssl genrsa -out jwt_private.pem 2048 -# openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem +# REQUIREMENTS (run these first): +# ```bash +# # 1. Ensure EasyHAProxy is installed in your cluster +# kubectl create namespace easyhaproxy +# kubectl apply -f https://raw.githubusercontent.com/byjg/docker-easy-haproxy/4.6.0/deploy/kubernetes/easyhaproxy-daemonset.yml # -# 3. Create ConfigMap with public key: -# kubectl create configmap jwt-keys --from-file=api_pubkey.pem=jwt_pubkey.pem +# # 2. Generate RSA key pair (idempotent - skips if exists) +# [ -f jwt_private.pem ] || openssl genrsa -out jwt_private.pem 2048 +# [ -f jwt_pubkey.pem ] || openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem # -# 4. Mount the ConfigMap in EasyHAProxy deployment (add to volumeMounts and volumes): -# volumeMounts: -# - name: jwt-keys -# mountPath: /etc/haproxy/jwt_keys -# volumes: -# - name: jwt-keys -# configMap: -# name: jwt-keys +# # 3. Create ConfigMap with public key +# kubectl create configmap jwt-keys --from-file=api_pubkey.pem=jwt_pubkey.pem # -# 5. Apply this manifest: -# kubectl apply -f jwt-validator.yml +# # 4. Mount the ConfigMap in EasyHAProxy deployment: +# # Edit your EasyHAProxy deployment and add: +# # volumeMounts: +# # - name: jwt-keys +# # mountPath: /etc/haproxy/jwt_keys +# # volumes: +# # - name: jwt-keys +# # configMap: +# # name: jwt-keys +# ``` # -# 6. Test without token (should fail): -# curl http://api.example.local/ -# # Response: Missing Authorization HTTP header +# HOW TO START: +# ```bash +# kubectl apply -f jwt-validator.yml +# ``` # -# 7. Generate test JWT at https://jwt.io with: -# - Algorithm: RS256 -# - Payload: {"iss":"https://auth.example.com/","aud":"https://api.example.com","exp":9999999999} -# - Use your jwt_private.pem for signing +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check resources are created +# kubectl get deployment,service,ingress -l app=api # -# 8. Test with token: -# TOKEN="eyJhbGc..." -# curl -H "Authorization: Bearer $TOKEN" http://api.example.local/ -# # Response: Success +# # Test without token (should fail) +# kubectl port-forward -n easyhaproxy deployment/easyhaproxy 8080:80 +# curl -H "Host: api.example.local" http://localhost:8080 +# # Expected: HTTP 403 - Missing Authorization HTTP header +# +# # Generate test JWT at https://jwt.io with: +# # - Algorithm: RS256 +# # - Payload: {"iss":"https://auth.example.com/","aud":"https://api.example.com","exp":9999999999} +# # - Paste contents of jwt_private.pem in private key field +# +# # Test with valid token +# TOKEN="eyJhbGc..." # Replace with your generated token +# curl -H "Authorization: Bearer $TOKEN" -H "Host: api.example.local" http://localhost:8080 +# # Expected: 200 OK with "Protected API - JWT Required" +# ``` +# +# CLEAN UP: +# ```bash +# kubectl delete -f jwt-validator.yml +# ``` +# +# ============================================================================== --- apiVersion: v1 diff --git a/examples/kubernetes/plugins-combined.yml b/examples/kubernetes/plugins-combined.yml index 321aeef..e8c76cb 100644 --- a/examples/kubernetes/plugins-combined.yml +++ b/examples/kubernetes/plugins-combined.yml @@ -1,31 +1,70 @@ -# Multiple Plugins Combined Example for Kubernetes +# ============================================================================== +# EXAMPLE: Multiple Plugins Combined for Kubernetes +# ============================================================================== # -# This example demonstrates using multiple plugins together for enhanced security +# WHAT THIS DEMONSTRATES: +# - Using multiple security plugins together +# - Different plugin combinations for different services +# - Layered security approach in Kubernetes +# - Three services with different security profiles: +# 1. Public website: Cloudflare + path blocking +# 2. Protected API: JWT validation + path blocking +# 3. Admin panel: Strict IP whitelist # -# Prerequisites: -# 1. EasyHAProxy installed in your cluster +# REQUIREMENTS (run these first): +# ```bash +# # 1. Ensure EasyHAProxy is installed in your cluster +# kubectl create namespace easyhaproxy +# kubectl apply -f https://raw.githubusercontent.com/byjg/docker-easy-haproxy/4.6.0/deploy/kubernetes/easyhaproxy-daemonset.yml # -# 2. Generate JWT keys and create ConfigMap: -# openssl genrsa -out jwt_private.pem 2048 -# openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem -# kubectl create configmap jwt-keys --from-file=api_pubkey.pem=jwt_pubkey.pem +# # 2. Generate JWT keys (idempotent - skips if exists) +# [ -f jwt_private.pem ] || openssl genrsa -out jwt_private.pem 2048 +# [ -f jwt_pubkey.pem ] || openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem +# kubectl create configmap jwt-keys --from-file=api_pubkey.pem=jwt_pubkey.pem # -# 3. Download Cloudflare IPs and create ConfigMap: -# curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst -# curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst -# kubectl create configmap cloudflare-ips \ -# --from-file=cloudflare_ips.lst=cloudflare_ips.lst \ -# -n easyhaproxy +# # 3. Download Cloudflare IPs +# curl -s https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst +# curl -s https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst +# kubectl create configmap cloudflare-ips \ +# --from-file=cloudflare_ips.lst=cloudflare_ips.lst \ +# -n easyhaproxy # -# 4. Mount ConfigMaps in EasyHAProxy deployment +# # 4. Mount ConfigMaps in EasyHAProxy deployment +# # (See individual plugin examples for mount configuration) +# ``` # -# 5. Apply this manifest: -# kubectl apply -f plugins-combined.yml +# HOW TO START: +# ```bash +# kubectl apply -f plugins-combined.yml +# ``` # -# This creates three services with different security profiles: -# - Public website: Cloudflare + path blocking -# - Protected API: JWT validation + path blocking -# - Admin panel: Strict IP whitelist +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check all resources are created +# kubectl get deployment,service,ingress +# +# # Test public website (Cloudflare + path blocking) +# kubectl port-forward -n easyhaproxy deployment/easyhaproxy 8080:80 +# curl -H "Host: website.example.local" http://localhost:8080 +# # Expected: 200 OK with "Public Website" +# curl -H "Host: website.example.local" http://localhost:8080/admin +# # Expected: HTTP 404 - Path blocked +# +# # Test protected API (JWT required) +# curl -H "Host: api.example.local" http://localhost:8080 +# # Expected: HTTP 403 - Missing Authorization header +# +# # Test admin panel (IP whitelist) +# curl -H "Host: admin.example.local" http://localhost:8080 +# # Expected: 200 OK from allowed IP, or HTTP 403 from blocked IP +# ``` +# +# CLEAN UP: +# ```bash +# kubectl delete -f plugins-combined.yml +# ``` +# +# ============================================================================== --- # Public website service diff --git a/examples/kubernetes/service.yml b/examples/kubernetes/service.yml index 2ac821a..36fbd31 100644 --- a/examples/kubernetes/service.yml +++ b/examples/kubernetes/service.yml @@ -1,3 +1,57 @@ +# ============================================================================== +# EXAMPLE: Basic Kubernetes Ingress +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - Basic ingress configuration with EasyHAProxy +# - Multiple domains pointing to the same service +# - Complete deployment + service + ingress setup +# - HTTP ingress without TLS +# +# REQUIREMENTS (run these first): +# ```bash +# # 1. Ensure EasyHAProxy is installed in your cluster +# kubectl create namespace easyhaproxy +# kubectl apply -f https://raw.githubusercontent.com/byjg/docker-easy-haproxy/4.6.0/deploy/kubernetes/easyhaproxy-daemonset.yml +# +# # 2. Label the node where EasyHAProxy will run +# kubectl label nodes "easyhaproxy/node=master" +# +# # 3. Add to /etc/hosts for local testing (idempotent) +# grep -q "example.org" /etc/hosts || echo " example.org www.example.org" | sudo tee -a /etc/hosts +# ``` +# +# HOW TO START: +# ```bash +# kubectl apply -f service.yml +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check resources are created +# kubectl get deployment,service,ingress container-example +# +# # Test via node IP +# curl -H "Host: example.org" http://:31080 +# # Expected: 200 OK with "My Host Example" +# +# # Or use port-forward for testing +# kubectl port-forward -n easyhaproxy deployment/easyhaproxy 8080:80 +# curl -H "Host: example.org" http://localhost:8080 +# # Expected: 200 OK with "My Host Example" +# +# # Test second domain +# curl -H "Host: www.example.org" http://:31080 +# # Expected: Same response +# ``` +# +# CLEAN UP: +# ```bash +# kubectl delete -f service.yml +# ``` +# +# ============================================================================== + --- apiVersion: networking.k8s.io/v1 kind: Ingress diff --git a/examples/kubernetes/service_tls.yml b/examples/kubernetes/service_tls.yml index e351fc2..f0135cb 100644 --- a/examples/kubernetes/service_tls.yml +++ b/examples/kubernetes/service_tls.yml @@ -1,3 +1,58 @@ +# ============================================================================== +# EXAMPLE: TLS/SSL Kubernetes Ingress +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - HTTPS/TLS configuration with custom certificates +# - Kubernetes TLS secrets for SSL certificates +# - Complete deployment + service + secret + ingress with TLS +# - Using pre-generated test certificates +# +# REQUIREMENTS (run these first): +# ```bash +# # 1. Ensure EasyHAProxy is installed in your cluster +# kubectl create namespace easyhaproxy +# kubectl apply -f https://raw.githubusercontent.com/byjg/docker-easy-haproxy/4.6.0/deploy/kubernetes/easyhaproxy-daemonset.yml +# +# # 2. Label the node where EasyHAProxy will run +# kubectl label nodes "easyhaproxy/node=master" +# +# # 3. Add to /etc/hosts for local testing (idempotent) +# grep -q "host2.local" /etc/hosts || echo " host2.local" | sudo tee -a /etc/hosts +# +# # Note: This example uses embedded test certificates +# # For production, create your own secret: +# # kubectl create secret tls host2-tls --cert=cert.crt --key=cert.key +# ``` +# +# HOW TO START: +# ```bash +# kubectl apply -f service_tls.yml +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check resources are created +# kubectl get deployment,service,ingress,secret tls-example +# kubectl get secret host2-tls +# +# # Test HTTPS (using port-forward) +# kubectl port-forward -n easyhaproxy deployment/easyhaproxy 8443:443 +# curl -k -H "Host: host2.local" https://localhost:8443 +# # Expected: 200 OK with "My Host Example" +# +# # Verify certificate +# openssl s_client -showcerts -connect localhost:8443 -servername host2.local < /dev/null +# # Expected: Certificate for host2.local +# ``` +# +# CLEAN UP: +# ```bash +# kubectl delete -f service_tls.yml +# ``` +# +# ============================================================================== + --- apiVersion: networking.k8s.io/v1 kind: Ingress diff --git a/examples/static/README.md b/examples/static/README.md index d6fefbc..a1f6c4b 100644 --- a/examples/static/README.md +++ b/examples/static/README.md @@ -1,312 +1,77 @@ # Static Configuration Example -This directory demonstrates EasyHAProxy using **static YAML configuration** instead of dynamic service discovery. +Self-contained example for EasyHAProxy using static YAML configuration. **All documentation is in the docker-compose file as header comments.** -Static mode is useful for: -- Non-containerized backends (VMs, bare metal) -- Fixed infrastructure -- Explicit routing control +## Quick Start ---- +1. Open [docker-compose.yml](docker-compose.yml) +2. Read the header comments for complete instructions +3. Choose a configuration scenario from `conf/` directory +4. Run the commands step-by-step -## Prerequisites +## What is Static Mode? -### 1. Generate SSL Certificates +Static mode uses explicit YAML configuration files instead of dynamic service discovery. This is useful for: +- **Non-containerized backends** - VMs, bare metal servers, external APIs +- **Fixed infrastructure** - When your backend IPs/ports don't change +- **Explicit routing control** - Precise control over HAProxy configuration -```bash -# From repository root -./examples/generate-keys.sh -``` - -### 2. Add Host Entry - -```bash -echo "127.0.0.1 host1.local www.host1.local" | sudo tee -a /etc/hosts -echo "127.0.0.1 host2.local www.host2.local" | sudo tee -a /etc/hosts -``` - ---- - -## Scenario 1: Basic (HTTP → HTTPS Redirect) - -**What it does:** Simple HTTP to HTTPS redirect with SSL termination. - -### Getting Started - -```bash -cd examples/static - -# 1. Copy the basic config -cp conf/config-basic.yml conf/config.yml - -# 2. Start backend container -docker run -d --name container -p 8080:8080 byjg/static-httpserver - -# 3. Start EasyHAProxy -docker compose up -d -``` - -### Test - -```bash -# Test HTTP redirect -curl -I http://host1.local -# Expected: HTTP/1.1 301 Moved Permanently -# Expected: Location: https://host1.local - -# Test HTTPS -curl -k https://host1.local -# Expected: Hello from Static HTTP Server! - -# Test www redirect -curl -I http://www.host1.local -# Expected: HTTP/1.1 301 Moved Permanently -# Expected: Location: https://host1.local -``` - -### Stats Interface - -Open: http://localhost:1936 -- Username: `admin` -- Password: `password` - -### Clean Up - -```bash -docker compose down -docker stop container && docker rm container -``` - ---- - -## Scenario 2: Certbot (Let's Encrypt SSL) - -**What it does:** Automatic SSL certificates from Let's Encrypt using ACME HTTP-01 challenge. - -### Requirements - -- Public IP address -- Domain pointing to your IP -- Ports 80/443 publicly accessible - -### Getting Started - -```bash -cd examples/static - -# 1. Copy the certbot config -cp conf/config-certbot.yml conf/config.yml - -# 2. Edit config.yml and change: -# - Replace "example.com" with your real domain -# - Update EASYHAPROXY_CERTBOT_EMAIL in docker-compose.yml - -# 3. Start backend container -docker run -d --name container -p 8080:8080 byjg/static-httpserver - -# 4. Start EasyHAProxy -docker compose up -d - -# 5. Check logs for certificate generation -docker compose logs -f -``` - -### What to Expect - -``` -# Certbot will: -# 1. Request certificate from Let's Encrypt -# 2. Complete HTTP-01 challenge -# 3. Save certificate in /certs/certbot/ -# 4. Reload HAProxy with new certificate -``` - -### Test - -```bash -# Test HTTPS with real certificate -curl https://your-domain.com -# Expected: No certificate warnings (valid SSL) - -# Test HTTP redirect -curl -I http://your-domain.com -# Expected: HTTP/1.1 301 Moved Permanently -``` - -### Clean Up - -```bash -docker compose down -docker stop container && docker rm container -``` - -**Note:** Certificates are stored in Docker volume `certs_certbot` and persist across restarts. - ---- - -## Scenario 3: Deny Pages (Block Specific Paths) - -**What it does:** Blocks access to sensitive paths like `/admin`, `/wp-login.php`, etc. - -### Getting Started - -```bash -cd examples/static - -# 1. Copy the deny-pages config -cp conf/config-deny-pages.yml conf/config.yml - -# 2. Start backend container -docker run -d --name container -p 8080:8080 byjg/static-httpserver - -# 3. Start EasyHAProxy -docker compose up -d -``` - -### Test - -```bash -# Test normal page (should work) -curl -k https://host1.local/ -# Expected: Hello from Static HTTP Server! - -# Test blocked path (should fail) -curl -I -k https://host1.local/admin -# Expected: HTTP/1.1 404 Not Found - -curl -I -k https://host1.local/wp-login.php -# Expected: HTTP/1.1 404 Not Found - -curl -I -k https://host1.local/.env -# Expected: HTTP/1.1 404 Not Found -``` - -### What's Blocked - -The example blocks these paths: -- `/admin` -- `/wp-admin` -- `/wp-login.php` -- `/.env` -- `/config` - -### Customize Blocked Paths - -Edit `conf/config.yml`: - -```yaml -plugin_config: - deny_pages: - paths: /admin,/private,/internal - status_code: 403 # or 404 -``` - -### Clean Up - -```bash -docker compose down -docker stop container && docker rm container -``` - ---- - -## Scenario 4: JWT Validator (API Authentication) - -**What it does:** Validates JWT tokens in Authorization header before allowing access. - -### Getting Started - -```bash -cd examples/static - -# 1. Copy the JWT validator config -cp conf/config-jwt-validator.yml conf/config.yml - -# 2. JWT keys were already generated by generate-keys.sh -# Location: examples/docker/jwt_pubkey.pem and jwt_private.pem - -# 3. Start backend container -docker run -d --name container -p 8080:8080 byjg/static-httpserver - -# 4. Start EasyHAProxy -docker compose up -d -``` - -### Test Without Token (Should Fail) - -```bash -curl -k https://host1.local/ -# Expected: Missing Authorization HTTP header -``` - -### Test With Valid Token - -```bash -# 1. Generate a test JWT token using jwt_private.pem -# You can use https://jwt.io or a JWT library - -# 2. Example with valid token: -TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." -curl -k -H "Authorization: Bearer $TOKEN" https://host1.local/ -# Expected: Hello from Static HTTP Server! (if token is valid) -``` - -### Generate Test Token - -```python -# Python example using PyJWT -import jwt -from datetime import datetime, timedelta - -with open('examples/docker/jwt_private.pem', 'r') as f: - private_key = f.read() - -payload = { - 'iss': 'https://auth.example.com/', - 'aud': 'https://api.example.com', - 'exp': datetime.utcnow() + timedelta(hours=1) -} - -token = jwt.encode(payload, private_key, algorithm='RS256') -print(token) -``` - -### What's Validated - -- Authorization header must be present -- Token must be valid JWT format -- Signature must match public key (`jwt_pubkey.pem`) -- Issuer must match: `https://auth.example.com/` -- Audience must match: `https://api.example.com` -- Token must not be expired - -### Customize JWT Settings - -Edit `conf/config.yml`: - -```yaml -plugin_config: - jwt_validator: - algorithm: RS256 - issuer: https://your-auth-server.com/ - audience: https://your-api.com - pubkey_path: /certs/haproxy/jwt_pubkey.pem -``` - -### Clean Up - -```bash -docker compose down -docker stop container && docker rm container -``` - ---- - -## Configuration File Reference +## Configuration Files All scenarios use `/etc/haproxy/static/config.yml` mounted from `./conf/config.yml`. -### Basic Structure +Choose one of these pre-made configurations: + +| Configuration File | Description | +|----------------------------|-------------------------------------------------| +| `config-basic.yml` | Simple HTTP→HTTPS redirect with SSL termination | +| `config-certbot.yml` | Let's Encrypt SSL (requires public domain) | +| `config-deny-pages.yml` | Block specific paths (e.g., `/admin`, `/.env`) | +| `config-jwt-validator.yml` | JWT token validation for API authentication | + +## Prerequisites + +- SSL certificates generated (`./examples/generate-keys.sh`) +- `/etc/hosts` entry for `host1.local` +- Backend container running on port 8080 + +See header comments in [docker-compose.yml](docker-compose.yml) for detailed setup. + +## Documentation Structure + +The docker-compose.yml file contains: +- **WHAT THIS DEMONSTRATES** - Key features and concepts +- **REQUIREMENTS** - Idempotent setup commands (safe to run multiple times) +- **HOW TO START** - Commands to start backend and EasyHAProxy +- **HOW TO VERIFY IT'S WORKING** - Test commands with expected outputs +- **CLEAN UP** - Commands to stop and remove resources + +## Example Workflow + +```bash +# 1. Generate certificates +cd ../.. && ./examples/generate-keys.sh && cd examples/static + +# 2. Choose a configuration +cp conf/config-basic.yml conf/config.yml + +# 3. Start backend +docker run -d --name container -p 8080:8080 byjg/static-httpserver + +# 4. Start EasyHAProxy +docker compose up -d + +# 5. Test +curl -k https://host1.local/ + +# 6. Clean up +docker compose down +docker stop container && docker rm container +``` + +## Configuration File Reference + +Basic structure of `config.yml`: ```yaml stats: @@ -314,111 +79,19 @@ stats: password: password port: 1936 -customerrors: true - -easymapping: - - port: 80 - redirect: - host1.local: https://host1.local - - - port: 443 - ssl: true - hosts: - host1.local: - containers: - - container:8080 -``` - -### With Plugins - -```yaml easymapping: - port: 443 ssl: true hosts: host1.local: containers: - - container:8080 - plugins: - - deny_pages - plugin_config: - deny_pages: - paths: /admin,/private - status_code: 404 + - container:8080 # Can also be IP:PORT for external backends ``` ---- +See `conf/` directory for complete examples. -## Advanced: Multiple Backends - -Load balance across multiple containers: - -```yaml -hosts: - api.example.com: - containers: - - api1:8080 - - api2:8080 - - api3:8080 -``` - ---- - -## Advanced: External Backends - -Route to non-Docker backends: - -```yaml -hosts: - legacy.example.com: - containers: - - 192.168.1.100:8080 - - 192.168.1.101:8080 -``` - ---- - -## Troubleshooting - -### FileNotFoundError: config.yml - -```bash -# Make sure config.yml exists -ls conf/config.yml - -# If missing, copy from an example: -cp conf/config-basic.yml conf/config.yml -``` - -### 503 Service Unavailable - -```bash -# Check backend is running -docker ps | grep container -curl http://localhost:8080 -``` - -### SSL Certificate Not Found - -```bash -# Verify certificate exists -ls -la host1.local.pem - -# Regenerate if needed -cd ../.. && ./examples/generate-keys.sh -``` - -### Changes Not Applied - -```bash -# Restart to reload config -docker compose restart -``` - ---- - -## Further Reading +## Additional Documentation - [Static Configuration Guide](../../docs/static.md) -- [Using Plugins](../../docs/plugins.md) +- [Using Plugins](../../docs/plugins/) - [Environment Variables](../../docs/environment-variable.md) diff --git a/examples/static/docker-compose.yml b/examples/static/docker-compose.yml index 49c1dc8..92f9966 100644 --- a/examples/static/docker-compose.yml +++ b/examples/static/docker-compose.yml @@ -1,5 +1,62 @@ -# To test: +# ============================================================================== +# EXAMPLE: Static Configuration Mode +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - EasyHAProxy using static YAML configuration (no service discovery) +# - Useful for non-containerized backends, VMs, or bare metal servers +# - Configuration via /etc/haproxy/static/config.yml +# +# REQUIREMENTS (run these first): +# ```bash +# # Generate SSL certificates +# cd ../.. && ./examples/generate-keys.sh && cd examples/static +# +# # Add to /etc/hosts (idempotent) +# grep -q "host1.local" /etc/hosts || echo "127.0.0.1 host1.local" | sudo tee -a /etc/hosts +# +# # Copy a configuration file (choose one): +# cp conf/config-basic.yml conf/config.yml # Basic HTTP→HTTPS redirect +# # OR +# cp conf/config-certbot.yml conf/config.yml # Let's Encrypt (requires public domain) +# # OR +# cp conf/config-deny-pages.yml conf/config.yml # Block specific paths +# # OR +# cp conf/config-jwt-validator.yml conf/config.yml # JWT authentication +# ``` +# +# HOW TO START: +# ```bash +# # Start backend container +# docker run -d --name container -p 8080:8080 byjg/static-httpserver +# +# # Start EasyHAProxy +# docker compose up -d +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Test HTTPS # curl -k -H "Host: host1.local" https://127.0.0.1/ +# # Expected: 200 OK with "Hello from Static HTTP Server!" +# +# # Test HTTP redirect (if using basic config) +# curl -I -H "Host: host1.local" http://127.0.0.1 +# # Expected: HTTP/1.1 301 Moved Permanently +# +# # View HAProxy stats +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# ``` +# +# CLEAN UP: +# ```bash +# docker compose down +# docker stop container && docker rm container +# ``` +# +# ============================================================================== services: haproxy: diff --git a/examples/swarm/README.md b/examples/swarm/README.md index f0fce59..64d67ef 100644 --- a/examples/swarm/README.md +++ b/examples/swarm/README.md @@ -1,170 +1,52 @@ # Docker Swarm Examples -This directory contains Docker Swarm stack examples demonstrating EasyHAProxy in a Swarm cluster environment. - -## What is Docker Swarm Mode? - -Docker Swarm mode enables: -- **Service orchestration** across multiple nodes -- **Service scaling** with replicas -- **Load balancing** across service replicas -- **Rolling updates** with zero downtime -- **Service discovery** via overlay networks - -EasyHAProxy automatically discovers Swarm services and routes traffic based on service labels. - ---- - -## Prerequisites - -### 1. Initialize Docker Swarm - -```bash -# On manager node -docker swarm init - -# On worker nodes (use token from swarm init output) -docker swarm join --token :2377 -``` - -### 2. Create Overlay Network - -```bash -# Create attachable overlay network for EasyHAProxy -docker network create --driver overlay --attachable easyhaproxy -``` - -**Why attachable?** Allows both swarm services and standalone containers to connect. - ---- - -## Files in This Directory - -- `easyhaproxy.yml` - EasyHAProxy service stack -- `services.yml` - Example application services -- `portainer.yml` - Portainer management interface -- `certs/` - Directory for SSL certificates - ---- - -## Prerequisites: Generate SSL Certificates - -**IMPORTANT:** Before running any examples, you must generate the required SSL certificates: - -```bash -# From the repository root -./examples/generate-keys.sh -``` - -This script automatically generates: -- SSL certificates for host1.local and host2.local (placed in `examples/swarm/certs/`) -- JWT keys for authentication examples -- All other .pem files needed for testing - -**Note:** These are self-signed certificates for testing only. Do not use in production. - ---- +Self-contained examples for EasyHAProxy in Docker Swarm mode. **All documentation is in the YAML files as header comments.** ## Quick Start -### 1. Deploy EasyHAProxy +1. Pick an example below +2. Open the YAML file +3. Read the header comments for complete instructions +4. Run the commands step-by-step -```bash -cd examples/swarm +## Prerequisites -# Edit easyhaproxy.yml and change: -# EASYHAPROXY_CERTBOT_EMAIL: your-email@example.com +All examples require: +- Docker Swarm initialized (`docker swarm init`) +- Overlay network created (`docker network create --driver overlay --attachable easyhaproxy`) +- EasyHAProxy deployed (`docker stack deploy -c easyhaproxy.yml easyhaproxy`) -# Deploy stack -docker stack deploy -c easyhaproxy.yml easyhaproxy -``` +See header comments in each file for detailed setup instructions. -**What this creates:** -- EasyHAProxy service with 1 replica -- Exposed ports: 80, 443, 1936 -- Mounts Docker socket for service discovery -- Mounts volume for certbot certificates +## Basic Examples -### 2. Deploy Example Services +| File | Description | +|------|-------------| +| [easyhaproxy.yml](easyhaproxy.yml) | EasyHAProxy service for Swarm with stats and certbot | +| [services.yml](services.yml) | Basic services with SSL (embedded cert and file-based) | +| [portainer.yml](portainer.yml) | Portainer management UI behind EasyHAProxy | -```bash -docker stack deploy -c services.yml myapp -``` +## Plugin Examples -### 3. (Optional) Deploy Portainer +| File | Description | +|------|-------------| +| [jwt-validator.yml](jwt-validator.yml) | JWT token validation for API protection | +| [ip-whitelist.yml](ip-whitelist.yml) | IP whitelist for admin panels or sensitive services | +| [cloudflare.yml](cloudflare.yml) | Restore real client IPs when behind Cloudflare CDN | +| [plugins-combined.yml](plugins-combined.yml) | Multiple plugins combined for layered security | -```bash -docker stack deploy -c portainer.yml portainer -``` +## Documentation Structure ---- +Each YAML file contains: +- **WHAT THIS DEMONSTRATES** - Key features and concepts +- **REQUIREMENTS** - Idempotent setup commands (safe to run multiple times) +- **HOW TO START** - Command to deploy the stack +- **HOW TO VERIFY IT'S WORKING** - Test commands with expected outputs +- **CLEAN UP** - Commands to remove resources -## Example Files Explained +## Important: Service Labels -### easyhaproxy.yml - -```yaml -services: - haproxy: - image: byjg/easy-haproxy:4.6.0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock # Service discovery - - ./certs:/certs/haproxy # SSL certificates - - certs_certbot:/certs/certbot # Let's Encrypt certs - deploy: - replicas: 1 # Single instance - environment: - EASYHAPROXY_DISCOVER: swarm # Swarm mode! - EASYHAPROXY_SSL_MODE: "loose" - EASYHAPROXY_CERTBOT_EMAIL: changeme@example.org # Change this! - HAPROXY_CUSTOMERRORS: "true" - HAPROXY_USERNAME: admin - HAPROXY_PASSWORD: password - HAPROXY_STATS_PORT: 1936 - ports: - - "80:80/tcp" - - "443:443/tcp" - - "1936:1936/tcp" - networks: - - easyhaproxy # Overlay network - -networks: - easyhaproxy: - external: true # Created separately - -volumes: - certs_certbot: # Persistent certbot data -``` - -**Key differences from Docker Compose mode:** -- `EASYHAPROXY_DISCOVER: swarm` - Discovery mode -- `deploy.replicas: 1` - Swarm deployment config -- External overlay network - ---- - -## Service Labels in Swarm - -Service labels are similar to container labels but applied to **services**, not containers. - -### Basic Service Example - -```yaml -services: - webapp: - image: nginx:alpine - deploy: - replicas: 3 # 3 instances for load balancing - labels: - # Service labels (not container labels!) - easyhaproxy.http.host: "webapp.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "80" - networks: - - easyhaproxy -``` - -**Important:** Use `deploy.labels`, NOT top-level `labels`! +In Swarm mode, labels must be under `deploy.labels`, NOT top-level `labels`: ```yaml # ✅ CORRECT - Service labels @@ -177,760 +59,9 @@ labels: easyhaproxy.http.host: example.com ``` ---- +## Additional Documentation -## Common Use Cases - -### Use Case 1: Simple HTTP Service - -```yaml -services: - myapp: - image: my-app:latest - deploy: - replicas: 3 - labels: - easyhaproxy.http.host: "myapp.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "3000" - networks: - - easyhaproxy - -networks: - easyhaproxy: - external: true -``` - -Deploy: -```bash -docker stack deploy -c myapp.yml myapp -``` - -### Use Case 2: HTTPS with Let's Encrypt - -```yaml -services: - secure-app: - image: secure-app:latest - deploy: - replicas: 2 - labels: - easyhaproxy.http.host: "secure.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "8080" - easyhaproxy.http.certbot: "true" - easyhaproxy.http.redirect_ssl: "true" - networks: - - easyhaproxy - -networks: - easyhaproxy: - external: true -``` - -**Requirements:** -- Public IP with DNS pointing to swarm -- Ports 80/443 open -- Certbot email configured in `easyhaproxy.yml` - -### Use Case 3: Multiple Domains, One Service - -```yaml -services: - webapp: - image: webapp:latest - deploy: - replicas: 4 - labels: - # Primary domain - easyhaproxy.http.host: "example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "8080" - - # Additional domain (www) - easyhaproxy.http2.host: "www.example.com" - easyhaproxy.http2.port: "80" - easyhaproxy.http2.localport: "8080" - - # API subdomain - easyhaproxy.api.host: "api.example.com" - easyhaproxy.api.port: "80" - easyhaproxy.api.localport: "8080" - networks: - - easyhaproxy -``` - -### Use Case 4: Service with Plugins - -```yaml -services: - api: - image: api-server:latest - deploy: - replicas: 3 - labels: - easyhaproxy.http.host: "api.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "8080" - # Enable plugins - easyhaproxy.http.plugins: "jwt_validator,deny_pages" - # Configure JWT validator - easyhaproxy.http.plugin.jwt_validator.algorithm: "RS256" - easyhaproxy.http.plugin.jwt_validator.issuer: "https://auth.example.com/" - easyhaproxy.http.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api.pem" - # Configure deny_pages - easyhaproxy.http.plugin.deny_pages.paths: "/admin,/private" - easyhaproxy.http.plugin.deny_pages.status_code: "403" - networks: - - easyhaproxy -``` - -### Use Case 5: Multiple Services with Load Balancing - -```yaml -services: - frontend: - image: frontend-app:latest - deploy: - replicas: 2 - labels: - easyhaproxy.http.host: "example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "3000" - networks: - - easyhaproxy - - api: - image: api-server:latest - deploy: - replicas: 5 # More replicas for API - labels: - easyhaproxy.http.host: "api.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "8080" - networks: - - easyhaproxy - - admin: - image: admin-panel:latest - deploy: - replicas: 1 - labels: - easyhaproxy.http.host: "admin.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "4000" - # Restrict access - easyhaproxy.http.plugins: "ip_whitelist" - easyhaproxy.http.plugin.ip_whitelist.allowed_ips: "192.168.1.0/24" - networks: - - easyhaproxy - -networks: - easyhaproxy: - external: true -``` - ---- - -## Plugin Examples - -### JWT Validator Plugin - -Secure your API with JWT token validation in Swarm: - -```yaml -services: - haproxy: - image: byjg/easy-haproxy:4.6.0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - jwt_keys:/etc/haproxy/jwt_keys - deploy: - replicas: 1 - environment: - EASYHAPROXY_DISCOVER: swarm - ports: - - "80:80/tcp" - - "443:443/tcp" - networks: - - easyhaproxy - - api: - image: my-api:latest - deploy: - replicas: 5 - labels: - easyhaproxy.http.host: "api.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "8080" - # Enable JWT validation - easyhaproxy.http.plugins: "jwt_validator" - easyhaproxy.http.plugin.jwt_validator.algorithm: "RS256" - easyhaproxy.http.plugin.jwt_validator.issuer: "https://auth.example.com/" - easyhaproxy.http.plugin.jwt_validator.audience: "https://api.example.com" - easyhaproxy.http.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api_pubkey.pem" - networks: - - easyhaproxy - -networks: - easyhaproxy: - external: true - -volumes: - jwt_keys: -``` - -**Deploy public key using Docker config:** -```bash -# Create Docker config with public key -docker config create jwt_api_pubkey ./api_pubkey.pem - -# Update EasyHAProxy service to use config -docker service update \ - --config-add source=jwt_api_pubkey,target=/etc/haproxy/jwt_keys/api_pubkey.pem \ - easyhaproxy_haproxy -``` - -**Test:** -```bash -# Without token -curl http://api.example.com/users -# Response: Missing Authorization HTTP header - -# With valid token -curl -H "Authorization: Bearer eyJhbGc..." http://api.example.com/users -# Response: Success -``` - ---- - -### Cloudflare IP Restoration Plugin - -Restore original visitor IPs in Swarm environment: - -```yaml -services: - haproxy: - image: byjg/easy-haproxy:4.6.0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - configs: - - source: cloudflare_ips - target: /etc/haproxy/cloudflare_ips.lst - deploy: - replicas: 1 - environment: - EASYHAPROXY_DISCOVER: swarm - ports: - - "80:80/tcp" - - "443:443/tcp" - networks: - - easyhaproxy - - webapp: - image: webapp:latest - deploy: - replicas: 3 - labels: - easyhaproxy.http.host: "myapp.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "8080" - # Enable Cloudflare plugin - easyhaproxy.http.plugins: "cloudflare" - networks: - - easyhaproxy - -networks: - easyhaproxy: - external: true - -configs: - cloudflare_ips: - file: ./cloudflare_ips.lst -``` - -**Create Cloudflare IP list:** -```bash -# Download Cloudflare IPs -curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst -curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst - -# Deploy stack -docker stack deploy -c cloudflare-stack.yml myapp -``` - ---- - -### IP Whitelist Plugin - -Restrict admin panel to specific IPs in Swarm: - -```yaml -services: - admin: - image: admin-panel:latest - deploy: - replicas: 2 - labels: - easyhaproxy.http.host: "admin.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "4000" - # Enable IP whitelist - easyhaproxy.http.plugins: "ip_whitelist" - # Allow office network and VPN - easyhaproxy.http.plugin.ip_whitelist.allowed_ips: "203.0.113.0/24,198.51.100.0/24,10.8.0.0/16" - easyhaproxy.http.plugin.ip_whitelist.status_code: "403" - networks: - - easyhaproxy - -networks: - easyhaproxy: - external: true -``` - -**Test:** -```bash -# From office IP (203.0.113.50) -curl http://admin.example.com -# Response: Success - -# From home/blocked IP -curl http://admin.example.com -# Response: HTTP 403 Forbidden -``` - ---- - -### Multiple Plugins Combined - -Production-ready setup with multiple security layers: - -```yaml -services: - haproxy: - image: byjg/easy-haproxy:4.6.0 - volumes: - - /var/run/docker.sock:/var/run/docker.sock - configs: - - source: cloudflare_ips - target: /etc/haproxy/cloudflare_ips.lst - - source: jwt_pubkey - target: /etc/haproxy/jwt_keys/api_pubkey.pem - deploy: - replicas: 1 - placement: - constraints: - - node.role == manager - environment: - EASYHAPROXY_DISCOVER: swarm - EASYHAPROXY_SSL_MODE: "loose" - EASYHAPROXY_CERTBOT_EMAIL: admin@example.com - ports: - - "80:80/tcp" - - "443:443/tcp" - - "1936:1936/tcp" - networks: - - easyhaproxy - - # Public website with Cloudflare - website: - image: website:latest - deploy: - replicas: 4 - labels: - easyhaproxy.http.host: "example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "3000" - easyhaproxy.http.certbot: "true" - easyhaproxy.http.redirect_ssl: "true" - # Cloudflare + block sensitive paths - easyhaproxy.http.plugins: "cloudflare,deny_pages" - easyhaproxy.http.plugin.deny_pages.paths: "/admin,/.env,/config" - easyhaproxy.http.plugin.deny_pages.status_code: "404" - networks: - - easyhaproxy - - # Authenticated API with JWT - api: - image: api:latest - deploy: - replicas: 6 - labels: - easyhaproxy.http.host: "api.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "8080" - easyhaproxy.http.certbot: "true" - # Cloudflare + JWT + block internal endpoints - easyhaproxy.http.plugins: "cloudflare,jwt_validator,deny_pages" - easyhaproxy.http.plugin.jwt_validator.algorithm: "RS256" - easyhaproxy.http.plugin.jwt_validator.issuer: "https://auth.example.com/" - easyhaproxy.http.plugin.jwt_validator.audience: "https://api.example.com" - easyhaproxy.http.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api_pubkey.pem" - easyhaproxy.http.plugin.deny_pages.paths: "/internal,/metrics" - networks: - - easyhaproxy - - # Admin panel with strict IP restrictions - admin: - image: admin:latest - deploy: - replicas: 2 - labels: - easyhaproxy.http.host: "admin.example.com" - easyhaproxy.http.port: "80" - easyhaproxy.http.localport: "4000" - easyhaproxy.http.certbot: "true" - # IP whitelist only (no public access) - easyhaproxy.http.plugins: "ip_whitelist" - easyhaproxy.http.plugin.ip_whitelist.allowed_ips: "203.0.113.0/24" - easyhaproxy.http.plugin.ip_whitelist.status_code: "403" - networks: - - easyhaproxy - -networks: - easyhaproxy: - external: true - -configs: - cloudflare_ips: - file: ./cloudflare_ips.lst - jwt_pubkey: - file: ./api_pubkey.pem -``` - -**Deploy:** -```bash -docker stack deploy -c production-stack.yml production -``` - -**Security layers:** -- **Website**: Cloudflare IP restoration + path blocking -- **API**: Cloudflare + JWT validation + internal path blocking -- **Admin**: Strict IP whitelist (office network only) - ---- - -## Scaling Services - -Scale services dynamically: - -```bash -# Scale up -docker service scale myapp_webapp=10 - -# Scale down -docker service scale myapp_webapp=2 - -# Check replicas -docker service ls -``` - -EasyHAProxy automatically detects all replicas and load balances across them. - ---- - -## Rolling Updates - -Update services with zero downtime: - -```bash -# Update service image -docker service update --image webapp:v2 myapp_webapp - -# Update with custom settings -docker service update \ - --image webapp:v2 \ - --update-parallelism 2 \ - --update-delay 10s \ - myapp_webapp -``` - -EasyHAProxy continues routing to healthy containers during rollout. - ---- - -## Management Commands - -### View Stacks - -```bash -docker stack ls -``` - -### View Services in Stack - -```bash -docker stack services myapp -``` - -### View Service Details - -```bash -docker service inspect myapp_webapp -``` - -### View Service Logs - -```bash -docker service logs -f myapp_webapp -``` - -### Update Service Labels - -```bash -docker service update \ - --label-add easyhaproxy.http.certbot=true \ - myapp_webapp -``` - -### Remove Stack - -```bash -docker stack rm myapp -``` - ---- - -## SSL Certificates in Swarm - -### Option 1: Let's Encrypt (Recommended) - -Configure in `easyhaproxy.yml`: -```yaml -environment: - EASYHAPROXY_CERTBOT_EMAIL: your-email@example.com -``` - -Enable per-service: -```yaml -deploy: - labels: - easyhaproxy.http.certbot: "true" -``` - -### Option 2: Custom Certificates - -Mount certificates directory: -```yaml -# easyhaproxy.yml -volumes: - - ./certs:/certs/haproxy -``` - -Place certificate files: -```bash -./certs/ - ├── example.com.pem - ├── api.example.com.pem - └── secure.example.com.pem -``` - -### Option 3: Docker Secrets (Production) - -```bash -# Create secret -docker secret create example_com_cert ./example.com.pem - -# Use in stack - -services: - haproxy: - secrets: - - example_com_cert - environment: - EASYHAPROXY_SSL_CERT_example_com: /run/secrets/example_com_cert - -secrets: - example_com_cert: - external: true -``` - ---- - -## Monitoring and Stats - -### HAProxy Stats Interface - -Access at: `http://:1936` -- Username: `admin` (configured in `easyhaproxy.yml`) -- Password: `password` (configured in `easyhaproxy.yml`) - -### Service Health - -```bash -# Check service health -docker service ps myapp_webapp - -# View detailed service info -docker service inspect --pretty myapp_webapp -``` - ---- - -## Troubleshooting - -### Service Not Detected - -**Check service labels:** -```bash -docker service inspect myapp_webapp | grep -A 20 Labels -``` - -Ensure labels are under `deploy.labels`, not top-level `labels`. - -**Check EasyHAProxy logs:** -```bash -docker service logs -f easyhaproxy_haproxy -``` - -### Service Unreachable (503) - -**Causes:** -- Service containers not ready yet -- Wrong network configuration -- Service crashed - -**Debug:** -```bash -# Check service is running -docker service ps myapp_webapp - -# Check network -docker network inspect easyhaproxy - -# Test service directly -docker run --rm --network easyhaproxy alpine \ - wget -O- http://myapp_webapp:8080 -``` - -### Overlay Network Issues - -**Create network if missing:** -```bash -docker network create --driver overlay --attachable easyhaproxy -``` - -**Verify service is on network:** -```bash -docker service inspect myapp_webapp | grep -A 5 Networks -``` - -### EasyHAProxy Not Starting - -**Check Docker socket permissions:** -```bash -docker service logs easyhaproxy_haproxy -``` - -**Verify socket is mounted:** -```bash -docker service inspect easyhaproxy_haproxy | grep -A 5 Mounts -``` - -### Certificate Issues - -**Certbot fails:** -- Ensure swarm is publicly accessible -- Check DNS points to swarm IP -- Verify ports 80/443 are open -- Check certbot logs: `docker service logs easyhaproxy_haproxy | grep certbot` - -**Custom cert not found:** -```bash -# Exec into service container -docker exec -it $(docker ps -q -f name=easyhaproxy) sh -ls -la /certs/haproxy/ -``` - ---- - -## High Availability Setup - -### Multiple Manager Nodes - -```bash -# On additional manager nodes -docker swarm join-token manager -# Use token on new nodes -``` - -### EasyHAProxy Constraints - -Run EasyHAProxy on specific node: - -```yaml -services: - haproxy: - deploy: - placement: - constraints: - - node.role == manager - - node.labels.haproxy == true -``` - -Label node: -```bash -docker node update --label-add haproxy=true -``` - -### Multiple EasyHAProxy Replicas - -**Not recommended** - EasyHAProxy should run as single instance because: -- Multiple instances would compete for port binding -- Use external load balancer (cloud LB, keepalived, etc.) for HA - -**Alternative HA pattern:** -``` -Internet → Cloud Load Balancer → Multiple Swarm Nodes - └→ EasyHAProxy (runs on 1 node) - └→ Services (distributed across nodes) -``` - ---- - -## Best Practices - -1. **Use Overlay Networks:** - - Create dedicated network for EasyHAProxy - - Use `--attachable` for flexibility - -2. **Service Labels:** - - Always use `deploy.labels`, never top-level `labels` - - Use clear, descriptive domain names - -3. **Replicas:** - - Start with 2-3 replicas per service - - Scale based on load monitoring - - Use odd number for consensus (3, 5, 7) - -4. **Updates:** - - Use rolling updates for zero downtime - - Set appropriate `update-delay` - - Test in staging first - -5. **Monitoring:** - - Enable HAProxy stats - - Use Portainer for visual management - - Monitor service health regularly - -6. **Security:** - - Use Docker secrets for sensitive data - - Restrict admin panel access - - Use SSL/TLS for production - - Apply IP whitelisting for admin interfaces - -7. **Persistence:** - - Use volumes for certbot certificates - - Backup certificate volumes - - Store custom certs in version control (encrypted) - ---- - -## Further Reading - -- [Docker Swarm Documentation](../../docs/swarm.md) +- [Docker Swarm Guide](../../docs/swarm.md) - [Container Labels Reference](../../docs/container-labels.md) -- [Using Plugins](../../docs/plugins.md) +- [Using Plugins](../../docs/plugins/) - [ACME/Let's Encrypt](../../docs/acme.md) -- [Environment Variables](../../docs/environment-variable.md) -- [Official Docker Swarm Docs](https://docs.docker.com/engine/swarm/) diff --git a/examples/swarm/cloudflare.yml b/examples/swarm/cloudflare.yml index f3c1520..5a0be1e 100644 --- a/examples/swarm/cloudflare.yml +++ b/examples/swarm/cloudflare.yml @@ -1,26 +1,69 @@ -# Cloudflare IP Restoration Plugin Example for Docker Swarm +# ============================================================================== +# EXAMPLE: Cloudflare IP Restoration (Swarm) +# ============================================================================== # -# This example demonstrates restoring original visitor IPs when using Cloudflare CDN +# WHAT THIS DEMONSTRATES: +# - Restoring original visitor IPs when behind Cloudflare CDN +# - Using Docker configs to manage Cloudflare IP lists +# - Service discovery in Swarm mode with plugins +# - Load balancing across multiple replicas # -# Prerequisites: -# 1. Docker Swarm initialized: -# docker swarm init +# REQUIREMENTS (run these first): +# ```bash +# # Initialize Docker Swarm (if not already initialized) +# docker swarm init # -# 2. Create overlay network: -# docker network create --driver overlay --attachable easyhaproxy +# # Create overlay network (idempotent) +# docker network ls | grep -q easyhaproxy || docker network create --driver overlay --attachable easyhaproxy # -# 3. Download Cloudflare IPs and create Docker config: -# curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst -# curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst -# docker config create cloudflare_ips cloudflare_ips.lst +# # Ensure EasyHAProxy is deployed +# docker stack deploy -c easyhaproxy.yml easyhaproxy # -# 4. Deploy the stack: -# docker stack deploy -c cloudflare.yml webapp +# # Download Cloudflare IP ranges and create Docker config +# curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst +# curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst +# docker config create cloudflare_ips cloudflare_ips.lst +# rm cloudflare_ips.lst # -# 5. Test: -# curl http:/// +# # Add to /etc/hosts for local testing (idempotent) +# grep -q "myapp.example.com" /etc/hosts || echo "127.0.0.1 myapp.example.com" | sudo tee -a /etc/hosts +# ``` # -# Note: This plugin is most useful when your site is actually behind Cloudflare. +# HOW TO START: +# ```bash +# docker stack deploy -c cloudflare.yml webapp +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check stack is deployed +# docker stack ls | grep webapp +# # Expected: webapp stack listed +# +# # Check service is running +# docker service ls | grep webapp_webapp +# # Expected: webapp_webapp with 4/4 replicas +# +# # Test the application +# curl -H "Host: myapp.example.com" http://localhost/ +# # Expected: 200 OK with "App Behind Cloudflare" +# +# # Check HAProxy config includes Cloudflare IPs +# docker exec $(docker ps -q -f name=easyhaproxy_haproxy) cat /etc/haproxy/haproxy.cfg | grep -A 5 "cloudflare" +# # Expected: ACL rules for Cloudflare IP ranges +# ``` +# +# CLEAN UP: +# ```bash +# docker stack rm webapp +# # To also remove the Cloudflare IPs config: +# # docker config rm cloudflare_ips +# ``` +# +# NOTE: This plugin is most useful when your site is actually behind Cloudflare CDN. +# The plugin uses the CF-Connecting-IP header to restore the original visitor IP. +# +# ============================================================================== version: "3.7" diff --git a/examples/swarm/easyhaproxy.yml b/examples/swarm/easyhaproxy.yml index cb9e967..0265571 100644 --- a/examples/swarm/easyhaproxy.yml +++ b/examples/swarm/easyhaproxy.yml @@ -1,6 +1,55 @@ -# To Install -# docker network create --driver overlay --attachable easyhaproxy +# ============================================================================== +# EXAMPLE: EasyHAProxy for Docker Swarm +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - EasyHAProxy running in Swarm mode with service discovery +# - HAProxy stats interface +# - Let's Encrypt/Certbot support +# - Shared overlay network for services +# +# REQUIREMENTS (run these first): +# ```bash +# # Initialize Docker Swarm (if not already initialized) +# docker swarm init +# +# # Create overlay network (idempotent) +# docker network ls | grep -q easyhaproxy || docker network create --driver overlay --attachable easyhaproxy +# +# # Edit this file and change: +# # Line 18: EASYHAPROXY_CERTBOT_EMAIL to your email +# ``` +# +# HOW TO START: +# ```bash # docker stack deploy -c easyhaproxy.yml easyhaproxy +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check stack is deployed +# docker stack ls +# # Expected: easyhaproxy stack listed +# +# # Check service is running +# docker service ls +# # Expected: easyhaproxy_haproxy with 1/1 replicas +# +# # View HAProxy stats +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# +# # Check logs +# docker service logs -f easyhaproxy_haproxy +# ``` +# +# CLEAN UP: +# ```bash +# docker stack rm easyhaproxy +# ``` +# +# ============================================================================== services: diff --git a/examples/swarm/ip-whitelist.yml b/examples/swarm/ip-whitelist.yml index 829925b..95c56ba 100644 --- a/examples/swarm/ip-whitelist.yml +++ b/examples/swarm/ip-whitelist.yml @@ -1,25 +1,65 @@ -# IP Whitelist Plugin Example for Docker Swarm +# ============================================================================== +# EXAMPLE: IP Whitelist Plugin (Swarm) +# ============================================================================== # -# This example demonstrates restricting access to specific IP addresses in Swarm +# WHAT THIS DEMONSTRATES: +# - Restricting access to specific IP addresses/networks +# - IP-based access control for admin panels or sensitive services +# - Returning custom status codes for blocked IPs +# - Service discovery in Swarm mode with plugins # -# Prerequisites: -# 1. Docker Swarm initialized: -# docker swarm init +# REQUIREMENTS (run these first): +# ```bash +# # Initialize Docker Swarm (if not already initialized) +# docker swarm init # -# 2. Create overlay network: -# docker network create --driver overlay --attachable easyhaproxy +# # Create overlay network (idempotent) +# docker network ls | grep -q easyhaproxy || docker network create --driver overlay --attachable easyhaproxy # -# 3. Update allowed_ips label with your actual IP addresses/networks +# # Ensure EasyHAProxy is deployed +# docker stack deploy -c easyhaproxy.yml easyhaproxy # -# 4. Deploy the stack: -# docker stack deploy -c ip-whitelist.yml admin +# # Add to /etc/hosts for local testing (idempotent) +# grep -q "admin.example.com" /etc/hosts || echo "127.0.0.1 admin.example.com" | sudo tee -a /etc/hosts # -# 5. Test from allowed IP: -# curl http:/// -# # Response: Success (200 OK) +# # IMPORTANT: Edit this file (ip-whitelist.yml) line 64 to add your actual IP addresses! +# # Get your current IP: curl ifconfig.me +# ``` # -# 6. Test from non-allowed IP: -# # Response: HTTP 403 Forbidden +# HOW TO START: +# ```bash +# docker stack deploy -c ip-whitelist.yml admin +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check stack is deployed +# docker stack ls | grep admin +# # Expected: admin stack listed +# +# # Check service is running +# docker service ls | grep admin_admin +# # Expected: admin_admin with 3/3 replicas +# +# # Test from allowed IP (assumes 127.0.0.1 or your IP is in the whitelist) +# curl -H "Host: admin.example.com" http://localhost/ +# # Expected: 200 OK with "Admin Panel - IP Restricted" +# +# # Test from blocked IP (using a different IP via proxy or VPN) +# # Expected: HTTP 403 Forbidden +# +# # View HAProxy stats to see IP whitelist rules +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# ``` +# +# CLEAN UP: +# ```bash +# docker stack rm admin +# ``` +# +# ============================================================================== version: "3.7" diff --git a/examples/swarm/jwt-validator.yml b/examples/swarm/jwt-validator.yml index f225f52..568e89b 100644 --- a/examples/swarm/jwt-validator.yml +++ b/examples/swarm/jwt-validator.yml @@ -1,35 +1,78 @@ -# JWT Validator Plugin Example for Docker Swarm +# ============================================================================== +# EXAMPLE: JWT Validator Plugin (Swarm) +# ============================================================================== # -# This example demonstrates JWT token validation for API protection in Swarm +# WHAT THIS DEMONSTRATES: +# - JWT token validation for API authentication +# - Using Docker configs to manage JWT public keys +# - Validating issuer, audience, and expiration claims +# - Service discovery in Swarm mode with plugins # -# Prerequisites: -# 1. Docker Swarm initialized: -# docker swarm init +# REQUIREMENTS (run these first): +# ```bash +# # Initialize Docker Swarm (if not already initialized) +# docker swarm init # -# 2. Create overlay network: -# docker network create --driver overlay --attachable easyhaproxy +# # Create overlay network (idempotent) +# docker network ls | grep -q easyhaproxy || docker network create --driver overlay --attachable easyhaproxy # -# 3. Generate JWT keys and create Docker config: -# openssl genrsa -out jwt_private.pem 2048 -# openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem -# docker config create jwt_api_pubkey jwt_pubkey.pem +# # Ensure EasyHAProxy is deployed +# docker stack deploy -c easyhaproxy.yml easyhaproxy # -# 4. Deploy the stack: -# docker stack deploy -c jwt-validator.yml api +# # Generate JWT key pair (RS256 algorithm) +# openssl genrsa -out jwt_private.pem 2048 +# openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem # -# 5. Test without token (should fail): -# curl http:/// -# # Response: Missing Authorization HTTP header +# # Create Docker config with public key +# docker config create jwt_api_pubkey jwt_pubkey.pem # -# 6. Generate test JWT at https://jwt.io with: -# - Algorithm: RS256 -# - Payload: {"iss":"https://auth.example.com/","aud":"https://api.example.com","exp":9999999999} -# - Use your jwt_private.pem for signing +# # Add to /etc/hosts for local testing (idempotent) +# grep -q "api.example.com" /etc/hosts || echo "127.0.0.1 api.example.com" | sudo tee -a /etc/hosts +# ``` # -# 7. Test with token: -# TOKEN="eyJhbGc..." -# curl -H "Authorization: Bearer $TOKEN" http:/// -# # Response: Success +# HOW TO START: +# ```bash +# docker stack deploy -c jwt-validator.yml api +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check stack is deployed +# docker stack ls | grep api +# # Expected: api stack listed +# +# # Check service is running +# docker service ls | grep api_api +# # Expected: api_api with 5/5 replicas +# +# # Test without token (should fail) +# curl -H "Host: api.example.com" http://localhost/ +# # Expected: HTTP 401 with "Missing Authorization HTTP header" +# +# # Generate test JWT at https://jwt.io with: +# # - Algorithm: RS256 +# # - Payload: {"iss":"https://auth.example.com/","aud":"https://api.example.com","exp":9999999999} +# # - Use your jwt_private.pem content in "Verify Signature" section +# +# # Test with valid token (replace TOKEN with your JWT) +# TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." +# curl -H "Host: api.example.com" -H "Authorization: Bearer $TOKEN" http://localhost/ +# # Expected: 200 OK with "Protected API - JWT Required" +# +# # Test with invalid/expired token +# curl -H "Host: api.example.com" -H "Authorization: Bearer invalid_token" http://localhost/ +# # Expected: HTTP 401 with error message +# ``` +# +# CLEAN UP: +# ```bash +# docker stack rm api +# # To also remove the JWT public key config and generated keys: +# # docker config rm jwt_api_pubkey +# # rm jwt_private.pem jwt_pubkey.pem +# ``` +# +# ============================================================================== version: "3.7" diff --git a/examples/swarm/plugins-combined.yml b/examples/swarm/plugins-combined.yml index c9c1f59..b48c493 100644 --- a/examples/swarm/plugins-combined.yml +++ b/examples/swarm/plugins-combined.yml @@ -1,31 +1,93 @@ -# Multiple Plugins Combined Example for Docker Swarm +# ============================================================================== +# EXAMPLE: Multiple Plugins Combined (Swarm) +# ============================================================================== # -# This example demonstrates using multiple plugins together for enhanced security +# WHAT THIS DEMONSTRATES: +# - Using multiple plugins together for layered security +# - Three different security profiles for different service types: +# * Public website: Cloudflare IP restoration + path blocking +# * Protected API: JWT authentication + path blocking +# * Admin panel: Strict IP whitelist +# - Complex production-ready security configuration # -# Prerequisites: -# 1. Docker Swarm initialized: -# docker swarm init +# REQUIREMENTS (run these first): +# ```bash +# # Initialize Docker Swarm (if not already initialized) +# docker swarm init # -# 2. Create overlay network: -# docker network create --driver overlay --attachable easyhaproxy +# # Create overlay network (idempotent) +# docker network ls | grep -q easyhaproxy || docker network create --driver overlay --attachable easyhaproxy # -# 3. Generate JWT keys and create Docker config: -# openssl genrsa -out jwt_private.pem 2048 -# openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem -# docker config create jwt_api_pubkey jwt_pubkey.pem +# # Ensure EasyHAProxy is deployed +# docker stack deploy -c easyhaproxy.yml easyhaproxy # -# 4. Download Cloudflare IPs and create Docker config: -# curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst -# curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst -# docker config create cloudflare_ips cloudflare_ips.lst +# # Generate JWT key pair (RS256 algorithm) +# openssl genrsa -out jwt_private.pem 2048 +# openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem +# docker config create jwt_api_pubkey jwt_pubkey.pem # -# 5. Deploy the stack: -# docker stack deploy -c plugins-combined.yml production +# # Download Cloudflare IP ranges and create Docker config +# curl https://www.cloudflare.com/ips-v4 > cloudflare_ips.lst +# curl https://www.cloudflare.com/ips-v6 >> cloudflare_ips.lst +# docker config create cloudflare_ips cloudflare_ips.lst +# rm cloudflare_ips.lst jwt_private.pem jwt_pubkey.pem # -# This creates three services with different security profiles: -# - Public website: Cloudflare + path blocking -# - Protected API: JWT validation + path blocking -# - Admin panel: Strict IP whitelist +# # Add to /etc/hosts for local testing (idempotent) +# grep -q "website.example.com" /etc/hosts || echo "127.0.0.1 website.example.com api.example.com admin.example.com" | sudo tee -a /etc/hosts +# +# # IMPORTANT: Edit this file (plugins-combined.yml) line 124 to add your actual IP! +# ``` +# +# HOW TO START: +# ```bash +# docker stack deploy -c plugins-combined.yml production +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check stack is deployed +# docker stack ls | grep production +# # Expected: production stack listed +# +# # Check all services are running +# docker service ls | grep production +# # Expected: 3 services (website, api, admin) with all replicas running +# +# # Test public website (Cloudflare + deny_pages) +# curl -H "Host: website.example.com" http://localhost/ +# # Expected: 200 OK with "Public Website" +# curl -H "Host: website.example.com" http://localhost/admin +# # Expected: HTTP 404 (blocked by deny_pages) +# +# # Test protected API (JWT + deny_pages) +# curl -H "Host: api.example.com" http://localhost/ +# # Expected: HTTP 401 with "Missing Authorization HTTP header" +# +# # Generate JWT at https://jwt.io (see jwt-validator.yml for details) +# TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." +# curl -H "Host: api.example.com" -H "Authorization: Bearer $TOKEN" http://localhost/ +# # Expected: 200 OK with "Protected API" +# curl -H "Host: api.example.com" -H "Authorization: Bearer $TOKEN" http://localhost/internal +# # Expected: HTTP 403 (blocked by deny_pages) +# +# # Test admin panel (IP whitelist) +# curl -H "Host: admin.example.com" http://localhost/ +# # Expected: 200 OK if your IP is whitelisted, 403 otherwise +# +# # View HAProxy stats to see all plugin configurations +# # URL: http://localhost:1936 +# # Username: admin +# # Password: password +# ``` +# +# CLEAN UP: +# ```bash +# docker stack rm production +# # To also remove Docker configs: +# # docker config rm cloudflare_ips jwt_api_pubkey +# ``` +# +# ============================================================================== version: "3.7" diff --git a/examples/swarm/portainer.yml b/examples/swarm/portainer.yml index aa4f4fc..2061a02 100644 --- a/examples/swarm/portainer.yml +++ b/examples/swarm/portainer.yml @@ -1,5 +1,46 @@ -# To install: +# ============================================================================== +# EXAMPLE: Portainer Behind EasyHAProxy (Swarm) +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - Running Portainer management UI in Swarm mode +# - Service discovery with EasyHAProxy +# - Using persistent volumes for Portainer data +# +# REQUIREMENTS (run these first): +# ```bash +# # Ensure EasyHAProxy is deployed +# docker stack deploy -c easyhaproxy.yml easyhaproxy +# +# # Add to /etc/hosts (idempotent) +# grep -q "portainer.local" /etc/hosts || echo "127.0.0.1 portainer.local" | sudo tee -a /etc/hosts +# ``` +# +# HOW TO START: +# ```bash # docker stack deploy -c portainer.yml portainer +# ``` +# +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check service is running +# docker service ls | grep portainer +# # Expected: portainer_portainer with 1/1 replicas +# +# # Access Portainer +# curl http://portainer.local +# # Or open in browser: http://portainer.local +# # First time: Create admin user +# ``` +# +# CLEAN UP: +# ```bash +# docker stack rm portainer +# # To also remove data volume: +# # docker volume rm portainer_portainer_data +# ``` +# +# ============================================================================== services: portainer: diff --git a/examples/swarm/services.yml b/examples/swarm/services.yml index fc9ac69..28c6b4f 100644 --- a/examples/swarm/services.yml +++ b/examples/swarm/services.yml @@ -1,22 +1,58 @@ -# To install: +# ============================================================================== +# EXAMPLE: Basic Swarm Services with SSL +# ============================================================================== +# +# WHAT THIS DEMONSTRATES: +# - Basic Swarm services with SSL configuration +# - HTTP to HTTPS redirect +# - Two services with different SSL setups (embedded cert vs SSL file) +# - Using deploy.labels for service discovery in Swarm +# +# REQUIREMENTS (run these first): +# ```bash +# # Ensure EasyHAProxy is deployed +# docker stack deploy -c easyhaproxy.yml easyhaproxy +# +# # Generate SSL certificates +# cd ../.. && ./examples/generate-keys.sh && cd examples/swarm +# +# # Add to /etc/hosts (idempotent) +# grep -q "host1.local" /etc/hosts || echo "127.0.0.1 host1.local host2.local" | sudo tee -a /etc/hosts +# ``` +# +# HOW TO START: +# ```bash # docker stack deploy -c services.yml services +# ``` # -# To test: +# HOW TO VERIFY IT'S WORKING: +# ```bash +# # Check services are running +# docker service ls | grep services +# # Expected: services_container and services_container2 with 1/1 replicas +# +# # Test HTTPS for host1.local # curl -k -H "Host: host1.local" https://127.0.0.1/ +# # Expected: 200 OK with hostname +# +# # Test HTTPS for host2.local # curl -k -H "Host: host2.local" https://127.0.0.1/ -# -# curl -I -H Host:host1.local http://127.0.0.1 -# HTTP/1.1 301 Moved Permanently -# content-length: 0 -# location: https://host1.local/ +# # Expected: 200 OK with hostname # -# curl -I -H Host:host2.local http://127.0.0.1 -# HTTP/1.1 301 Moved Permanently -# content-length: 0 -# location: https://host1.local/ +# # Test HTTP redirect +# curl -I -H "Host: host1.local" http://127.0.0.1 +# # Expected: HTTP/1.1 301 Moved Permanently, Location: https://host1.local/ # -# Test SSL: -# openssl s_client -showcerts -connect 127.0.0.1:443 -servername host1.local +# # Verify SSL certificate +# openssl s_client -showcerts -connect 127.0.0.1:443 -servername host1.local < /dev/null +# ``` +# +# CLEAN UP: +# ```bash +# docker stack rm services +# ``` +# +# ============================================================================== services: container: