Remove Python app example and replace with header-echo server for testing
- Deleted the `/examples/docker/python-app` directory and associated files. - Integrated a custom `header-echo` server for Kubernetes and Docker Compose tests. - Updated Kubernetes and Docker Compose configurations to use the new `header-echo-server:test` image. - Enhanced Kubernetes integration tests to validate JSON responses and Cloudflare IP translation behavior.
This commit is contained in:
parent
1fd5873dc9
commit
b34d7822e4
6 changed files with 139 additions and 14 deletions
|
|
@ -66,7 +66,7 @@ services:
|
|||
|
||||
# Web application behind Cloudflare (header-echo server for testing)
|
||||
webapp:
|
||||
build: ./python-app
|
||||
build: ../fixtures/header-echo
|
||||
labels:
|
||||
easyhaproxy.http.host: myapp.local
|
||||
easyhaproxy.http.port: 80
|
||||
|
|
|
|||
66
examples/fixtures/header-echo/README.md
Normal file
66
examples/fixtures/header-echo/README.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Header Echo Server - Test Fixture
|
||||
|
||||
A lightweight Python HTTP server that echoes all request headers as JSON. Used for testing HAProxy plugins that manipulate headers and client IPs.
|
||||
|
||||
## Purpose
|
||||
|
||||
This test fixture is used by both Docker Compose and Kubernetes test suites to verify:
|
||||
- Header manipulation (e.g., X-Forwarded-For, CF-Connecting-IP)
|
||||
- IP restoration plugins (Cloudflare, custom CDN integrations)
|
||||
- Request routing and backend visibility
|
||||
|
||||
## Usage
|
||||
|
||||
### Docker Compose
|
||||
```yaml
|
||||
services:
|
||||
webapp:
|
||||
build: ../fixtures/header-echo
|
||||
ports:
|
||||
- "8080:8080"
|
||||
```
|
||||
|
||||
### Kubernetes
|
||||
```bash
|
||||
# Build and load into kind cluster
|
||||
docker build -t header-echo-server:test .
|
||||
kind load docker-image header-echo-server:test --name your-cluster
|
||||
|
||||
# Use in deployment
|
||||
spec:
|
||||
containers:
|
||||
- name: webapp
|
||||
image: header-echo-server:test
|
||||
imagePullPolicy: Never
|
||||
```
|
||||
|
||||
### Manual Testing
|
||||
```bash
|
||||
# Start the server
|
||||
python3 server.py
|
||||
|
||||
# Test it
|
||||
curl http://localhost:8080
|
||||
# Returns JSON with all headers, client IP, and X-Forwarded-For value
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"headers": {
|
||||
"Host": "localhost:8080",
|
||||
"User-Agent": "curl/7.81.0",
|
||||
"Accept": "*/*"
|
||||
},
|
||||
"client_ip": "127.0.0.1",
|
||||
"x_forwarded_for": "NOT SET"
|
||||
}
|
||||
```
|
||||
|
||||
## Used By
|
||||
|
||||
- `examples/docker/docker-compose-cloudflare.yml`
|
||||
- `examples/docker/test_docker_compose.py::TestCloudflare`
|
||||
- `examples/kubernetes/cloudflare.yml`
|
||||
- `examples/kubernetes/test_kubernetes.py::TestCloudflare`
|
||||
|
|
@ -48,10 +48,11 @@
|
|||
# # 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"
|
||||
# # Expected: 200 OK with JSON response containing headers, client_ip, and x_forwarded_for
|
||||
#
|
||||
# # In production behind Cloudflare, the plugin will restore real client IPs
|
||||
# # from the CF-Connecting-IP header
|
||||
# # Test IP translation with CF-Connecting-IP header
|
||||
# curl -H "Host: myapp.example.local" -H "CF-Connecting-IP: 1.2.3.4" http://localhost:8080
|
||||
# # Expected: x_forwarded_for should be "1.2.3.4"
|
||||
# ```
|
||||
#
|
||||
# CLEAN UP:
|
||||
|
|
@ -93,12 +94,10 @@ spec:
|
|||
spec:
|
||||
containers:
|
||||
- name: webapp
|
||||
image: byjg/static-httpserver
|
||||
image: header-echo-server:test
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: TITLE
|
||||
value: "App Behind Cloudflare"
|
||||
resources:
|
||||
limits:
|
||||
cpu: '0.1'
|
||||
|
|
|
|||
|
|
@ -840,9 +840,28 @@ def k8s_jwt_validator_secret(kind_cluster) -> Generator[dict, None, None]:
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def k8s_cloudflare(kind_cluster) -> Generator[str, None, None]:
|
||||
def k8s_cloudflare(kind_cluster, kind_cmd) -> Generator[str, None, None]:
|
||||
"""Fixture for cloudflare.yml with base64-encoded IP list"""
|
||||
kubectl_cmd = kind_cluster["kubectl"]
|
||||
cluster_name = kind_cluster["name"]
|
||||
|
||||
# Build header-echo server image locally
|
||||
header_echo_dir = BASE_DIR.parent / "fixtures" / "header-echo"
|
||||
print(" → Building header-echo-server:test image...")
|
||||
subprocess.run(
|
||||
["docker", "build", "-t", "header-echo-server:test", str(header_echo_dir)],
|
||||
check=True,
|
||||
capture_output=True
|
||||
)
|
||||
|
||||
# Load image into kind cluster
|
||||
print(" → Loading header-echo-server:test into kind cluster...")
|
||||
subprocess.run(
|
||||
[kind_cmd, "load", "docker-image", "header-echo-server:test",
|
||||
"--name", cluster_name],
|
||||
check=True,
|
||||
capture_output=True
|
||||
)
|
||||
|
||||
# Create a modified cloudflare manifest with base64-encoded test IPs
|
||||
# Include 127.0.0.1 and Docker/kind network ranges so test requests work
|
||||
|
|
@ -1881,7 +1900,7 @@ class TestCloudflare:
|
|||
"Built-in Cloudflare IP found (ip_list should take precedence)"
|
||||
|
||||
def test_access_to_webapp(self, k8s_cloudflare):
|
||||
"""Test that the webapp is accessible via the Cloudflare ingress"""
|
||||
"""Test that the webapp is accessible and returns JSON"""
|
||||
kubectl = k8s_cloudflare
|
||||
|
||||
# Wait for EasyHAProxy to discover and configure the ingress
|
||||
|
|
@ -1898,8 +1917,49 @@ class TestCloudflare:
|
|||
)
|
||||
|
||||
assert result.returncode == 0, f"Curl failed with return code {result.returncode}"
|
||||
assert "App Behind Cloudflare" in result.stdout, \
|
||||
f"Expected 'App Behind Cloudflare' in response, got: {result.stdout}"
|
||||
|
||||
# Parse JSON response
|
||||
data = json.loads(result.stdout)
|
||||
|
||||
# Verify JSON structure
|
||||
assert "headers" in data, "Response should contain 'headers' field"
|
||||
assert "client_ip" in data, "Response should contain 'client_ip' field"
|
||||
assert "x_forwarded_for" in data, "Response should contain 'x_forwarded_for' field"
|
||||
|
||||
def test_cloudflare_ip_translation_works(self, k8s_cloudflare):
|
||||
"""Test that Cloudflare plugin actually translates CF-Connecting-IP to X-Forwarded-For"""
|
||||
kubectl = k8s_cloudflare
|
||||
|
||||
# Wait for EasyHAProxy to be ready
|
||||
assert wait_for_easyhaproxy_discovery(kubectl, "myapp.example.local", timeout=30), \
|
||||
"EasyHAProxy did not become ready within 30 seconds"
|
||||
|
||||
# Send request with CF-Connecting-IP header
|
||||
test_ip = "203.0.113.50"
|
||||
result = subprocess.run(
|
||||
["curl", "-s",
|
||||
"-H", "Host: myapp.example.local",
|
||||
"-H", f"CF-Connecting-IP: {test_ip}",
|
||||
f"http://localhost:{HTTP_PORT}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
assert result.returncode == 0, f"Curl failed"
|
||||
|
||||
# Parse JSON response from header-echo server
|
||||
data = json.loads(result.stdout)
|
||||
|
||||
# VERIFY: X-Forwarded-For was set to the CF-Connecting-IP value
|
||||
# This proves the Cloudflare plugin actually works, not just that config exists
|
||||
assert data['x_forwarded_for'] == test_ip, \
|
||||
f"Expected X-Forwarded-For to be '{test_ip}' (from CF-Connecting-IP), " \
|
||||
f"got '{data['x_forwarded_for']}'. Cloudflare IP translation NOT working!"
|
||||
|
||||
# Verify client_ip is still the HAProxy/ingress IP (connection doesn't change)
|
||||
assert data['client_ip'] != test_ip, \
|
||||
f"client_ip should be HAProxy pod IP, not the translated IP"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue