feat(paperless-ai): mint the LLM token with a machine key, not an init container
The init container fetched a token once at pod start with a client_credentials grant against a web app. A web app has no such grant, so the fetch could not work; and a token fetched once is dead within the hour while the pod runs for weeks. llm-token-vendor runs beside the app. It signs a machine-key assertion for the paperless-llm service account, exchanges it for a token, and rewrites /app/data/llm_token.txt atomically at 80% of the token lifetime. The vendor image keeps using CUSTOM_API_KEY_FILE, so it needs no change and no OIDC support. The key is re-read on every mint, so a rotation lands without a restart: publish the new key, the next mint uses it, retire the old one. paperless-llm is a separate service account from poimen-llm, so revoking one caller leaves the other working.
This commit is contained in:
@@ -36,53 +36,43 @@ spec:
|
||||
- name: paperless-ai-env-config
|
||||
configMap:
|
||||
name: paperless-ai-env
|
||||
initContainers:
|
||||
- name: fetch-llm-token
|
||||
image: curlimages/curl:8.12.0
|
||||
securityContext:
|
||||
runAsUser: 0
|
||||
fsGroup: 0
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "[init] Fetching JWT token from Authentik for paperless-ai-agent..."
|
||||
|
||||
# Get JWT token via client_credentials grant
|
||||
# Client secret sourced from environment (injected from paperless-ai-config Secret)
|
||||
TOKEN_RESPONSE=$(curl -s -X POST https://zitadel.riotpiao.com/oauth/v2/token \
|
||||
-d "grant_type=client_credentials" \
|
||||
-d "client_id=396757803559027071" \
|
||||
-d "client_secret=${LLM_AUTH_CLIENT_SECRET}" \
|
||||
-d "scope=openid llm:inference" 2>/dev/null)
|
||||
|
||||
# Extract token from JSON response using sed (handles spaces after colons)
|
||||
TOKEN=$(echo "$TOKEN_RESPONSE" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "[error] Failed to get token. Response: $TOKEN_RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Store token in file for main container to read
|
||||
# Write to both locations for compatibility
|
||||
mkdir -p /tmp/llm-token /app/data 2>/dev/null || true
|
||||
echo "$TOKEN" | tee /tmp/llm-token/llm_token.txt > /dev/null 2>&1
|
||||
echo "$TOKEN" > /app/data/llm_token.txt 2>/dev/null || true
|
||||
echo "[init] Token fetched and stored"
|
||||
[ -f /tmp/llm-token/llm_token.txt ] && echo " -> /tmp/llm-token/llm_token.txt"
|
||||
[ -f /app/data/llm_token.txt ] && echo " -> /app/data/llm_token.txt"
|
||||
env:
|
||||
- name: LLM_AUTH_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: paperless-ai-config
|
||||
key: LLM_AUTH_CLIENT_SECRET
|
||||
volumeMounts:
|
||||
- name: paperless-ai-data
|
||||
mountPath: /app/data
|
||||
- name: llm-key
|
||||
secret:
|
||||
secretName: paperless-llm-key
|
||||
defaultMode: 0400
|
||||
# No init container. The token used to be fetched once at pod start with a
|
||||
# client_credentials grant against a web app, which cannot work: a web app has
|
||||
# no such grant, and even a token that was minted would be dead within the hour
|
||||
# while this pod runs for weeks.
|
||||
#
|
||||
# llm-token-vendor runs beside the app instead. It signs a machine-key assertion,
|
||||
# exchanges it for a token, and rewrites /app/data/llm_token.txt at 80% of the
|
||||
# token lifetime. The vendor image keeps reading CUSTOM_API_KEY_FILE unchanged.
|
||||
# The key is re-read on every mint, so a rotation lands without a restart.
|
||||
containers:
|
||||
- name: llm-token-vendor
|
||||
image: forgejo.riotpiao.com/riotpiao-poimen/llm-token-vendor:0.1.0
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- --token-url=https://zitadel.riotpiao.com/oauth/v2/token
|
||||
- --issuer=https://zitadel.riotpiao.com
|
||||
- --user-id=397072668735570071
|
||||
- --key-id=397072674272051351
|
||||
- --key-file=/var/run/llm-key/key
|
||||
- --scope=openid urn:zitadel:iam:org:project:id:396757796898471937:aud
|
||||
- --out=/app/data/llm_token.txt
|
||||
volumeMounts:
|
||||
- {name: paperless-ai-data, mountPath: /app/data}
|
||||
- {name: llm-key, mountPath: /var/run/llm-key, readOnly: true}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities: {drop: ["ALL"]}
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
seccompProfile: {type: RuntimeDefault}
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 32Mi}
|
||||
limits: {cpu: 100m, memory: 64Mi}
|
||||
- name: paperless-ai
|
||||
image: clusterzx/paperless-ai:latest
|
||||
env:
|
||||
|
||||
Reference in New Issue
Block a user