From b60ec942675a035587c869676899fd9b4ce4c1f9 Mon Sep 17 00:00:00 2001 From: Maxim Goncharenko Date: Mon, 13 May 2019 21:27:47 +0300 Subject: [PATCH] Updated code in the project to be compilable with new version of Policy. Moved logic from webhooks/mutation.go to policyengine/mutation.go and server.go --- kubeclient/certificates.go | 6 +- kubeclient/kubeclient.go | 44 ++--- main.go | 11 +- pkg/policyengine/generation.go | 54 ++++++ pkg/policyengine/mutation.go | 113 ++++------- pkg/policyengine/mutation/checkRules.go | 18 +- pkg/policyengine/mutation/overlay.go | 6 + pkg/policyengine/mutation/patches.go | 61 ++---- pkg/policyengine/policyengine.go | 16 +- pkg/policyviolation/builder.go | 2 +- policycontroller/processPolicy.go | 6 +- server/server.go | 86 ++++++++- webhooks/admission.go | 2 +- webhooks/mutation.go | 239 ------------------------ 14 files changed, 237 insertions(+), 427 deletions(-) create mode 100644 pkg/policyengine/generation.go create mode 100644 pkg/policyengine/mutation/overlay.go delete mode 100644 webhooks/mutation.go diff --git a/kubeclient/certificates.go b/kubeclient/certificates.go index 199111ab63..2c5666cc68 100644 --- a/kubeclient/certificates.go +++ b/kubeclient/certificates.go @@ -89,7 +89,7 @@ func (kc *KubeClient) fetchCertificateFromRequest(req *certificates.CertificateS timeStart := time.Now() certClient := kc.client.CertificatesV1beta1().CertificateSigningRequests() for time.Now().Sub(timeStart) < time.Duration(maxWaitSeconds)*time.Second { - r, err := certClient.Get(req.ObjectMeta.Name, defaultGetOptions()) + r, err := certClient.Get(req.ObjectMeta.Name, metav1.GetOptions{}) if err != nil { return nil, err } @@ -113,7 +113,7 @@ const certificateField string = "certificate" // Reads the pair of TLS certificate and key from the specified secret. func (kc *KubeClient) ReadTlsPair(props utils.TlsCertificateProps) *utils.TlsPemPair { name := generateSecretName(props) - secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, defaultGetOptions()) + secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, metav1.GetOptions{}) if err != nil { kc.logger.Printf("Unable to get secret %s/%s: %s", props.Namespace, name, err) return nil @@ -138,7 +138,7 @@ func (kc *KubeClient) ReadTlsPair(props utils.TlsCertificateProps) *utils.TlsPem // Updates existing secret or creates new one. func (kc *KubeClient) WriteTlsPair(props utils.TlsCertificateProps, pemPair *utils.TlsPemPair) error { name := generateSecretName(props) - secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, defaultGetOptions()) + secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, metav1.GetOptions{}) if err == nil { // Update existing secret if secret.Data == nil { diff --git a/kubeclient/kubeclient.go b/kubeclient/kubeclient.go index 2edc317900..eca5f406b6 100644 --- a/kubeclient/kubeclient.go +++ b/kubeclient/kubeclient.go @@ -22,8 +22,8 @@ import ( // KubeClient is the api-client for core Kubernetes objects type KubeClient struct { - logger *log.Logger client *kubernetes.Clientset + logger *log.Logger } // Checks parameters and creates new instance of KubeClient @@ -49,12 +49,9 @@ func (kc *KubeClient) GetEventsInterface(namespace string) event.EventInterface func (kc *KubeClient) GetKubePolicyDeployment() (*apps.Deployment, error) { kubePolicyDeployment, err := kc.client. - Apps(). + AppsV1(). Deployments(config.KubePolicyNamespace). - Get(config.KubePolicyDeploymentName, meta.GetOptions{ - ResourceVersion: "1", - IncludeUninitialized: false, - }) + Get(config.KubePolicyDeploymentName, meta.GetOptions{}) if err != nil { return nil, err @@ -65,17 +62,16 @@ func (kc *KubeClient) GetKubePolicyDeployment() (*apps.Deployment, error) { // Generates new ConfigMap in given namespace. If the namespace does not exists yet, // waits until it is created for maximum namespaceCreationMaxWaitTime (see below) -func (kc *KubeClient) GenerateConfigMap(generator types.PolicyConfigGenerator, namespace string) error { +func (kc *KubeClient) GenerateConfigMap(generator types.Generation, namespace string) error { kc.logger.Printf("Preparing to create configmap %s/%s", namespace, generator.Name) configMap := &v1.ConfigMap{} var err error - 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, defaultGetOptions()) - if err != nil { - return err - } + + 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{ @@ -100,17 +96,16 @@ func (kc *KubeClient) GenerateConfigMap(generator types.PolicyConfigGenerator, n // Generates new Secret in given namespace. If the namespace does not exists yet, // waits until it is created for maximum namespaceCreationMaxWaitTime (see below) -func (kc *KubeClient) GenerateSecret(generator types.PolicyConfigGenerator, namespace string) error { +func (kc *KubeClient) GenerateSecret(generator types.Generation, namespace string) error { kc.logger.Printf("Preparing to create secret %s/%s", namespace, generator.Name) secret := &v1.Secret{} var err error - 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, defaultGetOptions()) - if err != nil { - return err - } + + 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{ @@ -133,13 +128,6 @@ func (kc *KubeClient) GenerateSecret(generator types.PolicyConfigGenerator, name return nil } -func defaultGetOptions() metav1.GetOptions { - return metav1.GetOptions{ - ResourceVersion: "1", - IncludeUninitialized: true, - } -} - func defaultDeleteOptions() *metav1.DeleteOptions { var deletePeriod int64 = 0 return &metav1.DeleteOptions{ @@ -156,7 +144,7 @@ func (kc *KubeClient) waitUntilNamespaceIsCreated(name string) error { var lastError error = nil for time.Now().Sub(timeStart) < namespaceCreationMaxWaitTime { - _, lastError = kc.client.CoreV1().Namespaces().Get(name, defaultGetOptions()) + _, lastError = kc.client.CoreV1().Namespaces().Get(name, metav1.GetOptions{}) if lastError == nil { break } diff --git a/main.go b/main.go index 4e04177455..9273a6d767 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,6 @@ import ( "github.com/nirmata/kube-policy/kubeclient" "github.com/nirmata/kube-policy/policycontroller" "github.com/nirmata/kube-policy/server" - "github.com/nirmata/kube-policy/webhooks" policyclientset "github.com/nirmata/kube-policy/pkg/client/clientset/versioned" informers "github.com/nirmata/kube-policy/pkg/client/informers/externalversions" @@ -42,7 +41,7 @@ func main() { //TODO wrap the policyInformer inside a factory policyInformerFactory := informers.NewSharedInformerFactory(policyClientset, 0) - policyInformer := policyInformerFactory.Nirmata().V1alpha1().Policies() + policyInformer := policyInformerFactory.Kubepolicy().V1alpha1().Policies() eventController := event.NewEventController(kubeclient, policyInformer.Lister(), nil) violationBuilder := policyviolation.NewPolicyViolationBuilder(kubeclient, policyInformer.Lister(), policyClientset, eventController, nil) @@ -56,12 +55,6 @@ func main() { nil, kubeclient) - mutationWebhook, err := webhooks.CreateMutationWebhook(clientConfig, - kubeclient, - policyInformer.Lister(), - violationBuilder, - eventController, - nil) if err != nil { log.Fatalf("Error creating mutation webhook: %v\n", err) } @@ -71,7 +64,7 @@ func main() { log.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err) } - server, err := server.NewWebhookServer(tlsPair, mutationWebhook, nil) + server, err := server.NewWebhookServer(tlsPair, kubeclient, policyInformer.Lister(), nil) if err != nil { log.Fatalf("Unable to create webhook server: %v\n", err) } diff --git a/pkg/policyengine/generation.go b/pkg/policyengine/generation.go new file mode 100644 index 0000000000..86ce884419 --- /dev/null +++ b/pkg/policyengine/generation.go @@ -0,0 +1,54 @@ +package policyengine + +import ( + "fmt" + + kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + "github.com/nirmata/kube-policy/pkg/policyengine/mutation" +) + +// TODO: To be reworked due to spec policy-v2 + +// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule +func (p *policyEngine) applyRuleGenerators(rawResource []byte, rule kubepolicy.Rule) error { + kind := mutation.ParseKindFromObject(rawResource) + + // configMapGenerator and secretGenerator can be applied only to namespaces + if kind == "Namespace" { + namespaceName := mutation.ParseNameFromObject(rawResource) + + err := p.applyConfigGenerator(rule.Generation, namespaceName, "ConfigMap") + if err == nil { + err = p.applyConfigGenerator(rule.Generation, namespaceName, "Secret") + } + return err + } + return nil +} + +// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request. +func (p *policyEngine) applyConfigGenerator(generator *kubepolicy.Generation, namespace string, configKind string) error { + if generator == nil { + return nil + } + + err := generator.Validate() + if err != nil { + return fmt.Errorf("Generator for '%s' is invalid: %s", configKind, err) + } + + switch configKind { + case "ConfigMap": + err = p.kubeClient.GenerateConfigMap(*generator, namespace) + case "Secret": + err = p.kubeClient.GenerateSecret(*generator, namespace) + default: + err = fmt.Errorf("Unsupported config Kind '%s'", configKind) + } + + if err != nil { + return fmt.Errorf("Unable to apply generator for %s '%s/%s' : %s", configKind, namespace, generator.Name, err) + } + + return nil +} diff --git a/pkg/policyengine/mutation.go b/pkg/policyengine/mutation.go index 85483bf53a..75f20193b3 100644 --- a/pkg/policyengine/mutation.go +++ b/pkg/policyengine/mutation.go @@ -1,96 +1,61 @@ package policyengine import ( - "errors" - "fmt" - - types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" "github.com/nirmata/kube-policy/pkg/policyengine/mutation" ) -func (p *policyEngine) ProcessMutation(policy types.Policy, rawResource []byte) ([]mutation.PatchBytes, error) { - patchingSets := mutation.GetPolicyPatchingSets(policy) +// Mutate performs mutation. Overlay first and then mutation patches +func (p *policyEngine) Mutate(policy kubepolicy.Policy, rawResource []byte) []mutation.PatchBytes { var policyPatches []mutation.PatchBytes - for ruleIdx, rule := range policy.Spec.Rules { + for i, rule := range policy.Spec.Rules { + + // Checks for preconditions + // TODO: Rework PolicyEngine interface that it receives not a policy, but mutation object for + // Mutate, validation for Validate and so on. It will allow to bring this checks outside of PolicyEngine + // to common part as far as they present for all: mutation, validation, generation + err := rule.Validate() if err != nil { - p.logger.Printf("Invalid rule detected: #%s in policy %s, err: %v\n", rule.Name, policy.ObjectMeta.Name, err) + p.logger.Printf("Rule has invalid structure: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err) continue } - if ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.Resource); !ok { - p.logger.Printf("Rule %d of policy %s is not applicable to the request", ruleIdx, policy.Name) - return nil, err - } - - err = p.applyRuleGenerators(rawResource, rule) - if err != nil && patchingSets == mutation.PatchingSetsStopOnError { - return nil, fmt.Errorf("Failed to apply generators from rule #%s: %v", rule.Name, err) - } - - rulePatchesProcessed, err := mutation.ProcessPatches(rule.Patches, rawResource, patchingSets) + ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.ResourceDescription) if err != nil { - return nil, fmt.Errorf("Failed to process patches from rule #%s: %v", rule.Name, err) + p.logger.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err) + continue } - if rulePatchesProcessed != nil { - policyPatches = append(policyPatches, rulePatchesProcessed...) - p.logger.Printf("Rule %d: prepared %d patches", ruleIdx, len(rulePatchesProcessed)) - // TODO: add PolicyApplied events per rule for policy and resource - } else { - p.logger.Printf("Rule %d: no patches prepared", ruleIdx) + if !ok { + p.logger.Printf("Rule is not applicable t the request: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err) + continue } - } - // empty patch, return error to deny resource creation - if policyPatches == nil { - return nil, fmt.Errorf("no patches prepared") - } + // Process Overlay - return policyPatches, nil -} - -// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule -func (p *policyEngine) applyRuleGenerators(rawResource []byte, rule types.PolicyRule) error { - kind := mutation.ParseKindFromObject(rawResource) - - // configMapGenerator and secretGenerator can be applied only to namespaces - if kind == "Namespace" { - namespaceName := mutation.ParseNameFromObject(rawResource) - - err := p.applyConfigGenerator(rule.ConfigMapGenerator, namespaceName, "ConfigMap") - if err == nil { - err = p.applyConfigGenerator(rule.SecretGenerator, namespaceName, "Secret") + if rule.Mutation.Overlay != nil { + overlayPatches, err := mutation.ProcessOverlay(rule.Mutation.Overlay, rawResource) + if err != nil { + p.logger.Printf("Overlay application failed: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err) + } else { + policyPatches = append(policyPatches, overlayPatches...) + } } - return err + + // Process Patches + + if rule.Mutation.Patches != nil { + processedPatches, err := mutation.ProcessPatches(rule.Mutation.Patches, rawResource) + if err != nil { + p.logger.Printf("Patches application failed: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err) + } else { + policyPatches = append(policyPatches, processedPatches...) + } + } + } - return nil -} - -// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request. -func (p *policyEngine) applyConfigGenerator(generator *types.PolicyConfigGenerator, namespace string, configKind string) error { - if generator == nil { - return nil - } - - err := generator.Validate() - if err != nil { - return errors.New(fmt.Sprintf("Generator for '%s' is invalid: %s", configKind, err)) - } - - switch configKind { - case "ConfigMap": - err = p.kubeClient.GenerateConfigMap(*generator, namespace) - case "Secret": - err = p.kubeClient.GenerateSecret(*generator, namespace) - default: - err = errors.New(fmt.Sprintf("Unsupported config Kind '%s'", configKind)) - } - - if err != nil { - return errors.New(fmt.Sprintf("Unable to apply generator for %s '%s/%s' : %s", configKind, namespace, generator.Name, err)) - } - - return nil + + return policyPatches } diff --git a/pkg/policyengine/mutation/checkRules.go b/pkg/policyengine/mutation/checkRules.go index ecb7f7ff35..bcd73a0840 100644 --- a/pkg/policyengine/mutation/checkRules.go +++ b/pkg/policyengine/mutation/checkRules.go @@ -8,25 +8,25 @@ import ( // kind is the type of object being manipulated // Checks requests kind, name and labels to fit the policy -func IsRuleApplicableToResource(resourceRaw []byte, policyResource types.PolicyResource) (bool, error) { - // kind := ParseKindFromObject(resourceRaw) - // if policyResource.Kind != kind { - // return false, nil - // } +func IsRuleApplicableToResource(resourceRaw []byte, description types.ResourceDescription) (bool, error) { + kind := ParseKindFromObject(resourceRaw) + if description.Kind != kind { + return false, nil + } if resourceRaw != nil { meta := ParseMetadataFromObject(resourceRaw) name := ParseNameFromObject(resourceRaw) - if policyResource.Name != nil { + if description.Name != nil { - if !wildcard.Match(*policyResource.Name, name) { + if !wildcard.Match(*description.Name, name) { return false, nil } } - if policyResource.Selector != nil { - selector, err := metav1.LabelSelectorAsSelector(policyResource.Selector) + if description.Selector != nil { + selector, err := metav1.LabelSelectorAsSelector(description.Selector) if err != nil { return false, err diff --git a/pkg/policyengine/mutation/overlay.go b/pkg/policyengine/mutation/overlay.go new file mode 100644 index 0000000000..308596e1bc --- /dev/null +++ b/pkg/policyengine/mutation/overlay.go @@ -0,0 +1,6 @@ +package mutation + +func ProcessOverlay(overlay interface{}, rawResource []byte) ([]PatchBytes, error) { + // TODO: Overlay to be implemented + return nil, nil +} diff --git a/pkg/policyengine/mutation/patches.go b/pkg/policyengine/mutation/patches.go index 83f66863dc..0b3015020d 100644 --- a/pkg/policyengine/mutation/patches.go +++ b/pkg/policyengine/mutation/patches.go @@ -5,60 +5,37 @@ import ( "errors" jsonpatch "github.com/evanphx/json-patch" - types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" -) - -type PatchingSets uint8 - -const ( - PatchingSetsStopOnError PatchingSets = 0 - PatchingSetsContinueOnRemoveFailure PatchingSets = 1 - PatchingSetsContinueAlways PatchingSets = 255 - - PatchingSetsDefault PatchingSets = PatchingSetsContinueOnRemoveFailure + kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" ) 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. -func ProcessPatches(patches []types.PolicyPatch, originalDocument []byte, sets PatchingSets) ([]PatchBytes, error) { - if len(originalDocument) == 0 { +func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes, error) { + if len(resource) == 0 { return nil, errors.New("Source document for patching is empty") } + var appliedPatches []PatchBytes - patchedDocument := originalDocument for _, patch := range patches { - patchBytes, err := json.Marshal(patch) - if err != nil && sets == PatchingSetsStopOnError { + patchRaw, err := json.Marshal(patch) + if err != nil { return nil, err } - patchedDocument, err = CheckPatch(patchedDocument, patchBytes) - if err != nil { // Ignore errors on "remove" operations - if sets == PatchingSetsContinueOnRemoveFailure && patch.Operation == "remove" { - continue - } else if sets != PatchingSetsContinueAlways { - return nil, err - } - } else { // In any case we should collect only valid patches - appliedPatches = append(appliedPatches, patchBytes) + _, err = applyPatch(resource, patchRaw) + if err != nil { + return nil, err } + + appliedPatches = append(appliedPatches, patchRaw) } return appliedPatches, nil } -// Joins array of serialized JSON patches to the single JSONPatch array +// JoinPatches joins array of serialized JSON patches to the single JSONPatch array func JoinPatches(patches []PatchBytes) PatchBytes { var result PatchBytes if len(patches) == 0 { @@ -76,19 +53,15 @@ func JoinPatches(patches []PatchBytes) PatchBytes { return result } -// Checks patch for document, returns patched document. -// On error returns original document and error. -func CheckPatch(document []byte, patchRaw []byte) (PatchBytes, error) { +// ApplyPatch applies patch for resource, returns patched resource. +func applyPatch(resource []byte, patchRaw []byte) ([]byte, error) { patchRaw = append([]byte{'['}, patchRaw...) // push [ forward patchRaw = append(patchRaw, ']') // push ] back + patch, err := jsonpatch.DecodePatch(patchRaw) if err != nil { - return document, err + return nil, err } - patchedDocument, err := patch.Apply(document) - if err != nil { - return document, err - } - return patchedDocument, err + return patch.Apply(resource) } diff --git a/pkg/policyengine/policyengine.go b/pkg/policyengine/policyengine.go index 67a9084a51..cbe8367d78 100644 --- a/pkg/policyengine/policyengine.go +++ b/pkg/policyengine/policyengine.go @@ -15,16 +15,20 @@ type PolicyEngine interface { // ProcessMutation should be called from admission contoller // when there is an creation / update of the resource // ProcessMutation(policy types.Policy, rawResource []byte) (patchBytes []byte, events []Events, err error) - ProcessMutation(policy types.Policy, rawResource []byte) ([]mutation.PatchBytes, error) + Mutate(policy types.Policy, rawResource []byte) []mutation.PatchBytes // ProcessValidation should be called from admission contoller // when there is an creation / update of the resource + // TODO: Change name to Validate ProcessValidation(policy types.Policy, rawResource []byte) // ProcessExisting should be called from policy controller // when there is an create / update of the policy // we should process the policy on matched resources, generate violations accordingly + // TODO: This method should not be in PolicyEngine. Validate will do this work instead ProcessExisting(policy types.Policy, rawResource []byte) ([]policyviolation.Info, []event.Info, error) + + // TODO: Add Generate method } type policyEngine struct { @@ -43,8 +47,6 @@ func (p *policyEngine) ProcessExisting(policy types.Policy, rawResource []byte) var violations []policyviolation.Info var events []event.Info - patchingSets := mutation.GetPolicyPatchingSets(policy) - for _, rule := range policy.Spec.Rules { err := rule.Validate() if err != nil { @@ -52,12 +54,12 @@ func (p *policyEngine) ProcessExisting(policy types.Policy, rawResource []byte) continue } - if ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.Resource); !ok { + if ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.ResourceDescription); !ok { p.logger.Printf("Rule %s of policy %s is not applicable to the request", rule.Name, policy.Name) return nil, nil, err } - violation, eventInfos, err := p.processRuleOnResource(policy.Name, rule, rawResource, patchingSets) + violation, eventInfos, err := p.processRuleOnResource(policy.Name, rule, rawResource) if err != nil { p.logger.Printf("Failed to process rule %s, err: %v\n", rule.Name, err) continue @@ -71,7 +73,7 @@ func (p *policyEngine) ProcessExisting(policy types.Policy, rawResource []byte) return violations, events, nil } -func (p *policyEngine) processRuleOnResource(policyName string, rule types.PolicyRule, rawResource []byte, patchingSets mutation.PatchingSets) ( +func (p *policyEngine) processRuleOnResource(policyName string, rule types.Rule, rawResource []byte) ( policyviolation.Info, []event.Info, error) { var violationInfo policyviolation.Info @@ -81,7 +83,7 @@ func (p *policyEngine) processRuleOnResource(policyName string, rule types.Polic resourceName := mutation.ParseNameFromObject(rawResource) resourceNamespace := mutation.ParseNamespaceFromObject(rawResource) - rulePatchesProcessed, err := mutation.ProcessPatches(rule.Patches, nil, patchingSets) + rulePatchesProcessed, err := mutation.ProcessPatches(rule.Mutation.Patches, nil) if err != nil { return violationInfo, eventInfos, fmt.Errorf("Failed to process patches from rule %s: %v", rule.Name, err) } diff --git a/pkg/policyviolation/builder.go b/pkg/policyviolation/builder.go index ff43c5dfd0..69e0b1e56f 100644 --- a/pkg/policyviolation/builder.go +++ b/pkg/policyviolation/builder.go @@ -89,7 +89,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.policyInterface.NirmataV1alpha1().Policies(namespace).UpdateStatus(modifiedPolicy) + _, err = b.policyInterface.KubepolicyV1alpha1().Policies(namespace).UpdateStatus(modifiedPolicy) if err != nil { return err } diff --git a/policycontroller/processPolicy.go b/policycontroller/processPolicy.go index de4950bbea..a707f7325d 100644 --- a/policycontroller/processPolicy.go +++ b/policycontroller/processPolicy.go @@ -70,7 +70,7 @@ func (pc *PolicyController) processPolicy(policy types.Policy) ( return violations, events, nil } -func (pc *PolicyController) filterResourceByRule(rule types.PolicyRule) ([]runtime.Object, error) { +func (pc *PolicyController) filterResourceByRule(rule types.Rule) ([]runtime.Object, error) { var targetResources []runtime.Object // TODO: make this namespace all var namespace = "default" @@ -79,7 +79,7 @@ func (pc *PolicyController) filterResourceByRule(rule types.PolicyRule) ([]runti } // Get the resource list from kind - resources, err := pc.kubeClient.ListResource(rule.Resource.Kind, namespace) + resources, err := pc.kubeClient.ListResource(rule.ResourceDescription.Kind, namespace) if err != nil { return nil, err } @@ -97,7 +97,7 @@ func (pc *PolicyController) filterResourceByRule(rule types.PolicyRule) ([]runti } // filter the resource by name and label - if ok, _ := mutation.IsRuleApplicableToResource(rawResource, rule.Resource); ok { + if ok, _ := mutation.IsRuleApplicableToResource(rawResource, rule.ResourceDescription); ok { targetResources = append(targetResources, resource) } } diff --git a/server/server.go b/server/server.go index f714f535ac..68894497fe 100644 --- a/server/server.go +++ b/server/server.go @@ -10,31 +10,43 @@ import ( "log" "net/http" "os" + "sort" "time" "github.com/nirmata/kube-policy/config" + "github.com/nirmata/kube-policy/kubeclient" + kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1" + "github.com/nirmata/kube-policy/pkg/policyengine" + "github.com/nirmata/kube-policy/pkg/policyengine/mutation" "github.com/nirmata/kube-policy/utils" "github.com/nirmata/kube-policy/webhooks" - v1beta1 "k8s.io/api/admission/v1beta1" + "k8s.io/apimachinery/pkg/labels" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) // WebhookServer contains configured TLS server with MutationWebhook. // MutationWebhook gets policies from policyController and takes control of the cluster with kubeclient. type WebhookServer struct { - server http.Server - mutationWebhook *webhooks.MutationWebhook - logger *log.Logger + server http.Server + policyEngine policyengine.PolicyEngine + policyLister policylister.PolicyLister + logger *log.Logger } // NewWebhookServer creates new instance of WebhookServer accordingly to given configuration // Policy Controller and Kubernetes Client should be initialized in configuration -func NewWebhookServer(tlsPair *utils.TlsPemPair, mutationWebhook *webhooks.MutationWebhook, logger *log.Logger) (*WebhookServer, error) { +func NewWebhookServer( + tlsPair *utils.TlsPemPair, + kubeclient *kubeclient.KubeClient, + policyLister policylister.PolicyLister, + logger *log.Logger) (*WebhookServer, error) { if logger == nil { logger = log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile) } - if tlsPair == nil || mutationWebhook == nil { + if tlsPair == nil { return nil, errors.New("NewWebhookServer is not initialized properly") } @@ -44,10 +56,12 @@ func NewWebhookServer(tlsPair *utils.TlsPemPair, mutationWebhook *webhooks.Mutat return nil, err } tlsConfig.Certificates = []tls.Certificate{pair} + policyEngine := policyengine.NewPolicyEngine(kubeclient, logger) ws := &WebhookServer{ - logger: logger, - mutationWebhook: mutationWebhook, + policyEngine: policyEngine, + policyLister: policyLister, + logger: logger, } mux := http.NewServeMux() @@ -75,7 +89,7 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { var admissionResponse *v1beta1.AdmissionResponse if webhooks.AdmissionIsRequired(admissionReview.Request) { - admissionResponse = ws.mutationWebhook.Mutate(admissionReview.Request) + admissionResponse = ws.Mutate(admissionReview.Request) } if admissionResponse == nil { @@ -154,3 +168,57 @@ func (ws *WebhookServer) Stop() { ws.server.Close() } } + +func (ws *WebhookServer) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { + ws.logger.Printf("AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v patchOperation=%v UserInfo=%v", + request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation, request.UserInfo) + + policies, err := ws.getPolicies() + if err != nil { + utilruntime.HandleError(err) + return nil + } + if len(policies) == 0 { + return nil + } + + var allPatches []mutation.PatchBytes + for _, policy := range policies { + ws.logger.Printf("Applying policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules)) + + policyPatches := ws.policyEngine.Mutate(policy, request.Object.Raw) + allPatches = append(allPatches, policyPatches...) + + if len(policyPatches) > 0 { + namespace := mutation.ParseNamespaceFromObject(request.Object.Raw) + name := mutation.ParseNameFromObject(request.Object.Raw) + ws.logger.Printf("Policy %s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name) + } + } + + patchType := v1beta1.PatchTypeJSONPatch + return &v1beta1.AdmissionResponse{ + Allowed: true, + Patch: mutation.JoinPatches(allPatches), + PatchType: &patchType, + } +} + +func (ws *WebhookServer) getPolicies() ([]kubepolicy.Policy, error) { + selector := labels.NewSelector() + cachedPolicies, err := ws.policyLister.List(selector) + if err != nil { + ws.logger.Printf("Error: %v", err) + return nil, err + } + + var policies []kubepolicy.Policy + for _, elem := range cachedPolicies { + policies = append(policies, *elem.DeepCopy()) + } + + sort.Slice(policies, func(i, j int) bool { + return policies[i].CreationTimestamp.Time.Before(policies[j].CreationTimestamp.Time) + }) + return policies, nil +} diff --git a/webhooks/admission.go b/webhooks/admission.go index ba2913588f..a3323ff917 100644 --- a/webhooks/admission.go +++ b/webhooks/admission.go @@ -23,6 +23,6 @@ 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) { +func IsRuleApplicableToRequest(policyResource types.ResourceDescription, request *v1beta1.AdmissionRequest) (bool, error) { return mutation.IsRuleApplicableToResource(request.Object.Raw, policyResource) } diff --git a/webhooks/mutation.go b/webhooks/mutation.go deleted file mode 100644 index 1b74c35ae4..0000000000 --- a/webhooks/mutation.go +++ /dev/null @@ -1,239 +0,0 @@ -package webhooks - -import ( - "errors" - "fmt" - "log" - "os" - "sort" - - kubeclient "github.com/nirmata/kube-policy/kubeclient" - types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" - policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1" - event "github.com/nirmata/kube-policy/pkg/event" - policyengine "github.com/nirmata/kube-policy/pkg/policyengine" - mutation "github.com/nirmata/kube-policy/pkg/policyengine/mutation" - policyviolation "github.com/nirmata/kube-policy/pkg/policyviolation" - v1beta1 "k8s.io/api/admission/v1beta1" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - rest "k8s.io/client-go/rest" -) - -// MutationWebhook is a data type that represents -// business logic for resource mutation -type MutationWebhook struct { - kubeclient *kubeclient.KubeClient - policyEngine policyengine.PolicyEngine - policyLister policylister.PolicyLister - registration *MutationWebhookRegistration - violationBuilder policyviolation.Generator - eventBuilder event.Generator - logger *log.Logger -} - -// Registers mutation webhook in cluster and creates object for this webhook -func CreateMutationWebhook( - clientConfig *rest.Config, - kubeclient *kubeclient.KubeClient, - policyLister policylister.PolicyLister, - violationBuilder policyviolation.Generator, - eventController event.Generator, - logger *log.Logger) (*MutationWebhook, error) { - if clientConfig == nil || kubeclient == nil { - return nil, errors.New("Some parameters are not set") - } - - registration, err := NewMutationWebhookRegistration(clientConfig, kubeclient) - if err != nil { - return nil, err - } - - err = registration.Register() - if err != nil { - return nil, err - } - - if logger == nil { - logger = log.New(os.Stdout, "Mutation WebHook: ", log.LstdFlags|log.Lshortfile) - } - policyengine := policyengine.NewPolicyEngine(kubeclient, logger) - - return &MutationWebhook{ - kubeclient: kubeclient, - policyEngine: policyengine, - policyLister: policyLister, - registration: registration, - violationBuilder: violationBuilder, - eventBuilder: eventController, - logger: logger, - }, nil -} - -func (mw *MutationWebhook) getPolicies() ([]types.Policy, error) { - selector := labels.NewSelector() - cachedPolicies, err := mw.policyLister.List(selector) - if err != nil { - mw.logger.Printf("Error: %v", err) - return nil, err - } - - var policies []types.Policy - for _, elem := range cachedPolicies { - policies = append(policies, *elem.DeepCopy()) - } - - sort.Slice(policies, func(i, j int) bool { - return policies[i].CreationTimestamp.Time.Before(policies[j].CreationTimestamp.Time) - }) - return policies, nil - -} - -// Mutate applies admission to request -func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { - mw.logger.Printf("AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v patchOperation=%v UserInfo=%v", - request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation, request.UserInfo) - - policies, err := mw.getPolicies() - if err != nil { - utilruntime.HandleError(err) - return nil - } - if len(policies) == 0 { - return nil - } - - var allPatches []mutation.PatchBytes - for _, policy := range policies { - mw.logger.Printf("Applying policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules)) - - policyPatches, err := mw.applyPolicyRules(request, policy) - if err != nil { - //TODO Log Policy Error - - errStr := fmt.Sprintf("Unable to apply policy %s: %v", policy.Name, err) - mw.logger.Printf("Denying the request because of error: %s", errStr) - return mw.denyResourceCreation(errStr) - } - - if len(policyPatches) > 0 { - namespace := mutation.ParseNamespaceFromObject(request.Object.Raw) - name := mutation.ParseNameFromObject(request.Object.Raw) - //TODO Log Policy Info - mw.logger.Printf("%s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name) - - allPatches = append(allPatches, policyPatches...) - } - } - - patchType := v1beta1.PatchTypeJSONPatch - return &v1beta1.AdmissionResponse{ - Allowed: true, - Patch: mutation.JoinPatches(allPatches), - PatchType: &patchType, - } -} - -// 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) ([]mutation.PatchBytes, error) { - return mw.policyEngine.ProcessMutation(policy, request.Object.Raw) -} - -// kind is the type of object being manipulated, e.g. request.Kind.kind -func (mw *MutationWebhook) applyPolicyRulesOnResource(kind string, rawResource []byte, policy types.Policy) ([]mutation.PatchBytes, error) { - patchingSets := mutation.GetPolicyPatchingSets(policy) - var policyPatches []mutation.PatchBytes - - for ruleIdx, rule := range policy.Spec.Rules { - err := rule.Validate() - if err != nil { - mw.logger.Printf("Invalid rule detected: #%d in policy %s, err: %v\n", ruleIdx, policy.ObjectMeta.Name, err) - continue - } - - if ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.Resource); !ok { - mw.logger.Printf("Rule %d of policy %s is not applicable to the request", ruleIdx, policy.Name) - return nil, err - } - - // configMapGenerator and secretGenerator can be applied only to namespaces - if kind == "Namespace" { - err = mw.applyRuleGenerators(rawResource, rule) - if err != nil && patchingSets == mutation.PatchingSetsStopOnError { - return nil, fmt.Errorf("Failed to apply generators from rule #%d: %s", ruleIdx, err) - } - } - - rulePatchesProcessed, err := mutation.ProcessPatches(rule.Patches, rawResource, patchingSets) - if err != nil { - return nil, fmt.Errorf("Failed to process patches from rule #%d: %s", ruleIdx, err) - } - - if rulePatchesProcessed != nil { - policyPatches = append(policyPatches, rulePatchesProcessed...) - mw.logger.Printf("Rule %d: prepared %d patches", ruleIdx, len(rulePatchesProcessed)) - } else { - mw.logger.Printf("Rule %d: no patches prepared", ruleIdx) - } - } - - // empty patch, return error to deny resource creation - if policyPatches == nil { - return nil, fmt.Errorf("no patches prepared") - } - - return policyPatches, nil -} - -// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule -func (mw *MutationWebhook) applyRuleGenerators(rawResource []byte, rule types.PolicyRule) error { - namespaceName := mutation.ParseNameFromObject(rawResource) - - err := mw.applyConfigGenerator(rule.ConfigMapGenerator, namespaceName, "ConfigMap") - if err == nil { - err = mw.applyConfigGenerator(rule.SecretGenerator, namespaceName, "Secret") - } - return err -} - -// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request. -func (mw *MutationWebhook) applyConfigGenerator(generator *types.PolicyConfigGenerator, namespace string, configKind string) error { - if generator == nil { - return nil - } - - err := generator.Validate() - if err != nil { - return errors.New(fmt.Sprintf("Generator for '%s' is invalid: %s", configKind, err)) - } - - switch configKind { - case "ConfigMap": - err = mw.kubeclient.GenerateConfigMap(*generator, namespace) - case "Secret": - err = mw.kubeclient.GenerateSecret(*generator, namespace) - default: - err = errors.New(fmt.Sprintf("Unsupported config Kind '%s'", configKind)) - } - - if err != nil { - return errors.New(fmt.Sprintf("Unable to apply generator for %s '%s/%s' : %s", configKind, namespace, generator.Name, err)) - } - - return nil -} - -// Forms AdmissionResponse with denial of resource creation and error message -func (mw *MutationWebhook) denyResourceCreation(errStr string) *v1beta1.AdmissionResponse { - return &v1beta1.AdmissionResponse{ - Result: &metav1.Status{ - Message: errStr, - }, - Allowed: false, - } -}