diff --git a/controller/controller.go b/controller/controller.go index 5ec827f8a8..a4a56c36c0 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -102,9 +102,10 @@ func (c *PolicyController) deletePolicyHandler(resource interface{}) { func (c *PolicyController) getResourceKey(resource interface{}) string { if key, err := cache.MetaNamespaceKeyFunc(resource); err != nil { - c.logger.Printf("Error retrieving policy key: %v\n", err) - return "" + c.logger.Fatalf("Error retrieving policy key: %v\n", err) } else { return key } + + return "" } diff --git a/crd/selector-policy.yaml b/crd/selector-policy.yaml new file mode 100644 index 0000000000..9fdd2df055 --- /dev/null +++ b/crd/selector-policy.yaml @@ -0,0 +1,17 @@ +apiVersion: nirmata.io/v1alpha1 +kind : Policy +metadata: + name: selector-policy +spec: + failurePolicy: continueOnError + rules: + - resource: + kind: ConfigMap + selector: + matchLabels: + label1: test1 + matchExpressions: + patches: + - path: / + op : Add + value : "20" diff --git a/main.go b/main.go index 9adda990bf..ee8437553c 100644 --- a/main.go +++ b/main.go @@ -44,10 +44,10 @@ func main() { fmt.Printf("Error running PolicyController! Error: %s\n", err) } - fmt.Printf("Policy PolicyController has started") + fmt.Println("Policy Controller has started") <-stopCh server.Stop() - fmt.Printf("Policy PolicyController has stopped") + fmt.Println("Policy Controller has stopped") } func init() { diff --git a/pkg/apis/policy/v1alpha1/types.go b/pkg/apis/policy/v1alpha1/types.go index 84d6918e8a..6400d0940f 100644 --- a/pkg/apis/policy/v1alpha1/types.go +++ b/pkg/apis/policy/v1alpha1/types.go @@ -24,7 +24,7 @@ type PolicySpec struct { // PolicyRule is policy rule that will be applied to resource type PolicyRule struct { Resource PolicyResource `json:"resource"` - Patches *[]PolicyPatch `json:"patches"` + Patches []PolicyPatch `json:"patches"` ConfigMapGenerator *PolicyConfigGenerator `json:"configMapGenerator"` SecretGenerator *PolicyConfigGenerator `json:"secretGenerator"` } diff --git a/scripts/create-test-configmap.sh b/scripts/create-test-configmap.sh new file mode 100755 index 0000000000..e95960f3cd --- /dev/null +++ b/scripts/create-test-configmap.sh @@ -0,0 +1,3 @@ +cd "$(dirname "$0")" +kubectl create -f resources/test-configmap.yaml +kubectl delete -f resources/test-configmap.yaml diff --git a/scripts/resources/test-configmap.yaml b/scripts/resources/test-configmap.yaml new file mode 100644 index 0000000000..1ad9d1f5f4 --- /dev/null +++ b/scripts/resources/test-configmap.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +data: + properties: + arms=3 +kind: ConfigMap +metadata: + name: test-configmap + labels: + label1: test1 + label2: test2 diff --git a/webhooks/admission.go b/webhooks/admission.go index 698128bf87..262cb9de6b 100644 --- a/webhooks/admission.go +++ b/webhooks/admission.go @@ -1,8 +1,11 @@ package webhooks import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/api/admission/v1beta1" + "encoding/json" ) var supportedKinds = [...]string{ @@ -35,20 +38,72 @@ func kindIsSupported(kind string) bool { return false } +// AdmissionIsRequired checks for admission if kind is supported func AdmissionIsRequired(request *v1beta1.AdmissionRequest) bool { // Here you can make additional hardcoded checks return kindIsSupported(request.Kind.Kind) } -func IsRuleApplicableToRequest(rule types.PolicyRule, request *v1beta1.AdmissionRequest) bool { - return IsRuleResourceFitsRequest(rule.Resource, request) -} - -func IsRuleResourceFitsRequest(resource types.PolicyResource, request *v1beta1.AdmissionRequest) bool { - if resource.Kind != request.Kind.Kind { +// IsRuleApplicableToRequest checks requests kind, name and labels to fit the policy +func IsRuleApplicableToRequest(policyResource types.PolicyResource, request *v1beta1.AdmissionRequest) bool { + if policyResource.Selector == nil && policyResource.Name == nil { + // TODO: selector or name MUST be specified return false } - // TODO: resource.Name must be equal to request.Object.Raw -> /metadata/name + + if policyResource.Kind != request.Kind.Kind { + return false + } + + if request.Object.Raw != nil { + meta := parseMetadataFromObject(request.Object.Raw) + name := parseNameFromMetadata(meta) + + if (policyResource.Name != nil && *policyResource.Name != name) { + return false + } + + if policyResource.Selector != nil { + selector, err := metav1.LabelSelectorAsSelector(policyResource.Selector) + + if err != nil { + // TODO: log that selector is invalid + return false + } + + labelMap := parseLabelsFromMetadata(meta) + + if !selector.Matches(labelMap) { + return false + } + } + } return true } + +func parseMetadataFromObject(bytes []byte) map[string]interface{} { + var objectJSON map[string]interface{} + json.Unmarshal(bytes, &objectJSON) + + return objectJSON["metadata"].(map[string]interface{}) +} + +func parseLabelsFromMetadata(meta map[string]interface{}) labels.Set { + if interfaceMap, ok := meta["labels"].(map[string]interface{}); ok { + labelMap := make(labels.Set, len(interfaceMap)) + + for key, value := range interfaceMap { + labelMap[key] = value.(string) + } + return labelMap + } + return nil +} + +func parseNameFromMetadata(meta map[string]interface{}) string { + if name, ok := meta["name"].(string); ok { + return name + } + return "" +} \ No newline at end of file diff --git a/webhooks/admission_test.go b/webhooks/admission_test.go index dddc9eaad6..226083297f 100644 --- a/webhooks/admission_test.go +++ b/webhooks/admission_test.go @@ -50,38 +50,84 @@ func TestAdmissionIsRequired(t *testing.T) { } func TestIsRuleResourceFitsRequest_Kind(t *testing.T) { - resource := types.PolicyResource{ + resourceName := "test-config-map" + resource := types.PolicyResource { Kind: "ConfigMap", + Name: &resourceName, } - request := v1beta1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, + request := v1beta1.AdmissionRequest { + Kind: metav1.GroupVersionKind{ Kind: "ConfigMap" }, } - assertEq(t, true, webhooks.IsRuleResourceFitsRequest(resource, &request)) + objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) resource.Kind = "Deployment" - assertEq(t, false, webhooks.IsRuleResourceFitsRequest(resource, &request)) + assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) } func TestIsRuleResourceFitsRequest_Name(t *testing.T) { resourceName := "test-config-map" - resource := types.PolicyResource{ + resource := types.PolicyResource { Kind: "ConfigMap", Name: &resourceName, } request := v1beta1.AdmissionRequest{ Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, - Name: "test-config-map", } - assertEq(t, true, webhooks.IsRuleResourceFitsRequest(resource, &request)) + objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) resourceName = "test-config-map-new" - assertEq(t, false, webhooks.IsRuleResourceFitsRequest(resource, &request)) - request.Name = "test-config-map-new" - assertEq(t, true, webhooks.IsRuleResourceFitsRequest(resource, &request)) - request.Name = "" - assertEq(t, false, webhooks.IsRuleResourceFitsRequest(resource, &request)) + assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) + + objectByteArray = []byte(`{"metadata":{"name":"test-config-map-new","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + + objectByteArray = []byte(`{"metadata":{"name":"","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) } -func TestIsRuleApplicableToRequest(t *testing.T) { - // TODO -} +func TestIsRuleResourceFitsRequest_Selector(t *testing.T) { + resource := types.PolicyResource { + Kind: "ConfigMap", + Selector: &metav1.LabelSelector { + MatchLabels: map[string]string { + "label1" : "test1", + "label2" : "test2", + }, + MatchExpressions: nil, + }, + } + + request := v1beta1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, + } + + objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + + objectByteArray = []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label3":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) + + resource = types.PolicyResource { + Kind: "ConfigMap", + Selector: &metav1.LabelSelector { + MatchLabels: map[string]string { + "label3" : "test1", + "label2" : "test2", + }, + MatchExpressions: nil, + }, + } + + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + + // TODO: MatchExpressions tests should be done +} \ No newline at end of file diff --git a/webhooks/mutation.go b/webhooks/mutation.go index b8226278b2..cfe5ded759 100644 --- a/webhooks/mutation.go +++ b/webhooks/mutation.go @@ -37,7 +37,7 @@ func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest, policies [] } for ruleIdx, rule := range policy.Spec.Rules { - if IsRuleApplicableToRequest(rule, request) { + if IsRuleApplicableToRequest(rule.Resource, request) { mw.logger.Printf("Applying policy %v, rule index = %v", policy.ObjectMeta.Name, ruleIdx) rulePatches, err := mw.applyPolicyRule(request, rule) /* @@ -82,7 +82,7 @@ func (mw *MutationWebhook) applyPolicyRule(request *v1beta1.AdmissionRequest, ru } if rule.Patches != nil { - for _, patch := range *rule.Patches { + for _, patch := range rule.Patches { allPatches = append(allPatches, patch) } }