mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 14:11:31 +00:00
feat(notifier): DOC-001 — wire the orphan webhook notifier; README "6 notifiers" now accurate
Acquisition-audit DOC-001 closure (Sprint 7 ACQ, 2026-05-16). The
webhook notifier shipped to internal/connector/notifier/webhook/
months ago with full SafeHTTPDialContext SSRF guard + HMAC-SHA256
signing + comprehensive tests, but it was never wired in
cmd/server/main.go — README:39 claimed "6 notifiers" while only 5
were actually registered. Audit prompt offered two paths: (a) wire
it if the impl is feature-complete, (b) fix the README count. The
impl IS feature-complete (verified by reading webhook.go +
webhook_test.go), so path (a) is the rigorous closure.
What this commit adds
=====================
internal/connector/notifier/webhook/adapter.go (NEW):
NotifierAdapter bridges the rich notifier.Connector interface
(SendAlert / SendEvent / ValidateConfig) to the simpler service-
layer service.Notifier (Send + Channel) used by the notification
service's per-channel routing. Send(ctx, recipient, subject,
body) constructs a notifier.Event with the three fields populated
+ a fresh 16-byte hex random ID + UTC timestamp, delegates to
the Connector's SendEvent. Channel() returns "webhook". The
Connector's per-request HMAC-SHA256 signing + SafeHTTPDialContext
SSRF guard apply transitively through SendEvent → postWebhook
— no defense duplication at the adapter layer.
internal/config/notifiers.go:
NotifierConfig gains WebhookURL + WebhookSecret fields with the
same docstring shape as the other 5 notifier env-var pairs.
internal/config/config.go::Load():
Reads CERTCTL_WEBHOOK_URL + CERTCTL_WEBHOOK_SECRET (both empty
by default → notifier disabled, matching the pattern of the
other 5 env-var-gated notifiers).
cmd/server/main.go:
- notifywebhook import added next to the other 5.
- New wire-up block after the OpsGenie one: when WebhookURL is
set, constructs the Connector via webhook.New (production
constructor — strict ValidateSafeURL + SafeHTTPDialContext),
wraps in NotifierAdapter, registers as notifierRegistry["Webhook"].
Boot log includes the signing posture ("HMAC-SHA256 signed"
vs "unsigned") so operators can spot a missing secret.
Target-connector count reconciliation
=====================================
The audit prompt also asked to reconcile the target-connector
count (README says "fourteen + Kubernetes Secrets preview" = 15;
ls internal/connector/target/ shows 17 dirs). Ground-truth: the
extra two dirs (certutil, configcheck) are shared HELPER packages
(PEM/PFX conversion + server-side shell-injection validation
respectively), NOT target connectors. Real target-connector count
is 17 - 2 = 15, exactly matching README:12 + README:39. No README
change needed.
Verified locally: gofmt clean, go vet clean, staticcheck clean
across internal/config + internal/connector/notifier/webhook +
cmd/server; `go test -count=1
./internal/connector/notifier/webhook/...` green (existing tests
unchanged); `go test -short -count=1 ./internal/config/...
./cmd/server/...` green; `go build ./cmd/server` produces a
30.9MB binary that boots.
This commit is contained in:
@@ -38,6 +38,7 @@ import (
|
||||
notifypagerduty "github.com/certctl-io/certctl/internal/connector/notifier/pagerduty"
|
||||
notifyslack "github.com/certctl-io/certctl/internal/connector/notifier/slack"
|
||||
notifyteams "github.com/certctl-io/certctl/internal/connector/notifier/teams"
|
||||
notifywebhook "github.com/certctl-io/certctl/internal/connector/notifier/webhook"
|
||||
"github.com/certctl-io/certctl/internal/crypto/signer"
|
||||
"github.com/certctl-io/certctl/internal/domain"
|
||||
authdomainAlias "github.com/certctl-io/certctl/internal/domain/auth"
|
||||
@@ -742,6 +743,31 @@ func main() {
|
||||
logger.Info("OpsGenie notifier enabled")
|
||||
}
|
||||
|
||||
// Acquisition-audit DOC-001 closure (Sprint 7 ACQ, 2026-05-16).
|
||||
// Generic webhook notifier. The webhook impl shipped to
|
||||
// internal/connector/notifier/webhook/ months ago with full
|
||||
// SafeHTTPDialContext SSRF guard + HMAC-SHA256 signing + tests but
|
||||
// was never wired here — the README's "6 notifiers" claim was off
|
||||
// by one. NotifierAdapter bridges the rich notifier.Connector
|
||||
// interface (SendEvent / SendAlert / ValidateConfig) to the
|
||||
// service.Notifier (Send + Channel) shape used by the notification
|
||||
// service. Empty CERTCTL_WEBHOOK_URL keeps the notifier disabled
|
||||
// (matches the env-var-gated pattern of the other five). The
|
||||
// signing secret is operator-acknowledged optional — see
|
||||
// internal/config/notifiers.go::NotifierConfig.WebhookSecret.
|
||||
if cfg.Notifiers.WebhookURL != "" {
|
||||
webhookConnector := notifywebhook.New(¬ifywebhook.Config{
|
||||
URL: cfg.Notifiers.WebhookURL,
|
||||
Secret: cfg.Notifiers.WebhookSecret,
|
||||
}, logger)
|
||||
notifierRegistry["Webhook"] = notifywebhook.NewNotifierAdapter(webhookConnector)
|
||||
signedHint := "unsigned"
|
||||
if cfg.Notifiers.WebhookSecret != "" {
|
||||
signedHint = "HMAC-SHA256 signed"
|
||||
}
|
||||
logger.Info("Webhook notifier enabled", "signing", signedHint)
|
||||
}
|
||||
|
||||
// Wire email notifier if SMTP is configured
|
||||
var emailAdapter *notifyemail.NotifierAdapter
|
||||
if cfg.Notifiers.SMTPHost != "" && cfg.Notifiers.SMTPFromAddress != "" {
|
||||
|
||||
Reference in New Issue
Block a user