Migrate configuration paths to /etc/easyhaproxy and improve health check support in E2E tests
- Refactored HAProxy configuration files, templates, and paths to use `/etc/easyhaproxy` instead of `/etc/haproxy`. - Updated Dockerfile to generate DH params and placeholder certificates in the new configuration directory. - Added health check support with timeout to `DockerComposeFixture` in E2E test utilities. - Adjusted tests, templates, and plugins to use the new `Consts`-based configuration paths. - Introduced pytest fixtures for environment isolation and temporary directory management.
This commit is contained in:
parent
3e963228f3
commit
045dd3817e
73 changed files with 600 additions and 287 deletions
|
|
@ -5,7 +5,7 @@
|
|||
# WHAT THIS TESTS:
|
||||
# - HAProxy routing of /.well-known/acme-challenge/ to certbot backend
|
||||
# - Certbot HTTP-01 challenge completion with Pebble ACME server
|
||||
# - Certificate issuance and storage in /certs/certbot/live/{domain}/
|
||||
# - Certificate issuance and storage in /etc/easyhaproxy/certs/live/{domain}/
|
||||
# - HTTPS serving with issued certificate
|
||||
# - Full end-to-end ACME protocol flow
|
||||
#
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
# docker compose -f docker-compose-acme-e2e.yml logs haproxy
|
||||
#
|
||||
# # Verify certificate was issued
|
||||
# ls -la ../../certs/certbot/live/test.local/
|
||||
# ls -la ../../certs/live/test.local/
|
||||
#
|
||||
# # Test HTTPS (will show certificate warning - expected for test certs)
|
||||
# curl -k https://localhost/ -H "Host: test.local"
|
||||
|
|
@ -104,7 +104,7 @@ services:
|
|||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Certificate storage (Docker volume for clean test isolation)
|
||||
- certbot-certs:/certs/certbot
|
||||
- certbot-certs:/etc/easyhaproxy/certs
|
||||
# Pebble CA certificate (downloaded during test session)
|
||||
- ./pebble-ca.pem:/etc/ssl/certs/pebble-ca.pem:ro
|
||||
ports:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
# # - Line 36: easyhaproxy.http.host to your real domain
|
||||
#
|
||||
# # Create certs directory
|
||||
# mkdir -p ./certs/certbot
|
||||
# mkdir -p ./certs
|
||||
# ```
|
||||
#
|
||||
# HOW TO START:
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
# # Expected: Issuer: C = US, O = Let's Encrypt
|
||||
#
|
||||
# # Check certificate files
|
||||
# ls -la ./certs/certbot/
|
||||
# ls -la ./certs/
|
||||
# # Expected: Your domain certificate files
|
||||
# ```
|
||||
#
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
# # 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
|
||||
# # docker compose -f docker-compose-acme.yml down && rm -rf ./certs
|
||||
# ```
|
||||
#
|
||||
# ==============================================================================
|
||||
|
|
@ -64,7 +64,7 @@ services:
|
|||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Persist the CERTBOT to avoid re-challenge when the server restarts
|
||||
- ./certs/certbot:/certs/certbot
|
||||
- ./certs:/etc/easyhaproxy/certs
|
||||
environment:
|
||||
EASYHAPROXY_DISCOVER: docker
|
||||
HAPROXY_CUSTOMERRORS: "true"
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ services:
|
|||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Mount Cloudflare IP list
|
||||
- ./cloudflare_ips.lst:/etc/haproxy/cloudflare_ips.lst:ro
|
||||
- ./cloudflare_ips.lst:/etc/easyhaproxy/cloudflare_ips.lst:ro
|
||||
environment:
|
||||
EASYHAPROXY_DISCOVER: docker
|
||||
HAPROXY_CUSTOMERRORS: "true"
|
||||
|
|
@ -77,4 +77,4 @@ services:
|
|||
|
||||
# Use custom IP list (disable built-in IPs)
|
||||
easyhaproxy.http.plugin.cloudflare.use_builtin_ips: false
|
||||
easyhaproxy.http.plugin.cloudflare.ip_list_path: /etc/haproxy/cloudflare_ips.lst
|
||||
easyhaproxy.http.plugin.cloudflare.ip_list_path: /etc/easyhaproxy/cloudflare_ips.lst
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ services:
|
|||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# Mount the public key for JWT verification
|
||||
- ./jwt_pubkey.pem:/etc/haproxy/jwt_keys/api_pubkey.pem:ro
|
||||
- ./jwt_pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro
|
||||
environment:
|
||||
EASYHAPROXY_DISCOVER: docker
|
||||
HAPROXY_CUSTOMERRORS: "true"
|
||||
|
|
@ -85,4 +85,4 @@ services:
|
|||
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.jwt_validator.pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ services:
|
|||
image: byjg/easy-haproxy:local
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./cloudflare_ips.lst:/etc/haproxy/cloudflare_ips.lst:ro
|
||||
- ./jwt_pubkey.pem:/etc/haproxy/jwt_keys/api_pubkey.pem:ro
|
||||
- ./cloudflare_ips.lst:/etc/easyhaproxy/cloudflare_ips.lst:ro
|
||||
- ./jwt_pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro
|
||||
environment:
|
||||
EASYHAPROXY_DISCOVER: docker
|
||||
HAPROXY_CUSTOMERRORS: "true"
|
||||
|
|
@ -114,7 +114,7 @@ services:
|
|||
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.jwt_validator.pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
|
||||
|
||||
# Block internal/debug endpoints
|
||||
easyhaproxy.http.plugin.deny_pages.paths: /internal,/debug,/metrics
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ services:
|
|||
image: byjg/easy-haproxy:5.0.0
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- certs_certbot:/certs/certbot
|
||||
# - certs_haproxy:/certs/haproxy
|
||||
- certs_certbot:/etc/easyhaproxy/certs/certbot
|
||||
# - certs_haproxy:/etc/easyhaproxy/certs/haproxy
|
||||
|
||||
environment:
|
||||
EASYHAPROXY_DISCOVER: docker
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ services:
|
|||
image: byjg/easy-haproxy:local
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./host2.local.pem:/certs/haproxy/host2.local.pem
|
||||
- ./host2.local.pem:/etc/easyhaproxy/certs/haproxy/host2.local.pem
|
||||
environment:
|
||||
EASYHAPROXY_DISCOVER: docker
|
||||
EASYHAPROXY_SSL_MODE: "loose"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
# # Edit your EasyHAProxy deployment and add:
|
||||
# # volumeMounts:
|
||||
# # - name: cloudflare-ips
|
||||
# # mountPath: /etc/haproxy/cloudflare_ips.lst
|
||||
# # mountPath: /etc/easyhaproxy/cloudflare_ips.lst
|
||||
# # subPath: cloudflare_ips.lst
|
||||
# # volumes:
|
||||
# # - name: cloudflare-ips
|
||||
|
|
@ -121,7 +121,7 @@ metadata:
|
|||
# easyhaproxy.plugin.cloudflare.ip_list: "MTAuMC4wLjAvOAoxNzIuMTYuMC4wLzEyCjE5Mi4xNjguMC4wLzE2CjEyNy4wLjAuMQ=="
|
||||
|
||||
# Optional: Specify custom IP list file path (only used if ip_list is not provided)
|
||||
# easyhaproxy.plugin.cloudflare.ip_list_path: "/etc/haproxy/cloudflare_ips.lst"
|
||||
# easyhaproxy.plugin.cloudflare.ip_list_path: "/etc/easyhaproxy/cloudflare_ips.lst"
|
||||
name: webapp-ingress-cloudflare
|
||||
namespace: default
|
||||
spec:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
# There are three ways to provide the JWT public key:
|
||||
#
|
||||
# 1. pubkey_path - Mount a file and reference the path (requires ConfigMap or Volume)
|
||||
# easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/haproxy/jwt_keys/api_pubkey.pem"
|
||||
# easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/easyhaproxy/jwt_keys/api_pubkey.pem"
|
||||
#
|
||||
# 2. k8s_secret.pubkey - Reference a Kubernetes secret (RECOMMENDED)
|
||||
# Auto-detect key (tries common variations):
|
||||
|
|
@ -141,7 +141,7 @@ metadata:
|
|||
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"
|
||||
easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/easyhaproxy/jwt_keys/api_pubkey.pem"
|
||||
name: api-ingress-jwt
|
||||
namespace: default
|
||||
spec:
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ metadata:
|
|||
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"
|
||||
easyhaproxy.plugin.jwt_validator.pubkey_path: "/etc/easyhaproxy/jwt_keys/api_pubkey.pem"
|
||||
# Block internal paths
|
||||
easyhaproxy.plugin.deny_pages.paths: "/internal,/debug,/metrics"
|
||||
easyhaproxy.plugin.deny_pages.status_code: "403"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Static mode uses explicit YAML configuration files instead of dynamic service di
|
|||
|
||||
## Configuration Files
|
||||
|
||||
All scenarios use `/etc/haproxy/static/config.yml` mounted from `./conf/config.yml`.
|
||||
All scenarios use `/etc/easyhaproxy/static/config.yml` mounted from `./conf/config.yml`.
|
||||
|
||||
Choose one of these pre-made configurations:
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
#
|
||||
# To use:
|
||||
# 1. Update the container name and ports to match your setup
|
||||
# 2. Place SSL certificate at /certs/haproxy/host1.local.pem
|
||||
# 3. Mount this config: -v ./conf/config-basic.yml:/etc/haproxy/static/config.yml
|
||||
# 2. Place SSL certificate at /etc/easyhaproxy/certs/haproxy/host1.local.pem
|
||||
# 3. Mount this config: -v ./conf/config-basic.yml:/etc/easyhaproxy/static/config.yml
|
||||
|
||||
stats:
|
||||
username: admin
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@
|
|||
# EASYHAPROXY_CERTBOT_EMAIL=your-email@example.com
|
||||
#
|
||||
# 4. Mount this config:
|
||||
# -v ./conf/config-certbot.yml:/etc/haproxy/static/config.yml
|
||||
# -v ./conf/config-certbot.yml:/etc/easyhaproxy/static/config.yml
|
||||
#
|
||||
# 5. Persist certificates:
|
||||
# -v ./certs/certbot:/certs/certbot
|
||||
# -v ./etc/easyhaproxy/certs/certbot:/etc/easyhaproxy/certs/certbot
|
||||
#
|
||||
# How it works:
|
||||
# - EasyHAProxy requests certificates from Let's Encrypt via HTTP-01 challenge
|
||||
# - Certificates are stored in /certs/certbot/
|
||||
# - Certificates are stored in /etc/easyhaproxy/certs/certbot/
|
||||
# - Certificates auto-renew when needed
|
||||
#
|
||||
# Note: Let's Encrypt has rate limits. Use staging environment for testing:
|
||||
|
|
@ -56,7 +56,7 @@ containers:
|
|||
# Domain without certbot (uses custom certificate)
|
||||
"custom.example.com:80":
|
||||
ip: ["custom-app:8080"]
|
||||
# No certbot - expects certificate at /certs/haproxy/custom.example.com.pem
|
||||
# No certbot - expects certificate at /etc/easyhaproxy/certs/haproxy/custom.example.com.pem
|
||||
|
||||
# HTTPS Port 443
|
||||
# Serves HTTPS traffic with auto-generated certificates
|
||||
|
|
@ -65,7 +65,7 @@ containers:
|
|||
ip: ["webapp:8080"]
|
||||
ssl: true
|
||||
# Certificate path (auto-generated by certbot)
|
||||
# /certs/certbot/example.com/fullchain.pem
|
||||
# /etc/easyhaproxy/certs/certbot/example.com/fullchain.pem
|
||||
|
||||
"app.example.com:443":
|
||||
ip: ["app:3000"]
|
||||
|
|
@ -76,7 +76,7 @@ containers:
|
|||
ip: ["custom-app:8080"]
|
||||
ssl: true
|
||||
# Place your certificate at:
|
||||
# /certs/haproxy/custom.example.com.pem
|
||||
# /etc/easyhaproxy/certs/haproxy/custom.example.com.pem
|
||||
|
||||
# Multiple domains with different backends
|
||||
# Certbot will request separate certificates for each domain
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
# To use:
|
||||
# 1. Update container names and ports
|
||||
# 2. Mount this config: -v ./conf/config-deny-pages.yml:/etc/haproxy/static/config.yml
|
||||
# 2. Mount this config: -v ./conf/config-deny-pages.yml:/etc/easyhaproxy/static/config.yml
|
||||
# 3. Test blocked paths:
|
||||
# curl http://host1.local/admin # Should return 404
|
||||
# curl http://host2.local/wp-admin # Should return 403 (different config)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@
|
|||
# openssl rsa -in jwt_private.pem -pubout -out jwt_pubkey.pem
|
||||
#
|
||||
# 2. Mount public keys:
|
||||
# -v ./jwt_pubkey.pem:/etc/haproxy/jwt_keys/api_pubkey.pem:ro
|
||||
# -v ./jwt_pubkey2.pem:/etc/haproxy/jwt_keys/admin_pubkey.pem:ro
|
||||
# -v ./jwt_pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro
|
||||
# -v ./jwt_pubkey2.pem:/etc/easyhaproxy/jwt_keys/admin_pubkey.pem:ro
|
||||
#
|
||||
# 3. Mount this config:
|
||||
# -v ./conf/config-jwt-validator.yml:/etc/haproxy/static/config.yml
|
||||
# -v ./conf/config-jwt-validator.yml:/etc/easyhaproxy/static/config.yml
|
||||
#
|
||||
# 4. Test:
|
||||
# # Without token - should fail
|
||||
|
|
@ -42,7 +42,7 @@ containers:
|
|||
algorithm: RS256
|
||||
issuer: https://auth.example.com/
|
||||
audience: https://api.example.com
|
||||
pubkey_path: /etc/haproxy/jwt_keys/api_pubkey.pem
|
||||
pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
|
||||
|
||||
# Internal API - validate signature only (no issuer/audience check)
|
||||
"internal-api.local:80":
|
||||
|
|
@ -52,7 +52,7 @@ containers:
|
|||
jwt_validator:
|
||||
algorithm: RS256
|
||||
# No issuer/audience = skip those validations
|
||||
pubkey_path: /etc/haproxy/jwt_keys/api_pubkey.pem
|
||||
pubkey_path: /etc/easyhaproxy/jwt_keys/api_pubkey.pem
|
||||
|
||||
# Admin API - different issuer and key
|
||||
"admin-api.local:80":
|
||||
|
|
@ -63,7 +63,7 @@ containers:
|
|||
algorithm: RS256
|
||||
issuer: https://admin-auth.example.com/
|
||||
audience: https://admin.example.com
|
||||
pubkey_path: /etc/haproxy/jwt_keys/admin_pubkey.pem
|
||||
pubkey_path: /etc/easyhaproxy/jwt_keys/admin_pubkey.pem
|
||||
deny_pages:
|
||||
paths:
|
||||
- /internal
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
# 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
|
||||
# - Configuration via /etc/easyhaproxy/static/config.yml
|
||||
#
|
||||
# REQUIREMENTS (run these first):
|
||||
# ```bash
|
||||
|
|
@ -65,10 +65,10 @@ services:
|
|||
context: ../..
|
||||
dockerfile: build/Dockerfile
|
||||
volumes:
|
||||
- ./conf/:/etc/haproxy/static/
|
||||
- ../static/host1.local.pem:/certs/haproxy/host1.local.pem:ro
|
||||
- ../docker/jwt_pubkey.pem:/etc/haproxy/jwt_keys/api_pubkey.pem:ro
|
||||
- ../docker/jwt_pubkey.pem:/etc/haproxy/jwt_keys/admin_pubkey.pem:ro
|
||||
- ./conf/:/etc/easyhaproxy/static/
|
||||
- ../static/host1.local.pem:/etc/easyhaproxy/certs/haproxy/host1.local.pem:ro
|
||||
- ../docker/jwt_pubkey.pem:/etc/easyhaproxy/jwt_keys/api_pubkey.pem:ro
|
||||
- ../docker/jwt_pubkey.pem:/etc/easyhaproxy/jwt_keys/admin_pubkey.pem:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
EASYHAPROXY_DISCOVER: static
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ class TestBasicSSL:
|
|||
def test_haproxy_config(self, docker_compose_basic_ssl):
|
||||
"""Test HAProxy configuration has SSL and redirect configurations"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -280,7 +280,7 @@ class TestJWTValidator:
|
|||
def test_haproxy_config(self, docker_compose_jwt_validator):
|
||||
"""Test HAProxy configuration has JWT validator rules in the correct backend"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -308,7 +308,7 @@ class TestJWTValidator:
|
|||
assert "var(txn.aud) -m str https://api.example.com" in backend_block
|
||||
|
||||
# Verify JWT signature verification
|
||||
assert 'jwt_verify(txn.alg,"/etc/haproxy/jwt_keys/api_pubkey.pem")' in backend_block
|
||||
assert 'jwt_verify(txn.alg,"/etc/easyhaproxy/jwt_keys/api_pubkey.pem")' in backend_block
|
||||
|
||||
# Verify expiration check
|
||||
assert "JWT has expired" in backend_block
|
||||
|
|
@ -350,7 +350,7 @@ class TestMultiContainers:
|
|||
def test_haproxy_config(self, docker_compose_multi_containers):
|
||||
"""Test HAProxy configuration has multiple backend servers for load balancing"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -409,7 +409,7 @@ class TestPHPFPM:
|
|||
def test_haproxy_config(self, docker_compose_php_fpm):
|
||||
"""Test HAProxy configuration has FastCGI plugin configuration"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -495,7 +495,7 @@ class TestPluginsCombined:
|
|||
def test_haproxy_config(self, docker_compose_plugins_combined):
|
||||
"""Test HAProxy configuration has all plugin configurations in correct backends"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -506,7 +506,7 @@ class TestPluginsCombined:
|
|||
website_block = extract_backend_block(config, "srv_website_local_80")
|
||||
assert website_block, "Backend srv_website_local_80 not found"
|
||||
assert "# Cloudflare - Restore original visitor IP" in website_block
|
||||
assert "acl from_cloudflare src -f /etc/haproxy/cloudflare_ips.lst" in website_block
|
||||
assert "acl from_cloudflare src -f /etc/easyhaproxy/cloudflare_ips.lst" in website_block
|
||||
assert "# Deny Pages - Block specific paths" in website_block
|
||||
assert "acl denied_path path_beg /admin /wp-admin /wp-login.php /.env /config" in website_block
|
||||
assert "http-request deny deny_status 404 if denied_path" in website_block
|
||||
|
|
@ -604,7 +604,7 @@ class TestIPWhitelist:
|
|||
def test_haproxy_config(self, docker_compose_ip_whitelist):
|
||||
"""Test HAProxy configuration has IP whitelist rules in the correct backend"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -657,7 +657,7 @@ class TestCloudflare:
|
|||
def test_haproxy_config(self, docker_compose_cloudflare):
|
||||
"""Test HAProxy configuration has Cloudflare plugin rules in the correct backend"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -672,7 +672,7 @@ class TestCloudflare:
|
|||
assert "# Cloudflare - Restore original visitor IP" in backend_block
|
||||
|
||||
# Verify ACL for Cloudflare IPs
|
||||
assert "acl from_cloudflare src -f /etc/haproxy/cloudflare_ips.lst" in backend_block
|
||||
assert "acl from_cloudflare src -f /etc/easyhaproxy/cloudflare_ips.lst" in backend_block
|
||||
|
||||
# Verify transaction variable for real IP
|
||||
assert "http-request set-var(txn.real_ip) req.hdr(CF-Connecting-IP) if from_cloudflare" in backend_block
|
||||
|
|
@ -767,7 +767,7 @@ class TestChangedLabel:
|
|||
def test_haproxy_config(self, docker_compose_changed_label):
|
||||
"""Test HAProxy configuration with custom label prefix"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -878,7 +878,7 @@ class TestACME:
|
|||
def test_haproxy_config(self, docker_compose_acme):
|
||||
"""Test HAProxy configuration has ACME challenge routing"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "docker-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -952,8 +952,8 @@ class TestACME:
|
|||
f"HAProxy cannot connect to Pebble ACME server. Check docker network.\nLogs:\n{logs[-2000:]}"
|
||||
|
||||
# Verify merged certificate file exists
|
||||
# EasyHAProxy merges cert+key from /etc/letsencrypt/live/ to /certs/certbot/{domain}.pem
|
||||
merged_cert_path = "/certs/certbot/test.local.pem"
|
||||
# EasyHAProxy merges cert+key from /etc/easyhaproxy/certs/live/ to /etc/easyhaproxy/certs/certbot/{domain}.pem
|
||||
merged_cert_path = "/etc/easyhaproxy/certs/certbot/test.local.pem"
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "docker-haproxy-1", "test", "-f", merged_cert_path],
|
||||
capture_output=True
|
||||
|
|
|
|||
|
|
@ -1145,7 +1145,7 @@ class TestIPWhitelist:
|
|||
# Get HAProxy configuration
|
||||
result = subprocess.run(
|
||||
[kubectl, "exec", "-n", "easyhaproxy", pod_name,
|
||||
"--", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
"--", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
|
|
@ -1292,7 +1292,7 @@ class TestJWTValidatorSecret:
|
|||
# Get HAProxy configuration
|
||||
result = subprocess.run(
|
||||
[kubectl, "exec", "-n", "easyhaproxy", pod_name,
|
||||
"--", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
"--", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
|
|
@ -1327,7 +1327,7 @@ class TestJWTValidatorSecret:
|
|||
"JWT audience validation not found in API backend"
|
||||
|
||||
# Verify JWT keys directory is used in this backend
|
||||
assert "/etc/haproxy/jwt_keys/" in backend_block, \
|
||||
assert "/etc/easyhaproxy/jwt_keys/" in backend_block, \
|
||||
"JWT keys directory not found in API backend"
|
||||
|
||||
def test_access_without_token_denied(self, k8s_jwt_validator_secret):
|
||||
|
|
@ -1589,7 +1589,7 @@ class TestCloudflare:
|
|||
# Get HAProxy configuration
|
||||
result = subprocess.run(
|
||||
[kubectl, "exec", "-n", "easyhaproxy", pod_name,
|
||||
"--", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
"--", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
|
|
@ -1606,7 +1606,7 @@ class TestCloudflare:
|
|||
"Cloudflare plugin comment not found in myapp backend"
|
||||
|
||||
# Verify ACL for Cloudflare IPs is in this backend
|
||||
assert "acl from_cloudflare src -f /etc/haproxy/cloudflare_ips.lst" in backend_block, \
|
||||
assert "acl from_cloudflare src -f /etc/easyhaproxy/cloudflare_ips.lst" in backend_block, \
|
||||
"Cloudflare IP ACL not found in myapp backend"
|
||||
|
||||
# Verify real IP extraction from CF-Connecting-IP header is in this backend
|
||||
|
|
@ -1639,7 +1639,7 @@ class TestCloudflare:
|
|||
# Read the Cloudflare IP list file
|
||||
result = subprocess.run(
|
||||
[kubectl, "exec", "-n", "easyhaproxy", pod_name,
|
||||
"--", "cat", "/etc/haproxy/cloudflare_ips.lst"],
|
||||
"--", "cat", "/etc/easyhaproxy/cloudflare_ips.lst"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class TestStaticBasic:
|
|||
def test_haproxy_config(self, static_basic):
|
||||
"""Test HAProxy configuration has SSL and redirect configurations"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "static-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "static-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -155,7 +155,7 @@ class TestStaticDenyPages:
|
|||
def test_haproxy_config(self, static_deny_pages):
|
||||
"""Test HAProxy configuration has deny pages rules"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "static-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "static-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
@ -209,7 +209,7 @@ class TestStaticJWTValidator:
|
|||
def test_haproxy_config(self, static_jwt_validator):
|
||||
"""Test HAProxy configuration has JWT validation rules"""
|
||||
result = subprocess.run(
|
||||
["docker", "exec", "static-haproxy-1", "cat", "/etc/haproxy/haproxy.cfg"],
|
||||
["docker", "exec", "static-haproxy-1", "cat", "/etc/easyhaproxy/haproxy/haproxy.cfg"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import subprocess
|
|||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
import requests
|
||||
import jwt as jwt_lib
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
|
@ -21,9 +22,12 @@ _docker_image_built = False
|
|||
class DockerComposeFixture:
|
||||
"""Helper class to manage docker-compose lifecycle"""
|
||||
|
||||
def __init__(self, compose_file: str, startup_wait: int = 3, build: bool = None):
|
||||
def __init__(self, compose_file: str, startup_wait: int = 3, build: bool = None,
|
||||
health_check: callable = None, health_check_timeout: int = 60):
|
||||
self.compose_file = compose_file
|
||||
self.startup_wait = startup_wait
|
||||
self.health_check = health_check
|
||||
self.health_check_timeout = health_check_timeout
|
||||
|
||||
# Smart build strategy: build on first call, skip on subsequent calls
|
||||
global _docker_image_built
|
||||
|
|
@ -60,9 +64,31 @@ class DockerComposeFixture:
|
|||
if self.build:
|
||||
_docker_image_built = True
|
||||
|
||||
print(f" ✓ Services started, waiting {self.startup_wait}s for initialization...")
|
||||
time.sleep(self.startup_wait)
|
||||
print(f" ✓ Services ready")
|
||||
# Use health check if provided, otherwise fall back to fixed delay
|
||||
if self.health_check:
|
||||
print(f" ✓ Services started, waiting for health check (timeout: {self.health_check_timeout}s)...")
|
||||
start_time = time.time()
|
||||
poll_interval = 1
|
||||
|
||||
while time.time() - start_time < self.health_check_timeout:
|
||||
try:
|
||||
if self.health_check():
|
||||
elapsed = time.time() - start_time
|
||||
print(f" ✓ Services ready (health check passed in {elapsed:.1f}s)")
|
||||
return
|
||||
except Exception:
|
||||
# Health check not ready yet, continue polling
|
||||
pass
|
||||
|
||||
time.sleep(poll_interval)
|
||||
|
||||
# Health check timed out
|
||||
elapsed = time.time() - start_time
|
||||
raise TimeoutError(f"Health check did not pass within {elapsed:.1f}s")
|
||||
else:
|
||||
print(f" ✓ Services started, waiting {self.startup_wait}s for initialization...")
|
||||
time.sleep(self.startup_wait)
|
||||
print(f" ✓ Services ready")
|
||||
|
||||
def down(self):
|
||||
"""Stop and remove docker-compose services"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue