Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 17 additions & 7 deletions AuthBridge/client-registration/example_deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@ metadata:
namespace: my-agent
data:
SPIRE_ENABLED: "false"
# Replace these placeholder values with real endpoints/credentials
# Replace these placeholder values with real endpoints
KEYCLOAK_URL: "http://keycloak.keycloak.svc:8080"
KEYCLOAK_REALM: "master"
KEYCLOAK_ADMIN_USERNAME: "admin"
KEYCLOAK_ADMIN_PASSWORD: "admin"
KEYCLOAK_TOKEN_EXCHANGE_ENABLED: "true"
KEYCLOAK_CLIENT_REGISTRATION_ENABLED: "true"
---
# keycloak-admin-secret - Keycloak admin credentials for client registration
# Replace the placeholder values with your actual admin credentials.
apiVersion: v1
kind: Secret
metadata:
name: keycloak-admin-secret
namespace: my-agent
type: Opaque
stringData:
KEYCLOAK_ADMIN_USERNAME: "admin"
KEYCLOAK_ADMIN_PASSWORD: "changeme"
---
apiVersion: v1
kind: ServiceAccount
metadata:
Expand Down Expand Up @@ -77,13 +87,13 @@ spec:
key: KEYCLOAK_REALM
- name: KEYCLOAK_ADMIN_USERNAME
valueFrom:
configMapKeyRef:
name: environments
secretKeyRef:
name: keycloak-admin-secret
key: KEYCLOAK_ADMIN_USERNAME
- name: KEYCLOAK_ADMIN_PASSWORD
valueFrom:
configMapKeyRef:
name: environments
secretKeyRef:
name: keycloak-admin-secret
key: KEYCLOAK_ADMIN_PASSWORD
- name: CLIENT_NAME
value: my-app
Expand Down
24 changes: 17 additions & 7 deletions AuthBridge/client-registration/example_deployment_spiffe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@ metadata:
namespace: my-agent
data:
SPIRE_ENABLED: "true"
# Replace these placeholder values with real endpoints/credentials
# Replace these placeholder values with real endpoints
KEYCLOAK_URL: "http://keycloak.keycloak.svc:8080"
KEYCLOAK_REALM: "master"
KEYCLOAK_ADMIN_USERNAME: "admin"
KEYCLOAK_ADMIN_PASSWORD: "admin"
KEYCLOAK_TOKEN_EXCHANGE_ENABLED: "true"
KEYCLOAK_CLIENT_REGISTRATION_ENABLED: "true"
---
# keycloak-admin-secret - Keycloak admin credentials for client registration
# Replace the placeholder values with your actual admin credentials.
apiVersion: v1
kind: Secret
metadata:
name: keycloak-admin-secret
namespace: my-agent
type: Opaque
stringData:
KEYCLOAK_ADMIN_USERNAME: "admin"
KEYCLOAK_ADMIN_PASSWORD: "changeme"
---
apiVersion: v1
kind: ServiceAccount
metadata:
Expand Down Expand Up @@ -91,13 +101,13 @@ spec:
key: KEYCLOAK_REALM
- name: KEYCLOAK_ADMIN_USERNAME
valueFrom:
configMapKeyRef:
name: environments
secretKeyRef:
name: keycloak-admin-secret
key: KEYCLOAK_ADMIN_USERNAME
- name: KEYCLOAK_ADMIN_PASSWORD
valueFrom:
configMapKeyRef:
name: environments
secretKeyRef:
name: keycloak-admin-secret
key: KEYCLOAK_ADMIN_PASSWORD
- name: CLIENT_NAME
value: my-app
Expand Down
32 changes: 24 additions & 8 deletions AuthBridge/demos/github-issue/demo-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,13 +282,26 @@ This creates:

---

## Step 3: Apply Demo ConfigMaps
## Step 3: Create Keycloak Admin Secret and Apply Demo ConfigMaps

The Kagenti installer creates default ConfigMaps (`environments`,
`spiffe-helper-config`, `envoy-config`, `authbridge-config`) with the correct
`kagenti` realm settings and 300s Envoy timeouts. This step overrides only
`authbridge-config` with demo-specific values — the token exchange target
audience (`github-tool`), scopes, and the agent's SPIFFE ID for inbound
`kagenti` realm settings and 300s Envoy timeouts.

