From f97406698dd5a67c913f0db39d268fa0fb3e6709 Mon Sep 17 00:00:00 2001 From: shivkumar dudhani <shivkumar@nirmata.com> Date: Fri, 15 Nov 2019 12:03:58 -0800 Subject: [PATCH] remove namespace from resource spec --- pkg/api/kyverno/v1/types.go | 9 +- pkg/api/kyverno/v1/utils.go | 15 +- pkg/namespace/controller.go | 2 - pkg/policy/cleanup.go | 5 +- pkg/policy/controller.go | 2 +- pkg/policyviolation/clusterpv.go | 152 +++++++++++++++ pkg/policyviolation/controller.go | 35 ++-- pkg/policyviolation/generator.go | 195 +------------------ pkg/policyviolation/helpers.go | 61 +++--- pkg/policyviolation/namespacedpv.go | 36 +++- pkg/policyviolation/namespacepvcontroller.go | 16 +- pkg/testrunner/scenario.go | 9 +- pkg/webhooks/report.go | 1 - 13 files changed, 255 insertions(+), 283 deletions(-) create mode 100644 pkg/policyviolation/clusterpv.go diff --git a/pkg/api/kyverno/v1/types.go b/pkg/api/kyverno/v1/types.go index 0e90399be3..688ccfedc0 100644 --- a/pkg/api/kyverno/v1/types.go +++ b/pkg/api/kyverno/v1/types.go @@ -188,9 +188,8 @@ type PolicyViolationSpec struct { // ResourceSpec information to identify the resource type ResourceSpec struct { - Kind string `json:"kind"` - Namespace string `json:"namespace,omitempty"` - Name string `json:"name"` + Kind string `json:"kind"` + Name string `json:"name"` } // ViolatedRule stores the information regarding the rule @@ -201,9 +200,10 @@ type ViolatedRule struct { ManagedResource ManagedResourceSpec `json:"managedResource,omitempty"` } +// ManagedResourceSpec is used when the violations is created on resource owner +// to determing the kind of child resource that caused the violation type ManagedResourceSpec struct { Kind string `json:"kind,omitempty"` - Namespace string `json:"namespace,omitempty"` CreationBlocked bool `json:"creationBlocked,omitempty"` } @@ -212,5 +212,4 @@ type ManagedResourceSpec struct { // LastUpdateTime : the time the polivy violation was updated type PolicyViolationStatus struct { LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"` - //TODO: having user information regarding the owner of resource can be helpful } diff --git a/pkg/api/kyverno/v1/utils.go b/pkg/api/kyverno/v1/utils.go index a330645929..31c755061b 100644 --- a/pkg/api/kyverno/v1/utils.go +++ b/pkg/api/kyverno/v1/utils.go @@ -57,18 +57,5 @@ func (gen *Generation) DeepCopyInto(out *Generation) { //ToKey generates the key string used for adding label to polivy violation func (rs ResourceSpec) ToKey() string { - if rs.Namespace == "" { - return rs.Kind + "." + rs.Name - } - return rs.Kind + "." + rs.Namespace + "." + rs.Name -} - -//BuildKey builds the key -func BuildResourceKey(kind, namespace, name string) string { - resource := ResourceSpec{ - Kind: kind, - Namespace: namespace, - Name: name, - } - return resource.ToKey() + return rs.Kind + "." + rs.Name } diff --git a/pkg/namespace/controller.go b/pkg/namespace/controller.go index 4e4075ed74..d89ca6d19e 100644 --- a/pkg/namespace/controller.go +++ b/pkg/namespace/controller.go @@ -39,8 +39,6 @@ type NamespaceController struct { //nsLister provides expansion to the namespace lister to inject GVK for the resource nsLister NamespaceListerExpansion - // nLsister can list/get namespaces from the shared informer's store - // nsLister v1CoreLister.NamespaceLister // nsListerSynced returns true if the Namespace store has been synced at least once nsListerSynced cache.InformerSynced // pvLister can list/get policy violation from the shared informer's store diff --git a/pkg/policy/cleanup.go b/pkg/policy/cleanup.go index 21a090d1cb..c52cffb312 100644 --- a/pkg/policy/cleanup.go +++ b/pkg/policy/cleanup.go @@ -152,7 +152,7 @@ func getNamespacedPVs(nspvLister kyvernolister.NamespacedPolicyViolationLister, } func getNamespacedPVOnResource(nspvLister kyvernolister.NamespacedPolicyViolationLister, policyName, kind, namespace, name string) (kyverno.NamespacedPolicyViolation, error) { - nspvs, err := nspvLister.List(labels.Everything()) + nspvs, err := nspvLister.NamespacedPolicyViolations(namespace).List(labels.Everything()) if err != nil { glog.V(2).Infof("failed to list namespaced pv: %v", err) return kyverno.NamespacedPolicyViolation{}, fmt.Errorf("failed to list namespaced pv: %v", err) @@ -162,7 +162,6 @@ func getNamespacedPVOnResource(nspvLister kyvernolister.NamespacedPolicyViolatio // find a policy on same resource and policy combination if nspv.Spec.Policy == policyName && nspv.Spec.ResourceSpec.Kind == kind && - nspv.Spec.ResourceSpec.Namespace == namespace && nspv.Spec.ResourceSpec.Name == name { return *nspv, nil } @@ -185,7 +184,7 @@ func getNamespacedPVonOwnerRef(nspvLister kyvernolister.NamespacedPolicyViolatio // as we can have multiple top level owners to a resource // check if pv exists on each one for owner := range owners { - pv, err := getNamespacedPVOnResource(nspvLister, policyName, owner.Kind, owner.Namespace, owner.Name) + pv, err := getNamespacedPVOnResource(nspvLister, policyName, owner.Kind, namespace, owner.Name) if err != nil { glog.Errorf("error while fetching resource owners: %v", err) continue diff --git a/pkg/policy/controller.go b/pkg/policy/controller.go index 7f40735750..7df41fc2c8 100644 --- a/pkg/policy/controller.go +++ b/pkg/policy/controller.go @@ -485,7 +485,7 @@ func (pc *PolicyController) syncPolicy(key string) error { engineResponses := pc.processExistingResources(*p) // report errors pc.cleanupAndReport(engineResponses) - // fetch the policy again via the aggreagator to remain consistent + // sync active return pc.syncStatusOnly(p, pvList, nspvList) } diff --git a/pkg/policyviolation/clusterpv.go b/pkg/policyviolation/clusterpv.go new file mode 100644 index 0000000000..26d50c790a --- /dev/null +++ b/pkg/policyviolation/clusterpv.go @@ -0,0 +1,152 @@ +package policyviolation + +import ( + "fmt" + "reflect" + + "github.com/golang/glog" + kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" + kyvernov1 "github.com/nirmata/kyverno/pkg/client/clientset/versioned/typed/kyverno/v1" + kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1" + client "github.com/nirmata/kyverno/pkg/dclient" + labels "k8s.io/apimachinery/pkg/labels" +) + +func createPVS(dclient *client.Client, pvs []kyverno.ClusterPolicyViolation, pvLister kyvernolister.ClusterPolicyViolationLister, pvInterface kyvernov1.KyvernoV1Interface) error { + for _, pv := range pvs { + if err := createPVNew(dclient, pv, pvLister, pvInterface); err != nil { + return err + } + } + return nil +} + +func (gen *Generator) createCusterPV(info Info) error { + var pvs []kyverno.ClusterPolicyViolation + if !info.Blocked { + pvs = append(pvs, buildPV(info)) + } else { + // blocked + // get owners + pvs = buildPVWithOwners(gen.dclient, info) + } + // create policy violation + if err := createPVS(gen.dclient, pvs, gen.pvLister, gen.pvInterface); err != nil { + return err + } + + glog.V(3).Infof("Created cluster policy violation policy=%s, resource=%s/%s/%s", + info.PolicyName, info.Resource.GetKind(), info.Resource.GetNamespace(), info.Resource.GetName()) + return nil + +} +func createPVNew(dclient *client.Client, pv kyverno.ClusterPolicyViolation, pvLister kyvernolister.ClusterPolicyViolationLister, pvInterface kyvernov1.KyvernoV1Interface) error { + var err error + // PV already exists + ePV, err := getExistingPVIfAny(pvLister, pv) + if err != nil { + glog.Error(err) + return fmt.Errorf("failed to get existing pv on resource '%s': %v", pv.Spec.ResourceSpec.ToKey(), err) + } + if ePV == nil { + // Create a New PV + glog.V(4).Infof("creating new policy violation for policy %s & resource %s/%s", pv.Spec.Policy, pv.Spec.ResourceSpec.Kind, pv.Spec.ResourceSpec.Name) + err := retryGetResource(pv.Namespace, dclient, pv.Spec.ResourceSpec) + if err != nil { + return fmt.Errorf("failed to retry getting resource for policy violation %s/%s: %v", pv.Name, pv.Spec.Policy, err) + } + + _, err = pvInterface.ClusterPolicyViolations().Create(&pv) + if err != nil { + glog.Error(err) + return fmt.Errorf("failed to create cluster policy violation: %v", err) + } + glog.Infof("policy violation created for resource %v", pv.Spec.ResourceSpec) + return nil + } + // Update existing PV if there any changes + if reflect.DeepEqual(pv.Spec, ePV.Spec) { + glog.V(4).Infof("policy violation spec %v did not change so not updating it", pv.Spec) + return nil + } + + pv.SetName(ePV.Name) + _, err = pvInterface.ClusterPolicyViolations().Update(&pv) + if err != nil { + glog.Error(err) + return fmt.Errorf("failed to update cluster polciy violation: %v", err) + } + glog.Infof("policy violation updated for resource %v", pv.Spec.ResourceSpec) + return nil +} + +// build PV without owners +func buildPV(info Info) kyverno.ClusterPolicyViolation { + pv := buildPVObj(info.PolicyName, kyverno.ResourceSpec{ + Kind: info.Resource.GetKind(), + Name: info.Resource.GetName(), + }, info.Rules, + ) + return pv +} + +// build PV object +func buildPVObj(policyName string, resourceSpec kyverno.ResourceSpec, rules []kyverno.ViolatedRule) kyverno.ClusterPolicyViolation { + pv := kyverno.ClusterPolicyViolation{ + Spec: kyverno.PolicyViolationSpec{ + Policy: policyName, + ResourceSpec: resourceSpec, + ViolatedRules: rules, + }, + } + + labelMap := map[string]string{ + "policy": policyName, + "resource": resourceSpec.ToKey(), + } + pv.SetLabels(labelMap) + pv.SetGenerateName("pv-") + return pv +} + +// build PV with owners +func buildPVWithOwners(dclient *client.Client, info Info) []kyverno.ClusterPolicyViolation { + var pvs []kyverno.ClusterPolicyViolation + // as its blocked resource, the violation is created on owner + ownerMap := map[kyverno.ResourceSpec]interface{}{} + GetOwner(dclient, ownerMap, info.Resource) + + // standaloneresource, set pvResourceSpec with resource itself + if len(ownerMap) == 0 { + pvResourceSpec := kyverno.ResourceSpec{ + Kind: info.Resource.GetKind(), + Name: info.Resource.GetName(), + } + return append(pvs, buildPVObj(info.PolicyName, pvResourceSpec, info.Rules)) + } + + // Generate owner on all owners + for owner := range ownerMap { + pv := buildPVObj(info.PolicyName, owner, info.Rules) + pvs = append(pvs, pv) + } + return pvs +} + +func getExistingPVIfAny(pvLister kyvernolister.ClusterPolicyViolationLister, currpv kyverno.ClusterPolicyViolation) (*kyverno.ClusterPolicyViolation, error) { + pvs, err := pvLister.List(labels.Everything()) + if err != nil { + glog.Errorf("unable to list policy violations : %v", err) + return nil, err + } + + for _, pv := range pvs { + // find a policy on same resource and policy combination + if pv.Spec.Policy == currpv.Spec.Policy && + pv.Spec.ResourceSpec.Kind == currpv.Spec.ResourceSpec.Kind && + pv.Spec.ResourceSpec.Name == currpv.Spec.ResourceSpec.Name { + return pv, nil + } + } + return nil, nil +} diff --git a/pkg/policyviolation/controller.go b/pkg/policyviolation/controller.go index 869d13ba8c..6f6a58429c 100644 --- a/pkg/policyviolation/controller.go +++ b/pkg/policyviolation/controller.go @@ -210,13 +210,13 @@ func (pvc *PolicyViolationController) syncPolicyViolation(key string) error { // Deep-copy otherwise we are mutating our cache. // TODO: Deep-copy only when needed. pv := policyViolation.DeepCopy() - // TODO: Update Status to update ObserverdGeneration - // TODO: check if the policy violation refers to a resource thats active ? // done by policy controller - // TODO: remove the PV, if the corresponding policy is not present - // TODO: additional check on deleted webhook for a resource, to delete a policy violation it has a policy violation - // list the resource with label selectors, but this can be expensive for each delete request of a resource + // Check if the policy violation resource is active if err := pvc.syncActiveResource(pv); err != nil { - glog.V(4).Infof("not syncing policy violation status") + return err + } + // If policy violations is on resource owner, + // check if the resource owner is active + if err := pvc.syncBlockedResource(pv); err != nil { return err } @@ -227,30 +227,19 @@ func (pvc *PolicyViolationController) syncActiveResource(curPv *kyverno.ClusterP // check if the resource is active or not ? rspec := curPv.Spec.ResourceSpec // get resource - _, err := pvc.client.GetResource(rspec.Kind, rspec.Namespace, rspec.Name) + _, err := pvc.client.GetResource(rspec.Kind, "", rspec.Name) if errors.IsNotFound(err) { - // TODO: does it help to retry? - // resource is not found - // remove the violation - if err := pvc.pvControl.RemovePolicyViolation(curPv.Name); err != nil { glog.Infof("unable to delete the policy violation %s: %v", curPv.Name, err) return err } - glog.V(4).Infof("removing policy violation %s as the corresponding resource %s/%s/%s does not exist anymore", curPv.Name, rspec.Kind, rspec.Namespace, rspec.Name) + glog.V(4).Infof("removing policy violation %s as the corresponding resource %s/%s does not exist anymore", curPv.Name, rspec.Kind, rspec.Name) return nil } if err != nil { - glog.V(4).Infof("error while retrieved resource %s/%s/%s: %v", rspec.Kind, rspec.Namespace, rspec.Name, err) + glog.V(4).Infof("error while retrieved resource %s/%s: %v", rspec.Kind, rspec.Name, err) return err } - - // cleanup pv with dependant - if err := pvc.syncBlockedResource(curPv); err != nil { - return err - } - - //TODO- if the policy is not present, remove the policy violation return nil } @@ -264,7 +253,7 @@ func (pvc *PolicyViolationController) syncBlockedResource(curPv *kyverno.Cluster // get resource blockedResource := violatedRule.ManagedResource - resources, _ := pvc.client.ListResource(blockedResource.Kind, blockedResource.Namespace, nil) + resources, _ := pvc.client.ListResource(blockedResource.Kind, "", nil) for _, resource := range resources.Items { glog.V(4).Infof("getting owners for %s/%s/%s\n", resource.GetKind(), resource.GetNamespace(), resource.GetName()) @@ -286,8 +275,8 @@ func (pvc *PolicyViolationController) syncBlockedResource(curPv *kyverno.Cluster glog.Infof("unable to delete the policy violation %s: %v", curPv.Name, err) return err } - glog.V(4).Infof("removed policy violation %s as the blocked resource %s/%s successfully created, owner: %s", - curPv.Name, blockedResource.Kind, blockedResource.Namespace, strings.ReplaceAll(curPv.Spec.ResourceSpec.ToKey(), ".", "/")) + glog.V(4).Infof("removed policy violation %s as the blocked resource %s successfully created, owner: %s", + curPv.Name, blockedResource.Kind, strings.ReplaceAll(curPv.Spec.ResourceSpec.ToKey(), ".", "/")) } } } diff --git a/pkg/policyviolation/generator.go b/pkg/policyviolation/generator.go index cd9a715232..bb7b0c8744 100644 --- a/pkg/policyviolation/generator.go +++ b/pkg/policyviolation/generator.go @@ -1,7 +1,6 @@ package policyviolation import ( - "fmt" "reflect" "strconv" "strings" @@ -16,7 +15,6 @@ import ( client "github.com/nirmata/kyverno/pkg/dclient" dclient "github.com/nirmata/kyverno/pkg/dclient" unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/workqueue" @@ -201,195 +199,12 @@ func (gen *Generator) processNextWorkitem() bool { func (gen *Generator) syncHandler(info Info) error { glog.V(4).Infof("recieved info:%v", info) - // cluster policy violations + + // cluster scope resource generate a clusterpolicy violation + // namespaced resources generated a namespaced policy violation in the namespace of the resource if info.Resource.GetNamespace() == "" { - var pvs []kyverno.ClusterPolicyViolation - if !info.Blocked { - pvs = append(pvs, buildPV(info)) - } else { - // blocked - // get owners - pvs = buildPVWithOwners(gen.dclient, info) - } - // create policy violation - if err := createPVS(gen.dclient, pvs, gen.pvLister, gen.pvInterface); err != nil { - return err - } - - glog.V(3).Infof("Created cluster policy violation policy=%s, resource=%s/%s/%s", - info.PolicyName, info.Resource.GetKind(), info.Resource.GetNamespace(), info.Resource.GetName()) - return nil + return gen.createCusterPV(info) } + return gen.createNamespacedPV(info) - // namespaced policy violations - var pvs []kyverno.NamespacedPolicyViolation - if !info.Blocked { - pvs = append(pvs, buildNamespacedPV(info)) - } else { - pvs = buildNamespacedPVWithOwner(gen.dclient, info) - } - - if err := createNamespacedPV(gen.dclient, gen.nspvLister, gen.pvInterface, pvs); err != nil { - return err - } - - glog.V(3).Infof("Created namespaced policy violation policy=%s, resource=%s/%s/%s", - info.PolicyName, info.Resource.GetKind(), info.Resource.GetNamespace(), info.Resource.GetName()) - return nil -} - -func createPVS(dclient *client.Client, pvs []kyverno.ClusterPolicyViolation, pvLister kyvernolister.ClusterPolicyViolationLister, pvInterface kyvernov1.KyvernoV1Interface) error { - for _, pv := range pvs { - if err := createPVNew(dclient, pv, pvLister, pvInterface); err != nil { - return err - } - } - return nil -} - -func createPVNew(dclient *client.Client, pv kyverno.ClusterPolicyViolation, pvLister kyvernolister.ClusterPolicyViolationLister, pvInterface kyvernov1.KyvernoV1Interface) error { - var err error - // PV already exists - ePV, err := getExistingPVIfAny(pvLister, pv) - if err != nil { - glog.Error(err) - return fmt.Errorf("failed to get existing pv on resource '%s': %v", pv.Spec.ResourceSpec.ToKey(), err) - } - if ePV == nil { - // Create a New PV - glog.V(4).Infof("creating new policy violation for policy %s & resource %s/%s/%s", pv.Spec.Policy, pv.Spec.ResourceSpec.Kind, pv.Spec.ResourceSpec.Namespace, pv.Spec.ResourceSpec.Name) - err := retryGetResource(dclient, pv.Spec.ResourceSpec) - if err != nil { - return fmt.Errorf("failed to retry getting resource for policy violation %s/%s: %v", pv.Name, pv.Spec.Policy, err) - } - - _, err = pvInterface.ClusterPolicyViolations().Create(&pv) - if err != nil { - glog.Error(err) - return fmt.Errorf("failed to create cluster policy violation: %v", err) - } - glog.Infof("policy violation created for resource %v", pv.Spec.ResourceSpec) - return nil - } - // Update existing PV if there any changes - if reflect.DeepEqual(pv.Spec, ePV.Spec) { - glog.V(4).Infof("policy violation spec %v did not change so not updating it", pv.Spec) - return nil - } - - pv.SetName(ePV.Name) - _, err = pvInterface.ClusterPolicyViolations().Update(&pv) - if err != nil { - glog.Error(err) - return fmt.Errorf("failed to update cluster polciy violation: %v", err) - } - glog.Infof("policy violation updated for resource %v", pv.Spec.ResourceSpec) - return nil -} - -func getExistingPVIfAny(pvLister kyvernolister.ClusterPolicyViolationLister, currpv kyverno.ClusterPolicyViolation) (*kyverno.ClusterPolicyViolation, error) { - pvs, err := pvLister.List(labels.Everything()) - if err != nil { - glog.Errorf("unable to list policy violations : %v", err) - return nil, err - } - - for _, pv := range pvs { - // find a policy on same resource and policy combination - if pv.Spec.Policy == currpv.Spec.Policy && - pv.Spec.ResourceSpec.Kind == currpv.Spec.ResourceSpec.Kind && - pv.Spec.ResourceSpec.Namespace == currpv.Spec.ResourceSpec.Namespace && - pv.Spec.ResourceSpec.Name == currpv.Spec.ResourceSpec.Name { - return pv, nil - } - } - return nil, nil -} - -// build PV without owners -func buildPV(info Info) kyverno.ClusterPolicyViolation { - pv := buildPVObj(info.PolicyName, kyverno.ResourceSpec{ - Kind: info.Resource.GetKind(), - Namespace: info.Resource.GetNamespace(), - Name: info.Resource.GetName(), - }, info.Rules, - ) - return pv -} - -// build PV object -func buildPVObj(policyName string, resourceSpec kyverno.ResourceSpec, rules []kyverno.ViolatedRule) kyverno.ClusterPolicyViolation { - pv := kyverno.ClusterPolicyViolation{ - Spec: kyverno.PolicyViolationSpec{ - Policy: policyName, - ResourceSpec: resourceSpec, - ViolatedRules: rules, - }, - } - - labelMap := map[string]string{ - "policy": policyName, - "resource": resourceSpec.ToKey(), - } - pv.SetLabels(labelMap) - pv.SetGenerateName("pv-") - return pv -} - -// build PV with owners -func buildPVWithOwners(dclient *client.Client, info Info) []kyverno.ClusterPolicyViolation { - var pvs []kyverno.ClusterPolicyViolation - // as its blocked resource, the violation is created on owner - ownerMap := map[kyverno.ResourceSpec]interface{}{} - GetOwner(dclient, ownerMap, info.Resource) - - // standaloneresource, set pvResourceSpec with resource itself - if len(ownerMap) == 0 { - pvResourceSpec := kyverno.ResourceSpec{ - Namespace: info.Resource.GetNamespace(), - Kind: info.Resource.GetKind(), - Name: info.Resource.GetName(), - } - return append(pvs, buildPVObj(info.PolicyName, pvResourceSpec, info.Rules)) - } - - // Generate owner on all owners - for owner := range ownerMap { - pv := buildPVObj(info.PolicyName, owner, info.Rules) - pvs = append(pvs, pv) - } - return pvs -} - -// GetOwner of a resource by iterating over ownerReferences -func GetOwner(dclient *client.Client, ownerMap map[kyverno.ResourceSpec]interface{}, resource unstructured.Unstructured) { - var emptyInterface interface{} - resourceSpec := kyverno.ResourceSpec{ - Kind: resource.GetKind(), - Namespace: resource.GetNamespace(), - Name: resource.GetName(), - } - if _, ok := ownerMap[resourceSpec]; ok { - // owner seen before - // breaking loop - return - } - rOwners := resource.GetOwnerReferences() - // if there are no resource owners then its top level resource - if len(rOwners) == 0 { - // add resource to map - ownerMap[resourceSpec] = emptyInterface - return - } - for _, rOwner := range rOwners { - // lookup resource via client - // owner has to be in same namespace - owner, err := dclient.GetResource(rOwner.Kind, resource.GetNamespace(), rOwner.Name) - if err != nil { - glog.Errorf("Failed to get resource owner for %s/%s/%s, err: %v", rOwner.Kind, resource.GetNamespace(), rOwner.Name, err) - // as we want to process other owners - continue - } - GetOwner(dclient, ownerMap, *owner) - } } diff --git a/pkg/policyviolation/helpers.go b/pkg/policyviolation/helpers.go index c771fe2c4f..19004ff692 100644 --- a/pkg/policyviolation/helpers.go +++ b/pkg/policyviolation/helpers.go @@ -7,6 +7,7 @@ import ( "github.com/golang/glog" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" + client "github.com/nirmata/kyverno/pkg/dclient" v1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -30,21 +31,6 @@ func converLabelToSelector(labelMap map[string]string) (labels.Selector, error) return policyViolationSelector, nil } -func containsOwner(owners []kyverno.ResourceSpec, pvResourceSpec kyverno.ResourceSpec) bool { - curOwner := kyverno.ResourceSpec{ - Kind: pvResourceSpec.Kind, - Namespace: pvResourceSpec.Namespace, - Name: pvResourceSpec.Name, - } - - for _, targetOwner := range owners { - if reflect.DeepEqual(curOwner, targetOwner) { - return true - } - } - return false -} - // validDependantForDeployment checks if resource (pod) matches the intent of the given deployment // explicitly handles deployment-replicaset-pod relationship func validDependantForDeployment(client appsv1.AppsV1Interface, pvResourceSpec kyverno.ResourceSpec, resource unstructured.Unstructured) bool { @@ -58,15 +44,14 @@ func validDependantForDeployment(client appsv1.AppsV1Interface, pvResourceSpec k } owner := kyverno.ResourceSpec{ - Kind: pvResourceSpec.Kind, - Namespace: pvResourceSpec.Namespace, - Name: pvResourceSpec.Name, + Kind: pvResourceSpec.Kind, + Name: pvResourceSpec.Name, } start := time.Now() - deploy, err := client.Deployments(owner.Namespace).Get(owner.Name, metav1.GetOptions{}) + deploy, err := client.Deployments(resource.GetNamespace()).Get(owner.Name, metav1.GetOptions{}) if err != nil { - glog.Errorf("failed to get resourceOwner deployment %s/%s/%s: %v", owner.Kind, owner.Namespace, owner.Name, err) + glog.Errorf("failed to get resourceOwner deployment %s/%s/%s: %v", owner.Kind, resource.GetNamespace(), owner.Name, err) return false } glog.V(4).Infof("Time getting deployment %v", time.Since(start)) @@ -74,12 +59,12 @@ func validDependantForDeployment(client appsv1.AppsV1Interface, pvResourceSpec k // TODO(shuting): replace typed client AppsV1Interface expectReplicaset, err := deployutil.GetNewReplicaSet(deploy, client) if err != nil { - glog.Errorf("failed to get replicaset owned by %s/%s/%s: %v", owner.Kind, owner.Namespace, owner.Name, err) + glog.Errorf("failed to get replicaset owned by %s/%s/%s: %v", owner.Kind, resource.GetNamespace(), owner.Name, err) return false } if reflect.DeepEqual(expectReplicaset, v1.ReplicaSet{}) { - glog.V(2).Infof("no replicaset found for deploy %s/%s/%s", owner.Namespace, owner.Kind, owner.Name) + glog.V(2).Infof("no replicaset found for deploy %s/%s/%s", resource.GetNamespace(), owner.Kind, owner.Name) return false } var actualReplicaset *v1.ReplicaSet @@ -105,3 +90,35 @@ func validDependantForDeployment(client appsv1.AppsV1Interface, pvResourceSpec k } return false } + +// GetOwner of a resource by iterating over ownerReferences +func GetOwner(dclient *client.Client, ownerMap map[kyverno.ResourceSpec]interface{}, resource unstructured.Unstructured) { + var emptyInterface interface{} + resourceSpec := kyverno.ResourceSpec{ + Kind: resource.GetKind(), + Name: resource.GetName(), + } + if _, ok := ownerMap[resourceSpec]; ok { + // owner seen before + // breaking loop + return + } + rOwners := resource.GetOwnerReferences() + // if there are no resource owners then its top level resource + if len(rOwners) == 0 { + // add resource to map + ownerMap[resourceSpec] = emptyInterface + return + } + for _, rOwner := range rOwners { + // lookup resource via client + // owner has to be in same namespace + owner, err := dclient.GetResource(rOwner.Kind, resource.GetNamespace(), rOwner.Name) + if err != nil { + glog.Errorf("Failed to get resource owner for %s/%s/%s, err: %v", rOwner.Kind, resource.GetNamespace(), rOwner.Name, err) + // as we want to process other owners + continue + } + GetOwner(dclient, ownerMap, *owner) + } +} diff --git a/pkg/policyviolation/namespacedpv.go b/pkg/policyviolation/namespacedpv.go index 2e270a9ad2..a2f9ef0c40 100644 --- a/pkg/policyviolation/namespacedpv.go +++ b/pkg/policyviolation/namespacedpv.go @@ -12,12 +12,29 @@ import ( labels "k8s.io/apimachinery/pkg/labels" ) +func (gen *Generator) createNamespacedPV(info Info) error { + // namespaced policy violations + var pvs []kyverno.NamespacedPolicyViolation + if !info.Blocked { + pvs = append(pvs, buildNamespacedPV(info)) + } else { + pvs = buildNamespacedPVWithOwner(gen.dclient, info) + } + + if err := createNamespacedPV(info.Resource.GetNamespace(), gen.dclient, gen.nspvLister, gen.pvInterface, pvs); err != nil { + return err + } + + glog.V(3).Infof("Created namespaced policy violation policy=%s, resource=%s/%s/%s", + info.PolicyName, info.Resource.GetKind(), info.Resource.GetNamespace(), info.Resource.GetName()) + return nil +} + func buildNamespacedPV(info Info) kyverno.NamespacedPolicyViolation { return buildNamespacedPVObj(info.PolicyName, kyverno.ResourceSpec{ - Kind: info.Resource.GetKind(), - Namespace: info.Resource.GetNamespace(), - Name: info.Resource.GetName(), + Kind: info.Resource.GetKind(), + Name: info.Resource.GetName(), }, info.Rules) } @@ -49,9 +66,8 @@ func buildNamespacedPVWithOwner(dclient *dclient.Client, info Info) (pvs []kyver // standaloneresource, set pvResourceSpec with resource itself if len(ownerMap) == 0 { pvResourceSpec := kyverno.ResourceSpec{ - Namespace: info.Resource.GetNamespace(), - Kind: info.Resource.GetKind(), - Name: info.Resource.GetName(), + Kind: info.Resource.GetKind(), + Name: info.Resource.GetName(), } return append(pvs, buildNamespacedPVObj(info.PolicyName, pvResourceSpec, info.Rules)) } @@ -62,7 +78,7 @@ func buildNamespacedPVWithOwner(dclient *dclient.Client, info Info) (pvs []kyver return } -func createNamespacedPV(dclient *dclient.Client, pvLister kyvernolister.NamespacedPolicyViolationLister, pvInterface kyvernov1.KyvernoV1Interface, pvs []kyverno.NamespacedPolicyViolation) error { +func createNamespacedPV(namespace string, dclient *dclient.Client, pvLister kyvernolister.NamespacedPolicyViolationLister, pvInterface kyvernov1.KyvernoV1Interface, pvs []kyverno.NamespacedPolicyViolation) error { for _, newPv := range pvs { glog.V(4).Infof("creating namespaced policyViolation resource for policy %s and resource %s", newPv.Spec.Policy, newPv.Spec.ResourceSpec.ToKey()) // check if there was a previous policy voilation for policy & resource combination @@ -76,11 +92,11 @@ func createNamespacedPV(dclient *dclient.Client, pvLister kyvernolister.Namespac if reflect.DeepEqual(curPv, kyverno.NamespacedPolicyViolation{}) { glog.V(4).Infof("creating new namespaced policy violation for policy %s & resource %s", newPv.Spec.Policy, newPv.Spec.ResourceSpec.ToKey()) - if err := retryGetResource(dclient, newPv.Spec.ResourceSpec); err != nil { + if err := retryGetResource(newPv.Namespace, dclient, newPv.Spec.ResourceSpec); err != nil { return fmt.Errorf("failed to get resource for policy violation on resource '%s': %v", newPv.Spec.ResourceSpec.ToKey(), err) } - if _, err := pvInterface.NamespacedPolicyViolations(newPv.Spec.ResourceSpec.Namespace).Create(&newPv); err != nil { + if _, err := pvInterface.NamespacedPolicyViolations(namespace).Create(&newPv); err != nil { return fmt.Errorf("failed to create namespaced policy violation: %v", err) } @@ -104,7 +120,7 @@ func createNamespacedPV(dclient *dclient.Client, pvLister kyvernolister.Namespac glog.V(4).Infof("creating new policy violation for policy %s & resource %s", curPv.Spec.Policy, curPv.Spec.ResourceSpec.ToKey()) //TODO: using a generic name, but would it be helpful to have naming convention for policy violations // as we can only have one policy violation for each (policy + resource) combination - if _, err = pvInterface.NamespacedPolicyViolations(newPv.Spec.ResourceSpec.Namespace).Update(&newPv); err != nil { + if _, err = pvInterface.NamespacedPolicyViolations(namespace).Update(&newPv); err != nil { return fmt.Errorf("failed to update namespaced policy violation: %v", err) } glog.Infof("namespaced policy violation updated for resource %s", newPv.Spec.ResourceSpec.ToKey()) diff --git a/pkg/policyviolation/namespacepvcontroller.go b/pkg/policyviolation/namespacepvcontroller.go index 59e1455807..96d0257a32 100644 --- a/pkg/policyviolation/namespacepvcontroller.go +++ b/pkg/policyviolation/namespacepvcontroller.go @@ -226,7 +226,7 @@ func (pvc *NamespacedPolicyViolationController) syncActiveResource(curPv *kyvern // check if the resource is active or not ? rspec := curPv.Spec.ResourceSpec // get resource - _, err := pvc.client.GetResource(rspec.Kind, rspec.Namespace, rspec.Name) + _, err := pvc.client.GetResource(rspec.Kind, curPv.Namespace, rspec.Name) if errors.IsNotFound(err) { // TODO: does it help to retry? // resource is not found @@ -236,11 +236,11 @@ func (pvc *NamespacedPolicyViolationController) syncActiveResource(curPv *kyvern glog.Infof("unable to delete the policy violation %s: %v", curPv.Name, err) return err } - glog.V(4).Infof("removing policy violation %s as the corresponding resource %s/%s/%s does not exist anymore", curPv.Name, rspec.Kind, rspec.Namespace, rspec.Name) + glog.V(4).Infof("removing policy violation %s as the corresponding resource %s/%s/%s does not exist anymore", curPv.Name, rspec.Kind, curPv.Namespace, rspec.Name) return nil } if err != nil { - glog.V(4).Infof("error while retrieved resource %s/%s/%s: %v", rspec.Kind, rspec.Namespace, rspec.Name, err) + glog.V(4).Infof("error while retrieved resource %s/%s/%s: %v", rspec.Kind, curPv.Namespace, rspec.Name, err) return err } @@ -263,7 +263,7 @@ func (pvc *NamespacedPolicyViolationController) syncBlockedResource(curPv *kyver // get resource blockedResource := violatedRule.ManagedResource - resources, _ := pvc.client.ListResource(blockedResource.Kind, blockedResource.Namespace, nil) + resources, _ := pvc.client.ListResource(blockedResource.Kind, curPv.Namespace, nil) for _, resource := range resources.Items { glog.V(4).Infof("getting owners for %s/%s/%s\n", resource.GetKind(), resource.GetNamespace(), resource.GetName()) @@ -285,7 +285,7 @@ func (pvc *NamespacedPolicyViolationController) syncBlockedResource(curPv *kyver return err } glog.V(4).Infof("removed policy violation %s as the blocked resource %s/%s successfully created, owner: %s", - curPv.Name, blockedResource.Kind, blockedResource.Namespace, strings.ReplaceAll(curPv.Spec.ResourceSpec.ToKey(), ".", "/")) + curPv.Name, blockedResource.Kind, curPv.Namespace, strings.ReplaceAll(curPv.Spec.ResourceSpec.ToKey(), ".", "/")) } } } @@ -341,11 +341,11 @@ func (r RealNamespacedPVControl) RemovePolicyViolation(ns, name string) error { return r.Client.KyvernoV1().NamespacedPolicyViolations(ns).Delete(name, &metav1.DeleteOptions{}) } -func retryGetResource(client *client.Client, rspec kyverno.ResourceSpec) error { +func retryGetResource(namespace string, client *client.Client, rspec kyverno.ResourceSpec) error { var i int getResource := func() error { - _, err := client.GetResource(rspec.Kind, rspec.Namespace, rspec.Name) - glog.V(5).Infof("retry %v getting %s/%s/%s", i, rspec.Kind, rspec.Namespace, rspec.Name) + _, err := client.GetResource(rspec.Kind, namespace, rspec.Name) + glog.V(5).Infof("retry %v getting %s/%s/%s", i, rspec.Kind, namespace, rspec.Name) i++ return err } diff --git a/pkg/testrunner/scenario.go b/pkg/testrunner/scenario.go index 5d23fdaf57..752989bef9 100644 --- a/pkg/testrunner/scenario.go +++ b/pkg/testrunner/scenario.go @@ -181,7 +181,8 @@ func runTestCase(t *testing.T, tc scaseT) bool { er = engine.Generate(policyContext) t.Log(("---Generation---")) validateResponse(t, er.PolicyResponse, tc.Expected.Generation.PolicyResponse) - validateGeneratedResources(t, client, *policy, tc.Expected.Generation.GeneratedResources) + // Expected generate resource will be in same namesapces as resource + validateGeneratedResources(t, client, *policy, resource.GetName(), tc.Expected.Generation.GeneratedResources) } } return true @@ -191,12 +192,12 @@ func createNamespace(client *client.Client, ns *unstructured.Unstructured) error _, err := client.CreateResource("Namespace", "", ns, false) return err } -func validateGeneratedResources(t *testing.T, client *client.Client, policy kyverno.ClusterPolicy, expected []kyverno.ResourceSpec) { +func validateGeneratedResources(t *testing.T, client *client.Client, policy kyverno.ClusterPolicy, namespace string, expected []kyverno.ResourceSpec) { t.Log("--validate if resources are generated---") // list of expected generated resources for _, resource := range expected { - if _, err := client.GetResource(resource.Kind, resource.Namespace, resource.Name); err != nil { - t.Errorf("generated resource %s/%s/%s not found. %v", resource.Kind, resource.Namespace, resource.Name, err) + if _, err := client.GetResource(resource.Kind, namespace, resource.Name); err != nil { + t.Errorf("generated resource %s/%s/%s not found. %v", resource.Kind, namespace, resource.Name, err) } } } diff --git a/pkg/webhooks/report.go b/pkg/webhooks/report.go index ede22a20f5..516f164700 100644 --- a/pkg/webhooks/report.go +++ b/pkg/webhooks/report.go @@ -128,7 +128,6 @@ func buildViolatedRules(er engine.EngineResponse, blocked bool) []kyverno.Violat // if resource was blocked we create dependent dependant := kyverno.ManagedResourceSpec{ Kind: er.PolicyResponse.Resource.Kind, - Namespace: er.PolicyResponse.Resource.Namespace, CreationBlocked: true, }