diff --git a/AuthBridge/client-registration/example_deployment.yaml b/AuthBridge/client-registration/example_deployment.yaml index fbbf4bf24..c2dfa742a 100644 --- a/AuthBridge/client-registration/example_deployment.yaml +++ b/AuthBridge/client-registration/example_deployment.yaml @@ -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: @@ -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 diff --git a/AuthBridge/client-registration/example_deployment_spiffe.yaml b/AuthBridge/client-registration/example_deployment_spiffe.yaml index 327838a94..e1327b747 100644 --- a/AuthBridge/client-registration/example_deployment_spiffe.yaml +++ b/AuthBridge/client-registration/example_deployment_spiffe.yaml @@ -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: @@ -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 diff --git a/AuthBridge/demos/github-issue/k8s/configmaps.yaml b/AuthBridge/demos/github-issue/k8s/configmaps.yaml index 48619950a..c77e09d41 100644 --- a/AuthBridge/demos/github-issue/k8s/configmaps.yaml +++ b/AuthBridge/demos/github-issue/k8s/configmaps.yaml @@ -24,11 +24,19 @@ 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. + +--- +# 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: KEYCLOAK_ADMIN_USERNAME: "admin" - KEYCLOAK_ADMIN_PASSWORD: "admin" + KEYCLOAK_ADMIN_PASSWORD: "changeme" --- # authbridge-config ConfigMap - Used by envoy-proxy for token exchange diff --git a/AuthBridge/demos/webhook/k8s/configmaps-webhook.yaml b/AuthBridge/demos/webhook/k8s/configmaps-webhook.yaml index 406c5a897..016ce5ebf 100644 --- a/AuthBridge/demos/webhook/k8s/configmaps-webhook.yaml +++ b/AuthBridge/demos/webhook/k8s/configmaps-webhook.yaml @@ -17,11 +17,19 @@ 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-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: KEYCLOAK_ADMIN_USERNAME: "admin" - KEYCLOAK_ADMIN_PASSWORD: "admin" + KEYCLOAK_ADMIN_PASSWORD: "changeme" --- # authbridge-config ConfigMap - Used by envoy-proxy for token exchange and inbound validation diff --git a/CLAUDE.md b/CLAUDE.md index aa00d288a..42e8e3222 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/charts/kagenti-webhook/templates/clusterrole.yaml b/charts/kagenti-webhook/templates/clusterrole.yaml index 780ee2705..6056c0401 100644 --- a/charts/kagenti-webhook/templates/clusterrole.yaml +++ b/charts/kagenti-webhook/templates/clusterrole.yaml @@ -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"] diff --git a/kagenti-webhook/CLAUDE.md b/kagenti-webhook/CLAUDE.md index 6ee4a72f2..fd7f3770b 100644 --- a/kagenti-webhook/CLAUDE.md +++ b/kagenti-webhook/CLAUDE.md @@ -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. diff --git a/kagenti-webhook/config/rbac/role.yaml b/kagenti-webhook/config/rbac/role.yaml index 36af9f0a5..77114f2d2 100644 --- a/kagenti-webhook/config/rbac/role.yaml +++ b/kagenti-webhook/config/rbac/role.yaml @@ -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"] diff --git a/kagenti-webhook/internal/webhook/injector/container_builder.go b/kagenti-webhook/internal/webhook/injector/container_builder.go index ea176f2ff..999f16f57 100644 --- a/kagenti-webhook/internal/webhook/injector/container_builder.go +++ b/kagenti-webhook/internal/webhook/injector/container_builder.go @@ -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", }, @@ -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", }, diff --git a/kagenti-webhook/internal/webhook/injector/container_builder_test.go b/kagenti-webhook/internal/webhook/injector/container_builder_test.go index 14dc63eee..43d833466 100644 --- a/kagenti-webhook/internal/webhook/injector/container_builder_test.go +++ b/kagenti-webhook/internal/webhook/injector/container_builder_test.go @@ -107,3 +107,48 @@ func TestBuildEnvoyProxyContainer_Name(t *testing.T) { t.Errorf("container name = %q, want %q", container.Name, EnvoyProxyContainerName) } } + +func TestBuildClientRegistrationContainer_AdminCredentialsFromSecret(t *testing.T) { + builder := NewContainerBuilder(config.CompiledDefaults()) + container := builder.BuildClientRegistrationContainer("my-app", "my-ns") + + sensitiveKeys := []string{"KEYCLOAK_ADMIN_USERNAME", "KEYCLOAK_ADMIN_PASSWORD"} + for _, key := range sensitiveKeys { + for _, env := range container.Env { + if env.Name != key { + continue + } + 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 env.ValueFrom.ConfigMapKeyRef != nil { + t.Errorf("env %q must NOT use ConfigMapKeyRef", key) + } + } + } +} + +func TestBuildClientRegistrationContainer_NonSensitiveKeysFromConfigMap(t *testing.T) { + builder := NewContainerBuilder(config.CompiledDefaults()) + container := builder.BuildClientRegistrationContainer("my-app", "my-ns") + + nonSensitiveKeys := []string{"KEYCLOAK_URL", "KEYCLOAK_REALM"} + for _, key := range nonSensitiveKeys { + for _, env := range container.Env { + if env.Name != key { + continue + } + 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") + } + } + } +}