diff --git a/AGENTS.md b/AGENTS.md index 3741729..b9d48f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -272,6 +272,17 @@ Docker: --- +## 8. CryptoLabs Public Sites Are Not Routed Here + +This repository is the fleet proxy product for customer datacenters. It never +routes CryptoLabs' own public sites (`*.cryptolabs.co.za` on wk01). Those are +served by the common proxy in `cryptolabs-ai-platform`: +https://github.com/cryptolabsza/cryptolabs-ai-platform/tree/dev/services/nginx-configs +— change public-site routing by a PR there. `tests/test_edge_ownership.py` +keeps wk01 routes out of the shipped nginx config and templates. + +--- + ## Summary | Policy | One-Liner | diff --git a/README.md b/README.md index b2045b9..1729a50 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,15 @@ Unified reverse proxy and fleet management landing page for CryptoLabs products. | Grafana | `/grafana/` | Metrics visualization | | Prometheus | `/prometheus/` | Metrics collection (with auth) | +### CryptoLabs' own public sites + +This product is the fleet proxy for customer datacenters. It does **not** route +CryptoLabs' own public sites under `cryptolabs.co.za`. Those are served by the +common proxy owned by `cryptolabs-ai-platform` +([services/nginx-configs](https://github.com/cryptolabsza/cryptolabs-ai-platform/tree/dev/services/nginx-configs), +deployed by its Synchronized Platform Deployment). +Add or change public-site routing there, not here. + ## Quick Start The easiest way to deploy is through **DC Overview** or **IPMI Monitor** quickstart, which automatically sets up cryptolabs-proxy: diff --git a/nginx/nginx.conf b/nginx/nginx.conf index f607f57..00e1623 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -497,61 +497,10 @@ http { } # ===================================================== - # Subdomain Services - Proxy to GPU Worker (wk01) - # These services run on 192.168.1.101 behind their own - # nginx with SSL (Let's Encrypt) and service-specific config. + # CryptoLabs' own public sites (kb, api.ai, webui.ai, ipmi-ai, ...) are + # NOT routed by this product. They are served by the common proxy owned by + # cryptolabs-ai-platform (services/nginx-configs), which is the only place + # that ships public-site routing. See that repo before adding any + # *.cryptolabs.co.za server block here. # ===================================================== - - server { - listen 80; - server_name kb.cryptolabs.co.za - ipmi-ai.cryptolabs.co.za - webui.ai.cryptolabs.co.za - api.ai.cryptolabs.co.za - tts.cryptolabs.co.za - wpbm.ai.cryptolabs.co.za - framepack.ai.cryptolabs.co.za; - - location / { - proxy_pass http://192.168.1.101:80; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - } - - server { - listen 443 ssl; - http2 on; - server_name kb.cryptolabs.co.za - ipmi-ai.cryptolabs.co.za - webui.ai.cryptolabs.co.za - api.ai.cryptolabs.co.za - tts.cryptolabs.co.za - wpbm.ai.cryptolabs.co.za - framepack.ai.cryptolabs.co.za; - - # Use proxy's own certs (wk01 nginx handles real SSL termination) - ssl_certificate /etc/nginx/ssl/server.crt; - ssl_certificate_key /etc/nginx/ssl/server.key; - ssl_protocols TLSv1.2 TLSv1.3; - - location / { - proxy_pass https://192.168.1.101; - proxy_ssl_verify off; - proxy_ssl_server_name on; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto https; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_read_timeout 300s; - proxy_send_timeout 300s; - proxy_buffering off; - client_max_body_size 100M; - } - } } diff --git a/pyproject.toml b/pyproject.toml index 4fa32da..8334cc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "cryptolabs-proxy" -version = "1.1.9" +version = "1.1.10" description = "Unified reverse proxy and fleet management landing page for CryptoLabs products" readme = "README.md" license = {text = "MIT"} diff --git a/src/cryptolabs_proxy/__init__.py b/src/cryptolabs_proxy/__init__.py index e92123b..94185dd 100644 --- a/src/cryptolabs_proxy/__init__.py +++ b/src/cryptolabs_proxy/__init__.py @@ -1,6 +1,6 @@ """CryptoLabs Proxy - Unified reverse proxy for CryptoLabs products.""" -__version__ = "1.1.9" +__version__ = "1.1.10" # Export programmatic setup API from .setup import ( diff --git a/tests/test_edge_ownership.py b/tests/test_edge_ownership.py new file mode 100644 index 0000000..6febff6 --- /dev/null +++ b/tests/test_edge_ownership.py @@ -0,0 +1,87 @@ +"""CryptoLabs' own public wk01 sites are served by the common proxy in +cryptolabs-ai-platform (services/nginx-configs), not by this customer fleet +proxy product. These tests keep wk01 routing out of the shipped nginx config +and templates.""" + +import re +from pathlib import Path + +import pytest + +REPOSITORY = Path(__file__).resolve().parents[1] +WK01_ADDRESS = "192.168.1.101" +SERVER_NAME = re.compile(r"^\s*server_name\s+([^;]*);", re.MULTILINE) + + +def _nginx_files(): + files = [REPOSITORY / "nginx" / "nginx.conf"] + files += sorted((REPOSITORY / "src" / "cryptolabs_proxy" / "templates").glob("*.j2")) + return files + + +def _server_names(text): + names = [] + for match in SERVER_NAME.finditer(text): + names.extend(match.group(1).split()) + return names + + +def _rendered_templates(tmp_path): + from cryptolabs_proxy.config import generate_nginx_config + from cryptolabs_proxy.services import DEFAULT_SERVICES + + generate_nginx_config(tmp_path, "fleet.example.test", services=dict(DEFAULT_SERVICES)) + plain = (tmp_path / "nginx.conf").read_text() + generate_nginx_config(tmp_path, "fleet.example.test", letsencrypt=True, services=dict(DEFAULT_SERVICES)) + return [plain, (tmp_path / "nginx.conf").read_text()] + + +def test_scanned_files_exist(): + names = [path.name for path in _nginx_files()] + assert "nginx.conf" in names and "nginx.conf.j2" in names + + +@pytest.mark.parametrize("path", _nginx_files(), ids=lambda path: path.name) +def test_shipped_nginx_files_do_not_route_to_wk01(path): + assert WK01_ADDRESS not in path.read_text() + + +@pytest.mark.parametrize("path", _nginx_files(), ids=lambda path: path.name) +def test_shipped_nginx_files_do_not_serve_cryptolabs_co_za_hosts(path): + offenders = [n for n in _server_names(path.read_text()) if n.rstrip(".").endswith(".cryptolabs.co.za")] + assert offenders == [] + + +def test_rendered_template_does_not_route_to_wk01_or_serve_cryptolabs_co_za(tmp_path): + for config in _rendered_templates(tmp_path): + assert "server {" in config + assert WK01_ADDRESS not in config + offenders = [n for n in _server_names(config) if n.rstrip(".").endswith(".cryptolabs.co.za")] + assert offenders == [] + + +def test_nginx_conf_points_to_the_common_proxy(): + text = (REPOSITORY / "nginx" / "nginx.conf").read_text() + assert "cryptolabs-ai-platform" in text + assert "services/nginx-configs" in text + + +def test_readme_points_to_the_common_proxy(): + text = (REPOSITORY / "README.md").read_text() + assert "cryptolabs-ai-platform" in text + assert "services/nginx-configs" in text + + +COMMON_PROXY_LINK = "https://github.com/cryptolabsza/cryptolabs-ai-platform/tree/dev/services/nginx-configs" + + +def test_readme_links_to_the_common_proxy_and_names_no_hosts(): + text = (REPOSITORY / "README.md").read_text() + assert COMMON_PROXY_LINK in text + assert "framepack" not in text + + +def test_agents_md_forbids_routing_cryptolabs_sites(): + text = (REPOSITORY / "AGENTS.md").read_text() + assert "common proxy" in text + assert COMMON_PROXY_LINK in text diff --git a/tests/test_version_consistency.py b/tests/test_version_consistency.py new file mode 100644 index 0000000..1a612b0 --- /dev/null +++ b/tests/test_version_consistency.py @@ -0,0 +1,13 @@ +"""The package version must be the same in pyproject.toml and __init__.py.""" +import re +from pathlib import Path + +import cryptolabs_proxy + +ROOT = Path(__file__).resolve().parents[1] + + +def test_pyproject_and_dunder_version_match(): + text = (ROOT / "pyproject.toml").read_text() + pyproject = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE).group(1) + assert pyproject == cryptolabs_proxy.__version__