Files
broker/tests/test_static_disclosure.py
T
coreci 7f7868f7cf docs(milestone): complete coreci-broker
---ci---
phase: 5
milestone: v0.1
status: complete
requirements:
  covered: [REQ-01, REQ-02, REQ-03, REQ-04, REQ-05, REQ-06, REQ-07, REQ-08, REQ-09, REQ-10, REQ-11, REQ-12, REQ-13, REQ-14, REQ-15, REQ-16, REQ-17, REQ-18, REQ-19, REQ-20]
  partial: []
---/ci---
2026-10-10 15:30:14 +00:00

181 lines
7.1 KiB
Python

import contextlib
import io
import json
import os
import sys
import tempfile
import unittest
import urllib.error
import urllib.request
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)))
import coreci_broker
class StaticDisclosureGuardTests(unittest.TestCase):
def setUp(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.tmp = tmp.name
self.env = {
"BROKER_ENV_PATH": os.path.join(self.tmp, ".env"),
"BROKER_DB_PATH": os.path.join(self.tmp, "broker.db"),
"BROKER_AUDIT_LOG_PATH": os.path.join(self.tmp, "broker.audit.log"),
"BROKER_UI_ROOT": self.tmp,
"BROKER_STATIC_ROOT": self.tmp,
}
with contextlib.redirect_stdout(io.StringIO()):
self.broker = coreci_broker.boot(dict(self.env))
self.addCleanup(self.broker.audit.close)
self.addCleanup(self.broker.store.close)
with open(
os.path.join(self.tmp, "coreci_broker_client.py"), "w", encoding="utf-8"
) as handle:
handle.write("import urllib.request\n")
with open(
os.path.join(self.tmp, ".hidden.css"), "w", encoding="utf-8"
) as handle:
handle.write("body{display:none}")
os.symlink(self.env["BROKER_ENV_PATH"], os.path.join(self.tmp, "env_alias"))
self.server = coreci_broker.serve("127.0.0.1", 0, self.broker)
self.addCleanup(self.server.server_close)
self.addCleanup(self.server.shutdown)
self.base = "http://127.0.0.1:%d" % self.server.server_address[1]
def _get(self, path):
try:
with urllib.request.urlopen(self.base + path, timeout=5) as response:
return response.status, response.read()
except urllib.error.HTTPError as error:
return error.code, error.read()
def test_state_files_never_served_when_roots_overlap_state_dir(self):
for path in (
"/static/.env",
"/static/broker.db",
"/static/broker.db-journal",
"/static/broker.db-wal",
"/static/broker.audit.log",
"/ui/.env",
"/ui/broker.db",
"/ui/broker.audit.log",
):
with self.subTest(path=path):
status, body = self._get(path)
self.assertEqual(404, status)
self.assertEqual(
{"error": 404, "message": "not found"}, json.loads(body.decode("utf-8"))
)
def test_symlink_alias_to_env_rejected(self):
status, _ = self._get("/static/env_alias")
self.assertEqual(404, status)
def test_dotfiles_rejected_even_with_known_extension(self):
for path in ("/static/.hidden.css", "/ui/.hidden.css"):
with self.subTest(path=path):
status, _ = self._get(path)
self.assertEqual(404, status)
def test_public_files_still_served(self):
status, body = self._get("/static/coreci_broker_client.py")
self.assertEqual(200, status)
self.assertEqual(b"import urllib.request\n", body)
def test_default_config_relative_paths_do_not_disclose_state(self):
cwd = os.getcwd()
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
os.chdir(tmp.name)
try:
env = {"BROKER_ENV_PATH": os.path.join(tmp.name, ".env")}
with contextlib.redirect_stdout(io.StringIO()):
broker = coreci_broker.boot(dict(env))
server = coreci_broker.serve("127.0.0.1", 0, broker)
base = "http://127.0.0.1:%d" % server.server_address[1]
for path in ("/static/.env", "/static/broker.db", "/static/broker.audit.log"):
with self.subTest(path=path):
try:
with urllib.request.urlopen(base + path, timeout=5) as response:
status = response.status
except urllib.error.HTTPError as error:
status = error.code
self.assertEqual(404, status)
server.shutdown()
server.server_close()
broker.audit.close()
broker.store.close()
finally:
os.chdir(cwd)
class CredsFileDisclosureGuardTests(unittest.TestCase):
SECRET_JSON = json.dumps({"coreci/proxmox": {"token": "pve-tok"}})
def _get(self, base, path):
try:
with urllib.request.urlopen(base + path, timeout=5) as response:
return response.status, response.read()
except urllib.error.HTTPError as error:
return error.code, error.read()
def _write_creds(self, path):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as handle:
handle.write(self.SECRET_JSON)
def test_default_layout_never_serves_creds_file(self):
cwd = os.getcwd()
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
creds_path = os.path.join(tmp.name, "data", "creds.json")
self._write_creds(creds_path)
os.chdir(tmp.name)
try:
with contextlib.redirect_stdout(io.StringIO()):
broker = coreci_broker.boot({"BROKER_ENV_PATH": os.path.join(tmp.name, ".env")})
server = coreci_broker.serve("127.0.0.1", 0, broker)
base = "http://127.0.0.1:%d" % server.server_address[1]
for path in ("/static/data/creds.json", "/static/data%2Fcreds.json"):
with self.subTest(path=path):
status, body = self._get(base, path)
self.assertEqual(404, status)
self.assertNotIn(b"pve-tok", body)
server.shutdown()
server.server_close()
broker.audit.close()
broker.store.close()
finally:
os.chdir(cwd)
def test_creds_file_blocked_when_roots_overlap_creds_dir(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
creds_path = os.path.join(tmp.name, "creds.json")
self._write_creds(creds_path)
env = {
"BROKER_ENV_PATH": os.path.join(tmp.name, ".env"),
"BROKER_DB_PATH": os.path.join(tmp.name, "broker.db"),
"BROKER_AUDIT_LOG_PATH": os.path.join(tmp.name, "broker.audit.log"),
"BROKER_UI_ROOT": tmp.name,
"BROKER_STATIC_ROOT": tmp.name,
"BROKER_CREDS_FILE": creds_path,
}
with contextlib.redirect_stdout(io.StringIO()):
broker = coreci_broker.boot(dict(env))
self.addCleanup(broker.audit.close)
self.addCleanup(broker.store.close)
server = coreci_broker.serve("127.0.0.1", 0, broker)
self.addCleanup(server.server_close)
self.addCleanup(server.shutdown)
base = "http://127.0.0.1:%d" % server.server_address[1]
for path in ("/static/creds.json", "/ui/creds.json"):
with self.subTest(path=path):
status, body = self._get(base, path)
self.assertEqual(404, status)
self.assertNotIn(b"pve-tok", body)
if __name__ == "__main__":
unittest.main()