Documentation
¶
Overview ¶
Package webhook provides a pure library for Kubernetes admission webhooks.
This package implements an HTTPS webhook server with flexible validation handlers, without dependencies on other project packages. It can be used in any Kubernetes controller project.
The package provides:
- Generic webhook server with configurable validation
- AdmissionReview v1 request/response handling
- ValidationContext with full admission request details
- Thread-safe concurrent request handling
External dependencies required (not provided by this library):
- TLS certificates (from cert-manager, Kubernetes Secret, or Helm)
- ValidatingWebhookConfiguration (via Helm chart or kubectl apply)
Example usage:
// Load certificates from external source (Kubernetes Secret)
secret, err := client.CoreV1().Secrets("default").Get(ctx, "webhook-certs", metav1.GetOptions{})
certPEM := secret.Data["tls.crt"]
keyPEM := secret.Data["tls.key"]
// Create webhook server
server := webhook.NewServer(&webhook.ServerConfig{
Port: 9443,
CertPEM: certPEM,
KeyPEM: keyPEM,
})
// Register validator with full context
server.RegisterValidator("networking.k8s.io/v1.Ingress", func(ctx *webhook.ValidationContext) (bool, string, error) {
// Validation logic with access to operation type and old/new objects
if ctx.Operation == "UPDATE" && ctx.OldObject != nil {
// Implement immutability checks
}
return true, "", nil
})
// Start server
server.Start(ctx)
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is an HTTPS webhook server that validates Kubernetes resources.
The server handles AdmissionReview requests from the Kubernetes API server and calls registered validation functions to determine whether resources should be admitted.
The server is thread-safe and can handle multiple concurrent requests.
The certificate is resolved per TLS handshake through getCertificate. When ServerConfig.CertDir is set, that callback reloads tls.crt/tls.key from disk on change, so a rotated certificate (cert-manager renewal written to a mounted Secret) is served without restarting. Otherwise getCertificate returns a fixed certificate parsed once from CertPEM/KeyPEM. Either way the certificate is validated eagerly in NewServer so a malformed cert surfaces there rather than at the first handshake.
func NewServer ¶
func NewServer(config *ServerConfig) (*Server, error)
NewServer creates a new webhook server with the given configuration.
The server will not start until Start() is called. The certificate is loaded eagerly — from CertDir (reloading, on change) or from CertPEM/KeyPEM (fixed) — so configuration errors surface here rather than at the first TLS handshake.
func (*Server) Listening ¶
func (s *Server) Listening() <-chan struct{}
Listening returns a channel that is closed once the TLS listener has been bound to the configured port. Until this channel is closed, admission requests sent to the server's address fail with "connection refused". The controller uses this signal so its Pod readiness probe only flips healthy after the webhook is actually reachable.
func (*Server) RegisterValidator ¶
func (s *Server) RegisterValidator(gvk string, fn ValidationFunc)
RegisterValidator registers a validation function for a specific resource type.
The gvk parameter should be in the format "version.Kind" (e.g., "v1.Ingress"). For resources with a group, use "group/version.Kind" (e.g., "networking.k8s.io/v1.Ingress").
If a validator is already registered for this gvk, it will be replaced.
This method is thread-safe.
func (*Server) Start ¶
Start starts the HTTPS webhook server.
The server binds to the configured port synchronously, closes the channel returned by Listening() to signal readiness, and then serves in a background goroutine. The method blocks until the server is shut down (context cancellation) or the serve loop returns an error.
Splitting bind from serve matters because the Pod readiness probe must not flip healthy until admission is reachable — otherwise the API server starts routing AdmissionReview requests at the controller before net.Listen has returned, and every request bounces with "connection refused" until the listener finally binds. Callers that need to gate on the bind read Listening().
type ServerConfig ¶
type ServerConfig struct {
// Port is the HTTPS port to listen on.
// Default: 9443
Port int
// BindAddress is the address to bind to.
// Default: "0.0.0.0"
BindAddress string
// CertDir, when set, is a directory containing tls.crt and tls.key. The
// server reads them through a reloading GetCertificate callback, so a
// rotated certificate (e.g. a cert-manager renewal written to a mounted
// Secret) is picked up on the next handshake without a restart. Takes
// precedence over CertPEM/KeyPEM.
CertDir string
// CertPEM is the PEM-encoded server certificate.
// Required unless CertDir is set.
CertPEM []byte
// KeyPEM is the PEM-encoded private key.
// Required unless CertDir is set.
KeyPEM []byte
// Path is the URL path for the webhook endpoint.
// Default: "/validate"
Path string
// ReadTimeout is the maximum duration for reading the entire request.
// Default: 10s
ReadTimeout time.Duration
// WriteTimeout is the maximum duration before timing out writes of the response.
// Default: 10s
WriteTimeout time.Duration
}
ServerConfig configures the webhook HTTPS server.
type ValidationContext ¶
type ValidationContext struct {
// Object is the resource object being validated (new version).
// For CREATE: the object being created
// For UPDATE: the new version of the object
// For DELETE: the object being deleted
// Stored as unstructured.Unstructured (same type as resource stores use).
Object *unstructured.Unstructured
// OldObject is the existing version of the resource (for UPDATE/DELETE operations).
// For CREATE: nil
// For UPDATE: the current version in the cluster
// For DELETE: the object being deleted (same as Object)
// Stored as unstructured.Unstructured (same type as resource stores use).
OldObject *unstructured.Unstructured
// Operation indicates the admission operation type.
// Values: "CREATE", "UPDATE", "DELETE", "CONNECT"
Operation string
// Namespace is the namespace of the resource (empty for cluster-scoped resources).
Namespace string
// Name is the name of the resource.
// May be empty for CREATE operations using generateName.
Name string
// UID is a unique identifier for this admission request.
// Can be used for correlation and logging.
UID string
// UserInfo contains information about the user making the request.
// Includes username, UID, groups, and extra fields.
// Can be used for authorization decisions.
UserInfo authenticationv1.UserInfo
}
ValidationContext provides the complete context for validating a Kubernetes resource.
This includes the resource object, operation type, and related metadata from the AdmissionRequest. This allows validators to make informed decisions based on the full context of the admission request.
type ValidationFunc ¶
type ValidationFunc func(ctx *ValidationContext) (allowed bool, reason string, warnings []string, err error)
ValidationFunc is called to validate a Kubernetes resource admission request.
Parameters:
- ctx: The validation context with full admission request information
Returns:
- allowed: Whether the resource should be admitted
- reason: Human-readable reason for denial (empty if allowed)
- warnings: Soft warnings surfaced via AdmissionResponse.Warnings. Returned for both allowed and denied responses so kubectl prints them as "Warning:" lines without blocking admission. Each entry should fit in 256 chars; over that, the API server truncates.
- err: Error during validation (500 response if non-nil)
The function receives complete context including both old and new objects, operation type, and metadata. This allows validators to implement sophisticated validation logic based on the admission operation.
Example:
func validateIngress(ctx *webhook.ValidationContext) (bool, string, []string, error) {
// Access new object (already unstructured.Unstructured)
if ctx.Object == nil {
return false, "", nil, errors.New("object is nil")
}
// For UPDATE operations, compare with old object
if ctx.Operation == "UPDATE" && ctx.OldObject != nil {
// Both ctx.Object and ctx.OldObject are *unstructured.Unstructured
// Validate the change...
}
spec, found, err := unstructured.NestedMap(ctx.Object.Object, "spec")
if err != nil || !found {
return false, "spec is required", nil, nil
}
return true, "", nil, nil
}