Migrate from homelab.fhirworx.io (LAN-only, IP allowlist) to fhirworx.io (public, Gitea SSO via oauth2-proxy). - Domain: homelab.fhirworx.io → fhirworx.io across all configs - SSO: oauth2-proxy (OIDC/Gitea) + auth-handler nginx for Traefik ForwardAuth (converts 401 → 302 redirect, same as corwins.media auth_request pattern) - Cloudflared: tunnel remote config with 20 hostnames → traefik, DNS CNAME records via CF API - Bootstrap: `docker compose run --rm wire` — idempotent cold-start that creates Gitea admin, OAuth2 app, oauth2-proxy credentials, clears Cloudflare Access apps, syncs tunnel config + DNS - Dashboard: rebranded FHIRWORX, HTTPS links, API tile added - Grafana/Woodpecker/Gitea ROOT_URLs updated to HTTPS
228 lines
7.8 KiB
Python
228 lines
7.8 KiB
Python
"""Tests for dev/scripts/bootstrap_certs.py — TLS cert bootstrap."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from dev.scripts.bootstrap_certs import (
|
|
HOSTS_SUBDOMAINS,
|
|
distribute_ca,
|
|
generate_ca,
|
|
generate_coredns_hosts,
|
|
generate_wildcard,
|
|
verify_ca,
|
|
verify_wildcard,
|
|
)
|
|
|
|
|
|
@pytest.fixture()
|
|
def cert_dir(tmp_path: Path) -> Path:
|
|
"""Provide a temporary cert directory."""
|
|
d = tmp_path / "certs"
|
|
d.mkdir()
|
|
return d
|
|
|
|
|
|
@pytest.fixture()
|
|
def _patch_dirs(tmp_path: Path, cert_dir: Path):
|
|
"""Patch all module-level paths to use tmp_path."""
|
|
coredns = tmp_path / "coredns"
|
|
coredns.mkdir()
|
|
docker_certs = cert_dir / "docker-certs.d"
|
|
docker_certs.mkdir()
|
|
user_certs = tmp_path / "user-docker"
|
|
with (
|
|
patch("dev.scripts.bootstrap_certs.CERT_DIR", cert_dir),
|
|
patch("dev.scripts.bootstrap_certs.CA_KEY", cert_dir / "ca.key"),
|
|
patch("dev.scripts.bootstrap_certs.CA_CRT", cert_dir / "ca.crt"),
|
|
patch("dev.scripts.bootstrap_certs.TLS_KEY", cert_dir / "homelab.key"),
|
|
patch("dev.scripts.bootstrap_certs.TLS_CRT", cert_dir / "homelab.crt"),
|
|
patch("dev.scripts.bootstrap_certs.COREDNS_DIR", coredns),
|
|
patch("dev.scripts.bootstrap_certs.DOCKER_CERTS_DIR", docker_certs),
|
|
patch("dev.scripts.bootstrap_certs.Path.home", return_value=user_certs),
|
|
):
|
|
yield
|
|
|
|
|
|
class TestGenerateCA:
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_creates_key_and_cert(self, cert_dir: Path) -> None:
|
|
key, crt = generate_ca(force=True)
|
|
assert key.exists()
|
|
assert crt.exists()
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_key_is_private(self, cert_dir: Path) -> None:
|
|
key, _ = generate_ca(force=True)
|
|
assert oct(key.stat().st_mode)[-3:] == "600"
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_cert_has_correct_subject(self, cert_dir: Path) -> None:
|
|
_, crt = generate_ca(force=True)
|
|
result = subprocess.run(
|
|
["openssl", "x509", "-in", str(crt), "-noout", "-subject"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
assert "Homelab CA" in result.stdout
|
|
assert "fhirworx" in result.stdout
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_skips_if_exists(self, cert_dir: Path) -> None:
|
|
generate_ca(force=True)
|
|
mtime_key = (cert_dir / "ca.key").stat().st_mtime
|
|
generate_ca(force=False)
|
|
assert (cert_dir / "ca.key").stat().st_mtime == mtime_key
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_force_overwrites(self, cert_dir: Path) -> None:
|
|
generate_ca(force=True)
|
|
content_before = (cert_dir / "ca.crt").read_bytes()
|
|
generate_ca(force=True)
|
|
content_after = (cert_dir / "ca.crt").read_bytes()
|
|
assert content_before != content_after
|
|
|
|
|
|
class TestGenerateWildcard:
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_creates_key_and_cert(self, cert_dir: Path) -> None:
|
|
generate_ca(force=True)
|
|
key, crt = generate_wildcard(force=True)
|
|
assert key.exists()
|
|
assert crt.exists()
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_signed_by_ca(self, cert_dir: Path) -> None:
|
|
generate_ca(force=True)
|
|
_, crt = generate_wildcard(force=True)
|
|
ca_crt = cert_dir / "ca.crt"
|
|
result = subprocess.run(
|
|
["openssl", "verify", "-CAfile", str(ca_crt), str(crt)],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
assert "OK" in result.stdout
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_has_wildcard_san(self, cert_dir: Path) -> None:
|
|
generate_ca(force=True)
|
|
_, crt = generate_wildcard(domain="test.example.com", force=True)
|
|
result = subprocess.run(
|
|
["openssl", "x509", "-in", str(crt), "-noout", "-ext", "subjectAltName"],
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
assert "*.test.example.com" in result.stdout
|
|
assert "test.example.com" in result.stdout
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_raises_without_ca(self, cert_dir: Path) -> None:
|
|
with pytest.raises(FileNotFoundError, match="CA cert/key not found"):
|
|
generate_wildcard(force=True)
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_no_csr_leftover(self, cert_dir: Path) -> None:
|
|
generate_ca(force=True)
|
|
generate_wildcard(force=True)
|
|
csrs = list(cert_dir.glob("*.csr"))
|
|
srls = list(cert_dir.glob("*.srl"))
|
|
assert csrs == []
|
|
assert srls == []
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_key_is_private(self, cert_dir: Path) -> None:
|
|
generate_ca(force=True)
|
|
key, _ = generate_wildcard(force=True)
|
|
assert oct(key.stat().st_mode)[-3:] == "600"
|
|
|
|
|
|
class TestDistributeCA:
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_copies_to_docker_certs_d(self, cert_dir: Path) -> None:
|
|
generate_ca(force=True)
|
|
dest = distribute_ca(registry="reg.example.com")
|
|
assert dest.exists()
|
|
assert dest.parent.name == "reg.example.com"
|
|
assert dest.name == "ca.crt"
|
|
assert dest.read_bytes() == (cert_dir / "ca.crt").read_bytes()
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_copies_to_user_docker_config(
|
|
self,
|
|
cert_dir: Path,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
generate_ca(force=True)
|
|
distribute_ca(registry="reg.example.com")
|
|
user_cert = (
|
|
tmp_path
|
|
/ "user-docker"
|
|
/ ".config"
|
|
/ "docker"
|
|
/ "certs.d"
|
|
/ "reg.example.com"
|
|
/ "ca.crt"
|
|
)
|
|
assert user_cert.exists()
|
|
|
|
|
|
class TestGenerateCoreDNSHosts:
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_creates_hosts_file(self, tmp_path: Path) -> None:
|
|
hosts = generate_coredns_hosts(host_ip="10.0.0.1", domain="x.io")
|
|
assert hosts.exists()
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_contains_all_subdomains(self, tmp_path: Path) -> None:
|
|
hosts = generate_coredns_hosts(host_ip="10.0.0.1", domain="x.io")
|
|
content = hosts.read_text()
|
|
for sub in HOSTS_SUBDOMAINS:
|
|
fqdn = f"{sub}.x.io" if sub else "x.io"
|
|
assert f"10.0.0.1 {fqdn}" in content
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_uses_correct_ip(self, tmp_path: Path) -> None:
|
|
hosts = generate_coredns_hosts(host_ip="172.16.5.99", domain="d.io")
|
|
content = hosts.read_text()
|
|
assert all(
|
|
line.startswith("172.16.5.99 ") for line in content.strip().splitlines()
|
|
)
|
|
|
|
@pytest.mark.usefixtures("_patch_dirs")
|
|
def test_line_count_matches_subdomains(self, tmp_path: Path) -> None:
|
|
hosts = generate_coredns_hosts(host_ip="10.0.0.1", domain="x.io")
|
|
lines = [l for l in hosts.read_text().strip().splitlines() if l.strip()]
|
|
assert len(lines) == len(HOSTS_SUBDOMAINS)
|
|
|
|
|
|
class TestVerify:
|
|
"""Test verify functions against the actual repo certs."""
|
|
|
|
def test_verify_ca_returns_subject(self) -> None:
|
|
info = verify_ca()
|
|
assert "Homelab CA" in info.get("subject", "")
|
|
|
|
def test_verify_ca_returns_issuer(self) -> None:
|
|
info = verify_ca()
|
|
assert "fhirworx" in info.get("issuer", "")
|
|
|
|
def test_verify_wildcard_chain(self) -> None:
|
|
info = verify_wildcard()
|
|
assert info["verified"] == "true"
|
|
|
|
def test_verify_wildcard_san(self) -> None:
|
|
info = verify_wildcard()
|
|
san = info.get("san", "")
|
|
assert "fhirworx.io" in san
|
|
|
|
def test_verify_wildcard_issuer_is_ca(self) -> None:
|
|
info = verify_wildcard()
|
|
assert "Homelab CA" in info.get("issuer", "")
|