Skip to content

fix: roll connector deployment when webhook secret is rotated - #74

Merged
DominikPinsel merged 1 commit into
mainfrom
fix/connector-secret-rotation
Aug 10, 2026
Merged

fix: roll connector deployment when webhook secret is rotated#74
DominikPinsel merged 1 commit into
mainfrom
fix/connector-secret-rotation

Conversation

@DominikPinsel

Copy link
Copy Markdown
Owner

Problem

The "Generate new" token button on connector detail pages appeared broken: the newly generated webhook token failed signature validation from then on, while the old token kept working.

Root cause: the connector's webhook-receiver pod reads the HMAC secret from the WEBHOOK_SECRET environment variable, which Kubernetes only resolves at pod startup. POST /connectors/{id}/rotate-secret updated the K8s Secret object but never rolled the connector Deployment — so the running pod kept validating against the old secret indefinitely.

Fix

event-gateway operator (webhookconnector_controller.go)

  • Stamp the connector Deployment's pod template with an ainsel.dev/webhook-secret-hash annotation (SHA-256 of the referenced secret's value — the hash, not the credential). Any change to the hash alters the pod template, forcing a rolling restart so new pods resolve the rotated secret.
  • Watch Secrets and map webhook HMAC secrets back to their connector (ainsel.dev/connector label first, falling back to the connector-<id>-webhook-hmac naming convention for pre-existing unlabeled secrets), so a rotation triggers a reconcile automatically.

hub (handlers_connectors.go, secrets.go)

  • Label newly created webhook secrets with ainsel.dev/connector: <id> for robust event mapping.
  • Add the previously missing requireWrite authorization check on the rotate-secret endpoint (PUT/DELETE already had it).

Tests

  • Operator (envtest): annotation stamped with correct hash; empty hash tolerated when the secret doesn't exist yet; rotating the secret changes the pod template annotation (rollout triggered); secret-to-connector mapping by label, by naming convention, and unrelated secrets ignored (9/9 specs pass).
  • Hub: secret carries the connector label after create; existing rotate tests still pass. Full hub + operator suites and golangci-lint clean.

Operational note

No chart/RBAC changes needed: the operator already had get;list;watch on secrets. Existing connectors with unlabeled secrets are covered by the naming-convention fallback.

The webhook-receiver pod reads the HMAC secret from the WEBHOOK_SECRET
env var, which is only resolved at pod startup. Rotating the connector
secret updated the K8s Secret but never rolled the deployment, so the
new token kept failing signature validation while the old one stayed
valid — making "Generate new" tokens appear unpersisted/invalid.

- operator: stamp the pod template with a sha256 hash of the referenced
  webhook secret and watch secrets (mapped via the ainsel.dev/connector
  label, with the connector-<id>-webhook-hmac naming convention as
  fallback) so a rotation triggers a reconcile and rolling restart
- hub: label created webhook secrets with ainsel.dev/connector and apply
  the previously missing requireWrite authz check on rotate-secret
@DominikPinsel

Copy link
Copy Markdown
Owner Author

@review-agent please have a look

@DominikPinsel
DominikPinsel merged commit a325861 into main Aug 10, 2026
5 checks passed
@DominikPinsel
DominikPinsel deleted the fix/connector-secret-rotation branch August 10, 2026 21:49

@ainsel-reviewer ainsel-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review: fix: roll connector deployment when webhook secret is rotated

Verdict: APPROVE (submitted post-merge; review run was blocked earlier by missing write credentials).

Clean, well-scoped change: the operator now hashes the webhook HMAC secret into a pod-template annotation (ainsel.dev/webhook-secret-hash) so secret rotation deterministically rolls the connector Deployment, with the connector back-linked via the ainsel.dev/connector label and covered by new controller + handler tests.

Two minor, non-blocking suggestions inline.

Labels: labels,
// Changing the annotation forces a rolling restart so
// the pod picks up the (possibly rotated) secret.
Annotations: map[string]string{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: this replaces the pod template's entire Annotations map on every reconcile. Fine today (no other annotations), but if a second annotation is ever added to the pod template it would be wiped here. Merging the key into the existing map would make this future-proof. Not a blocker.

// webhookConnectorLabel links a webhook HMAC secret back to its
// WebhookConnector. The event-gateway operator uses it to map secret events
// (e.g. rotations) to the owning connector so the deployment can roll.
const webhookConnectorLabel = "ainsel.dev/connector"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: this constant is duplicated in the event-gateway operator (webhookConnectorLabel). If both services already depend on shared/, consider defining forge-agreed label/annotation keys there once. Not a blocker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant