Skip to content

Commit 976378d

Browse files
committed
feat(panda-chat)!: Authentik service-account bot identity via client_credentials
Replace the seeded-credentials bot identity (credentials.panda.credentialsJson / credentialsFile + seed-panda-creds initContainer) with an Authentik service account: credentials.panda.botUsername / botToken materialize as PANDA_BOT_USERNAME / PANDA_BOT_TOKEN, and panda-config.yaml now configures proxy.auth mode client_credentials against the Authentik panda-proxy application issuer (default panda.issuerUrl switches from Dex to Authentik; the trailing slash is part of the issuer). panda-server mints access tokens on demand and keeps them in memory — nothing is written under credentials/. Both bot values are required when panda.enabled, so a values gap fails at template time instead of producing a half-authenticated pod. Requires a hermes-agent-panda image carrying panda with client_credentials support. Part of chat docs/identity-and-attribution-plan.md (Phase 3).
1 parent 75777a3 commit 976378d

7 files changed

Lines changed: 49 additions & 61 deletions

File tree

charts/panda-chat/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: panda-chat
33
description: AI chat for an Ethereum devnet — an Open-WebUI front end backed by a NousResearch Hermes agent wired to the `panda` CLI, giving anyone access to devnet analytics (Xatu/Prometheus/Loki/Dora/Ethnode via panda-proxy), account funding (powfaucet) and join-the-devnet helpers.
44
home: https://github.com/ethpandaops/chat
55
type: application
6-
version: 0.1.0
6+
version: 0.2.0
77
# Hermes Agent upstream version baked into the panda-overlay image.
88
appVersion: "0.11.0"
99
keywords:

charts/panda-chat/README.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# panda-chat
33