The client-registration sidecar needs Keycloak admin credentials to register
agents as OAuth clients. These are stored in a Kubernetes Secret (not a
ConfigMap) to follow security best practices. Create the secret **before**
deploying the agent:

```bash
kubectl create secret generic keycloak-admin-secret -n team1 \
--from-literal=KEYCLOAK_ADMIN_USERNAME=admin \
--from-literal=KEYCLOAK_ADMIN_PASSWORD=admin \
--dry-run=client -o yaml | kubectl apply -f -
```

Then apply the demo-specific `authbridge-config` override — the token exchange
target audience (`github-tool`), scopes, and the agent's SPIFFE ID for inbound
audience validation. Apply this **before** deploying the agent.

```bash
Expand Down Expand Up @@ -806,17 +819,20 @@ kubectl delete pod test-client -n team1 --ignore-not-found

**Symptom:** `{"error":"invalid_client","error_description":"Invalid client or Invalid client credentials"}`

**Cause:** The agent pod's `environments` ConfigMap was missing or incorrect at startup,
so the client-registration sidecar registered the client with wrong settings.
**Cause:** The `keycloak-admin-secret` Secret or `environments` ConfigMap was missing
or incorrect at startup, so the client-registration sidecar couldn't register the client.

**Fix:**

```bash
# 1. Verify the installer's environments ConfigMap has the correct realm
# 1. Verify the keycloak-admin-secret exists
kubectl get secret keycloak-admin-secret -n team1

# 2. Verify the installer's environments ConfigMap has the correct realm
kubectl get configmap environments -n team1 -o jsonpath='{.data.KEYCLOAK_REALM}'
# Should show: kagenti

# 2. Re-apply the demo ConfigMap and restart
# 3. Re-apply the demo ConfigMap and restart
kubectl apply -f demos/github-issue/k8s/configmaps.yaml
kubectl rollout restart deployment/git-issue-agent -n team1
```
Expand Down
33 changes: 24 additions & 9 deletions AuthBridge/demos/github-issue/demo-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,26 @@ This creates:

---

## Step 2: Apply Demo ConfigMaps
## Step 2: Create Keycloak Admin Secret and Apply Demo ConfigMaps

The Kagenti installer creates default ConfigMaps (`environments`,
`spiffe-helper-config`, `envoy-config`, `authbridge-config`) with the correct
`kagenti` realm settings and 300s Envoy timeouts. This step overrides only
`authbridge-config` with demo-specific values — the token exchange target
audience (`github-tool`), scopes, and the agent's SPIFFE ID for inbound
audience validation.
`kagenti` realm settings and 300s Envoy timeouts.

The client-registration sidecar needs Keycloak admin credentials to register
agents as OAuth clients. These are stored in a Kubernetes Secret (not a
ConfigMap) to follow security best practices:

```bash
kubectl create secret generic keycloak-admin-secret -n team1 \
--from-literal=KEYCLOAK_ADMIN_USERNAME=admin \
--from-literal=KEYCLOAK_ADMIN_PASSWORD=admin \
--dry-run=client -o yaml | kubectl apply -f -
```

Then apply the demo-specific `authbridge-config` override — the token exchange
target audience (`github-tool`), scopes, and the agent's SPIFFE ID for inbound
audience validation:

```bash
cd AuthBridge
Expand Down Expand Up @@ -682,17 +694,20 @@ kubectl rollout status deployment/git-issue-agent -n team1 --timeout=180s

**Symptom:** `{"error":"invalid_client","error_description":"Invalid client or Invalid client credentials"}`

**Cause:** The agent pod's `environments` ConfigMap was missing or incorrect at startup,
so the client-registration sidecar registered the client with wrong settings.
**Cause:** The `keycloak-admin-secret` Secret or `environments` ConfigMap was missing
or incorrect at startup, so the client-registration sidecar couldn't register the client.

**Fix:**

