From e8de9a111ae89e3eaad72bef802a23395fdff59c Mon Sep 17 00:00:00 2001 From: shuting Date: Thu, 16 May 2019 14:09:02 -0700 Subject: [PATCH 1/4] Finished Generate() logic to actual generating the resource --- .../policy-namespace-patch-cmgCG-sgCG.yaml | 51 ++++++++++--------- kubeclient/kubeclient.go | 20 +++++--- main.go | 2 +- pkg/apis/policy/v1alpha1/types.go | 6 +-- pkg/apis/policy/v1alpha1/utils.go | 5 +- pkg/engine/generation.go | 39 ++++++++------ pkg/webhooks/server.go | 8 +++ 7 files changed, 78 insertions(+), 53 deletions(-) diff --git a/examples/ConfigMapGenerator-SecretGenerator/policy-namespace-patch-cmgCG-sgCG.yaml b/examples/ConfigMapGenerator-SecretGenerator/policy-namespace-patch-cmgCG-sgCG.yaml index 922eeee05a..a400677700 100644 --- a/examples/ConfigMapGenerator-SecretGenerator/policy-namespace-patch-cmgCG-sgCG.yaml +++ b/examples/ConfigMapGenerator-SecretGenerator/policy-namespace-patch-cmgCG-sgCG.yaml @@ -3,42 +3,47 @@ # To apply this policy you need to create secret and configMap in "default" namespace # and then create a namespace -apiVersion : policy.nirmata.io/v1alpha1 +apiVersion : kubepolicy.nirmata.io/v1alpha1 kind : Policy metadata : name : "policy-ns-patch-cmg-sg" spec : - failurePolicy: stopOnError rules: - - resource : + - name: "patchNamespace2" + resource : kind : Namespace selector: matchLabels: LabelForSelector : "namespace2" - patch: - - path: "/metadata/labels/isMutatedByPolicy" - op: add - value: "true" + mutate: + patches: + - path: "/metadata/labels/isMutatedByPolicy" + op: add + value: "true" - - resource : + - name: "copyCM" + resource : kind : Namespace selector: matchLabels: LabelForSelector : "namespace2" - configMapGenerator : + generate : + kind: ConfigMap name : copied-cm copyFrom : namespace : default name : game-config data : secretData: "data from cmg" - - - resource : + + - name: "generateCM" + resource : kind : Namespace selector: matchLabels: LabelForSelector : "namespace2" - configMapGenerator : + generate : + kind: ConfigMap name : generated-cm data : secretData: "very sensitive data from cmg" @@ -49,13 +54,12 @@ spec : image.public.key=771 rsa.public.key=42 - - resource : + - name: "generateSecret" + resource : kind : Namespace - selector: - matchLabels: - LabelForSelector : "namespace2" - - secretGenerator : + name: ns2 + generate : + kind: Secret name : generated-secrets data : foo : bar @@ -66,13 +70,12 @@ spec : foo1=bar1 foo2=bar2 - - resource : + - name: "copySecret" + resource : kind : Namespace - selector: - matchLabels: - LabelForSelector : "namespace2" - - secretGenerator : + name: ns2 + generate : + kind: Secret name : copied-secrets copyFrom : namespace : default diff --git a/kubeclient/kubeclient.go b/kubeclient/kubeclient.go index f32d6950b6..98797907bc 100644 --- a/kubeclient/kubeclient.go +++ b/kubeclient/kubeclient.go @@ -67,10 +67,12 @@ func (kc *KubeClient) GenerateConfigMap(generator types.Generation, namespace st var err error - kc.logger.Printf("Copying data from configmap %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name) - configMap, err = kc.client.CoreV1().ConfigMaps(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{}) - if err != nil { - return err + if generator.CopyFrom != nil { + kc.logger.Printf("Copying data from configmap %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name) + configMap, err = kc.client.CoreV1().ConfigMaps(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{}) + if err != nil { + return err + } } configMap.ObjectMeta = metav1.ObjectMeta{ @@ -101,10 +103,12 @@ func (kc *KubeClient) GenerateSecret(generator types.Generation, namespace strin var err error - kc.logger.Printf("Copying data from secret %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name) - secret, err = kc.client.CoreV1().Secrets(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{}) - if err != nil { - return err + if generator.CopyFrom != nil { + kc.logger.Printf("Copying data from secret %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name) + secret, err = kc.client.CoreV1().Secrets(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{}) + if err != nil { + return err + } } secret.ObjectMeta = metav1.ObjectMeta{ diff --git a/main.go b/main.go index abf9b47711..9bd39c21a5 100644 --- a/main.go +++ b/main.go @@ -59,7 +59,7 @@ func main() { log.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err) } - server, err := webhooks.NewWebhookServer(tlsPair, policyInformer.Lister(), nil) + server, err := webhooks.NewWebhookServer(tlsPair, policyInformer.Lister(), kubeclient, nil) if err != nil { log.Fatalf("Unable to create webhook server: %v\n", err) } diff --git a/pkg/apis/policy/v1alpha1/types.go b/pkg/apis/policy/v1alpha1/types.go index dd7d8cd43d..0bc0699eef 100644 --- a/pkg/apis/policy/v1alpha1/types.go +++ b/pkg/apis/policy/v1alpha1/types.go @@ -60,9 +60,9 @@ type Validation struct { // Generation describes which resources will be created when other resource is created type Generation struct { - Kind string `json:"kind"` - Name string `json:"name"` - CopyFrom `json:"copyFrom"` + Kind string `json:"kind"` + Name string `json:"name"` + CopyFrom *CopyFrom `json:"copyFrom,omitempty"` Data map[string]string `json:"data"` Labels map[string]string `json:"labels"` } diff --git a/pkg/apis/policy/v1alpha1/utils.go b/pkg/apis/policy/v1alpha1/utils.go index 653513a232..dd0374bb26 100644 --- a/pkg/apis/policy/v1alpha1/utils.go +++ b/pkg/apis/policy/v1alpha1/utils.go @@ -78,7 +78,10 @@ func (pcg *Generation) Validate() error { return errors.New("Name or/and Kind of generator is not specified") } - return pcg.CopyFrom.Validate() + if pcg.CopyFrom != nil { + return pcg.CopyFrom.Validate() + } + return nil } // DeepCopyInto is declared because k8s:deepcopy-gen is diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index e5cb259942..11f70ae346 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -4,6 +4,7 @@ import ( "fmt" "log" + kubeClient "github.com/nirmata/kube-policy/kubeclient" kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" "github.com/nirmata/kube-policy/pkg/engine/mutation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -16,14 +17,12 @@ type GenerationResponse struct { // Generate should be called to process generate rules on the resource // TODO: extend kubeclient(will change to dynamic client) to create resources -func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) []GenerationResponse { +func Generate(policy kubepolicy.Policy, rawResource []byte, kubeClient *kubeClient.KubeClient, gvk metav1.GroupVersionKind) { // configMapGenerator and secretGenerator can be applied only to namespaces if gvk.Kind != "Namespace" { - return nil + return } - var generateResps []GenerationResponse - for i, rule := range policy.Spec.Rules { // Checks for preconditions @@ -48,31 +47,39 @@ func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers continue } - generateResps, err = applyRuleGenerator(rawResource, rule.Generation) + err = applyRuleGenerator(rawResource, rule.Generation, kubeClient) if err != nil { log.Printf("Failed to apply rule generator: %v", err) - } else { - generateResps = append(generateResps, generateResps...) } } - - return generateResps } // Applies "configMapGenerator" and "secretGenerator" described in PolicyRule // TODO: plan to support all kinds of generator -func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation) ([]GenerationResponse, error) { - var generateResps []GenerationResponse +func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation, kubeClient *kubeClient.KubeClient) error { if generator == nil { - return nil, nil + return nil } err := generator.Validate() if err != nil { - return nil, fmt.Errorf("Generator for '%s' is invalid: %s", generator.Kind, err) + return fmt.Errorf("Generator for '%s/%s' is invalid: %s", generator.Kind, generator.Name, err) } - namespaceName := mutation.ParseNameFromObject(rawResource) - generateResps = append(generateResps, GenerationResponse{generator, namespaceName}) - return generateResps, nil + namespace := mutation.ParseNameFromObject(rawResource) + switch generator.Kind { + case "ConfigMap": + err = kubeClient.GenerateConfigMap(*generator, namespace) + case "Secret": + err = kubeClient.GenerateSecret(*generator, namespace) + default: + err = fmt.Errorf("Unsupported config Kind '%s'", generator.Kind) + } + + if err != nil { + return fmt.Errorf("Unable to apply generator for %s '%s/%s' : %v", generator.Kind, namespace, generator.Name, err) + } + + log.Printf("Successfully applied generator %s/%s", generator.Kind, generator.Name) + return nil } diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index b74a6a36e8..807f6bc23b 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -13,6 +13,7 @@ import ( "time" "github.com/nirmata/kube-policy/config" + kubeClient "github.com/nirmata/kube-policy/kubeclient" policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1" engine "github.com/nirmata/kube-policy/pkg/engine" "github.com/nirmata/kube-policy/pkg/engine/mutation" @@ -27,6 +28,7 @@ import ( type WebhookServer struct { server http.Server policyLister policylister.PolicyLister + kubeClient *kubeClient.KubeClient logger *log.Logger } @@ -35,6 +37,7 @@ type WebhookServer struct { func NewWebhookServer( tlsPair *tlsutils.TlsPemPair, policyLister policylister.PolicyLister, + kubeClient *kubeClient.KubeClient, logger *log.Logger) (*WebhookServer, error) { if logger == nil { logger = log.New(os.Stdout, "Webhook Server: ", log.LstdFlags) @@ -53,6 +56,7 @@ func NewWebhookServer( ws := &WebhookServer{ policyLister: policyLister, + kubeClient: kubeClient, logger: logger, } @@ -174,6 +178,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1 allowed := true for _, policy := range policies { + // validation ws.logger.Printf("Validating resource with policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules)) if ok := engine.Validate(*policy, request.Object.Raw, request.Kind); !ok { @@ -183,6 +188,9 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1 } else { ws.logger.Println("Validation is successful") } + + // generation + engine.Generate(*policy, request.Object.Raw, ws.kubeClient, request.Kind) } return &v1beta1.AdmissionResponse{ From 36f76a0f2fe8f701ea24cc4beea8ba5118bed394 Mon Sep 17 00:00:00 2001 From: shuting Date: Thu, 16 May 2019 17:19:38 -0700 Subject: [PATCH 2/4] - Correct crd yaml, since we only allow 1 generation per rule. - update example for generator --- definitions/install.yaml | 57 +++++++++---------- .../policy-cm-test.yaml | 20 ++++--- pkg/apis/policy/v1alpha1/types.go | 2 +- pkg/apis/policy/v1alpha1/utils.go | 16 +----- 4 files changed, 41 insertions(+), 54 deletions(-) diff --git a/definitions/install.yaml b/definitions/install.yaml index 5884555bbc..f23e12fad3 100644 --- a/definitions/install.yaml +++ b/definitions/install.yaml @@ -29,14 +29,14 @@ spec: required: - name - resource - parameters: + properties: name: type: string resource: type: object required: - kind - parameters: + properties: kind: type: string enum: @@ -115,36 +115,33 @@ spec: pattern: AnyValue: {} generate: - type: array - items: - type: object - required: - - kind - - name - - copyFrom - properties: - kind: - type: string - name: - type: string - copyFrom: - type: object - required: - - namespace - - name - properties: - namespace: - type: string - name: - type: string - data: - type: object - additionalProperties: + type: object + required: + - kind + - name + properties: + kind: + type: string + name: + type: string + copyFrom: + type: object + required: + - namespace + - name + properties: + namespace: type: string - labels: - type: object - additionalProperties: + name: type: string + data: + type: object + additionalProperties: + type: string + labels: + type: object + additionalProperties: + type: string --- apiVersion: v1 kind: Service diff --git a/examples/ConfigMapGenerator-SecretGenerator/policy-cm-test.yaml b/examples/ConfigMapGenerator-SecretGenerator/policy-cm-test.yaml index 9e1056611b..3a0ce26477 100644 --- a/examples/ConfigMapGenerator-SecretGenerator/policy-cm-test.yaml +++ b/examples/ConfigMapGenerator-SecretGenerator/policy-cm-test.yaml @@ -4,15 +4,17 @@ metadata : name: "policy-configmapgenerator-test" spec: rules: - - name: "Policy ConfigMap sample rule" - resource: + - name: "copyCM" + resource : kind : Namespace - name: "ns2" - generate: + selector: + matchLabels: + LabelForSelector : "namespace2" + generate : kind: ConfigMap - name: copied-cm - copyFrom: - namespace: default - name: game-config - data: + name : copied-cm + copyFrom : + namespace : default + name : game-config + data : secretData: "data from cmg" \ No newline at end of file diff --git a/pkg/apis/policy/v1alpha1/types.go b/pkg/apis/policy/v1alpha1/types.go index 0bc0699eef..f167a8a09a 100644 --- a/pkg/apis/policy/v1alpha1/types.go +++ b/pkg/apis/policy/v1alpha1/types.go @@ -62,7 +62,7 @@ type Validation struct { type Generation struct { Kind string `json:"kind"` Name string `json:"name"` - CopyFrom *CopyFrom `json:"copyFrom,omitempty"` + CopyFrom *CopyFrom `json:"copyFrom"` Data map[string]string `json:"data"` Labels map[string]string `json:"labels"` } diff --git a/pkg/apis/policy/v1alpha1/utils.go b/pkg/apis/policy/v1alpha1/utils.go index dd0374bb26..65719ba360 100644 --- a/pkg/apis/policy/v1alpha1/utils.go +++ b/pkg/apis/policy/v1alpha1/utils.go @@ -64,22 +64,10 @@ func (pp *Patch) Validate() error { return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation) } -// Validate returns error if Name or namespace is not cpecified -func (pcf *CopyFrom) Validate() error { - if pcf.Name == "" || pcf.Namespace == "" { - return errors.New("Name or/and Namespace is not specified") - } - return nil -} - // Validate returns error if generator is configured incompletely func (pcg *Generation) Validate() error { - if pcg.Name == "" || pcg.Kind == "" { - return errors.New("Name or/and Kind of generator is not specified") - } - - if pcg.CopyFrom != nil { - return pcg.CopyFrom.Validate() + if len(pcg.Data) == 0 && pcg.CopyFrom == nil { + return fmt.Errorf("Neither Data nor CopyFrom (source) of %s/%s is specified", pcg.Kind, pcg.Name) } return nil } From e878c8bc1e595c2fef76f52a385637e5b4a197b5 Mon Sep 17 00:00:00 2001 From: shuting Date: Fri, 17 May 2019 11:15:30 -0700 Subject: [PATCH 3/4] move config to pkg/config --- init.go | 2 +- kubeclient/kubeclient.go | 2 +- {config => pkg/config}/config.go | 0 pkg/webhooks/registration.go | 2 +- pkg/webhooks/server.go | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename {config => pkg/config}/config.go (100%) diff --git a/init.go b/init.go index edb1f64f53..28f747e863 100644 --- a/init.go +++ b/init.go @@ -5,8 +5,8 @@ import ( "log" "net/url" - "github.com/nirmata/kube-policy/config" "github.com/nirmata/kube-policy/kubeclient" + "github.com/nirmata/kube-policy/pkg/config" "github.com/nirmata/kube-policy/pkg/tls" rest "k8s.io/client-go/rest" diff --git a/kubeclient/kubeclient.go b/kubeclient/kubeclient.go index 98797907bc..b259f911b1 100644 --- a/kubeclient/kubeclient.go +++ b/kubeclient/kubeclient.go @@ -6,8 +6,8 @@ import ( "os" "time" - "github.com/nirmata/kube-policy/config" types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + "github.com/nirmata/kube-policy/pkg/config" apps "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/config/config.go b/pkg/config/config.go similarity index 100% rename from config/config.go rename to pkg/config/config.go diff --git a/pkg/webhooks/registration.go b/pkg/webhooks/registration.go index 6fe8a64f2b..d917c36a8c 100644 --- a/pkg/webhooks/registration.go +++ b/pkg/webhooks/registration.go @@ -4,8 +4,8 @@ import ( "errors" "io/ioutil" - "github.com/nirmata/kube-policy/config" kubeclient "github.com/nirmata/kube-policy/kubeclient" + "github.com/nirmata/kube-policy/pkg/config" admregapi "k8s.io/api/admissionregistration/v1beta1" meta "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index 4cd34aa1c0..59735f1c2e 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -12,9 +12,9 @@ import ( "os" "time" - "github.com/nirmata/kube-policy/config" kubeClient "github.com/nirmata/kube-policy/kubeclient" policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1" + "github.com/nirmata/kube-policy/pkg/config" engine "github.com/nirmata/kube-policy/pkg/engine" "github.com/nirmata/kube-policy/pkg/engine/mutation" tlsutils "github.com/nirmata/kube-policy/pkg/tls" From 47916acc63a0a117cd4bb67571078975665e4906 Mon Sep 17 00:00:00 2001 From: shuting Date: Fri, 17 May 2019 11:36:58 -0700 Subject: [PATCH 4/4] move test utils to patches_test.go --- pkg/engine/mutation/patches_test.go | 21 +++++++++++++++++++++ pkg/engine/mutation/utils_test.go | 26 -------------------------- 2 files changed, 21 insertions(+), 26 deletions(-) delete mode 100644 pkg/engine/mutation/utils_test.go diff --git a/pkg/engine/mutation/patches_test.go b/pkg/engine/mutation/patches_test.go index 60ee4d2835..825a5723cb 100644 --- a/pkg/engine/mutation/patches_test.go +++ b/pkg/engine/mutation/patches_test.go @@ -116,3 +116,24 @@ func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) { assert.Assert(t, len(patchesBytes) == 1) assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, patchesBytes[0]) } + +func assertEqDataImpl(t *testing.T, expected, actual []byte, formatModifier string) { + if len(expected) != len(actual) { + t.Errorf("len(expected) != len(actual): %d != %d\n1:"+formatModifier+"\n2:"+formatModifier, len(expected), len(actual), expected, actual) + return + } + + for idx, val := range actual { + if val != expected[idx] { + t.Errorf("Slices not equal at index %d:\n1:"+formatModifier+"\n2:"+formatModifier, idx, expected, actual) + } + } +} + +func assertEqData(t *testing.T, expected, actual []byte) { + assertEqDataImpl(t, expected, actual, "%x") +} + +func assertEqStringAndData(t *testing.T, str string, data []byte) { + assertEqDataImpl(t, []byte(str), data, "%s") +} diff --git a/pkg/engine/mutation/utils_test.go b/pkg/engine/mutation/utils_test.go deleted file mode 100644 index f8473ae287..0000000000 --- a/pkg/engine/mutation/utils_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package mutation - -import ( - "testing" -) - -func assertEqDataImpl(t *testing.T, expected, actual []byte, formatModifier string) { - if len(expected) != len(actual) { - t.Errorf("len(expected) != len(actual): %d != %d\n1:"+formatModifier+"\n2:"+formatModifier, len(expected), len(actual), expected, actual) - return - } - - for idx, val := range actual { - if val != expected[idx] { - t.Errorf("Slices not equal at index %d:\n1:"+formatModifier+"\n2:"+formatModifier, idx, expected, actual) - } - } -} - -func assertEqData(t *testing.T, expected, actual []byte) { - assertEqDataImpl(t, expected, actual, "%x") -} - -func assertEqStringAndData(t *testing.T, str string, data []byte) { - assertEqDataImpl(t, []byte(str), data, "%s") -}