diff --git a/main.go b/main.go index 32847e7bc2..2451f8b483 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,16 @@ func main() { if err != nil { glog.Fatalf("Error creating policy sharedinformer: %v\n", err) } + + webhookRegistrationClient, err := webhooks.NewWebhookRegistrationClient(clientConfig, client, serverIP, int32(webhookTimeout)) + if err != nil { + glog.Fatalf("Unable to register admission webhooks on cluster: %v\n", err) + } + + if err = webhookRegistrationClient.Register(); err != nil { + glog.Fatalf("Failed registering Admission Webhooks: %v\n", err) + } + kubeInformer := utils.NewKubeInformerFactory(clientConfig) eventController := event.NewEventController(client, policyInformerFactory) violationBuilder := violation.NewPolicyViolationBuilder(client, policyInformerFactory, eventController) @@ -54,7 +64,6 @@ func main() { policyInformerFactory, violationBuilder, eventController, - annotationsController, filterK8Resources) genControler := gencontroller.NewGenController(client, eventController, policyInformerFactory, violationBuilder, kubeInformer.Core().V1().Namespaces(), annotationsController) @@ -62,22 +71,14 @@ func main() { if err != nil { glog.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err) } - server, err := webhooks.NewWebhookServer(client, tlsPair, policyInformerFactory, eventController, violationBuilder, annotationsController, filterK8Resources) + + server, err := webhooks.NewWebhookServer(client, tlsPair, policyInformerFactory, eventController, violationBuilder, annotationsController, webhookRegistrationClient, filterK8Resources) if err != nil { glog.Fatalf("Unable to create webhook server: %v\n", err) } - webhookRegistrationClient, err := webhooks.NewWebhookRegistrationClient(clientConfig, client, serverIP, int32(webhookTimeout)) - if err != nil { - glog.Fatalf("Unable to register admission webhooks on cluster: %v\n", err) - } - stopCh := signals.SetupSignalHandler() - if err = webhookRegistrationClient.Register(); err != nil { - glog.Fatalf("Failed registering Admission Webhooks: %v\n", err) - } - policyInformerFactory.Run(stopCh) kubeInformer.Start(stopCh) eventController.Run(stopCh) diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 67cd0908fd..471aa1d61a 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -30,14 +30,13 @@ import ( //PolicyController to manage Policy CRD type PolicyController struct { - client *client.Client - policyLister lister.PolicyLister - policySynced cache.InformerSynced - violationBuilder violation.Generator - eventController event.Generator - annotationsController annotations.Controller - queue workqueue.RateLimitingInterface - filterK8Resources []utils.K8Resource + client *client.Client + policyLister lister.PolicyLister + policySynced cache.InformerSynced + violationBuilder violation.Generator + eventController event.Generator + queue workqueue.RateLimitingInterface + filterK8Resources []utils.K8Resource } // NewPolicyController from cmd args @@ -45,18 +44,16 @@ func NewPolicyController(client *client.Client, policyInformer sharedinformer.PolicyInformer, violationBuilder violation.Generator, eventController event.Generator, - annotationsController annotations.Controller, filterK8Resources string) *PolicyController { controller := &PolicyController{ - client: client, - policyLister: policyInformer.GetLister(), - policySynced: policyInformer.GetInfomer().HasSynced, - violationBuilder: violationBuilder, - eventController: eventController, - annotationsController: annotationsController, - filterK8Resources: utils.ParseKinds(filterK8Resources), - queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), policyWorkQueueName), + client: client, + policyLister: policyInformer.GetLister(), + policySynced: policyInformer.GetInfomer().HasSynced, + violationBuilder: violationBuilder, + eventController: eventController, + filterK8Resources: utils.ParseKinds(filterK8Resources), + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), policyWorkQueueName), } policyInformer.GetInfomer().AddEventHandler(cache.ResourceEventHandlerFuncs{ diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index 4163d08fdf..1adace6946 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -42,7 +42,6 @@ func (f *fixture) runControler(policyName string) { policyInformerFactory, violationBuilder, eventController, - nil, "") stopCh := signals.SetupSignalHandler() diff --git a/pkg/webhooks/policyvalidation.go b/pkg/webhooks/policyvalidation.go index 873e7e5bfa..8c36ecc953 100644 --- a/pkg/webhooks/policyvalidation.go +++ b/pkg/webhooks/policyvalidation.go @@ -13,16 +13,21 @@ import ( //HandlePolicyValidation performs the validation check on policy resource func (ws *WebhookServer) HandlePolicyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { - return ws.validateUniqueRuleName(request.Object.Raw) + var policy *policyv1.Policy + json.Unmarshal(request.Object.Raw, &policy) + + admissionResp := ws.validateUniqueRuleName(policy) + + if admissionResp.Allowed { + ws.registerWebhookConfigurations(*policy) + } + return admissionResp } // Verify if the Rule names are unique within a policy -func (ws *WebhookServer) validateUniqueRuleName(rawPolicy []byte) *v1beta1.AdmissionResponse { - var policy *policyv1.Policy +func (ws *WebhookServer) validateUniqueRuleName(policy *policyv1.Policy) *v1beta1.AdmissionResponse { var ruleNames []string - json.Unmarshal(rawPolicy, &policy) - for _, rule := range policy.Spec.Rules { if utils.Contains(ruleNames, rule.Name) { msg := fmt.Sprintf(`The policy "%s" is invalid: duplicate rule name: "%s"`, policy.Name, rule.Name) diff --git a/pkg/webhooks/registration.go b/pkg/webhooks/registration.go index d7384dffa8..c1f09c840f 100644 --- a/pkg/webhooks/registration.go +++ b/pkg/webhooks/registration.go @@ -9,10 +9,9 @@ import ( "github.com/golang/glog" "github.com/nirmata/kyverno/pkg/config" client "github.com/nirmata/kyverno/pkg/dclient" - + "github.com/tevino/abool" admregapi "k8s.io/api/admissionregistration/v1beta1" - errorsapi "k8s.io/apimachinery/pkg/api/errors" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" admregclient "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1" rest "k8s.io/client-go/rest" ) @@ -23,8 +22,10 @@ type WebhookRegistrationClient struct { client *client.Client clientConfig *rest.Config // serverIP should be used if running Kyverno out of clutser - serverIP string - timeoutSeconds int32 + serverIP string + timeoutSeconds int32 + MutationRegistered *abool.AtomicBool + ValidationRegistered *abool.AtomicBool } // NewWebhookRegistrationClient creates new WebhookRegistrationClient instance @@ -37,11 +38,13 @@ func NewWebhookRegistrationClient(clientConfig *rest.Config, client *client.Clie glog.V(3).Infof("Registering webhook client using serverIP %s\n", serverIP) return &WebhookRegistrationClient{ - registrationClient: registrationClient, - client: client, - clientConfig: clientConfig, - serverIP: serverIP, - timeoutSeconds: webhookTimeout, + registrationClient: registrationClient, + client: client, + clientConfig: clientConfig, + serverIP: serverIP, + timeoutSeconds: webhookTimeout, + MutationRegistered: abool.New(), + ValidationRegistered: abool.New(), }, nil } @@ -50,39 +53,51 @@ func (wrc *WebhookRegistrationClient) Register() error { if wrc.serverIP != "" { glog.Infof("Registering webhook with url https://%s\n", wrc.serverIP) } + // For the case if cluster already has this configs wrc.Deregister() + // register policy validating webhook during inital start + return wrc.RegisterPolicyValidatingWebhook() +} + +func (wrc *WebhookRegistrationClient) RegisterMutatingWebhook() error { mutatingWebhookConfig, err := wrc.constructMutatingWebhookConfig(wrc.clientConfig) if err != nil { return err } - _, err = wrc.registrationClient.MutatingWebhookConfigurations().Create(mutatingWebhookConfig) - if err != nil { + if _, err = wrc.registrationClient.MutatingWebhookConfigurations().Create(mutatingWebhookConfig); err != nil { return err } + return nil +} + +func (wrc *WebhookRegistrationClient) RegisterValidatingWebhook() error { validationWebhookConfig, err := wrc.constructValidatingWebhookConfig(wrc.clientConfig) if err != nil { return err } - _, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(validationWebhookConfig) - if err != nil { + if _, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(validationWebhookConfig); err != nil { return err } + return nil +} + +func (wrc *WebhookRegistrationClient) RegisterPolicyValidatingWebhook() error { policyValidationWebhookConfig, err := wrc.contructPolicyValidatingWebhookConfig() if err != nil { return err } - _, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(policyValidationWebhookConfig) - if err != nil { + if _, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(policyValidationWebhookConfig); err != nil { return err } + glog.V(3).Infoln("Policy validating webhook registered") return nil } @@ -90,28 +105,25 @@ func (wrc *WebhookRegistrationClient) Register() error { // This function does not fail on error: // Register will fail if the config exists, so there is no need to fail on error func (wrc *WebhookRegistrationClient) Deregister() { - if wrc.serverIP != "" { - if err := wrc.registrationClient.MutatingWebhookConfigurations().Delete(config.MutatingWebhookConfigurationDebug, &meta.DeleteOptions{}); err != nil { - if !errorsapi.IsNotFound(err) { - glog.Errorf("Failed to deregister debug mutatingWebhookConfiguratinos, err: %v\n", err) - } + listOpt := v1.ListOptions{LabelSelector: "app=kyverno"} + + // cleanup MutatingWebhookConfigurations + mutatingWebhookConfigList, _ := wrc.registrationClient.MutatingWebhookConfigurations().List(listOpt) + + for _, mwc := range mutatingWebhookConfigList.Items { + if err := wrc.registrationClient.MutatingWebhookConfigurations().Delete(mwc.ObjectMeta.Name, &v1.DeleteOptions{}); err != nil { + glog.Errorf("Failed to delete mutatingWebhookConfiguration, %s, err: %v\n", mwc.ObjectMeta.Name, err) } - if err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.ValidatingWebhookConfigurationDebug, &meta.DeleteOptions{}); err != nil { - if !errorsapi.IsNotFound(err) { - glog.Errorf("Failed to deregister debug validatingWebhookConfiguratinos, err: %v\n", err) - } - } - if err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.PolicyValidatingWebhookConfigurationDebug, &meta.DeleteOptions{}); err != nil { - if !errorsapi.IsNotFound(err) { - glog.Errorf("Failed to deregister debug policyValidatingWebhookConfiguratinos, err: %v\n", err) - } - } - return } - wrc.registrationClient.MutatingWebhookConfigurations().Delete(config.MutatingWebhookConfigurationName, &meta.DeleteOptions{}) - wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.ValidatingWebhookConfigurationName, &meta.DeleteOptions{}) - wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.PolicyValidatingWebhookConfigurationName, &meta.DeleteOptions{}) + // cleanup validatingWebhookConfiguratinos + validatingWebhookConfigList, _ := wrc.registrationClient.ValidatingWebhookConfigurations().List(listOpt) + + for _, mwc := range validatingWebhookConfigList.Items { + if err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(mwc.ObjectMeta.Name, &v1.DeleteOptions{}); err != nil { + glog.Errorf("Failed to delete validatingWebhookConfiguration, %s, err: %v\n", mwc.ObjectMeta.Name, err) + } + } } func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(configuration *rest.Config) (*admregapi.MutatingWebhookConfiguration, error) { @@ -132,10 +144,10 @@ func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(configurati } return &admregapi.MutatingWebhookConfiguration{ - ObjectMeta: meta.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: config.MutatingWebhookConfigurationName, Labels: config.KubePolicyAppLabels, - OwnerReferences: []meta.OwnerReference{ + OwnerReferences: []v1.OwnerReference{ wrc.constructOwner(), }, }, @@ -156,7 +168,7 @@ func (wrc *WebhookRegistrationClient) contructDebugMutatingWebhookConfig(caData glog.V(3).Infof("Debug MutatingWebhookConfig is registered with url %s\n", url) return &admregapi.MutatingWebhookConfiguration{ - ObjectMeta: meta.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: config.MutatingWebhookConfigurationDebug, Labels: config.KubePolicyAppLabels, }, @@ -188,10 +200,10 @@ func (wrc *WebhookRegistrationClient) constructValidatingWebhookConfig(configura } return &admregapi.ValidatingWebhookConfiguration{ - ObjectMeta: meta.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: config.ValidatingWebhookConfigurationName, Labels: config.KubePolicyAppLabels, - OwnerReferences: []meta.OwnerReference{ + OwnerReferences: []v1.OwnerReference{ wrc.constructOwner(), }, }, @@ -211,7 +223,7 @@ func (wrc *WebhookRegistrationClient) contructDebugValidatingWebhookConfig(caDat glog.V(3).Infof("Debug ValidatingWebhookConfig is registered with url %s\n", url) return &admregapi.ValidatingWebhookConfiguration{ - ObjectMeta: meta.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: config.ValidatingWebhookConfigurationDebug, Labels: config.KubePolicyAppLabels, }, @@ -243,10 +255,10 @@ func (wrc *WebhookRegistrationClient) contructPolicyValidatingWebhookConfig() (* } return &admregapi.ValidatingWebhookConfiguration{ - ObjectMeta: meta.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: config.PolicyValidatingWebhookConfigurationName, Labels: config.KubePolicyAppLabels, - OwnerReferences: []meta.OwnerReference{ + OwnerReferences: []v1.OwnerReference{ wrc.constructOwner(), }, }, @@ -266,7 +278,7 @@ func (wrc *WebhookRegistrationClient) contructDebugPolicyValidatingWebhookConfig glog.V(3).Infof("Debug PolicyValidatingWebhookConfig is registered with url %s\n", url) return &admregapi.ValidatingWebhookConfiguration{ - ObjectMeta: meta.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: config.PolicyValidatingWebhookConfigurationDebug, Labels: config.KubePolicyAppLabels, }, @@ -375,15 +387,15 @@ func constructDebugWebhook(name, url string, caData []byte, validation bool, tim } } -func (wrc *WebhookRegistrationClient) constructOwner() meta.OwnerReference { +func (wrc *WebhookRegistrationClient) constructOwner() v1.OwnerReference { kubePolicyDeployment, err := wrc.client.GetKubePolicyDeployment() if err != nil { glog.Errorf("Error when constructing OwnerReference, err: %v\n", err) - return meta.OwnerReference{} + return v1.OwnerReference{} } - return meta.OwnerReference{ + return v1.OwnerReference{ APIVersion: config.DeploymentAPIVersion, Kind: config.DeploymentKind, Name: kubePolicyDeployment.ObjectMeta.Name, diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index 6c5ce351f7..2bc05d0535 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -26,13 +26,14 @@ import ( // WebhookServer contains configured TLS server with MutationWebhook. // MutationWebhook gets policies from policyController and takes control of the cluster with kubeclient. type WebhookServer struct { - server http.Server - client *client.Client - policyLister v1alpha1.PolicyLister - eventController event.Generator - violationBuilder violation.Generator - annotationsController annotations.Controller - filterK8Resources []utils.K8Resource + server http.Server + client *client.Client + policyLister v1alpha1.PolicyLister + eventController event.Generator + violationBuilder violation.Generator + annotationsController annotations.Controller + webhookRegistrationClient *WebhookRegistrationClient + filterK8Resources []utils.K8Resource } // NewWebhookServer creates new instance of WebhookServer accordingly to given configuration @@ -44,6 +45,7 @@ func NewWebhookServer( eventController event.Generator, violationBuilder violation.Generator, annotationsController annotations.Controller, + webhookRegistrationClient *WebhookRegistrationClient, filterK8Resources string) (*WebhookServer, error) { if tlsPair == nil { @@ -58,12 +60,13 @@ func NewWebhookServer( tlsConfig.Certificates = []tls.Certificate{pair} ws := &WebhookServer{ - client: client, - policyLister: shareInformer.GetLister(), - eventController: eventController, - violationBuilder: violationBuilder, - annotationsController: annotationsController, - filterK8Resources: utils.ParseKinds(filterK8Resources), + client: client, + policyLister: shareInformer.GetLister(), + eventController: eventController, + violationBuilder: violationBuilder, + annotationsController: annotationsController, + webhookRegistrationClient: webhookRegistrationClient, + filterK8Resources: utils.ParseKinds(filterK8Resources), } mux := http.NewServeMux() mux.HandleFunc(config.MutatingWebhookServicePath, ws.serve) diff --git a/pkg/webhooks/webhookManager.go b/pkg/webhooks/webhookManager.go new file mode 100644 index 0000000000..7101f930ed --- /dev/null +++ b/pkg/webhooks/webhookManager.go @@ -0,0 +1,30 @@ +package webhooks + +import ( + "github.com/golang/glog" + v1alpha1 "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1" +) + +func (ws *WebhookServer) registerWebhookConfigurations(policy v1alpha1.Policy) error { + + for _, rule := range policy.Spec.Rules { + if rule.Mutation != nil && !ws.webhookRegistrationClient.MutationRegistered.IsSet() { + if err := ws.webhookRegistrationClient.RegisterMutatingWebhook(); err != nil { + return err + } + + ws.webhookRegistrationClient.MutationRegistered.Set() + glog.Infof("Mutating webhook registered") + } + + if rule.Validation != nil && !ws.webhookRegistrationClient.ValidationRegistered.IsSet() { + if err := ws.webhookRegistrationClient.RegisterValidatingWebhook(); err != nil { + return err + } + + ws.webhookRegistrationClient.ValidationRegistered.Set() + glog.Infof("Validating webhook registered") + } + } + return nil +}