From da7a9788c4ce8545610853449b34d7b01e9ea097 Mon Sep 17 00:00:00 2001 From: jenkins Date: Tue, 11 Aug 2026 01:51:30 -0300 Subject: [PATCH] hermes: stabilize oauth sessions and enable images --- services/hermes/agent-deployment.yaml | 6 +- services/hermes/chat-configmap.yaml | 3 + services/hermes/chat-statefulset.yaml | 2 +- services/hermes/kustomization.yaml | 1 + services/hermes/networkpolicy.yaml | 6 ++ services/hermes/oauth-session-store.yaml | 103 ++++++++++++++++++++++ services/hermes/oauth2-proxy.yaml | 10 ++- testing/tests/test_hermes_chat_quality.py | 12 +++ testing/tests/test_hermes_cli_lanes.py | 6 ++ 9 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 services/hermes/oauth-session-store.yaml diff --git a/services/hermes/agent-deployment.yaml b/services/hermes/agent-deployment.yaml index 1b9ef238b..63423a2cb 100644 --- a/services/hermes/agent-deployment.yaml +++ b/services/hermes/agent-deployment.yaml @@ -24,7 +24,7 @@ spec: ai.bstein.dev/execution: Hermes Kanban with durable direct Codex and Claude Code CLI workers 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: "20260811-codex-image-broker-port" + ai.bstein.dev/config-rev: "20260811-redis-sessions" 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 @@ -494,8 +494,10 @@ spec: - --cookie-csrf-expire=10m - --cookie-csrf-per-request=true - --cookie-csrf-per-request-limit=8 - - --cookie-refresh=1h + - --cookie-refresh=19m - --cookie-expire=8h + - --session-store-type=redis + - --redis-connection-url=redis://hermes-oauth-sessions.hermes.svc.cluster.local:6379/0 - --upstream=http://127.0.0.1:7681/terminal/ - --upstream=http://127.0.0.1:9119/ - --http-address=0.0.0.0:4180 diff --git a/services/hermes/chat-configmap.yaml b/services/hermes/chat-configmap.yaml index 5b01341a7..597e930e2 100644 --- a/services/hermes/chat-configmap.yaml +++ b/services/hermes/chat-configmap.yaml @@ -44,6 +44,9 @@ data: image_gen: provider: atlas-broker model: gpt-image-2-high + plugins: + enabled: + - atlas-broker model_catalog: enabled: true ttl_hours: 1 diff --git a/services/hermes/chat-statefulset.yaml b/services/hermes/chat-statefulset.yaml index 8d09aa5ca..cb3bc542f 100644 --- a/services/hermes/chat-statefulset.yaml +++ b/services/hermes/chat-statefulset.yaml @@ -28,7 +28,7 @@ spec: ai.bstein.dev/role: isolated-user-chat ai.bstein.dev/isolation: one Hermes process and PVC per Keycloak subject ai.bstein.dev/model-policy: uniform automatic policy with per-user overrides - ai.bstein.dev/config-rev: "20260811-private-image-studio" + ai.bstein.dev/config-rev: "20260811-image-plugin-enabled" vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: hermes-chat vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens diff --git a/services/hermes/kustomization.yaml b/services/hermes/kustomization.yaml index 778ebec0e..416da66b5 100644 --- a/services/hermes/kustomization.yaml +++ b/services/hermes/kustomization.yaml @@ -12,6 +12,7 @@ resources: - agent-rbac.yaml - pvc.yaml - chat-pvcs.yaml + - oauth-session-store.yaml - model-gate-rbac.yaml - ariadne-handoff-rbac.yaml - model-gate-state.yaml diff --git a/services/hermes/networkpolicy.yaml b/services/hermes/networkpolicy.yaml index c7ee4a942..5c632fb01 100644 --- a/services/hermes/networkpolicy.yaml +++ b/services/hermes/networkpolicy.yaml @@ -382,6 +382,12 @@ spec: - {protocol: TCP, port: 7681} - {protocol: TCP, port: 8787} - {protocol: TCP, port: 8080} + - to: + - podSelector: + matchLabels: + app: hermes-oauth-sessions + ports: + - {protocol: TCP, port: 6379} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy diff --git a/services/hermes/oauth-session-store.yaml b/services/hermes/oauth-session-store.yaml new file mode 100644 index 000000000..3692a33db --- /dev/null +++ b/services/hermes/oauth-session-store.yaml @@ -0,0 +1,103 @@ +# services/hermes/oauth-session-store.yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: hermes-oauth-sessions + namespace: hermes +spec: + accessModes: [ReadWriteOnce] + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: Service +metadata: + name: hermes-oauth-sessions + namespace: hermes +spec: + selector: + app: hermes-oauth-sessions + ports: + - {name: redis, port: 6379, targetPort: redis} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hermes-oauth-sessions + namespace: hermes + labels: + app: hermes-oauth-sessions +spec: + replicas: 1 + revisionHistoryLimit: 2 + strategy: + type: Recreate + selector: + matchLabels: + app: hermes-oauth-sessions + template: + metadata: + labels: + app: hermes-oauth-sessions + spec: + securityContext: + fsGroup: 999 + seccompProfile: + type: RuntimeDefault + containers: + - name: redis + image: redis:7.4.1-alpine@sha256:c1e88455c85225310bbea54816e9c3f4b5295815e6dbf80c34d40afc6df28275 + args: [redis-server, --appendonly, "yes", --appendfsync, everysec, --save, "60", "1"] + ports: + - {name: redis, containerPort: 6379} + readinessProbe: + exec: {command: [redis-cli, ping]} + initialDelaySeconds: 3 + periodSeconds: 5 + livenessProbe: + exec: {command: [redis-cli, ping]} + initialDelaySeconds: 15 + periodSeconds: 15 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + seccompProfile: + type: RuntimeDefault + resources: + requests: {cpu: 25m, memory: 64Mi} + limits: {cpu: 250m, memory: 256Mi} + volumeMounts: + - {name: data, mountPath: /data} + volumes: + - name: data + persistentVolumeClaim: + claimName: hermes-oauth-sessions +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: hermes-oauth-sessions + namespace: hermes +spec: + podSelector: + matchLabels: + app: hermes-oauth-sessions + policyTypes: [Ingress, Egress] + ingress: + - from: + - podSelector: + matchExpressions: + - key: app + operator: In + values: + - hermes-agent + - oauth2-proxy-hermes-chat + - oauth2-proxy-hermes-triage + ports: + - {protocol: TCP, port: 6379} + egress: [] diff --git a/services/hermes/oauth2-proxy.yaml b/services/hermes/oauth2-proxy.yaml index fba9b67ce..24383b3ac 100644 --- a/services/hermes/oauth2-proxy.yaml +++ b/services/hermes/oauth2-proxy.yaml @@ -97,8 +97,10 @@ spec: - --cookie-csrf-expire=10m - --cookie-csrf-per-request=true - --cookie-csrf-per-request-limit=8 - - --cookie-refresh=1h + - --cookie-refresh=19m - --cookie-expire=168h + - --session-store-type=redis + - --redis-connection-url=redis://hermes-oauth-sessions.hermes.svc.cluster.local:6379/2 - --api-route=^/api/ - --api-route=^/health$ - --upstream=http://hermes-triage.hermes.svc.cluster.local:8787 @@ -155,7 +157,7 @@ spec: labels: app: oauth2-proxy-hermes-chat annotations: - ai.bstein.dev/config-rev: "20260809-oauth2-proxy-7-15-canary" + ai.bstein.dev/config-rev: "20260811-redis-sessions" vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/agent-pre-populate-only: "true" vault.hashicorp.com/role: hermes-chat @@ -193,8 +195,10 @@ spec: - --cookie-csrf-expire=10m - --cookie-csrf-per-request=true - --cookie-csrf-per-request-limit=8 - - --cookie-refresh=1h + - --cookie-refresh=19m - --cookie-expire=168h + - --session-store-type=redis + - --redis-connection-url=redis://hermes-oauth-sessions.hermes.svc.cluster.local:6379/1 - --custom-templates-dir=/etc/oauth2-proxy/templates - '--skip-auth-route=GET=^/sw[.]js([?].*)?$' - --api-route=^/api/ diff --git a/testing/tests/test_hermes_chat_quality.py b/testing/tests/test_hermes_chat_quality.py index bad07b9a9..56c06f30a 100644 --- a/testing/tests/test_hermes_chat_quality.py +++ b/testing/tests/test_hermes_chat_quality.py @@ -149,6 +149,12 @@ def test_chat_oauth_allows_stale_service_worker_retirement(): assert "--api-route=^/api/" in args assert "--api-route=^/health$" in args assert "--cookie-expire=168h" in args + assert "--cookie-refresh=19m" in args + assert "--session-store-type=redis" in args + assert any( + arg.startswith("--redis-connection-url=redis://hermes-oauth-sessions.") + for arg in args + ) def test_webui_recovers_auth_and_labels_session_scoped_controls(): @@ -312,6 +318,7 @@ def test_chat_image_generation_uses_private_owner_broker(): "provider": "atlas-broker", "model": "gpt-image-2-high", } + assert config["plugins"]["enabled"] == ["atlas-broker"] statefulset = _documents(HERMES / "chat-statefulset.yaml")[0] pod = statefulset["spec"]["template"]["spec"] @@ -340,6 +347,11 @@ def test_chat_image_generation_uses_private_owner_broker(): ) assert service["spec"]["selector"] == {"app": "hermes-agent"} + oauth_store = _documents(HERMES / "oauth-session-store.yaml") + redis = next(item for item in oauth_store if item["kind"] == "Deployment") + assert redis["spec"]["strategy"]["type"] == "Recreate" + assert "--appendonly" in redis["spec"]["template"]["spec"]["containers"][0]["args"] + policies = _documents(HERMES / "networkpolicy.yaml") agent_policy = next( item for item in policies if item["metadata"]["name"] == "hermes-agent-isolation" diff --git a/testing/tests/test_hermes_cli_lanes.py b/testing/tests/test_hermes_cli_lanes.py index ad8778f36..1ec8022b0 100644 --- a/testing/tests/test_hermes_cli_lanes.py +++ b/testing/tests/test_hermes_cli_lanes.py @@ -673,6 +673,12 @@ def test_agent_root_is_stock_dashboard_and_terminal_is_a_separate_path(): assert dashboard_upstream in args assert args.index(terminal_upstream) < args.index(dashboard_upstream) assert "--pass-host-header=false" in args + assert "--cookie-refresh=19m" in args + assert "--session-store-type=redis" in args + assert any( + arg.startswith("--redis-connection-url=redis://hermes-oauth-sessions.") + for arg in args + ) patch_init = next( item for item in pod["initContainers"]