chore(repo): move current KarlDNS source to Gitea

Preserve the GitHub history and the newer deployed source from ct_134_karldns. Exclude runtime data, credentials, AppleDouble files and local backup copies. Make Gitea the canonical source for documentation, package metadata and project links.
This commit is contained in:
StasonJatham committed 2026-10-28 08:47:52 +01:00
1 parent 4ecc8eb874
commit 5c46b47937
21 files changed
+1301 -593

No files matched your search

+26
View File
@@ -12,6 +12,32 @@ def _path(url: str) -> str:
return parsed.path
def test_llms_txt_documents_public_api_without_creating_accounts(tmp_path: Path) -> None:
app = make_app(
DdnsSettings(
database_path=tmp_path / "ddns.sqlite3",
admin_password="private-admin-password",
public_base_url="https://dns.example.net",
hostname_suffix="dns.example.net",
)
)
with TestClient(app) as client:
response = client.get("/llms.txt")
assert response.status_code == 200
assert response.headers["content-type"].startswith("text/plain")
assert response.text.startswith("# KarlDNS\n\n> ")
assert "https://dns.example.net/openapi.json" in response.text
assert "private-admin-password" not in response.text
assert "/api/v1/admin" not in response.text
assert "## Optional\n" in response.text
assert 'rel="describedby" href="/llms.txt"' in client.get("/").text
for path, operations in client.get("/openapi.json").json()["paths"].items():
assert path in response.text
for method in operations:
assert method.upper() in response.text
assert app.state.store.count_accounts() == 0
def test_custom_api_docs_include_only_current_api_surfaces(tmp_path: Path) -> None:
app = make_app(
DdnsSettings(
+27
View File
@@ -0,0 +1,27 @@
import json
import logging
from fastapi.testclient import TestClient
from karldns import DdnsSettings, make_app
def test_creation_audit_preserves_public_provisioning_and_redacts(tmp_path, caplog):
app = make_app(DdnsSettings(database_path=tmp_path / 'audit.sqlite3', admin_password='admin', hostname_suffix='example.net'))
with TestClient(app) as client, caplog.at_level(logging.WARNING, logger='karldns.security'):
for path, body, code in [('/', '', 303), ('/', '<?php eval("super-secret") ?>', 303), ('/api/v1/hostnames', 'super-secret', 201)]:
caplog.clear()
response = client.post(path, content=body, headers={'X-Request-ID': 'a' * 32}, follow_redirects=False)
assert response.status_code == code
event = json.loads(next(r.message.removeprefix('KARLDNSSEC ') for r in caplog.records if r.message.startswith('KARLDNSSEC ')))
assert event['http']['request']['id'] == response.headers['x-request-id'] == 'a' * 32
assert event['labels']['account_created'] is True
assert event['labels']['dns_activation'] is False
assert event['labels']['php_code_marker'] == ('<?php' in body)
assert event['labels']['unexpected_body'] == bool(body)
assert 'super-secret' not in json.dumps(event)
assert response.headers.get('location', 'no-private-url') not in caplog.text
caplog.clear()
response = client.post('/', content='x', headers={'X-Request-ID': 'private-invalid-id'}, follow_redirects=False)
assert len(response.headers['x-request-id']) == 32
assert 'private-invalid-id' not in caplog.text
assert app.state.store.count_accounts() == 4
assert app.state.store.list_records() == []
+48
View File
@@ -0,0 +1,48 @@
import json
import logging
from fastapi.testclient import TestClient
from karldns import DdnsSettings, make_app
from karldns.web.request_capture import private_clean,request_sample
def test_post_capture(tmp_path,caplog):
secret='runtime-canary-6729'
app=make_app(DdnsSettings(database_path=tmp_path/'post.sqlite3',admin_password=secret,hostname_suffix='example.net'))
def event():
events=[json.loads(r.message.removeprefix('KARLDNSSEC ')) for r in caplog.records if r.message.startswith('KARLDNSSEC ')]
return next(e for e in events if e['event']['action']=='karldns.post_request')
@app.post('/capture-exception')
async def failed():raise RuntimeError('synthetic failure')
with TestClient(app,raise_server_exceptions=False) as client,caplog.at_level(logging.WARNING,logger='karldns.security'):
for path,body,ctype,code in [('/',b'', 'text/plain',303),('/api/v1/hostnames',b'', 'text/plain',201),('/missing',b'<?php eval($_POST[x]); ?> UNION SELECT $(id) %252e%252e%252f', 'text/plain',404),('/',b'\xff', 'text/plain',400),('/missing',b'\x00binary','application/octet-stream',404)]:
caplog.clear();response=client.post(path,content=body,headers={'Content-Type':ctype,'X-Canary':secret,'Authorization':'Bearer auth-canary','Cookie':'session=cookie-canary','X-Request-ID':'f'*32},follow_redirects=False)
assert response.status_code==code
e=event();sample=e['karldns']['request_sample']
assert e['http']['request']['id']==response.headers['x-request-id']=='f'*32
for value in [secret,'auth-canary','cookie-canary']:assert value not in caplog.text
assert sample['payload']['original_bytes']==len(body)
if path=='/missing' and ctype=='text/plain':assert sample['payload']['text']==body.decode()
if body==b'\xff':assert sample['payload']['status']=='invalid_utf8_omitted'
caplog.clear();response=client.post('/capture-exception',content='probe=exception');assert response.status_code==500;assert event()['http']['response']['status_code']==500
assert app.state.store.count_accounts()==2
assert app.state.store.list_records()==[]
for headers,body,status in [({'Content-Length':'20000'},b'', 'oversized_omitted'),({'Content-Encoding':'gzip'},b'private', 'encoded_omitted'),({'Content-Type':'multipart/form-data;boundary=z'},b'password=private','multipart_omitted')]:
caplog.clear();r=client.post('/missing',headers=headers,content=body);s=event()['karldns']['request_sample'];assert s['payload']['status']==status;assert not s['payload']['text']
caplog.clear();r=client.post('/admin',content='password=short-canary',headers={'Content-Type':'application/x-www-form-urlencoded'});assert 'short-canary' not in caplog.text
def test_privacy_and_syntax():
for text in ['{"credential":"short-canary"','session=short-canary','private_key=short-canary','%2570assword=short-canary','password=short-canary','https://host.invalid/m/short-canary']:
assert 'short-canary' not in private_clean(text)
attack='<?php system($_GET[x]); ?>; UNION SELECT 1; $(id); %252e%252e%252f'
assert private_clean(attack)==attack
source={'home':{'honeypot':{'request':{'headers':[],'headers_complete':True,'uri':'/'}}},'http':{'request':{'method':'POST'}}}
for body in ['{"%2570assword":"short-canary"}', '{"private_key":"short-canary"}']:
assert 'short-canary' not in json.dumps(request_sample(source,body))
def test_structural_capture_truncation():
source={'home':{'honeypot':{'request':{'headers':[],'headers_complete':True,'uri':'/'}}},'http':{'request':{'method':'POST'}}}
for value in [list(range(81)),{str(i):i for i in range(81)}]:
assert request_sample(source,json.dumps(value))['payload']['truncated']
assert '%252e%252e%252f' in private_clean('password=short-canary&attack=%252e%252e%252f')
+13 -4
View File
@@ -126,15 +126,19 @@ esac
"user",
"--no-cron",
"--no-run",
"--base-url",
"https://karldns.test",
env={
"HOME": str(home_dir),
"PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}",
"KARLDNS_BASE_URL": "https://karldns.test",
"KARLDNS_RAW_BASE_URL": "https://raw.karldns.test",
},
)
log = log_file.read_text(encoding="utf-8")
assert log.count("Content-Type: application/x-www-form-urlencoded") == 2
assert "https://karldns.test/karldns-update.sh" in log
assert "/api/v1/hostnames" in log
assert "/api/v1/router/register/karl_manage_secret_12345678901234567890" in log
assert "KarlDNS: registering hostname" in result.stdout
@@ -238,15 +242,20 @@ cp "$1" {crontab_file}
assert not (home_dir / ".cache" / "karldns").exists()
def test_hosted_script_routes_redirect_to_github(tmp_path: Path) -> None:
def test_hosted_script_routes_serve_bundled_scripts(tmp_path: Path) -> None:
app = make_app(DdnsSettings(database_path=tmp_path / "ddns.sqlite3", admin_password="admin"))
client = TestClient(app)
assert client.get("/docs").text.count("Content-Type: application/x-www-form-urlencoded") >= 3
for path, target in {
"/install.sh": "install-linux.sh",
"/uninstall.sh": "uninstall-linux.sh",
"/karldns-update.sh": "karldns-update.sh",
}.items():
response = client.get(path, follow_redirects=False)
assert response.status_code == 302
assert response.headers["location"].endswith(f"/scripts/{target}")
assert response.status_code == 200
assert response.content == (ROOT / "scripts" / target).read_bytes()
assert response.headers["content-type"].startswith("text/plain")
assert response.headers["cache-control"] == "no-cache"
assert "location" not in response.headers
+1
View File
@@ -7,6 +7,7 @@ def test_reserved_admin_path_matches_exact_and_nested_public_routes() -> None:
assert _is_reserved_admin_path("/api")
assert _is_reserved_admin_path("/api/v1")
assert _is_reserved_admin_path("/logo.png")
assert _is_reserved_admin_path("/llms.txt")
assert not _is_reserved_admin_path("/ops")
assert not _is_reserved_admin_path("/apix")
+1 -1
View File
@@ -737,7 +737,7 @@ def test_footer_links_and_privacy_page_are_available(tmp_path: Path) -> None:
assert response.text.count('class="lucide-icon footer-icon"') == 5
assert 'class="lucide-icon theme-icon theme-icon-moon"' in response.text
assert 'class="lucide-icon copy-icon"' in response.text
assert 'href="https://github.com/StasonJatham/karldns.de"' in response.text
assert 'href="https://tea.karlcom.de/karl/karldns.de"' in response.text
assert 'href="https://karl.fail"' in response.text
assert 'href="https://karlcom.de"' in response.text
assert 'href="/privacy"' in response.text