```bash
# 1. Verify the installer's environments ConfigMap has the correct realm
# 1. Verify the keycloak-admin-secret exists
kubectl get secret keycloak-admin-secret -n team1

# 2. Verify the installer's environments ConfigMap has the correct realm
kubectl get configmap environments -n team1 -o jsonpath='{.data.KEYCLOAK_REALM}'
# Should show: kagenti

# 2. Re-apply the demo ConfigMap and restart
# 3. Re-apply the demo ConfigMap and restart
kubectl apply -f demos/github-issue/k8s/configmaps.yaml
kubectl rollout restart deployment/git-issue-agent -n team1
```
Expand Down
18 changes: 13 additions & 5 deletions AuthBridge/demos/webhook/k8s/configmaps-webhook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,25 @@ data:
SPIRE_ENABLED: "true"
KEYCLOAK_URL: "http://keycloak-service.keycloak.svc:8080"
KEYCLOAK_REALM: "demo"
# Demo/development only: do NOT use hardcoded admin credentials in production.
# For production deployments, supply these via a Kubernetes Secret and reference
# them from your Pod/Deployment using env.valueFrom.secretKeyRef instead of this ConfigMap.
KEYCLOAK_ADMIN_USERNAME: "admin"
KEYCLOAK_ADMIN_PASSWORD: "admin"
# Comma-separated list of Keycloak client IDs that should receive the agent's
# audience scope. This allows existing clients (like the UI) to include the
# agent's audience in their tokens without manual Keycloak configuration.
# Default: "kagenti" (the platform UI client)
# PLATFORM_CLIENT_IDS: "kagenti"

---
# keycloak-admin-secret - Keycloak admin credentials for client registration
# Replace the placeholder values with your actual admin credentials.
apiVersion: v1
kind: Secret
metadata:
name: keycloak-admin-secret
namespace: team1
type: Opaque
stringData:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The --- + Secret header insertion causes the PLATFORM_CLIENT_IDS comment block (the lines after KEYCLOAK_ADMIN_PASSWORD) to land under the Secret's stringData: instead of the ConfigMap's data:. The comments are YAML comments so it's not functionally broken, but it's misleading — a reader would think PLATFORM_CLIENT_IDS belongs in the Secret. Consider moving the PLATFORM_CLIENT_IDS comment block back above the --- separator under the ConfigMap.

KEYCLOAK_ADMIN_USERNAME: "admin"
KEYCLOAK_ADMIN_PASSWORD: "admin"

---
# authbridge-config ConfigMap - Used by envoy-proxy for token exchange and inbound validation
apiVersion: v1
Expand Down
13 changes: 7 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,13 @@ Hooks:

When the webhook injects sidecars, the target namespace needs these ConfigMaps:

| ConfigMap | Used by | Keys |
|-----------|---------|------|
| `environments` | client-registration | `KEYCLOAK_URL`, `KEYCLOAK_REALM`, `KEYCLOAK_ADMIN_USERNAME`, `KEYCLOAK_ADMIN_PASSWORD` |
| `authbridge-config` | envoy-proxy (ext-proc) | `TOKEN_URL`, `ISSUER`, `TARGET_AUDIENCE`, `TARGET_SCOPES` |
| `spiffe-helper-config` | spiffe-helper | SPIFFE helper configuration file |
| `envoy-config` | envoy-proxy | Envoy YAML configuration |
| Resource | Kind | Used by | Keys |
|----------|------|---------|------|
| `environments` | ConfigMap | client-registration | `KEYCLOAK_URL`, `KEYCLOAK_REALM` |
| `keycloak-admin-secret` | Secret | client-registration | `KEYCLOAK_ADMIN_USERNAME`, `KEYCLOAK_ADMIN_PASSWORD` |
| `authbridge-config` | ConfigMap | envoy-proxy (ext-proc) | `TOKEN_URL`, `ISSUER`, `TARGET_AUDIENCE`, `TARGET_SCOPES` |
| `spiffe-helper-config` | ConfigMap | spiffe-helper | SPIFFE helper configuration file |
| `envoy-config` | ConfigMap | envoy-proxy | Envoy YAML configuration |

## Common Development Tasks

Expand Down
3 changes: 3 additions & 0 deletions charts/kagenti-webhook/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good least-privilege: only get, list, watch on secrets — no write access needed.

- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["get", "create", "update"]
Expand Down
11 changes: 8 additions & 3 deletions kagenti-webhook/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,18 @@ make generate
- **Container existence checks**: `containerExists()` and `volumeExists()` helpers prevent duplicate injection.
- **Kubebuilder markers**: Webhook path markers (e.g., `+kubebuilder:webhook:path=...`) in Go comments generate the webhook manifests. Do not change these without running `make manifests`.

