feat(litellm): SSO and user management
This commit is contained in:
@@ -150,6 +150,81 @@ just litellm::verify-api-keys
|
|||||||
| `OLLAMA_NAMESPACE` | `ollama` | Ollama namespace for local models |
|
| `OLLAMA_NAMESPACE` | `ollama` | Ollama namespace for local models |
|
||||||
| `MONITORING_ENABLED` | (prompt) | Enable Prometheus ServiceMonitor |
|
| `MONITORING_ENABLED` | (prompt) | Enable Prometheus ServiceMonitor |
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
LiteLLM has two types of authentication:
|
||||||
|
|
||||||
|
1. **API Access**: Uses Master Key or Virtual Keys for programmatic access
|
||||||
|
2. **Admin UI**: Uses Keycloak SSO for browser-based access
|
||||||
|
|
||||||
|
### Enable SSO for Admin UI
|
||||||
|
|
||||||
|
After installing LiteLLM, enable Keycloak authentication for the Admin UI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just litellm::setup-oidc
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
- Create a Keycloak client for LiteLLM
|
||||||
|
- Store the client secret in Vault
|
||||||
|
- Configure LiteLLM with OIDC environment variables
|
||||||
|
- Upgrade the deployment with SSO enabled
|
||||||
|
|
||||||
|
### Disable SSO
|
||||||
|
|
||||||
|
To disable SSO and return to unauthenticated Admin UI access:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just litellm::disable-oidc
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSO Configuration Details
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
| ------- | ----- |
|
||||||
|
| Callback URL | `https://<litellm-host>/sso/callback` |
|
||||||
|
| Authorization Endpoint | `https://<keycloak-host>/realms/<realm>/protocol/openid-connect/auth` |
|
||||||
|
| Token Endpoint | `https://<keycloak-host>/realms/<realm>/protocol/openid-connect/token` |
|
||||||
|
| Userinfo Endpoint | `https://<keycloak-host>/realms/<realm>/protocol/openid-connect/userinfo` |
|
||||||
|
| Scope | `openid email profile` |
|
||||||
|
|
||||||
|
## User Management
|
||||||
|
|
||||||
|
SSO users are automatically created in LiteLLM when they first log in. By default, new users are assigned the `internal_user_viewer` role (read-only access).
|
||||||
|
|
||||||
|
### List Users
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just litellm::list-users
|
||||||
|
```
|
||||||
|
|
||||||
|
### Assign Role to User
|
||||||
|
|
||||||
|
Interactively select user and role:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just litellm::assign-role
|
||||||
|
```
|
||||||
|
|
||||||
|
Or specify directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
just litellm::assign-role buun proxy_admin
|
||||||
|
```
|
||||||
|
|
||||||
|
### User Roles
|
||||||
|
|
||||||
|
| Role | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
| `proxy_admin` | Full admin access (manage keys, users, models, settings) |
|
||||||
|
| `proxy_admin_viewer` | Admin read-only access |
|
||||||
|
| `internal_user` | Can create and manage own API keys |
|
||||||
|
| `internal_user_viewer` | Read-only access (default for SSO users) |
|
||||||
|
|
||||||
|
**Note**: To manage API keys in the Admin UI, users need at least `internal_user` or `proxy_admin` role.
|
||||||
|
|
||||||
## API Usage
|
## API Usage
|
||||||
|
|
||||||
LiteLLM exposes an OpenAI-compatible API at `https://your-litellm-host/`.
|
LiteLLM exposes an OpenAI-compatible API at `https://your-litellm-host/`.
|
||||||
@@ -163,9 +238,11 @@ just litellm::master-key
|
|||||||
### Generate Virtual Key for a User
|
### Generate Virtual Key for a User
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
just litellm::generate-virtual-key user@example.com
|
just litellm::generate-virtual-key buun
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This will prompt for a model selection and generate an API key for the specified user.
|
||||||
|
|
||||||
### OpenAI SDK Example
|
### OpenAI SDK Example
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -330,6 +407,7 @@ kubectl exec -n litellm deployment/litellm -- \
|
|||||||
| `models.example.yaml` | Example model configuration |
|
| `models.example.yaml` | Example model configuration |
|
||||||
| `litellm-values.gomplate.yaml` | Helm values template |
|
| `litellm-values.gomplate.yaml` | Helm values template |
|
||||||
| `apikey-external-secret.gomplate.yaml` | ExternalSecret for API keys |
|
| `apikey-external-secret.gomplate.yaml` | ExternalSecret for API keys |
|
||||||
|
| `keycloak-auth-external-secret.gomplate.yaml` | ExternalSecret for Keycloak OIDC |
|
||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
|
|||||||
260
litellm/justfile
260
litellm/justfile
@@ -3,10 +3,15 @@ set fallback := true
|
|||||||
export LITELLM_NAMESPACE := env("LITELLM_NAMESPACE", "litellm")
|
export LITELLM_NAMESPACE := env("LITELLM_NAMESPACE", "litellm")
|
||||||
export LITELLM_CHART_VERSION := env("LITELLM_CHART_VERSION", "0.1.825")
|
export LITELLM_CHART_VERSION := env("LITELLM_CHART_VERSION", "0.1.825")
|
||||||
export LITELLM_HOST := env("LITELLM_HOST", "")
|
export LITELLM_HOST := env("LITELLM_HOST", "")
|
||||||
|
export LITELLM_OIDC_CLIENT_ID := env("LITELLM_OIDC_CLIENT_ID", "litellm")
|
||||||
|
export LITELLM_OIDC_ENABLED := env("LITELLM_OIDC_ENABLED", "")
|
||||||
export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets")
|
export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets")
|
||||||
export OLLAMA_NAMESPACE := env("OLLAMA_NAMESPACE", "ollama")
|
export OLLAMA_NAMESPACE := env("OLLAMA_NAMESPACE", "ollama")
|
||||||
export PROMETHEUS_NAMESPACE := env("PROMETHEUS_NAMESPACE", "monitoring")
|
export PROMETHEUS_NAMESPACE := env("PROMETHEUS_NAMESPACE", "monitoring")
|
||||||
export MONITORING_ENABLED := env("MONITORING_ENABLED", "")
|
export MONITORING_ENABLED := env("MONITORING_ENABLED", "")
|
||||||
|
export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "buunstack")
|
||||||
|
export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "")
|
||||||
|
export K8S_VAULT_NAMESPACE := env("K8S_VAULT_NAMESPACE", "vault")
|
||||||
|
|
||||||
[private]
|
[private]
|
||||||
default:
|
default:
|
||||||
@@ -293,6 +298,11 @@ install:
|
|||||||
--placeholder="e.g., litellm.example.com")
|
--placeholder="e.g., litellm.example.com")
|
||||||
done
|
done
|
||||||
|
|
||||||
|
while [ -z "${KEYCLOAK_HOST}" ]; do
|
||||||
|
KEYCLOAK_HOST=$(gum input --prompt="Keycloak host (FQDN): " --width=80 \
|
||||||
|
--placeholder="e.g., auth.example.com")
|
||||||
|
done
|
||||||
|
|
||||||
if helm status kube-prometheus-stack -n ${PROMETHEUS_NAMESPACE} &>/dev/null; then
|
if helm status kube-prometheus-stack -n ${PROMETHEUS_NAMESPACE} &>/dev/null; then
|
||||||
if [ -z "${MONITORING_ENABLED}" ]; then
|
if [ -z "${MONITORING_ENABLED}" ]; then
|
||||||
if gum confirm "Enable Prometheus monitoring?"; then
|
if gum confirm "Enable Prometheus monitoring?"; then
|
||||||
@@ -320,6 +330,11 @@ install:
|
|||||||
just create-postgres-user-and-db
|
just create-postgres-user-and-db
|
||||||
just create-postgres-secret
|
just create-postgres-secret
|
||||||
|
|
||||||
|
echo "Setting up Keycloak OIDC authentication..."
|
||||||
|
just create-keycloak-client
|
||||||
|
just create-keycloak-auth-secret
|
||||||
|
LITELLM_OIDC_ENABLED="true"
|
||||||
|
|
||||||
echo "Generating Helm values..."
|
echo "Generating Helm values..."
|
||||||
gomplate -f litellm-values.gomplate.yaml -o litellm-values.yaml
|
gomplate -f litellm-values.gomplate.yaml -o litellm-values.yaml
|
||||||
|
|
||||||
@@ -331,6 +346,7 @@ install:
|
|||||||
echo ""
|
echo ""
|
||||||
echo "LiteLLM installed successfully!"
|
echo "LiteLLM installed successfully!"
|
||||||
echo "Access LiteLLM at: https://${LITELLM_HOST}"
|
echo "Access LiteLLM at: https://${LITELLM_HOST}"
|
||||||
|
echo "SSO Callback URL: https://${LITELLM_HOST}/sso/callback"
|
||||||
|
|
||||||
# Upgrade LiteLLM
|
# Upgrade LiteLLM
|
||||||
upgrade:
|
upgrade:
|
||||||
@@ -364,6 +380,9 @@ upgrade:
|
|||||||
echo "Generating Helm values..."
|
echo "Generating Helm values..."
|
||||||
gomplate -f litellm-values.gomplate.yaml -o litellm-values.yaml
|
gomplate -f litellm-values.gomplate.yaml -o litellm-values.yaml
|
||||||
|
|
||||||
|
# Delete the migration job as it's immutable and blocks helm upgrade
|
||||||
|
kubectl delete job litellm-migrations -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
echo "Upgrading LiteLLM Helm chart..."
|
echo "Upgrading LiteLLM Helm chart..."
|
||||||
helm upgrade litellm oci://ghcr.io/berriai/litellm-helm \
|
helm upgrade litellm oci://ghcr.io/berriai/litellm-helm \
|
||||||
--version ${LITELLM_CHART_VERSION} -n ${LITELLM_NAMESPACE} --wait \
|
--version ${LITELLM_CHART_VERSION} -n ${LITELLM_NAMESPACE} --wait \
|
||||||
@@ -416,7 +435,7 @@ generate-virtual-key user='' model='':
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
user="{{ user }}"
|
user="{{ user }}"
|
||||||
while [ -z "${user}" ]; do
|
while [ -z "${user}" ]; do
|
||||||
user=$(gum input --prompt="User email: " --width=80)
|
user=$(gum input --prompt="Username: " --width=80)
|
||||||
done
|
done
|
||||||
model="{{ model }}"
|
model="{{ model }}"
|
||||||
if [ -z "${model}" ]; then
|
if [ -z "${model}" ]; then
|
||||||
@@ -429,12 +448,247 @@ generate-virtual-key user='' model='':
|
|||||||
fi
|
fi
|
||||||
master_key=$(kubectl get secret litellm-masterkey -n ${LITELLM_NAMESPACE} \
|
master_key=$(kubectl get secret litellm-masterkey -n ${LITELLM_NAMESPACE} \
|
||||||
-o jsonpath="{.data.masterkey}" | base64 --decode)
|
-o jsonpath="{.data.masterkey}" | base64 --decode)
|
||||||
curl "https://${LITELLM_HOST}/key/generate" \
|
response=$(curl -s "https://${LITELLM_HOST}/key/generate" \
|
||||||
--header "Authorization: Bearer ${master_key}" \
|
--header "Authorization: Bearer ${master_key}" \
|
||||||
--header "Content-Type: application/json" \
|
--header "Content-Type: application/json" \
|
||||||
--data-raw "{\"models\": [\"${model}\"], \"metadata\": {\"user\": \"${user}\"}}" | jq .
|
--data-raw "{\"models\": [\"${model}\"], \"metadata\": {\"user\": \"${user}\"}}")
|
||||||
|
echo "${response}" | jq .
|
||||||
|
echo ""
|
||||||
|
echo "API Key: $(echo "${response}" | jq -r '.key')"
|
||||||
|
|
||||||
# Get master key
|
# Get master key
|
||||||
master-key:
|
master-key:
|
||||||
@kubectl get secret litellm-masterkey -n ${LITELLM_NAMESPACE} \
|
@kubectl get secret litellm-masterkey -n ${LITELLM_NAMESPACE} \
|
||||||
-o jsonpath="{.data.masterkey}" | base64 --decode
|
-o jsonpath="{.data.masterkey}" | base64 --decode
|
||||||
|
|
||||||
|
# List users
|
||||||
|
list-users:
|
||||||
|
kubectl exec -n postgres postgres-cluster-1 -- psql -U postgres -d litellm -c \
|
||||||
|
"SELECT user_id, user_email, user_role FROM \"LiteLLM_UserTable\";"
|
||||||
|
|
||||||
|
# Assign role to user
|
||||||
|
assign-role user='' role='':
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
user="{{ user }}"
|
||||||
|
role="{{ role }}"
|
||||||
|
if [ -z "${user}" ]; then
|
||||||
|
users=$(kubectl exec -n postgres postgres-cluster-1 -- psql -U postgres -d litellm -t -c \
|
||||||
|
"SELECT user_id FROM \"LiteLLM_UserTable\";" 2>/dev/null | tr -d ' ' | grep -v '^$')
|
||||||
|
if [ -z "${users}" ]; then
|
||||||
|
echo "No users found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
user=$(echo "${users}" | gum choose --header="Select user:")
|
||||||
|
fi
|
||||||
|
if [ -z "${role}" ]; then
|
||||||
|
role=$(gum choose --header="Select role:" \
|
||||||
|
"proxy_admin" \
|
||||||
|
"proxy_admin_viewer" \
|
||||||
|
"internal_user" \
|
||||||
|
"internal_user_viewer")
|
||||||
|
fi
|
||||||
|
kubectl exec -n postgres postgres-cluster-1 -- psql -U postgres -d litellm -c \
|
||||||
|
"UPDATE \"LiteLLM_UserTable\" SET user_role = '${role}' WHERE user_id = '${user}';"
|
||||||
|
echo "Assigned role '${role}' to user '${user}'"
|
||||||
|
|
||||||
|
# Create Keycloak client for LiteLLM OIDC
|
||||||
|
create-keycloak-client:
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
while [ -z "${LITELLM_HOST}" ]; do
|
||||||
|
LITELLM_HOST=$(
|
||||||
|
gum input --prompt="LiteLLM host (FQDN): " --width=100 \
|
||||||
|
--placeholder="e.g., litellm.example.com"
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Creating Keycloak client for LiteLLM..."
|
||||||
|
|
||||||
|
just keycloak::delete-client ${KEYCLOAK_REALM} ${LITELLM_OIDC_CLIENT_ID} || true
|
||||||
|
|
||||||
|
CLIENT_SECRET=$(just utils::random-password)
|
||||||
|
|
||||||
|
just keycloak::create-client \
|
||||||
|
realm=${KEYCLOAK_REALM} \
|
||||||
|
client_id=${LITELLM_OIDC_CLIENT_ID} \
|
||||||
|
redirect_url="https://${LITELLM_HOST}/sso/callback" \
|
||||||
|
client_secret="${CLIENT_SECRET}"
|
||||||
|
|
||||||
|
# Store temporarily in k8s secret for create-keycloak-auth-secret to pick up
|
||||||
|
kubectl delete secret litellm-oauth-temp -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
kubectl create secret generic litellm-oauth-temp -n ${LITELLM_NAMESPACE} \
|
||||||
|
--from-literal=client_id="${LITELLM_OIDC_CLIENT_ID}" \
|
||||||
|
--from-literal=client_secret="${CLIENT_SECRET}"
|
||||||
|
|
||||||
|
echo "Keycloak client created successfully"
|
||||||
|
echo "Client ID: ${LITELLM_OIDC_CLIENT_ID}"
|
||||||
|
echo "Redirect URI: https://${LITELLM_HOST}/sso/callback"
|
||||||
|
|
||||||
|
# Delete Keycloak client for LiteLLM
|
||||||
|
delete-keycloak-client:
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Deleting Keycloak client for LiteLLM..."
|
||||||
|
just keycloak::delete-client ${KEYCLOAK_REALM} ${LITELLM_OIDC_CLIENT_ID} || true
|
||||||
|
kubectl delete secret litellm-oauth-temp -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
if just vault::exist keycloak/client/litellm &>/dev/null; then
|
||||||
|
just vault::delete keycloak/client/litellm
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create Keycloak auth secret
|
||||||
|
create-keycloak-auth-secret:
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Prioritize temporary secret (freshly created) over Vault (potentially stale)
|
||||||
|
if kubectl get secret litellm-oauth-temp -n ${LITELLM_NAMESPACE} &>/dev/null; then
|
||||||
|
oauth_client_id=$(kubectl get secret litellm-oauth-temp -n ${LITELLM_NAMESPACE} \
|
||||||
|
-o jsonpath='{.data.client_id}' | base64 -d)
|
||||||
|
oauth_client_secret=$(kubectl get secret litellm-oauth-temp -n ${LITELLM_NAMESPACE} \
|
||||||
|
-o jsonpath='{.data.client_secret}' | base64 -d)
|
||||||
|
elif helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null && \
|
||||||
|
just vault::get keycloak/client/litellm client_secret &>/dev/null; then
|
||||||
|
oauth_client_id=$(just vault::get keycloak/client/litellm client_id)
|
||||||
|
oauth_client_secret=$(just vault::get keycloak/client/litellm client_secret)
|
||||||
|
else
|
||||||
|
echo "Error: Cannot retrieve OAuth client secret. Please run 'just litellm::create-keycloak-client' first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then
|
||||||
|
echo "External Secrets Operator detected. Storing secrets in Vault..."
|
||||||
|
|
||||||
|
# Store OAuth credentials in Vault
|
||||||
|
just vault::put keycloak/client/litellm \
|
||||||
|
client_id="${oauth_client_id}" \
|
||||||
|
client_secret="${oauth_client_secret}"
|
||||||
|
|
||||||
|
# Delete existing secrets and ExternalSecrets
|
||||||
|
kubectl delete secret keycloak-auth -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
kubectl delete externalsecret keycloak-auth-external-secret -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
|
# Create ExternalSecret
|
||||||
|
gomplate -f keycloak-auth-external-secret.gomplate.yaml | kubectl apply -f -
|
||||||
|
|
||||||
|
echo "Waiting for ExternalSecret to sync..."
|
||||||
|
kubectl wait --for=condition=Ready externalsecret/keycloak-auth-external-secret \
|
||||||
|
-n ${LITELLM_NAMESPACE} --timeout=60s
|
||||||
|
|
||||||
|
echo "ExternalSecret created successfully"
|
||||||
|
else
|
||||||
|
echo "External Secrets Operator not found. Creating Kubernetes Secret directly..."
|
||||||
|
|
||||||
|
# Create Keycloak OAuth Secret
|
||||||
|
kubectl delete secret keycloak-auth -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
kubectl create secret generic keycloak-auth -n ${LITELLM_NAMESPACE} \
|
||||||
|
--from-literal=GENERIC_CLIENT_ID="${oauth_client_id}" \
|
||||||
|
--from-literal=GENERIC_CLIENT_SECRET="${oauth_client_secret}"
|
||||||
|
|
||||||
|
# Store credentials in Vault if available (backup for admin credentials)
|
||||||
|
if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then
|
||||||
|
just vault::put keycloak/client/litellm \
|
||||||
|
client_id="${oauth_client_id}" \
|
||||||
|
client_secret="${oauth_client_secret}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Kubernetes Secret created successfully"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up temporary OAuth secret
|
||||||
|
kubectl delete secret litellm-oauth-temp -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
|
# Delete Keycloak auth secret
|
||||||
|
delete-keycloak-auth-secret:
|
||||||
|
kubectl delete externalsecret keycloak-auth-external-secret -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
kubectl delete secret keycloak-auth -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
|
# Setup OIDC authentication for Admin UI
|
||||||
|
setup-oidc:
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "Setting up OIDC authentication for LiteLLM Admin UI..."
|
||||||
|
|
||||||
|
while [ -z "${LITELLM_HOST}" ]; do
|
||||||
|
LITELLM_HOST=$(
|
||||||
|
gum input --prompt="LiteLLM host (FQDN): " --width=100 \
|
||||||
|
--placeholder="e.g., litellm.example.com"
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
while [ -z "${KEYCLOAK_HOST}" ]; do
|
||||||
|
KEYCLOAK_HOST=$(
|
||||||
|
gum input --prompt="Keycloak host (FQDN): " --width=100 \
|
||||||
|
--placeholder="e.g., auth.example.com"
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
just create-keycloak-client
|
||||||
|
just create-keycloak-auth-secret
|
||||||
|
|
||||||
|
LITELLM_OIDC_ENABLED="true"
|
||||||
|
|
||||||
|
if helm status kube-prometheus-stack -n ${PROMETHEUS_NAMESPACE} &>/dev/null; then
|
||||||
|
if [ -z "${MONITORING_ENABLED}" ]; then
|
||||||
|
if gum confirm "Enable Prometheus monitoring?"; then
|
||||||
|
MONITORING_ENABLED="true"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Generating Helm values with OIDC configuration..."
|
||||||
|
gomplate -f litellm-values.gomplate.yaml -o litellm-values.yaml
|
||||||
|
|
||||||
|
# Delete the migration job as it's immutable and blocks helm upgrade
|
||||||
|
kubectl delete job litellm-migrations -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
|
echo "Upgrading LiteLLM with OIDC configuration..."
|
||||||
|
helm upgrade litellm oci://ghcr.io/berriai/litellm-helm \
|
||||||
|
--version ${LITELLM_CHART_VERSION} -n ${LITELLM_NAMESPACE} --wait \
|
||||||
|
-f litellm-values.yaml
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "OIDC authentication configured successfully!"
|
||||||
|
echo "Access LiteLLM at: https://${LITELLM_HOST}"
|
||||||
|
echo "SSO Callback URL: https://${LITELLM_HOST}/sso/callback"
|
||||||
|
|
||||||
|
# Disable OIDC authentication
|
||||||
|
disable-oidc:
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "Disabling OIDC authentication for LiteLLM Admin UI..."
|
||||||
|
|
||||||
|
LITELLM_OIDC_ENABLED=""
|
||||||
|
|
||||||
|
while [ -z "${LITELLM_HOST}" ]; do
|
||||||
|
LITELLM_HOST=$(
|
||||||
|
gum input --prompt="LiteLLM host (FQDN): " --width=100 \
|
||||||
|
--placeholder="e.g., litellm.example.com"
|
||||||
|
)
|
||||||
|
done
|
||||||
|
|
||||||
|
if helm status kube-prometheus-stack -n ${PROMETHEUS_NAMESPACE} &>/dev/null; then
|
||||||
|
if [ -z "${MONITORING_ENABLED}" ]; then
|
||||||
|
if gum confirm "Enable Prometheus monitoring?"; then
|
||||||
|
MONITORING_ENABLED="true"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Generating Helm values without OIDC..."
|
||||||
|
gomplate -f litellm-values.gomplate.yaml -o litellm-values.yaml
|
||||||
|
|
||||||
|
# Delete the migration job as it's immutable and blocks helm upgrade
|
||||||
|
kubectl delete job litellm-migrations -n ${LITELLM_NAMESPACE} --ignore-not-found
|
||||||
|
|
||||||
|
echo "Upgrading LiteLLM without OIDC..."
|
||||||
|
helm upgrade litellm oci://ghcr.io/berriai/litellm-helm \
|
||||||
|
--version ${LITELLM_CHART_VERSION} -n ${LITELLM_NAMESPACE} --wait \
|
||||||
|
-f litellm-values.yaml
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "OIDC authentication disabled."
|
||||||
|
echo "Note: Keycloak client was NOT deleted. To clean up, run:"
|
||||||
|
echo " just litellm::delete-keycloak-client"
|
||||||
|
|||||||
22
litellm/keycloak-auth-external-secret.gomplate.yaml
Normal file
22
litellm/keycloak-auth-external-secret.gomplate.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: keycloak-auth-external-secret
|
||||||
|
namespace: {{ .Env.LITELLM_NAMESPACE }}
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
secretStoreRef:
|
||||||
|
name: vault-secret-store
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
target:
|
||||||
|
name: keycloak-auth
|
||||||
|
creationPolicy: Owner
|
||||||
|
data:
|
||||||
|
- secretKey: GENERIC_CLIENT_ID
|
||||||
|
remoteRef:
|
||||||
|
key: keycloak/client/litellm
|
||||||
|
property: client_id
|
||||||
|
- secretKey: GENERIC_CLIENT_SECRET
|
||||||
|
remoteRef:
|
||||||
|
key: keycloak/client/litellm
|
||||||
|
property: client_secret
|
||||||
@@ -18,8 +18,26 @@ migrationJob:
|
|||||||
limits:
|
limits:
|
||||||
memory: 1Gi
|
memory: 1Gi
|
||||||
|
|
||||||
|
{{- if .Env.LITELLM_OIDC_ENABLED }}
|
||||||
environmentSecrets:
|
environmentSecrets:
|
||||||
- apikey
|
- apikey
|
||||||
|
- keycloak-auth
|
||||||
|
|
||||||
|
extraEnvVars:
|
||||||
|
- name: PROXY_BASE_URL
|
||||||
|
value: "https://{{ .Env.LITELLM_HOST }}"
|
||||||
|
- name: GENERIC_AUTHORIZATION_ENDPOINT
|
||||||
|
value: "https://{{ .Env.KEYCLOAK_HOST }}/realms/{{ .Env.KEYCLOAK_REALM }}/protocol/openid-connect/auth"
|
||||||
|
- name: GENERIC_TOKEN_ENDPOINT
|
||||||
|
value: "https://{{ .Env.KEYCLOAK_HOST }}/realms/{{ .Env.KEYCLOAK_REALM }}/protocol/openid-connect/token"
|
||||||
|
- name: GENERIC_USERINFO_ENDPOINT
|
||||||
|
value: "https://{{ .Env.KEYCLOAK_HOST }}/realms/{{ .Env.KEYCLOAK_REALM }}/protocol/openid-connect/userinfo"
|
||||||
|
- name: GENERIC_SCOPE
|
||||||
|
value: "openid email profile"
|
||||||
|
{{- else }}
|
||||||
|
environmentSecrets:
|
||||||
|
- apikey
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
proxy_config:
|
proxy_config:
|
||||||
model_list:
|
model_list:
|
||||||
|
|||||||
Reference in New Issue
Block a user