Archived
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:
1 parent
4ecc8eb874
commit
5c46b47937
21 files changed
+1301
-593
No files matched your search
@@ -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(
|
||||
|
||||
@@ -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() == []
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user