1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 10:28:36 +00:00

clean up mutation

This commit is contained in:
shivkumar dudhani 2019-08-09 12:59:37 -07:00
parent a30ad6bab2
commit 373d9a45ad
6 changed files with 188 additions and 94 deletions

View file

@ -5,16 +5,19 @@ import (
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/info"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// Mutate performs mutation. Overlay first and then mutation patches
//TODO: check if gvk needs to be passed or can be set in resource
func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([][]byte, []*info.RuleInfo) {
//
var allPatches [][]byte
patchedDocument := rawResource
ris := []*info.RuleInfo{}
//TODO: convert rawResource to unstructured to avoid unmarhalling all the time for get some resource information
//TODO: pass unstructured instead of rawResource ?
resource, err := convertToUnstructured(rawResource)
if err != nil {
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
}
var patches [][]byte
var ruleInfos []*info.RuleInfo
for _, rule := range policy.Spec.Rules {
if rule.Mutation == nil {
@ -24,56 +27,57 @@ func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersio
// check if the resource satisfies the filter conditions defined in the rule
//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
ok := ResourceMeetsDescription(rawResource, rule.MatchResources.ResourceDescription, rule.ExcludeResources.ResourceDescription, gvk)
ok := MatchesResourceDescription(resource, rule, gvk)
if !ok {
name := ParseNameFromObject(rawResource)
namespace := ParseNamespaceFromObject(rawResource)
glog.V(3).Infof("resource %s/%s does not satisfy the resource description for the rule ", namespace, name)
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resource.GetNamespace(), resource.GetName())
continue
}
ri := info.NewRuleInfo(rule.Name, info.Mutation)
ruleInfo := info.NewRuleInfo(rule.Name, info.Mutation)
// Process Overlay
if rule.Mutation.Overlay != nil {
overlayPatches, err := processOverlay(rule, rawResource, gvk)
oPatches, err := processOverlay(resource, rule, rawResource)
if err == nil {
if len(overlayPatches) == 0 {
if len(oPatches) == 0 {
// if array elements dont match then we skip(nil patch, no error)
// or if acnohor is defined and doenst match
// policy is not applicable
glog.V(4).Info("overlay does not match, so skipping applying rule")
continue
}
glog.V(4).Infof("overlay applied succesfully on resource")
ri.Add("Overlay succesfully applied")
patch := JoinPatches(overlayPatches)
allPatches = append(allPatches, overlayPatches...)
glog.V(4).Infof("overlay applied succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
ruleInfo.Add("Overlay succesfully applied")
// update rule information
// strip slashes from string
ri.Changes = string(patch)
patch := JoinPatches(oPatches)
ruleInfo.Changes = string(patch)
patches = append(patches, oPatches...)
} else {
glog.V(4).Infof("failed to apply overlay: %v", err)
ri.Fail()
ri.Addf("failed to apply overlay: %v", err)
ruleInfo.Fail()
ruleInfo.Addf("failed to apply overlay: %v", err)
}
}
// Process Patches
if len(rule.Mutation.Patches) != 0 {
rulePatches, errs := processPatches(rule, patchedDocument)
jsonPatches, errs := processPatches(rule, rawResource)
if len(errs) > 0 {
ri.Fail()
ruleInfo.Fail()
for _, err := range errs {
glog.V(4).Infof("failed to apply patches: %v", err)
ri.Addf("patches application has failed, err %v.", err)
ruleInfo.Addf("patches application has failed, err %v.", err)
}
} else {
glog.V(4).Infof("patches applied succesfully on resource")
ri.Addf("Patches succesfully applied.")
allPatches = append(allPatches, rulePatches...)
glog.V(4).Infof("patches applied succesfully on resource %s/%s", resource.GetNamespace(), resource.GetName())
ruleInfo.Addf("Patches succesfully applied.")
patches = append(patches, jsonPatches...)
}
}
ris = append(ris, ri)
ruleInfos = append(ruleInfos, ruleInfo)
}
return allPatches, ris
return patches, ruleInfos
}

View file

@ -12,22 +12,30 @@ import (
jsonpatch "github.com/evanphx/json-patch"
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
// ProcessOverlay handles validating admission request
// Checks the target resources for rules defined in the policy
func processOverlay(rule kubepolicy.Rule, rawResource []byte, gvk metav1.GroupVersionKind) ([][]byte, error) {
func processOverlay(resourceUnstr *unstructured.Unstructured, rule kubepolicy.Rule, rawResource []byte) ([][]byte, error) {
//TODO check if there is better solution
resourceRaw, err := resourceUnstr.MarshalJSON()
if err != nil {
glog.V(4).Infof("unable to marshal resource : %v", err)
return nil, err
}
var resource interface{}
if err := json.Unmarshal(rawResource, &resource); err != nil {
if err := json.Unmarshal(resourceRaw, &resource); err != nil {
glog.V(4).Infof("unable to unmarshal resource : %v", err)
return nil, err
}
resourceInfo := ParseResourceInfoFromObject(rawResource)
// resourceInfo := ParseResourceInfoFromObject(rawResource)
patches, err := processOverlayPatches(resource, *rule.Mutation.Overlay)
if err != nil && strings.Contains(err.Error(), "Conditions are not met") {
glog.Infof("Resource does not meet conditions in overlay pattern, resource=%s, rule=%s\n", resourceInfo, rule.Name)
glog.V(4).Infof("overlay pattern %s does not match resource %s/%s", *rule.Mutation.Overlay, resourceUnstr.GetNamespace(), resourceUnstr.GetName())
// glog.Infof("Resource does not meet conditions in overlay pattern, resource=%s, rule=%s\n", resourceInfo, rule.Name)
return nil, nil
}

View file

@ -150,6 +150,66 @@ func excludeNamespaces(namespaces []string, excludeNs *string) []string {
return filteredNamespaces
}
//MatchesResourceDescription checks if the resource matches resource desription of the rule or not
func MatchesResourceDescription(resource *unstructured.Unstructured, rule v1alpha1.Rule, gvk metav1.GroupVersionKind) bool {
matches := rule.MatchResources.ResourceDescription
exclude := rule.ExcludeResources.ResourceDescription
if !findKind(matches.Kinds, gvk.Kind) {
return false
}
// meta := parseMetadataFromObject(resourceRaw)
name := resource.GetName()
namespace := resource.GetNamespace()
if matches.Name != nil {
// Matches
if !wildcard.Match(*matches.Name, name) {
return false
}
}
// Exclude
// the resource name matches the exclude resource name then reject
if exclude.Name != nil {
if wildcard.Match(*exclude.Name, name) {
return false
}
}
// Matches
if matches.Namespace != nil && *matches.Namespace != namespace {
return false
}
// Exclude
if exclude.Namespace != nil && *exclude.Namespace == namespace {
return false
}
// Matches
if matches.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(matches.Selector)
if err != nil {
glog.Error(err)
return false
}
if !selector.Matches(labels.Set(resource.GetLabels())) {
return false
}
}
// Exclude
if exclude.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(exclude.Selector)
// if the label selector is incorrect, should be fail or
if err != nil {
glog.Error(err)
return false
}
if selector.Matches(labels.Set(resource.GetLabels())) {
return false
}
}
return true
}
// ResourceMeetsDescription checks requests kind, name and labels to fit the policy rule
func ResourceMeetsDescription(resourceRaw []byte, matches v1alpha1.ResourceDescription, exclude v1alpha1.ResourceDescription, gvk metav1.GroupVersionKind) bool {
if !findKind(matches.Kinds, gvk.Kind) {
@ -458,3 +518,13 @@ type resourceInfo struct {
Resource unstructured.Unstructured
Gvk *metav1.GroupVersionKind
}
func convertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
resource := &unstructured.Unstructured{}
err := resource.UnmarshalJSON(data)
if err != nil {
glog.V(4).Infof("failed to unmarshall resource: %v", err)
return nil, err
}
return resource, nil
}

View file

@ -18,38 +18,53 @@ import (
// Validate handles validating admission request
// Checks the target resources for rules defined in the policy
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]*info.RuleInfo, error) {
var resource interface{}
ris := []*info.RuleInfo{}
err := json.Unmarshal(rawResource, &resource)
//TODO: convert rawResource to unstructured to avoid unmarhalling all the time for get some resource information
//TODO: pass unstructured instead of rawResource ?
resourceUnstr, err := convertToUnstructured(rawResource)
if err != nil {
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
}
resourceRaw, err := resourceUnstr.MarshalJSON()
if err != nil {
glog.V(4).Infof("unable to marshal resource : %v", err)
return nil, err
}
var resource interface{}
if err := json.Unmarshal(resourceRaw, &resource); err != nil {
glog.V(4).Infof("unable to unmarshal resource : %v", err)
return nil, err
}
var ruleInfos []*info.RuleInfo
for _, rule := range policy.Spec.Rules {
if rule.Validation == nil {
continue
}
ri := info.NewRuleInfo(rule.Name, info.Validation)
ok := ResourceMeetsDescription(rawResource, rule.MatchResources.ResourceDescription, rule.ExcludeResources.ResourceDescription, gvk)
// check if the resource satisfies the filter conditions defined in the rule
//TODO: this needs to be extracted, to filter the resource so that we can avoid passing resources that
// dont statisfy a policy rule resource description
ok := MatchesResourceDescription(resourceUnstr, rule, gvk)
if !ok {
glog.V(3).Infof("Not applicable on specified resource kind%s", gvk.Kind)
glog.V(4).Infof("resource %s/%s does not satisfy the resource description for the rule ", resourceUnstr.GetNamespace(), resourceUnstr.GetName())
continue
}
ruleInfo := info.NewRuleInfo(rule.Name, info.Validation)
err := validateResourceWithPattern(resource, rule.Validation.Pattern)
if err != nil {
ri.Fail()
ri.Addf("validation has failed, err %v.", err)
ruleInfo.Fail()
ruleInfo.Addf("Failed to apply pattern: %v.", err)
} else {
ri.Addf("Rule %s: Validation succesfully.", rule.Name)
ruleInfo.Add("Pattern succesfully validated")
glog.V(4).Infof("pattern validated succesfully on resource %s/%s", resourceUnstr.GetNamespace(), resourceUnstr.GetName())
}
ris = append(ris, ri)
ruleInfos = append(ruleInfos, ruleInfo)
}
return ris, nil
return ruleInfos, nil
}
// validateResourceWithPattern is a start of element-by-element validation process

View file

@ -7,16 +7,19 @@ import (
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// HandleMutation handles mutating webhook admission request
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
var patches [][]byte
var policyInfos []*info.PolicyInfo
glog.V(4).Infof("Receive request in mutating webhook: Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
policies, err := ws.policyLister.List(labels.NewSelector())
if err != nil {
//TODO check if the CRD is created ?
// Unable to connect to policy Lister to access policies
glog.Error("Unable to connect to policy controller to access policies. Mutation Rules are NOT being applied")
glog.Warning(err)
@ -24,74 +27,58 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
Allowed: true,
}
}
rname := engine.ParseNameFromObject(request.Object.Raw)
rns := engine.ParseNamespaceFromObject(request.Object.Raw)
rkind := request.Kind.Kind
if rkind == "" {
glog.Errorf("failed to parse KIND from request: Namespace=%s Name=%s UID=%s patchOperation=%s\n", request.Namespace, request.Name, request.UID, request.Operation)
}
var allPatches [][]byte
policyInfos := []*info.PolicyInfo{}
resource, err := convertToUnstructured(request.Object.Raw)
if err != nil {
glog.Errorf("unable to convert raw resource to unstructured: %v", err)
}
//TODO: check if resource gvk is available in raw resource,
// if not then set it from the api request
resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind})
//TODO: check if the name and namespace is also passed right in the resource?
// all the patches to be applied on the resource
for _, policy := range policies {
// check if policy has a rule for the admission request kind
if !StringInSlice(request.Kind.Kind, getApplicableKindsForPolicy(policy)) {
continue
}
//TODO: HACK Check if an update of annotations
if checkIfOnlyAnnotationsUpdate(request) {
return &v1beta1.AdmissionResponse{
Allowed: true,
}
}
policyInfo := info.NewPolicyInfo(policy.Name,
rkind,
rname,
rns,
resource.GetKind(),
resource.GetName(),
resource.GetNamespace(),
policy.Spec.ValidationFailureAction)
glog.V(3).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
request.Kind.Kind, rns, rname, request.UID, request.Operation)
glog.Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
glog.V(4).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
resource.GetKind(), resource.GetNamespace(), resource.GetName(), request.UID, request.Operation)
glog.V(4).Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
//TODO: passing policy value as we dont wont to modify the policy
policyPatches, ruleInfos := engine.Mutate(*policy, request.Object.Raw, request.Kind)
policyInfo.AddRuleInfos(ruleInfos)
if !policyInfo.IsSuccessful() {
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
for _, r := range ruleInfos {
glog.Warningf("%s: %s\n", r.Name, r.Msgs)
}
} else {
// CleanUp Violations if exists
err := ws.violationBuilder.RemoveInactiveViolation(policy.Name, request.Kind.Kind, rns, rname, info.Mutation)
if err != nil {
glog.Info(err)
}
allPatches = append(allPatches, policyPatches...)
glog.Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, rname, rns)
}
policyInfos = append(policyInfos, policyInfo)
annPatch := addAnnotationsToResource(request.Object.Raw, policyInfo, info.Mutation)
if annPatch != nil {
// add annotations
ws.annotationsController.Add(rkind, rns, rname, annPatch)
if !policyInfo.IsSuccessful() {
glog.V(4).Infof("Failed to apply policy %s on resource %s/%s", policy.Name, resource.GetNamespace(), resource.GetName())
glog.V(4).Info("Failed rule details")
for _, r := range ruleInfos {
glog.V(4).Infof("%s: %s\n", r.Name, r.Msgs)
}
continue
}
patches = append(patches, policyPatches...)
glog.V(4).Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName())
}
if len(allPatches) > 0 {
eventsInfo, _ := newEventInfoFromPolicyInfo(policyInfos, (request.Operation == v1beta1.Update), info.Mutation)
ws.eventController.Add(eventsInfo...)
}
// ADD ANNOTATIONS
// ADD EVENTS
ok, msg := isAdmSuccesful(policyInfos)
if ok {
patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{
Allowed: true,
Patch: engine.JoinPatches(allPatches),
Patch: engine.JoinPatches(patches),
PatchType: &patchType,
}
}

View file

@ -210,3 +210,13 @@ func setKindForObject(bytes []byte, kind string) []byte {
}
return data
}
func convertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
resource := &unstructured.Unstructured{}
err := resource.UnmarshalJSON(data)
if err != nil {
glog.V(4).Infof("failed to unmarshall resource: %v", err)
return nil, err
}
return resource, nil
}