1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-10 09:56:55 +00:00
kyverno/webhooks/mutation.go
belyshevdenis b320b4b433 NK-23: Implemented Validate() methods for structures in types.go.
Implemented tests for added methods.
Added usage of Validate() methods to the code, removed old checks.
Fixed some comments, added new comments.
2019-03-05 20:04:23 +02:00

158 lines
4.8 KiB
Go

package webhooks
import (
"encoding/json"
"errors"
"fmt"
"log"
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// MutationWebhook is a data type that represents
// buisness logic for resource mutation
type MutationWebhook struct {
logger *log.Logger
}
// NewMutationWebhook is a method that returns new instance
// of MutationWebhook struct
func NewMutationWebhook(logger *log.Logger) (*MutationWebhook, error) {
if logger == nil {
return nil, errors.New("Logger must be set for the mutation webhook")
}
return &MutationWebhook{logger: logger}, nil
}
// Mutate applies admission to request
func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest, policies []types.Policy) *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)
if len(policies) == 0 {
return nil
}
var allPatches []types.PolicyPatch
for _, policy := range policies {
stopOnError := true
if policy.Spec.FailurePolicy != nil && *policy.Spec.FailurePolicy == "continueOnError" {
stopOnError = false
}
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) {
mw.logger.Printf("Applying policy %s, rule index = %s", policy.ObjectMeta.Name, ruleIdx)
rulePatches, err := mw.applyPolicyRule(request, rule)
/*
* If at least one error is detected in the rule, the entire rule will not be applied.
* This may be changed in the future by varying the policy.Spec.FailurePolicy values.
*/
if err != nil {
mw.logger.Printf("Error occurred while applying the policy: %v", err)
if stopOnError {
mw.logger.Printf("/!\\ Denying the request according to FailurePolicy spec /!\\")
return errorToResponse(err, false)
}
} else {
mw.logger.Printf("Prepared %v patches", len(rulePatches))
allPatches = append(allPatches, rulePatches...)
}
}
}
}
patchesBytes, err := SerializePatches(allPatches)
if err != nil {
mw.logger.Printf("Error occerred while serializing JSONPathch: %v", err)
return errorToResponse(err, true)
}
return &v1beta1.AdmissionResponse{
Allowed: true,
Patch: patchesBytes,
PatchType: func() *v1beta1.PatchType {
pt := v1beta1.PatchTypeJSONPatch
return &pt
}(),
}
}
// Applies all possible patches in a rule
func (mw *MutationWebhook) applyPolicyRule(request *v1beta1.AdmissionRequest, rule types.PolicyRule) ([]types.PolicyPatch, error) {
var allPatches []types.PolicyPatch
allPatches = append(allPatches, rule.Patches...)
// configMapGenerator and secretGenerator can be applied only to namespaces
if request.Kind.Kind == "Namespace" {
if rule.ConfigMapGenerator != nil {
err := mw.applyConfigGenerator(request, *rule.ConfigMapGenerator, "ConfigMap")
if err != nil {
mw.logger.Printf("Unable to apply configMapGenerator: %s", err)
}
}
if rule.SecretGenerator != nil {
err := mw.applyConfigGenerator(request, *rule.SecretGenerator, "Secret")
if err != nil {
mw.logger.Printf("Unable to apply secretGenerator: %s", err)
}
}
}
return allPatches, nil
}
// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request
func (mw *MutationWebhook) applyConfigGenerator(request *v1beta1.AdmissionRequest, generator types.PolicyConfigGenerator, resourceKind string) error {
err := generator.Validate()
if err != nil {
return errors.New(fmt.Sprintf("Generator for %s is invalid: %s", resourceKind, err))
}
if generator.CopyFrom != nil {
// TODO: Implement copying of the object to another namespace
}
// TODO: Implement filling of existing object with data from generator
return nil
}
// SerializePatches converts JSON patches to byte array
func SerializePatches(patches []types.PolicyPatch) ([]byte, error) {
var result []byte
result = append(result, []byte("[\n")...)
for index, patch := range patches {
if patch.Operation == "" || patch.Path == "" {
return nil, errors.New("JSONPatch doesn't contain mandatory fields 'path' or 'op'")
}
patchBytes, err := json.Marshal(patch)
if err != nil {
return nil, err
}
result = append(result, patchBytes...)
if index != (len(patches) - 1) {
result = append(result, []byte(",\n")...)
}
}
result = append(result, []byte("\n]")...)
return result, nil
}
func errorToResponse(err error, allowed bool) *v1beta1.AdmissionResponse {
return &v1beta1.AdmissionResponse{
Result: &metav1.Status{
Message: err.Error(),
},
Allowed: allowed,
}
}