mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-09 01:16:55 +00:00
581 lines
16 KiB
Go
581 lines
16 KiB
Go
package violation
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/golang/glog"
|
|
v1alpha1 "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
|
|
lister "github.com/nirmata/kyverno/pkg/client/listers/policy/v1alpha1"
|
|
client "github.com/nirmata/kyverno/pkg/dclient"
|
|
event "github.com/nirmata/kyverno/pkg/event"
|
|
"github.com/nirmata/kyverno/pkg/info"
|
|
"github.com/nirmata/kyverno/pkg/sharedinformer"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
)
|
|
|
|
//Generator to generate policy violation
|
|
type Generator interface {
|
|
Add(infos ...*Info) error
|
|
RemoveInactiveViolation(policy, rKind, rNs, rName string, ruleType info.RuleType) error
|
|
ResourceRemoval(policy, rKind, rNs, rName string) error
|
|
}
|
|
|
|
type builder struct {
|
|
client *client.Client
|
|
policyLister lister.PolicyLister
|
|
eventBuilder event.Generator
|
|
}
|
|
|
|
//Builder is to build policy violations
|
|
type Builder interface {
|
|
Generator
|
|
processViolation(info *Info) error
|
|
isActive(kind string, rname string, rnamespace string) (bool, error)
|
|
}
|
|
|
|
//NewPolicyViolationBuilder returns new violation builder
|
|
func NewPolicyViolationBuilder(client *client.Client,
|
|
sharedInfomer sharedinformer.PolicyInformer,
|
|
eventController event.Generator) Builder {
|
|
|
|
builder := &builder{
|
|
client: client,
|
|
policyLister: sharedInfomer.GetLister(),
|
|
eventBuilder: eventController,
|
|
}
|
|
return builder
|
|
}
|
|
|
|
func (b *builder) Add(infos ...*Info) error {
|
|
if infos == nil {
|
|
return nil
|
|
}
|
|
for _, info := range infos {
|
|
return b.processViolation(info)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *builder) processViolation(info *Info) error {
|
|
statusMap := map[string]interface{}{}
|
|
violationsMap := map[string]interface{}{}
|
|
violationMap := map[string]interface{}{}
|
|
var violations interface{}
|
|
var violation interface{}
|
|
// Get Policy
|
|
obj, err := b.client.GetResource("Policy", "", info.Policy, "status")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
unstr := obj.UnstructuredContent()
|
|
// get "status" subresource
|
|
status, ok := unstr["status"]
|
|
if ok {
|
|
// status exists
|
|
// status is already present then we append violations
|
|
if statusMap, ok = status.(map[string]interface{}); !ok {
|
|
return errors.New("Unable to parse status subresource")
|
|
}
|
|
// get policy violations
|
|
violations, ok = statusMap["violations"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
violationsMap, ok = violations.(map[string]interface{})
|
|
if !ok {
|
|
return errors.New("Unable to get status.violations subresource")
|
|
}
|
|
// check if the resource has a violation
|
|
violation, ok = violationsMap[info.getKey()]
|
|
if !ok {
|
|
// add resource violation
|
|
violationsMap[info.getKey()] = info.Violation
|
|
statusMap["violations"] = violationsMap
|
|
unstr["status"] = statusMap
|
|
} else {
|
|
violationMap, ok = violation.(map[string]interface{})
|
|
if !ok {
|
|
return errors.New("Unable to get status.violations.violation subresource")
|
|
}
|
|
// we check if the new violation updates are different from stored violation info
|
|
v := v1alpha1.Violation{}
|
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(violationMap, &v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// compare v & info.Violation
|
|
if v.IsEqual(info.Violation) {
|
|
// no updates to violation
|
|
// do nothing
|
|
return nil
|
|
}
|
|
// update the violation
|
|
violationsMap[info.getKey()] = info.Violation
|
|
statusMap["violations"] = violationsMap
|
|
unstr["status"] = statusMap
|
|
}
|
|
} else {
|
|
violationsMap[info.getKey()] = info.Violation
|
|
statusMap["violations"] = violationsMap
|
|
unstr["status"] = statusMap
|
|
}
|
|
|
|
obj.SetUnstructuredContent(unstr)
|
|
// update the status sub-resource for policy
|
|
_, err = b.client.UpdateStatusResource("Policy", "", obj, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *builder) RemoveInactiveViolation(policy, rKind, rNs, rName string, ruleType info.RuleType) error {
|
|
statusMap := map[string]interface{}{}
|
|
violationsMap := map[string]interface{}{}
|
|
violationMap := map[string]interface{}{}
|
|
var violations interface{}
|
|
var violation interface{}
|
|
// Get Policy
|
|
obj, err := b.client.GetResource("Policy", "", policy, "status")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
unstr := obj.UnstructuredContent()
|
|
// get "status" subresource
|
|
status, ok := unstr["status"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
// status exists
|
|
// status is already present then we append violations
|
|
if statusMap, ok = status.(map[string]interface{}); !ok {
|
|
return errors.New("Unable to parse status subresource")
|
|
}
|
|
// get policy violations
|
|
violations, ok = statusMap["violations"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
violationsMap, ok = violations.(map[string]interface{})
|
|
if !ok {
|
|
return errors.New("Unable to get status.violations subresource")
|
|
}
|
|
// check if the resource has a violation
|
|
violation, ok = violationsMap[BuildKey(rKind, rNs, rName)]
|
|
if !ok {
|
|
// no violation for this resource
|
|
return nil
|
|
}
|
|
violationMap, ok = violation.(map[string]interface{})
|
|
if !ok {
|
|
return errors.New("Unable to get status.violations.violation subresource")
|
|
}
|
|
// check remove the rules of the given type
|
|
// this is called when the policy is applied succesfully, so we can remove the previous failed rules
|
|
// if all rules are to be removed, the deleted the violation
|
|
v := v1alpha1.Violation{}
|
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(violationMap, &v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !v.RemoveRulesOfType(ruleType.String()) {
|
|
// no rule of given type found,
|
|
// no need to remove rule
|
|
return nil
|
|
}
|
|
// if there are no faile rules remove the violation
|
|
if len(v.Rules) == 0 {
|
|
delete(violationsMap, BuildKey(rKind, rNs, rName))
|
|
} else {
|
|
// update the rules
|
|
violationsMap[BuildKey(rKind, rNs, rName)] = v
|
|
}
|
|
statusMap["violations"] = violationsMap
|
|
unstr["status"] = statusMap
|
|
|
|
obj.SetUnstructuredContent(unstr)
|
|
// update the status sub-resource for policy
|
|
_, err = b.client.UpdateStatusResource("Policy", "", obj, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b *builder) ResourceRemoval(policy, rKind, rNs, rName string) error {
|
|
statusMap := map[string]interface{}{}
|
|
violationsMap := map[string]interface{}{}
|
|
var violations interface{}
|
|
// Get Policy
|
|
obj, err := b.client.GetResource("Policy", "", policy, "status")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
unstr := obj.UnstructuredContent()
|
|
// get "status" subresource
|
|
status, ok := unstr["status"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
// status exists
|
|
// status is already present then we append violations
|
|
if statusMap, ok = status.(map[string]interface{}); !ok {
|
|
return errors.New("Unable to parse status subresource")
|
|
}
|
|
// get policy violations
|
|
violations, ok = statusMap["violations"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
violationsMap, ok = violations.(map[string]interface{})
|
|
if !ok {
|
|
return errors.New("Unable to get status.violations subresource")
|
|
}
|
|
|
|
// check if the resource has a violation
|
|
_, ok = violationsMap[BuildKey(rKind, rNs, rName)]
|
|
if !ok {
|
|
// no violation for this resource
|
|
return nil
|
|
}
|
|
// remove the pair from the map
|
|
delete(violationsMap, BuildKey(rKind, rNs, rName))
|
|
if len(violationsMap) == 0 {
|
|
delete(statusMap, "violations")
|
|
} else {
|
|
statusMap["violations"] = violationsMap
|
|
}
|
|
unstr["status"] = statusMap
|
|
|
|
obj.SetUnstructuredContent(unstr)
|
|
// update the status sub-resource for policy
|
|
_, err = b.client.UpdateStatusResource("Policy", "", obj, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// func (b *builder) processViolation(info *Info) error {
|
|
// currVs := map[string]interface{}{}
|
|
// statusMap := map[string]interface{}{}
|
|
// var ok bool
|
|
// //TODO: hack get from client
|
|
// p1, err := b.client.GetResource("Policy", "", info.Policy, "status")
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// unstr := p1.UnstructuredContent()
|
|
// // check if "status" field exists
|
|
// status, ok := unstr["status"]
|
|
// if ok {
|
|
// // status is already present then we append violations
|
|
// if statusMap, ok = status.(map[string]interface{}); !ok {
|
|
// return errors.New("Unable to parse status subresource")
|
|
// }
|
|
// violations, ok := statusMap["violations"]
|
|
// if !ok {
|
|
// glog.Info("violation not present")
|
|
// }
|
|
// // Violations map[string][]Violations
|
|
// glog.Info(reflect.TypeOf(violations))
|
|
// if currVs, ok = violations.(map[string]interface{}); !ok {
|
|
// return errors.New("Unable to parse violations")
|
|
// }
|
|
// }
|
|
// // Info:
|
|
// // Key - Kind, Namespace, Name
|
|
// // policy - Name
|
|
// // violation, ok := currVs[info.getKey()]
|
|
// // Key -> resource
|
|
// // 1> Check if there were any previous violations for the given key
|
|
// // 2> If No, create a new one
|
|
// if !ok {
|
|
// currVs[info.getKey()] = info.Violation
|
|
// } else {
|
|
// currV := currVs[info.getKey()]
|
|
// glog.Info(reflect.TypeOf(currV))
|
|
// v, ok := currV.(map[string]interface{})
|
|
// if !ok {
|
|
// glog.Info("type not matching")
|
|
// }
|
|
// // get rules
|
|
// rules, ok := v["rules"]
|
|
// if !ok {
|
|
// glog.Info("rules not found")
|
|
// }
|
|
// glog.Info(reflect.TypeOf(rules))
|
|
// rs, ok := rules.([]interface{})
|
|
// if !ok {
|
|
// glog.Info("type not matching")
|
|
// }
|
|
// // check if rules are samre
|
|
// if isRuleNamesEqual(rs, info.Violation.Rules) {
|
|
// return nil
|
|
// }
|
|
// // else update the errors
|
|
// currVs[info.getKey()] = info.Violation
|
|
// }
|
|
// // newViolation := info.Violation
|
|
// // for _, violation := range currentViolations {
|
|
// // glog.Info(reflect.TypeOf(violation))
|
|
// // if v, ok := violation.(map[string]interface{}); ok {
|
|
// // if name, ok := v["name"].(string); ok {
|
|
// // if namespace, ok := v["namespace"].(string); ok {
|
|
// // ok, err := b.isActive(info.Kind, name, namespace)
|
|
// // if err != nil {
|
|
// // glog.Error(err)
|
|
// // continue
|
|
// // }
|
|
// // if !ok {
|
|
// // //TODO remove the violation as it corresponds to resource that does not exist
|
|
// // glog.Info("removed violation")
|
|
// // }
|
|
// // }
|
|
// // }
|
|
// // }
|
|
// // }
|
|
// // currentViolations = append(currentViolations, newViolation)
|
|
// // // update violations
|
|
// // set the updated status
|
|
// statusMap["violations"] = currVs
|
|
// unstr["status"] = statusMap
|
|
// p1.SetUnstructuredContent(unstr)
|
|
// _, err = b.client.UpdateStatusResource("Policy", "", p1, false)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// return nil
|
|
// }
|
|
|
|
func (b *builder) isActive(kind, rname, rnamespace string) (bool, error) {
|
|
// Generate Merge Patch
|
|
_, err := b.client.GetResource(b.client.DiscoveryClient.GetGVRFromKind(kind).Resource, rnamespace, rname)
|
|
if err != nil {
|
|
glog.Errorf("unable to get resource %s/%s ", rnamespace, rname)
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
//NewViolation return new policy violation
|
|
func NewViolation(reason event.Reason, policyName, kind, rname, rnamespace, msg string) *Info {
|
|
return &Info{Policy: policyName,
|
|
Violation: v1alpha1.Violation{
|
|
Kind: kind,
|
|
Name: rname,
|
|
Namespace: rnamespace,
|
|
Reason: reason.String(),
|
|
},
|
|
}
|
|
}
|
|
|
|
// //NewViolationFromEvent returns violation info from event
|
|
// func NewViolationFromEvent(e *event.Info, pName, rKind, rName, rnamespace string) *Info {
|
|
// return &Info{
|
|
// Policy: pName,
|
|
// Violation: types.Violation{
|
|
// Kind: rKind,
|
|
// Name: rName,
|
|
// Namespace: rnamespace,
|
|
// Reason: e.Reason,
|
|
// Message: e.Message,
|
|
// },
|
|
// }
|
|
// }
|
|
// Build a new Violation
|
|
func BuldNewViolation(pName string, rKind string, rNs string, rName string, reason string, frules []v1alpha1.FailedRule) *Info {
|
|
return &Info{
|
|
Policy: pName,
|
|
Violation: v1alpha1.Violation{
|
|
Kind: rKind,
|
|
Namespace: rNs,
|
|
Name: rName,
|
|
Reason: reason,
|
|
Rules: frules,
|
|
},
|
|
}
|
|
}
|
|
|
|
func removeRuleTypes(currRules []interface{}, ruleType info.RuleType) ([]interface{}, error) {
|
|
//rules := []v1alpha1.FailedRule{}
|
|
var rules []interface{}
|
|
// removedRuleCount := 0
|
|
for _, r := range currRules {
|
|
glog.Info(reflect.TypeOf(r))
|
|
rfule, ok := r.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("incorrect type")
|
|
}
|
|
glog.Info(reflect.TypeOf(rfule["type"]))
|
|
rtype, ok := rfule["type"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("incorrect type")
|
|
}
|
|
name, ok := rfule["name"].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("incorrect type")
|
|
}
|
|
if rtype != ruleType.String() {
|
|
rules = append(rules, v1alpha1.FailedRule{Name: name, Type: rtype})
|
|
}
|
|
}
|
|
return rules, nil
|
|
}
|
|
|
|
func isRuleNamesEqual(currRules []interface{}, newRules []v1alpha1.FailedRule) bool {
|
|
if len(currRules) != len(newRules) {
|
|
return false
|
|
}
|
|
for i, r := range currRules {
|
|
glog.Info(reflect.TypeOf(r))
|
|
rfule, ok := r.(map[string]interface{})
|
|
if !ok {
|
|
return false
|
|
}
|
|
glog.Info(reflect.TypeOf(rfule["name"]))
|
|
// name
|
|
name, ok := rfule["name"].(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if name != newRules[i].Name {
|
|
return false
|
|
}
|
|
// type
|
|
|
|
rtype, ok := rfule["type"].(string)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if rtype != newRules[i].Type {
|
|
return false
|
|
}
|
|
|
|
}
|
|
return true
|
|
}
|
|
|
|
// //RemoveViolation will remove the violation for the resource if there was one
|
|
// func (b *builder) RemoveInactiveViolation(policy, rKind, rNs, rName string, ruleType info.RuleType) error {
|
|
// // Remove the <resource, Violation> pair from map
|
|
// statusMap := map[string]interface{}{}
|
|
// currVs := map[string]interface{}{}
|
|
// // Get the policy
|
|
// p, err := b.client.GetResource("Policy", "", policy, "status")
|
|
// if err != nil {
|
|
// glog.Infof("policy %s not found", policy)
|
|
// return err
|
|
// }
|
|
// unstr := p.UnstructuredContent()
|
|
|
|
// // check if "status" field exists
|
|
// status, ok := unstr["status"]
|
|
// if ok {
|
|
// // status is already present then we append violations
|
|
// if statusMap, ok = status.(map[string]interface{}); !ok {
|
|
// return errors.New("Unable to parse status subresource")
|
|
// }
|
|
// violations, ok := statusMap["violations"]
|
|
// if !ok {
|
|
// glog.Info("violation not present")
|
|
// }
|
|
// glog.Info(reflect.TypeOf(violations))
|
|
// if currVs, ok = violations.(map[string]interface{}); !ok {
|
|
// return errors.New("Unable to parse violations")
|
|
// }
|
|
// currV, ok := currVs[BuildKey(rKind, rNs, rName)]
|
|
// if !ok {
|
|
// // No Violation present
|
|
// return nil
|
|
// }
|
|
// glog.Info(reflect.TypeOf(currV))
|
|
// v, ok := currV.(map[string]interface{})
|
|
// if !ok {
|
|
// glog.Info("type not matching")
|
|
// }
|
|
// // get rules
|
|
// rules, ok := v["rules"]
|
|
// if !ok {
|
|
// glog.Info("rules not found")
|
|
// }
|
|
// glog.Info(reflect.TypeOf(rules))
|
|
// rs, ok := rules.([]interface{})
|
|
// if !ok {
|
|
// glog.Info("type not matching")
|
|
// }
|
|
// // Remove rules of defined type
|
|
// newrs, err := removeRuleTypes(rs, ruleType)
|
|
// if err != nil {
|
|
// glog.Info(err)
|
|
// }
|
|
// if newrs == nil {
|
|
// // all records are removed and is empty
|
|
// glog.Info("can remove the record")
|
|
// delete(currVs, BuildKey(rKind, rNs, rName))
|
|
// } else {
|
|
// v["rules"] = newrs
|
|
// // update the violation with new rule
|
|
// currVs[BuildKey(rKind, rNs, rName)] = v
|
|
// }
|
|
// // update violations
|
|
// statusMap["violations"] = currVs
|
|
// // update status
|
|
// unstr["status"] = statusMap
|
|
// p.SetUnstructuredContent(unstr)
|
|
// _, err = b.client.UpdateStatusResource("Policy", "", p, false)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// }
|
|
// return nil
|
|
// }
|
|
|
|
// func (b *builder) ResourceRemoval(policy, rKind, rNs, rName string) error {
|
|
// // Remove the <resource, Violation> pair from map
|
|
// statusMap := map[string]interface{}{}
|
|
// currVs := map[string]interface{}{}
|
|
// // Get the policy
|
|
// p, err := b.client.GetResource("Policy", "", policy, "status")
|
|
// if err != nil {
|
|
// glog.Infof("policy %s not found", policy)
|
|
// return err
|
|
// }
|
|
// unstr := p.UnstructuredContent()
|
|
// // check if "status" field exists
|
|
// status, ok := unstr["status"]
|
|
// if ok {
|
|
// // status is already present then we append violations
|
|
// if statusMap, ok = status.(map[string]interface{}); !ok {
|
|
// return errors.New("Unable to parse status subresource")
|
|
// }
|
|
// violations, ok := statusMap["violations"]
|
|
// if !ok {
|
|
// glog.Info("violation not present")
|
|
// }
|
|
// glog.Info(reflect.TypeOf(violations))
|
|
// if currVs, ok = violations.(map[string]interface{}); !ok {
|
|
// return errors.New("Unable to parse violations")
|
|
// }
|
|
// _, ok = currVs[BuildKey(rKind, rNs, rName)]
|
|
// if !ok {
|
|
// // No Violation for this resource
|
|
// return nil
|
|
// }
|
|
// // remove the pair from the map
|
|
// delete(currVs, BuildKey(rKind, rNs, rName))
|
|
// glog.Info("Removed Violation")
|
|
// // update violations
|
|
// statusMap["violations"] = currVs
|
|
// // update status
|
|
// unstr["status"] = statusMap
|
|
// p.SetUnstructuredContent(unstr)
|
|
// _, err = b.client.UpdateStatusResource("Policy", "", p, false)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
// }
|
|
// return nil
|
|
// }
|