1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-15 17:51:20 +00:00
kyverno/pkg/autogen/rule.go
Mariam Fahmy 2140a0239b
chore: rename validationFailureAction to failureAction under the rule (#10893)
Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
Co-authored-by: Jim Bugwadia <jim@nirmata.com>
2024-08-27 20:07:57 +00:00

385 lines
13 KiB
Go

package autogen
import (
"bytes"
"sort"
"strings"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/variables"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
)
// the kyvernoRule holds the temporary kyverno rule struct
// each field is a pointer to the actual object
// when serializing data, we would expect to drop the omitempty key
// otherwise (without the pointer), it will be set to empty value
// - an empty struct in this case, some may fail the schema validation
// may related to:
// https://github.com/kyverno/kyverno/pull/549#discussion_r360088556
// https://github.com/kyverno/kyverno/issues/568
type kyvernoRule struct {
Name string `json:"name"`
MatchResources *kyvernov1.MatchResources `json:"match"`
ExcludeResources *kyvernov1.MatchResources `json:"exclude,omitempty"`
Context *[]kyvernov1.ContextEntry `json:"context,omitempty"`
AnyAllConditions *kyvernov1.ConditionsWrapper `json:"preconditions,omitempty"`
Mutation *kyvernov1.Mutation `json:"mutate,omitempty"`
Validation *kyvernov1.Validation `json:"validate,omitempty"`
VerifyImages []kyvernov1.ImageVerification `json:"verifyImages,omitempty" yaml:"verifyImages,omitempty"`
}
func createRule(rule *kyvernov1.Rule) *kyvernoRule {
if rule == nil {
return nil
}
jsonFriendlyStruct := kyvernoRule{
Name: rule.Name,
VerifyImages: rule.VerifyImages,
}
if !datautils.DeepEqual(rule.MatchResources, kyvernov1.MatchResources{}) {
jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy()
}
if !datautils.DeepEqual(rule.ExcludeResources, kyvernov1.MatchResources{}) {
jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy()
}
if !datautils.DeepEqual(rule.Mutation, kyvernov1.Mutation{}) {
jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy()
}
if !datautils.DeepEqual(rule.Validation, kyvernov1.Validation{}) {
jsonFriendlyStruct.Validation = rule.Validation.DeepCopy()
}
kyvernoAnyAllConditions := rule.GetAnyAllConditions()
switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) {
case kyvernov1.AnyAllConditions:
if !datautils.DeepEqual(typedAnyAllConditions, kyvernov1.AnyAllConditions{}) {
jsonFriendlyStruct.AnyAllConditions = rule.DeepCopy().RawAnyAllConditions
}
case []kyvernov1.Condition:
if len(typedAnyAllConditions) > 0 {
jsonFriendlyStruct.AnyAllConditions = rule.DeepCopy().RawAnyAllConditions
}
}
if len(rule.Context) > 0 {
jsonFriendlyStruct.Context = &rule.DeepCopy().Context
}
return &jsonFriendlyStruct
}
type generateResourceFilters func(kyvernov1.ResourceFilters, []string) kyvernov1.ResourceFilters
func generateRule(name string, rule *kyvernov1.Rule, tplKey, shift string, kinds []string, grf generateResourceFilters) *kyvernov1.Rule {
if rule == nil {
return nil
}
rule = rule.DeepCopy()
rule.Name = name
// overwrite Kinds by pod controllers defined in the annotation
if len(rule.MatchResources.Any) > 0 {
rule.MatchResources.Any = grf(rule.MatchResources.Any, kinds)
} else if len(rule.MatchResources.All) > 0 {
rule.MatchResources.All = grf(rule.MatchResources.All, kinds)
} else {
rule.MatchResources.Kinds = kinds
}
if len(rule.ExcludeResources.Any) > 0 {
rule.ExcludeResources.Any = grf(rule.ExcludeResources.Any, kinds)
} else if len(rule.ExcludeResources.All) > 0 {
rule.ExcludeResources.All = grf(rule.ExcludeResources.All, kinds)
} else {
if len(rule.ExcludeResources.Kinds) != 0 {
rule.ExcludeResources.Kinds = kinds
}
}
if target := rule.Mutation.GetPatchStrategicMerge(); target != nil {
newMutation := kyvernov1.Mutation{}
newMutation.SetPatchStrategicMerge(
map[string]interface{}{
"spec": map[string]interface{}{
tplKey: target,
},
},
)
rule.Mutation = newMutation
return rule
}
if len(rule.Mutation.ForEachMutation) > 0 && rule.Mutation.ForEachMutation != nil {
var newForEachMutation []kyvernov1.ForEachMutation
for _, foreach := range rule.Mutation.ForEachMutation {
temp := kyvernov1.ForEachMutation{
List: foreach.List,
Context: foreach.Context,
AnyAllConditions: foreach.AnyAllConditions,
}
temp.SetPatchStrategicMerge(
map[string]interface{}{
"spec": map[string]interface{}{
tplKey: foreach.GetPatchStrategicMerge(),
},
},
)
newForEachMutation = append(newForEachMutation, temp)
}
rule.Mutation = kyvernov1.Mutation{
ForEachMutation: newForEachMutation,
}
return rule
}
if target := rule.Validation.GetPattern(); target != nil {
newValidate := kyvernov1.Validation{
Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "pattern"),
FailureAction: rule.Validation.FailureAction,
FailureActionOverrides: rule.Validation.FailureActionOverrides,
}
newValidate.SetPattern(
map[string]interface{}{
"spec": map[string]interface{}{
tplKey: target,
},
},
)
rule.Validation = newValidate
return rule
}
if rule.Validation.Deny != nil {
deny := kyvernov1.Validation{
Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "deny"),
Deny: rule.Validation.Deny,
FailureAction: rule.Validation.FailureAction,
FailureActionOverrides: rule.Validation.FailureActionOverrides,
}
rule.Validation = deny
return rule
}
if rule.Validation.PodSecurity != nil {
newExclude := make([]kyvernov1.PodSecurityStandard, len(rule.Validation.PodSecurity.Exclude))
copy(newExclude, rule.Validation.PodSecurity.Exclude)
podSecurity := kyvernov1.Validation{
Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "podSecurity"),
PodSecurity: &kyvernov1.PodSecurity{
Level: rule.Validation.PodSecurity.Level,
Version: rule.Validation.PodSecurity.Version,
Exclude: newExclude,
},
FailureAction: rule.Validation.FailureAction,
FailureActionOverrides: rule.Validation.FailureActionOverrides,
}
rule.Validation = podSecurity
return rule
}
if rule.Validation.GetAnyPattern() != nil {
anyPatterns, err := rule.Validation.DeserializeAnyPattern()
if err != nil {
logger.Error(err, "failed to deserialize anyPattern, expect type array")
}
var patterns []interface{}
for _, pattern := range anyPatterns {
newPattern := map[string]interface{}{
"spec": map[string]interface{}{
tplKey: pattern,
},
}
patterns = append(patterns, newPattern)
}
failureAction := rule.Validation.FailureAction
failureActionOverrides := rule.Validation.FailureActionOverrides
rule.Validation = kyvernov1.Validation{
Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "anyPattern"),
FailureAction: failureAction,
FailureActionOverrides: failureActionOverrides,
}
rule.Validation.SetAnyPattern(patterns)
return rule
}
if len(rule.Validation.ForEachValidation) > 0 && rule.Validation.ForEachValidation != nil {
newForeachValidate := make([]kyvernov1.ForEachValidation, len(rule.Validation.ForEachValidation))
copy(newForeachValidate, rule.Validation.ForEachValidation)
failureAction := rule.Validation.FailureAction
failureActionOverrides := rule.Validation.FailureActionOverrides
rule.Validation = kyvernov1.Validation{
Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "pattern"),
ForEachValidation: newForeachValidate,
FailureAction: failureAction,
FailureActionOverrides: failureActionOverrides,
}
return rule
}
if rule.VerifyImages != nil {
newVerifyImages := make([]kyvernov1.ImageVerification, len(rule.VerifyImages))
for i, vi := range rule.VerifyImages {
newVerifyImages[i] = *vi.DeepCopy()
}
rule.VerifyImages = newVerifyImages
return rule
}
if rule.HasValidateCEL() {
cel := rule.Validation.CEL.DeepCopy()
rule.Validation.CEL = cel
return rule
}
if rule.HasValidateAssert() {
rule.Validation.Assert = createAutogenAssertion(*rule.Validation.Assert.DeepCopy(), tplKey)
return rule
}
return nil
}
func getAutogenRuleName(prefix string, name string) string {
name = prefix + "-" + name
if len(name) > 63 {
name = name[:63]
}
return name
}
func isAutogenRuleName(name string) bool {
return strings.HasPrefix(name, "autogen-")
}
func getAnyAllAutogenRule(v kyvernov1.ResourceFilters, match string, kinds []string) kyvernov1.ResourceFilters {
anyKind := v.DeepCopy()
for i, value := range v {
if kubeutils.ContainsKind(value.Kinds, match) {
anyKind[i].Kinds = kinds
}
}
return anyKind
}
func generateRuleForControllers(rule *kyvernov1.Rule, controllers string) *kyvernov1.Rule {
if isAutogenRuleName(rule.Name) || controllers == "" {
debug.Info("skip generateRuleForControllers")
return nil
}
debug.Info("processing rule", "rulename", rule.Name)
match, exclude := rule.MatchResources, rule.ExcludeResources
matchKinds, excludeKinds := match.GetKinds(), exclude.GetKinds()
if !kubeutils.ContainsKind(matchKinds, "Pod") || (len(excludeKinds) != 0 && !kubeutils.ContainsKind(excludeKinds, "Pod")) {
return nil
}
// Support backwards compatibility
skipAutoGeneration := false
var controllersValidated []string
if controllers == "all" {
skipAutoGeneration = true
} else if controllers != "none" && controllers != "all" {
controllersList := map[string]int{
"DaemonSet": 1,
"Deployment": 1,
"Job": 1,
"StatefulSet": 1,
"ReplicaSet": 1,
"ReplicationController": 1,
}
for _, value := range splitKinds(controllers, ",") {
if _, ok := controllersList[value]; ok {
controllersValidated = append(controllersValidated, value)
}
}
if len(controllersValidated) > 0 {
skipAutoGeneration = true
}
}
if skipAutoGeneration {
if controllers == "all" {
controllers = "DaemonSet,Deployment,Job,StatefulSet,ReplicaSet,ReplicationController"
} else {
controllers = strings.Join(controllersValidated, ",")
}
}
return generateRule(
getAutogenRuleName("autogen", rule.Name),
rule,
"template",
"spec/template",
splitKinds(controllers, ","),
func(r kyvernov1.ResourceFilters, kinds []string) kyvernov1.ResourceFilters {
return getAnyAllAutogenRule(r, "Pod", kinds)
},
)
}
func splitKinds(controllers, separator string) []string {
kinds := strings.Split(controllers, separator)
sort.Strings(kinds)
return kinds
}
func generateCronJobRule(rule *kyvernov1.Rule, controllers string) *kyvernov1.Rule {
hasCronJob := strings.Contains(controllers, PodControllerCronJob) || strings.Contains(controllers, "all")
if !hasCronJob {
return nil
}
debug.Info("generating rule for cronJob")
return generateRule(
getAutogenRuleName("autogen-cronjob", rule.Name),
generateRuleForControllers(rule, controllers),
"jobTemplate",
"spec/jobTemplate/spec/template",
[]string{PodControllerCronJob},
func(r kyvernov1.ResourceFilters, kinds []string) kyvernov1.ResourceFilters {
anyKind := r.DeepCopy()
for i := range anyKind {
anyKind[i].Kinds = kinds
}
return anyKind
},
)
}
var (
podReplacementRules [][2][]byte = [][2][]byte{
{[]byte("request.object.spec"), []byte("request.object.spec.template.spec")},
{[]byte("request.oldObject.spec"), []byte("request.oldObject.spec.template.spec")},
{[]byte("request.object.metadata"), []byte("request.object.spec.template.metadata")},
{[]byte("request.oldObject.metadata"), []byte("request.oldObject.spec.template.metadata")},
}
podCELReplacementRules [][2][]byte = [][2][]byte{
{[]byte("object.spec"), []byte("object.spec.template.spec")},
{[]byte("oldObject.spec"), []byte("oldObject.spec.template.spec")},
{[]byte("object.metadata"), []byte("object.spec.template.metadata")},
{[]byte("oldObject.metadata"), []byte("oldObject.spec.template.metadata")},
}
cronJobReplacementRules [][2][]byte = [][2][]byte{
{[]byte("request.object.spec"), []byte("request.object.spec.jobTemplate.spec.template.spec")},
{[]byte("request.oldObject.spec"), []byte("request.oldObject.spec.jobTemplate.spec.template.spec")},
{[]byte("request.object.metadata"), []byte("request.object.spec.jobTemplate.spec.template.metadata")},
{[]byte("request.oldObject.metadata"), []byte("request.oldObject.spec.jobTemplate.spec.template.metadata")},
}
cronJobCELReplacementRules [][2][]byte = [][2][]byte{
{[]byte("object.spec"), []byte("object.spec.jobTemplate.spec.template.spec")},
{[]byte("oldObject.spec"), []byte("oldObject.spec.jobTemplate.spec.template.spec")},
{[]byte("object.metadata"), []byte("object.spec.jobTemplate.spec.template.metadata")},
{[]byte("oldObject.metadata"), []byte("oldObject.spec.jobTemplate.spec.template.metadata")},
}
)
func updateFields(data []byte, kind string, cel bool) []byte {
switch kind {
case "Pod":
if cel {
for _, replacement := range podCELReplacementRules {
data = bytes.ReplaceAll(data, replacement[0], replacement[1])
}
} else {
for _, replacement := range podReplacementRules {
data = bytes.ReplaceAll(data, replacement[0], replacement[1])
}
}
case "Cronjob":
if cel {
for _, replacement := range cronJobCELReplacementRules {
data = bytes.ReplaceAll(data, replacement[0], replacement[1])
}
} else {
for _, replacement := range cronJobReplacementRules {
data = bytes.ReplaceAll(data, replacement[0], replacement[1])
}
}
}
return data
}