7f7868f7cf
---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---
181 lines
7.1 KiB
Python
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() |