2019-05-13 18:17:28 -07:00
|
|
|
package controller
|
2019-05-10 00:05:21 -07:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2019-06-28 17:22:00 -07:00
|
|
|
"reflect"
|
2019-05-10 00:05:21 -07:00
|
|
|
"time"
|
|
|
|
|
2019-07-17 17:53:13 -07:00
|
|
|
jsonpatch "github.com/evanphx/json-patch"
|
|
|
|
|
|
|
|
"github.com/nirmata/kyverno/pkg/annotations"
|
2019-06-26 12:19:11 -07:00
|
|
|
"github.com/nirmata/kyverno/pkg/info"
|
|
|
|
|
2019-06-25 23:58:28 -07:00
|
|
|
"github.com/nirmata/kyverno/pkg/engine"
|
|
|
|
|
2019-05-31 17:59:36 -07:00
|
|
|
"github.com/golang/glog"
|
2019-07-15 14:49:22 -07:00
|
|
|
v1alpha1 "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
|
2019-05-21 11:00:09 -07:00
|
|
|
lister "github.com/nirmata/kyverno/pkg/client/listers/policy/v1alpha1"
|
2019-05-29 14:12:09 -07:00
|
|
|
client "github.com/nirmata/kyverno/pkg/dclient"
|
2019-06-05 13:43:07 +03:00
|
|
|
"github.com/nirmata/kyverno/pkg/event"
|
2019-05-21 11:00:09 -07:00
|
|
|
"github.com/nirmata/kyverno/pkg/sharedinformer"
|
|
|
|
violation "github.com/nirmata/kyverno/pkg/violation"
|
2019-05-10 00:05:21 -07:00
|
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
|
|
"k8s.io/client-go/tools/cache"
|
|
|
|
"k8s.io/client-go/util/workqueue"
|
|
|
|
)
|
|
|
|
|
2019-05-10 10:38:38 -07:00
|
|
|
//PolicyController to manage Policy CRD
|
2019-05-10 00:05:21 -07:00
|
|
|
type PolicyController struct {
|
2019-07-17 17:53:13 -07:00
|
|
|
client *client.Client
|
|
|
|
policyLister lister.PolicyLister
|
|
|
|
policySynced cache.InformerSynced
|
|
|
|
violationBuilder violation.Generator
|
|
|
|
eventController event.Generator
|
|
|
|
annotationsController annotations.Controller
|
|
|
|
queue workqueue.RateLimitingInterface
|
2019-05-10 00:05:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewPolicyController from cmd args
|
2019-05-15 11:24:27 -07:00
|
|
|
func NewPolicyController(client *client.Client,
|
2019-05-15 12:29:09 -07:00
|
|
|
policyInformer sharedinformer.PolicyInformer,
|
2019-05-13 18:17:28 -07:00
|
|
|
violationBuilder violation.Generator,
|
2019-07-17 17:53:13 -07:00
|
|
|
eventController event.Generator,
|
|
|
|
annotationsController annotations.Controller) *PolicyController {
|
2019-05-10 00:05:21 -07:00
|
|
|
|
|
|
|
controller := &PolicyController{
|
2019-07-17 17:53:13 -07:00
|
|
|
client: client,
|
|
|
|
policyLister: policyInformer.GetLister(),
|
|
|
|
policySynced: policyInformer.GetInfomer().HasSynced,
|
|
|
|
violationBuilder: violationBuilder,
|
|
|
|
eventController: eventController,
|
|
|
|
annotationsController: annotationsController,
|
|
|
|
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), policyWorkQueueName),
|
2019-05-10 00:05:21 -07:00
|
|
|
}
|
|
|
|
|
2019-05-15 12:29:09 -07:00
|
|
|
policyInformer.GetInfomer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
2019-05-10 00:05:21 -07:00
|
|
|
AddFunc: controller.createPolicyHandler,
|
|
|
|
UpdateFunc: controller.updatePolicyHandler,
|
|
|
|
DeleteFunc: controller.deletePolicyHandler,
|
|
|
|
})
|
|
|
|
return controller
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pc *PolicyController) createPolicyHandler(resource interface{}) {
|
|
|
|
pc.enqueuePolicy(resource)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pc *PolicyController) updatePolicyHandler(oldResource, newResource interface{}) {
|
2019-07-15 14:49:22 -07:00
|
|
|
newPolicy := newResource.(*v1alpha1.Policy)
|
|
|
|
oldPolicy := oldResource.(*v1alpha1.Policy)
|
|
|
|
newPolicy.Status = v1alpha1.Status{}
|
|
|
|
oldPolicy.Status = v1alpha1.Status{}
|
2019-06-28 17:22:00 -07:00
|
|
|
newPolicy.ResourceVersion = ""
|
|
|
|
oldPolicy.ResourceVersion = ""
|
2019-07-15 14:49:22 -07:00
|
|
|
if reflect.DeepEqual(newPolicy, oldPolicy) {
|
2019-05-10 00:05:21 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
pc.enqueuePolicy(newResource)
|
|
|
|
}
|
2019-05-10 10:38:38 -07:00
|
|
|
|
2019-05-10 00:05:21 -07:00
|
|
|
func (pc *PolicyController) deletePolicyHandler(resource interface{}) {
|
|
|
|
var object metav1.Object
|
|
|
|
var ok bool
|
|
|
|
if object, ok = resource.(metav1.Object); !ok {
|
2019-06-20 11:20:09 -07:00
|
|
|
glog.Error("error decoding object, invalid type")
|
2019-05-10 00:05:21 -07:00
|
|
|
return
|
|
|
|
}
|
2019-07-17 17:53:13 -07:00
|
|
|
//TODO: need to clear annotations on the resources
|
2019-07-17 23:13:28 -07:00
|
|
|
cleanAnnotations(pc.client, resource)
|
2019-05-31 17:59:36 -07:00
|
|
|
glog.Infof("policy deleted: %s", object.GetName())
|
2019-05-10 00:05:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pc *PolicyController) enqueuePolicy(obj interface{}) {
|
|
|
|
var key string
|
|
|
|
var err error
|
|
|
|
if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil {
|
2019-06-20 11:20:09 -07:00
|
|
|
glog.Error(err)
|
2019-05-10 00:05:21 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
pc.queue.Add(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run is main controller thread
|
|
|
|
func (pc *PolicyController) Run(stopCh <-chan struct{}) error {
|
|
|
|
defer utilruntime.HandleCrash()
|
|
|
|
|
|
|
|
if ok := cache.WaitForCacheSync(stopCh, pc.policySynced); !ok {
|
|
|
|
return fmt.Errorf("failed to wait for caches to sync")
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < policyControllerWorkerCount; i++ {
|
|
|
|
go wait.Until(pc.runWorker, time.Second, stopCh)
|
|
|
|
}
|
2019-05-31 17:59:36 -07:00
|
|
|
glog.Info("started policy controller workers")
|
2019-05-10 00:05:21 -07:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-05 17:43:59 -07:00
|
|
|
//Stop to perform actions when controller is stopped
|
2019-05-15 07:30:22 -07:00
|
|
|
func (pc *PolicyController) Stop() {
|
2019-06-20 16:50:54 -07:00
|
|
|
defer pc.queue.ShutDown()
|
2019-05-31 17:59:36 -07:00
|
|
|
glog.Info("shutting down policy controller workers")
|
2019-05-15 07:30:22 -07:00
|
|
|
}
|
2019-05-10 00:05:21 -07:00
|
|
|
func (pc *PolicyController) runWorker() {
|
|
|
|
for pc.processNextWorkItem() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pc *PolicyController) processNextWorkItem() bool {
|
|
|
|
obj, shutdown := pc.queue.Get()
|
|
|
|
if shutdown {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
err := func(obj interface{}) error {
|
|
|
|
defer pc.queue.Done(obj)
|
|
|
|
err := pc.syncHandler(obj)
|
|
|
|
pc.handleErr(err, obj)
|
|
|
|
return nil
|
|
|
|
}(obj)
|
|
|
|
if err != nil {
|
2019-06-20 11:20:09 -07:00
|
|
|
glog.Error(err)
|
2019-05-10 00:05:21 -07:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pc *PolicyController) handleErr(err error, key interface{}) {
|
|
|
|
if err == nil {
|
|
|
|
pc.queue.Forget(key)
|
|
|
|
return
|
|
|
|
}
|
2019-05-10 10:38:38 -07:00
|
|
|
// This controller retries if something goes wrong. After that, it stops trying.
|
2019-05-10 00:05:21 -07:00
|
|
|
if pc.queue.NumRequeues(key) < policyWorkQueueRetryLimit {
|
2019-05-31 17:59:36 -07:00
|
|
|
glog.Warningf("Error syncing events %v: %v", key, err)
|
2019-05-10 00:05:21 -07:00
|
|
|
// Re-enqueue the key rate limited. Based on the rate limiter on the
|
|
|
|
// queue and the re-enqueue history, the key will be processed later again.
|
|
|
|
pc.queue.AddRateLimited(key)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pc.queue.Forget(key)
|
2019-06-20 11:20:09 -07:00
|
|
|
glog.Error(err)
|
2019-06-05 17:43:59 -07:00
|
|
|
glog.Warningf("Dropping the key %q out of the queue: %v", key, err)
|
2019-05-10 00:05:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (pc *PolicyController) syncHandler(obj interface{}) error {
|
|
|
|
var key string
|
|
|
|
var ok bool
|
|
|
|
if key, ok = obj.(string); !ok {
|
|
|
|
return fmt.Errorf("expected string in workqueue but got %#v", obj)
|
|
|
|
}
|
2019-05-20 10:56:12 -07:00
|
|
|
_, name, err := cache.SplitMetaNamespaceKey(key)
|
2019-05-10 00:05:21 -07:00
|
|
|
if err != nil {
|
2019-06-20 11:20:09 -07:00
|
|
|
glog.Errorf("invalid policy key: %s", key)
|
2019-05-10 00:05:21 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get Policy resource with namespace/name
|
2019-05-20 10:56:12 -07:00
|
|
|
policy, err := pc.policyLister.Get(name)
|
2019-05-10 00:05:21 -07:00
|
|
|
if err != nil {
|
|
|
|
if errors.IsNotFound(err) {
|
2019-06-20 11:20:09 -07:00
|
|
|
glog.Errorf("policy '%s' in work queue no longer exists", key)
|
2019-05-10 00:05:21 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// process policy on existing resource
|
|
|
|
// get the violations and pass to violation Builder
|
|
|
|
// get the events and pass to event Builder
|
2019-05-15 11:24:27 -07:00
|
|
|
//TODO: processPolicy
|
2019-06-26 12:19:11 -07:00
|
|
|
glog.Infof("process policy %s on existing resources", policy.GetName())
|
2019-06-25 23:58:28 -07:00
|
|
|
policyInfos := engine.ProcessExisting(pc.client, policy)
|
2019-07-17 23:13:28 -07:00
|
|
|
events, _ := pc.createEventsAndViolations(policyInfos)
|
2019-06-27 11:43:07 -07:00
|
|
|
pc.eventController.Add(events...)
|
2019-07-17 23:13:28 -07:00
|
|
|
// err = pc.violationBuilder.Add(violations...)
|
|
|
|
// if err != nil {
|
|
|
|
// glog.Error(err)
|
|
|
|
// }
|
2019-07-17 17:53:13 -07:00
|
|
|
|
|
|
|
// add annotations to resources
|
|
|
|
pc.createAnnotations(policyInfos)
|
|
|
|
|
2019-06-26 12:19:11 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-17 17:53:13 -07:00
|
|
|
func (pc *PolicyController) createAnnotations(policyInfos []*info.PolicyInfo) {
|
|
|
|
for _, pi := range policyInfos {
|
|
|
|
var patch []byte
|
|
|
|
//get resource
|
|
|
|
obj, err := pc.client.GetResource(pi.RKind, pi.RNamespace, pi.RName)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// add annotation for policy application
|
|
|
|
ann := obj.GetAnnotations()
|
|
|
|
// Mutation rules
|
|
|
|
ann, mpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Mutation)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Validation rules
|
|
|
|
ann, vpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Validation)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(err)
|
|
|
|
}
|
|
|
|
if mpatch == nil && mpatch == nil {
|
|
|
|
//nothing to patch
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// merge the patches
|
|
|
|
if mpatch != nil && vpatch != nil {
|
|
|
|
patch, err = jsonpatch.MergePatch(mpatch, vpatch)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if mpatch == nil {
|
|
|
|
patch = vpatch
|
|
|
|
} else {
|
2019-07-17 23:13:28 -07:00
|
|
|
patch = mpatch
|
2019-07-17 17:53:13 -07:00
|
|
|
}
|
2019-07-17 23:13:28 -07:00
|
|
|
// add the anotation to the resource
|
2019-07-17 17:53:13 -07:00
|
|
|
_, err = pc.client.PatchResource(pi.RKind, pi.RNamespace, pi.RName, patch)
|
|
|
|
if err != nil {
|
|
|
|
glog.Error(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-15 14:49:22 -07:00
|
|
|
func (pc *PolicyController) createEventsAndViolations(policyInfos []*info.PolicyInfo) ([]*event.Info, []*violation.Info) {
|
2019-06-26 18:16:06 -07:00
|
|
|
events := []*event.Info{}
|
2019-06-27 11:43:07 -07:00
|
|
|
violations := []*violation.Info{}
|
2019-06-25 23:58:28 -07:00
|
|
|
// Create events from the policyInfo
|
|
|
|
for _, policyInfo := range policyInfos {
|
2019-07-15 14:49:22 -07:00
|
|
|
frules := []v1alpha1.FailedRule{}
|
2019-06-26 12:19:11 -07:00
|
|
|
sruleNames := []string{}
|
|
|
|
|
2019-06-26 15:31:18 -07:00
|
|
|
for _, rule := range policyInfo.Rules {
|
|
|
|
if !rule.IsSuccessful() {
|
2019-06-26 18:16:06 -07:00
|
|
|
e := &event.Info{}
|
2019-07-15 14:49:22 -07:00
|
|
|
frule := v1alpha1.FailedRule{Name: rule.Name}
|
2019-06-26 15:31:18 -07:00
|
|
|
switch rule.RuleType {
|
|
|
|
case info.Mutation, info.Validation, info.Generation:
|
2019-06-27 11:43:07 -07:00
|
|
|
// Events
|
2019-06-26 15:31:18 -07:00
|
|
|
e = event.NewEvent(policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, event.PolicyViolation, event.FProcessRule, rule.Name, policyInfo.Name)
|
2019-07-15 14:49:22 -07:00
|
|
|
switch rule.RuleType {
|
|
|
|
case info.Mutation:
|
|
|
|
frule.Type = info.Mutation.String()
|
|
|
|
case info.Validation:
|
|
|
|
frule.Type = info.Validation.String()
|
|
|
|
case info.Generation:
|
|
|
|
frule.Type = info.Generation.String()
|
|
|
|
}
|
2019-06-26 15:31:18 -07:00
|
|
|
default:
|
|
|
|
glog.Info("Unsupported Rule type")
|
2019-06-26 12:19:11 -07:00
|
|
|
}
|
2019-07-15 14:49:22 -07:00
|
|
|
frules = append(frules, frule)
|
2019-06-26 15:31:18 -07:00
|
|
|
events = append(events, e)
|
|
|
|
} else {
|
2019-06-26 12:19:11 -07:00
|
|
|
sruleNames = append(sruleNames, rule.Name)
|
|
|
|
}
|
2019-06-26 15:31:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if !policyInfo.IsSuccessful() {
|
2019-07-15 14:49:22 -07:00
|
|
|
e := event.NewEvent("Policy", "", policyInfo.Name, event.PolicyViolation, event.FResourcePolcy, policyInfo.RNamespace+"/"+policyInfo.RName, concatFailedRules(frules))
|
2019-06-26 12:19:11 -07:00
|
|
|
events = append(events, e)
|
2019-06-27 11:43:07 -07:00
|
|
|
// Violation
|
2019-07-15 11:29:58 -07:00
|
|
|
// TODO: Violation is currently create at policy, level not resource level
|
|
|
|
// As we create violation, we check if the
|
2019-07-15 14:49:22 -07:00
|
|
|
v := violation.BuldNewViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, event.PolicyViolation.String(), frules)
|
2019-06-27 11:43:07 -07:00
|
|
|
violations = append(violations, v)
|
2019-07-15 14:49:22 -07:00
|
|
|
} else {
|
|
|
|
// clean up violations
|
|
|
|
pc.violationBuilder.RemoveInactiveViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, info.Mutation)
|
|
|
|
pc.violationBuilder.RemoveInactiveViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, info.Validation)
|
2019-06-25 23:58:28 -07:00
|
|
|
}
|
2019-07-15 14:49:22 -07:00
|
|
|
|
2019-06-27 11:43:07 -07:00
|
|
|
// else {
|
|
|
|
// // Policy was processed succesfully
|
|
|
|
// e := event.NewEvent("Policy", "", policyInfo.Name, event.PolicyApplied, event.SPolicyApply, policyInfo.Name)
|
|
|
|
// events = append(events, e)
|
|
|
|
// // Policy applied succesfully on resource
|
|
|
|
// e = event.NewEvent(policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, event.PolicyApplied, event.SRuleApply, strings.Join(sruleNames, ";"), policyInfo.RName)
|
|
|
|
// events = append(events, e)
|
|
|
|
// }
|
2019-06-25 23:58:28 -07:00
|
|
|
}
|
2019-06-27 11:43:07 -07:00
|
|
|
return events, violations
|
2019-06-05 17:43:59 -07:00
|
|
|
}
|