4-
![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.11.0](https://img.shields.io/badge/AppVersion-0.11.0-informational?style=flat-square)
4+
![Version: 0.2.0](https://img.shields.io/badge/Version-0.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.11.0](https://img.shields.io/badge/AppVersion-0.11.0-informational?style=flat-square)
55

66
AI chat for an Ethereum devnet — an Open-WebUI front end backed by a NousResearch Hermes agent wired to the `panda` CLI, giving anyone access to devnet analytics (Xatu/Prometheus/Loki/Dora/Ethnode via panda-proxy), account funding (powfaucet) and join-the-devnet helpers.
77

@@ -28,8 +28,11 @@ AI chat for an Ethereum devnet — an Open-WebUI front end backed by a NousResea
2828

2929
When `panda.enabled` is true the agent pod runs `panda-server` + `dockerd`
3030
alongside Hermes and is **privileged** (dockerd needs root). The bot identity
31-
for the proxy is provisioned once (GitHub bot user + `panda auth login`) and
32-
its credentials are supplied via `credentials.panda.*`.
31+
for the proxy is an Authentik **service account** (e.g. `panda-chat-svc` with a
32+
non-expiring app password) supplied via `credentials.panda.botUsername` /
33+
`credentials.panda.botToken`. panda-server mints proxy access tokens on demand
34+
with the OAuth2 `client_credentials` grant and keeps them in memory only — no
35+
seeded credential files, no refresh-token rotation.
3336

3437
## Access control
3538

@@ -64,9 +67,10 @@ SSO** (no password), auto-provisioning the user on first visit.
6467
6568
Per-user identity is also propagated to the agent: the Open-WebUI image
6669
(`ethpandaops/open-webui-cf`) forwards `Cf-Access-Jwt-Assertion` upstream so
67-
Hermes sees the individual user (per-user auth on downstream resources + Langfuse
68-
attribution). On devnets the bal-devnets ansible template wires the trusted-header
69-
config from a single toggle — see `chat.yaml.j2`.
70+
Hermes can attribute traffic to the individual user (Langfuse `user_id`,
71+
`X-Panda-On-Behalf-Of` audit header). Authentication to panda-proxy itself is
72+
always the bot service account. On devnets the bal-devnets ansible template
73+
wires the trusted-header config from a single toggle — see `chat.yaml.j2`.
7074

7175
## Image
7276

@@ -104,8 +108,8 @@ open-webui:
104108
| credentials.langfuse.publicKey | string | `""` | Langfuse public key (pk-lf-...) |
105109
| credentials.langfuse.secretKey | string | `""` | Langfuse secret key (sk-lf-...) |
106110
| credentials.llmApiKey | string | `""` | The LLM API key value (materialized into the Secret under `llm.apiKeyEnv`) |
107-
| credentials.panda.credentialsFile | string | `""` | Filename panda expects under credentials/ (derived from issuer+client hash) |
108-
| credentials.panda.credentialsJson | string | `""` | panda-server bot credentials JSON (contents of credentials/<hash>.json) |
111+
| credentials.panda.botToken | string | `""` | Authentik app-password token for the bot service account (client_credentials grant; minted tokens stay in memory). Required when `panda.enabled`. |
112+
| credentials.panda.botUsername | string | `""` | Authentik service-account username for the bot (e.g. `panda-chat-svc`). Required when `panda.enabled`. |
109113
| devnetTools.faucet.enabled | bool | `true` | Enable the faucet (account funding) skill |
110114
| devnetTools.faucet.url | string | `""` | powfaucet base URL |
111115
| devnetTools.join.configUrl | string | `""` | Base config service URL (serves /cl/config.yaml, /el/enodes.txt, etc.) |
@@ -149,9 +153,9 @@ open-webui:
149153
| open-webui.sso.trustedHeader.nameHeader | string | `""` | |
150154
| open-webui.websocket.enabled | bool | `false` | |
151155
| open-webui.websocket.redis.enabled | bool | `false` | |
152-
| panda.clientId | string | `"panda-proxy"` | OIDC client id at the proxy |
156+
| panda.clientId | string | `"panda-proxy"` | OAuth client id at the proxy |
153157
| panda.enabled | bool | `true` | Enable the panda sidecar processes + privileged pod |
154-
| panda.issuerUrl | string | `"https://dex.primary.production.platform.ethpandaops.io"` | OIDC issuer (Dex) the bot identity authenticates against |
158+
| panda.issuerUrl | string | `"https://authentik.analytics.production.platform.ethpandaops.io/application/o/panda-proxy/"` | Authentik application issuer the bot service account mints client_credentials tokens against (the trailing slash is part of the issuer — keep it) |
155159
| panda.proxyUrl | string | `"https://panda-proxy.analytics.production.platform.ethpandaops.io"` | Hosted panda-proxy URL (analytics data plane) |
156160
| panda.sandboxImage | string | `"ethpandaops/panda:sandbox-v0.31.0"` | Sandbox container image panda-server spawns for Python execution |
157161
| panda.storageDriver | string | `"overlay2"` | dockerd storage driver (overlay2; set to vfs if overlayfs is unavailable in-pod) |

charts/panda-chat/README.md.gotmpl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525

2626
When `panda.enabled` is true the agent pod runs `panda-server` + `dockerd`
2727
alongside Hermes and is **privileged** (dockerd needs root). The bot identity
28-
for the proxy is provisioned once (GitHub bot user + `panda auth login`) and
29-
its credentials are supplied via `credentials.panda.*`.
28+
for the proxy is an Authentik **service account** (e.g. `panda-chat-svc` with a
29+
non-expiring app password) supplied via `credentials.panda.botUsername` /
30+
`credentials.panda.botToken`. panda-server mints proxy access tokens on demand
31+
with the OAuth2 `client_credentials` grant and keeps them in memory only — no
32+
seeded credential files, no refresh-token rotation.
3033

3134
## Access control
3235

@@ -61,9 +64,10 @@ SSO** (no password), auto-provisioning the user on first visit.
6164

6265
Per-user identity is also propagated to the agent: the Open-WebUI image
6366
(`ethpandaops/open-webui-cf`) forwards `Cf-Access-Jwt-Assertion` upstream so
64-
Hermes sees the individual user (per-user auth on downstream resources + Langfuse
65-
attribution). On devnets the bal-devnets ansible template wires the trusted-header
66-
config from a single toggle — see `chat.yaml.j2`.
67+
Hermes can attribute traffic to the individual user (Langfuse `user_id`,
68+
`X-Panda-On-Behalf-Of` audit header). Authentication to panda-proxy itself is
69+
always the bot service account. On devnets the bal-devnets ansible template
70+
wires the trusted-header config from a single toggle — see `chat.yaml.j2`.
6771

6872
## Image
6973

charts/panda-chat/templates/configmap.yaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ data:
2727
- observability/langfuse
2828
{{- end }}
2929
{{- if .Values.panda.enabled }}
30-
# panda-server config — seeded to /opt/data/.config/panda/config.yaml (creds live in the Secret).
30+
# panda-server config — seeded to /opt/data/.config/panda/config.yaml. The
31+
# bot identity (PANDA_BOT_USERNAME / PANDA_BOT_TOKEN) lives in the Secret and
32+
# is substituted from the environment by panda's config loader; access tokens
33+
# are minted via client_credentials and never touch the PVC.
3134
panda-config.yaml: |
3235
server:
3336
host: "127.0.0.1"
@@ -46,7 +49,9 @@ data:
4649
proxy:
4750
url: {{ .Values.panda.proxyUrl | quote }}
4851
auth:
49-
mode: "oidc"
52+
mode: "client_credentials"
5053
issuer_url: {{ .Values.panda.issuerUrl | quote }}
5154
client_id: {{ .Values.panda.clientId | quote }}
55+
username: "${PANDA_BOT_USERNAME}"
56+
password: "${PANDA_BOT_TOKEN}"
5257
{{- end }}

charts/panda-chat/templates/deployment.yaml

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ spec:
4545
[ -f /data/config.yaml ] && old=$(sha256sum /data/config.yaml | cut -d' ' -f1)
4646
[ "$new" != "$old" ] && cp /config-src/config.yaml /data/config.yaml || true
4747
{{- if .Values.panda.enabled }}
48-
mkdir -p /data/.config/panda/credentials /data/panda-storage
48+
mkdir -p /data/.config/panda /data/panda-storage
4949
cp /config-src/panda-config.yaml /data/.config/panda/config.yaml
5050
{{- end }}
5151
volumeMounts:
@@ -58,33 +58,6 @@ spec:
5858
allowPrivilegeEscalation: false
5959
capabilities:
6060
drop: [ALL]
61-
{{- if .Values.panda.enabled }}
62-
# Seed panda bot credentials from the Secret (refreshed every pod start).
63-
- name: seed-panda-creds
64-
image: busybox:1.37
65-
command: ["sh", "-c"]
66-
args:
67-
- |
68-
set -eu
69-
mkdir -p /data/.config/panda/credentials
70-
if [ -n "${PANDA_CREDENTIALS_JSON:-}" ] && [ -n "${PANDA_CREDENTIALS_FILE:-}" ]; then
71-
printf '%s' "$PANDA_CREDENTIALS_JSON" \
72-
> "/data/.config/panda/credentials/${PANDA_CREDENTIALS_FILE}"
73-
fi
74-
envFrom:
75-
- secretRef:
76-
name: {{ include "panda-chat.secretName" . }}
77-
optional: true
78-
volumeMounts:
79-
- {name: data, mountPath: /data}
80-
securityContext:
81-
runAsNonRoot: true
82-
runAsUser: 10000
83-
runAsGroup: 10000
84-
allowPrivilegeEscalation: false
85-
capabilities:
86-
drop: [ALL]
87-
{{- end }}
8861
containers:
8962
- name: hermes
9063
image: {{ include "panda-chat.image" . }}

charts/panda-chat/templates/secret.yaml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{{/*
22
Holds: API_SERVER_KEY (Hermes bearer, generated once and preserved),
3-
<llm.apiKeyEnv> (model key), PANDA_CREDENTIALS_* (panda bot creds)
4-
and HERMES_LANGFUSE_* (tracing keys).
3+
<llm.apiKeyEnv> (model key), PANDA_BOT_USERNAME / PANDA_BOT_TOKEN (the
4+
Authentik service-account identity panda-server mints client_credentials
5+
tokens with) and HERMES_LANGFUSE_* (tracing keys).
56
*/}}
67
{{- $secretName := include "panda-chat.secretName" . -}}
78
{{- $existing := lookup "v1" "Secret" .Release.Namespace $secretName -}}
@@ -24,12 +25,8 @@ stringData:
2425
{{ $.Values.llm.apiKeyEnv }}: {{ . | quote }}
2526
{{- end }}
2627
{{- if .Values.panda.enabled }}
27-
{{- with .Values.credentials.panda.credentialsJson }}
28-
PANDA_CREDENTIALS_JSON: {{ . | quote }}
29-
{{- end }}
30-
{{- with .Values.credentials.panda.credentialsFile }}
31-
PANDA_CREDENTIALS_FILE: {{ . | quote }}
32-
{{- end }}
28+
PANDA_BOT_USERNAME: {{ required "credentials.panda.botUsername is required when panda.enabled" .Values.credentials.panda.botUsername | quote }}
29+
PANDA_BOT_TOKEN: {{ required "credentials.panda.botToken is required when panda.enabled" .Values.credentials.panda.botToken | quote }}
3330
{{- end }}
3431
{{- if .Values.langfuse.enabled }}
3532
{{- with .Values.credentials.langfuse.publicKey }}

charts/panda-chat/values.yaml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ panda:
4949
enabled: true
5050
# -- Hosted panda-proxy URL (analytics data plane)
5151
proxyUrl: "https://panda-proxy.analytics.production.platform.ethpandaops.io"
52-
# -- OIDC issuer (Dex) the bot identity authenticates against
53-
issuerUrl: "https://dex.primary.production.platform.ethpandaops.io"
54-
# -- OIDC client id at the proxy
52+
# -- Authentik application issuer the bot service account mints
53+
# client_credentials tokens against (the trailing slash is part of the
54+
# issuer — keep it)
55+
issuerUrl: "https://authentik.analytics.production.platform.ethpandaops.io/application/o/panda-proxy/"
56+
# -- OAuth client id at the proxy
5557
clientId: "panda-proxy"
5658
# -- Sandbox container image panda-server spawns for Python execution
5759
sandboxImage: "ethpandaops/panda:sandbox-v0.31.0"
@@ -90,10 +92,13 @@ credentials:
9092
# -- The LLM API key value (materialized into the Secret under `llm.apiKeyEnv`)
9193
llmApiKey: ""
9294
panda:
93-
# -- panda-server bot credentials JSON (contents of credentials/<hash>.json)
94-
credentialsJson: ""
95-
# -- Filename panda expects under credentials/ (derived from issuer+client hash)
96-
credentialsFile: ""
95+
# -- Authentik service-account username for the bot (e.g. `panda-chat-svc`).
96+
# Required when `panda.enabled`.
97+
botUsername: ""
98+
# -- Authentik app-password token for the bot service account
99+
# (client_credentials grant; minted tokens stay in memory). Required when
100+
# `panda.enabled`.
101+
botToken: ""
97102
langfuse:
98103
# -- Langfuse public key (pk-lf-...)
99104
publicKey: ""

0 commit comments

Comments
 (0)