diff --git a/main.go b/main.go index 76e2d79e3f..d89fac5be9 100644 --- a/main.go +++ b/main.go @@ -13,8 +13,9 @@ import ( "github.com/nirmata/kyverno/pkg/namespace" "github.com/nirmata/kyverno/pkg/policy" "github.com/nirmata/kyverno/pkg/policyviolation" + "github.com/nirmata/kyverno/pkg/utils" "github.com/nirmata/kyverno/pkg/webhooks" - "k8s.io/client-go/informers" + kubeinformer "k8s.io/client-go/informers" "k8s.io/sample-controller/pkg/signals" ) @@ -27,6 +28,7 @@ var ( webhookTimeout int ) +// TODO: tune resync time differently for each informer const defaultReSyncTime = 10 * time.Second func main() { @@ -48,6 +50,7 @@ func main() { if err != nil { glog.Fatalf("Error creating client: %v\n", err) } + // DYNAMIC CLIENT // - client for all registered resources client, err := client.NewClient(clientConfig) @@ -65,9 +68,16 @@ func main() { // - generate event with retry egen := event.NewEventGenerator(client, pInformer.Kyverno().V1alpha1().Policies()) - // mutatingWebhookConfiguration Informer - kubeInformer := informers.NewSharedInformerFactory(client.Kclient, defaultReSyncTime) - mutatingWebhookConfigurationLister := kubeInformer.Admissionregistration().V1beta1().MutatingWebhookConfigurations().Lister() + kubeClient, err := utils.NewKubeClient(clientConfig) + if err != nil { + glog.Fatalf("Error creating kubernetes client: %v\n", err) + } + + // - cache resync time: 10 seconds + kubeInformer := kubeinformer.NewSharedInformerFactoryWithOptions(kubeClient, defaultReSyncTime) + + // MutatingWebhookConfiguration Informer + mutatingWebhookConfigurationInformer := kubeInformer.Admissionregistration().V1beta1().MutatingWebhookConfigurations() tlsPair, err := initTLSPemPair(clientConfig, client) if err != nil { @@ -91,7 +101,7 @@ func main() { // - process policy on existing resources // - status: violation count - pc, err := policy.NewPolicyController(pclient, client, pInformer.Kyverno().V1alpha1().Policies(), pInformer.Kyverno().V1alpha1().PolicyViolations(), egen, mutatingWebhookConfigurationLister, webhookRegistrationClient) + pc, err := policy.NewPolicyController(pclient, client, pInformer.Kyverno().V1alpha1().Policies(), pInformer.Kyverno().V1alpha1().PolicyViolations(), egen, mutatingWebhookConfigurationInformer, webhookRegistrationClient) if err != nil { glog.Fatalf("error creating policy controller: %v\n", err) } diff --git a/pkg/dclient/client.go b/pkg/dclient/client.go index 4c46dd13f9..50a1f1d3e2 100644 --- a/pkg/dclient/client.go +++ b/pkg/dclient/client.go @@ -31,7 +31,7 @@ type Client struct { client dynamic.Interface cachedClient discovery.CachedDiscoveryInterface clientConfig *rest.Config - Kclient kubernetes.Interface + kclient kubernetes.Interface DiscoveryClient IDiscovery } @@ -48,7 +48,7 @@ func NewClient(config *rest.Config) (*Client, error) { client := Client{ client: dclient, clientConfig: config, - Kclient: kclient, + kclient: kclient, } // Set discovery client // @@ -75,12 +75,12 @@ func (c *Client) GetKubePolicyDeployment() (*apps.Deployment, error) { //TODO: can we use dynamic client to fetch the typed interface // or generate a kube client value to access the interface func (c *Client) GetEventsInterface() (event.EventInterface, error) { - return c.Kclient.CoreV1().Events(""), nil + return c.kclient.CoreV1().Events(""), nil } //GetCSRInterface provides type interface for CSR func (c *Client) GetCSRInterface() (csrtype.CertificateSigningRequestInterface, error) { - return c.Kclient.CertificatesV1beta1().CertificateSigningRequests(), nil + return c.kclient.CertificatesV1beta1().CertificateSigningRequests(), nil } func (c *Client) getInterface(resource string) dynamic.NamespaceableResourceInterface { diff --git a/pkg/dclient/utils.go b/pkg/dclient/utils.go index d82500f0d0..6492187038 100644 --- a/pkg/dclient/utils.go +++ b/pkg/dclient/utils.go @@ -32,7 +32,7 @@ func NewMockClient(scheme *runtime.Scheme, objects ...runtime.Object) (*Client, kclient := kubernetesfake.NewSimpleClientset(objects...) return &Client{ client: client, - Kclient: kclient, + kclient: kclient, }, nil } diff --git a/pkg/policy/controller.go b/pkg/policy/controller.go index 6e816621cb..6746022b8e 100644 --- a/pkg/policy/controller.go +++ b/pkg/policy/controller.go @@ -27,8 +27,9 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" + webhookinformer "k8s.io/client-go/informers/admissionregistration/v1beta1" typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" - v1beta1 "k8s.io/client-go/listers/admissionregistration/v1beta1" + webhooklister "k8s.io/client-go/listers/admissionregistration/v1beta1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -67,8 +68,8 @@ type PolicyController struct { pListerSynced cache.InformerSynced // pvListerSynced returns true if the Policy store has been synced at least once pvListerSynced cache.InformerSynced - // mutationwebhookInformer can list/get mutatingwebhookconfigurations - mutationwebhookInformer v1beta1.MutatingWebhookConfigurationLister + // mutationwebhookLister can list/get mutatingwebhookconfigurations + mutationwebhookLister webhooklister.MutatingWebhookConfigurationLister // WebhookRegistrationClient webhookRegistrationClient *webhooks.WebhookRegistrationClient // Resource manager, manages the mapping for already processed resource @@ -79,7 +80,7 @@ type PolicyController struct { // NewPolicyController create a new PolicyController func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client.Client, pInformer kyvernoinformer.PolicyInformer, pvInformer kyvernoinformer.PolicyViolationInformer, - eventGen event.Interface, mutationwebhookInformer v1beta1.MutatingWebhookConfigurationLister, webhookRegistrationClient *webhooks.WebhookRegistrationClient) (*PolicyController, error) { + eventGen event.Interface, webhookInformer webhookinformer.MutatingWebhookConfigurationInformer, webhookRegistrationClient *webhooks.WebhookRegistrationClient) (*PolicyController, error) { // Event broad caster eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) @@ -95,7 +96,6 @@ func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client. eventGen: eventGen, eventRecorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "policy_controller"}), queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "policy"), - mutationwebhookInformer: mutationwebhookInformer, webhookRegistrationClient: webhookRegistrationClient, } @@ -121,6 +121,8 @@ func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client. pc.pListerSynced = pInformer.Informer().HasSynced pc.pvListerSynced = pInformer.Informer().HasSynced + pc.mutationwebhookLister = webhookInformer.Lister() + // resource manager // rebuild after 300 seconds/ 5 mins //TODO: pass the time in seconds instead of converting it internally @@ -394,7 +396,9 @@ func (pc *PolicyController) syncPolicy(key string) error { policy, err := pc.pLister.Get(key) if errors.IsNotFound(err) { glog.V(2).Infof("Policy %v has been deleted", key) - err = pc.handleWebhookRegistration(true) + if err := pc.handleWebhookRegistration(true, nil); err != nil { + glog.Errorln(err) + } return err } @@ -402,7 +406,7 @@ func (pc *PolicyController) syncPolicy(key string) error { return err } - if err := pc.handleWebhookRegistration(false); err != nil { + if err := pc.handleWebhookRegistration(false, policy); err != nil { glog.Errorln(err) } @@ -421,28 +425,40 @@ func (pc *PolicyController) syncPolicy(key string) error { return pc.syncStatusOnly(p, pvList) } -func (pc *PolicyController) handleWebhookRegistration(emptyPolicy bool) error { +// TODO: here checks mutatingwebhook only +// as 'kubectl scale' is not funtional with validatingwebhook +// refer to https://github.com/nirmata/kyverno/issues/250 +func (pc *PolicyController) handleWebhookRegistration(delete bool, policy *kyverno.Policy) error { + policies, _ := pc.pLister.List(labels.NewSelector()) selector := &metav1.LabelSelector{MatchLabels: config.KubePolicyAppLabels} webhookSelector, err := metav1.LabelSelectorAsSelector(selector) if err != nil { return fmt.Errorf("invalid label selector: %v", err) } - list, err := pc.mutationwebhookInformer.List(webhookSelector) + webhookList, err := pc.mutationwebhookLister.List(webhookSelector) if err != nil { return fmt.Errorf("failed to list mutatingwebhookconfigurations, err %v", err) } - if emptyPolicy { - // deregister webhookconfigurations it it exists - if list != nil { + if delete { + if webhookList == nil { + return nil + } + + // webhook exist, deregister webhookconfigurations on condition + // check empty policy first, then rule type in terms of O(time) + if policies == nil { glog.V(3).Infoln("No policy found in the cluster, deregistering webhook") pc.webhookRegistrationClient.DeregisterMutatingWebhook() + } else if !webhooks.HasMutateOrValidatePolicies(policies) { + glog.V(3).Infoln("No muatate/validate policy found in the cluster, deregistering webhook") + pc.webhookRegistrationClient.DeregisterMutatingWebhook() } return nil } - if list == nil { + if webhookList == nil && webhooks.HasMutateOrValidate(*policy) { glog.V(3).Infoln("Found policy without mutatingwebhook, registering webhook") pc.webhookRegistrationClient.RegisterMutatingWebhook() } diff --git a/pkg/webhooks/webhookManager.go b/pkg/webhooks/webhookManager.go index 5a498f68fa..e808bb53aa 100644 --- a/pkg/webhooks/webhookManager.go +++ b/pkg/webhooks/webhookManager.go @@ -1,6 +1,8 @@ package webhooks import ( + "reflect" + "github.com/golang/glog" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" v1beta1 "k8s.io/api/admission/v1beta1" @@ -26,6 +28,10 @@ func (ws *WebhookServer) manageWebhookConfigurations(policy kyverno.Policy, op v } func (ws *WebhookServer) registerWebhookConfigurations(policy kyverno.Policy) error { + if !HasMutateOrValidate(policy) { + return nil + } + if !ws.webhookRegistrationClient.MutationRegistered.IsSet() { if err := ws.webhookRegistrationClient.RegisterMutatingWebhook(); err != nil { return err @@ -39,11 +45,30 @@ func (ws *WebhookServer) registerWebhookConfigurations(policy kyverno.Policy) er func (ws *WebhookServer) deregisterWebhookConfigurations(policy kyverno.Policy) error { policies, _ := ws.pLister.List(labels.NewSelector()) - // deregister webhook if no policy found in cluster - if len(policies) == 1 { + // deregister webhook if no mutate/validate policy found in cluster + if !HasMutateOrValidatePolicies(policies) { ws.webhookRegistrationClient.DeregisterMutatingWebhook() glog.Infoln("Mutating webhook deregistered") } return nil } + +func HasMutateOrValidatePolicies(policies []*kyverno.Policy) bool { + for _, policy := range policies { + if HasMutateOrValidate(*policy) { + return true + } + } + return false +} + +func HasMutateOrValidate(policy kyverno.Policy) bool { + for _, rule := range policy.Spec.Rules { + if !reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) || !reflect.DeepEqual(rule.Validation, kyverno.Validation{}) { + glog.Infoln(rule.Name) + return true + } + } + return false +}