diff --git a/client/certificates.go b/client/certificates.go index 28a1c12111..0549eef489 100644 --- a/client/certificates.go +++ b/client/certificates.go @@ -48,7 +48,7 @@ func (c *Client) submitAndApproveCertificateRequest(req *certificates.Certificat return nil, err } // certClient := kc.client.CertificatesV1beta1().CertificateSigningRequests() - csrList, err := c.ListResource("certificatesigningrequest", "") + csrList, err := c.ListResource("certificatesigningrequests", "") // csrList, err := certClient.List(metav1.ListOptions{}) if err != nil { return nil, errors.New(fmt.Sprintf("Unable to list existing certificate requests: %v", err)) @@ -58,7 +58,7 @@ func (c *Client) submitAndApproveCertificateRequest(req *certificates.Certificat csr.GetName() if csr.GetName() == req.ObjectMeta.Name { // Delete - err := c.DeleteResouce("certificatesigningrequest", "", csr.GetName()) + err := c.DeleteResouce("certificatesigningrequests", "", csr.GetName()) if err != nil { return nil, errors.New(fmt.Sprintf("Unable to delete existing certificate request: %v", err)) } @@ -68,7 +68,7 @@ func (c *Client) submitAndApproveCertificateRequest(req *certificates.Certificat } // Create - unstrRes, err := c.CreateResource("certificatesigningrequest", "", req) + unstrRes, err := c.CreateResource("certificatesigningrequests", "", req) // res, err := certClient.Create(req) if err != nil { return nil, err @@ -99,9 +99,9 @@ const certificateFetchWaitInterval time.Duration = 200 * time.Millisecond func (c *Client) fetchCertificateFromRequest(req *certificates.CertificateSigningRequest, maxWaitSeconds uint8) ([]byte, error) { // TODO: react of SIGINT and SIGTERM timeStart := time.Now() - c.GetResource("certificatesigningrequest", "", req.ObjectMeta.Name) + c.GetResource("certificatesigningrequests", "", req.ObjectMeta.Name) for time.Now().Sub(timeStart) < time.Duration(maxWaitSeconds)*time.Second { - unstrR, err := c.GetResource("certificatesigningrequest", "", req.ObjectMeta.Name) + unstrR, err := c.GetResource("certificatesigningrequests", "", req.ObjectMeta.Name) if err != nil { return nil, err } diff --git a/client/client.go b/client/client.go index 721ae55fb4..2bd584619f 100644 --- a/client/client.go +++ b/client/client.go @@ -3,10 +3,11 @@ package client import ( "fmt" "log" + "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" certificates "k8s.io/api/certificates/v1beta1" v1 "k8s.io/api/core/v1" @@ -32,6 +33,11 @@ func NewDynamicClient(config *rest.Config, logger *log.Logger) (*Client, error) if err != nil { return nil, err } + + if logger == nil { + logger = log.New(os.Stdout, "Client : ", log.LstdFlags) + } + return &Client{ logger: logger, client: client, @@ -39,6 +45,10 @@ func NewDynamicClient(config *rest.Config, logger *log.Logger) (*Client, error) }, nil } +func (c *Client) Test() { + +} + func (c *Client) GetKubePolicyDeployment() (*apps.Deployment, error) { kubePolicyDeployment, err := c.GetResource("deployments", config.KubePolicyNamespace, config.KubePolicyDeploymentName) if err != nil { @@ -172,7 +182,7 @@ func (c *Client) GenerateSecret(generator types.Generation, namespace string) er // if generator.CopyFrom != nil { c.logger.Printf("Copying data from secret %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name) // Get configMap resource - unstrSecret, err := c.GetResource("secret", generator.CopyFrom.Namespace, generator.CopyFrom.Name) + unstrSecret, err := c.GetResource("secrets", generator.CopyFrom.Namespace, generator.CopyFrom.Name) if err != nil { return err } @@ -212,7 +222,7 @@ func (c *Client) GenerateConfigMap(generator types.Generation, namespace string) // if generator.CopyFrom != nil { c.logger.Printf("Copying data from configmap %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name) // Get configMap resource - unstrConfigMap, err := c.GetResource("configmap", generator.CopyFrom.Namespace, generator.CopyFrom.Name) + unstrConfigMap, err := c.GetResource("configmaps", generator.CopyFrom.Namespace, generator.CopyFrom.Name) if err != nil { return err } @@ -269,7 +279,7 @@ func convertToCSR(obj *unstructured.Unstructured) (*certificates.CertificateSign func (c *Client) createConfigMapAfterNamespaceIsCreated(configMap v1.ConfigMap, namespace string) { err := c.waitUntilNamespaceIsCreated(namespace) if err == nil { - _, err = c.CreateResource("configmap", namespace, configMap) + _, err = c.CreateResource("configmaps", namespace, configMap) } if err != nil { c.logger.Printf("Can't create a configmap: %s", err) @@ -292,7 +302,7 @@ func (c *Client) waitUntilNamespaceIsCreated(name string) error { var lastError error = nil for time.Now().Sub(timeStart) < namespaceCreationMaxWaitTime { - _, lastError = c.GetResource("namespace", "", name) + _, lastError = c.GetResource("namespaces", "", name) if lastError == nil { break } diff --git a/client/utils.go b/client/utils.go index 63a5f46a41..8c52c2e14e 100644 --- a/client/utils.go +++ b/client/utils.go @@ -36,6 +36,7 @@ func getGrpVersionMapper(kind string, clientConfig *rest.Config, refresh bool) s } func getValue(kind string) (*schema.GroupVersionResource, bool) { + if val, ok := groupVersionMapper[kind]; ok { return &val, true } @@ -48,12 +49,12 @@ func refreshRegisteredResources(mapper map[string]schema.GroupVersionResource, c if err != nil { return err } + // get registered server groups and resources _, resourceList, err := client.Discovery().ServerGroupsAndResources() if err != nil { return err } - for _, apiResource := range resourceList { for _, resource := range apiResource.APIResources { grpVersion := strings.Split(apiResource.GroupVersion, "/") @@ -63,6 +64,12 @@ func refreshRegisteredResources(mapper map[string]schema.GroupVersionResource, c Version: grpVersion[1], Resource: resource.Name, } + } else { + // resources with only versions + mapper[resource.Name] = schema.GroupVersionResource{ + Version: apiResource.GroupVersion, + Resource: resource.Name, + } } } } diff --git a/init.go b/init.go index b6553dc3c3..236d172ac9 100644 --- a/init.go +++ b/init.go @@ -6,7 +6,7 @@ import ( "net/url" client "github.com/nirmata/kube-policy/client" - "github.com/nirmata/kube-policy/config" + "github.com/nirmata/kube-policy/pkg/config" tls "github.com/nirmata/kube-policy/pkg/tls" rest "k8s.io/client-go/rest" diff --git a/main.go b/main.go index cdbdb32587..850f1a1655 100644 --- a/main.go +++ b/main.go @@ -4,14 +4,13 @@ import ( "flag" "log" - "k8s.io/sample-controller/pkg/signals" - client "github.com/nirmata/kube-policy/client" controller "github.com/nirmata/kube-policy/pkg/controller" event "github.com/nirmata/kube-policy/pkg/event" "github.com/nirmata/kube-policy/pkg/sharedinformer" "github.com/nirmata/kube-policy/pkg/violation" "github.com/nirmata/kube-policy/pkg/webhooks" + "k8s.io/sample-controller/pkg/signals" ) var ( @@ -23,6 +22,7 @@ var ( func main() { clientConfig, err := createClientConfig(kubeconfig) if err != nil { + log.Fatalf("Error building kubeconfig: %v\n", err) } @@ -30,6 +30,7 @@ func main() { if err != nil { log.Fatalf("Error creating client: %v\n", err) } + // client.Test() policyInformerFactory, err := sharedinformer.NewSharedInformerFactory(clientConfig) if err != nil { @@ -50,7 +51,7 @@ func main() { log.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err) } - server, err := webhooks.NewWebhookServer(tlsPair, policyInformerFactory, nil) + server, err := webhooks.NewWebhookServer(client, tlsPair, policyInformerFactory, 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..0e4374494c 100644 --- a/pkg/apis/policy/v1alpha1/types.go +++ b/pkg/apis/policy/v1alpha1/types.go @@ -5,6 +5,7 @@ import ( ) // +genclient +// +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Policy contains rules to be applied to created resources @@ -60,9 +61,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/controller/controller.go b/pkg/controller/controller.go index f29659ae2c..71660ac779 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -161,14 +161,15 @@ func (pc *PolicyController) syncHandler(obj interface{}) error { return fmt.Errorf("expected string in workqueue but got %#v", obj) } // convert the namespace/name string into distinct namespace and name - namespace, name, err := cache.SplitMetaNamespaceKey(key) + //TODO: currently policies are clustered, but the above code is to support namespaced as well + _, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { utilruntime.HandleError(fmt.Errorf("invalid policy key: %s", key)) return nil } // Get Policy resource with namespace/name - policy, err := pc.policyLister.Policies(namespace).Get(name) + policy, err := pc.policyLister.Get(name) if err != nil { if errors.IsNotFound(err) { utilruntime.HandleError(fmt.Errorf("policy '%s' in work queue no longer exists", key)) @@ -180,6 +181,6 @@ func (pc *PolicyController) syncHandler(obj interface{}) error { // get the violations and pass to violation Builder // get the events and pass to event Builder //TODO: processPolicy - fmt.Println(policy) + pc.logger.Printf("process policy %s on existing resources", policy.GetName()) return nil } diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index 01e3aa00ed..4a5d45f180 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -1,7 +1,6 @@ package engine import ( - "errors" "fmt" "log" @@ -56,18 +55,23 @@ func applyRuleGenerator(client *client.Client, rawResource []byte, generator *ku err := generator.Validate() if err != nil { - return 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 := ParseNameFromObject(rawResource) - // Generate the resource - switch gvk.Kind { - case "configmap": - err = client.GenerateConfigMap(*generator, namespaceName) - case "secret": - err = client.GenerateSecret(*generator, namespaceName) - case "default": - err = errors.New("resource not supported") + namespace := ParseNameFromObject(rawResource) + switch generator.Kind { + case "ConfigMap": + err = client.GenerateConfigMap(*generator, namespace) + case "Secret": + err = client.GenerateSecret(*generator, namespace) + default: + err = fmt.Errorf("Unsupported config Kind '%s'", generator.Kind) } - return err + + 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/event/controller.go b/pkg/event/controller.go index d291afe734..e0f011ef0e 100644 --- a/pkg/event/controller.go +++ b/pkg/event/controller.go @@ -132,21 +132,21 @@ func (c *controller) processNextWorkItem() bool { func (c *controller) SyncHandler(key Info) error { var resource runtime.Object var err error - namespace, name, err := cache.SplitMetaNamespaceKey(key.Resource) - if err != nil { - utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key.Resource)) - return err - } switch key.Kind { case "Policy": //TODO: policy is clustered resource so wont need namespace - resource, err = c.policyLister.Policies(namespace).Get(name) + resource, err = c.policyLister.Get(key.Resource) if err != nil { utilruntime.HandleError(fmt.Errorf("unable to create event for policy %s, will retry ", key.Resource)) return err } default: + namespace, name, err := cache.SplitMetaNamespaceKey(key.Resource) + if err != nil { + utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key.Resource)) + return err + } resource, err = c.client.GetResource(key.Kind, namespace, name) if err != nil { return err diff --git a/pkg/violation/builder.go b/pkg/violation/builder.go index 35209f49c3..283027ff2e 100644 --- a/pkg/violation/builder.go +++ b/pkg/violation/builder.go @@ -63,7 +63,7 @@ func (b *builder) processViolation(info Info) error { utilruntime.HandleError(fmt.Errorf("unable to extract namespace and name for %s", info.Policy)) return err } - policy, err := b.policyLister.Policies(namespace).Get(name) + policy, err := b.policyLister.Get(name) if err != nil { utilruntime.HandleError(err) return err @@ -90,7 +90,7 @@ func (b *builder) processViolation(info Info) error { modifiedPolicy.Status.Violations = modifiedViolations // Violations are part of the status sub resource, so we can use the Update Status api instead of updating the policy object - _, err = b.client.UpdateStatusResource("policy", namespace, modifiedPolicy) + _, err = b.client.UpdateStatusResource("policies", namespace, modifiedPolicy) if err != nil { return err } diff --git a/pkg/webhooks/registration.go b/pkg/webhooks/registration.go index c36c448b88..38b3fc441e 100644 --- a/pkg/webhooks/registration.go +++ b/pkg/webhooks/registration.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "github.com/nirmata/kube-policy/client" - "github.com/nirmata/kube-policy/config" + "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 8af8b951d0..c9a453c467 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -12,8 +12,9 @@ import ( "os" "time" - "github.com/nirmata/kube-policy/config" + "github.com/nirmata/kube-policy/client" "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" "github.com/nirmata/kube-policy/pkg/sharedinformer" @@ -27,6 +28,7 @@ import ( // MutationWebhook gets policies from policyController and takes control of the cluster with kubeclient. type WebhookServer struct { server http.Server + client *client.Client policyLister v1alpha1.PolicyLister logger *log.Logger } @@ -34,6 +36,7 @@ type WebhookServer struct { // NewWebhookServer creates new instance of WebhookServer accordingly to given configuration // Policy Controller and Kubernetes Client should be initialized in configuration func NewWebhookServer( + client *client.Client, tlsPair *tlsutils.TlsPemPair, shareInformer sharedinformer.PolicyInformer, logger *log.Logger) (*WebhookServer, error) { @@ -53,6 +56,7 @@ func NewWebhookServer( tlsConfig.Certificates = []tls.Certificate{pair} ws := &WebhookServer{ + client: client, policyLister: shareInformer.GetLister(), logger: logger, } @@ -175,6 +179,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 { @@ -184,6 +189,9 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1 } else { ws.logger.Println("Validation is successful") } + + // generation + engine.Generate(ws.client, ws.logger, *policy, request.Object.Raw, request.Kind) } return &v1beta1.AdmissionResponse{