### ConfigMap Dependencies at Runtime
Injected sidecars expect these ConfigMaps to exist in the target namespace:
- `environments` -- `KEYCLOAK_URL`, `KEYCLOAK_REALM`, `KEYCLOAK_ADMIN_USERNAME`, `KEYCLOAK_ADMIN_PASSWORD`
### Runtime Dependencies
Injected sidecars expect these resources to exist in the target namespace:

ConfigMaps:
- `environments` -- `KEYCLOAK_URL`, `KEYCLOAK_REALM`
- `authbridge-config` -- `TOKEN_URL`, `ISSUER`, `TARGET_AUDIENCE`, `TARGET_SCOPES`
- `spiffe-helper-config` -- SPIFFE helper configuration (when SPIRE is enabled)
- `envoy-config` -- Envoy proxy configuration

Secrets:
- `keycloak-admin-secret` -- `KEYCLOAK_ADMIN_USERNAME`, `KEYCLOAK_ADMIN_PASSWORD`

### Security Model
- `proxy-init` runs as an init container with a short lifetime (iptables setup).
- `envoy-proxy` runs as UID 1337.
Expand Down
3 changes: 3 additions & 0 deletions kagenti-webhook/config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["get", "create", "update"]
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ func (b *ContainerBuilder) BuildClientRegistrationContainerWithSpireOption(name,
{
Name: "KEYCLOAK_ADMIN_USERNAME",
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "environments",
Name: "keycloak-admin-secret",
},
Key: "KEYCLOAK_ADMIN_USERNAME",
},
Expand All @@ -146,9 +146,9 @@ func (b *ContainerBuilder) BuildClientRegistrationContainerWithSpireOption(name,
{
Name: "KEYCLOAK_ADMIN_PASSWORD",
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "environments",
Name: "keycloak-admin-secret",
},
Key: "KEYCLOAK_ADMIN_PASSWORD",
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,55 @@ func TestBuildClientRegistrationContainer_HasPlatformClientIDsEnv(t *testing.T)
t.Error("client-registration container missing PLATFORM_CLIENT_IDS env var")
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This test silently passes if neither KEYCLOAK_ADMIN_USERNAME nor KEYCLOAK_ADMIN_PASSWORD is found in the env list at all (the inner loop just skips). Consider adding a found bool or t.Errorf if the key is absent, so the test catches accidental removal of these env vars.


func TestBuildClientRegistrationContainer_AdminCredentialsFromSecret(t *testing.T) {
builder := NewContainerBuilder(config.CompiledDefaults())
container := builder.BuildClientRegistrationContainerWithSpireOption("my-app", "my-ns", true)

sensitiveKeys := []string{"KEYCLOAK_ADMIN_USERNAME", "KEYCLOAK_ADMIN_PASSWORD"}
for _, key := range sensitiveKeys {
found := false
for _, env := range container.Env {
if env.Name != key {
continue
}
found = true
if env.ValueFrom == nil || env.ValueFrom.SecretKeyRef == nil {
t.Errorf("env %q must use SecretKeyRef, got ConfigMapKeyRef or literal", key)
continue
}
if env.ValueFrom.SecretKeyRef.Name != "keycloak-admin-secret" {
t.Errorf("env %q SecretKeyRef.Name = %q, want %q", key, env.ValueFrom.SecretKeyRef.Name, "keycloak-admin-secret")
}
}
if !found {
t.Errorf("client-registration container missing env var %q", key)
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Same as above — silently passes if KEYCLOAK_URL or KEYCLOAK_REALM are missing from the env list entirely.


func TestBuildClientRegistrationContainer_NonSensitiveKeysFromConfigMap(t *testing.T) {
builder := NewContainerBuilder(config.CompiledDefaults())
container := builder.BuildClientRegistrationContainerWithSpireOption("my-app", "my-ns", true)

nonSensitiveKeys := []string{"KEYCLOAK_URL", "KEYCLOAK_REALM"}
for _, key := range nonSensitiveKeys {
found := false
for _, env := range container.Env {
if env.Name != key {
continue
}
found = true
if env.ValueFrom == nil || env.ValueFrom.ConfigMapKeyRef == nil {
t.Errorf("env %q must use ConfigMapKeyRef", key)
continue
}
if env.ValueFrom.ConfigMapKeyRef.Name != "environments" {
t.Errorf("env %q ConfigMapKeyRef.Name = %q, want %q", key, env.ValueFrom.ConfigMapKeyRef.Name, "environments")
}
}
if !found {
t.Errorf("client-registration container missing env var %q", key)
}
}
}
Loading