diff --git a/main.go b/main.go index 9e914f0d93..a83c100120 100644 --- a/main.go +++ b/main.go @@ -3,11 +3,10 @@ package main import ( "flag" "time" - + "github.com/golang/glog" kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned" kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions" - clusterpv "github.com/nirmata/kyverno/pkg/clusterpolicyviolation" "github.com/nirmata/kyverno/pkg/config" client "github.com/nirmata/kyverno/pkg/dclient" event "github.com/nirmata/kyverno/pkg/event" @@ -123,7 +122,7 @@ func main() { // POLICY VIOLATION CONTROLLER // policy violation cleanup if the corresponding resource is deleted // status: lastUpdatTime - pvc, err := clusterpv.NewPolicyViolationController(client, pclient, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations()) + pvc, err := policyviolation.NewPolicyViolationController(client, pclient, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations()) if err != nil { glog.Fatalf("error creating policy violation controller: %v\n", err) } @@ -153,7 +152,7 @@ func main() { // -- annotations on resources with update details on mutation JSON patches // -- generate policy violation resource // -- generate events on policy and resource - server, err := webhooks.NewWebhookServer(pclient, client, tlsPair, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations(), egen, webhookRegistrationClient, pc.GetPolicyStatusAggregator(), configData, policyMetaStore, pvgen, cleanUp) + server, err := webhooks.NewWebhookServer(pclient, client, tlsPair, pInformer.Kyverno().V1alpha1().ClusterPolicies(), pInformer.Kyverno().V1alpha1().ClusterPolicyViolations(), pInformer.Kyverno().V1alpha1().NamespacedPolicyViolations(), egen, webhookRegistrationClient, pc.GetPolicyStatusAggregator(), configData, policyMetaStore, pvgen, cleanUp) if err != nil { glog.Fatalf("Unable to create webhook server: %v\n", err) } diff --git a/pkg/namespace/report.go b/pkg/namespace/report.go index 36c88760ff..90975f3791 100644 --- a/pkg/namespace/report.go +++ b/pkg/namespace/report.go @@ -10,7 +10,6 @@ import ( "github.com/nirmata/kyverno/pkg/policyviolation" ) - func (nsc *NamespaceController) report(engineResponses []engine.EngineResponse) { // generate events eventInfos := generateEvents(engineResponses) diff --git a/pkg/policy/cleanup.go b/pkg/policy/cleanup.go index d7ac11bf24..7a445bbad7 100644 --- a/pkg/policy/cleanup.go +++ b/pkg/policy/cleanup.go @@ -9,7 +9,7 @@ import ( kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1" dclient "github.com/nirmata/kyverno/pkg/dclient" "github.com/nirmata/kyverno/pkg/engine" - clusterpv "github.com/nirmata/kyverno/pkg/clusterpolicyviolation" + "github.com/nirmata/kyverno/pkg/policyviolation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" ) @@ -69,7 +69,7 @@ func getPVonOwnerRef(pvLister kyvernolister.ClusterPolicyViolationLister, dclien } // get owners // getOwners returns nil if there is any error - owners := clusterpv.GetOwners(dclient, *resource) + owners := policyviolation.GetOwners(dclient, *resource) // as we can have multiple top level owners to a resource // check if pv exists on each one // does not check for cycles diff --git a/pkg/clusterpolicyviolation/helpers.go b/pkg/policyviolation/clusterpv.go similarity index 98% rename from pkg/clusterpolicyviolation/helpers.go rename to pkg/policyviolation/clusterpv.go index 903ffcabcc..23089c885e 100644 --- a/pkg/clusterpolicyviolation/helpers.go +++ b/pkg/policyviolation/clusterpv.go @@ -1,4 +1,4 @@ -package clusterpolicyviolation +package policyviolation import ( "fmt" @@ -82,7 +82,8 @@ func createClusterPV(pvLister kyvernolister.ClusterPolicyViolationLister, client // check if there was a previous policy voilation for policy & resource combination curPv, err := getExistingPolicyViolationIfAny(nil, pvLister, newPv) if err != nil { - glog.Error(err) + // TODO(shuting): remove + // glog.Error(err) continue } if curPv == nil { @@ -180,7 +181,7 @@ func getExistingPolicyViolationIfAny(pvListerSynced cache.InformerSynced, pvList } //TODO: ideally there should be only one policy violation returned if len(pvs) > 1 { - glog.Errorf("more than one policy violation exists with labels %v", labelMap) + glog.V(4).Infof("more than one policy violation exists with labels %v", labelMap) return nil, fmt.Errorf("more than one policy violation exists with labels %v", labelMap) } diff --git a/pkg/clusterpolicyviolation/controller.go b/pkg/policyviolation/controller.go similarity index 99% rename from pkg/clusterpolicyviolation/controller.go rename to pkg/policyviolation/controller.go index 5945a15777..b10062da3d 100644 --- a/pkg/clusterpolicyviolation/controller.go +++ b/pkg/policyviolation/controller.go @@ -1,4 +1,4 @@ -package clusterpolicyviolation +package policyviolation import ( "fmt" diff --git a/pkg/policyviolation/namespacedpv.go b/pkg/policyviolation/namespacedpv.go new file mode 100644 index 0000000000..5370b6157c --- /dev/null +++ b/pkg/policyviolation/namespacedpv.go @@ -0,0 +1,121 @@ +package policyviolation + +import ( + "fmt" + "reflect" + + "github.com/golang/glog" + kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" + kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned" + kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1alpha1" + engine "github.com/nirmata/kyverno/pkg/engine" + labels "k8s.io/apimachinery/pkg/labels" +) + +func CreateNamespacePV(pvLister kyvernolister.NamespacedPolicyViolationLister, client *kyvernoclient.Clientset, engineResponses []engine.EngineResponse) { + var pvs []kyverno.NamespacedPolicyViolation + for _, er := range engineResponses { + // ignore creation of PV for resoruces that are yet to be assigned a name + if er.PolicyResponse.Resource.Name == "" { + glog.V(4).Infof("resource %v, has not been assigned a name, not creating a namespace policy violation for it", er.PolicyResponse.Resource) + continue + } + + if !er.IsSuccesful() { + glog.V(4).Infof("Building namespace policy violation for engine response %v", er) + if pv := buildNamespacedPVForPolicy(er); !reflect.DeepEqual(pv, kyverno.NamespacedPolicyViolation{}) { + pvs = append(pvs, pv) + } + } + } + + createNamespacedPV(pvLister, client, pvs) +} + +func buildNamespacedPVForPolicy(er engine.EngineResponse) kyverno.NamespacedPolicyViolation { + pvResourceSpec := kyverno.ResourceSpec{ + Kind: er.PolicyResponse.Resource.Kind, + Namespace: er.PolicyResponse.Resource.Namespace, + Name: er.PolicyResponse.Resource.Name, + } + + violatedRules := newViolatedRules(er, "") + return buildNamespacedPolicyViolation(er.PolicyResponse.Policy, pvResourceSpec, violatedRules) +} + +//buildNamespacedPolicyViolation returns an value of type PolicyViolation +func buildNamespacedPolicyViolation(policy string, resource kyverno.ResourceSpec, fRules []kyverno.ViolatedRule) kyverno.NamespacedPolicyViolation { + pv := kyverno.NamespacedPolicyViolation{ + Spec: kyverno.PolicyViolationSpec{ + Policy: policy, + ResourceSpec: resource, + ViolatedRules: fRules, + }, + } + //TODO: check if this can be removed or use unstructured? + + // pv.SetGroupVersionKind(kyverno.SchemeGroupVersion.WithKind("NamespacedPolicyViolation")) + pv.SetGenerateName("pv-") + return pv +} + +func createNamespacedPV(pvLister kyvernolister.NamespacedPolicyViolationLister, client *kyvernoclient.Clientset, pvs []kyverno.NamespacedPolicyViolation) { + if len(pvs) == 0 { + return + } + + 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 + curPv, err := getExistingNamespacedPVIfAny(pvLister, newPv) + if err != nil { + glog.Error(err) + continue + } + + if curPv == nil { + glog.V(4).Infof("creating new namespaced policy violation for policy %s & resource %s", newPv.Spec.Policy, newPv.Spec.ResourceSpec.ToKey()) + // no existing policy violation, create a new one + _, err := client.KyvernoV1alpha1().NamespacedPolicyViolations(newPv.Spec.ResourceSpec.Namespace).Create(&newPv) + if err != nil { + glog.Error(err) + } else { + glog.Infof("namespaced policy violation created for resource %s", newPv.Spec.ResourceSpec.ToKey()) + } + continue + } + // compare the policyviolation spec for existing resource if present else + if reflect.DeepEqual(curPv.Spec, newPv.Spec) { + // if they are equal there has been no change so dont update the polivy violation + glog.V(3).Infof("namespaced policy violation '%s' spec did not change so not updating it", newPv.Spec.ToKey()) + glog.V(4).Infof("namespaced policy violation spec %v did not change so not updating it", newPv.Spec) + continue + } + // spec changed so update the policyviolation + 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 + _, err = client.KyvernoV1alpha1().NamespacedPolicyViolations(newPv.Spec.ResourceSpec.Namespace).Update(&newPv) + if err != nil { + glog.Error(err) + continue + } + glog.Infof("namespaced policy violation updated for resource %s", newPv.Spec.ResourceSpec.ToKey()) + } +} + +func getExistingNamespacedPVIfAny(nspvLister kyvernolister.NamespacedPolicyViolationLister, newPv kyverno.NamespacedPolicyViolation) (*kyverno.NamespacedPolicyViolation, error) { + // TODO(shuting): list pvs by labels + pvs, err := nspvLister.List(labels.NewSelector()) + if err != nil { + return nil, fmt.Errorf("failed to list namespaced policy violations err: %v", err) + } + + for _, pv := range pvs { + if pv.Spec.Policy == newPv.Spec.Policy && reflect.DeepEqual(pv.Spec.ResourceSpec, newPv.Spec.ResourceSpec) { + return pv, nil + } + } + + return nil, nil +} diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index 638aec93fe..0acf9cf92a 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -36,8 +36,10 @@ type WebhookServer struct { kyvernoClient *kyvernoclient.Clientset pLister kyvernolister.ClusterPolicyLister pvLister kyvernolister.ClusterPolicyViolationLister + namespacepvLister kyvernolister.NamespacedPolicyViolationLister pListerSynced cache.InformerSynced pvListerSynced cache.InformerSynced + namespacepvListerSynced cache.InformerSynced eventGen event.Interface webhookRegistrationClient *webhookconfig.WebhookRegistrationClient // API to send policy stats for aggregation @@ -62,6 +64,7 @@ func NewWebhookServer( tlsPair *tlsutils.TlsPemPair, pInformer kyvernoinformer.ClusterPolicyInformer, pvInformer kyvernoinformer.ClusterPolicyViolationInformer, + namespacepvInformer kyvernoinformer.NamespacedPolicyViolationInformer, eventGen event.Interface, webhookRegistrationClient *webhookconfig.WebhookRegistrationClient, policyStatus policy.PolicyStatusInterface, @@ -87,8 +90,10 @@ func NewWebhookServer( kyvernoClient: kyvernoClient, pLister: pInformer.Lister(), pvLister: pvInformer.Lister(), + namespacepvLister: namespacepvInformer.Lister(), pListerSynced: pvInformer.Informer().HasSynced, pvListerSynced: pInformer.Informer().HasSynced, + namespacepvListerSynced: namespacepvInformer.Informer().HasSynced, eventGen: eventGen, webhookRegistrationClient: webhookRegistrationClient, policyStatus: policyStatus,