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:
parent
a30ad6bab2
commit
373d9a45ad
6 changed files with 188 additions and 94 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue