1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/webhooks/server.go
Charles-Edouard Brétéché a08c8b03fe
refactor: move all middlewares in handlers sub package (#5244)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
2022-11-07 14:53:40 +00:00

222 lines
8.3 KiB
Go

package webhooks
import (
"context"
"crypto/tls"
"net/http"
"time"
"github.com/go-logr/logr"
"github.com/julienschmidt/httprouter"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/toggle"
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
runtimeutils "github.com/kyverno/kyverno/pkg/utils/runtime"
"github.com/kyverno/kyverno/pkg/webhooks/handlers"
admissionv1 "k8s.io/api/admission/v1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
coordinationv1 "k8s.io/api/coordination/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// DebugModeOptions holds the options to configure debug mode
type DebugModeOptions struct {
// DumpPayload is used to activate/deactivate debug mode.
DumpPayload bool
}
type Server interface {
// Run TLS server in separate thread and returns control immediately
Run(<-chan struct{})
// Stop TLS server and returns control after the server is shut down
Stop(context.Context)
// Cleanup returns the chanel used to wait for the server to clean up resources
Cleanup() <-chan struct{}
}
type PolicyHandlers interface {
// Mutate performs the mutation of policy resources
Mutate(logr.Logger, *admissionv1.AdmissionRequest, time.Time) *admissionv1.AdmissionResponse
// Validate performs the validation check on policy resources
Validate(logr.Logger, *admissionv1.AdmissionRequest, time.Time) *admissionv1.AdmissionResponse
}
type ResourceHandlers interface {
// Mutate performs the mutation of kube resources
Mutate(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse
// Validate performs the validation check on kube resources
Validate(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse
}
type server struct {
server *http.Server
runtime runtimeutils.Runtime
mwcClient controllerutils.DeleteClient[*admissionregistrationv1.MutatingWebhookConfiguration]
vwcClient controllerutils.DeleteClient[*admissionregistrationv1.ValidatingWebhookConfiguration]
leaseClient controllerutils.DeleteClient[*coordinationv1.Lease]
cleanUp chan struct{}
}
type TlsProvider func() ([]byte, []byte, error)
// NewServer creates new instance of server accordingly to given configuration
func NewServer(
policyHandlers PolicyHandlers,
resourceHandlers ResourceHandlers,
configuration config.Configuration,
debugModeOpts DebugModeOptions,
tlsProvider TlsProvider,
mwcClient controllerutils.DeleteClient[*admissionregistrationv1.MutatingWebhookConfiguration],
vwcClient controllerutils.DeleteClient[*admissionregistrationv1.ValidatingWebhookConfiguration],
leaseClient controllerutils.DeleteClient[*coordinationv1.Lease],
runtime runtimeutils.Runtime,
) Server {
mux := httprouter.New()
resourceLogger := logger.WithName("resource")
policyLogger := logger.WithName("policy")
verifyLogger := logger.WithName("verify")
registerWebhookHandlers(resourceLogger.WithName("mutate"), mux, config.MutatingWebhookServicePath, configuration, resourceHandlers.Mutate, debugModeOpts)
registerWebhookHandlers(resourceLogger.WithName("validate"), mux, config.ValidatingWebhookServicePath, configuration, resourceHandlers.Validate, debugModeOpts)
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, admission(policyLogger.WithName("mutate"), filter(configuration, policyHandlers.Mutate), debugModeOpts))
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, admission(policyLogger.WithName("validate"), filter(configuration, policyHandlers.Validate), debugModeOpts))
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, admission(verifyLogger.WithName("mutate"), handlers.Verify(), DebugModeOptions{}))
mux.HandlerFunc("GET", config.LivenessServicePath, handlers.Probe(runtime.IsLive))
mux.HandlerFunc("GET", config.ReadinessServicePath, handlers.Probe(runtime.IsReady))
return &server{
server: &http.Server{
Addr: ":9443",
TLSConfig: &tls.Config{
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
certPem, keyPem, err := tlsProvider()
if err != nil {
return nil, err
}
pair, err := tls.X509KeyPair(certPem, keyPem)
if err != nil {
return nil, err
}
return &pair, nil
},
MinVersion: tls.VersionTLS12,
},
Handler: mux,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
ReadHeaderTimeout: 30 * time.Second,
IdleTimeout: 5 * time.Minute,
ErrorLog: logging.StdLogger(logger.WithName("server"), ""),
},
mwcClient: mwcClient,
vwcClient: vwcClient,
leaseClient: leaseClient,
runtime: runtime,
cleanUp: make(chan struct{}),
}
}
func (s *server) Run(stopCh <-chan struct{}) {
go func() {
logger.V(3).Info("started serving requests", "addr", s.server.Addr)
if err := s.server.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
logger.Error(err, "failed to listen to requests")
}
}()
logger.Info("starting service")
}
func (s *server) Stop(ctx context.Context) {
s.cleanup(ctx)
err := s.server.Shutdown(ctx)
if err != nil {
logger.Error(err, "shutting down server")
err = s.server.Close()
if err != nil {
logger.Error(err, "server shut down failed")
}
}
}
func (s *server) Cleanup() <-chan struct{} {
return s.cleanUp
}
func (s *server) cleanup(ctx context.Context) {
if s.runtime.IsGoingDown() {
deleteLease := func(name string) {
if err := s.leaseClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
logger.Error(err, "failed to clean up lease", "name", name)
}
}
deleteVwc := func(name string) {
if err := s.vwcClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
logger.Error(err, "failed to clean up validating webhook configuration", "name", name)
}
}
deleteMwc := func(name string) {
if err := s.mwcClient.Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
logger.Error(err, "failed to clean up mutating webhook configuration", "name", name)
}
}
deleteLease("kyvernopre-lock")
deleteLease("kyverno-health")
deleteVwc(config.ValidatingWebhookConfigurationName)
deleteVwc(config.PolicyValidatingWebhookConfigurationName)
deleteMwc(config.MutatingWebhookConfigurationName)
deleteMwc(config.PolicyMutatingWebhookConfigurationName)
deleteMwc(config.VerifyMutatingWebhookConfigurationName)
}
close(s.cleanUp)
}
func dump(inner handlers.AdmissionHandler, debugModeOpts DebugModeOptions) handlers.AdmissionHandler {
// debug mode not enabled, no need to add debug middleware
if !debugModeOpts.DumpPayload {
return inner
}
return handlers.Dump(inner)
}
func protect(inner handlers.AdmissionHandler) handlers.AdmissionHandler {
if !toggle.ProtectManagedResources.Enabled() {
return inner
}
return handlers.Protect(inner)
}
func filter(configuration config.Configuration, inner handlers.AdmissionHandler) handlers.AdmissionHandler {
return handlers.Filter(configuration, inner)
}
func admission(logger logr.Logger, inner handlers.AdmissionHandler, debugModeOpts DebugModeOptions) http.HandlerFunc {
return handlers.Admission(logger, dump(protect(inner), debugModeOpts))
}
func registerWebhookHandlers(
logger logr.Logger,
mux *httprouter.Router,
basePath string,
configuration config.Configuration,
handlerFunc func(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse,
debugModeOpts DebugModeOptions,
) {
mux.HandlerFunc("POST", basePath, admission(logger, filter(
configuration,
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
return handlerFunc(logger, request, "all", startTime)
}), debugModeOpts),
)
mux.HandlerFunc("POST", basePath+"/fail", admission(logger, filter(
configuration,
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
return handlerFunc(logger, request, "fail", startTime)
}), debugModeOpts),
)
mux.HandlerFunc("POST", basePath+"/ignore", admission(logger, filter(
configuration,
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
return handlerFunc(logger, request, "ignore", startTime)
}), debugModeOpts),
)
}