1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-29 02:45:06 +00:00

fix: split webhook handlers per failure policy (#4650)

* fix: split webhook handlers per failure policy

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>

* fix handlers

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>

* rolling update

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>

* better error message

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
Co-authored-by: Vyankatesh Kudtarkar <vyankateshkd@gmail.com>
This commit is contained in:
Charles-Edouard Brétéché 2022-09-26 17:55:46 +02:00 committed by GitHub
parent 8741c34081
commit 665e513c5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 117 additions and 38 deletions

View file

@ -289,7 +289,8 @@ func defaultResourceWebhookRule(autoUpdate bool) admissionregistrationv1.Rule {
}
func constructDefaultDebugMutatingWebhookConfig(serverIP string, caData []byte, timeoutSeconds int32, autoUpdate bool, owner metav1.OwnerReference) *admissionregistrationv1.MutatingWebhookConfiguration {
name, url := config.MutatingWebhookName, fmt.Sprintf("https://%s%s", serverIP, config.MutatingWebhookServicePath)
name, baseUrl := config.MutatingWebhookName, fmt.Sprintf("https://%s%s", serverIP, config.MutatingWebhookServicePath)
url := fmt.Sprintf("%s/ignore", baseUrl)
webhook := &admissionregistrationv1.MutatingWebhookConfiguration{
ObjectMeta: generateObjectMeta(config.MutatingWebhookConfigurationDebugName, owner),
Webhooks: []admissionregistrationv1.MutatingWebhook{
@ -297,13 +298,15 @@ func constructDefaultDebugMutatingWebhookConfig(serverIP string, caData []byte,
},
}
if autoUpdate {
url := fmt.Sprintf("%s/fail", baseUrl)
webhook.Webhooks = append(webhook.Webhooks, generateDebugMutatingWebhook(name+"-fail", url, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), createUpdate, admissionregistrationv1.Fail))
}
return webhook
}
func constructDefaultMutatingWebhookConfig(caData []byte, timeoutSeconds int32, autoUpdate bool, owner metav1.OwnerReference) *admissionregistrationv1.MutatingWebhookConfiguration {
name, path := config.MutatingWebhookName, config.MutatingWebhookServicePath
name, basePath := config.MutatingWebhookName, config.MutatingWebhookServicePath
path := fmt.Sprintf("%s/ignore", basePath)
webhook := &admissionregistrationv1.MutatingWebhookConfiguration{
ObjectMeta: generateObjectMeta(config.MutatingWebhookConfigurationName, owner),
Webhooks: []admissionregistrationv1.MutatingWebhook{
@ -311,13 +314,15 @@ func constructDefaultMutatingWebhookConfig(caData []byte, timeoutSeconds int32,
},
}
if autoUpdate {
path := fmt.Sprintf("%s/fail", basePath)
webhook.Webhooks = append(webhook.Webhooks, generateMutatingWebhook(name+"-fail", path, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), createUpdate, admissionregistrationv1.Fail))
}
return webhook
}
func constructDefaultDebugValidatingWebhookConfig(serverIP string, caData []byte, timeoutSeconds int32, autoUpdate bool, owner metav1.OwnerReference) *admissionregistrationv1.ValidatingWebhookConfiguration {
name, url := config.ValidatingWebhookName, fmt.Sprintf("https://%s%s", serverIP, config.ValidatingWebhookServicePath)
name, baseUrl := config.ValidatingWebhookName, fmt.Sprintf("https://%s%s", serverIP, config.ValidatingWebhookServicePath)
url := fmt.Sprintf("%s/ignore", baseUrl)
webhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
ObjectMeta: generateObjectMeta(config.ValidatingWebhookConfigurationDebugName, owner),
Webhooks: []admissionregistrationv1.ValidatingWebhook{
@ -325,13 +330,15 @@ func constructDefaultDebugValidatingWebhookConfig(serverIP string, caData []byte
},
}
if autoUpdate {
url := fmt.Sprintf("%s/fail", baseUrl)
webhook.Webhooks = append(webhook.Webhooks, generateDebugValidatingWebhook(name+"-fail", url, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), all, admissionregistrationv1.Fail))
}
return webhook
}
func constructDefaultValidatingWebhookConfig(caData []byte, timeoutSeconds int32, autoUpdate bool, owner metav1.OwnerReference) *admissionregistrationv1.ValidatingWebhookConfiguration {
name, path := config.ValidatingWebhookName, config.ValidatingWebhookServicePath
name, basePath := config.ValidatingWebhookName, config.ValidatingWebhookServicePath
path := fmt.Sprintf("%s/ignore", basePath)
webhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
ObjectMeta: generateObjectMeta(config.ValidatingWebhookConfigurationName, owner),
Webhooks: []admissionregistrationv1.ValidatingWebhook{
@ -339,6 +346,7 @@ func constructDefaultValidatingWebhookConfig(caData []byte, timeoutSeconds int32
},
}
if autoUpdate {
path := fmt.Sprintf("%s/fail", basePath)
webhook.Webhooks = append(webhook.Webhooks, generateValidatingWebhook(name+"-fail", path, caData, timeoutSeconds, defaultResourceWebhookRule(autoUpdate), all, admissionregistrationv1.Fail))
}
return webhook

View file

@ -16,6 +16,7 @@ import (
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/metrics"
tlsutils "github.com/kyverno/kyverno/pkg/tls"
"github.com/kyverno/kyverno/pkg/utils"
"github.com/pkg/errors"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
@ -277,21 +278,31 @@ func (wrc *Register) UpdateWebhookConfigurations(configHandler config.Configurat
<-wrc.UpdateWebhookChan
logger.V(4).Info("received the signal to update webhook configurations")
webhookCfgs := configHandler.GetWebhooks()
webhookCfg := config.WebhookConfig{}
if len(webhookCfgs) > 0 {
webhookCfg = webhookCfgs[0]
}
retry := false
if err := wrc.updateResourceMutatingWebhookConfiguration(webhookCfg); err != nil {
logger.Error(err, "unable to update mutatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
deploy, err := wrc.GetKubePolicyDeployment()
if err != nil {
retry = true
}
if tlsutils.IsKyvernoInRollingUpdate(deploy) {
retry = true
}
if err := wrc.updateResourceValidatingWebhookConfiguration(webhookCfg); err != nil {
logger.Error(err, "unable to update validatingWebhookConfigurations", "name", getResourceValidatingWebhookConfigName(wrc.serverIP))
retry = true
if !retry {
webhookCfgs := configHandler.GetWebhooks()
webhookCfg := config.WebhookConfig{}
if len(webhookCfgs) > 0 {
webhookCfg = webhookCfgs[0]
}
if err := wrc.updateResourceMutatingWebhookConfiguration(webhookCfg); err != nil {
logger.Error(err, "unable to update mutatingWebhookConfigurations", "name", getResourceMutatingWebhookConfigName(wrc.serverIP))
retry = true
}
if err := wrc.updateResourceValidatingWebhookConfiguration(webhookCfg); err != nil {
logger.Error(err, "unable to update validatingWebhookConfigurations", "name", getResourceValidatingWebhookConfigName(wrc.serverIP))
retry = true
}
}
if retry {
@ -418,6 +429,13 @@ func (wrc *Register) checkEndpoint() error {
if err != nil {
return fmt.Errorf("failed to get endpoint %s/%s: %v", config.KyvernoNamespace(), config.KyvernoServiceName(), err)
}
deploy, err := wrc.GetKubePolicyDeployment()
if err != nil {
return err
}
if tlsutils.IsKyvernoInRollingUpdate(deploy) {
return errors.New("kyverno is in rolling update, please update the timeout by setting the webhookRegistrationTimeout flag")
}
selector := &metav1.LabelSelector{
MatchLabels: map[string]string{
"app.kubernetes.io/name": kyvernov1.ValueKyvernoApp,

View file

@ -21,7 +21,7 @@ type handlers struct {
openAPIController *openapi.Controller
}
func NewHandlers(client dclient.Interface, openAPIController *openapi.Controller) webhooks.Handlers {
func NewHandlers(client dclient.Interface, openAPIController *openapi.Controller) webhooks.PolicyHandlers {
return &handlers{
client: client,
openAPIController: openAPIController,

View file

@ -21,7 +21,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
)
func NewFakeHandlers(ctx context.Context, policyCache policycache.Cache) webhooks.Handlers {
func NewFakeHandlers(ctx context.Context, policyCache policycache.Cache) webhooks.ResourceHandlers {
client := fake.NewSimpleClientset()
metricsConfig := metrics.NewFakeMetricsConfig(client)

View file

@ -75,7 +75,7 @@ func NewHandlers(
eventGen event.Interface,
auditHandler audit.AuditHandler,
openAPIController openapi.ValidateInterface,
) webhooks.Handlers {
) webhooks.ResourceHandlers {
return &handlers{
client: client,
kyvernoClient: kyvernoClient,
@ -96,7 +96,7 @@ func NewHandlers(
}
}
func (h *handlers) Validate(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
func (h *handlers) Validate(logger logr.Logger, request *admissionv1.AdmissionRequest, failurePolicy string, startTime time.Time) *admissionv1.AdmissionResponse {
if webhookutils.ExcludeKyvernoResources(request.Kind.Kind) {
return admissionutils.ResponseSuccess()
}
@ -105,10 +105,10 @@ func (h *handlers) Validate(logger logr.Logger, request *admissionv1.AdmissionRe
logger.V(4).Info("received an admission request in validating webhook")
// timestamp at which this admission request got triggered
policies := h.pCache.GetPolicies(policycache.ValidateEnforce, kind, request.Namespace)
mutatePolicies := h.pCache.GetPolicies(policycache.Mutate, kind, request.Namespace)
generatePolicies := h.pCache.GetPolicies(policycache.Generate, kind, request.Namespace)
imageVerifyValidatePolicies := h.pCache.GetPolicies(policycache.VerifyImagesValidate, kind, request.Namespace)
policies := filterPolicies(failurePolicy, h.pCache.GetPolicies(policycache.ValidateEnforce, kind, request.Namespace)...)
mutatePolicies := filterPolicies(failurePolicy, h.pCache.GetPolicies(policycache.Mutate, kind, request.Namespace)...)
generatePolicies := filterPolicies(failurePolicy, h.pCache.GetPolicies(policycache.Generate, kind, request.Namespace)...)
imageVerifyValidatePolicies := filterPolicies(failurePolicy, h.pCache.GetPolicies(policycache.VerifyImagesValidate, kind, request.Namespace)...)
policies = append(policies, imageVerifyValidatePolicies...)
if len(policies) == 0 && len(mutatePolicies) == 0 && len(generatePolicies) == 0 {
@ -152,7 +152,7 @@ func (h *handlers) Validate(logger logr.Logger, request *admissionv1.AdmissionRe
return admissionutils.ResponseSuccess()
}
func (h *handlers) Mutate(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
func (h *handlers) Mutate(logger logr.Logger, request *admissionv1.AdmissionRequest, failurePolicy string, startTime time.Time) *admissionv1.AdmissionResponse {
if webhookutils.ExcludeKyvernoResources(request.Kind.Kind) {
return admissionutils.ResponseSuccess()
}
@ -162,8 +162,8 @@ func (h *handlers) Mutate(logger logr.Logger, request *admissionv1.AdmissionRequ
kind := request.Kind.Kind
logger = logger.WithValues("kind", kind)
logger.V(4).Info("received an admission request in mutating webhook")
mutatePolicies := h.pCache.GetPolicies(policycache.Mutate, kind, request.Namespace)
verifyImagesPolicies := h.pCache.GetPolicies(policycache.VerifyImagesMutate, kind, request.Namespace)
mutatePolicies := filterPolicies(failurePolicy, h.pCache.GetPolicies(policycache.Mutate, kind, request.Namespace)...)
verifyImagesPolicies := filterPolicies(failurePolicy, h.pCache.GetPolicies(policycache.VerifyImagesMutate, kind, request.Namespace)...)
if len(mutatePolicies) == 0 && len(verifyImagesPolicies) == 0 {
logger.V(4).Info("no policies matched mutate admission request")
return admissionutils.ResponseSuccess()
@ -226,3 +226,21 @@ func (h *handlers) handleDelete(logger logr.Logger, request *admissionv1.Admissi
}
}
}
func filterPolicies(failurePolicy string, policies ...kyvernov1.PolicyInterface) []kyvernov1.PolicyInterface {
var results []kyvernov1.PolicyInterface
for _, policy := range policies {
if failurePolicy == "fail" {
if policy.GetSpec().GetFailurePolicy() == kyvernov1.Fail {
results = append(results, policy)
}
} else if failurePolicy == "ignore" {
if policy.GetSpec().GetFailurePolicy() == kyvernov1.Ignore {
results = append(results, policy)
}
} else {
results = append(results, policy)
}
}
return results
}

View file

@ -176,18 +176,18 @@ func Test_AdmissionResponseValid(t *testing.T) {
},
}
response := handlers.Mutate(logger, request, time.Now())
response := handlers.Mutate(logger, request, "", time.Now())
assert.Assert(t, response != nil)
assert.Equal(t, response.Allowed, true)
response = handlers.Validate(logger, request, time.Now())
response = handlers.Validate(logger, request, "", time.Now())
assert.Equal(t, response.Allowed, true)
assert.Equal(t, len(response.Warnings), 0)
validPolicy.Spec.ValidationFailureAction = kyverno.Enforce
policyCache.Set(key, &validPolicy)
response = handlers.Validate(logger, request, time.Now())
response = handlers.Validate(logger, request, "", time.Now())
assert.Equal(t, response.Allowed, false)
assert.Equal(t, len(response.Warnings), 0)
@ -220,7 +220,7 @@ func Test_AdmissionResponseInvalid(t *testing.T) {
invalidPolicy.Spec.ValidationFailureAction = kyverno.Enforce
policyCache.Set(keyInvalid, &invalidPolicy)
response := handlers.Validate(logger, request, time.Now())
response := handlers.Validate(logger, request, "", time.Now())
assert.Equal(t, response.Allowed, false)
assert.Equal(t, len(response.Warnings), 0)
@ -228,7 +228,7 @@ func Test_AdmissionResponseInvalid(t *testing.T) {
invalidPolicy.Spec.FailurePolicy = &ignore
policyCache.Set(keyInvalid, &invalidPolicy)
response = handlers.Validate(logger, request, time.Now())
response = handlers.Validate(logger, request, "", time.Now())
assert.Equal(t, response.Allowed, true)
assert.Equal(t, len(response.Warnings), 1)
}
@ -261,7 +261,7 @@ func Test_ImageVerify(t *testing.T) {
policy.Spec.ValidationFailureAction = kyverno.Enforce
policyCache.Set(key, &policy)
response := handlers.Mutate(logger, request, time.Now())
response := handlers.Mutate(logger, request, "", time.Now())
assert.Equal(t, response.Allowed, false)
assert.Equal(t, len(response.Warnings), 0)
@ -269,7 +269,7 @@ func Test_ImageVerify(t *testing.T) {
policy.Spec.FailurePolicy = &ignore
policyCache.Set(key, &policy)
response = handlers.Mutate(logger, request, time.Now())
response = handlers.Mutate(logger, request, "", time.Now())
assert.Equal(t, response.Allowed, false)
assert.Equal(t, len(response.Warnings), 0)
}

View file

@ -28,13 +28,20 @@ type Server interface {
Stop(context.Context)
}
type Handlers interface {
type PolicyHandlers interface {
// Mutate performs the mutation of policy resources
Mutate(logr.Logger, *admissionv1.AdmissionRequest, time.Time) *admissionv1.AdmissionResponse
// Validate performs the validation check on policy resources
Validate(logr.Logger, *admissionv1.AdmissionRequest, time.Time) *admissionv1.AdmissionResponse
}
type ResourceHandlers interface {
// Mutate performs the mutation of kube resources
Mutate(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse
// Validate performs the validation check on kube resources
Validate(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse
}
type server struct {
server *http.Server
webhookRegister *webhookconfig.Register
@ -45,8 +52,8 @@ type TlsProvider func() ([]byte, []byte, error)
// NewServer creates new instance of server accordingly to given configuration
func NewServer(
policyHandlers Handlers,
resourceHandlers Handlers,
policyHandlers PolicyHandlers,
resourceHandlers ResourceHandlers,
tlsProvider TlsProvider,
configuration config.Configuration,
register *webhookconfig.Register,
@ -57,8 +64,8 @@ func NewServer(
resourceLogger := logger.WithName("resource")
policyLogger := logger.WithName("policy")
verifyLogger := logger.WithName("verify")
mux.HandlerFunc("POST", config.MutatingWebhookServicePath, admission(resourceLogger.WithName("mutate"), monitor, filter(configuration, resourceHandlers.Mutate)))
mux.HandlerFunc("POST", config.ValidatingWebhookServicePath, admission(resourceLogger.WithName("validate"), monitor, filter(configuration, resourceHandlers.Validate)))
registerWebhookHandlers(resourceLogger.WithName("mutate"), mux, config.MutatingWebhookServicePath, monitor, configuration, resourceHandlers.Mutate)
registerWebhookHandlers(resourceLogger.WithName("validate"), mux, config.ValidatingWebhookServicePath, monitor, configuration, resourceHandlers.Validate)
mux.HandlerFunc("POST", config.PolicyMutatingWebhookServicePath, admission(policyLogger.WithName("mutate"), monitor, filter(configuration, policyHandlers.Mutate)))
mux.HandlerFunc("POST", config.PolicyValidatingWebhookServicePath, admission(policyLogger.WithName("validate"), monitor, filter(configuration, policyHandlers.Validate)))
mux.HandlerFunc("POST", config.VerifyMutatingWebhookServicePath, admission(verifyLogger.WithName("mutate"), monitor, handlers.Verify(monitor)))
@ -153,3 +160,31 @@ func filter(configuration config.Configuration, inner handlers.AdmissionHandler)
func admission(logger logr.Logger, monitor *webhookconfig.Monitor, inner handlers.AdmissionHandler) http.HandlerFunc {
return handlers.Monitor(monitor, handlers.Admission(logger, protect(inner)))
}
func registerWebhookHandlers(
logger logr.Logger,
mux *httprouter.Router,
basePath string,
monitor *webhookconfig.Monitor,
configuration config.Configuration,
handlerFunc func(logr.Logger, *admissionv1.AdmissionRequest, string, time.Time) *admissionv1.AdmissionResponse,
) {
mux.HandlerFunc("POST", basePath, admission(logger, monitor, filter(
configuration,
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
return handlerFunc(logger, request, "all", startTime)
})),
)
mux.HandlerFunc("POST", basePath+"/fail", admission(logger, monitor, filter(
configuration,
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
return handlerFunc(logger, request, "fail", startTime)
})),
)
mux.HandlerFunc("POST", basePath+"/ignore", admission(logger, monitor, filter(
configuration,
func(logger logr.Logger, request *admissionv1.AdmissionRequest, startTime time.Time) *admissionv1.AdmissionResponse {
return handlerFunc(logger, request, "ignore", startTime)
})),
)
}