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/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/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 f32d6950b6..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" @@ -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..f167a8a09a 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"` 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..65719ba360 100644 --- a/pkg/apis/policy/v1alpha1/utils.go +++ b/pkg/apis/policy/v1alpha1/utils.go @@ -64,21 +64,12 @@ 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 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 pcg.CopyFrom.Validate() + return nil } // DeepCopyInto is declared because k8s:deepcopy-gen is 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/engine/generation.go b/pkg/engine/generation.go index 9785487347..77366d3f2d 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -1,8 +1,10 @@ package engine import ( + "fmt" "log" + kubeClient "github.com/nirmata/kube-policy/kubeclient" kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -14,14 +16,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 @@ -46,26 +46,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 generationResponse []GenerationResponse +func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation, kubeClient *kubeClient.KubeClient) error { if generator == nil { - return nil, nil + return nil } - namespaceName := ParseNameFromObject(rawResource) - generationResponse = append(generationResponse, GenerationResponse{generator, namespaceName}) - return generationResponse, nil + err := generator.Validate() + if err != nil { + return fmt.Errorf("Generator for '%s/%s' is invalid: %s", generator.Kind, generator.Name, err) + } + + namespace := 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/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/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 61a6e5cba5..59735f1c2e 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -12,8 +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" @@ -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{