Archived
Prepare KarlDNS production deployment
This commit is contained in:
1 parent
5a90bd9d87
commit
ce5b4e0ff2
101 files changed
+3364
-2018
No files matched your search
+31
-45
@@ -6,10 +6,10 @@ from pathlib import Path
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
import router_dyndns.ddns_service as ddns_service
|
||||
from router_dyndns.ddns import DdnsSettings, DdnsStore, DnsProviderError, DnsReconciliationError, UpdateResult, make_app
|
||||
from router_dyndns.ddns_dns import validate_dns_provider_settings
|
||||
from router_dyndns.ddns_security import DASHBOARD_UNLOCK_COOKIE
|
||||
import karldns.services.ddns as ddns_module
|
||||
from karldns import DdnsSettings, DdnsStore, DnsProviderError, DnsReconciliationError, UpdateResult, make_app
|
||||
from karldns.dns import validate_dns_provider_settings
|
||||
from karldns.security import DASHBOARD_UNLOCK_COOKIE
|
||||
|
||||
|
||||
def test_generated_hostname_is_not_an_update_secret(tmp_path: Path) -> None:
|
||||
@@ -20,14 +20,11 @@ def test_generated_hostname_is_not_an_update_secret(tmp_path: Path) -> None:
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get(
|
||||
"/nic/update",
|
||||
params={
|
||||
"hostname": "public-target.ddns.example.net",
|
||||
"myip": "203.0.113.9",
|
||||
},
|
||||
f"/api/v1/router/update/{'a' * 43}",
|
||||
params={"hostname": "public-target.ddns.example.net", "myip": "203.0.113.9"},
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
assert response.status_code == 404
|
||||
assert store.list_records() == []
|
||||
|
||||
|
||||
@@ -39,16 +36,13 @@ def test_generated_account_cannot_update_other_hostname(tmp_path: Path) -> None:
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get(
|
||||
"/nic/update",
|
||||
params={
|
||||
"hostname": "other.example.net",
|
||||
"myip": "203.0.113.9",
|
||||
"username": account["username"],
|
||||
"password": account["password"],
|
||||
},
|
||||
f"/api/v1/router/update/{account['update_slug']}",
|
||||
params={"hostname": "other.example.net", "myip": "203.0.113.9"},
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
assert response.status_code == 200
|
||||
assert store.list_records()[0]["hostname"] == "home.example.net"
|
||||
assert store.get_account_by_hostname("other.example.net") is None
|
||||
|
||||
|
||||
def test_bearer_link_routes_share_rate_limit_bucket(tmp_path: Path) -> None:
|
||||
@@ -70,7 +64,7 @@ def test_bearer_link_routes_share_rate_limit_bucket(tmp_path: Path) -> None:
|
||||
|
||||
|
||||
def test_redacted_access_log_masks_bearer_paths_and_query_strings(tmp_path: Path, caplog) -> None:
|
||||
caplog.set_level(logging.INFO, logger="router_dyndns.access")
|
||||
caplog.set_level(logging.INFO, logger="karldns.access")
|
||||
app = make_app(
|
||||
DdnsSettings(
|
||||
database_path=tmp_path / "ddns.sqlite3",
|
||||
@@ -81,30 +75,22 @@ def test_redacted_access_log_masks_bearer_paths_and_query_strings(tmp_path: Path
|
||||
account = app.state.store.create_account("home.example.net", "router")
|
||||
client = TestClient(app)
|
||||
|
||||
client.get(f"/u/{account['update_slug']}", params={"myip": "203.0.113.1"})
|
||||
client.get(
|
||||
"/nic/update",
|
||||
params={
|
||||
"hostname": account["hostname"],
|
||||
"username": account["username"],
|
||||
"password": account["password"],
|
||||
"myip": "203.0.113.2",
|
||||
},
|
||||
)
|
||||
client.get(f"/api/v1/router/update/{account['update_slug']}", params={"myip": "203.0.113.1"})
|
||||
client.get(f"/api/v1/hostnames/{account['management_slug']}")
|
||||
|
||||
assert "/u/<secret>" in caplog.text
|
||||
assert "/nic/update?<redacted>" in caplog.text
|
||||
assert "/api/v1/router/update/<secret>" in caplog.text
|
||||
assert "/api/v1/hostnames/<secret>" in caplog.text
|
||||
assert account["update_slug"] not in caplog.text
|
||||
assert account["password"] not in caplog.text
|
||||
assert account["management_slug"] not in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
"/u/short",
|
||||
"/api/v1/updates/short",
|
||||
"/api/v1/router/update/short",
|
||||
"/api/v1/router/register/short",
|
||||
"/api/v1/hostnames/short",
|
||||
"/m/short",
|
||||
"/api/v1/management/short",
|
||||
],
|
||||
)
|
||||
def test_secret_path_tokens_are_constrained(tmp_path: Path, path: str) -> None:
|
||||
@@ -174,7 +160,7 @@ def test_secret_html_responses_are_not_cached(tmp_path: Path) -> None:
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
created = client.post("/magic", data={"username": "router"})
|
||||
created = client.post("/")
|
||||
|
||||
assert created.status_code == 200
|
||||
assert created.headers["cache-control"] == "no-store"
|
||||
@@ -192,7 +178,7 @@ def test_oversized_request_body_is_rejected(tmp_path: Path) -> None:
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post("/magic", data={"username": "x" * 2000})
|
||||
response = client.post("/", data={"username": "x" * 2000})
|
||||
|
||||
assert response.status_code == 413
|
||||
|
||||
@@ -208,7 +194,7 @@ def test_invalid_utf8_form_body_is_rejected(tmp_path: Path) -> None:
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post(
|
||||
"/magic",
|
||||
"/",
|
||||
content=b"username=\xff",
|
||||
headers={"content-type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
@@ -229,7 +215,7 @@ def test_too_many_form_fields_are_rejected(tmp_path: Path) -> None:
|
||||
body = "&".join(f"k{index}=v" for index in range(101))
|
||||
|
||||
response = client.post(
|
||||
"/magic",
|
||||
"/",
|
||||
content=body.encode("ascii"),
|
||||
headers={"content-type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
@@ -249,8 +235,8 @@ def test_untrusted_forwarded_for_does_not_bypass_rate_limit(tmp_path: Path) -> N
|
||||
)
|
||||
client = TestClient(app)
|
||||
|
||||
first = client.post("/magic", data={"username": "alice"}, headers={"x-forwarded-for": "198.51.100.10"})
|
||||
second = client.post("/magic", data={"username": "bob"}, headers={"x-forwarded-for": "198.51.100.11"})
|
||||
first = client.post("/", headers={"x-forwarded-for": "198.51.100.10"})
|
||||
second = client.post("/", headers={"x-forwarded-for": "198.51.100.11"})
|
||||
|
||||
assert first.status_code == 200
|
||||
assert second.status_code == 429
|
||||
@@ -260,8 +246,8 @@ def test_dns_delete_failure_is_logged_without_secret_management_slug(tmp_path: P
|
||||
def fail_delete(hostname, settings):
|
||||
raise DnsProviderError("dns down")
|
||||
|
||||
caplog.set_level(logging.WARNING, logger="router_dyndns.ddns_service")
|
||||
monkeypatch.setattr(ddns_service, "delete_dns_records", fail_delete)
|
||||
caplog.set_level(logging.WARNING, logger="karldns.services.ddns")
|
||||
monkeypatch.setattr(ddns_module, "delete_dns_records", fail_delete)
|
||||
database_path = tmp_path / "ddns.sqlite3"
|
||||
store = DdnsStore(database_path)
|
||||
account = store.create_account("home.example.net", "alice")
|
||||
@@ -363,7 +349,7 @@ def test_required_rfc2136_provider_requires_server_and_zone(tmp_path: Path) -> N
|
||||
|
||||
|
||||
def test_cloudflare_publish_reconciles_provider_state(monkeypatch) -> None:
|
||||
import router_dyndns.dns.cloudflare as cloudflare
|
||||
import karldns.dns.cloudflare as cloudflare
|
||||
|
||||
calls = {"find": 0}
|
||||
|
||||
@@ -394,7 +380,7 @@ def test_rfc2136_delete_checks_response_code(monkeypatch) -> None:
|
||||
import dns.query
|
||||
import dns.rcode
|
||||
|
||||
from router_dyndns.dns.rfc2136 import delete_rfc2136_records
|
||||
from karldns.dns.rfc2136 import delete_rfc2136_records
|
||||
|
||||
class RefusedResponse:
|
||||
def rcode(self) -> int:
|
||||
|
||||
Reference in new issue
Block a user