diff --git a/cmd/cluster-agent/admission/server.go b/cmd/cluster-agent/admission/server.go index 06f7924a4a612..34a33896c93f0 100644 --- a/cmd/cluster-agent/admission/server.go +++ b/cmd/cluster-agent/admission/server.go @@ -20,6 +20,7 @@ import ( authenticationv1 "k8s.io/api/authentication/v1" + admicommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/metrics" "github.com/DataDog/datadog-agent/pkg/config" "github.com/DataDog/datadog-agent/pkg/util/kubernetes/apiserver/common" @@ -29,7 +30,6 @@ import ( admiv1 "k8s.io/api/admission/v1" admiv1beta1 "k8s.io/api/admission/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/dynamic" @@ -38,8 +38,8 @@ import ( const jsonContentType = "application/json" -// ValidateRequest contains the information of a validation request -type ValidateRequest struct { +// Request contains the information of a validation request +type Request struct { // Raw is the raw request object Raw []byte // Name is the name of the object @@ -54,27 +54,8 @@ type ValidateRequest struct { APIClient kubernetes.Interface } -// MutateRequest contains the information of a mutation request -type MutateRequest struct { - // Raw is the raw request object - Raw []byte - // Name is the name of the object - Name string - // Namespace is the namespace of the object - Namespace string - // UserInfo contains information about the requesting user - UserInfo *authenticationv1.UserInfo - // DynamicClient holds a dynamic Kubernetes client - DynamicClient dynamic.Interface - // APIClient holds a Kubernetes client - APIClient kubernetes.Interface -} - -// ValidatingWebhookFunc is the function that runs the validation webhook logic -type ValidatingWebhookFunc func(request *ValidateRequest) (bool, error) - -// MutatingWebhookFunc is the function that runs the mutating webhook logic -type MutatingWebhookFunc func(request *MutateRequest) ([]byte, error) +// WebhookFunc is the generic function type that runs either a validating or mutating webhook logic +type WebhookFunc func(request *Request) *admiv1.AdmissionResponse // Server TODO type Server struct { @@ -109,19 +90,11 @@ func (s *Server) initDecoder() { s.decoder = serializer.NewCodecFactory(scheme).UniversalDeserializer() } -// RegisterValidatingWebhook adds a Validating admission webhook handler. -// It must be called to register the desired webhook handlers before calling Run. -func (s *Server) RegisterValidatingWebhook(uri string, webhookName string, f ValidatingWebhookFunc, dc dynamic.Interface, apiClient kubernetes.Interface) { - s.mux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) { - s.validateHandler(w, r, webhookName, f, dc, apiClient) - }) -} - -// RegisterMutatingWebhook adds a Mutating admission webhook handler. +// RegisterWebhook adds a Validating admission webhook handler. // It must be called to register the desired webhook handlers before calling Run. -func (s *Server) RegisterMutatingWebhook(uri string, webhookName string, f MutatingWebhookFunc, dc dynamic.Interface, apiClient kubernetes.Interface) { +func (s *Server) RegisterWebhook(uri string, webhookName string, webhookType admicommon.WebhookType, f WebhookFunc, dc dynamic.Interface, apiClient kubernetes.Interface) { s.mux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) { - s.mutateHandler(w, r, webhookName, f, dc, apiClient) + s.Handle(w, r, webhookName, webhookType, f, dc, apiClient) }) } @@ -161,22 +134,32 @@ func (s *Server) Run(mainCtx context.Context, client kubernetes.Interface) error return server.Shutdown(shutdownCtx) } -// validateHandler contains the main logic responsible for handling validation requests. +// Handle contains the main logic responsible for handling admission requests. // It supports both v1 and v1beta1 requests. -func (s *Server) validateHandler(w http.ResponseWriter, r *http.Request, webhookName string, validateFunc ValidatingWebhookFunc, dc dynamic.Interface, apiClient kubernetes.Interface) { - metrics.ValidatingWebhooksReceived.Inc(webhookName) +func (s *Server) Handle(w http.ResponseWriter, r *http.Request, webhookName string, webhookType admicommon.WebhookType, webhookFunc WebhookFunc, dc dynamic.Interface, apiClient kubernetes.Interface) { + // Increment the metrics for the received webhook + if webhookType == admicommon.ValidatingWebhook { + metrics.ValidatingWebhooksReceived.Inc(webhookName) + } else if webhookType == admicommon.MutatingWebhook { + metrics.MutatingWebhooksReceived.Inc(webhookName) + } + // Measure the time it takes to process the request start := time.Now() defer func() { - metrics.ValidatingWebhooksResponseDuration.Observe(time.Since(start).Seconds(), webhookName) + if webhookType == admicommon.ValidatingWebhook { + metrics.ValidatingWebhooksResponseDuration.Observe(time.Since(start).Seconds(), webhookName) + } else if webhookType == admicommon.MutatingWebhook { + metrics.MutatingWebhooksResponseDuration.Observe(time.Since(start).Seconds(), webhookName) + } }() + // Validate admission request if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) log.Warnf("Invalid method %s, only POST requests are allowed", r.Method) return } - body, err := io.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -184,13 +167,13 @@ func (s *Server) validateHandler(w http.ResponseWriter, r *http.Request, webhook return } defer r.Body.Close() - if contentType := r.Header.Get("Content-Type"); contentType != jsonContentType { w.WriteHeader(http.StatusBadRequest) log.Warnf("Unsupported content type %s, only %s is supported", contentType, jsonContentType) return } + // Deserialize admission request obj, gvk, err := s.decoder.Decode(body, nil, nil) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -198,6 +181,7 @@ func (s *Server) validateHandler(w http.ResponseWriter, r *http.Request, webhook return } + // Handle the request based on GroupVersionKind var response runtime.Object switch *gvk { case admiv1.SchemeGroupVersion.WithKind("AdmissionReview"): @@ -207,26 +191,7 @@ func (s *Server) validateHandler(w http.ResponseWriter, r *http.Request, webhook } admissionReviewResp := &admiv1.AdmissionReview{} admissionReviewResp.SetGroupVersionKind(*gvk) - validateRequest := ValidateRequest{ - Raw: admissionReviewReq.Request.Object.Raw, - Name: admissionReviewReq.Request.Name, - Namespace: admissionReviewReq.Request.Namespace, - UserInfo: &admissionReviewReq.Request.UserInfo, - DynamicClient: dc, - APIClient: apiClient, - } - validation, err := validateFunc(&validateRequest) - admissionReviewResp.Response = validationResponse(validation, err) - admissionReviewResp.Response.UID = admissionReviewReq.Request.UID - response = admissionReviewResp - case admiv1beta1.SchemeGroupVersion.WithKind("AdmissionReview"): - admissionReviewReq, ok := obj.(*admiv1beta1.AdmissionReview) - if !ok { - log.Errorf("Expected v1beta1.AdmissionReview, got type %T", obj) - } - admissionReviewResp := &admiv1beta1.AdmissionReview{} - admissionReviewResp.SetGroupVersionKind(*gvk) - mutateRequest := ValidateRequest{ + admissionRequest := Request{ Raw: admissionReviewReq.Request.Object.Raw, Name: admissionReviewReq.Request.Name, Namespace: admissionReviewReq.Request.Namespace, @@ -234,81 +199,18 @@ func (s *Server) validateHandler(w http.ResponseWriter, r *http.Request, webhook DynamicClient: dc, APIClient: apiClient, } - validation, err := validateFunc(&mutateRequest) - admissionReviewResp.Response = responseV1ToV1beta1(validationResponse(validation, err)) - admissionReviewResp.Response.UID = admissionReviewReq.Request.UID - response = admissionReviewResp - default: - log.Errorf("Group version kind %v is not supported", gvk) - w.WriteHeader(http.StatusBadRequest) - return - } - - encoder := json.NewEncoder(w) - err = encoder.Encode(&response) - if err != nil { - log.Warnf("Failed to encode the response: %v", err) - w.WriteHeader(http.StatusInternalServerError) - return - } -} -// mutateHandler contains the main logic responsible for handling mutation requests. -// It supports both v1 and v1beta1 requests. -func (s *Server) mutateHandler(w http.ResponseWriter, r *http.Request, webhookName string, mutateFunc MutatingWebhookFunc, dc dynamic.Interface, apiClient kubernetes.Interface) { - metrics.MutatingWebhooksReceived.Inc(webhookName) - - start := time.Now() - defer func() { - metrics.MutatingWebhooksResponseDuration.Observe(time.Since(start).Seconds(), webhookName) - }() - - if r.Method != http.MethodPost { - w.WriteHeader(http.StatusMethodNotAllowed) - log.Warnf("Invalid method %s, only POST requests are allowed", r.Method) - return - } - - body, err := io.ReadAll(r.Body) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - log.Warnf("Could not read request body: %v", err) - return - } - defer r.Body.Close() - - if contentType := r.Header.Get("Content-Type"); contentType != jsonContentType { - w.WriteHeader(http.StatusBadRequest) - log.Warnf("Unsupported content type %s, only %s is supported", contentType, jsonContentType) - return - } - - obj, gvk, err := s.decoder.Decode(body, nil, nil) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - log.Warnf("Could not deserialize request: %v", err) - return - } - - var response runtime.Object - switch *gvk { - case admiv1.SchemeGroupVersion.WithKind("AdmissionReview"): - admissionReviewReq, ok := obj.(*admiv1.AdmissionReview) - if !ok { - log.Errorf("Expected v1.AdmissionReview, got type %T", obj) + // Generate admission response + if webhookType == admicommon.ValidatingWebhook { + validation := webhookFunc(&admissionRequest) + admissionReviewResp.Response = validation + } else if webhookType == admicommon.MutatingWebhook { + mutationResponse := webhookFunc(&admissionRequest) + admissionReviewResp.Response = mutationResponse + } else { + log.Errorf("Invalid webhook type %v", webhookType) + w.WriteHeader(http.StatusInternalServerError) } - admissionReviewResp := &admiv1.AdmissionReview{} - admissionReviewResp.SetGroupVersionKind(*gvk) - mutateRequest := MutateRequest{ - Raw: admissionReviewReq.Request.Object.Raw, - Name: admissionReviewReq.Request.Name, - Namespace: admissionReviewReq.Request.Namespace, - UserInfo: &admissionReviewReq.Request.UserInfo, - DynamicClient: dc, - APIClient: apiClient, - } - jsonPatch, err := mutateFunc(&mutateRequest) - admissionReviewResp.Response = mutationResponse(jsonPatch, err) admissionReviewResp.Response.UID = admissionReviewReq.Request.UID response = admissionReviewResp case admiv1beta1.SchemeGroupVersion.WithKind("AdmissionReview"): @@ -318,7 +220,7 @@ func (s *Server) mutateHandler(w http.ResponseWriter, r *http.Request, webhookNa } admissionReviewResp := &admiv1beta1.AdmissionReview{} admissionReviewResp.SetGroupVersionKind(*gvk) - mutateRequest := MutateRequest{ + admissionRequest := Request{ Raw: admissionReviewReq.Request.Object.Raw, Name: admissionReviewReq.Request.Name, Namespace: admissionReviewReq.Request.Namespace, @@ -326,8 +228,18 @@ func (s *Server) mutateHandler(w http.ResponseWriter, r *http.Request, webhookNa DynamicClient: dc, APIClient: apiClient, } - jsonPatch, err := mutateFunc(&mutateRequest) - admissionReviewResp.Response = responseV1ToV1beta1(mutationResponse(jsonPatch, err)) + + // Generate admission response + if webhookType == admicommon.ValidatingWebhook { + validation := webhookFunc(&admissionRequest) + admissionReviewResp.Response = responseV1ToV1beta1(validation) + } else if webhookType == admicommon.MutatingWebhook { + mutationResponse := webhookFunc(&admissionRequest) + admissionReviewResp.Response = responseV1ToV1beta1(mutationResponse) + } else { + log.Errorf("Invalid webhook type %v", webhookType) + w.WriteHeader(http.StatusInternalServerError) + } admissionReviewResp.Response.UID = admissionReviewReq.Request.UID response = admissionReviewResp default: @@ -345,48 +257,6 @@ func (s *Server) mutateHandler(w http.ResponseWriter, r *http.Request, webhookNa } } -// validationResponse returns the adequate v1.AdmissionResponse based on the mutation result. -func validationResponse(validation bool, err error) *admiv1.AdmissionResponse { - if err != nil { - log.Warnf("Failed to validate: %v", err) - - return &admiv1.AdmissionResponse{ - Result: &metav1.Status{ - Message: err.Error(), - }, - Allowed: false, //TODO We should have a DefaultValidation variable with false. Modify all occurences. - } - - } - - return &admiv1.AdmissionResponse{ - Allowed: validation, - } -} - -// mutationResponse returns the adequate v1.AdmissionResponse based on the mutation result. -func mutationResponse(jsonPatch []byte, err error) *admiv1.AdmissionResponse { - if err != nil { - log.Warnf("Failed to mutate: %v", err) - - return &admiv1.AdmissionResponse{ - Result: &metav1.Status{ - Message: err.Error(), - }, - Allowed: true, - } - - } - - patchType := admiv1.PatchTypeJSONPatch - - return &admiv1.AdmissionResponse{ - Patch: jsonPatch, - PatchType: &patchType, - Allowed: true, - } -} - // responseV1ToV1beta1 converts a v1.AdmissionResponse into a v1beta1.AdmissionResponse. func responseV1ToV1beta1(resp *admiv1.AdmissionResponse) *admiv1beta1.AdmissionResponse { var patchType *admiv1beta1.PatchType diff --git a/cmd/cluster-agent/subcommands/start/command.go b/cmd/cluster-agent/subcommands/start/command.go index 9d780dafcc42f..a7ca020d43b05 100644 --- a/cmd/cluster-agent/subcommands/start/command.go +++ b/cmd/cluster-agent/subcommands/start/command.go @@ -476,20 +476,16 @@ func start(log log.Component, StopCh: stopCh, } - validatingWebhooks, mutatingWebhooks, err := admissionpkg.StartControllers(admissionCtx, wmeta, pa) + webhooks, err := admissionpkg.StartControllers(admissionCtx, wmeta, pa) if err != nil { pkglog.Errorf("Could not start admission controller: %v", err) } else { // Webhook and secret controllers are started successfully - // Setup the k8s admission webhook server + // Set up the k8s admission webhook server server := admissioncmd.NewServer() - for _, webhookConf := range validatingWebhooks { - server.RegisterValidatingWebhook(webhookConf.Endpoint(), webhookConf.Name(), webhookConf.ValidateFunc(), apiCl.DynamicCl, apiCl.Cl) - } - - for _, webhookConf := range mutatingWebhooks { - server.RegisterMutatingWebhook(webhookConf.Endpoint(), webhookConf.Name(), webhookConf.MutateFunc(), apiCl.DynamicCl, apiCl.Cl) + for _, webhookConf := range webhooks { + server.RegisterWebhook(webhookConf.Endpoint(), webhookConf.Name(), webhookConf.WebhookType(), webhookConf.WebhookFunc(), apiCl.DynamicCl, apiCl.Cl) } // Start the k8s admission webhook server diff --git a/pkg/clusteragent/admission/common/const.go b/pkg/clusteragent/admission/common/const.go index b216f69ffca74..5549213f59424 100644 --- a/pkg/clusteragent/admission/common/const.go +++ b/pkg/clusteragent/admission/common/const.go @@ -8,11 +8,14 @@ // Package common defines constants and types used by the Admission Controller. package common +// WebhookType is the type of the webhook. +type WebhookType int + const ( // EnabledLabelKey pod label to disable/enable mutations at the pod level. EnabledLabelKey = "admission.datadoghq.com/enabled" - // InjectionModeLabelKey pod label to chose the config injection at the pod level. + // InjectionModeLabelKey pod label to choose the config injection at the pod level. InjectionModeLabelKey = "admission.datadoghq.com/config.mode" // LibVersionAnnotKeyFormat is the format of the library version annotation @@ -20,4 +23,9 @@ const ( // LibConfigV1AnnotKeyFormat is the format of the library config annotation LibConfigV1AnnotKeyFormat = "admission.datadoghq.com/%s-lib.config.v1" + + // ValidatingWebhook is type for ValidatingWebhook. + ValidatingWebhook WebhookType = iota + // MutatingWebhook is type for MutatingWebhook. + MutatingWebhook WebhookType = iota ) diff --git a/pkg/clusteragent/admission/common/global.go b/pkg/clusteragent/admission/common/global.go index 78b5e5cd935f1..3f966bc22921c 100644 --- a/pkg/clusteragent/admission/common/global.go +++ b/pkg/clusteragent/admission/common/global.go @@ -10,9 +10,54 @@ package common import ( "strconv" "time" + + "github.com/DataDog/datadog-agent/pkg/util/log" + + admiv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var ( // ClusterAgentStartTime records the Cluster Agent start time ClusterAgentStartTime = strconv.FormatInt(time.Now().Unix(), 10) ) + +// ValidationResponse returns the result of the validation +func ValidationResponse(validation bool, err error) *admiv1.AdmissionResponse { + if err != nil { + log.Warnf("Failed to validate: %v", err) + + return &admiv1.AdmissionResponse{ + Result: &metav1.Status{ + Message: err.Error(), + }, + Allowed: false, + } + } + + return &admiv1.AdmissionResponse{ + Allowed: validation, + } +} + +// MutationResponse returns the result of the mutation +func MutationResponse(jsonPatch []byte, err error) *admiv1.AdmissionResponse { + if err != nil { + log.Warnf("Failed to mutate: %v", err) + + return &admiv1.AdmissionResponse{ + Result: &metav1.Status{ + Message: err.Error(), + }, + Allowed: true, + } + } + + patchType := admiv1.PatchTypeJSONPatch + + return &admiv1.AdmissionResponse{ + Patch: jsonPatch, + PatchType: &patchType, + Allowed: true, + } +} diff --git a/pkg/clusteragent/admission/mutate/common/label_selectors.go b/pkg/clusteragent/admission/common/label_selectors.go similarity index 95% rename from pkg/clusteragent/admission/mutate/common/label_selectors.go rename to pkg/clusteragent/admission/common/label_selectors.go index d305427ab782b..2711cf2afdb97 100644 --- a/pkg/clusteragent/admission/mutate/common/label_selectors.go +++ b/pkg/clusteragent/admission/common/label_selectors.go @@ -10,7 +10,6 @@ package common import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" "github.com/DataDog/datadog-agent/pkg/config" ) @@ -25,7 +24,7 @@ func DefaultLabelSelectors(useNamespaceSelector bool) (namespaceSelector, object labelSelector = metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{ { - Key: common.EnabledLabelKey, + Key: EnabledLabelKey, Operator: metav1.LabelSelectorOpNotIn, Values: []string{"false"}, }, @@ -35,7 +34,7 @@ func DefaultLabelSelectors(useNamespaceSelector bool) (namespaceSelector, object // Ignore all, accept pods if they're explicitly allowed labelSelector = metav1.LabelSelector{ MatchLabels: map[string]string{ - common.EnabledLabelKey: "true", + EnabledLabelKey: "true", }, } } diff --git a/pkg/clusteragent/admission/controllers/webhook/controller_base.go b/pkg/clusteragent/admission/controllers/webhook/controller_base.go index 1e3eeb3667355..6c0fbf13cda44 100644 --- a/pkg/clusteragent/admission/controllers/webhook/controller_base.go +++ b/pkg/clusteragent/admission/controllers/webhook/controller_base.go @@ -9,9 +9,16 @@ package webhook import ( "fmt" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/validate/always_admit" + "github.com/DataDog/datadog-agent/cmd/cluster-agent/admission" + agentsidecar "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/agent_sidecar" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoscaling" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/config" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/cwsinstrumentation" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/tagsfromlabels" - admiv1 "k8s.io/api/admissionregistration/v1" + admiv1 "k8s.io/api/admission/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -22,15 +29,10 @@ import ( "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - "github.com/DataDog/datadog-agent/cmd/cluster-agent/admission" workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/metrics" - agentsidecar "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/agent_sidecar" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoinstrumentation" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/autoscaling" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/config" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/cwsinstrumentation" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/tagsfromlabels" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/validate/always_admit" "github.com/DataDog/datadog-agent/pkg/clusteragent/autoscaling/workload" "github.com/DataDog/datadog-agent/pkg/util/log" ) @@ -38,8 +40,7 @@ import ( // Controller is an interface implemented by ControllerV1 and ControllerV1beta1. type Controller interface { Run(stopCh <-chan struct{}) - EnabledValidatingWebhooks() []ValidatingWebhook - EnabledMutatingWebhooks() []MutatingWebhook + EnabledWebhooks() []Webhook } // NewController returns the adequate implementation of the Controller interface. @@ -57,14 +58,16 @@ func NewController( return NewControllerV1(client, secretInformer, admissionInterface.V1().ValidatingWebhookConfigurations(), admissionInterface.V1().MutatingWebhookConfigurations(), isLeaderFunc, isLeaderNotif, config, wmeta, pa) } return nil - // TODO Fix this + // TODO (wassim): Fix this //return NewControllerV1beta1(client, secretInformer, admissionInterface.V1beta1().MutatingWebhookConfigurations(), isLeaderFunc, isLeaderNotif, config, wmeta, pa) } -// ValidatingWebhook represents a validating webhook -type ValidatingWebhook interface { +// Webhook represents an admission webhook +type Webhook interface { // Name returns the name of the webhook Name() string + // WebhookType Type returns the type of the webhook + WebhookType() common.WebhookType // IsEnabled returns whether the webhook is enabled IsEnabled() bool // Endpoint returns the endpoint of the webhook @@ -74,64 +77,38 @@ type ValidatingWebhook interface { Resources() []string // Operations returns the operations on the resources specified for which // the webhook should be invoked - Operations() []admiv1.OperationType + Operations() []admissionregistrationv1.OperationType // LabelSelectors returns the label selectors that specify when the webhook // should be invoked LabelSelectors(useNamespaceSelector bool) (namespaceSelector *metav1.LabelSelector, objectSelector *metav1.LabelSelector) - // ValidateFunc returns the function that validates the resources - ValidateFunc() admission.ValidatingWebhookFunc + // WebhookFunc runs the logic of the webhook and returns the admission response + WebhookFunc() func(request *admission.Request) *admiv1.AdmissionResponse } -// MutatingWebhook represents a mutating webhook -type MutatingWebhook interface { - // Name returns the name of the webhook - Name() string - // IsEnabled returns whether the webhook is enabled - IsEnabled() bool - // Endpoint returns the endpoint of the webhook - Endpoint() string - // Resources returns the kubernetes resources for which the webhook should - // be invoked - Resources() []string - // Operations returns the operations on the resources specified for which - // the webhook should be invoked - Operations() []admiv1.OperationType - // LabelSelectors returns the label selectors that specify when the webhook - // should be invoked - LabelSelectors(useNamespaceSelector bool) (namespaceSelector *metav1.LabelSelector, objectSelector *metav1.LabelSelector) - // MutateFunc returns the function that mutates the resources - MutateFunc() admission.MutatingWebhookFunc -} - -// validatingWebhooks returns the list of validating webhooks. Notice that the order -// of the webhooks returned is the order in which they will be executed. -func validatingWebhooks() []ValidatingWebhook { - webhooks := []ValidatingWebhook{ - always_admit.NewWebhook(), - } - - return webhooks -} - -// mutatingWebhooks returns the list of mutating webhooks. Notice that the order -// of the webhooks returned is the order in which they will be executed. For -// now, the only restriction is that the agent sidecar webhook needs to go after -// the config one. The reason is that the volume mount for the APM socket added -// by the config webhook doesn't always work on Fargate (one of the envs where -// we use an agent sidecar), and the agent sidecar webhook needs to remove it. -func mutatingWebhooks(wmeta workloadmeta.Component, pa workload.PodPatcher) []MutatingWebhook { +// generateWebhooks returns the list of webhooks. The order of the webhooks returned +// is the order in which they will be executed. For now, the only restriction is that +// the agent sidecar webhook needs to go after the config one. +// The reason is that the volume mount for the APM socket added by the config webhook +// doesn't always work on Fargate (one of the envs where we use an agent sidecar), and +// the agent sidecar webhook needs to remove it. +func generateWebhooks(wmeta workloadmeta.Component, pa workload.PodPatcher) []Webhook { // Note: the auto_instrumentation pod injection filter is used across // multiple mutating webhooks, so we add it as a hard dependency to each // of the components that use it via the injectionFilter parameter. injectionFilter := autoinstrumentation.GetInjectionFilter() - webhooks := []MutatingWebhook{ + webhooks := []Webhook{ + // Validating webhooks + always_admit.NewWebhook(), + + // Mutating webhooks config.NewWebhook(wmeta, injectionFilter), tagsfromlabels.NewWebhook(wmeta, injectionFilter), agentsidecar.NewWebhook(), autoscaling.NewWebhook(pa), } + // APM Instrumentation webhook needs to be registered after the config webhook. apm, err := autoinstrumentation.NewWebhook(wmeta, injectionFilter) if err == nil { webhooks = append(webhooks, apm) @@ -139,6 +116,7 @@ func mutatingWebhooks(wmeta workloadmeta.Component, pa workload.PodPatcher) []Mu log.Errorf("failed to register APM Instrumentation webhook: %v", err) } + // TODO (wassim): Finish refactoring CWSInstrumentation. cws, err := cwsinstrumentation.NewCWSInstrumentation(wmeta) if err == nil { webhooks = append(webhooks, cws.WebhookForPods(), cws.WebhookForCommands()) @@ -162,28 +140,14 @@ type controllerBase struct { queue workqueue.RateLimitingInterface isLeaderFunc func() bool isLeaderNotif <-chan struct{} - validatingWebhooks []ValidatingWebhook - mutatingWebhooks []MutatingWebhook -} - -// EnabledValidatingWebhooks returns the list of enabled validating webhooks. -func (c *controllerBase) EnabledValidatingWebhooks() []ValidatingWebhook { - var res []ValidatingWebhook - - for _, webhook := range c.validatingWebhooks { - if webhook.IsEnabled() { - res = append(res, webhook) - } - } - - return res + webhooks []Webhook } -// EnabledMutatingWebhooks returns the list of enabled mutating webhooks. -func (c *controllerBase) EnabledMutatingWebhooks() []MutatingWebhook { - var res []MutatingWebhook +// EnabledWebhooks returns the list of enabled webhooks. +func (c *controllerBase) EnabledWebhooks() []Webhook { + var res []Webhook - for _, webhook := range c.mutatingWebhooks { + for _, webhook := range c.webhooks { if webhook.IsEnabled() { res = append(res, webhook) } diff --git a/pkg/clusteragent/admission/controllers/webhook/controller_v1.go b/pkg/clusteragent/admission/controllers/webhook/controller_v1.go index 97dec606bbc95..539618cf37d41 100644 --- a/pkg/clusteragent/admission/controllers/webhook/controller_v1.go +++ b/pkg/clusteragent/admission/controllers/webhook/controller_v1.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" admiv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -65,8 +66,7 @@ func NewControllerV1( controller.queue = workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "webhooks") controller.isLeaderFunc = isLeaderFunc controller.isLeaderNotif = isLeaderNotif - controller.validatingWebhooks = validatingWebhooks() - controller.mutatingWebhooks = mutatingWebhooks(wmeta, pa) + controller.webhooks = generateWebhooks(wmeta, pa) controller.generateTemplates() if _, err := secretInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -209,7 +209,7 @@ func (c *ControllerV1) updateValidatingWebhook(secret *corev1.Secret, webhook *a return err } -// newValidatingWebhooks generates ValidatingWebhook objects from config templates with updated CABundle from Secret. +// newValidatingWebhooks generates Webhook objects from config templates with updated CABundle from Secret. func (c *ControllerV1) newValidatingWebhooks(secret *corev1.Secret) []admiv1.ValidatingWebhook { webhooks := []admiv1.ValidatingWebhook{} for _, tpl := range c.validatingWebhookTemplates { @@ -246,7 +246,7 @@ func (c *ControllerV1) updateMutatingWebhook(secret *corev1.Secret, webhook *adm return err } -// newMutatingWebhooks generates MutatingWebhook objects from config templates with updated CABundle from Secret. +// newMutatingWebhooks generates Webhook objects from config templates with updated CABundle from Secret. func (c *ControllerV1) newMutatingWebhooks(secret *corev1.Secret) []admiv1.MutatingWebhook { webhooks := []admiv1.MutatingWebhook{} for _, tpl := range c.mutatingWebhookTemplates { @@ -257,17 +257,17 @@ func (c *ControllerV1) newMutatingWebhooks(secret *corev1.Secret) []admiv1.Mutat return webhooks } +// generateTemplates generates the webhook templates from the configuration. func (c *ControllerV1) generateTemplates() { // Generate validating webhook templates - vWebhooks := []admiv1.ValidatingWebhook{} - - for _, webhook := range c.validatingWebhooks { - if !webhook.IsEnabled() { + validatingWebhooks := []admiv1.ValidatingWebhook{} + for _, webhook := range c.webhooks { + if !webhook.IsEnabled() || webhook.WebhookType() != common.ValidatingWebhook { continue } nsSelector, objSelector := webhook.LabelSelectors(c.config.useNamespaceSelector()) - vWebhooks = append( - vWebhooks, + validatingWebhooks = append( + validatingWebhooks, c.getValidatingWebhookSkeleton( webhook.Name(), webhook.Endpoint(), @@ -278,19 +278,17 @@ func (c *ControllerV1) generateTemplates() { ), ) } - - c.validatingWebhookTemplates = vWebhooks + c.validatingWebhookTemplates = validatingWebhooks // Generate mutating webhook templates - mWebhooks := []admiv1.MutatingWebhook{} - - for _, webhook := range c.mutatingWebhooks { - if !webhook.IsEnabled() { + mutatingWebhooks := []admiv1.MutatingWebhook{} + for _, webhook := range c.webhooks { + if !webhook.IsEnabled() || webhook.WebhookType() != common.MutatingWebhook { continue } nsSelector, objSelector := webhook.LabelSelectors(c.config.useNamespaceSelector()) - mWebhooks = append( - mWebhooks, + mutatingWebhooks = append( + mutatingWebhooks, c.getMutatingWebhookSkeleton( webhook.Name(), webhook.Endpoint(), @@ -301,8 +299,7 @@ func (c *ControllerV1) generateTemplates() { ), ) } - - c.mutatingWebhookTemplates = mWebhooks + c.mutatingWebhookTemplates = mutatingWebhooks } func (c *ControllerV1) getValidatingWebhookSkeleton(nameSuffix, path string, operations []admiv1.OperationType, resources []string, namespaceSelector, objectSelector *metav1.LabelSelector) admiv1.ValidatingWebhook { @@ -310,7 +307,7 @@ func (c *ControllerV1) getValidatingWebhookSkeleton(nameSuffix, path string, ope sideEffects := admiv1.SideEffectClassNone port := c.config.getServicePort() timeout := c.config.getTimeout() - failurePolicy := c.getAdmiV1FailurePolicy() + failurePolicy := c.getFailurePolicy() webhook := admiv1.ValidatingWebhook{ Name: c.config.configName(nameSuffix), ClientConfig: admiv1.WebhookClientConfig{ @@ -348,7 +345,7 @@ func (c *ControllerV1) getMutatingWebhookSkeleton(nameSuffix, path string, opera sideEffects := admiv1.SideEffectClassNone port := c.config.getServicePort() timeout := c.config.getTimeout() - failurePolicy := c.getAdmiV1FailurePolicy() + failurePolicy := c.getFailurePolicy() reinvocationPolicy := c.getReinvocationPolicy() webhook := admiv1.MutatingWebhook{ Name: c.config.configName(nameSuffix), @@ -383,7 +380,7 @@ func (c *ControllerV1) getMutatingWebhookSkeleton(nameSuffix, path string, opera return webhook } -func (c *ControllerV1) getAdmiV1FailurePolicy() admiv1.FailurePolicyType { +func (c *ControllerV1) getFailurePolicy() admiv1.FailurePolicyType { policy := strings.ToLower(c.config.getFailurePolicy()) switch policy { case "ignore": diff --git a/pkg/clusteragent/admission/controllers/webhook/controller_v1_test.go b/pkg/clusteragent/admission/controllers/webhook/controller_v1_test.go index bea42d46ca9fc..a638936c3007c 100644 --- a/pkg/clusteragent/admission/controllers/webhook/controller_v1_test.go +++ b/pkg/clusteragent/admission/controllers/webhook/controller_v1_test.go @@ -962,7 +962,7 @@ func TestGenerateTemplatesV1(t *testing.T) { c := &ControllerV1{} c.config = tt.configFunc() - c.mutatingWebhooks = mutatingWebhooks(wmeta, nil) + c.webhooks = mutatingWebhooks(wmeta, nil) c.generateTemplates() assert.EqualValues(t, tt.want(), c.mutatingWebhookTemplates) diff --git a/pkg/clusteragent/admission/controllers/webhook/controller_v1beta1.go b/pkg/clusteragent/admission/controllers/webhook/controller_v1beta1.go index 27de9b871f95b..a0e70c537229e 100644 --- a/pkg/clusteragent/admission/controllers/webhook/controller_v1beta1.go +++ b/pkg/clusteragent/admission/controllers/webhook/controller_v1beta1.go @@ -53,8 +53,7 @@ func NewControllerV1beta1(client kubernetes.Interface, secretInformer coreinform controller.queue = workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "webhooks") controller.isLeaderFunc = isLeaderFunc controller.isLeaderNotif = isLeaderNotif - controller.validatingWebhooks = validatingWebhooks() - controller.mutatingWebhooks = mutatingWebhooks(wmeta, pa) + controller.webhooks = generateWebhooks(wmeta, pa) controller.generateTemplates() if _, err := secretInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -189,7 +188,7 @@ func (c *ControllerV1beta1) updateValidatingWebhook(secret *corev1.Secret, webho return err } -// newValidatingWebhooks generates ValidatingWebhook objects from config templates with updated CABundle from Secret. +// newValidatingWebhooks generates Webhook objects from config templates with updated CABundle from Secret. func (c *ControllerV1beta1) newValidatingWebhooks(secret *corev1.Secret) []admiv1beta1.ValidatingWebhook { webhooks := []admiv1beta1.ValidatingWebhook{} for _, tpl := range c.validatingWebhookTemplates { @@ -227,7 +226,7 @@ func (c *ControllerV1beta1) updateMutatingWebhook(secret *corev1.Secret, webhook return err } -// newWebhooks generates MutatingWebhook objects from config templates with updated CABundle from Secret. +// newWebhooks generates Webhook objects from config templates with updated CABundle from Secret. func (c *ControllerV1beta1) newMutatingWebhooks(secret *corev1.Secret) []admiv1beta1.MutatingWebhook { webhooks := []admiv1beta1.MutatingWebhook{} for _, tpl := range c.mutatingWebhookTemplates { @@ -241,7 +240,7 @@ func (c *ControllerV1beta1) newMutatingWebhooks(secret *corev1.Secret) []admiv1b func (c *ControllerV1beta1) generateTemplates() { webhooks := []admiv1beta1.MutatingWebhook{} - for _, webhook := range c.mutatingWebhooks { + for _, webhook := range c.webhooks { if !webhook.IsEnabled() { continue } @@ -269,7 +268,7 @@ func (c *ControllerV1beta1) getWebhookSkeleton(nameSuffix, path string, operatio sideEffects := admiv1beta1.SideEffectClassNone port := c.config.getServicePort() timeout := c.config.getTimeout() - failurePolicy := c.getAdmiV1Beta1FailurePolicy() + failurePolicy := c.getFailurePolicy() reinvocationPolicy := c.getReinvocationPolicy() webhook := admiv1beta1.MutatingWebhook{ Name: c.config.configName(nameSuffix), @@ -304,7 +303,7 @@ func (c *ControllerV1beta1) getWebhookSkeleton(nameSuffix, path string, operatio return webhook } -func (c *ControllerV1beta1) getAdmiV1Beta1FailurePolicy() admiv1beta1.FailurePolicyType { +func (c *ControllerV1beta1) getFailurePolicy() admiv1beta1.FailurePolicyType { policy := strings.ToLower(c.config.getFailurePolicy()) switch policy { case "ignore": diff --git a/pkg/clusteragent/admission/controllers/webhook/controller_v1beta1_test.go b/pkg/clusteragent/admission/controllers/webhook/controller_v1beta1_test.go index 596ccdeedb3a1..be643353e1db6 100644 --- a/pkg/clusteragent/admission/controllers/webhook/controller_v1beta1_test.go +++ b/pkg/clusteragent/admission/controllers/webhook/controller_v1beta1_test.go @@ -955,7 +955,7 @@ func TestGenerateTemplatesV1beta1(t *testing.T) { c := &ControllerV1beta1{} c.config = tt.configFunc() - c.mutatingWebhooks = mutatingWebhooks(wmeta, nil) + c.webhooks = mutatingWebhooks(wmeta, nil) c.generateTemplates() assert.EqualValues(t, tt.want(), c.mutatingWebhookTemplates) diff --git a/pkg/clusteragent/admission/mutate/agent_sidecar/agent_sidecar.go b/pkg/clusteragent/admission/mutate/agent_sidecar/agent_sidecar.go index 155d9395dde78..04da72729490a 100644 --- a/pkg/clusteragent/admission/mutate/agent_sidecar/agent_sidecar.go +++ b/pkg/clusteragent/admission/mutate/agent_sidecar/agent_sidecar.go @@ -13,19 +13,21 @@ import ( "encoding/json" "errors" "fmt" + admiv1 "k8s.io/api/admission/v1" "os" "slices" "strconv" - admiv1 "k8s.io/api/admissionregistration/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/dynamic" "github.com/DataDog/datadog-agent/cmd/cluster-agent/admission" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/metrics" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" + mutatecommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" "github.com/DataDog/datadog-agent/pkg/config" apiCommon "github.com/DataDog/datadog-agent/pkg/util/kubernetes/apiserver/common" "github.com/DataDog/datadog-agent/pkg/util/kubernetes/clustername" @@ -43,10 +45,11 @@ type Selector struct { // Webhook is the webhook that injects a Datadog Agent sidecar type Webhook struct { name string + webhookType common.WebhookType isEnabled bool endpoint string resources []string - operations []admiv1.OperationType + operations []admissionregistrationv1.OperationType namespaceSelector *metav1.LabelSelector objectSelector *metav1.LabelSelector containerRegistry string @@ -56,14 +59,15 @@ type Webhook struct { func NewWebhook() *Webhook { nsSelector, objSelector := labelSelectors() - containerRegistry := common.ContainerRegistry("admission_controller.agent_sidecar.container_registry") + containerRegistry := mutatecommon.ContainerRegistry("admission_controller.agent_sidecar.container_registry") return &Webhook{ name: webhookName, + webhookType: common.MutatingWebhook, isEnabled: config.Datadog().GetBool("admission_controller.agent_sidecar.enabled"), endpoint: config.Datadog().GetString("admission_controller.agent_sidecar.endpoint"), resources: []string{"pods"}, - operations: []admiv1.OperationType{admiv1.Create}, + operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, namespaceSelector: nsSelector, objectSelector: objSelector, containerRegistry: containerRegistry, @@ -75,6 +79,11 @@ func (w *Webhook) Name() string { return w.name } +// WebhookType returns the type of the webhook +func (w *Webhook) WebhookType() common.WebhookType { + return w.webhookType +} + // IsEnabled returns whether the webhook is enabled func (w *Webhook) IsEnabled() bool { return w.isEnabled && (w.namespaceSelector != nil || w.objectSelector != nil) @@ -93,7 +102,7 @@ func (w *Webhook) Resources() []string { // Operations returns the operations on the resources specified for which // the webhook should be invoked -func (w *Webhook) Operations() []admiv1.OperationType { +func (w *Webhook) Operations() []admissionregistrationv1.OperationType { return w.operations } @@ -103,14 +112,11 @@ func (w *Webhook) LabelSelectors(_ bool) (namespaceSelector *metav1.LabelSelecto return w.namespaceSelector, w.objectSelector } -// MutateFunc returns the function that mutates the resources -func (w *Webhook) MutateFunc() admission.MutatingWebhookFunc { - return w.mutate -} - -// mutate handles mutating pod requests for the agentsidecar webhook -func (w *Webhook) mutate(request *admission.MutateRequest) ([]byte, error) { - return common.Mutate(request.Raw, request.Namespace, w.Name(), w.injectAgentSidecar, request.DynamicClient) +// WebhookFunc returns the function that mutates the resources +func (w *Webhook) WebhookFunc() func(request *admission.Request) *admiv1.AdmissionResponse { + return func(request *admission.Request) *admiv1.AdmissionResponse { + return common.MutationResponse(mutatecommon.Mutate(request.Raw, request.Namespace, w.Name(), w.injectAgentSidecar, request.DynamicClient)) + } } func (w *Webhook) injectAgentSidecar(pod *corev1.Pod, _ string, _ dynamic.Interface) (bool, error) { diff --git a/pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation.go b/pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation.go index dc7e0e2181a82..097f50518844b 100644 --- a/pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation.go +++ b/pkg/clusteragent/admission/mutate/autoinstrumentation/auto_instrumentation.go @@ -13,12 +13,13 @@ import ( "encoding/json" "errors" "fmt" + admiv1 "k8s.io/api/admission/v1" "os" "strconv" "strings" "time" - admiv1 "k8s.io/api/admissionregistration/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -49,10 +50,11 @@ const ( // Webhook is the auto instrumentation webhook type Webhook struct { name string + webhookType common.WebhookType isEnabled bool endpoint string resources []string - operations []admiv1.OperationType + operations []admissionregistrationv1.OperationType initSecurityContext *corev1.SecurityContext initResourceRequirements corev1.ResourceRequirements containerRegistry string @@ -93,10 +95,11 @@ func NewWebhook(wmeta workloadmeta.Component, filter mutatecommon.InjectionFilte return &Webhook{ name: webhookName, + webhookType: common.MutatingWebhook, isEnabled: config.Datadog().GetBool("admission_controller.auto_instrumentation.enabled"), endpoint: config.Datadog().GetString("admission_controller.auto_instrumentation.endpoint"), resources: []string{"pods"}, - operations: []admiv1.OperationType{admiv1.Create}, + operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, initSecurityContext: initSecurityContext, initResourceRequirements: initResourceRequirements, injectionFilter: filter, @@ -113,6 +116,11 @@ func (w *Webhook) Name() string { return w.name } +// WebhookType returns the type of the webhook +func (w *Webhook) WebhookType() common.WebhookType { + return w.webhookType +} + // IsEnabled returns whether the webhook is enabled func (w *Webhook) IsEnabled() bool { return w.isEnabled @@ -131,24 +139,21 @@ func (w *Webhook) Resources() []string { // Operations returns the operations on the resources specified for which // the webhook should be invoked -func (w *Webhook) Operations() []admiv1.OperationType { +func (w *Webhook) Operations() []admissionregistrationv1.OperationType { return w.operations } // LabelSelectors returns the label selectors that specify when the webhook // should be invoked func (w *Webhook) LabelSelectors(useNamespaceSelector bool) (namespaceSelector *metav1.LabelSelector, objectSelector *metav1.LabelSelector) { - return mutatecommon.DefaultLabelSelectors(useNamespaceSelector) + return common.DefaultLabelSelectors(useNamespaceSelector) } -// MutateFunc returns the function that mutates the resources -func (w *Webhook) MutateFunc() admission.MutatingWebhookFunc { - return w.injectAutoInstrumentation -} - -// injectAutoInstrumentation injects APM libraries into pods -func (w *Webhook) injectAutoInstrumentation(request *admission.MutateRequest) ([]byte, error) { - return mutatecommon.Mutate(request.Raw, request.Namespace, w.Name(), w.inject, request.DynamicClient) +// WebhookFunc returns the function that mutates the resources +func (w *Webhook) WebhookFunc() func(request *admission.Request) *admiv1.AdmissionResponse { + return func(request *admission.Request) *admiv1.AdmissionResponse { + return common.MutationResponse(mutatecommon.Mutate(request.Raw, request.Namespace, w.Name(), w.inject, request.DynamicClient)) + } } func initContainerName(lang language) string { diff --git a/pkg/clusteragent/admission/mutate/autoscaling/autoscaling.go b/pkg/clusteragent/admission/mutate/autoscaling/autoscaling.go index ebdfee3031d4b..7e728158ba1df 100644 --- a/pkg/clusteragent/admission/mutate/autoscaling/autoscaling.go +++ b/pkg/clusteragent/admission/mutate/autoscaling/autoscaling.go @@ -10,11 +10,13 @@ package autoscaling import ( "github.com/DataDog/datadog-agent/cmd/cluster-agent/admission" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" + mutatecommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" "github.com/DataDog/datadog-agent/pkg/clusteragent/autoscaling/workload" "github.com/DataDog/datadog-agent/pkg/config" - admiv1 "k8s.io/api/admissionregistration/v1" + admiv1 "k8s.io/api/admission/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/dynamic" @@ -27,23 +29,25 @@ const ( // Webhook implements the MutatingWebhook interface type Webhook struct { - name string - isEnabled bool - endpoint string - resources []string - operations []admiv1.OperationType - patcher workload.PodPatcher + name string + webhookType common.WebhookType + isEnabled bool + endpoint string + resources []string + operations []admissionregistrationv1.OperationType + patcher workload.PodPatcher } // NewWebhook returns a new Webhook func NewWebhook(patcher workload.PodPatcher) *Webhook { return &Webhook{ - name: webhookName, - isEnabled: config.Datadog().GetBool("autoscaling.workload.enabled"), - endpoint: webhookEndpoint, - resources: []string{"pods"}, - operations: []admiv1.OperationType{admiv1.Create}, - patcher: patcher, + name: webhookName, + webhookType: common.MutatingWebhook, + isEnabled: config.Datadog().GetBool("autoscaling.workload.enabled"), + endpoint: webhookEndpoint, + resources: []string{"pods"}, + operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, + patcher: patcher, } } @@ -52,6 +56,11 @@ func (w *Webhook) Name() string { return w.name } +// WebhookType returns the type of the webhook +func (w *Webhook) WebhookType() common.WebhookType { + return w.webhookType +} + // IsEnabled returns whether the webhook is enabled func (w *Webhook) IsEnabled() bool { return w.isEnabled @@ -70,7 +79,7 @@ func (w *Webhook) Resources() []string { // Operations returns the operations on the resources specified for which // the webhook should be invoked -func (w *Webhook) Operations() []admiv1.OperationType { +func (w *Webhook) Operations() []admissionregistrationv1.OperationType { return w.operations } @@ -82,18 +91,14 @@ func (w *Webhook) LabelSelectors(_ bool) (namespaceSelector *metav1.LabelSelecto return nil, nil } -// MutateFunc returns the function that mutates the resources -func (w *Webhook) MutateFunc() admission.MutatingWebhookFunc { - return w.mutate -} - -// mutate adds the DD_AGENT_HOST and DD_ENTITY_ID env vars to the pod template if they don't exist -func (w *Webhook) mutate(request *admission.MutateRequest) ([]byte, error) { - return common.Mutate(request.Raw, request.Namespace, w.Name(), w.updateResources, request.DynamicClient) +// WebhookFunc returns the function that mutates the resources +func (w *Webhook) WebhookFunc() func(request *admission.Request) *admiv1.AdmissionResponse { + return func(request *admission.Request) *admiv1.AdmissionResponse { + return common.MutationResponse(mutatecommon.Mutate(request.Raw, request.Namespace, w.Name(), w.updateResources, request.DynamicClient)) + } } -// updateResource finds the owner of a pod, calls the recommender to retrieve the recommended CPU and Memory -// requests +// updateResources finds the owner of a pod, calls the recommender to retrieve the recommended CPU and Memory requests func (w *Webhook) updateResources(pod *corev1.Pod, _ string, _ dynamic.Interface) (bool, error) { return w.patcher.ApplyRecommendations(pod) } diff --git a/pkg/clusteragent/admission/mutate/common/common.go b/pkg/clusteragent/admission/mutate/common/common.go index 557fd10f24bae..d198b502f09ec 100644 --- a/pkg/clusteragent/admission/mutate/common/common.go +++ b/pkg/clusteragent/admission/mutate/common/common.go @@ -22,27 +22,6 @@ import ( "github.com/DataDog/datadog-agent/pkg/util/log" ) -// ValidationFunc is a function that validates a pod -type ValidationFunc func(pod *corev1.Pod, ns string, cl dynamic.Interface) (bool, error) - -// Validate handles validating pods and encoding and decoding admission -// requests and responses for the public validate functions -func Validate(rawPod []byte, ns string, validationType string, v ValidationFunc, dc dynamic.Interface) (bool, error) { - var pod corev1.Pod - if err := json.Unmarshal(rawPod, &pod); err != nil { - return false, fmt.Errorf("failed to decode raw object: %v", err) - } - - validated, err := v(&pod, ns, dc) - if err != nil { - metrics.ValidationAttempts.Inc(validationType, metrics.StatusError, strconv.FormatBool(false), err.Error()) - return false, fmt.Errorf("failed to validate pod: %v", err) - } - - metrics.ValidationAttempts.Inc(validationType, metrics.StatusSuccess, strconv.FormatBool(validated), "") - return validated, nil -} - // MutationFunc is a function that mutates a pod type MutationFunc func(pod *corev1.Pod, ns string, cl dynamic.Interface) (bool, error) diff --git a/pkg/clusteragent/admission/mutate/config/config.go b/pkg/clusteragent/admission/mutate/config/config.go index b6e0099d3a025..d40da6c65e84b 100644 --- a/pkg/clusteragent/admission/mutate/config/config.go +++ b/pkg/clusteragent/admission/mutate/config/config.go @@ -12,18 +12,20 @@ package config import ( "errors" "fmt" + admiv1 "k8s.io/api/admission/v1" "strings" - admiv1 "k8s.io/api/admissionregistration/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/dynamic" "github.com/DataDog/datadog-agent/cmd/cluster-agent/admission" workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" admCommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/metrics" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" + mutatecommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" "github.com/DataDog/datadog-agent/pkg/config" apiCommon "github.com/DataDog/datadog-agent/pkg/util/kubernetes/apiserver/common" "github.com/DataDog/datadog-agent/pkg/util/log" @@ -96,23 +98,25 @@ var ( // Webhook is the webhook that injects DD_AGENT_HOST and DD_ENTITY_ID into a pod type Webhook struct { name string + webhookType common.WebhookType isEnabled bool endpoint string resources []string - operations []admiv1.OperationType + operations []admissionregistrationv1.OperationType mode string wmeta workloadmeta.Component - injectionFilter common.InjectionFilter + injectionFilter mutatecommon.InjectionFilter } // NewWebhook returns a new Webhook -func NewWebhook(wmeta workloadmeta.Component, injectionFilter common.InjectionFilter) *Webhook { +func NewWebhook(wmeta workloadmeta.Component, injectionFilter mutatecommon.InjectionFilter) *Webhook { return &Webhook{ name: webhookName, + webhookType: common.MutatingWebhook, isEnabled: config.Datadog().GetBool("admission_controller.inject_config.enabled"), endpoint: config.Datadog().GetString("admission_controller.inject_config.endpoint"), resources: []string{"pods"}, - operations: []admiv1.OperationType{admiv1.Create}, + operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, mode: config.Datadog().GetString("admission_controller.inject_config.mode"), wmeta: wmeta, injectionFilter: injectionFilter, @@ -124,6 +128,11 @@ func (w *Webhook) Name() string { return w.name } +// WebhookType returns the type of the webhook +func (w *Webhook) WebhookType() common.WebhookType { + return w.webhookType +} + // IsEnabled returns whether the webhook is enabled func (w *Webhook) IsEnabled() bool { return w.isEnabled @@ -142,7 +151,7 @@ func (w *Webhook) Resources() []string { // Operations returns the operations on the resources specified for which // the webhook should be invoked -func (w *Webhook) Operations() []admiv1.OperationType { +func (w *Webhook) Operations() []admissionregistrationv1.OperationType { return w.operations } @@ -152,14 +161,11 @@ func (w *Webhook) LabelSelectors(useNamespaceSelector bool) (namespaceSelector * return common.DefaultLabelSelectors(useNamespaceSelector) } -// MutateFunc returns the function that mutates the resources -func (w *Webhook) MutateFunc() admission.MutatingWebhookFunc { - return w.mutate -} - -// mutate adds the DD_AGENT_HOST and DD_ENTITY_ID env vars to the pod template if they don't exist -func (w *Webhook) mutate(request *admission.MutateRequest) ([]byte, error) { - return common.Mutate(request.Raw, request.Namespace, w.Name(), w.inject, request.DynamicClient) +// WebhookFunc returns the function that mutates the resources +func (w *Webhook) WebhookFunc() func(request *admission.Request) *admiv1.AdmissionResponse { + return func(request *admission.Request) *admiv1.AdmissionResponse { + return common.MutationResponse(mutatecommon.Mutate(request.Raw, request.Namespace, w.Name(), w.inject, request.DynamicClient)) + } } // inject injects the following environment variables into the pod template: @@ -180,21 +186,21 @@ func (w *Webhook) inject(pod *corev1.Pod, _ string, _ dynamic.Interface) (bool, // Inject DD_AGENT_HOST switch injectionMode(pod, w.mode) { case hostIP: - injectedConfig = common.InjectEnv(pod, agentHostIPEnvVar) + injectedConfig = mutatecommon.InjectEnv(pod, agentHostIPEnvVar) case service: - injectedConfig = common.InjectEnv(pod, agentHostServiceEnvVar) + injectedConfig = mutatecommon.InjectEnv(pod, agentHostServiceEnvVar) case socket: volume, volumeMount := buildVolume(DatadogVolumeName, config.Datadog().GetString("admission_controller.inject_config.socket_path"), true) - injectedVol := common.InjectVolume(pod, volume, volumeMount) - injectedEnv := common.InjectEnv(pod, traceURLSocketEnvVar) - injectedEnv = common.InjectEnv(pod, dogstatsdURLSocketEnvVar) || injectedEnv + injectedVol := mutatecommon.InjectVolume(pod, volume, volumeMount) + injectedEnv := mutatecommon.InjectEnv(pod, traceURLSocketEnvVar) + injectedEnv = mutatecommon.InjectEnv(pod, dogstatsdURLSocketEnvVar) || injectedEnv injectedConfig = injectedEnv || injectedVol default: log.Errorf("invalid injection mode %q", w.mode) return false, errors.New(metrics.InvalidInput) } - injectedEntity = common.InjectEnv(pod, defaultDdEntityIDEnvVar) + injectedEntity = mutatecommon.InjectEnv(pod, defaultDdEntityIDEnvVar) // Inject External Data Environment Variable injectedExternalEnv = injectExternalDataEnvVar(pod) @@ -210,7 +216,7 @@ func injectionMode(pod *corev1.Pod, globalMode string) string { case hostIP, service, socket: return mode default: - log.Warnf("Invalid label value '%s=%s' on pod %s should be either 'hostip', 'service' or 'socket', defaulting to %q", admCommon.InjectionModeLabelKey, val, common.PodString(pod), globalMode) + log.Warnf("Invalid label value '%s=%s' on pod %s should be either 'hostip', 'service' or 'socket', defaulting to %q", admCommon.InjectionModeLabelKey, val, mutatecommon.PodString(pod), globalMode) return globalMode } } diff --git a/pkg/clusteragent/admission/mutate/config/config_test.go b/pkg/clusteragent/admission/mutate/config/config_test.go index 4fade296b0ca1..96b173123d8c1 100644 --- a/pkg/clusteragent/admission/mutate/config/config_test.go +++ b/pkg/clusteragent/admission/mutate/config/config_test.go @@ -400,11 +400,11 @@ func TestJSONPatchCorrectness(t *testing.T) { fx.Replace(config.MockParams{Overrides: tt.overrides}), ) webhook := NewWebhook(wmeta, autoinstrumentation.GetInjectionFilter()) - request := admission.MutateRequest{ + request := admission.Request{ Raw: podJSON, Namespace: "bar", } - jsonPatch, err := webhook.MutateFunc()(&request) + jsonPatch, err := webhook.WebhookFunc()(&request) assert.NoError(t, err) expected, err := os.ReadFile(tt.file) @@ -435,11 +435,11 @@ func BenchmarkJSONPatch(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - request := admission.MutateRequest{ + request := admission.Request{ Raw: podJSON, Namespace: "bar", } - jsonPatch, err := webhook.MutateFunc()(&request) + jsonPatch, err := webhook.WebhookFunc()(&request) if err != nil { b.Fatal(err) } diff --git a/pkg/clusteragent/admission/mutate/cwsinstrumentation/cws_instrumentation.go b/pkg/clusteragent/admission/mutate/cwsinstrumentation/cws_instrumentation.go index d611e769f661d..5c3cdfb2da123 100644 --- a/pkg/clusteragent/admission/mutate/cwsinstrumentation/cws_instrumentation.go +++ b/pkg/clusteragent/admission/mutate/cwsinstrumentation/cws_instrumentation.go @@ -14,6 +14,8 @@ import ( "encoding/json" "errors" "fmt" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" + admiv1 "k8s.io/api/admission/v1" "path/filepath" "strconv" @@ -21,7 +23,7 @@ import ( "k8s.io/utils/strings/slices" "github.com/wI2L/jsondiff" - admiv1 "k8s.io/api/admissionregistration/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" authenticationv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -33,7 +35,7 @@ import ( "github.com/DataDog/datadog-agent/comp/core/workloadmeta/collectors/util" workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/metrics" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" + mutatecommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/cwsinstrumentation/k8scp" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/cwsinstrumentation/k8sexec" "github.com/DataDog/datadog-agent/pkg/config" @@ -86,21 +88,23 @@ type mutatePodExecFunc func(*corev1.PodExecOptions, string, string, *authenticat // WebhookForPods is the webhook that injects CWS pod instrumentation type WebhookForPods struct { name string + webhookType common.WebhookType isEnabled bool endpoint string resources []string - operations []admiv1.OperationType - admissionFunc admission.MutatingWebhookFunc + operations []admissionregistrationv1.OperationType + admissionFunc admission.WebhookFunc } -func newWebhookForPods(admissionFunc admission.MutatingWebhookFunc) *WebhookForPods { +func newWebhookForPods(admissionFunc admission.WebhookFunc) *WebhookForPods { return &WebhookForPods{ - name: webhookForPodsName, + name: webhookForPodsName, + webhookType: common.MutatingWebhook, isEnabled: config.Datadog().GetBool("admission_controller.cws_instrumentation.enabled") && len(config.Datadog().GetString("admission_controller.cws_instrumentation.image_name")) > 0, endpoint: config.Datadog().GetString("admission_controller.cws_instrumentation.pod_endpoint"), resources: []string{"pods"}, - operations: []admiv1.OperationType{admiv1.Create}, + operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, admissionFunc: admissionFunc, } } @@ -110,6 +114,11 @@ func (w *WebhookForPods) Name() string { return w.name } +// WebhookType returns the type of the webhook +func (w *WebhookForPods) WebhookType() common.WebhookType { + return w.webhookType +} + // IsEnabled returns whether the webhook is enabled func (w *WebhookForPods) IsEnabled() bool { return w.isEnabled @@ -128,7 +137,7 @@ func (w *WebhookForPods) Resources() []string { // Operations returns the operations on the resources specified for which // the webhook should be invoked -func (w *WebhookForPods) Operations() []admiv1.OperationType { +func (w *WebhookForPods) Operations() []admissionregistrationv1.OperationType { return w.operations } @@ -138,30 +147,31 @@ func (w *WebhookForPods) LabelSelectors(useNamespaceSelector bool) (namespaceSel return labelSelectors(useNamespaceSelector) } -// MutateFunc returns the function that mutates the resources -func (w *WebhookForPods) MutateFunc() admission.MutatingWebhookFunc { +// WebhookFunc returns the function that mutates the resources +func (w *WebhookForPods) WebhookFunc() func(request *admission.Request) *admiv1.AdmissionResponse { return w.admissionFunc } // WebhookForCommands is the webhook that injects CWS pods/exec instrumentation type WebhookForCommands struct { name string + webhookType common.WebhookType isEnabled bool endpoint string resources []string - operations []admiv1.OperationType - admissionFunc admission.MutatingWebhookFunc + operations []admissionregistrationv1.OperationType + admissionFunc admission.WebhookFunc } -func newWebhookForCommands(admissionFunc admission.MutatingWebhookFunc) *WebhookForCommands { +func newWebhookForCommands(admissionFunc admission.WebhookFunc) *WebhookForCommands { return &WebhookForCommands{ - name: webhookForCommandsName, + name: webhookForCommandsName, + webhookType: common.MutatingWebhook, isEnabled: config.Datadog().GetBool("admission_controller.cws_instrumentation.enabled") && len(config.Datadog().GetString("admission_controller.cws_instrumentation.image_name")) > 0, - endpoint: config.Datadog().GetString("admission_controller.cws_instrumentation.command_endpoint"), - resources: []string{"pods/exec"}, - operations: []admiv1.OperationType{admiv1.Connect}, - admissionFunc: admissionFunc, + endpoint: config.Datadog().GetString("admission_controller.cws_instrumentation.command_endpoint"), + resources: []string{"pods/exec"}, + operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Connect}, } } @@ -170,6 +180,11 @@ func (w *WebhookForCommands) Name() string { return w.name } +// WebhookType returns the name of the webhook +func (w *WebhookForCommands) WebhookType() common.WebhookType { + return w.webhookType +} + // IsEnabled returns whether the webhook is enabled func (w *WebhookForCommands) IsEnabled() bool { return w.isEnabled @@ -188,7 +203,7 @@ func (w *WebhookForCommands) Resources() []string { // Operations returns the operations on the resources specified for which // the webhook should be invoked -func (w *WebhookForCommands) Operations() []admiv1.OperationType { +func (w *WebhookForCommands) Operations() []admissionregistrationv1.OperationType { return w.operations } @@ -198,8 +213,8 @@ func (w *WebhookForCommands) LabelSelectors(_ bool) (namespaceSelector *metav1.L return nil, nil } -// MutateFunc returns the function that mutates the resources -func (w *WebhookForCommands) MutateFunc() admission.MutatingWebhookFunc { +// WebhookFunc MutateFunc returns the function that mutates the resources +func (w *WebhookForCommands) WebhookFunc() func(request *admission.Request) *admiv1.AdmissionResponse { return w.admissionFunc } @@ -302,7 +317,7 @@ func NewCWSInstrumentation(wmeta workloadmeta.Component) (*CWSInstrumentation, e cwsInjectorImageName := config.Datadog().GetString("admission_controller.cws_instrumentation.image_name") cwsInjectorImageTag := config.Datadog().GetString("admission_controller.cws_instrumentation.image_tag") - cwsInjectorContainerRegistry := common.ContainerRegistry("admission_controller.cws_instrumentation.container_registry") + cwsInjectorContainerRegistry := mutatecommon.ContainerRegistry("admission_controller.cws_instrumentation.container_registry") if len(cwsInjectorImageName) == 0 { return nil, fmt.Errorf("can't initialize CWS Instrumentation without an image_name") @@ -357,8 +372,8 @@ func (ci *CWSInstrumentation) WebhookForCommands() *WebhookForCommands { return ci.webhookForCommands } -func (ci *CWSInstrumentation) injectForCommand(request *admission.MutateRequest) ([]byte, error) { - return mutatePodExecOptions(request.Raw, request.Name, request.Namespace, ci.webhookForCommands.Name(), request.UserInfo, ci.injectCWSCommandInstrumentation, request.DynamicClient, request.APIClient) +func (ci *CWSInstrumentation) injectForCommand(request *admission.Request) *admiv1.AdmissionResponse { + return common.MutationResponse(mutatePodExecOptions(request.Raw, request.Name, request.Namespace, ci.webhookForCommands.Name(), request.UserInfo, ci.injectCWSCommandInstrumentation, request.DynamicClient, request.APIClient)) } func (ci *CWSInstrumentation) resolveNodeArch(nodeName string, apiClient kubernetes.Interface) (string, error) { @@ -476,7 +491,7 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentation(exec *corev1.PodEx // is the pod instrumentation ready ? (i.e. has the CWS Instrumentation init container been added ?) if !isPodCWSInstrumentationReady(pod.Annotations) { // pod isn't instrumented, do not attempt to override the pod exec command - log.Debugf("Ignoring exec request into %s, pod not instrumented yet", common.PodString(pod)) + log.Debugf("Ignoring exec request into %s, pod not instrumented yet", mutatecommon.PodString(pod)) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsPodNotInstrumentedReason) return false, nil } @@ -487,7 +502,7 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentation(exec *corev1.PodEx if ci.mountVolumeForRemoteCopy { if !isPodCWSInstrumentationReady(pod.Annotations) { // pod isn't instrumented, do not attempt to override the pod exec command - log.Debugf("Ignoring exec request into %s, pod not instrumented yet", common.PodString(pod)) + log.Debugf("Ignoring exec request into %s, pod not instrumented yet", mutatecommon.PodString(pod)) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsPodNotInstrumentedReason) return false, nil } @@ -496,7 +511,7 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentation(exec *corev1.PodEx // check if the target pod has a read only filesystem if readOnly := ci.hasReadonlyRootfs(pod, exec.Container); readOnly { // readonly rootfs containers can't be instrumented - log.Errorf("Ignoring exec request into %s, container %s has read only rootfs. Try enabling admission_controller.cws_instrumentation.remote_copy.mount_volume", common.PodString(pod), exec.Container) + log.Errorf("Ignoring exec request into %s, container %s has read only rootfs. Try enabling admission_controller.cws_instrumentation.remote_copy.mount_volume", mutatecommon.PodString(pod), exec.Container) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsReadonlyFilesystemReason) return false, errors.New(metrics.InvalidInput) } @@ -512,7 +527,7 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentation(exec *corev1.PodEx arch, err := ci.resolveNodeArch(pod.Spec.NodeName, apiClient) if err != nil { - log.Errorf("Ignoring exec request into %s: %v", common.PodString(pod), err) + log.Errorf("Ignoring exec request into %s: %v", mutatecommon.PodString(pod), err) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsMissingArchReason) return false, errors.New(metrics.InternalError) } @@ -520,7 +535,7 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentation(exec *corev1.PodEx // check if the pod is ready to be exec-ed into if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed { - log.Errorf("Ignoring exec request into %s: cannot exec into a container in a completed pod; current phase is %s", common.PodString(pod), pod.Status.Phase) + log.Errorf("Ignoring exec request into %s: cannot exec into a container in a completed pod; current phase is %s", mutatecommon.PodString(pod), pod.Status.Phase) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsCompletedPodReason) return false, errors.New(metrics.InvalidInput) } @@ -528,19 +543,19 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentation(exec *corev1.PodEx // check if the input container exists, or select the default one to which the user will be redirected container, err := podcmd.FindOrDefaultContainerByName(pod, exec.Container, true, nil) if err != nil { - log.Errorf("Ignoring exec request into %s, invalid container: %v", common.PodString(pod), err) + log.Errorf("Ignoring exec request into %s, invalid container: %v", mutatecommon.PodString(pod), err) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsInvalidInputContainerReason) return false, errors.New(metrics.InvalidInput) } // copy CWS instrumentation directly to the target container if err := ci.injectCWSCommandInstrumentationRemoteCopy(pod, container.Name, cwsInstrumentationLocalPath, cwsInstrumentationRemotePath); err != nil { - log.Warnf("Ignoring exec request into %s, remote copy failed: %v", common.PodString(pod), err) + log.Warnf("Ignoring exec request into %s, remote copy failed: %v", mutatecommon.PodString(pod), err) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsRemoteCopyFailedReason) return false, errors.New(metrics.InternalError) } default: - log.Errorf("Ignoring exec request into %s, unknown CWS Instrumentation mode %v", common.PodString(pod), ci.mode) + log.Errorf("Ignoring exec request into %s, unknown CWS Instrumentation mode %v", mutatecommon.PodString(pod), ci.mode) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsUnknownModeReason) return false, errors.New(metrics.InvalidInput) } @@ -548,7 +563,7 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentation(exec *corev1.PodEx // prepare the user session context userSessionCtx, err := usersessions.PrepareK8SUserSessionContext(userInfo, cwsUserSessionDataMaxSize) if err != nil { - log.Debugf("ignoring instrumentation of %s: %v", common.PodString(pod), err) + log.Debugf("ignoring instrumentation of %s: %v", mutatecommon.PodString(pod), err) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsCredentialsSerializationErrorReason) return false, errors.New(metrics.InternalError) } @@ -563,7 +578,7 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentation(exec *corev1.PodEx exec.Command[6] == "--" { if exec.Command[5] == string(userSessionCtx) { - log.Debugf("Exec request into %s is already instrumented, ignoring", common.PodString(pod)) + log.Debugf("Exec request into %s is already instrumented, ignoring", mutatecommon.PodString(pod)) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsAlreadyInstrumentedReason) return true, nil } @@ -581,7 +596,7 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentation(exec *corev1.PodEx "--", }, exec.Command...) - log.Debugf("Pod exec request to %s by %s is now instrumented for CWS", common.PodString(pod), userInfo.Username) + log.Debugf("Pod exec request to %s by %s is now instrumented for CWS", mutatecommon.PodString(pod), userInfo.Username) metrics.CWSExecInstrumentationAttempts.Observe(1, ci.mode.String(), "true", "") injected = true @@ -604,8 +619,8 @@ func (ci *CWSInstrumentation) injectCWSCommandInstrumentationRemoteCopy(pod *cor return health.Run(cwsInstrumentationRemotePath, pod, container) } -func (ci *CWSInstrumentation) injectForPod(request *admission.MutateRequest) ([]byte, error) { - return common.Mutate(request.Raw, request.Namespace, ci.webhookForPods.Name(), ci.injectCWSPodInstrumentation, request.DynamicClient) +func (ci *CWSInstrumentation) injectForPod(request *admission.Request) *admiv1.AdmissionResponse { + return common.MutationResponse(mutatecommon.Mutate(request.Raw, request.Namespace, ci.webhookForPods.Name(), ci.injectCWSPodInstrumentation, request.DynamicClient)) } func (ci *CWSInstrumentation) injectCWSPodInstrumentation(pod *corev1.Pod, ns string, _ dynamic.Interface) (bool, error) { @@ -637,7 +652,7 @@ func (ci *CWSInstrumentation) injectCWSPodInstrumentation(pod *corev1.Pod, ns st case RemoteCopy: instrumented = ci.injectCWSPodInstrumentationRemoteCopy(pod) default: - log.Errorf("Ignoring Pod %s admission request: unknown CWS Instrumentation mode %v", common.PodString(pod), ci.mode) + log.Errorf("Ignoring Pod %s admission request: unknown CWS Instrumentation mode %v", mutatecommon.PodString(pod), ci.mode) metrics.CWSPodInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsUnknownModeReason) return false, errors.New(metrics.InvalidInput) } @@ -648,7 +663,7 @@ func (ci *CWSInstrumentation) injectCWSPodInstrumentation(pod *corev1.Pod, ns st pod.Annotations = make(map[string]string) } pod.Annotations[cwsInstrumentationPodAnotationStatus] = cwsInstrumentationPodAnotationReady - log.Debugf("Pod %s is now instrumented for CWS", common.PodString(pod)) + log.Debugf("Pod %s is now instrumented for CWS", mutatecommon.PodString(pod)) metrics.CWSPodInstrumentationAttempts.Observe(1, ci.mode.String(), "true", "") } else { metrics.CWSPodInstrumentationAttempts.Observe(1, ci.mode.String(), "false", cwsNoInstrumentationNeededReason) diff --git a/pkg/clusteragent/admission/mutate/doc.go b/pkg/clusteragent/admission/mutate/doc.go index 931e1e30f1e01..77f78813b1afd 100644 --- a/pkg/clusteragent/admission/mutate/doc.go +++ b/pkg/clusteragent/admission/mutate/doc.go @@ -25,10 +25,12 @@ // grouped in the same package. For example, the CWS webhooks are all in the // same package. // -// Each mutating webhook needs to implement the "MutatingWebhook" interface. +// Each mutating webhook needs to implement the "Webhook" interface. // Here's a brief description of each function and what they are used for: // - Name: it's the name of the webhook. It's used to identify it. The name // appears in some telemetry tags. +// - WebhookType: it's the type of the webhook. It can be either "mutating" or +// "validating". // - IsEnabled: returns whether the webhook is enabled or not. In general, the // recommendation is to disable the webhook by default unless it's needed by a // core feature that should be enabled for everyone that deploys the Datadog @@ -44,7 +46,7 @@ // minimize the number of requests that the webhook receives. The label // selectors help us with that. There are some default label selectors defined // in the "common" package. -// - MutateFunc: the function that mutates the Kubernetes object. +// - WebhookFunc: the function that runs the logic of the webhook and returns the admission response. // // As any other feature, mutating webhooks can be configured using the Datadog // configuration. When adding new configuration parameters, please try to follow @@ -58,7 +60,7 @@ // We should try to avoid depending on the order in which webhooks are executed. // When this cannot be avoided, keep in mind that the order in which the // webhooks are executed is the order in which they are returned by the -// "mutatingWebhooks" function in the "webhook" package. +// "generateWebhooks" function in the "webhook" package. // // Mutating webhooks emit telemetry metrics. Each webhook can define its own // metrics as needed but some metrics like "mutation_attempts" or diff --git a/pkg/clusteragent/admission/mutate/tagsfromlabels/tags.go b/pkg/clusteragent/admission/mutate/tagsfromlabels/tags.go index 72609e3dc5538..9e3846fb10096 100644 --- a/pkg/clusteragent/admission/mutate/tagsfromlabels/tags.go +++ b/pkg/clusteragent/admission/mutate/tagsfromlabels/tags.go @@ -13,10 +13,12 @@ import ( "context" "errors" "fmt" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" + admiv1 "k8s.io/api/admission/v1" "strings" "time" - admiv1 "k8s.io/api/admissionregistration/v1" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -25,7 +27,7 @@ import ( "github.com/DataDog/datadog-agent/cmd/cluster-agent/admission" workloadmeta "github.com/DataDog/datadog-agent/comp/core/workloadmeta/def" "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/metrics" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" + mutatecommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" "github.com/DataDog/datadog-agent/pkg/config" "github.com/DataDog/datadog-agent/pkg/util/cache" "github.com/DataDog/datadog-agent/pkg/util/kubernetes" @@ -43,23 +45,25 @@ const webhookName = "standard_tags" // Webhook is the webhook that injects DD_ENV, DD_VERSION, DD_SERVICE env vars type Webhook struct { name string + webhookType common.WebhookType isEnabled bool endpoint string resources []string - operations []admiv1.OperationType + operations []admissionregistrationv1.OperationType ownerCacheTTL time.Duration wmeta workloadmeta.Component - injectionFilter common.InjectionFilter + injectionFilter mutatecommon.InjectionFilter } // NewWebhook returns a new Webhook -func NewWebhook(wmeta workloadmeta.Component, injectionFilter common.InjectionFilter) *Webhook { +func NewWebhook(wmeta workloadmeta.Component, injectionFilter mutatecommon.InjectionFilter) *Webhook { return &Webhook{ name: webhookName, + webhookType: common.MutatingWebhook, isEnabled: config.Datadog().GetBool("admission_controller.inject_tags.enabled"), endpoint: config.Datadog().GetString("admission_controller.inject_tags.endpoint"), resources: []string{"pods"}, - operations: []admiv1.OperationType{admiv1.Create}, + operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, ownerCacheTTL: ownerCacheTTL(), wmeta: wmeta, injectionFilter: injectionFilter, @@ -71,6 +75,11 @@ func (w *Webhook) Name() string { return w.name } +// WebhookType returns the type of the webhook +func (w *Webhook) WebhookType() common.WebhookType { + return w.webhookType +} + // IsEnabled returns whether the webhook is enabled func (w *Webhook) IsEnabled() bool { return w.isEnabled @@ -89,7 +98,7 @@ func (w *Webhook) Resources() []string { // Operations returns the operations on the resources specified for which // the webhook should be invoked -func (w *Webhook) Operations() []admiv1.OperationType { +func (w *Webhook) Operations() []admissionregistrationv1.OperationType { return w.operations } @@ -99,11 +108,6 @@ func (w *Webhook) LabelSelectors(useNamespaceSelector bool) (namespaceSelector * return common.DefaultLabelSelectors(useNamespaceSelector) } -// MutateFunc returns the function that mutates the resources -func (w *Webhook) MutateFunc() admission.MutatingWebhookFunc { - return w.mutate -} - type owner struct { name string namespace string @@ -123,12 +127,14 @@ func (o *ownerInfo) buildID(ns string) string { return fmt.Sprintf("%s/%s/%s", ns, o.name, o.gvr.String()) } -// mutate adds the DD_ENV, DD_VERSION, DD_SERVICE env vars to -// the pod template from pod and higher-level resource labels -func (w *Webhook) mutate(request *admission.MutateRequest) ([]byte, error) { - return common.Mutate(request.Raw, request.Namespace, w.Name(), func(pod *corev1.Pod, ns string, dc dynamic.Interface) (bool, error) { - return w.injectTags(pod, ns, dc) - }, request.DynamicClient) +// WebhookFunc returns the function that mutates the resources +func (w *Webhook) WebhookFunc() func(request *admission.Request) *admiv1.AdmissionResponse { + return func(request *admission.Request) *admiv1.AdmissionResponse { + return common.MutationResponse(mutatecommon.Mutate(request.Raw, request.Namespace, w.Name(), func(pod *corev1.Pod, ns string, dc dynamic.Interface) (bool, error) { + // Adds the DD_ENV, DD_VERSION, DD_SERVICE env vars to the pod template from pod and higher-level resource labels. + return w.injectTags(pod, ns, dc) + }, request.DynamicClient)) + } } // injectTags injects DD_ENV, DD_VERSION, DD_SERVICE @@ -173,7 +179,7 @@ func (w *Webhook) injectTags(pod *corev1.Pod, ns string, dc dynamic.Interface) ( return false, errors.New(metrics.InternalError) } - log.Debugf("Looking for standard labels on '%s/%s' - kind '%s' owner of pod %s", owner.namespace, owner.name, owner.kind, common.PodString(pod)) + log.Debugf("Looking for standard labels on '%s/%s' - kind '%s' owner of pod %s", owner.namespace, owner.name, owner.kind, mutatecommon.PodString(pod)) _, injected = injectTagsFromLabels(owner.labels, pod) return injected, nil @@ -190,7 +196,7 @@ func injectTagsFromLabels(labels map[string]string, pod *corev1.Pod) (bool, bool Name: envName, Value: tagValue, } - if injected := common.InjectEnv(pod, env); injected { + if injected := mutatecommon.InjectEnv(pod, env); injected { injectedAtLeastOnce = true } found = true diff --git a/pkg/clusteragent/admission/start.go b/pkg/clusteragent/admission/start.go index cad8faa353258..bccb14bb0f771 100644 --- a/pkg/clusteragent/admission/start.go +++ b/pkg/clusteragent/admission/start.go @@ -36,10 +36,12 @@ type ControllerContext struct { } // StartControllers starts the secret and webhook controllers -func StartControllers(ctx ControllerContext, wmeta workloadmeta.Component, pa workload.PodPatcher) ([]webhook.ValidatingWebhook, []webhook.MutatingWebhook, error) { +func StartControllers(ctx ControllerContext, wmeta workloadmeta.Component, pa workload.PodPatcher) ([]webhook.Webhook, error) { + var webhooks []webhook.Webhook + if !config.Datadog().GetBool("admission_controller.enabled") { log.Info("Admission controller is disabled") - return nil, nil, nil + return webhooks, nil } certConfig := secret.NewCertConfig( @@ -60,41 +62,28 @@ func StartControllers(ctx ControllerContext, wmeta workloadmeta.Component, pa wo nsSelectorEnabled, err := useNamespaceSelector(ctx.Client.Discovery()) if err != nil { - return nil, nil, err + return webhooks, err } v1Enabled, err := UseAdmissionV1(ctx.Client.Discovery()) if err != nil { - return nil, nil, err + return webhooks, err } - validatingWebhookConfig := webhook.NewConfig(v1Enabled, nsSelectorEnabled) - validatingWebhookController := webhook.NewController( - ctx.Client, - ctx.SecretInformers.Core().V1().Secrets(), - ctx.WebhookInformers.Admissionregistration(), - ctx.IsLeaderFunc, - ctx.LeaderSubscribeFunc(), - validatingWebhookConfig, - wmeta, - pa, - ) - - mutatingWebhookConfig := webhook.NewConfig(v1Enabled, nsSelectorEnabled) - mutatingWebhookController := webhook.NewController( + webhookConfig := webhook.NewConfig(v1Enabled, nsSelectorEnabled) + webhookController := webhook.NewController( ctx.Client, ctx.SecretInformers.Core().V1().Secrets(), ctx.WebhookInformers.Admissionregistration(), ctx.IsLeaderFunc, ctx.LeaderSubscribeFunc(), - mutatingWebhookConfig, + webhookConfig, wmeta, pa, ) go secretController.Run(ctx.StopCh) - go validatingWebhookController.Run(ctx.StopCh) - go mutatingWebhookController.Run(ctx.StopCh) + go webhookController.Run(ctx.StopCh) ctx.SecretInformers.Start(ctx.StopCh) ctx.WebhookInformers.Start(ctx.StopCh) @@ -112,6 +101,8 @@ func StartControllers(ctx ControllerContext, wmeta workloadmeta.Component, pa wo informers[apiserver.MutatingWebhooksInformer] = ctx.WebhookInformers.Admissionregistration().V1beta1().MutatingWebhookConfigurations().Informer() getWebhookStatus = getWebhookStatusV1beta1 } - // TODO Put back mutating - return validatingWebhookController.EnabledValidatingWebhooks(), nil, apiserver.SyncInformers(informers, 0) + + webhooks = append(webhooks, webhookController.EnabledWebhooks()...) + + return webhooks, apiserver.SyncInformers(informers, 0) } diff --git a/pkg/clusteragent/admission/validate/always_admit/always_admit.go b/pkg/clusteragent/admission/validate/always_admit/always_admit.go index 5631be5e9bc8d..de7d219e5ee5d 100644 --- a/pkg/clusteragent/admission/validate/always_admit/always_admit.go +++ b/pkg/clusteragent/admission/validate/always_admit/always_admit.go @@ -11,8 +11,13 @@ package always_admit import ( "github.com/DataDog/datadog-agent/cmd/cluster-agent/admission" - "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/mutate/common" - admiv1 "k8s.io/api/admissionregistration/v1" + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/common" + validatecommon "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/validate/common" + admiv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/dynamic" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -22,21 +27,23 @@ const ( ) type Webhook struct { - name string - isEnabled bool - endpoint string - resources []string - operations []admiv1.OperationType + name string + webhookType common.WebhookType + isEnabled bool + endpoint string + resources []string + operations []admissionregistrationv1.OperationType } // NewWebhook returns a new Webhook func NewWebhook() *Webhook { return &Webhook{ - name: webhookName, - isEnabled: true, - endpoint: webhookEndpoint, - resources: []string{"pods"}, - operations: []admiv1.OperationType{admiv1.Create}, + name: webhookName, + webhookType: common.ValidatingWebhook, + isEnabled: true, + endpoint: webhookEndpoint, + resources: []string{"pods"}, + operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create}, } } @@ -45,6 +52,11 @@ func (w *Webhook) Name() string { return w.name } +// WebhookType returns the type of the webhook +func (w *Webhook) WebhookType() common.WebhookType { + return w.webhookType +} + // IsEnabled returns whether the webhook is enabled func (w *Webhook) IsEnabled() bool { return w.isEnabled @@ -63,7 +75,7 @@ func (w *Webhook) Resources() []string { // Operations returns the operations on the resources specified for which // the webhook should be invoked -func (w *Webhook) Operations() []admiv1.OperationType { +func (w *Webhook) Operations() []admissionregistrationv1.OperationType { return w.operations } @@ -73,10 +85,14 @@ func (w *Webhook) LabelSelectors(useNamespaceSelector bool) (namespaceSelector * return common.DefaultLabelSelectors(useNamespaceSelector) } -// ValidateFunc returns the function that validate the resources -func (w *Webhook) ValidateFunc() admission.ValidatingWebhookFunc { - return w.validate +// WebhookFunc returns the function that validate the resources +func (w *Webhook) WebhookFunc() func(request *admission.Request) *admiv1.AdmissionResponse { + return func(request *admission.Request) *admiv1.AdmissionResponse { + return common.ValidationResponse(validatecommon.Validate(request.Raw, request.Namespace, w.Name(), w.alwaysAdmit, request.DynamicClient)) + } } -// validate validates the resources -func (w *Webhook) validate(request *admission.ValidateRequest) (bool, error) { return true, nil } +// alwaysAdmit is a function that always admits the pod. +func (w *Webhook) alwaysAdmit(_ *corev1.Pod, _ string, _ dynamic.Interface) (bool, error) { + return true, nil +} diff --git a/pkg/clusteragent/admission/validate/common/common.go b/pkg/clusteragent/admission/validate/common/common.go new file mode 100644 index 0000000000000..f5cdc764bcb77 --- /dev/null +++ b/pkg/clusteragent/admission/validate/common/common.go @@ -0,0 +1,43 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build kubeapiserver + +// Package common provides functions used by several mutating webhooks +package common + +import ( + "encoding/json" + "fmt" + "strconv" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/dynamic" + + "github.com/DataDog/datadog-agent/pkg/clusteragent/admission/metrics" +) + +// ValidationFunc is a function that validates a pod +type ValidationFunc func(pod *corev1.Pod, ns string, cl dynamic.Interface) (bool, error) + +// Validate handles validating pods and encoding and decoding admission +// requests and responses for the public validate functions +func Validate(rawPod []byte, ns string, validationType string, v ValidationFunc, dc dynamic.Interface) (bool, error) { + var pod corev1.Pod + if err := json.Unmarshal(rawPod, &pod); err != nil { + return false, fmt.Errorf("failed to decode raw object: %v", err) + } + + validated, err := v(&pod, ns, dc) + if err != nil { + // TODO (wassim): Check telemetry + metrics.ValidationAttempts.Inc(validationType, metrics.StatusError, strconv.FormatBool(false), err.Error()) + return false, fmt.Errorf("failed to validate pod: %v", err) + } + + // TODO (wassim): Check telemetry + metrics.ValidationAttempts.Inc(validationType, metrics.StatusSuccess, strconv.FormatBool(validated), "") + return validated, nil +} diff --git a/pkg/clusteragent/admission/validate/doc.go b/pkg/clusteragent/admission/validate/doc.go index 4f581a8c10f0e..e6ceedd10a235 100644 --- a/pkg/clusteragent/admission/validate/doc.go +++ b/pkg/clusteragent/admission/validate/doc.go @@ -5,31 +5,32 @@ //go:build kubeapiserver -// TODO edit documenation -// Package mutate contains mutating webhooks registered in the admission +// Package validate contains validating webhooks registered in the admission // controller. // -// The general idea of mutating webhooks is to intercept requests to the -// Kubernetes API server and modify the Kubernetes objects before the operation -// specified in the request is applied. For example, a mutating webhook can be -// configured to receive all the requests about creating or updating a pod, and -// modify the pod to enforce some defaults. A typical example is to intercept +// The general idea of validating webhooks is to intercept requests to the +// Kubernetes API server and either validate or refuse the Kubernetes objects before the operation +// specified in the request is applied. For example, a validating webhook can be +// configured to receive all the requests about creating or updating a pod, and refuse pod creation +// if they don't respect specific A typical example is to intercept // requests to create a pod and add some environment variables or volumes to the // pod to enable some functionality automatically. This saves the user from // having to add environment variables or volumes manually on each pod in their // cluster. -// To learn more about mutating webhooks, see the official Kubernetes documentation: +// To learn more about validating webhooks, see the official Kubernetes documentation: // https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/ // -// In general, each mutating webhook should be implemented in its own Go +// In general, each validating webhook should be implemented in its own Go // package. If there are some related webhooks that share some code, they can be // grouped in the same package. For example, the CWS webhooks are all in the // same package. // -// Each mutating webhook needs to implement the "MutatingWebhook" interface. +// Each validating webhook needs to implement the "Webhook" interface. // Here's a brief description of each function and what they are used for: // - Name: it's the name of the webhook. It's used to identify it. The name // appears in some telemetry tags. +// - WebhookType: it's the type of the webhook. It can be either "mutating" or +// "validating". // - IsEnabled: returns whether the webhook is enabled or not. In general, the // recommendation is to disable the webhook by default unless it's needed by a // core feature that should be enabled for everyone that deploys the Datadog @@ -45,11 +46,11 @@ // minimize the number of requests that the webhook receives. The label // selectors help us with that. There are some default label selectors defined // in the "common" package. -// - MutateFunc: the function that mutates the Kubernetes object. +// - WebhookFunc: the function that runs the logic of the webhook and returns the admission response. // -// As any other feature, mutating webhooks can be configured using the Datadog +// As any other feature, validating webhooks can be configured using the Datadog // configuration. When adding new configuration parameters, please try to follow -// the convention of the other mutating webhooks. The configuration parameters +// the convention of the other validating webhooks. The configuration parameters // for a webhook should be under the "admission_controller.name_of_the_webhook" // key. // @@ -59,10 +60,10 @@ // We should try to avoid depending on the order in which webhooks are executed. // When this cannot be avoided, keep in mind that the order in which the // webhooks are executed is the order in which they are returned by the -// "mutatingWebhooks" function in the "webhook" package. +// "generateWebhooks" function in the "webhook" package. // -// Mutating webhooks emit telemetry metrics. Each webhook can define its own -// metrics as needed but some metrics like "mutation_attempts" or +// Validating webhooks emit telemetry metrics. Each webhook can define its own +// metrics as needed but some metrics like "validation_attempts" or // "webhooks_received" are common to all webhooks and defined in common code, so // new webhooks can use them without having to define them again. //