Documentation
¶
Overview ¶
Package webhook provides the webhook adapter component that bridges the pure webhook library to the event-driven controller architecture.
The webhook component manages the lifecycle of admission webhooks including:
- HTTPS webhook server
- Integration with controller validators
Note: TLS certificates are fetched from Kubernetes Secret via API. ValidatingWebhookConfiguration is created by Helm at installation time.
Index ¶
Constants ¶
const ( // ComponentName is the unique identifier for this component. ComponentName = "webhook" // DefaultWebhookPort is the default HTTPS port for the webhook server. DefaultWebhookPort = 9443 // DefaultWebhookPath is the default URL path for validation requests. DefaultWebhookPath = "/validate" )
const HAProxyTemplateConfigGVK = "haproxy-haptic.org/v1alpha1.HAProxyTemplateConfig"
HAProxyTemplateConfigGVK is the canonical GVK string the webhook server uses to dispatch admission requests for the controller's own CRD.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Component ¶
type Component struct {
// contains filtered or unexported fields
}
Component is the webhook adapter component that manages webhook lifecycle.
It coordinates the pure webhook library server with the event-driven controller architecture.
func New ¶
func New(logger *slog.Logger, config *Config, restMapper meta.RESTMapper, metrics MetricsRecorder) *Component
New creates a new webhook component.
Parameters:
- logger: Structured logger
- config: Component configuration (must include CertPEM and KeyPEM)
- restMapper: RESTMapper for resolving resource kinds from GVR
- metrics: Optional metrics recorder (can be nil)
Returns:
- A new Component instance ready to be started
func (*Component) Listening ¶
func (c *Component) Listening() <-chan struct{}
Listening returns a channel that is closed once the underlying webhook server has bound its TLS listener. Until this channel is closed, an admission request routed at the controller fails with "connection refused", because the listening socket simply doesn't exist yet.
type Config ¶
type Config struct {
// Port is the HTTPS port for the webhook server.
// Default: 9443
Port int
// Path is the URL path for validation requests.
// Default: "/validate"
Path string
// CertDir, when set, is a directory containing tls.crt and tls.key
// (typically the mounted webhook-cert Secret). The server reads them
// per-handshake and hot-reloads a rotated certificate without a restart.
// Takes precedence over CertPEM/KeyPEM; production uses this path.
CertDir string
// CertPEM is the PEM-encoded TLS certificate.
// Used when CertDir is unset (e.g. tests pass certs directly).
CertPEM []byte
// KeyPEM is the PEM-encoded TLS private key.
// Used when CertDir is unset.
KeyPEM []byte
// Rules defines which resources the webhook validates.
// Used for registering validators by GVK.
Rules []WebhookRule
// DryRunValidator performs dry-run validation of resources.
// If nil, validation is skipped (fail-open).
DryRunValidator DryRunValidator
// ConfigValidator validates HAProxyTemplateConfig admission requests.
// If nil, HAProxyTemplateConfig admission is admitted unconditionally
// (no handler registered → pure server's fail-open path). The chart
// pairs this with failurePolicy=Ignore so missing controller doesn't
// break the chicken-and-egg of first install / recovery.
ConfigValidator ConfigValidatorFunc
}
Config configures the webhook component.
type ConfigValidator ¶
type ConfigValidator struct {
// contains filtered or unexported fields
}
ConfigValidator validates a prospective HAProxyTemplateConfig admission by:
- Parsing the admitted CRD spec into a *config.Config.
- Compiling an ephemeral template engine from the prospective templates.
- Building an ephemeral RenderService + Pipeline (strict ValidationService) and executing it against the controller's CURRENT resource stores.
- Mapping the result to an admission decision.
This is the upstream gate that lets the leader-side reconcile pipeline safely skip `haproxy -c`.
Failure-policy on the chart-side ValidatingWebhookConfiguration is `Ignore`, so this validator MUST be safe to be entirely absent: when the webhook is unreachable, the dataplane API still runs `haproxy -c` server-side before accepting any /raw push, and the controller surfaces the resulting failure via HAProxyCfg.status.
func NewConfigValidator ¶
func NewConfigValidator(cfg *ConfigValidatorConfig) *ConfigValidator
NewConfigValidator constructs a ConfigValidator. Panics if any required field is nil — these are construction-time mistakes that should fail loudly, not at admission time.
func (*ConfigValidator) ValidateDirect ¶
func (v *ConfigValidator) ValidateDirect(ctx context.Context, gvk, namespace, name string, object any, operation string) (allowed bool, reason string, warnings []string)
ValidateDirect performs synchronous validation of a prospective HAProxyTemplateConfig admission. Returns the same triple the webhook component's DryRunValidator interface returns so it plugs into the same ValidationFunc bridge.
On DELETE: admits without rendering — the deletion can't render anything. On CREATE/UPDATE: parses the prospective CRD, builds an ephemeral pipeline, executes against live stores, maps result to a decision.
type ConfigValidatorConfig ¶
type ConfigValidatorConfig struct {
// Logger is the structured logger.
Logger *slog.Logger
// StrictValidator is the strict ValidationService used for the
// admission webhook (full syntax + schema + `haproxy -c` semantic
// validation). MUST be the strict instance — the whole point of this
// gate is to catch bad templates that the fast leader-side pipeline
// would skip.
StrictValidator *validation.ValidationService
// StoreProvider grants access to the controller's live resource
// stores. The prospective config is rendered against current cluster
// state — no overlay is applied (unlike DryRunValidator, which is
// validating a hypothetical resource addition/update).
StoreProvider stores.StoreProvider
// Capabilities are the HAProxy version capabilities the ephemeral
// RenderService needs to compute capability-conditional output.
Capabilities dataplane.Capabilities
// HTTPStoreComponent (optional) wires `http.Fetch` for templates that
// use it. Pass the live controller's instance — admission renders
// against accepted HTTP-store content, same as DryRunValidator.
HTTPStoreComponent *ctrlhttpstore.Component
// Declarations carries the typed-resource globals from typebootstrap
// (and the currentConfig declaration). The ephemeral engine MUST be
// constructed with these so chart templates compile identically
// against either render path.
Declarations map[string]any
// TypedResourceTypes is the per-resource generated Go type map fed
// to the ephemeral RenderService so it wraps each store snapshot
// into the typed shape expected by templates. Used as the fallback
// when Bootstrap is nil or its live-schema resolution fails.
TypedResourceTypes map[string]reflect.Type
// Bootstrap (optional) resolves typed schemas from the PROSPECTIVE config
// at admission, so admission builds the engine from the same type set the
// daemon load gate will use on load (true parity — see SchemaBootstrapper).
// When nil, the validator falls back to the startup-fixed Declarations /
// TypedResourceTypes (the prior behavior; still correct for configs that
// don't change their own typed watchedResources). Production wires the live
// bootstrapper; unit tests may leave it nil or pass a stub.
Bootstrap SchemaBootstrapper
}
ConfigValidatorConfig wires the ConfigValidator's dependencies. All fields are required except HTTPStoreComponent (templates that use `http.Fetch` need it; chart-default templates do not).
type ConfigValidatorFunc ¶
type ConfigValidatorFunc func(ctx context.Context, gvk, namespace, name string, object any, operation string) (allowed bool, reason string, warnings []string)
ConfigValidatorFunc validates a HAProxyTemplateConfig admission request. Same signature shape as DryRunValidator.ValidateDirect — kept as a function type rather than an interface so test wiring can pass a plain closure without declaring a satellite type. Nil means "no validator configured" — handler falls back to allow (failurePolicy=Ignore on the chart-side ValidatingWebhookConfiguration covers the remaining gap).
type DryRunValidator ¶
type DryRunValidator interface {
ValidateDirect(ctx context.Context, gvk, namespace, name string, object any, operation string) (allowed bool, reason string, warnings []string)
}
DryRunValidator defines the synchronous interface the webhook uses to validate resources. The implementation in pkg/controller/dryrunvalidator is a library, not a lifecycle component.
Warnings are surfaced via AdmissionResponse.Warnings on both allow and deny paths so kubectl prints them as "Warning:" lines without blocking admission. Pluggable validators (e.g. the SPOA hub running in --validate-socket mode) populate this slice from their non-fatal diagnostics; the standard render+validate pipeline does not.
type MetricsRecorder ¶
type MetricsRecorder interface {
RecordWebhookRequest(gvk, result string, durationSeconds float64)
RecordWebhookValidation(gvk, result string)
}
MetricsRecorder defines the interface for recording webhook metrics. This allows the component to work with or without metrics.
type SchemaBootstrapper ¶
type SchemaBootstrapper func(ctx context.Context, cfg *coreconfig.Config) (*typebootstrap.Result, error)
SchemaBootstrapper resolves the typed-resource reflect.Types and declarations for a prospective config from live schemas. It mirrors the validator.TypeBootstrapper the daemon load gate uses; the two share the same underlying signature so the controller wiring can convert one to the other.
When set on a ConfigValidator, admission bootstraps schemas from the PROSPECTIVE config — exactly as the load gate does on load — so the two gates build the test/render engine from the same type set. This is what makes the "a config the webhook admits will load" guarantee hold even when the prospective config changes its own typed `watchedResources` relative to what the controller was started with. Without it, the webhook validated against the startup-fixed type set, which could both false-reject a config that adds a new typed watched resource and false-admit one whose tests pass under the stale types but fail under the new ones.
type WebhookRule ¶
type WebhookRule struct {
// APIGroup that this rule matches.
// Example: "networking.k8s.io"
APIGroup string
// APIVersion that this rule matches.
// Example: "v1"
APIVersion string
// Resource that this rule matches (plural, lowercase).
// Example: "ingresses"
Resource string
}
WebhookRule specifies which resources a webhook should intercept.
func ExtractWebhookRules ¶
func ExtractWebhookRules(cfg *config.Config) []WebhookRule
ExtractWebhookRules extracts webhook rules from controller configuration.
It iterates through watched resources and creates webhook rules for resources with enable_validation_webhook: true.
Parameters:
- cfg: Controller configuration containing watched resources
Returns:
- Slice of webhook rules for resources that have validation enabled
- Empty slice if no resources have webhook validation enabled