1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-28 18:38:40 +00:00

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

This commit is contained in:
Maxim Goncharenko 2019-05-13 21:27:47 +03:00
parent 9e8540f280
commit b60ec94267
14 changed files with 237 additions and 427 deletions

View file

@ -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 {

View file

@ -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
}

11
main.go
View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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

View file

@ -0,0 +1,6 @@
package mutation
func ProcessOverlay(overlay interface{}, rawResource []byte) ([]PatchBytes, error) {
// TODO: Overlay to be implemented
return nil, nil
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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,
}
}