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
30 changes: 11 additions & 19 deletions charts/kagenti-webhook/templates/authbridge-mutatingwebhook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ webhooks:
namespace: {{ include "kagenti-webhook.namespace" . }}
path: /mutate-workloads-authbridge
failurePolicy: Fail
reinvocationPolicy: IfNeeded
timeoutSeconds: 10
sideEffects: None
# Exclude system namespaces and the webhook's own namespace from mutation.
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
Expand All @@ -29,36 +29,28 @@ webhooks:
- kube-public
- kube-node-lease
- {{ include "kagenti-webhook.namespace" . }}
# Only intercept workloads explicitly labelled as agent or tool.
# Per-sidecar eligibility is then decided inside the handler via the
# two-stage precedence chain (feature gates + workload opt-out labels).
matchLabels:
# Only trigger webhook for namespaces that have opted-in
# This aligns with the precedence evaluator Layer 3 which requires kagenti-enabled: true
kagenti-enabled: "true"
Comment thread
cwiklik marked this conversation as resolved.
objectSelector:
matchExpressions:
- key: kagenti.io/type
operator: In
values:
- agent
- tool
- key: kagenti.io/inject
operator: NotIn
values:
- disabled
rules:
- operations:
- CREATE
- UPDATE
apiGroups:
- apps
apiVersions:
- v1
resources:
- deployments
- statefulsets
- daemonsets
- operations:
- CREATE
- UPDATE
apiGroups:
- batch
- ""
apiVersions:
- v1
resources:
- jobs
- cronjobs
- pods
{{- end }}
5 changes: 1 addition & 4 deletions charts/kagenti-webhook/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ rules:
resources: ["serviceaccounts"]
verbs: ["get", "create", "update"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
resources: ["replicasets"]
verbs: ["get", "list", "watch"]
{{- end }}
19 changes: 4 additions & 15 deletions kagenti-webhook/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,10 @@ graph TB
end

subgraph "Kubernetes Resources"
subgraph "Standard Workloads"
DEPLOY[Deployments]
STS[StatefulSets]
DS[DaemonSets]
JOB[Jobs/CronJobs]
end
POD[Pods at CREATE]
end

API -->|mutate workloads| AUTH
API -->|mutate pods| AUTH

MAIN -->|creates & shares| MUTATOR
MAIN -->|registers| AUTH
Expand All @@ -43,19 +38,13 @@ graph TB
MUTATOR -->|builds containers| CONT
MUTATOR -->|builds volumes| VOL

AUTH -.->|modifies| DEPLOY
AUTH -.->|modifies| STS
AUTH -.->|modifies| DS
AUTH -.->|modifies| JOB
AUTH -.->|modifies| POD

style MUTATOR fill:#90EE90
style AUTH fill:#32CD32,stroke:#006400,stroke-width:3px
style CONT fill:#FFB6C1
style VOL fill:#FFB6C1
style DEPLOY fill:#87CEEB
style STS fill:#87CEEB
style DS fill:#87CEEB
style JOB fill:#87CEEB
style POD fill:#87CEEB
```

## Container Injection Flow
Expand Down
19 changes: 8 additions & 11 deletions kagenti-webhook/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ There is one registered webhook:

| Webhook | Path | Handles |
|---------|------|---------|
| **AuthBridge** | `/mutate-workloads-authbridge` | Deployments, StatefulSets, DaemonSets, Jobs, CronJobs |
| **AuthBridge** | `/mutate-workloads-authbridge` | Pods at CREATE time (works with any workload controller) |

The `PodMutator` instance is created in `cmd/main.go` and passed to the webhook setup function.

Expand Down Expand Up @@ -68,7 +68,8 @@ kagenti-webhook/
│ │ ├── container_builder.go # Build* functions for each injected container
│ │ └── volume_builder.go # BuildRequiredVolumes / BuildRequiredVolumesNoSpire
│ └── v1alpha1/ # Webhook handlers
│ ├── authbridge_webhook.go # AuthBridge: raw admission.Handler
│ ├── authbridge_webhook.go # AuthBridge: raw admission.Handler (Pod-level)
│ ├── authbridge_webhook_test.go # Webhook handler tests (Ginkgo)
│ └── webhook_suite_test.go # ENVTEST-based test setup (Ginkgo)
├── config/ # Kustomize manifests (CRDs, RBAC, webhook configs, etc.)
├── test/
Expand Down Expand Up @@ -199,20 +200,16 @@ Secrets:
5. Update `isAlreadyInjected()` in `authbridge_webhook.go` to check for the new container name.
6. Update `internal/webhook/config/types.go` and `defaults.go` with image/resource defaults.

### Adding a New Supported Workload Type
1. Add a new `case` in `AuthBridgeWebhook.Handle()` in `authbridge_webhook.go`.
2. Update the kubebuilder webhook marker to include the new resource in the `resources` list.
3. Run `make manifests` to regenerate webhook configuration YAML.
4. Update `scripts/webhook-rollout.sh` to include the new resource in the webhook rules.
5. Update the Helm chart template `charts/kagenti-webhook/templates/authbridge-mutatingwebhook.yaml`.
### Webhook Targeting Model
The webhook targets **Pods at CREATE time** (not Deployments/StatefulSets/etc.). This follows the same pattern used by Istio, Linkerd, and Vault Agent Injector. The handler decodes `corev1.Pod` directly — no switch on workload Kind. The `deriveWorkloadName()` helper extracts the workload name from `GenerateName` (trims trailing `-`). The `reinvocationPolicy` is set to `IfNeeded` so our webhook re-runs if other webhooks modify the Pod after our first pass.

### Modifying Injection Logic
- Injection decision logic lives in `pod_mutator.go` in `InjectAuthBridge()`.
- Changes to label/annotation keys require updating the constants at the top of `pod_mutator.go`.

### Updating Container Images
- Default images are defined as constants in `injector/container_builder.go` (`DefaultEnvoyImage`, `DefaultProxyInitImage`) and inline in `BuildSpiffeHelperContainer()` and `BuildClientRegistrationContainerWithSpireOption()`.
- The `internal/webhook/config/defaults.go` file has a parallel set of defaults in `CompiledDefaults()` -- keep them in sync (or wire the config system into the injector, which is a TODO).
- Default images are defined in `internal/webhook/config/defaults.go` via `CompiledDefaults()`. The `ContainerBuilder` reads from `*config.PlatformConfig` at build time — never hardcode images/ports/resources in the builder.
- The config system is fully wired in: `PodMutator` uses getter functions `func() *config.PlatformConfig` and creates a new `ContainerBuilder` per request with the current config snapshot, enabling hot-reload.
- The GitHub Actions CI builds images defined in `../.github/workflows/build.yaml`.

### Helm Chart
Expand All @@ -226,7 +223,7 @@ Secrets:

2. **Kubebuilder markers**: The `+kubebuilder:webhook` comments generate webhook manifests. If you change the path, resources, or groups, you must run `make manifests` to regenerate.

3. **AuthBridge uses raw admission.Handler**: Unlike webhooks that use `CustomDefaulter`/`CustomValidator`, the AuthBridge webhook registers directly via `mgr.GetWebhookServer().Register()`. This is because it handles multiple resource types in a single handler.
3. **AuthBridge uses raw admission.Handler**: Unlike webhooks that use `CustomDefaulter`/`CustomValidator`, the AuthBridge webhook registers directly via `mgr.GetWebhookServer().Register()`. It decodes `corev1.Pod` directly and includes a Kind guard for defense-in-depth against stale webhook configs.

4. **Idempotency check**: `isAlreadyInjected()` checks for all four injected components (`envoy-proxy`, `spiffe-helper`, `kagenti-client-registration` in sidecar containers, `proxy-init` in init containers). If any one is found, re-admission is short-circuited.

Expand Down
35 changes: 20 additions & 15 deletions kagenti-webhook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A Kubernetes admission webhook that automatically injects sidecar containers to

## Overview

This webhook provides security by automatically injecting sidecar containers that handle identity and authentication. It supports both standard Kubernetes workloads (Deployments, StatefulSets, etc.) and custom resources.
This webhook provides security by automatically injecting sidecar containers that handle identity and authentication. It intercepts **Pod CREATE** requests (not Deployments or other workload objects), which eliminates GitOps drift — the workload object in etcd remains exactly as the developer defined it, and sidecars are only visible at the Pod level.

The webhook injects:

Expand All @@ -25,17 +25,23 @@ Previous versions required `kagenti.io/inject: enabled` to trigger sidecar injec

**Impact on upgrade:** Existing agent workloads that relied on the *absence* of `kagenti.io/inject: enabled` to avoid injection will now receive sidecars. To preserve the previous behavior, add `kagenti.io/inject: disabled` to those workloads before upgrading.

## Supported Resources
### BREAKING CHANGE: Namespace opt-in required

The webhook now requires namespaces to have the `kagenti-enabled: "true"` label to receive sidecar injection. Existing namespaces that previously received injection will **silently stop** unless labeled.

**Migration step:** Label each namespace where agent workloads run:

```bash
kubectl label ns <namespace> kagenti-enabled=true
```

The webhook supports sidecar injection for:
The `webhook-rollout.sh` script automatically labels the AuthBridge demo namespace when `AUTHBRIDGE_DEMO=true` is set.

## Supported Resources

The **AuthBridge webhook** supports standard Kubernetes workload resources:
The **AuthBridge webhook** intercepts **Pod CREATE** requests in the core API group (`v1`). It injects sidecars into Pods created by any workload controller — Deployments, StatefulSets, DaemonSets, Jobs, and CronJobs all produce Pods that the webhook can mutate.

- **Deployments** (apps/v1)
- **StatefulSets** (apps/v1)
- **DaemonSets** (apps/v1)
- **Jobs** (batch/v1)
- **CronJobs** (batch/v1)
This Pod-level targeting follows the same pattern used by Istio, Linkerd, and Vault Agent Injector. Because the webhook mutates Pods (not the parent Deployment), GitOps tools like Argo CD and Flux see no drift on the workload object stored in etcd.

## Injection Control

Expand Down Expand Up @@ -206,7 +212,6 @@ The AuthBridge webhook supports two modes of operation:
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Your Application Container │ │
│ │ (Deployment/StatefulSet/Job/etc.) │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
│ │ │
Expand All @@ -232,7 +237,6 @@ The AuthBridge webhook supports two modes of operation:
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Your Application Container │ │
│ │ (Deployment/StatefulSet/Job/etc.) │ │
│ └──────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────┘
Expand Down Expand Up @@ -347,8 +351,9 @@ The script handles the full build-and-deploy cycle:
2. Loads the image into the Kind cluster
3. Deploys the platform defaults ConfigMap (`kagenti-webhook-defaults`)
4. Deploys the feature gates ConfigMap (`kagenti-webhook-feature-gates`)
5. Updates the deployment image and patches in config volume mounts
6. Applies the AuthBridge `MutatingWebhookConfiguration` (always, so updates take effect on re-runs)
5. Applies the AuthBridge `MutatingWebhookConfiguration` (before image rollout to avoid race conditions)
6. Updates the deployment image and patches in config volume mounts
7. Waits for rollout to complete

```bash
cd kagenti-webhook
Expand Down Expand Up @@ -435,8 +440,8 @@ The `InjectAuthBridge()` method supports:

- Init container injection (proxy-init)
- Sidecar container injection (envoy-proxy, spiffe-helper, client-registration)
- Optional SPIRE integration via pod labels
- Support for standard Kubernetes workloads (Deployments, StatefulSets, DaemonSets, Jobs, CronJobs)
- Optional SPIRE integration via pod labels and feature gates
- Pod-level mutation at CREATE time (works with any workload controller)

## Uninstallation

Expand Down
31 changes: 3 additions & 28 deletions kagenti-webhook/config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,14 @@ webhooks:
path: /mutate-workloads-authbridge
failurePolicy: Fail
name: inject.kagenti.io
namespaceSelector:
matchExpressions:
# Exclude system namespaces.
- key: kubernetes.io/metadata.name
operator: NotIn
values:
- kube-system
- kube-public
- kube-node-lease
# Only intercept workloads explicitly labelled as agent or tool.
# Per-sidecar eligibility is decided inside the handler via the two-stage
# precedence chain (feature gates + workload opt-out labels).
objectSelector:
matchExpressions:
- key: kagenti.io/type
operator: In
values:
- agent
- tool
timeoutSeconds: 10
reinvocationPolicy: IfNeeded
Comment thread
cwiklik marked this conversation as resolved.
Comment thread
cwiklik marked this conversation as resolved.
rules:
- apiGroups:
- apps
- batch
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- deployments
- statefulsets
- daemonsets
- jobs
- cronjobs
- pods
sideEffects: None
Loading
Loading