diff --git a/webhooks/patches.go b/pkg/policymanager/patches.go similarity index 85% rename from webhooks/patches.go rename to pkg/policymanager/patches.go index 8e7e09cb27..863873e732 100644 --- a/webhooks/patches.go +++ b/pkg/policymanager/patches.go @@ -1,4 +1,4 @@ -package webhooks +package policymanager import ( "encoding/json" @@ -20,6 +20,15 @@ const ( type PatchBytes []byte +func GetPolicyPatchingSets(policy types.Policy) PatchingSets { + // failurePolicy property is the only available way for now to define behavior on patching error. + // TODO: define new failurePolicy values specific for patching and other policy features. + if policy.Spec.FailurePolicy != nil && *policy.Spec.FailurePolicy == "continueOnError" { + return PatchingSetsContinueAlways + } + return PatchingSetsDefault +} + // Test patches on given document according to given sets. // Returns array from separate patches that can be applied to the document // Returns error ONLY in case when creation of resource should be denied. @@ -27,7 +36,6 @@ func ProcessPatches(patches []types.PolicyPatch, originalDocument []byte, sets P if len(originalDocument) == 0 { return nil, errors.New("Source document for patching is empty") } - var appliedPatches []PatchBytes patchedDocument := originalDocument for _, patch := range patches { diff --git a/webhooks/patches_test.go b/pkg/policymanager/patches_test.go similarity index 72% rename from webhooks/patches_test.go rename to pkg/policymanager/patches_test.go index 0c1c29d6be..40fcc14286 100644 --- a/webhooks/patches_test.go +++ b/pkg/policymanager/patches_test.go @@ -1,10 +1,9 @@ -package webhooks_test +package policymanager import ( - "gotest.tools/assert" "testing" - "github.com/nirmata/kube-policy/webhooks" + "gotest.tools/assert" types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" ) @@ -36,7 +35,7 @@ const endpointsDocument string = `{ func TestProcessPatches_EmptyPatches(t *testing.T) { var empty []types.PolicyPatch - patches, err := webhooks.ProcessPatches(empty, []byte(endpointsDocument), webhooks.PatchingSetsDefault) + patches, err := ProcessPatches(empty, []byte(endpointsDocument), PatchingSetsDefault) assert.NilError(t, err) assert.Assert(t, len(patches) == 0) } @@ -52,13 +51,13 @@ func makeAddIsMutatedLabelPatch() types.PolicyPatch { func TestProcessPatches_EmptyDocument(t *testing.T) { var patches []types.PolicyPatch patches = append(patches, makeAddIsMutatedLabelPatch()) - patchesBytes, err := webhooks.ProcessPatches(patches, nil, webhooks.PatchingSetsDefault) + patchesBytes, err := ProcessPatches(patches, nil, PatchingSetsDefault) assert.Assert(t, err != nil) assert.Assert(t, len(patchesBytes) == 0) } func TestProcessPatches_AllEmpty(t *testing.T) { - patchesBytes, err := webhooks.ProcessPatches(nil, nil, webhooks.PatchingSetsDefault) + patchesBytes, err := ProcessPatches(nil, nil, PatchingSetsDefault) assert.Assert(t, err != nil) assert.Assert(t, len(patchesBytes) == 0) } @@ -67,7 +66,7 @@ func TestProcessPatches_AddPathDoesntExist_StopOnError(t *testing.T) { patch := makeAddIsMutatedLabelPatch() patch.Path = "/metadata/additional/is-mutated" patches := []types.PolicyPatch{patch} - patchesBytes, err := webhooks.ProcessPatches(patches, []byte(endpointsDocument), webhooks.PatchingSetsStopOnError) + patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument), PatchingSetsStopOnError) assert.Assert(t, err != nil) assert.Assert(t, len(patchesBytes) == 0) } @@ -76,7 +75,7 @@ func TestProcessPatches_AddPathDoesntExist_ContinueOnError(t *testing.T) { patch := makeAddIsMutatedLabelPatch() patch.Path = "/metadata/additional/is-mutated" patches := []types.PolicyPatch{patch} - patchesBytes, err := webhooks.ProcessPatches(patches, []byte(endpointsDocument), webhooks.PatchingSetsContinueAlways) + patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument), PatchingSetsContinueAlways) assert.NilError(t, err) assert.Assert(t, len(patchesBytes) == 0) } @@ -84,7 +83,7 @@ func TestProcessPatches_AddPathDoesntExist_ContinueOnError(t *testing.T) { func TestProcessPatches_RemovePathDoesntExist_StopOnError(t *testing.T) { patch := types.PolicyPatch{Path: "/metadata/labels/is-mutated", Operation: "remove"} patches := []types.PolicyPatch{patch} - patchesBytes, err := webhooks.ProcessPatches(patches, []byte(endpointsDocument), webhooks.PatchingSetsStopOnError) + patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument), PatchingSetsStopOnError) assert.Assert(t, err != nil) assert.Assert(t, len(patchesBytes) == 0) } @@ -93,7 +92,7 @@ func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_EmptyResult(t patch1 := types.PolicyPatch{Path: "/metadata/labels/is-mutated", Operation: "remove"} patch2 := types.PolicyPatch{Path: "/spec/labels/label3", Operation: "add", Value: "label3Value"} patches := []types.PolicyPatch{patch1, patch2} - patchesBytes, err := webhooks.ProcessPatches(patches, []byte(endpointsDocument), webhooks.PatchingSetsContinueAlways) + patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument), PatchingSetsContinueAlways) assert.NilError(t, err) assert.Assert(t, len(patchesBytes) == 0) } @@ -103,7 +102,7 @@ func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResul patch2 := types.PolicyPatch{Path: "/spec/labels/label2", Operation: "remove", Value: "label2Value"} patch3 := types.PolicyPatch{Path: "/metadata/labels/label3", Operation: "add", Value: "label3Value"} patches := []types.PolicyPatch{patch1, patch2, patch3} - patchesBytes, err := webhooks.ProcessPatches(patches, []byte(endpointsDocument), webhooks.PatchingSetsContinueAlways) + patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument), PatchingSetsContinueAlways) assert.NilError(t, err) assert.Assert(t, len(patchesBytes) == 1) assertEqStringAndData(t, `{"path":"/metadata/labels/label3","op":"add","value":"label3Value"}`, patchesBytes[0]) @@ -112,7 +111,7 @@ func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResul func TestProcessPatches_RemovePathDoesntExist_IgnoreRemoveFailures_EmptyResult(t *testing.T) { patch := types.PolicyPatch{Path: "/metadata/labels/is-mutated", Operation: "remove"} patches := []types.PolicyPatch{patch} - patchesBytes, err := webhooks.ProcessPatches(patches, []byte(endpointsDocument), webhooks.PatchingSetsContinueOnRemoveFailure) + patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument), PatchingSetsContinueOnRemoveFailure) assert.NilError(t, err) assert.Assert(t, len(patchesBytes) == 0) } @@ -121,8 +120,16 @@ func TestProcessPatches_RemovePathDoesntExist_IgnoreRemoveFailures_NotEmptyResul patch1 := types.PolicyPatch{Path: "/metadata/labels/is-mutated", Operation: "remove"} patch2 := types.PolicyPatch{Path: "/metadata/labels/label2", Operation: "add", Value: "label2Value"} patches := []types.PolicyPatch{patch1, patch2} - patchesBytes, err := webhooks.ProcessPatches(patches, []byte(endpointsDocument), webhooks.PatchingSetsContinueOnRemoveFailure) + patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument), PatchingSetsContinueOnRemoveFailure) assert.NilError(t, err) assert.Assert(t, len(patchesBytes) == 1) assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, patchesBytes[0]) } + +// func TestProcessSamePatch_AddAndRemovePathsDontExist_ContinueOnError_EmptyResult(t *testing.T) { +// patch1 := types.PolicyPatch{Path: "/metadata/labels/label3", Operation: "add", Value: "label3Value"} +// patches := []types.PolicyPatch{patch1} +// patchesBytes, err := ProcessPatches(patches, []byte(endpointsDocument), PatchingSetsContinueAlways) +// assert.NilError(t, err) +// assert.Assert(t, len(patchesBytes) == 1) +// } diff --git a/webhooks/utils.go b/pkg/policymanager/utils.go similarity index 77% rename from webhooks/utils.go rename to pkg/policymanager/utils.go index 0f163514ba..3455aece1d 100644 --- a/webhooks/utils.go +++ b/pkg/policymanager/utils.go @@ -1,4 +1,4 @@ -package webhooks +package policymanager import ( "encoding/json" @@ -7,21 +7,21 @@ import ( "k8s.io/apimachinery/pkg/labels" ) -func parseMetadataFromObject(bytes []byte) map[string]interface{} { +func ParseMetadataFromObject(bytes []byte) map[string]interface{} { var objectJSON map[string]interface{} json.Unmarshal(bytes, &objectJSON) return objectJSON["metadata"].(map[string]interface{}) } -func parseKindFromObject(bytes []byte) string { +func ParseKindFromObject(bytes []byte) string { var objectJSON map[string]interface{} json.Unmarshal(bytes, &objectJSON) return objectJSON["kind"].(string) } -func parseLabelsFromMetadata(meta map[string]interface{}) labels.Set { +func ParseLabelsFromMetadata(meta map[string]interface{}) labels.Set { if interfaceMap, ok := meta["labels"].(map[string]interface{}); ok { labelMap := make(labels.Set, len(interfaceMap)) @@ -33,7 +33,7 @@ func parseLabelsFromMetadata(meta map[string]interface{}) labels.Set { return nil } -func parseNameFromObject(bytes []byte) string { +func ParseNameFromObject(bytes []byte) string { var objectJSON map[string]interface{} json.Unmarshal(bytes, &objectJSON) @@ -45,7 +45,7 @@ func parseNameFromObject(bytes []byte) string { return "" } -func parseNamespaceFromObject(bytes []byte) string { +func ParseNamespaceFromObject(bytes []byte) string { var objectJSON map[string]interface{} json.Unmarshal(bytes, &objectJSON) @@ -58,7 +58,7 @@ func parseNamespaceFromObject(bytes []byte) string { } // returns true if policyResourceName is a regexp -func parseRegexPolicyResourceName(policyResourceName string) (string, bool) { +func ParseRegexPolicyResourceName(policyResourceName string) (string, bool) { regex := strings.Split(policyResourceName, "regex:") if len(regex) == 1 { return regex[0], false diff --git a/webhooks/utils_test.go b/pkg/policymanager/utils_test.go similarity index 96% rename from webhooks/utils_test.go rename to pkg/policymanager/utils_test.go index ef000ecb08..275c8c5db6 100644 --- a/webhooks/utils_test.go +++ b/pkg/policymanager/utils_test.go @@ -1,4 +1,4 @@ -package webhooks_test +package policymanager import ( "testing" diff --git a/pkg/policymanager/validate.go b/pkg/policymanager/validate.go new file mode 100644 index 0000000000..ff5eeb0e72 --- /dev/null +++ b/pkg/policymanager/validate.go @@ -0,0 +1,43 @@ +package policymanager + +import ( + "github.com/minio/minio/pkg/wildcard" + types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// kind is the type of object being manipulated +// Checks requests kind, name and labels to fit the policy +func IsRuleApplicableToResource(kind string, resourceRaw []byte, policyResource types.PolicyResource) (bool, error) { + if policyResource.Kind != kind { + return false, nil + } + + if resourceRaw != nil { + meta := ParseMetadataFromObject(resourceRaw) + name := ParseNameFromObject(resourceRaw) + + if policyResource.Name != nil { + + if !wildcard.Match(*policyResource.Name, name) { + return false, nil + } + } + + if policyResource.Selector != nil { + selector, err := metav1.LabelSelectorAsSelector(policyResource.Selector) + + if err != nil { + return false, err + } + + labelMap := ParseLabelsFromMetadata(meta) + + if !selector.Matches(labelMap) { + return false, nil + } + + } + } + return true, nil +} diff --git a/webhooks/admission.go b/webhooks/admission.go index 652dcc3c34..aecdb3148a 100644 --- a/webhooks/admission.go +++ b/webhooks/admission.go @@ -1,11 +1,10 @@ package webhooks import ( - "github.com/minio/minio/pkg/wildcard" kubeclient "github.com/nirmata/kube-policy/kubeclient" types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + policymanager "github.com/nirmata/kube-policy/pkg/policymanager" "k8s.io/api/admission/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func kindIsSupported(kind string) bool { @@ -25,41 +24,5 @@ func AdmissionIsRequired(request *v1beta1.AdmissionRequest) bool { // Checks requests kind, name and labels to fit the policy func IsRuleApplicableToRequest(policyResource types.PolicyResource, request *v1beta1.AdmissionRequest) (bool, error) { - return IsRuleApplicableToResource(request.Kind.Kind, request.Object.Raw, policyResource) -} - -// kind is the type of object being manipulated -// Checks requests kind, name and labels to fit the policy -func IsRuleApplicableToResource(kind string, resourceRaw []byte, policyResource types.PolicyResource) (bool, error) { - if policyResource.Kind != kind { - return false, nil - } - - if resourceRaw != nil { - meta := parseMetadataFromObject(resourceRaw) - name := parseNameFromObject(resourceRaw) - - if policyResource.Name != nil { - - if !wildcard.Match(*policyResource.Name, name) { - return false, nil - } - } - - if policyResource.Selector != nil { - selector, err := metav1.LabelSelectorAsSelector(policyResource.Selector) - - if err != nil { - return false, err - } - - labelMap := parseLabelsFromMetadata(meta) - - if !selector.Matches(labelMap) { - return false, nil - } - - } - } - return true, nil + return policymanager.IsRuleApplicableToResource(request.Kind.Kind, request.Object.Raw, policyResource) } diff --git a/webhooks/mutation.go b/webhooks/mutation.go index ad651c329d..a553cbeeaa 100644 --- a/webhooks/mutation.go +++ b/webhooks/mutation.go @@ -9,6 +9,7 @@ import ( controllerinterfaces "github.com/nirmata/kube-policy/controller/interfaces" kubeclient "github.com/nirmata/kube-policy/kubeclient" types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + policymanager "github.com/nirmata/kube-policy/pkg/policymanager" v1beta1 "k8s.io/api/admission/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -65,7 +66,7 @@ func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.Ad return nil } - var allPatches []PatchBytes + var allPatches []policymanager.PatchBytes for _, policy := range policies { mw.logger.Printf("Applying policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules)) @@ -79,8 +80,8 @@ func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.Ad } if len(policyPatches) > 0 { - namespace := parseNamespaceFromObject(request.Object.Raw) - name := parseNameFromObject(request.Object.Raw) + namespace := policymanager.ParseNamespaceFromObject(request.Object.Raw) + name := policymanager.ParseNameFromObject(request.Object.Raw) mw.controller.LogPolicyInfo(policy.Name, fmt.Sprintf("Applied to %s %s/%s", request.Kind.Kind, namespace, name)) mw.logger.Printf("%s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name) @@ -91,31 +92,22 @@ func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.Ad patchType := v1beta1.PatchTypeJSONPatch return &v1beta1.AdmissionResponse{ Allowed: true, - Patch: JoinPatches(allPatches), + Patch: policymanager.JoinPatches(allPatches), PatchType: &patchType, } } -func getPolicyPatchingSets(policy types.Policy) PatchingSets { - // failurePolicy property is the only available way for now to define behavior on patching error. - // TODO: define new failurePolicy values specific for patching and other policy features. - if policy.Spec.FailurePolicy != nil && *policy.Spec.FailurePolicy == "continueOnError" { - return PatchingSetsContinueAlways - } - return PatchingSetsDefault -} - // Applies all policy rules to the created object and returns list of processed JSON patches. // May return nil patches if it is not necessary to create patches for requested object. // Returns error ONLY in case when creation of resource should be denied. -func (mw *MutationWebhook) applyPolicyRules(request *v1beta1.AdmissionRequest, policy types.Policy) ([]PatchBytes, error) { +func (mw *MutationWebhook) applyPolicyRules(request *v1beta1.AdmissionRequest, policy types.Policy) ([]policymanager.PatchBytes, error) { return mw.applyPolicyRulesOnResource(request.Kind.Kind, request.Object.Raw, policy) } // kind is the type of object being manipulated -func (mw *MutationWebhook) applyPolicyRulesOnResource(kind string, rawResource []byte, policy types.Policy) ([]PatchBytes, error) { - patchingSets := getPolicyPatchingSets(policy) - var policyPatches []PatchBytes +func (mw *MutationWebhook) applyPolicyRulesOnResource(kind string, rawResource []byte, policy types.Policy) ([]policymanager.PatchBytes, error) { + patchingSets := policymanager.GetPolicyPatchingSets(policy) + var policyPatches []policymanager.PatchBytes for ruleIdx, rule := range policy.Spec.Rules { err := rule.Validate() @@ -124,7 +116,7 @@ func (mw *MutationWebhook) applyPolicyRulesOnResource(kind string, rawResource [ continue } - if ok, err := IsRuleApplicableToResource(kind, rawResource, rule.Resource); !ok { + if ok, err := policymanager.IsRuleApplicableToResource(kind, rawResource, rule.Resource); !ok { mw.logger.Printf("Rule %d of policy %s is not applicable to the request", ruleIdx, policy.Name) return nil, err } @@ -132,12 +124,12 @@ func (mw *MutationWebhook) applyPolicyRulesOnResource(kind string, rawResource [ // configMapGenerator and secretGenerator can be applied only to namespaces if kind == "Namespace" { err = mw.applyRuleGenerators(rawResource, rule) - if err != nil && patchingSets == PatchingSetsStopOnError { + if err != nil && patchingSets == policymanager.PatchingSetsStopOnError { return nil, fmt.Errorf("Failed to apply generators from rule #%d: %s", ruleIdx, err) } } - rulePatchesProcessed, err := ProcessPatches(rule.Patches, rawResource, patchingSets) + rulePatchesProcessed, err := policymanager.ProcessPatches(rule.Patches, rawResource, patchingSets) if err != nil { return nil, fmt.Errorf("Failed to process patches from rule #%d: %s", ruleIdx, err) } @@ -160,7 +152,7 @@ func (mw *MutationWebhook) applyPolicyRulesOnResource(kind string, rawResource [ // Applies "configMapGenerator" and "secretGenerator" described in PolicyRule func (mw *MutationWebhook) applyRuleGenerators(rawResource []byte, rule types.PolicyRule) error { - namespaceName := parseNameFromObject(rawResource) + namespaceName := policymanager.ParseNameFromObject(rawResource) err := mw.applyConfigGenerator(rule.ConfigMapGenerator, namespaceName, "ConfigMap") if err == nil {