v0.33.8
This commit is contained in:
1 parent
6b937aaf85
commit
87c8393d86
79 files changed
+6727
-297
No files matched your search
Executable
+184
@@ -0,0 +1,184 @@
|
||||
"""Live ACL, authentication, atomic reload and logging regressions."""
|
||||
import base64
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
|
||||
from common import http, write_json
|
||||
|
||||
|
||||
DENY = {"default": "deny", "rules": []}
|
||||
ALLOW_LOCAL = {"default": "deny", "rules": [{"network": "127.0.0.1/32", "action": "allow"}]}
|
||||
|
||||
|
||||
def run(check, rust, peer):
|
||||
port = rust.reverse_port
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["host_policies"] = [{"hosts": ["fixture.test"], "acl": DENY}]
|
||||
rust.apply(config, "policy-global-host")
|
||||
count = peer.count("GET", "/echo")
|
||||
s, h, b = http(port, "/echo", headers={"Host": "fixture.test"})
|
||||
check("acl.global_host_without_vhost_acl", s == 403 and peer.count("GET", "/echo") == count,
|
||||
status=s, upstream_requests=peer.count("GET", "/echo") - count)
|
||||
s, h, b = http(port, "/echo", headers={"Host": "localhost"})
|
||||
check("acl.global_other_host_unchanged", s == 200, status=s)
|
||||
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["vhosts"][0]["acl"] = DENY
|
||||
rust.apply(config, "policy-vhost-deny")
|
||||
s, h, b = http(port)
|
||||
check("acl.vhost_deny", s == 403, status=s)
|
||||
s, h, b = http(rust.static_port)
|
||||
check("acl.vhost_isolation", s == 200, status=s)
|
||||
config["vhosts"][0]["acl"] = ALLOW_LOCAL
|
||||
rust.apply(config, "policy-vhost-allow")
|
||||
s, h, b = http(port)
|
||||
check("acl.hot_enable_allow", s == 200, status=s)
|
||||
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["acl_profiles"] = [{"id": "restricted", "name": "Restricted", "acl": DENY}]
|
||||
config["vhosts"][0]["acl_profile_id"] = "restricted"
|
||||
config["vhosts"][0]["acl"] = ALLOW_LOCAL
|
||||
rust.apply(config, "policy-profile-deny")
|
||||
s, h, b = http(port)
|
||||
check("acl.profile_and_vhost_stack", s == 403, status=s)
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["vhosts"][0]["routes"] = [{"id": "denied", "path_prefix": "/echo", "acl": DENY}]
|
||||
rust.apply(config, "policy-location-deny")
|
||||
count = peer.count("GET", "/echo")
|
||||
s, h, b = http(port, "/echo")
|
||||
check("acl.location_before_upstream", s == 403 and peer.count("GET", "/echo") == count, status=s)
|
||||
s, h, b = http(port)
|
||||
check("acl.location_other_path_unchanged", s == 200, status=s)
|
||||
config["vhosts"][0]["acl"] = DENY
|
||||
config["vhosts"][0]["routes"][0]["acl"] = ALLOW_LOCAL
|
||||
rust.apply(config, "policy-location-allow-vhost-deny")
|
||||
s, h, b = http(port, "/echo")
|
||||
check("acl.location_cannot_override_vhost_deny", s == 403, status=s)
|
||||
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["vhosts"][0]["acl"] = {"default": "deny", "rules": [{"network": "203.0.113.22/32", "action": "allow"}]}
|
||||
rust.apply(config, "policy-untrusted-forwarded")
|
||||
s, h, b = http(port, headers={"X-Forwarded-For": "203.0.113.22", "X-Real-IP": "203.0.113.22"})
|
||||
check("acl.spoofed_forwarded_ip_denied", s == 403, status=s)
|
||||
config["forwarding"] = {"trusted_proxies": ["127.0.0.1/32"]}
|
||||
rust.apply(config, "policy-trusted-forwarded")
|
||||
s, h, b = http(port, headers={"X-Forwarded-For": "203.0.113.22"})
|
||||
check("acl.trusted_forwarded_ip_allowed", s == 200, status=s)
|
||||
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["vhosts"][0]["rate_limit"] = {"country_access": {"mode": "allow", "countries": ["PL"]}}
|
||||
config["vhosts"][0]["cloudflare_client_ip"] = True
|
||||
rust.apply(config, "policy-country")
|
||||
s, h, b = http(port, headers={"CF-IPCountry": "PL", "CF-Connecting-IP": "203.0.113.22"})
|
||||
check("acl.untrusted_country_header_cannot_allow", s == 403, status=s)
|
||||
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["vhosts"][0]["method_policy"] = {"mode": "allow", "methods": ["GET", "HEAD"]}
|
||||
rust.apply(config, "policy-method")
|
||||
s, h, b = http(port, "/echo", "POST", body=b"denied method")
|
||||
check("policy.method_allowlist", s in (403, 405), status=s)
|
||||
s, h, b = http(port)
|
||||
check("policy.allowed_method_survives", s == 200, status=s)
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["vhosts"][0]["rate_limit"] = {"enabled": True, "requests": 2, "window_seconds": 60, "burst": 0}
|
||||
rust.apply(config, "policy-rate-limit")
|
||||
statuses = [http(port)[0] for _ in range(3)]
|
||||
check("policy.rate_limit", statuses[:2] == [200, 200] and statuses[2] == 429, statuses=statuses)
|
||||
rust.reset()
|
||||
s, h, b = http(port)
|
||||
check("policy.hot_disable_restores_request", s == 200, status=s)
|
||||
|
||||
# Every route check uses the real admin socket and no bearer/backdoor token.
|
||||
for path in ["/api/config", "/api/status", "/api/logs", "/api/events", "/api/openapi.json", "/assets/app.js"]:
|
||||
s, h, b = http(rust.admin_port, path)
|
||||
check("admin.private." + path, s in (401, 403), status=s)
|
||||
check("admin.login", rust.login() == 200)
|
||||
s, h, b = rust.admin("/api/config")
|
||||
envelope = json.loads(b)
|
||||
check("admin.authenticated_config", s == 200 and "config" in envelope and "revision" in envelope, status=s)
|
||||
s, h, b = rust.admin("/api/config", "PUT", {"expected_revision": envelope["revision"], "config": rust.base}, csrf=False)
|
||||
check("admin.csrf_required", s == 403, status=s)
|
||||
s, h, b = rust.admin("/api/config", origin="http://foreign.example")
|
||||
check("admin.foreign_origin_denied", s == 403, status=s)
|
||||
|
||||
# Generate the disposable Basic Auth hash through the existing secured API.
|
||||
s, h, b = rust.admin("/api/basic-auth/hash", "POST", {"username": "fixtureuser", "password": rust.password})
|
||||
hashed = json.loads(b)
|
||||
check("basic.hash_api", s == 200 and "password_hash" in hashed, status=s)
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["vhosts"][0]["basic_auth"] = {"enabled": True, "require_tls": False,
|
||||
"users": [{"username": "fixtureuser", "password_hash": hashed["password_hash"]}]}
|
||||
rust.apply(config, "policy-basic-auth")
|
||||
s, h, b = http(port, "/echo")
|
||||
check("basic.challenge", s == 401 and "Basic" in h.get("www-authenticate", ""), status=s)
|
||||
credentials = base64.b64encode(("fixtureuser:" + rust.password).encode()).decode()
|
||||
s, h, b = http(port, "/echo", headers={"Authorization": "Basic " + credentials})
|
||||
check("basic.correct_password_and_strip", s == 200 and "authorization" not in json.loads(b)["headers"], status=s)
|
||||
s, h, b = http(port, "/echo", headers={"Authorization": "Basic Zml4dHVyZXVzZXI6d3Jvbmc="})
|
||||
check("basic.wrong_password_denied", s == 401, status=s)
|
||||
config["vhosts"][0]["acl"] = DENY
|
||||
rust.apply(config, "policy-acl-before-basic")
|
||||
s, h, b = http(port, "/echo")
|
||||
check("basic.acl_precedes_auth", s == 403, status=s)
|
||||
rust.reset()
|
||||
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["vhosts"][0]["frontend"]["listener_ids"] = ["does-not-exist"]
|
||||
rust.apply(config, "invalid-config-retains-snapshot", expected_success=False)
|
||||
s, h, b = http(port)
|
||||
check("reload.invalid_document_retains_previous_snapshot", s == 200, status=s)
|
||||
s, h, b = http(rust.static_port)
|
||||
check("reload.invalid_document_keeps_other_vhost", s == 200, status=s)
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["vhosts"][0]["backends"][0]["allow_private"] = False
|
||||
# Explicit private-address rejection is checked through apply, preserving the
|
||||
# active configuration if compile-time validation rejects the backend.
|
||||
rust.apply(config, "ssrf-private-origin-disallowed", expected_success=False)
|
||||
s, h, b = http(port)
|
||||
check("ssrf.rejected_origin_does_not_replace_snapshot", s == 200, status=s)
|
||||
|
||||
query = "/echo?observability=" + rust.label
|
||||
s, h, b = http(port, query)
|
||||
request_id = json.loads(b)["headers"]["x-request-id"]
|
||||
log = None
|
||||
for _ in range(20):
|
||||
ls, lh, lb = rust.admin("/api/logs?vhost=reverse&limit=512")
|
||||
logs = json.loads(lb)
|
||||
log = next((entry for entry in logs.get("entries", []) if entry.get("request_id") == request_id), None)
|
||||
if log:
|
||||
break
|
||||
time.sleep(.05)
|
||||
check("observability.log_fields_and_body_bytes", log is not None and log.get("status") == 200
|
||||
and log.get("response_bytes") == len(b) and log.get("request_bytes") == 0
|
||||
and log.get("cache_effective_enabled") is False and log.get("transform_effective_enabled") is False
|
||||
and bool(re.fullmatch("[0-9a-f]{64}", log.get("request_id", ""))),
|
||||
recorded_status=log.get("status") if log else None,
|
||||
recorded_response_bytes=log.get("response_bytes") if log else None, response_bytes=len(b))
|
||||
if log:
|
||||
write_json(rust.fixture.results / f"{rust.label}-sample-exchange.json", log)
|
||||
config = copy.deepcopy(rust.base)
|
||||
config["diagnostic_headers"] = {"request_id": True}
|
||||
rust.apply(config, "observability-static-eos")
|
||||
s, h, b = http(rust.static_port)
|
||||
static_request_id = h.get("x-request-id")
|
||||
static_log = None
|
||||
for _ in range(20):
|
||||
ls, lh, lb = rust.admin("/api/logs?vhost=static&limit=64")
|
||||
static_log = next((entry for entry in json.loads(lb).get("entries", [])
|
||||
if entry.get("request_id") == static_request_id), None)
|
||||
if static_log:
|
||||
break
|
||||
time.sleep(.05)
|
||||
check("observability.static_complete_after_content_length", s == 200 and static_log is not None
|
||||
and static_log.get("outcome") == "complete" and static_log.get("response_bytes") == len(b),
|
||||
outcome=static_log.get("outcome") if static_log else None,
|
||||
recorded_response_bytes=static_log.get("response_bytes") if static_log else None,
|
||||
response_bytes=len(b))
|
||||
if static_log:
|
||||
write_json(rust.fixture.results / f"{rust.label}-sample-static-exchange.json", static_log)
|
||||
s, h, b = rust.admin("/api/status")
|
||||
status = json.loads(b)
|
||||
write_json(rust.fixture.results / f"{rust.label}-runtime-status.json", status)
|
||||
check("observability.status_available", s == 200, status=s)
|
||||
Reference in new issue
Block a user