diff --git a/k8s/bases/infrastructure/controllers/coroot/cilium-network-policy-allow-gateway-to-coroot-mcp.yaml b/k8s/bases/infrastructure/controllers/coroot/cilium-network-policy-allow-gateway-to-coroot-mcp.yaml new file mode 100644 index 0000000000..af9d2be9e1 --- /dev/null +++ b/k8s/bases/infrastructure/controllers/coroot/cilium-network-policy-allow-gateway-to-coroot-mcp.yaml @@ -0,0 +1,24 @@ +# The Gateway reaches the Coroot app pod directly for exactly one route: the +# /mcp rule in http-route.yaml, which bypasses oauth2-proxy because Coroot's MCP +# endpoint authenticates every request with a bearer token itself. Only the +# Coroot app pod is selected, so no other observability workload becomes +# reachable from the Gateway. Every other path still arrives through oauth2-proxy +# and the auth-proxy, which allow-coroot admits. +--- +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: allow-gateway-to-coroot-mcp + namespace: observability +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/part-of: coroot + app.kubernetes.io/component: coroot + ingress: + - fromEntities: + - ingress + toPorts: + - ports: + - port: "8080" + protocol: TCP diff --git a/k8s/bases/infrastructure/controllers/coroot/http-route.yaml b/k8s/bases/infrastructure/controllers/coroot/http-route.yaml index 998e27fc5c..5578c9b421 100644 --- a/k8s/bases/infrastructure/controllers/coroot/http-route.yaml +++ b/k8s/bases/infrastructure/controllers/coroot/http-route.yaml @@ -39,6 +39,23 @@ spec: hostnames: - observability.${domain} rules: + # Coroot's MCP endpoint authenticates every request itself — a bearer OAuth + # token or a service-account API key, never the anonymous role — so agents + # reach exactly /mcp directly, without the browser SSO they cannot complete. + # Every other path still goes through oauth2-proxy. + - matches: + - path: + type: Exact + value: /mcp + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + set: + - name: Strict-Transport-Security + value: max-age=63072000; includeSubDomains; preload + backendRefs: + - name: coroot-coroot + port: 8080 - filters: - type: ResponseHeaderModifier responseHeaderModifier: diff --git a/k8s/bases/infrastructure/controllers/coroot/kustomization.yaml b/k8s/bases/infrastructure/controllers/coroot/kustomization.yaml index e57d2113cb..cbd07620b3 100644 --- a/k8s/bases/infrastructure/controllers/coroot/kustomization.yaml +++ b/k8s/bases/infrastructure/controllers/coroot/kustomization.yaml @@ -12,6 +12,7 @@ resources: - helm-release.yaml - http-route.yaml - cilium-network-policy.yaml + - cilium-network-policy-allow-gateway-to-coroot-mcp.yaml - cron-job.yaml # Heartbeat monitor URL, held in a Secret rather than substituted into the # CronJob spec above (see that file and secret-cluster-heartbeat.yaml): diff --git a/k8s/bases/infrastructure/coroot/coroot.yaml b/k8s/bases/infrastructure/coroot/coroot.yaml index c7c1c09bd5..43ae2a0de5 100644 --- a/k8s/bases/infrastructure/coroot/coroot.yaml +++ b/k8s/bases/infrastructure/coroot/coroot.yaml @@ -30,7 +30,9 @@ spec: # cluster-wide, `--reload-strategy=annotations`) watches the named Secret and # rolls the server's pods on change; the annotation goes on the pod template via # podAnnotations, matching the platform's existing convention (auth-proxy, - # oauth2-proxy, coredns). + # oauth2-proxy, coredns). It also watches coroot-mcp-agent: the operator passes + # the agents' MCP service-account key to Coroot through an env var too, so a + # rotated key takes effect only after a restart. # # The DNS-policy rollout token is deliberately repeated on every Coroot-owned # pod template below. set-observability-dns-ndots is admission-only, so this @@ -39,7 +41,7 @@ spec: # before this rollout. Change the token only after a new admission-time DNS # policy is Ready; combining both changes can recreate pods under the old rule. podAnnotations: - secret.reloader.stakater.com/reload: coroot-db-app + secret.reloader.stakater.com/reload: coroot-db-app,coroot-mcp-agent platform.devantler.tech/dns-policy-rollout: 2026-09-30-ndots-3 # COMPONENT IMAGE PINS. The operator does NOT bake a fixed tag into its @@ -77,6 +79,22 @@ spec: # as Admin, mirroring the old single-operator "everyone -> Grafana Admin". authAnonymousRole: Admin + # A Viewer service account for the agents' MCP access. Coroot's /mcp endpoint + # never uses the anonymous role above: every request needs a bearer token (an + # OAuth token or a service-account API key), and every tool call is checked + # against that identity's role. A Viewer can read topology, alerts, incidents, + # traces, metrics and logs but cannot resolve alerts. The operator generates + # the key into the coroot-mcp-agent Secret when it is missing; nothing about + # the key is committed. http-route.yaml routes /mcp past oauth2-proxy for it. + serviceAccounts: + - name: agentic-engineer + role: Viewer + apiKeys: + - description: Agentic Engineer MCP access (agent-plugins coroot server) + keySecret: + name: coroot-mcp-agent + key: api-key + # Metric resolution. The operator default is 15s; 30s halves the bundled # Prometheus ingest, the node-agent scrape volume, and Coroot's continuous # query load on a memory-constrained cluster, while staying at the diff --git a/scripts/capture-openbao-replacement/README.md b/scripts/capture-openbao-replacement/README.md index 7331e3d857..9e622146b4 100644 --- a/scripts/capture-openbao-replacement/README.md +++ b/scripts/capture-openbao-replacement/README.md @@ -16,7 +16,7 @@ workflow runs it in `preflight` mode and then runs both judges for the server it The expected servers and release come from the reviewed render, never from the cluster. | Exit | Line | Meaning | -|---|---|---| +| --- | --- | --- | | 0 | `OPENBAO_REPLACEMENT_EVIDENCE=CAPTURED` | `health.json` and `quorum.json` are in `--out`, readable only by the owner. | | 2 | `OPENBAO_REPLACEMENT_EVIDENCE=UNVERIFIED stage=` | A read failed, so nothing was written. Invalid arguments also exit 2. | diff --git a/scripts/tests/test-coroot-reservation-refresh.sh b/scripts/tests/test-coroot-reservation-refresh.sh index fb96436259..1ce5e84f72 100755 --- a/scripts/tests/test-coroot-reservation-refresh.sh +++ b/scripts/tests/test-coroot-reservation-refresh.sh @@ -20,7 +20,7 @@ kubectl kustomize "${root_dir}/k8s/providers/hetzner/infrastructure" >"${scratch yq -e ' select(.kind == "Coroot" and .metadata.name == "coroot") | .spec.podAnnotations."platform.devantler.tech/vpa-recommendation-rollout" == "2026-10-11-coroot-reservation-refresh" and - .spec.podAnnotations."secret.reloader.stakater.com/reload" == "coroot-db-app" and + .spec.podAnnotations."secret.reloader.stakater.com/reload" == "coroot-db-app,coroot-mcp-agent" and .spec.replicas == 2 and .spec.resources.requests.memory == "1Gi" and .spec.resources.limits.memory == "6Gi" and