Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cf189e9
feat(operator): add ClientIDsFromSecret helper for api-key client IDs
dcmcand Jun 26, 2026
f6ecc79
feat(operator): scope API keys per-model via per-route authorization
dcmcand Jun 26, 2026
9986ab2
feat(operator): re-render api-key authorization when keys change
dcmcand Jun 26, 2026
f9b8ac6
docs: describe per-model API-key authorization on the external endpoint
dcmcand Jun 26, 2026
d6aa9ac
docs: fold redundant forwardClientIDHeader bullet into the authorizat…
dcmcand Jun 26, 2026
2f68a9f
fix(operator): drop Host matcher from AIGatewayRoute so models route …
dcmcand Jun 29, 2026
67d45a2
fix(operator): grant EPP RBAC for the inference scheduler's GIE watches
dcmcand Jun 29, 2026
ecd1b96
feat(operator): pool api-key authentication across the shared listener
dcmcand Jun 29, 2026
71fecfe
chore(dev): mock-vllm answers chat completions for end-to-end testing
dcmcand Jun 29, 2026
fa745b7
fix(operator): grant the operator the inference.networking.x-k8s.io p…
dcmcand Jun 30, 2026
91b864f
fix(operator): set explicit vLLM command for the CUDA serving image
dcmcand Jun 30, 2026
6914ac1
fix(key-manager): scope API-key client IDs by model
dcmcand Jun 30, 2026
32ae2e4
style(key-manager): gofmt handler_test.go struct field alignment
dcmcand Jul 7, 2026
5b12f07
chore(docs): exclude architecture.md from content-parity
dcmcand Jul 7, 2026
183ef1f
fix(operator): use constants for authz actions and client-ID header
dcmcand Jul 7, 2026
0fdf5f7
fix(key-manager): make client IDs collision-proof and never reuse liv…
dcmcand Jul 9, 2026
2690f35
feat(operator): allow overriding the vLLM container command per model
dcmcand Jul 9, 2026
c10290d
fix(dev): grant the operator the GIE x-k8s.io RBAC in the dev manifests
dcmcand Jul 9, 2026
fed7131
refactor(operator): consolidate the api-keys Secret watch plumbing
dcmcand Jul 9, 2026
e4bb9a1
docs: record the validated AI Gateway v0.5 routing and client-ID cont…
dcmcand Jul 9, 2026
2c8f010
docs: finish aligning prose with pooled authentication
dcmcand Jul 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions charts/nebari-llm-serving/crds/llmmodel-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,17 @@ spec:
serving:
description: serving configures the vLLM serving layer
properties:
command:
description: |-
command overrides the container command for the vLLM container.
Defaults to the standard vLLM OpenAI-server entrypoint
(python3 -m vllm.entrypoints.openai.api_server), which the default
serving image requires because its entrypoint is the NVIDIA CUDA
wrapper with no default CMD. Set this when a custom serving image
needs a different launcher; vllmArgs are appended as arguments.
items:
type: string
type: array
dataParallelism:
default: 1
description: dataParallelism controls data parallelism
Expand Down
7 changes: 7 additions & 0 deletions charts/nebari-llm-serving/templates/operator-clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ rules:
- apiGroups: ["inference.networking.k8s.io"]
resources: ["inferencepools"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# The operator creates a per-model EPP Role granting these
# inference.networking.x-k8s.io permissions (see reconcilers/inferencepool.go).
# RBAC privilege-escalation prevention requires the operator to already hold
# any permission it grants, so it must carry them here too.
- apiGroups: ["inference.networking.x-k8s.io"]
resources: ["inferenceobjectives", "inferencemodelrewrites", "inferencepools", "inferencepoolimports"]
verbs: ["get", "list", "watch"]
- apiGroups: ["aigateway.envoyproxy.io"]
resources: ["aigatewayroutes", "aiservicebackends", "backendsecuritypolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Expand Down
8 changes: 8 additions & 0 deletions dev/manifests/operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ rules:
- apiGroups: ["inference.networking.k8s.io"]
resources: ["inferencepools"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# The operator creates a per-model EPP Role granting these
# inference.networking.x-k8s.io permissions (see reconcilers/inferencepool.go).
# RBAC privilege-escalation prevention requires the operator to already hold
# any permission it grants, so it must carry them here too (mirrors the Helm
# chart ClusterRole; #121).
- apiGroups: ["inference.networking.x-k8s.io"]
resources: ["inferenceobjectives", "inferencemodelrewrites", "inferencepools", "inferencepoolimports"]
verbs: ["get", "list", "watch"]
- apiGroups: ["aigateway.envoyproxy.io"]
resources: ["aigatewayroutes", "aiservicebackends", "backendsecuritypolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Expand Down
4 changes: 3 additions & 1 deletion dev/manifests/passthrough-test-model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#
# Once reconciled (kubectl -n llm-operator-system get passthroughmodel), reach
# it through the gateway. The external endpoint uses API-key auth, so inject a
# client key into the api-keys Secret to skip the key-manager:
# client key into the api-keys Secret to skip the key-manager. A curl right
# after the patch can 403 until the operator re-renders the SecurityPolicy
# allow-list from the Secret (a few seconds) - retry on 403:
# kubectl -n llm-operator-system patch secret openrouter-api-keys --type merge \
# -p '{"stringData":{"localtester":"sk-localtest-abc123"}}'
# kubectl -n envoy-gateway-system port-forward svc/envoy-...-nebari-gateway 8443:443 &
Expand Down
4 changes: 4 additions & 0 deletions dev/manifests/test-model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ spec:
memory: "256Mi"
serving:
image: mock-vllm:dev
# The operator defaults the container command to the real vLLM entrypoint
# (required by the llm-d-cuda serving image); the mock image runs a plain
# Python HTTP server instead, so override it here.
command: ["python", "/server.py"]
replicas: 1
monitoring:
enabled: false
Expand Down
28 changes: 27 additions & 1 deletion dev/mock-vllm/server.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Mock vLLM server that responds to health check and model list endpoints."""
"""Mock vLLM server that responds to health check, model list, and chat endpoints."""
import json
from http.server import HTTPServer, BaseHTTPRequestHandler

Expand All @@ -19,6 +19,32 @@ def do_GET(self):
self.send_response(404)
self.end_headers()

def do_POST(self):
# Minimal OpenAI-compatible chat-completion response so the dev stack can
# exercise the full request path (gateway auth/routing -> backend -> 200)
# without a real model.
if self.path in ("/v1/chat/completions", "/v1/completions"):
length = int(self.headers.get("Content-Length", 0) or 0)
if length:
self.rfile.read(length)
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
body = json.dumps({
"id": "chatcmpl-mock",
"object": "chat.completion",
"model": "test/tiny-model",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "ok"},
"finish_reason": "stop",
}],
})
self.wfile.write(body.encode())
else:
self.send_response(404)
self.end_headers()

def log_message(self, format, *args):
pass # suppress logs

Expand Down
Loading