diff --git a/main.go b/main.go index 643b8d3c91..b99ca7673a 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( event "github.com/nirmata/kyverno/pkg/event" "github.com/nirmata/kyverno/pkg/namespace" "github.com/nirmata/kyverno/pkg/policy" + "github.com/nirmata/kyverno/pkg/policystore" "github.com/nirmata/kyverno/pkg/policyviolation" "github.com/nirmata/kyverno/pkg/utils" "github.com/nirmata/kyverno/pkg/webhookconfig" @@ -79,6 +80,9 @@ func main() { glog.Fatalf("Unable to register admission webhooks on cluster: %v\n", err) } + // Policy meta-data store + policyMetaStore := policystore.NewPolicyStore() + // KYVERNO CRD INFORMER // watches CRD resources: // - Policy @@ -106,7 +110,7 @@ func main() { // - process policy on existing resources // - status aggregator: recieves stats when a policy is applied // & updates the policy status - pc, err := policy.NewPolicyController(pclient, client, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations(), egen, kubeInformer.Admissionregistration().V1beta1().MutatingWebhookConfigurations(), webhookRegistrationClient, configData) + pc, err := policy.NewPolicyController(pclient, client, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations(), egen, kubeInformer.Admissionregistration().V1beta1().MutatingWebhookConfigurations(), webhookRegistrationClient, configData, policyMetaStore) if err != nil { glog.Fatalf("error creating policy controller: %v\n", err) } diff --git a/pkg/policy/controller.go b/pkg/policy/controller.go index b838c1ea9a..3465e9777a 100644 --- a/pkg/policy/controller.go +++ b/pkg/policy/controller.go @@ -16,6 +16,7 @@ import ( "github.com/nirmata/kyverno/pkg/config" client "github.com/nirmata/kyverno/pkg/dclient" "github.com/nirmata/kyverno/pkg/event" + "github.com/nirmata/kyverno/pkg/policystore" "github.com/nirmata/kyverno/pkg/webhookconfig" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -77,12 +78,15 @@ type PolicyController struct { configHandler config.Interface // recieves stats and aggregates details statusAggregator *PolicyStatusAggregator + // store to hold policy meta data for faster lookup + pMetaStore policystore.Interface } // NewPolicyController create a new PolicyController func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client.Client, pInformer kyvernoinformer.ClusterPolicyInformer, pvInformer kyvernoinformer.ClusterPolicyViolationInformer, eventGen event.Interface, webhookInformer webhookinformer.MutatingWebhookConfigurationInformer, webhookRegistrationClient *webhookconfig.WebhookRegistrationClient, - configHandler config.Interface) (*PolicyController, error) { + configHandler config.Interface, + pMetaStore policystore.Interface) (*PolicyController, error) { // Event broad caster eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(glog.Infof) @@ -100,6 +104,7 @@ func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client. queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "policy"), webhookRegistrationClient: webhookRegistrationClient, configHandler: configHandler, + pMetaStore: pMetaStore, } pc.pvControl = RealPVControl{Client: kyvernoClient, Recorder: pc.eventRecorder} @@ -141,6 +146,8 @@ func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client. func (pc *PolicyController) addPolicy(obj interface{}) { p := obj.(*kyverno.ClusterPolicy) glog.V(4).Infof("Adding Policy %s", p.Name) + // register with policy meta-store + pc.pMetaStore.Register(*p) pc.enqueuePolicy(p) } @@ -148,6 +155,10 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) { oldP := old.(*kyverno.ClusterPolicy) curP := cur.(*kyverno.ClusterPolicy) glog.V(4).Infof("Updating Policy %s", oldP.Name) + // TODO: optimize this : policy meta-store + // Update policy-> (remove,add) + pc.pMetaStore.UnRegister(*oldP) + pc.pMetaStore.Register(*curP) pc.enqueuePolicy(curP) } @@ -166,6 +177,8 @@ func (pc *PolicyController) deletePolicy(obj interface{}) { } } glog.V(4).Infof("Deleting Policy %s", p.Name) + // Unregister from policy meta-store + pc.pMetaStore.UnRegister(*p) pc.enqueuePolicy(p) } diff --git a/pkg/policystore/policystore.go b/pkg/policystore/policystore.go index 2c3c6bcd66..2d173a9aef 100644 --- a/pkg/policystore/policystore.go +++ b/pkg/policystore/policystore.go @@ -4,60 +4,203 @@ import ( "sync" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +type PolicyElement struct { + Name string + Rule string +} + +//Operation defines the operation that a rule is performing +// we can only have a single operation per rule +type Operation string + +const ( + //Mutation : mutation rules + Mutation Operation = "Mutation" + //Validation : validation rules + Validation Operation = "Validation" + //Generation : generation rules + Generation Operation = "Generation" +) + +type policyMap map[PolicyElement]interface{} + +//PolicyStore Store the meta-data information to faster lookup policies +type PolicyStore struct { + data map[Operation]map[string]map[string]policyMap + mu sync.RWMutex +} + type Interface interface { - Register(policy *kyverno.Policy) error - UnRegister(policy *kyverno.Policy) error // check if the controller can see the policy spec for details? - LookUp(kind, namespace, name string, ls *metav1.LabelSelector) // returns a list of policies and rules that apply + // Register a new policy + Register(policy kyverno.ClusterPolicy) + // Remove policy information + UnRegister(policy kyverno.ClusterPolicy) error + // Lookup based on kind and namespaces + LookUp(operation Operation, kind, namespace string) []PolicyElement } -type Store struct { - data map[string]string - mux sync.RWMutex -} - -func NewStore() *Store { - s := Store{ - data: make(map[string]string), //key: kind, value is the name of the policy +// NewPolicyStore returns a new policy store +func NewPolicyStore() *PolicyStore { + ps := PolicyStore{ + data: make(map[Operation]map[string]map[string]policyMap), } - - return &s + return &ps } -var empty struct{} - -func (s *Store) Register(policy *kyverno.Policy) error { - // check if this policy is already registered for this resource kind - kinds := map[string]string{} - // get kinds from the rules - for _, r := range policy.Spec.Rules { - rkinds := map[string]string{} - // matching resources - for _, k := range r.MatchResources.Kinds { - rkinds[k] = policy.Name - } - for _, k := range r.ExcludeResources.Kinds { - delete(rkinds, k) - } - // merge the result - mergeMap(kinds, rkinds) - +func operation(rule kyverno.Rule) Operation { + if rule.HasMutate() { + return Mutation + } else if rule.HasValidate() { + return Validation + } else { + return Generation } +} - // have all the kinds that the policy has rule on - s.mux.Lock() - defer s.mux.Unlock() - // merge kinds - mergeMap(s.data, kinds) +//Register a new policy +func (ps *PolicyStore) Register(policy kyverno.ClusterPolicy) { + ps.mu.Lock() + defer ps.mu.Unlock() + var pmap policyMap + // add an entry for each rule in policy + for _, rule := range policy.Spec.Rules { + // get operation + operation := operation(rule) + operationMap := ps.addOperation(operation) + // rule.MatchResources.Kinds - List - mandatory - atleast on entry + for _, kind := range rule.MatchResources.Kinds { + kindMap := addKind(operationMap, kind) + // namespaces + if len(rule.MatchResources.Namespaces) == 0 { + // all namespaces - * + pmap = addNamespace(kindMap, "*") + } else { + for _, ns := range rule.MatchResources.Namespaces { + pmap = addNamespace(kindMap, ns) + } + } + // add policy to the pmap + addPolicyElement(pmap, policy.Name, rule.Name) + } + } +} + +//UnRegister Remove policy information +func (ps *PolicyStore) UnRegister(policy kyverno.ClusterPolicy) error { + ps.mu.Lock() + defer ps.mu.Unlock() + for _, rule := range policy.Spec.Rules { + // get operation + operation := operation(rule) + operationMap := ps.getOperation(operation) + for _, kind := range rule.MatchResources.Kinds { + // get kind Map + kindMap := getKind(operationMap, kind) + if kindMap == nil { + // kind does not exist + return nil + } + if len(rule.MatchResources.Namespaces) == 0 { + namespace := "*" + pmap := getNamespace(kindMap, namespace) + // remove element + delete(pmap, PolicyElement{Name: policy.Name, Rule: rule.Name}) + } else { + for _, ns := range rule.MatchResources.Namespaces { + pmap := getNamespace(kindMap, ns) + // remove element + delete(pmap, PolicyElement{Name: policy.Name, Rule: rule.Name}) + } + } + } + } return nil } -// merge m2 into m2 -func mergeMap(m1, m2 map[string]string) { - for k, v := range m2 { - m1[k] = v +//LookUp lookups up the policies for kind and namespace +// returns a list of that statisfy the filters +func (ps *PolicyStore) LookUp(operation Operation, kind, namespace string) []PolicyElement { + ps.mu.RLock() + defer ps.mu.RUnlock() + var policyMap policyMap + var ret []PolicyElement + // operation + operationMap := ps.getOperation(operation) + if operationMap == nil { + return []PolicyElement{} + } + // kind + kindMap := getKind(operationMap, kind) + if kindMap == nil { + return []PolicyElement{} + } + // get namespace specific policies + policyMap = kindMap[namespace] + ret = append(ret, transform(policyMap)...) + // get policies on all namespaces + policyMap = kindMap["*"] + ret = append(ret, transform(policyMap)...) + return ret +} + +// generates a copy +func transform(pmap policyMap) []PolicyElement { + ret := []PolicyElement{} + for k := range pmap { + ret = append(ret, k) + } + return ret +} + +func (ps *PolicyStore) addOperation(operation Operation) map[string]map[string]policyMap { + operationMap, ok := ps.data[operation] + if ok { + return operationMap + } + ps.data[operation] = make(map[string]map[string]policyMap) + return ps.data[operation] +} + +func (ps *PolicyStore) getOperation(operation Operation) map[string]map[string]policyMap { + return ps.data[operation] +} + +func addKind(operationMap map[string]map[string]policyMap, kind string) map[string]policyMap { + val, ok := operationMap[kind] + if ok { + return val + } + operationMap[kind] = make(map[string]policyMap) + return operationMap[kind] +} + +func getKind(operationMap map[string]map[string]policyMap, kind string) map[string]policyMap { + return operationMap[kind] +} + +func addNamespace(kindMap map[string]policyMap, namespace string) policyMap { + val, ok := kindMap[namespace] + if ok { + return val + } + kindMap[namespace] = make(policyMap) + return kindMap[namespace] +} + +func getNamespace(kindMap map[string]policyMap, namespace string) policyMap { + return kindMap[namespace] +} + +func addPolicyElement(pmap policyMap, name, rule string) { + var emptyInterface interface{} + key := PolicyElement{ + Name: name, + Rule: rule, + } + if _, ok := pmap[key]; !ok { + pmap[key] = emptyInterface } } diff --git a/pkg/policystore/policystore_test.go b/pkg/policystore/policystore_test.go new file mode 100644 index 0000000000..b121c8bb78 --- /dev/null +++ b/pkg/policystore/policystore_test.go @@ -0,0 +1,162 @@ +package policystore + +import ( + "encoding/json" + "testing" + + kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" +) + +func Test_Add(t *testing.T) { + rawPolicy1 := []byte(` + { + "apiVersion": "kyverno.io/v1alpha1", + "kind": "ClusterPolicy", + "metadata": { + "name": "test-policy" + }, + "spec": { + "rules": [ + { + "name": "r1", + "match": { + "resources": { + "kinds": [ + "Pod" + ] + } + }, + "mutate": { + "overlay": "temp" + } + }, + { + "name": "r2", + "match": { + "resources": { + "kinds": [ + "Pod", + "Deployment" + ] + } + }, + "mutate": { + "overlay": "temp" + } + }, + { + "name": "r3", + "match": { + "resources": { + "kinds": [ + "Pod", + "Deployment" + ], + "namespaces": [ + "n1" + ] + } + }, + "mutate": { + "overlay": "temp" + } + }, + { + "name": "r4", + "match": { + "resources": { + "kinds": [ + "Pod", + "Deployment" + ], + "namespaces": [ + "n1", + "n2" + ] + } + }, + "validate": { + "pattern": "temp" + } + } + ] + } + } + `) + + rawPolicy2 := []byte(` + { + "apiVersion": "kyverno.io/v1alpha1", + "kind": "ClusterPolicy", + "metadata": { + "name": "test-policy1" + }, + "spec": { + "rules": [ + { + "name": "r1", + "match": { + "resources": { + "kinds": [ + "Pod" + ] + } + }, + "mutate": { + "overlay": "temp" + } + }, + { + "name": "r2", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "n4" + ] + } + }, + "mutate": { + "overlay": "temp" + } + }, + { + "name": "r2", + "match": { + "resources": { + "kinds": [ + "Pod" + ], + "namespaces": [ + "n4", + "n5", + "n6" + ] + } + }, + "validate": { + "pattern": "temp" + } + } + ] + } + }`) + var policy1 kyverno.ClusterPolicy + json.Unmarshal(rawPolicy1, &policy1) + var policy2 kyverno.ClusterPolicy + json.Unmarshal(rawPolicy2, &policy2) + + var store Interface + store = NewPolicyStore() + // Add + store.Register(policy1) + store.Register(policy2) + t.Log(store.LookUp(Mutation, "Pod", "")) + store.UnRegister(policy1) + t.Log(store.LookUp(Mutation, "Pod", "")) + store.Register(policy1) + t.Log(store.LookUp(Mutation, "Pod", "")) + t.Fail() +} diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index 981860f36b..fd7be6fe7c 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -19,6 +19,7 @@ import ( client "github.com/nirmata/kyverno/pkg/dclient" "github.com/nirmata/kyverno/pkg/event" "github.com/nirmata/kyverno/pkg/policy" + "github.com/nirmata/kyverno/pkg/policystore" tlsutils "github.com/nirmata/kyverno/pkg/tls" "github.com/nirmata/kyverno/pkg/webhookconfig" v1beta1 "k8s.io/api/admission/v1beta1" @@ -46,6 +47,8 @@ type WebhookServer struct { cleanUp chan<- struct{} // last request time lastReqTime *checker.LastReqTime + // store to hold policy meta data for faster lookup + pMetaStore policystore.Interface } // NewWebhookServer creates new instance of WebhookServer accordingly to given configuration @@ -60,6 +63,7 @@ func NewWebhookServer( webhookRegistrationClient *webhookconfig.WebhookRegistrationClient, policyStatus policy.PolicyStatusInterface, configHandler config.Interface, + pMetaStore policystore.Interface, cleanUp chan<- struct{}) (*WebhookServer, error) { if tlsPair == nil { @@ -87,6 +91,7 @@ func NewWebhookServer( configHandler: configHandler, cleanUp: cleanUp, lastReqTime: checker.NewLastReqTime(), + pMetaStore: pMetaStore, } mux := http.NewServeMux() mux.HandleFunc(config.MutatingWebhookServicePath, ws.serve)