diff --git a/src/agent_env/env/envs/computer/utils/sites.py b/src/agent_env/env/envs/computer/utils/sites.py
index 040bfe734..931d902d8 100644
--- a/src/agent_env/env/envs/computer/utils/sites.py
+++ b/src/agent_env/env/envs/computer/utils/sites.py
@@ -8,12 +8,13 @@
site's names (``issue``), and the site proxy (``site_proxy.py``) is installed through the sandbox's root channel and
started (``route``), which a reset does again on its fresh computer. In the computer they live in
``/etc/agentenv/sites``, root's alone (0700) before the config and its keys are written into it, until the install has
-made the keys and the config private; the CA and certificates are valid for the deploy's ttl and a day more, from a few
-minutes back for a clock that's behind. The CA also joins the trust agentenv adds, two files at fixed paths for each OS
-site routing reaches (``TRUST_ENVIRONMENTS``), which on Linux are the local provider's (``LOCAL_TRUST_ENV``):
-``/etc/agentenv/ca.pem``, every CA agentenv adds, for an app with a certificate list of its own, and
-``/etc/agentenv/ca-bundle.pem``, the public roots and them. The computer's boot environment names them, so every program
-in it trusts the sites however it was started. Site routing reaches Linux computers alone."""
+made the keys and the config private; the CA and certificates are valid from 1950 to the end of 9999 (``UNIVERSE_FROM``
+to ``UNIVERSE_UNTIL``), since its programs check them against the universe's time. The CA also
+joins the trust agentenv adds, two files at fixed paths for each OS site routing reaches (``TRUST_ENVIRONMENTS``),
+which on Linux are the local provider's (``LOCAL_TRUST_ENV``): ``/etc/agentenv/ca.pem``, every CA agentenv adds, for
+an app with a certificate list of its own, and ``/etc/agentenv/ca-bundle.pem``, the public roots and them. The
+computer's boot environment names them, so every program in it trusts the sites however it was started. Site routing
+reaches Linux computers alone."""
from __future__ import annotations
@@ -26,7 +27,7 @@
import time
from collections.abc import Collection
from dataclasses import dataclass, field
-from datetime import UTC, datetime, timedelta
+from datetime import UTC, datetime
from pathlib import Path, PurePosixPath
from typing import TYPE_CHECKING, Iterable, Optional
from urllib.parse import urlsplit
@@ -69,8 +70,12 @@
NETWORK_LOG = FOLDER / "network.jsonl"
SYNTHETIC, ALLOWLIST = "synthetic", "allowlist"
RANKS = (SYNTHETIC, ALLOWLIST)
-VALID_MARGIN = timedelta(days=1)
-CLOCK_SKEW = timedelta(minutes=5)
+#: The dates the sites' CA and certificates are valid between, the widest a certificate carries: from 1950, the
+#: earliest X.509's UTCTime holds, to the end of 9999, RFC 5280's date for one with no expiry. A program on the computer
+#: checks them against the universe's time, so they hold however far the universe is from now. The CA is
+#: name-constrained and its key gone once they're signed, so the wide window vouches for nothing more.
+UNIVERSE_FROM = datetime(1950, 1, 1, tzinfo=UTC)
+UNIVERSE_UNTIL = datetime(9999, 12, 31, 23, 59, 59, tzinfo=UTC)
CARD_ATTEMPTS = 3
CARD_RETRY_S = 1.0
#: How long a computer's deploy waits for the run's websites it routes to say they're ready, as each said at its own
@@ -228,14 +233,14 @@ class Issued:
leaves: dict[str, tuple[str, str]]
-def issue(routes: list[Route], *, valid_for: timedelta, label: str, now: Optional[datetime] = None) -> Issued:
+def issue(routes: list[Route], *, label: str) -> Issued:
"""A CA name-constrained to the routes' names, so it vouches for nothing else however widely it's trusted, and a
- certificate signed by it for each route's names served over HTTPS, all valid ``valid_for`` and a margin more."""
+ certificate signed by it for each route's names served over HTTPS, all valid from ``UNIVERSE_FROM`` to
+ ``UNIVERSE_UNTIL``."""
tls = [route for route in routes if route.tls_names]
if not tls:
return Issued(None, {})
- now = now or datetime.now(UTC)
- start, end = now - CLOCK_SKEW, now + valid_for + VALID_MARGIN
+ start, end = UNIVERSE_FROM, UNIVERSE_UNTIL
ca_key = ec.generate_private_key(ec.SECP256R1())
subject = x509.Name([x509.NameAttribute(NameOID.ORGANIZATION_NAME, "agentenv"),
x509.NameAttribute(NameOID.COMMON_NAME, f"agentenv sites {label}"[:64])])
@@ -430,7 +435,7 @@ async def _exec(self, argv: list[str]) -> str:
async def route(sandbox: Sandbox, sites: list[Site], *, user: Optional[str], network: Optional[dict],
- valid_for: timedelta, label: str, os_type: str, display: str, live_hosts: Iterable[str] = ()) -> dict:
+ label: str, os_type: str, display: str, live_hosts: Iterable[str] = ()) -> dict:
"""Have the ``os_type`` computer on ``sandbox`` reach each of ``sites`` by its names: the table made, the CA and
certificates issued, the proxy installed and started as root, with its forward port on a computer with no
``display``, a browser alone's, which also takes the run's ``live_hosts`` straight to them, and each name checked
@@ -446,7 +451,7 @@ async def route(sandbox: Sandbox, sites: list[Site], *, user: Optional[str], net
return {"names": table.to_dict()}
from agent_env.providers.sandbox_providers.sandbox_provider import sandbox_request_headers_for_url
- issued = issue(table.routes, valid_for=valid_for, label=label)
+ issued = issue(table.routes, label=label)
upstream_headers = {r.site: sandbox_request_headers_for_url(r.url) for r in table.routes}
channel = RootChannel(sandbox)
await channel.private_folder(FOLDER)
diff --git a/src/agent_env/providers/env_providers/env_computer_provider.py b/src/agent_env/providers/env_providers/env_computer_provider.py
index 0176ed859..a4090fd43 100644
--- a/src/agent_env/providers/env_providers/env_computer_provider.py
+++ b/src/agent_env/providers/env_providers/env_computer_provider.py
@@ -6,7 +6,7 @@
import asyncio
import functools
import logging
-from datetime import datetime, timedelta, timezone
+from datetime import datetime, timezone
from typing import TYPE_CHECKING, ClassVar, Optional
from urllib.parse import urlsplit
@@ -68,7 +68,7 @@ class EnvironmentComputerProvider(_SandboxEnvironmentProvider):
def __init__(self) -> None:
super().__init__()
- self._later: dict[str, tuple[Sandbox, int]] = {} # each computer booted with sites_later: its sandbox and CA lifetime
+ self._later: dict[str, Sandbox] = {} # each computer booted with sites_later: its sandbox
async def deploy(
self,
@@ -131,13 +131,13 @@ async def deploy(
async def route_sites(self, env: ComputerEnv, record: DeployedSandboxEnv, sites: list[dict]) -> None:
"""Route ``sites`` on the computer ``record`` names, which this provider booted with ``sites_later``, launch its
held apps and finish its boot as a deploy does; its record then keeps them, so a reset routes them again."""
- sandbox, valid_for_s = self._later.pop(record.sandbox_id)
+ sandbox = self._later.pop(record.sandbox_id)
computer = record.metadata["computer"]
params, _, problems = computer_params.checked(env, {**computer["env_params"], "sites": sites})
if problems:
raise ValueError(f"{env.id}: " + "; ".join(problems))
computer["env_params"] = params.model_dump(exclude_defaults=True)
- if routed := await _finish_boot(env, record, sandbox, params, valid_for_s, computer.get("display")):
+ if routed := await _finish_boot(env, record, sandbox, params, computer.get("display")):
computer["sites"] = routed
async def follow_clock(self, record: DeployedSandboxEnv, gateway_url: str, name: str) -> None:
@@ -211,8 +211,8 @@ async def _boot(self, env: ComputerEnv, builds: list[tuple[str, str, dict]], par
"env_params": params.model_dump(exclude_defaults=True)}},
)
if sites_later:
- self._later[record.sandbox_id] = (booted.sandbox, ttl_seconds)
- elif routed := await _finish_boot(env, record, booted.sandbox, params, ttl_seconds, values.get("resolution")):
+ self._later[record.sandbox_id] = booted.sandbox
+ elif routed := await _finish_boot(env, record, booted.sandbox, params, values.get("resolution")):
record.metadata["computer"]["sites"] = routed
except BaseException:
await discard(booted.sandbox)
@@ -245,14 +245,12 @@ async def reboot(self, env: ComputerEnv, deployed: DeployedSandboxEnv) -> None:
card = await ready(deployed.environment_url, ready_timeout(env.composed_apps(), platform_of(env.computer.os)),
deployed.control_token, answering=bool(params.site_routing), sandbox=sandbox)
_require_name(env, card)
- valid_for_s = (record.get("sites") or {}).get("valid_for_s", 0)
- if routed := await _finish_boot(env, deployed, sandbox, params, valid_for_s, values.get("resolution")):
+ if routed := await _finish_boot(env, deployed, sandbox, params, values.get("resolution")):
record["sites"] = routed
async def _finish_boot(env: ComputerEnv, record: DeployedSandboxEnv, sandbox: Sandbox,
- params: computer_params.ComputerParams, valid_for_s: int,
- resolution: Optional[str]) -> Optional[dict]:
+ params: computer_params.ComputerParams, resolution: Optional[str]) -> Optional[dict]:
"""What a deploy and a reset do once the computer's driver answers, in order: the clock follower's config written
again when the record keeps one (a reset's); with ``site_routing``, the sites routed, each browser handed their
names and the held boot apps launched, the computer then ready; then its display checked; then each browser's start
@@ -261,8 +259,7 @@ async def _finish_boot(env: ComputerEnv, record: DeployedSandboxEnv, sandbox: Sa
await computer_clock.reconfigure(sandbox, record)
routed = None
if params.site_routing:
- routed = await _route_sites(env, sandbox, params, valid_for_s,
- tuple(record.metadata["computer"].get("live_hosts") or ()))
+ routed = await _route_sites(env, sandbox, params, tuple(record.metadata["computer"].get("live_hosts") or ()))
await _launch_held_apps(env, record, record.environment_url, params.sites)
await _require_display(record, resolution)
if start_tabs := await _start_tabs(record):
@@ -272,13 +269,12 @@ async def _finish_boot(env: ComputerEnv, record: DeployedSandboxEnv, sandbox: Sa
async def _route_sites(env: ComputerEnv, sandbox: Sandbox, params: computer_params.ComputerParams,
- valid_for_s: int, live_hosts: tuple[str, ...] = ()) -> dict:
- """The computer on ``sandbox`` routed to ``params.sites`` under a CA of its own, valid ``valid_for_s``, a browser
- alone's forward port taking ``live_hosts`` straight to them; what its record keeps of it."""
- routed = await sites.route(sandbox, params.sites, user=env.computer.session.user, network=env.network,
- valid_for=timedelta(seconds=valid_for_s), label=env.environment_name,
- os_type=env.computer.os.type, display=env.computer.os.display, live_hosts=live_hosts)
- return {**routed, "valid_for_s": valid_for_s}
+ live_hosts: tuple[str, ...] = ()) -> dict:
+ """The computer on ``sandbox`` routed to ``params.sites`` under a CA of its own, a browser alone's forward port
+ taking ``live_hosts`` straight to them; what its record keeps of it."""
+ return await sites.route(sandbox, params.sites, user=env.computer.session.user, network=env.network,
+ label=env.environment_name, os_type=env.computer.os.type,
+ display=env.computer.os.display, live_hosts=live_hosts)
async def _launch_held_apps(env: ComputerEnv, record: DeployedSandboxEnv, url: str,
diff --git a/tst/integration/computer/clock_desktop_test.py b/tst/integration/computer/clock_desktop_test.py
index 8b671d1db..0a604b9f8 100644
--- a/tst/integration/computer/clock_desktop_test.py
+++ b/tst/integration/computer/clock_desktop_test.py
@@ -5,7 +5,8 @@
Python reach a live site, its certificate checked against real time by the TLS shim; a shell, Chrome's JavaScript and
the driver read universe time through 30 forward jumps of 8 h, a day or a week, a rate change and a freeze; Chrome,
Xvfb and the desktop's panel run on through them; and the driver's input reaches Chrome after every jump and, frozen,
-is drawn. Needs Docker and the local registry on localhost:5000."""
+is drawn. Routing a site, a fresh Chrome loads it over HTTPS with the clock six years back, six years on and past
+2099, its certificate checked against the universe's time. Needs Docker and the local registry on localhost:5000."""
import ast
import asyncio
@@ -28,6 +29,7 @@
from agent_env.task_step.context import TaskStepContext
from tst.clock.bounds import LATE_S
from tst.integration.computer.conftest import PAGE_WRITER
+from tst.integration.computer.start_tabs import host_pages
from tst.integration.computer.website_browser_test import _window_center
pytestmark = [pytest.mark.integration, pytest.mark.int_test_slow]
@@ -50,6 +52,12 @@
"python": ("python3 -c \"import urllib.request; urllib.request.build_opener(urllib.request.ProxyHandler({}))"
".open('https://example.com/', timeout=30)\"")}
LIBRARY = str(PurePosixPath(FOLLOWER).parent / "libfaketimeMT.so.1")
+#: A fresh Chrome, as the session user, its own profile beside the desktop's, printing the DOM of the page it's given:
+#: each run makes a TLS handshake of its own, which checks the site's certificate against the universe's time (Chrome's
+#: TLS isn't OpenSSL, so the TLS shim doesn't reach it).
+FRESH_CHROME = ('profile=$(mktemp -d) && /opt/agentenv/apps/chromium/chromium/chrome/chrome --headless=new '
+ '--no-sandbox --user-data-dir="$profile" --dump-dom "$0"; rm -rf "$profile"')
+ROUTED = "https://shop.test/landed"
def test_composed_with_the_library_in_every_process_the_computer_passes_the_probe_at_full(clock_desktop):
@@ -120,6 +128,30 @@ async def test_the_desktop_follows_the_universe_through_jumps_a_rate_change_and_
assert not report.failed
+@pytest.mark.asyncio
+async def test_a_fresh_chrome_loads_a_routed_site_six_years_either_side_of_now_and_past_2099(clock_desktop):
+ with host_pages() as port, namespace_routing():
+ params = {"site_routing": True, "sites": [{"name": "shop", "url": f"http://127.0.0.1:{port}/website/shop/",
+ "domains": ["shop.test"]}]}
+ deployed = await Env.get(clock_desktop.id).deploy(env_params=params)
+ context = TaskStepContext(deployed_envs=[deployed])
+ try:
+ clock, loaded = _Clock(deployed), {}
+ for years in (-6, 6, 80):
+ clock.set(time.time() + years * 365 * DAY, 1)
+ await asyncio.sleep(2) # each program maps the page the next time it reads the clock, within a second
+ ran = await inside.run(deployed, ["sh", "-c", FRESH_CHROME, ROUTED], timeout_s=90)
+ loaded[years] = {"universe": await clock.read(deployed),
+ "landed": "
landed" in ran["stdout"],
+ "output": (ran["stdout"] + ran.get("stderr", ""))[-600:]}
+ finally:
+ report = await teardown_run(context)
+
+ assert all(shown["universe"] for shown in loaded.values()), loaded
+ assert {years: shown["landed"] for years, shown in loaded.items()} == {-6: True, 6: True, 80: True}, loaded
+ assert not report.failed
+
+
class _Clock:
"""The computer's clock page, written as its follower's user, as its follower writes it, and what the session
reads of it."""
diff --git a/tst/unit/env/envs/test_computer_env.py b/tst/unit/env/envs/test_computer_env.py
index 838dbd3be..e3fdf992b 100644
--- a/tst/unit/env/envs/test_computer_env.py
+++ b/tst/unit/env/envs/test_computer_env.py
@@ -15,7 +15,6 @@
import shutil
import subprocess
import urllib.parse
-from datetime import timedelta
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock
@@ -1144,11 +1143,11 @@ async def test_a_computer_with_site_routing_reaches_the_websites_the_run_deploye
assert sandbox is boots.sandboxes[0]
assert [site.model_dump() for site in sites] == [{"name": "notes", "url": "http://localhost:8080/website/notes/",
"domains": ["notes.test"]}]
- assert options == {"user": "user", "network": None, "valid_for": timedelta(seconds=3600), "label": "desktop",
- "os_type": "linux", "display": "x11", "live_hosts": ()}
+ assert options == {"user": "user", "network": None, "label": "desktop", "os_type": "linux", "display": "x11",
+ "live_hosts": ()}
assert boots.boots[0]["environment"][HOLD_BOOT_LAUNCH_ENV] == "1"
record = context.deployed_envs[-1].metadata["computer"]
- assert record["sites"] == {"names": {"routes": {"notes": ["notes.test"]}}, "ca": "CA PEM", "valid_for_s": 3600}
+ assert record["sites"] == {"names": {"routes": {"notes": ["notes.test"]}}, "ca": "CA PEM"}
assert record["env_params"]["site_routing"] is True and record["env_params"]["sites"][0]["name"] == "notes"
@@ -1166,9 +1165,8 @@ async def test_a_computer_deployed_to_route_its_sites_later_boots_held_and_route
assert boots.boots[0]["environment"].items() >= computer_sites.TRUST_ENVIRONMENTS["linux"].items()
((sandbox, sites, options),) = routes
assert sandbox is boots.sandboxes[0] and [site.name for site in sites] == ["notes"]
- assert options["valid_for"] == timedelta(seconds=3600)
record = deployed.metadata["computer"]
- assert record["env_params"]["sites"][0]["name"] == "notes" and record["sites"]["valid_for_s"] == 3600
+ assert record["env_params"]["sites"][0]["name"] == "notes" and record["sites"]["ca"] == "CA PEM"
@pytest.mark.asyncio
@@ -1390,7 +1388,7 @@ async def test_a_start_tab_on_one_of_the_env_sites_that_didnt_load_fails_the_boo
with pytest.raises(RuntimeError) as failed:
await env_computer_provider._finish_boot(SimpleNamespace(), record, None,
- SimpleNamespace(site_routing=False), 0, None)
+ SimpleNamespace(site_routing=False), None)
assert record.metadata["computer"]["start_tabs"] == {"chromium": tabs}
assert str(failed.value) == (
@@ -1435,8 +1433,8 @@ async def test_a_reset_routes_the_fresh_computer_to_the_same_sites_under_a_ca_of
await ResetEnvTaskStep(id="reset", version=None, env_id=env.id, fail_task_on_error=True).execute(context)
first, again = routes
- assert again[0] is sandbox and again[1] == first[1] and again[2]["valid_for"] == timedelta(seconds=3600)
- assert context.deployed_envs[-1].metadata["computer"]["sites"]["valid_for_s"] == 3600
+ assert again[0] is sandbox and again[1] == first[1] and again[2] == first[2]
+ assert context.deployed_envs[-1].metadata["computer"]["sites"]["ca"] == "CA PEM"
@pytest.mark.asyncio
diff --git a/tst/unit/env/envs/test_computer_site_proxy.py b/tst/unit/env/envs/test_computer_site_proxy.py
index 5fd2000a2..a6818f9e3 100644
--- a/tst/unit/env/envs/test_computer_site_proxy.py
+++ b/tst/unit/env/envs/test_computer_site_proxy.py
@@ -18,7 +18,6 @@
import sys
import time
import urllib.error
-from datetime import timedelta
from pathlib import Path
from types import SimpleNamespace
@@ -121,7 +120,7 @@ async def _exchange(port, request: bytes, *, server_hostname=None, cadata=None,
def _issued(tmp_path, routes):
- issued = sites.issue(routes, valid_for=timedelta(hours=1), label="test")
+ issued = sites.issue(routes, label="test")
files = {}
for name, (cert, key) in issued.leaves.items():
(tmp_path / f"{name}.crt").write_text(cert)
@@ -875,7 +874,7 @@ def test_install_writes_the_sites_files_names_and_trust_then_starts_the_proxy_on
table = sites.site_table([SiteParams(name="notes", url="http://gw/website/notes/", domains=["notes.test"]),
SiteParams(name="chat", url="http://gw/website/chat/", domains=["*.chat.test"]),
SiteParams(name="bare", url="http://gw/website/bare/")])
- issued = sites.issue(table.routes, valid_for=timedelta(hours=1), label="t")
+ issued = sites.issue(table.routes, label="t")
(folder / "config.json").write_text(json.dumps(sites.proxy_config(table, issued, user=None, os_type="linux",
forward_port=True, direct=["github.com"])))
started = []
@@ -922,7 +921,7 @@ def test_install_keeps_the_upstream_headers_in_a_file_root_alone_reads_and_serve
folder.mkdir()
folder.chmod(0o700)
table = sites.site_table([SiteParams(name="notes", url="http://gw/website/notes/", domains=["notes.test"])])
- issued = sites.issue(table.routes, valid_for=timedelta(hours=1), label="t")
+ issued = sites.issue(table.routes, label="t")
given = sites.proxy_config(table, issued, user=None, os_type="linux", forward_port=False,
upstream_headers={"notes": {"CF-Access-Client-Secret": "s3cret"}})
(folder / "config.json").write_text(json.dumps(given))
@@ -1061,8 +1060,7 @@ def _calls(store: Path) -> list[str]:
def test_every_jdk_trusts_the_ca_an_apt_one_through_the_system_hook_the_rest_by_keytool(tmp_path, monkeypatch):
issued = sites.issue(sites.site_table([SiteParams(name="notes", url="http://gw/website/notes/",
- domains=["notes.test"])]).routes, valid_for=timedelta(hours=1),
- label="t")
+ domains=["notes.test"])]).routes, label="t")
fingerprint = site_proxy.sha256_fingerprint(issued.ca_pem)
ca = tmp_path / "ca.pem"
ca.write_text(issued.ca_pem)
@@ -1101,8 +1099,7 @@ def test_a_jdk_store_the_ca_isnt_listed_in_after_routing_is_named(tmp_path, monk
apt.unlink()
apt.symlink_to(hook)
issued = sites.issue(sites.site_table([SiteParams(name="notes", url="http://gw/website/notes/",
- domains=["notes.test"])]).routes, valid_for=timedelta(hours=1),
- label="t")
+ domains=["notes.test"])]).routes, label="t")
trusted = site_proxy.trust_java(site_proxy.Paths(root), str(tmp_path / "ca.pem"), issued.ca_pem)
@@ -1120,8 +1117,7 @@ def test_each_runtime_loads_the_first_https_site_through_its_own_trust_and_says_
monkeypatch.delenv("SSL_CERT_FILE", raising=False)
unset = site_proxy.check_runtimes("https://notes.test/")
issued = sites.issue(sites.site_table([SiteParams(name="notes", url="http://gw/website/notes/",
- domains=["notes.test"])]).routes, valid_for=timedelta(hours=1),
- label="t")
+ domains=["notes.test"])]).routes, label="t")
(tmp_path / "bundle.pem").write_text(issued.ca_pem)
monkeypatch.setenv("SSL_CERT_FILE", str(tmp_path / "bundle.pem"))
opened = []
@@ -1153,7 +1149,7 @@ def test_a_wildcard_name_starts_dns_even_when_resolv_conf_names_no_other_nameser
if resolv is not None:
(root / "etc/resolv.conf").write_text(resolv)
table = sites.site_table([SiteParams(name="chat", url="http://gw/website/chat/", domains=["*.chat.test"])])
- issued = sites.issue(table.routes, valid_for=timedelta(hours=1), label="t")
+ issued = sites.issue(table.routes, label="t")
(folder / "config.json").write_text(json.dumps(sites.proxy_config(table, issued, user=None, os_type="linux",
forward_port=False)))
started = []
diff --git a/tst/unit/env/envs/test_computer_sites.py b/tst/unit/env/envs/test_computer_sites.py
index bf90a5485..85937cfe1 100644
--- a/tst/unit/env/envs/test_computer_sites.py
+++ b/tst/unit/env/envs/test_computer_sites.py
@@ -101,13 +101,12 @@ def test_a_deploys_ca_signs_each_sites_names_and_is_constrained_to_them():
table = sites.site_table([_site("notes", "notes.test"), _site("slack", "slack.com", "*.slack.com"),
_site("bare")])
- issued = sites.issue(table.routes, valid_for=timedelta(hours=3), label="desktop", now=now)
+ issued = sites.issue(table.routes, label="desktop")
assert set(issued.leaves) == {"notes", "slack"}
ca = x509.load_pem_x509_certificate(issued.ca_pem.encode())
constraints = ca.extensions.get_extension_for_class(x509.NameConstraints).value
assert [n.value for n in constraints.permitted_subtrees] == ["notes.test", "slack.com"]
- assert ca.not_valid_after_utc == now + timedelta(hours=3) + sites.VALID_MARGIN
for site, name in (("notes", "notes.test"), ("slack", "slack.com"), ("slack", "eu.slack.com")):
_verify(issued.ca_pem, issued.leaves[site][0], name, now)
with pytest.raises(VerificationError):
@@ -115,19 +114,32 @@ def test_a_deploys_ca_signs_each_sites_names_and_is_constrained_to_them():
assert "PRIVATE KEY" in issued.leaves["notes"][1] and "PRIVATE KEY" not in issued.ca_pem
-def test_a_name_longer_than_a_common_name_takes_is_signed_by_its_alternative_names_alone():
+def test_the_ca_and_certificates_hold_at_any_date_a_computers_clock_can_show():
+ issued = sites.issue(sites.site_table([_site("notes", "notes.test")]).routes, label="desktop")
now = datetime.now(UTC).replace(microsecond=0)
+
+ for cert in (issued.ca_pem, issued.leaves["notes"][0]):
+ loaded = x509.load_pem_x509_certificate(cert.encode())
+ assert (loaded.not_valid_before_utc, loaded.not_valid_after_utc) == (sites.UNIVERSE_FROM, sites.UNIVERSE_UNTIL)
+ for universe in (datetime(1950, 1, 1, tzinfo=UTC), now - timedelta(days=6 * 365), now,
+ now + timedelta(days=6 * 365), datetime(2100, 1, 1, tzinfo=UTC),
+ datetime(9999, 12, 31, 23, 59, 59, tzinfo=UTC)):
+ _verify(issued.ca_pem, issued.leaves["notes"][0], "notes.test", universe)
+ with pytest.raises(VerificationError):
+ _verify(issued.ca_pem, issued.leaves["notes"][0], "notes.test", sites.UNIVERSE_FROM - timedelta(seconds=1))
+
+
+def test_a_name_longer_than_a_common_name_takes_is_signed_by_its_alternative_names_alone():
name = f"{'a' * 63}.test"
- issued = sites.issue(sites.site_table([_site("long", name)]).routes, valid_for=timedelta(hours=1), label="x",
- now=now)
+ issued = sites.issue(sites.site_table([_site("long", name)]).routes, label="x")
leaf = x509.load_pem_x509_certificate(issued.leaves["long"][0].encode())
assert len(leaf.subject) == 0 and leaf.extensions.get_extension_for_class(x509.SubjectAlternativeName).critical
- _verify(issued.ca_pem, issued.leaves["long"][0], name, now)
+ _verify(issued.ca_pem, issued.leaves["long"][0], name, datetime.now(UTC))
def test_no_ca_is_made_when_no_name_is_served_over_https():
- assert sites.issue(sites.site_table([_site("bare")]).routes, valid_for=timedelta(hours=1), label="x") == \
+ assert sites.issue(sites.site_table([_site("bare")]).routes, label="x") == \
sites.Issued(None, {})
@@ -185,7 +197,7 @@ async def test_route_installs_the_proxy_as_root_with_each_sites_route_as_the_com
sandbox = FakeVm(_REPORT)
routed = await sites.route(sandbox, [_site("notes", "notes.test")], user="user", network=None,
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display="x11")
+ label="desktop", os_type="linux", display="x11")
assert set(sandbox.written) == {"/etc/agentenv/sites/site_proxy.py", "/etc/agentenv/sites/config.json"}
config = json.loads(sandbox.written["/etc/agentenv/sites/config.json"])
@@ -210,7 +222,7 @@ async def test_route_gives_a_browser_alone_the_forward_port_and_a_computer_with_
for sandbox, display in ((alone, "none"), (desktop, "x11")):
await sites.route(sandbox, [_site("notes", "notes.test")], user="user", network=None,
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display=display)
+ label="desktop", os_type="linux", display=display)
configs = [json.loads(sandbox.written["/etc/agentenv/sites/config.json"]) for sandbox in (alone, desktop)]
assert [config.get("proxy_port") for config in configs] == [3129, None]
@@ -221,10 +233,10 @@ async def test_a_browser_alones_forward_port_takes_the_live_sites_hosts_straight
routed, alone, desktop = FakeVm(_REPORT), FakeVm(_REPORT), FakeVm(_REPORT)
live = ("github.com", "*.githubassets.com")
- await sites.route(routed, [_site("notes", "notes.test")], user="user", network=None, valid_for=timedelta(hours=1),
- label="browser", os_type="linux", display="none", live_hosts=live)
+ await sites.route(routed, [_site("notes", "notes.test")], user="user", network=None, label="browser",
+ os_type="linux", display="none", live_hosts=live)
for sandbox, display in ((alone, "none"), (desktop, "x11")):
- await sites.route(sandbox, [], user="user", network=None, valid_for=timedelta(hours=1), label="browser",
+ await sites.route(sandbox, [], user="user", network=None, label="browser",
os_type="linux", display=display, live_hosts=live)
beside = json.loads(routed.written["/etc/agentenv/sites/config.json"])
@@ -239,7 +251,7 @@ async def test_the_config_holding_the_keys_is_written_over_stdin_into_a_private_
sandbox = FakeVm(_REPORT)
await sites.route(sandbox, [_site("notes", "notes.test")], user="user", network=None,
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display="x11")
+ label="desktop", os_type="linux", display="x11")
assert ("write", "/etc/agentenv/sites/config.json") not in sandbox.events
secret = sandbox.events.index(("secret", "/tmp/secret-1/config.json"))
@@ -257,7 +269,7 @@ async def test_the_headers_the_gateways_provider_asks_for_go_in_the_private_conf
"CF-Access-Client-Secret": "s3cret"} if url.startswith("http://host.docker.internal:8080/") else {})
routed = await sites.route(sandbox, [_site("notes", "notes.test")], user="user", network=None,
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display="x11")
+ label="desktop", os_type="linux", display="x11")
(site,) = json.loads(sandbox.written["/etc/agentenv/sites/config.json"])["sites"]
assert site["upstream_headers"] == {"CF-Access-Client-Secret": "s3cret"}
@@ -272,7 +284,7 @@ async def test_the_sites_keys_land_in_a_folder_root_alone_can_enter():
sandbox = FakeVm(_REPORT)
await sites.route(sandbox, [_site("notes", "notes.test")], user="user", network=None,
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display="x11")
+ label="desktop", os_type="linux", display="x11")
private = sandbox.events.index("docker exec -u 0 computer-1 chmod 0700 /etc/agentenv/sites")
assert private < sandbox.events.index(("cp", "/tmp/secret-1/config.json", "computer-1:/etc/agentenv/sites/config.json"))
@@ -285,7 +297,7 @@ async def test_a_name_that_doesnt_load_in_the_computer_fails_the_route():
with pytest.raises(RuntimeError, match="https://notes.test/ didn't load in the computer: certificate verify"):
await sites.route(FakeVm(report), [_site("notes", "notes.test")], user="user", network=None,
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display="x11")
+ label="desktop", os_type="linux", display="x11")
@pytest.mark.asyncio
@@ -295,8 +307,8 @@ async def test_a_network_that_keeps_the_computer_from_the_gateway_is_refused_bef
with pytest.raises(RuntimeError, match="allows only api.example, so it can't reach the gateway at "
"host.docker.internal"):
await sites.route(sandbox, [_site("notes", "notes.test")], user="user",
- network={"mode": "allowlist", "allow_hosts": ["api.example"]}, valid_for=timedelta(hours=1),
- label="desktop", os_type="linux", display="x11")
+ network={"mode": "allowlist", "allow_hosts": ["api.example"]}, label="desktop",
+ os_type="linux", display="x11")
assert sandbox.scripts == [] and sandbox.written == {}
@@ -310,7 +322,7 @@ async def test_a_gateway_the_sandboxs_applied_network_allows_by_its_address_rang
routed = await sites.route(sandbox, [_site("notes", "notes.test")], user="user",
network={"mode": "allowlist", "allow_hosts": ["api.example"]},
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display="x11")
+ label="desktop", os_type="linux", display="x11")
assert routed["checks"] == _REPORT["checks"]
routes = [sites.Route("notes", "http://10.0.0.7:8080/website/notes/", ("notes.test",))]
@@ -335,7 +347,7 @@ async def write_file_from_text(content, path):
write_file_from_text=write_file_from_text)
await sites.route(sandbox, [_site("notes", "notes.test")], user="user", network=None,
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display="x11")
+ label="desktop", os_type="linux", display="x11")
assert calls == [("id", "-u"), ("mkdir", "-p", "/etc/agentenv/sites"), ("chmod", "0700", "/etc/agentenv/sites"),
("mkdir", "-p", "/etc/agentenv/sites"), ("write", "/etc/agentenv/sites/site_proxy.py"),
@@ -360,7 +372,7 @@ async def write_file_from_text(content, path):
with pytest.raises(RuntimeError, match="the coreweave sandbox runs the computer's commands as uid 1000"):
await sites.route(sandbox, [_site("notes", "notes.test")], user="user", network=None,
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display="x11")
+ label="desktop", os_type="linux", display="x11")
assert calls == [("id", "-u")]
@@ -373,7 +385,7 @@ async def test_a_site_naming_the_gateway_its_routes_are_on_is_refused_before_any
with pytest.raises(RuntimeError, match=r"the gateway at gw.sites.test \(named by notes's .*sites.test\) would"):
await sites.route(sandbox, [_site("notes", *domains)], user="user", network=None,
- valid_for=timedelta(hours=1), label="desktop", os_type="linux", display="x11")
+ label="desktop", os_type="linux", display="x11")
assert sandbox.scripts == [] and sandbox.written == {}
@@ -430,7 +442,7 @@ def gateway(env_id, *domains):
table = sites.site_table([Site(**s) for s in found])
assert {route.site: route.domains for route in table.routes} == {"team-a-notes": ("notes.test",),
"team-b-notes": ("notes.example",)}
- assert set(sites.issue(table.routes, valid_for=timedelta(hours=1), label="x").leaves) == {"team-a-notes",
+ assert set(sites.issue(table.routes, label="x").leaves) == {"team-a-notes",
"team-b-notes"}
clash = await sites.deployed_sites([gateway("team-a", "notes.test"), gateway("team-b", "notes.test")])
with pytest.raises(sites.SiteConflict, match="'notes.test' \\(team-b-notes\\) overlaps 'notes.test' \\(team-a-notes\\)"):