2019-02-21 20:31:18 +02:00
|
|
|
package webhooks
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"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"
|
|
|
|
)
|
|
|
|
|
|
|
|
type MutationWebhook struct {
|
|
|
|
logger *log.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-02-22 18:12:14 +02:00
|
|
|
var allPatches []types.PolicyPatch
|
|
|
|
for _, policy := range policies {
|
|
|
|
var stopOnError bool = true
|
|
|
|
if policy.Spec.FailurePolicy != nil && *policy.Spec.FailurePolicy == "continueOnError" {
|
|
|
|
stopOnError = false
|
|
|
|
}
|
|
|
|
|
|
|
|
for ruleIdx, rule := range policy.Spec.Rules {
|
2019-02-26 20:05:07 +02:00
|
|
|
if IsRuleApplicableToRequest(rule.Resource, request) {
|
2019-02-22 18:12:14 +02:00
|
|
|
mw.logger.Printf("Applying policy %v, rule index = %v", 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)
|
2019-02-21 20:31:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return &v1beta1.AdmissionResponse{
|
|
|
|
Allowed: true,
|
2019-02-22 18:12:14 +02:00
|
|
|
Patch: patchesBytes,
|
2019-02-21 20:31:18 +02:00
|
|
|
PatchType: func() *v1beta1.PatchType {
|
|
|
|
pt := v1beta1.PatchTypeJSONPatch
|
|
|
|
return &pt
|
|
|
|
}(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-22 18:12:14 +02:00
|
|
|
// Applies all possible patches in a rule
|
|
|
|
func (mw *MutationWebhook) applyPolicyRule(request *v1beta1.AdmissionRequest, rule types.PolicyRule) ([]types.PolicyPatch, error) {
|
|
|
|
var allPatches []types.PolicyPatch
|
|
|
|
if rule.Patches == nil && rule.ConfigMapGenerator == nil && rule.SecretGenerator == nil {
|
|
|
|
return nil, errors.New("The rule is empty!")
|
|
|
|
}
|
|
|
|
|
|
|
|
if rule.Patches != nil {
|
2019-02-26 20:05:07 +02:00
|
|
|
for _, patch := range rule.Patches {
|
2019-02-22 18:12:14 +02:00
|
|
|
allPatches = append(allPatches, patch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if rule.ConfigMapGenerator != nil {
|
|
|
|
// TODO: Make patches from configMapGenerator and add them to returned array
|
|
|
|
}
|
|
|
|
|
|
|
|
if rule.SecretGenerator != nil {
|
|
|
|
// TODO: Make patches from secretGenerator and add them to returned array
|
|
|
|
}
|
|
|
|
|
|
|
|
return allPatches, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2019-02-21 20:31:18 +02:00
|
|
|
return &v1beta1.AdmissionResponse{
|
|
|
|
Result: &metav1.Status{
|
|
|
|
Message: err.Error(),
|
|
|
|
},
|
2019-02-22 18:12:14 +02:00
|
|
|
Allowed: allowed,
|
2019-02-21 20:31:18 +02:00
|
|
|
}
|
|
|
|
}
|