2019-02-21 20:31:18 +02:00
|
|
|
package webhooks
|
|
|
|
|
|
|
|
import (
|
2019-03-05 20:04:23 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2019-03-25 10:11:50 +02:00
|
|
|
"os"
|
2019-03-05 20:04:23 +02:00
|
|
|
|
2019-03-07 17:42:37 +02:00
|
|
|
controller "github.com/nirmata/kube-policy/controller"
|
2019-03-06 13:01:17 +02:00
|
|
|
kubeclient "github.com/nirmata/kube-policy/kubeclient"
|
2019-03-05 20:04:23 +02:00
|
|
|
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
|
|
|
v1beta1 "k8s.io/api/admission/v1beta1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2019-03-25 10:11:50 +02:00
|
|
|
rest "k8s.io/client-go/rest"
|
2019-02-21 20:31:18 +02:00
|
|
|
)
|
|
|
|
|
2019-03-01 14:16:20 +02:00
|
|
|
// MutationWebhook is a data type that represents
|
2019-03-25 10:11:50 +02:00
|
|
|
// business logic for resource mutation
|
2019-02-21 20:31:18 +02:00
|
|
|
type MutationWebhook struct {
|
2019-03-25 10:11:50 +02:00
|
|
|
kubeclient *kubeclient.KubeClient
|
|
|
|
controller *controller.PolicyController
|
|
|
|
registration *MutationWebhookRegistration
|
|
|
|
logger *log.Logger
|
2019-02-21 20:31:18 +02:00
|
|
|
}
|
|
|
|
|
2019-03-25 10:11:50 +02:00
|
|
|
// Registers mutation webhook in cluster and creates object for this webhook
|
|
|
|
func CreateMutationWebhook(clientConfig *rest.Config, kubeclient *kubeclient.KubeClient, controller *controller.PolicyController, logger *log.Logger) (*MutationWebhook, error) {
|
|
|
|
if clientConfig == nil || kubeclient == nil || controller == nil {
|
2019-03-07 17:42:37 +02:00
|
|
|
return nil, errors.New("Some parameters are not set")
|
2019-03-05 20:04:23 +02:00
|
|
|
}
|
2019-03-25 10:11:50 +02:00
|
|
|
|
|
|
|
registration, err := NewMutationWebhookRegistration(clientConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if logger == nil {
|
|
|
|
logger = log.New(os.Stdout, "Mutation WebHook: ", log.LstdFlags|log.Lshortfile)
|
|
|
|
}
|
|
|
|
return &MutationWebhook{
|
|
|
|
kubeclient: kubeclient,
|
|
|
|
controller: controller,
|
|
|
|
registration: registration,
|
|
|
|
logger: logger,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mw *MutationWebhook) Deregister() error {
|
|
|
|
return mw.registration.Deregister()
|
2019-02-21 20:31:18 +02:00
|
|
|
}
|
|
|
|
|
2019-03-01 14:16:20 +02:00
|
|
|
// Mutate applies admission to request
|
2019-03-07 17:42:37 +02:00
|
|
|
func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
|
2019-03-05 20:04:23 +02:00
|
|
|
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)
|
|
|
|
|
2019-03-07 17:42:37 +02:00
|
|
|
policies := mw.controller.GetPolicies()
|
2019-03-05 20:04:23 +02:00
|
|
|
if len(policies) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-12 22:02:39 +02:00
|
|
|
var allPatches []PatchBytes
|
2019-03-05 20:04:23 +02:00
|
|
|
for _, policy := range policies {
|
2019-03-21 18:09:58 +02:00
|
|
|
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 {
|
|
|
|
mw.controller.LogPolicyError(policy.Name, err.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 {
|
|
|
|
meta := parseMetadataFromObject(request.Object.Raw)
|
|
|
|
namespace := parseNamespaceFromMetadata(meta)
|
|
|
|
name := parseNameFromMetadata(meta)
|
|
|
|
mw.controller.LogPolicyInfo(policy.Name, fmt.Sprintf("Applied to %s %s/%s", request.Kind.Kind, namespace, name))
|
|
|
|
|
|
|
|
allPatches = append(allPatches, policyPatches...)
|
2019-03-12 22:02:39 +02:00
|
|
|
}
|
2019-03-05 20:04:23 +02:00
|
|
|
}
|
|
|
|
|
2019-03-21 18:09:58 +02:00
|
|
|
patchType := v1beta1.PatchTypeJSONPatch
|
2019-03-05 20:04:23 +02:00
|
|
|
return &v1beta1.AdmissionResponse{
|
2019-03-21 18:09:58 +02:00
|
|
|
Allowed: true,
|
|
|
|
Patch: JoinPatches(allPatches),
|
|
|
|
PatchType: &patchType,
|
2019-03-05 20:04:23 +02:00
|
|
|
}
|
2019-02-21 20:31:18 +02:00
|
|
|
}
|
|
|
|
|
2019-03-12 22:02:39 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-03-21 18:09:58 +02:00
|
|
|
// Applies all policy rules to the created object and returns list of processed JSON patches.
|
2019-03-06 13:01:17 +02:00
|
|
|
// May return nil patches if it is not necessary to create patches for requested object.
|
2019-03-19 14:16:09 +02:00
|
|
|
// Returns error ONLY in case when creation of resource should be denied.
|
2019-03-21 18:09:58 +02:00
|
|
|
func (mw *MutationWebhook) applyPolicyRules(request *v1beta1.AdmissionRequest, policy types.Policy) ([]PatchBytes, error) {
|
|
|
|
patchingSets := getPolicyPatchingSets(policy)
|
|
|
|
var policyPatches []PatchBytes
|
|
|
|
|
|
|
|
for ruleIdx, rule := range policy.Spec.Rules {
|
|
|
|
err := rule.Validate()
|
|
|
|
if err != nil {
|
|
|
|
mw.logger.Printf("Invalid rule detected: #%d in policy %s", ruleIdx, policy.ObjectMeta.Name)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !IsRuleApplicableToRequest(rule.Resource, request) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err = mw.applyRuleGenerators(request, rule)
|
|
|
|
if err != nil && patchingSets == PatchingSetsStopOnError {
|
|
|
|
return nil, errors.New(fmt.Sprintf("Failed to apply generators from rule #%d: %s", ruleIdx, err))
|
|
|
|
}
|
|
|
|
|
|
|
|
rulePatchesProcessed, err := ProcessPatches(rule.Patches, request.Object.Raw, patchingSets)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.New(fmt.Sprintf("Failed to process patches from rule #%d: %s", ruleIdx, err))
|
|
|
|
}
|
2019-03-06 13:01:17 +02:00
|
|
|
|
2019-03-21 18:09:58 +02:00
|
|
|
if rulePatchesProcessed != nil {
|
|
|
|
policyPatches = append(policyPatches, rulePatchesProcessed...)
|
|
|
|
mw.logger.Printf("Rule %d: prepared %d patches", ruleIdx, len(rulePatchesProcessed))
|
|
|
|
} else {
|
|
|
|
mw.logger.Print("Rule %d: no patches prepared")
|
|
|
|
}
|
2019-03-06 13:01:17 +02:00
|
|
|
}
|
2019-03-21 18:09:58 +02:00
|
|
|
|
|
|
|
return policyPatches, nil
|
2019-03-06 13:01:17 +02:00
|
|
|
}
|
|
|
|
|
2019-03-07 17:57:43 +02:00
|
|
|
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
|
2019-03-06 13:01:17 +02:00
|
|
|
func (mw *MutationWebhook) applyRuleGenerators(request *v1beta1.AdmissionRequest, rule types.PolicyRule) error {
|
2019-03-05 20:04:23 +02:00
|
|
|
// configMapGenerator and secretGenerator can be applied only to namespaces
|
|
|
|
if request.Kind.Kind == "Namespace" {
|
2019-03-06 13:01:17 +02:00
|
|
|
meta := parseMetadataFromObject(request.Object.Raw)
|
|
|
|
namespaceName := parseNameFromMetadata(meta)
|
2019-03-05 20:04:23 +02:00
|
|
|
|
2019-03-06 13:01:17 +02:00
|
|
|
err := mw.applyConfigGenerator(rule.ConfigMapGenerator, namespaceName, "ConfigMap")
|
|
|
|
if err == nil {
|
|
|
|
err = mw.applyConfigGenerator(rule.SecretGenerator, namespaceName, "Secret")
|
2019-03-05 20:04:23 +02:00
|
|
|
}
|
2019-03-06 13:01:17 +02:00
|
|
|
return err
|
2019-03-05 20:04:23 +02:00
|
|
|
}
|
2019-03-06 13:01:17 +02:00
|
|
|
return nil
|
2019-03-05 20:04:23 +02:00
|
|
|
}
|
2019-02-22 18:12:14 +02:00
|
|
|
|
2019-03-21 18:09:58 +02:00
|
|
|
// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request.
|
2019-03-06 13:01:17 +02:00
|
|
|
func (mw *MutationWebhook) applyConfigGenerator(generator *types.PolicyConfigGenerator, namespace string, configKind string) error {
|
|
|
|
if generator == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-05 20:04:23 +02:00
|
|
|
err := generator.Validate()
|
|
|
|
if err != nil {
|
2019-03-06 13:01:17 +02:00
|
|
|
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))
|
2019-03-05 20:04:23 +02:00
|
|
|
}
|
|
|
|
|
2019-03-06 13:01:17 +02:00
|
|
|
if err != nil {
|
|
|
|
return errors.New(fmt.Sprintf("Unable to apply generator for %s '%s/%s' : %s", configKind, namespace, generator.Name, err))
|
2019-03-05 20:04:23 +02:00
|
|
|
}
|
2019-03-06 13:01:17 +02:00
|
|
|
|
2019-03-05 20:04:23 +02:00
|
|
|
return nil
|
2019-02-22 18:12:14 +02:00
|
|
|
}
|
|
|
|
|
2019-03-19 14:16:09 +02:00
|
|
|
// Forms AdmissionResponse with denial of resource creation and error message
|
2019-03-21 18:09:58 +02:00
|
|
|
func (mw *MutationWebhook) denyResourceCreation(errStr string) *v1beta1.AdmissionResponse {
|
2019-03-05 20:04:23 +02:00
|
|
|
return &v1beta1.AdmissionResponse{
|
|
|
|
Result: &metav1.Status{
|
2019-03-19 14:16:09 +02:00
|
|
|
Message: errStr,
|
2019-03-05 20:04:23 +02:00
|
|
|
},
|
2019-03-19 14:16:09 +02:00
|
|
|
Allowed: false,
|
2019-03-05 20:04:23 +02:00
|
|
|
}
|
2019-02-21 20:31:18 +02:00
|
|
|
}
|