fix(hermes): provision Cassandra diagnostics
Some checks failed
Tests / Declarative: Post Actions failed: 2, passed: 179
Some checks failed
Tests / Declarative: Post Actions failed: 2, passed: 179
This commit is contained in:
parent
be2aa52d51
commit
c3ad73bd20
52
services/cassandra/hermes-agent-rbac.yaml
Normal file
52
services/cassandra/hermes-agent-rbac.yaml
Normal file
@ -0,0 +1,52 @@
|
||||
# services/cassandra/hermes-agent-rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: hermes-agent-readonly
|
||||
namespace: cassandra
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- configmaps
|
||||
- endpoints
|
||||
- events
|
||||
- persistentvolumeclaims
|
||||
- pods
|
||||
- pods/log
|
||||
- replicationcontrollers
|
||||
- services
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["events.k8s.io"]
|
||||
resources: ["events"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["apps"]
|
||||
resources:
|
||||
- daemonsets
|
||||
- deployments
|
||||
- replicasets
|
||||
- statefulsets
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["batch"]
|
||||
resources:
|
||||
- cronjobs
|
||||
- jobs
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources:
|
||||
- ingresses
|
||||
- networkpolicies
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: hermes-agent-readonly
|
||||
namespace: cassandra
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: hermes-agent
|
||||
namespace: hermes
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: hermes-agent-readonly
|
||||
@ -11,6 +11,7 @@ resources:
|
||||
- limitrange.yaml
|
||||
- configmap.yaml
|
||||
- rbac.yaml
|
||||
- hermes-agent-rbac.yaml
|
||||
- artifacts-pvc.yaml
|
||||
- postgres-service.yaml
|
||||
- postgres-statefulset.yaml
|
||||
|
||||
@ -215,9 +215,11 @@ data:
|
||||
objective's difficulty warrants it; otherwise synthesize at the original
|
||||
effort.
|
||||
|
||||
This pod has no Kubernetes RBAC. Do not use it for automated test intake or
|
||||
cluster mutation. Triage belongs at triage.hermes.bstein.dev and changes to
|
||||
Atlas are delivered through the titan-iac Git/Flux workflow.
|
||||
This pod has Cassandra-scoped, read-only Kubernetes access for workloads,
|
||||
logs, events, networking, and rollout diagnosis. It cannot read Secrets,
|
||||
exec into pods, or mutate the cluster. Triage belongs at
|
||||
triage.hermes.bstein.dev and all Atlas changes are delivered through the
|
||||
titan-iac Git/Flux workflow.
|
||||
START-HERE.md: |
|
||||
# Agent Hermes
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ spec:
|
||||
ai.bstein.dev/execution: Herdr-supervised Codex and Claude Code
|
||||
ai.bstein.dev/model-policy: Jetson-assisted AUTO routing, low through xhigh, cross-provider fallback
|
||||
ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available
|
||||
ai.bstein.dev/config-rev: "20260809-browser-runtime-complete-resume"
|
||||
ai.bstein.dev/config-rev: "20260809-cassandra-readonly"
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/role: hermes-agent
|
||||
vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens
|
||||
@ -88,6 +88,7 @@ spec:
|
||||
/opt/data/home/.claude \
|
||||
/opt/data/home/.codex \
|
||||
/opt/data/home/.config/herdr \
|
||||
/opt/data/home/.kube \
|
||||
/opt/data/herdr \
|
||||
/opt/data/logs \
|
||||
/opt/data/tools/bin \
|
||||
@ -172,6 +173,14 @@ spec:
|
||||
@anthropic-ai/claude-code@2.1.226
|
||||
touch "${tools}/.cli-versions-0.147.0-2.1.226"
|
||||
fi
|
||||
kubectl_version="$("${tools}/bin/kubectl" version --client --output=json 2>/dev/null || true)"
|
||||
case "${kubectl_version}" in *\"gitVersion\":\"v1.33.3\"*) kubectl_ready=1 ;; *) kubectl_ready=0 ;; esac
|
||||
if [ "${kubectl_ready}" != "1" ]; then
|
||||
curl -fsSL -o "${tools}/bin/kubectl.tmp" https://dl.k8s.io/release/v1.33.3/bin/linux/arm64/kubectl
|
||||
printf '%s %s\n' 3d514dbae5dc8c09f773df0ef0f5d449dfad05b3aca5c96b13565f886df345fd "${tools}/bin/kubectl.tmp" | sha256sum -c -
|
||||
chmod 0755 "${tools}/bin/kubectl.tmp"
|
||||
mv "${tools}/bin/kubectl.tmp" "${tools}/bin/kubectl"
|
||||
fi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsUser: 10000
|
||||
@ -276,6 +285,7 @@ spec:
|
||||
- {name: CLAUDE_CONFIG_DIR, value: /opt/data/home/.claude}
|
||||
- {name: HERDR_CONFIG_PATH, value: /opt/data/home/.config/herdr/config.toml}
|
||||
- {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock}
|
||||
- {name: KUBECONFIG, value: /opt/data/home/.kube/config}
|
||||
- {name: PATH, value: /opt/data/tools/bin:/opt/data/home/.local/bin:/opt/hermes/.venv/bin:/opt/hermes/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin}
|
||||
- {name: PLAYWRIGHT_BROWSERS_PATH, value: /opt/hermes/.playwright}
|
||||
- {name: AGENT_BROWSER_EXECUTABLE_PATH, value: /opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell}
|
||||
@ -292,6 +302,7 @@ spec:
|
||||
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
||||
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
||||
- {name: coordinator, mountPath: /opt/data/home/.local/bin/herdr-dispatch, subPath: herdr_dispatch.py, readOnly: true}
|
||||
- {name: kubeconfig, mountPath: /opt/data/home/.kube/config, subPath: config, readOnly: true}
|
||||
- {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true}
|
||||
startupProbe:
|
||||
tcpSocket: {port: api}
|
||||
@ -480,6 +491,7 @@ spec:
|
||||
--env HERMES_HOME=/opt/data \
|
||||
--env HERMES_AUTH_FILE=/shared-auth/auth.json \
|
||||
--env HOME=/opt/data/home \
|
||||
--env KUBECONFIG=/opt/data/home/.kube/config \
|
||||
--env PYTHONPATH=/opt/hermes \
|
||||
--env PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright \
|
||||
--env AGENT_BROWSER_EXECUTABLE_PATH=/opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell \
|
||||
@ -519,6 +531,7 @@ spec:
|
||||
- {name: CLAUDE_CONFIG_DIR, value: /opt/data/home/.claude}
|
||||
- {name: HERDR_CONFIG_PATH, value: /opt/data/home/.config/herdr/config.toml}
|
||||
- {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock}
|
||||
- {name: KUBECONFIG, value: /opt/data/home/.kube/config}
|
||||
- {name: PYTHONPATH, value: /opt/hermes}
|
||||
- {name: PATH, value: /opt/data/tools/bin:/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin}
|
||||
- {name: PLAYWRIGHT_BROWSERS_PATH, value: /opt/hermes/.playwright}
|
||||
@ -536,6 +549,7 @@ spec:
|
||||
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
|
||||
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
|
||||
- {name: coordinator, mountPath: /opt/data/home/.local/bin/herdr-dispatch, subPath: herdr_dispatch.py, readOnly: true}
|
||||
- {name: kubeconfig, mountPath: /opt/data/home/.kube/config, subPath: config, readOnly: true}
|
||||
- {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true}
|
||||
resources:
|
||||
requests: {cpu: 50m, memory: 128Mi}
|
||||
@ -577,6 +591,10 @@ spec:
|
||||
configMap:
|
||||
name: hermes-coordinator
|
||||
defaultMode: 0555
|
||||
- name: kubeconfig
|
||||
configMap:
|
||||
name: hermes-agent-kubeconfig
|
||||
defaultMode: 0444
|
||||
- name: auth-patch
|
||||
emptyDir: {}
|
||||
- name: auto-router-plugin
|
||||
|
||||
19
services/hermes/agent-kubeconfig.yaml
Normal file
19
services/hermes/agent-kubeconfig.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
# services/hermes/agent-kubeconfig.yaml
|
||||
apiVersion: v1
|
||||
kind: Config
|
||||
clusters:
|
||||
- name: atlas
|
||||
cluster:
|
||||
server: https://kubernetes.default.svc
|
||||
certificate-authority: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
|
||||
users:
|
||||
- name: hermes-agent
|
||||
user:
|
||||
tokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
contexts:
|
||||
- name: cassandra-readonly
|
||||
context:
|
||||
cluster: atlas
|
||||
user: hermes-agent
|
||||
namespace: cassandra
|
||||
current-context: cassandra-readonly
|
||||
@ -52,6 +52,12 @@ configMapGenerator:
|
||||
- patch_hermes_auth.py=scripts/patch_hermes_auth.py
|
||||
options:
|
||||
disableNameSuffixHash: true
|
||||
- name: hermes-agent-kubeconfig
|
||||
namespace: hermes
|
||||
files:
|
||||
- config=agent-kubeconfig.yaml
|
||||
options:
|
||||
disableNameSuffixHash: true
|
||||
- name: hermes-auto-router-plugin
|
||||
namespace: hermes
|
||||
files:
|
||||
|
||||
@ -108,6 +108,20 @@ spec:
|
||||
app: hermes-model-gate
|
||||
ports:
|
||||
- {protocol: TCP, port: 8080}
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 10.43.0.1/32
|
||||
ports:
|
||||
- {protocol: TCP, port: 443}
|
||||
- to:
|
||||
- ipBlock:
|
||||
cidr: 192.168.22.11/32
|
||||
- ipBlock:
|
||||
cidr: 192.168.22.12/32
|
||||
- ipBlock:
|
||||
cidr: 192.168.22.13/32
|
||||
ports:
|
||||
- {protocol: TCP, port: 6443}
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
|
||||
@ -343,6 +343,53 @@ def test_agent_mounts_auto_router_into_both_hermes_runtimes():
|
||||
assert volume["configMap"]["name"] == "hermes-auto-router-plugin"
|
||||
|
||||
|
||||
def test_agent_has_cassandra_readonly_kubernetes_context():
|
||||
deployment = yaml.safe_load((HERMES / "agent-deployment.yaml").read_text())
|
||||
pod = deployment["spec"]["template"]["spec"]
|
||||
containers = {container["name"]: container for container in pod["containers"]}
|
||||
|
||||
installer = next(
|
||||
container
|
||||
for container in pod["initContainers"]
|
||||
if container["name"] == "install-agent-tools"
|
||||
)
|
||||
install_command = installer["command"][-1]
|
||||
assert "v1.33.3/bin/linux/arm64/kubectl" in install_command
|
||||
assert "3d514dbae5dc8c09f773df0ef0f5d449" in install_command
|
||||
|
||||
for name in ("hermes", "herdr-server"):
|
||||
env = {item["name"]: item["value"] for item in containers[name]["env"]}
|
||||
assert env["KUBECONFIG"] == "/opt/data/home/.kube/config"
|
||||
mounts = {
|
||||
mount["mountPath"]: mount for mount in containers[name]["volumeMounts"]
|
||||
}
|
||||
assert mounts["/opt/data/home/.kube/config"]["name"] == "kubeconfig"
|
||||
assert mounts["/opt/data/home/.kube/config"]["readOnly"] is True
|
||||
|
||||
config = yaml.safe_load((HERMES / "agent-kubeconfig.yaml").read_text())
|
||||
assert config["current-context"] == "cassandra-readonly"
|
||||
assert config["contexts"][0]["context"]["namespace"] == "cassandra"
|
||||
assert config["users"][0]["user"] == {
|
||||
"tokenFile": "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
}
|
||||
|
||||
rbac_path = HERMES.parent / "cassandra" / "hermes-agent-rbac.yaml"
|
||||
documents = [document for document in yaml.safe_load_all(rbac_path.read_text()) if document]
|
||||
role = next(document for document in documents if document["kind"] == "Role")
|
||||
binding = next(document for document in documents if document["kind"] == "RoleBinding")
|
||||
assert role["metadata"]["namespace"] == "cassandra"
|
||||
assert binding["metadata"]["namespace"] == "cassandra"
|
||||
assert binding["subjects"] == [
|
||||
{"kind": "ServiceAccount", "name": "hermes-agent", "namespace": "hermes"}
|
||||
]
|
||||
assert "secrets" not in {
|
||||
resource for rule in role["rules"] for resource in rule["resources"]
|
||||
}
|
||||
assert {
|
||||
verb for rule in role["rules"] for verb in rule["verbs"]
|
||||
} == {"get", "list", "watch"}
|
||||
|
||||
|
||||
def test_agent_reconnect_renders_complete_conversation_history():
|
||||
documents = [
|
||||
document
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user