-
Notifications
You must be signed in to change notification settings - Fork 7
fix: unify webhook listeners on path based endpoints #304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,30 @@ | ||
| # Webhook Source | ||
|
|
||
| This guide explains how to set up the Webhook notification source for the Reloader component in your environment. Using Webhooks as a notification source allows you to trigger secret rotation events via HTTP calls to your Webhook endpoint. | ||
| This guide explains how to set up the Webhook notification source for the Reloader component in your environment. Using webhooks as a notification source lets you trigger secret rotation events by sending HTTP POST requests to the Reloader process. | ||
|
|
||
| ## How it works | ||
|
|
||
| The controller runs a **single shared HTTP server** for all `Config` resources. The listen address is set with the controller flag **`--webhook-bind-address`** (default `:8082`). Each cluster-scoped `Config` is exposed at: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a mismatch between 8082 and whatever the helm template is setting up with 8090.. Something is fishy there. Please double check. |
||
|
|
||
| `POST /webhook/<Config.metadata.name>` | ||
|
|
||
| There is no per-CR URL path or bind address; callers use the `Config` name in the path. | ||
|
|
||
| ## Configuration | ||
|
|
||
| To configure a Webhook as a notification source, the Reloader needs to be provided with the URL path to listen on, as well as the identifier in the payload that refers to the secret being rotated. | ||
| Configure a `NotificationSource` with `type: Webhook` and a `webhook` block. The main field is **`identifierPathOnPayload`** (JSON path in the body where the secret name appears). | ||
|
|
||
| ### Key Fields | ||
| ### Key fields | ||
|
|
||
| * **path**: Specifies the Webhook path that the Reloader will listen to. This is the endpoint where Webhook notifications will be received. | ||
| * **identifierPathOnPayload**: Defines the key in the payload that contains the secret identifier. The identifier must match the name of the secret being rotated. By default, the path is `0.data.ObjectName`. | ||
| * **identifierPathOnPayload**: JSON path in the POST body for the secret identifier. It must match the name of the secret being rotated. If omitted, the default path is `0.data.ObjectName`. | ||
| * **webhookAuth** (optional): Basic or bearer authentication for incoming requests. | ||
| * **retryPolicy** (optional): Retry failed publishes to the internal event channel. | ||
|
|
||
| ### Payload Structure | ||
| ### Payload structure | ||
|
|
||
| The Webhook notification must contain a payload with a secret identifier. The Reloader will extract this identifier based on the path defined in the configuration. | ||
| The POST body must be JSON containing the secret identifier at the configured path. | ||
|
|
||
| #### Example Payload | ||
| #### Example payload | ||
|
|
||
| ```json | ||
| { | ||
|
|
@@ -27,14 +36,14 @@ The Webhook notification must contain a payload with a secret identifier. The Re | |
| } | ||
| ``` | ||
|
|
||
| In this example, the Webhook payload contains a secret identifier at `0.data.ObjectName`, which corresponds to the secret named `my-secret`. The Reloader will use this identifier to rotate the appropriate secret. | ||
| Here the identifier is at `0.data.ObjectName`, matching the secret name `my-secret`. | ||
|
|
||
| ### Triggering a webhook notification | ||
|
|
||
| To trigger a secret rotation, send an HTTP POST request to the Webhook endpoint you've configured. | ||
| Send an HTTP POST to the Reloader webhook base URL with path `/webhook/<your-config-name>`. | ||
|
|
||
| ```bash | ||
| curl -X POST https://your-rotator-endpoint/webhook \ | ||
| curl -X POST "http://<reloader-host>:<webhook-port>/webhook/my-reloader-config" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "0": { | ||
|
|
@@ -45,6 +54,12 @@ curl -X POST https://your-rotator-endpoint/webhook \ | |
| }' | ||
| ``` | ||
|
|
||
| Once this request is received by the Reloader, it will extract the secret identifier and proceed with the rotation process for the specified secret. | ||
| Replace `my-reloader-config` with the `metadata.name` of your `Config` CR. | ||
|
|
||
| ### Helm | ||
|
|
||
| If you use the chart under `deploy/charts/reloader`, set **`service.webhook.enabled: true`**. The chart then adds **`--webhook-bind-address`** and a **`webhook`** container port using **`service.webhook.listenPort`** (default `8090`, aligned with the optional `*-webhook` Service). You can still override the flag with **`extraArgs`** if needed. | ||
|
|
||
| There is no default “main” HTTP `Service` on port 8080. **`ingress.enabled`** requires **`service.webhook.enabled`**: the Ingress targets the **`{{ release }}-webhook`** Service on **`service.webhook.port`** (paths such as **`/webhook/...`**). | ||
|
|
||
| Any service that can call an endpoint can trigger the rotation as long as you configure the keys accordingly. | ||
| Any client that can reach the Service or host on that port can trigger rotation as long as the JSON path and optional auth match your `Config`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| apiVersion: reloader.external-secrets.io/v1alpha1 | ||
| kind: Config | ||
| metadata: | ||
| name: keeper # will listen on the webhook at /webhook/keeper | ||
| spec: | ||
| notificationSources: | ||
| - type: Webhook | ||
| webhook: | ||
| identifierPathOnPayload: "record_uid" | ||
| destinationsToWatch: | ||
| - type: ExternalSecret | ||
| externalSecret: | ||
| labelSelectors: | ||
| matchLabels: {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| module github.com/external-secrets/reloader | ||
|
|
||
| go 1.26.1 | ||
| go 1.26.2 | ||
|
|
||
| require ( | ||
| cloud.google.com/go/iam v1.8.0 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ import ( | |
| "github.com/external-secrets/reloader/internal/events" | ||
| "github.com/external-secrets/reloader/internal/handler" | ||
| "github.com/external-secrets/reloader/internal/listener" | ||
| "github.com/external-secrets/reloader/internal/listener/webhook" | ||
| apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| "k8s.io/apimachinery/pkg/types" | ||
|
|
@@ -38,26 +39,28 @@ type ReloaderReconciler struct { | |
|
|
||
| // Internal fields | ||
| listenerManager *listener.Manager | ||
| webhookServer *webhook.WebhookServer | ||
|
|
||
| // eventChan is a channel that transports SecretRotationEvent instances between various parts of the system, such as event handlers and listeners. | ||
| eventChan chan events.SecretRotationEvent | ||
| eventHandler *handler.EventHandler | ||
| } | ||
|
|
||
| // NewReloaderReconciler creates a new ReloaderReconciler with the default factory. | ||
| func NewReloaderReconciler(client client.Client, scheme *runtime.Scheme) *ReloaderReconciler { | ||
| func NewReloaderReconciler(client client.Client, scheme *runtime.Scheme, hook *webhook.WebhookServer) *ReloaderReconciler { | ||
| return &ReloaderReconciler{ | ||
| Client: client, | ||
| Scheme: scheme, | ||
| eventChan: make(chan events.SecretRotationEvent), | ||
| eventHandler: handler.NewEventHandler(client), | ||
| Client: client, | ||
| Scheme: scheme, | ||
| webhookServer: hook, | ||
| eventChan: make(chan events.SecretRotationEvent), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this needs a sane buffer. 🤔 Especially since it's a shared channel. |
||
| eventHandler: handler.NewEventHandler(client), | ||
| } | ||
| } | ||
|
|
||
| // SetupWithManager sets up the controller with the Manager. | ||
| func (r *ReloaderReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
| ctx, cancel := context.WithCancel(context.Background()) | ||
| r.listenerManager = listener.NewListenerManager(ctx, r.eventChan, r.Client, log.FromContext(ctx)) | ||
| r.listenerManager = listener.NewListenerManager(ctx, r.eventChan, r.Client, log.FromContext(ctx), r.webhookServer) | ||
|
|
||
| // Start a goroutine to process events | ||
| go r.processEvents(ctx) | ||
|
|
@@ -107,17 +110,18 @@ func (r *ReloaderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c | |
|
|
||
| if err := r.Get(ctx, req.NamespacedName, &cfg); err != nil { | ||
| if apierrors.IsNotFound(err) { | ||
| if err := r.listenerManager.StopAll(); err != nil { | ||
| // Object is gone (e.g. after finalizer). Only tear down listeners for this Config — not all Configs. | ||
| manifestName := types.NamespacedName{ | ||
| Namespace: req.Namespace, | ||
| Name: req.Name, | ||
| } | ||
| if err := r.listenerManager.ManageListeners(manifestName, nil); err != nil { | ||
| return ctrl.Result{}, err | ||
| } | ||
| return ctrl.Result{}, nil | ||
| } | ||
|
|
||
| // Error reading the object - requeue the request. | ||
| if !apierrors.IsNotFound(err) { | ||
| logger.Error(err, "unable to fetch Reloader deployment") | ||
| return ctrl.Result{}, err | ||
| } | ||
| logger.Error(err, "unable to fetch Config") | ||
| return ctrl.Result{}, err | ||
| } | ||
| if cfg.DeletionTimestamp != nil && controllerutil.ContainsFinalizer(&cfg, reloaderFinalizer) { | ||
| // Handle any cleanup logic here, as this is a DELETE request | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will warrant 2.0.0.