203 lines
7.3 KiB
Python
203 lines
7.3 KiB
Python
"""Agent authentication, networking, and Flux health contracts."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from testing.tests.test_hermes_cli_support import (
|
|
FLUX_HERMES,
|
|
HERMES,
|
|
KEYCLOAK,
|
|
_agent_deployment,
|
|
yaml,
|
|
)
|
|
|
|
|
|
def test_agent_image_runs_execution_safety_patch_and_regressions():
|
|
dockerfiles = HERMES.parents[1] / "dockerfiles"
|
|
dockerfile = (dockerfiles / "Dockerfile.hermes-agent").read_text(encoding="utf-8")
|
|
dockerignore = (dockerfiles / "Dockerfile.hermes-agent.dockerignore").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
|
|
for name in (
|
|
"patch-hermes-execution-safety.py",
|
|
"hermes-execution-safety-regression.py",
|
|
"hermes_execution_patch_support.py",
|
|
"patch_hermes_run_safety.py",
|
|
"patch_hermes_decomposition_safety.py",
|
|
"hermes_execution_regression_support.py",
|
|
"hermes_run_safety_regression.py",
|
|
"hermes_decomposition_safety_regression.py",
|
|
):
|
|
assert f"COPY dockerfiles/{name}" in dockerfile
|
|
assert f"!dockerfiles/{name}" in dockerignore
|
|
assert "/opt/hermes/.venv/bin/python /tmp/patch-hermes-execution-safety.py" in (
|
|
dockerfile
|
|
)
|
|
assert "/opt/hermes/.venv/bin/python /tmp/hermes-execution-safety-regression.py" in (
|
|
dockerfile
|
|
)
|
|
assert "COPY services/hermes/scripts/cli_lane_*.py" in dockerfile
|
|
assert "!services/hermes/scripts/cli_lane_*.py" in dockerignore
|
|
assert "HERMES_CLI_LANE_SOURCE=/tmp/hermes-lane-regression" in dockerfile
|
|
|
|
|
|
def test_agent_refreshes_routes_after_restoring_cli_logins():
|
|
deployment = _agent_deployment()
|
|
init_containers = {
|
|
item["name"]: item
|
|
for item in deployment["spec"]["template"]["spec"]["initContainers"]
|
|
}
|
|
configure = init_containers["configure-agent-clients"]
|
|
command = configure["command"][-1]
|
|
assert "configure_agent_clients.py" in command
|
|
assert command.index("configure_agent_clients.py") < command.index(
|
|
"hermes_coordinator.py --once"
|
|
)
|
|
env = {item["name"]: item["value"] for item in configure["env"]}
|
|
assert env["HERMES_AUTH_FILE"] == "/runtime-access/hermes-auth.json"
|
|
assert env["PYTHONPATH"] == "/opt/hermes"
|
|
for name in ("bootstrap-coordinator", "configure-agent-clients"):
|
|
route_env = {
|
|
item["name"]: item["value"]
|
|
for item in init_containers[name]["env"]
|
|
}
|
|
assert route_env["CODEX_HOME"] == "/runtime-access/codex"
|
|
assert route_env["CLAUDE_CONFIG_DIR"] == "/runtime-access/claude"
|
|
assert "/opt/data/tools/bin" in route_env["PATH"]
|
|
|
|
assert "patch-web-session-activity" in init_containers
|
|
web_patch = init_containers["patch-web-session-activity"]
|
|
assert "/opt/coordinator/patch_web_session_activity.py" in web_patch["command"]
|
|
|
|
containers = {
|
|
item["name"]: item
|
|
for item in deployment["spec"]["template"]["spec"]["containers"]
|
|
}
|
|
steward_env = {
|
|
item["name"]: item["value"]
|
|
for item in containers["model-steward"]["env"]
|
|
}
|
|
assert steward_env["CODEX_HOME"] == "/runtime-access/codex"
|
|
assert steward_env["CLAUDE_CONFIG_DIR"] == "/runtime-access/claude"
|
|
assert "/opt/data/tools/bin" in steward_env["PATH"]
|
|
hermes_mounts = {
|
|
(item["name"], item["mountPath"], item.get("subPath"))
|
|
for item in containers["hermes"]["volumeMounts"]
|
|
}
|
|
assert (
|
|
"web-server-patch",
|
|
"/opt/hermes/hermes_cli/web_server.py",
|
|
"web_server.py",
|
|
) in hermes_mounts
|
|
|
|
|
|
def test_flux_health_checks_follow_the_owner_oauth_sidecar():
|
|
flux = yaml.safe_load(FLUX_HERMES.read_text())
|
|
checks = {
|
|
(item["kind"], item["name"])
|
|
for item in flux["spec"]["healthChecks"]
|
|
}
|
|
assert ("Deployment", "hermes-agent") in checks
|
|
assert ("DaemonSet", "hermes-node-ssh-access") in checks
|
|
assert ("Deployment", "oauth2-proxy-hermes-agent") not in checks
|
|
|
|
|
|
def test_agent_auth_is_bstein_group_and_email_bounded():
|
|
deployment = _agent_deployment()
|
|
oauth = next(
|
|
item for item in deployment["spec"]["template"]["spec"]["containers"]
|
|
if item["name"] == "oauth2-proxy"
|
|
)
|
|
args = oauth["args"]
|
|
assert "--user-id-claim=sub" in args
|
|
assert "--oidc-groups-claim=groups" in args
|
|
assert "--allowed-group=/hermes-owner" in args
|
|
assert "--authenticated-emails-file=/etc/oauth2-proxy/allowed-emails" in args
|
|
script = (KEYCLOAK / "scripts/hermes_access_oidc_ensure.sh").read_text()
|
|
assert 'group_name="hermes-owner"' in script
|
|
assert "username=bstein&exact=true" in script
|
|
assert '"full.path":"true"' in script
|
|
|
|
|
|
def test_agent_network_boundary_allows_only_authenticated_and_metrics_surfaces():
|
|
documents = [
|
|
item
|
|
for item in yaml.safe_load_all((HERMES / "networkpolicy.yaml").read_text())
|
|
if item
|
|
]
|
|
isolation = next(item for item in documents if item.get("metadata", {}).get("name") == "hermes-agent-isolation")
|
|
assert isolation["spec"]["ingress"] == [
|
|
{
|
|
"from": [
|
|
{
|
|
"namespaceSelector": {
|
|
"matchLabels": {
|
|
"kubernetes.io/metadata.name": "traefik"
|
|
}
|
|
},
|
|
"podSelector": {
|
|
"matchLabels": {"app.kubernetes.io/name": "traefik"}
|
|
},
|
|
}
|
|
],
|
|
"ports": [{"protocol": "TCP", "port": 4180}],
|
|
},
|
|
{
|
|
"from": [
|
|
{
|
|
"podSelector": {
|
|
"matchLabels": {"app": "hermes-chat-tenant"}
|
|
}
|
|
}
|
|
],
|
|
"ports": [
|
|
{"protocol": "TCP", "port": 9002},
|
|
{"protocol": "TCP", "port": 9003},
|
|
],
|
|
},
|
|
{
|
|
"from": [
|
|
{
|
|
"podSelector": {
|
|
"matchLabels": {"app": "hermes-switchyard"}
|
|
}
|
|
}
|
|
],
|
|
"ports": [
|
|
{"protocol": "TCP", "port": 9003},
|
|
{"protocol": "TCP", "port": 9006},
|
|
],
|
|
},
|
|
{
|
|
"from": [
|
|
{
|
|
"namespaceSelector": {
|
|
"matchLabels": {
|
|
"kubernetes.io/metadata.name": "monitoring"
|
|
}
|
|
},
|
|
"podSelector": {"matchLabels": {"app": "server"}},
|
|
}
|
|
],
|
|
"ports": [{"protocol": "TCP", "port": 9010}],
|
|
},
|
|
]
|
|
assert isolation["spec"]["egress"] == [{}]
|
|
|
|
|
|
def test_owner_agent_has_cluster_admin_kubernetes_context():
|
|
config = yaml.safe_load((HERMES / "agent-kubeconfig.yaml").read_text())
|
|
assert config["current-context"] == "atlas-owner"
|
|
assert config["contexts"][0]["context"]["namespace"] == "default"
|
|
rbac_path = HERMES / "agent-rbac.yaml"
|
|
documents = [item for item in yaml.safe_load_all(rbac_path.read_text()) if item]
|
|
binding = next(item for item in documents if item["kind"] == "ClusterRoleBinding")
|
|
assert binding["roleRef"] == {
|
|
"apiGroup": "rbac.authorization.k8s.io",
|
|
"kind": "ClusterRole",
|
|
"name": "cluster-admin",
|
|
}
|
|
assert binding["subjects"] == [
|
|
{"kind": "ServiceAccount", "name": "hermes-agent", "namespace": "hermes"}
|
|
]
|