mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
ce21ac50b3
* fix: missing autogen rules in status Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * feat: add replicaset and replicationcontroller to autogen Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * merge main Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix tests Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix tests Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com> Co-authored-by: shuting <shuting@nirmata.com>
319 lines
11 KiB
Go
319 lines
11 KiB
Go
package autogen
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
|
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
|
"github.com/kyverno/kyverno/pkg/engine/variables"
|
|
"github.com/kyverno/kyverno/pkg/utils"
|
|
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
|
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
)
|
|
|
|
// 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 *apiextensions.JSON `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 !reflect.DeepEqual(rule.MatchResources, kyvernov1.MatchResources{}) {
|
|
jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy()
|
|
}
|
|
if !reflect.DeepEqual(rule.ExcludeResources, kyvernov1.MatchResources{}) {
|
|
jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy()
|
|
}
|
|
if !reflect.DeepEqual(rule.Mutation, kyvernov1.Mutation{}) {
|
|
jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy()
|
|
}
|
|
if !reflect.DeepEqual(rule.Validation, kyvernov1.Validation{}) {
|
|
jsonFriendlyStruct.Validation = rule.Validation.DeepCopy()
|
|
}
|
|
kyvernoAnyAllConditions, _ := utils.ApiextensionsJsonToKyvernoConditions(rule.GetAnyAllConditions())
|
|
switch typedAnyAllConditions := kyvernoAnyAllConditions.(type) {
|
|
case kyvernov1.AnyAllConditions:
|
|
if !reflect.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"),
|
|
}
|
|
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,
|
|
}
|
|
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,
|
|
},
|
|
}
|
|
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)
|
|
}
|
|
rule.Validation = kyvernov1.Validation{
|
|
Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "anyPattern"),
|
|
}
|
|
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)
|
|
rule.Validation = kyvernov1.Validation{
|
|
Message: variables.FindAndShiftReferences(logger, rule.Validation.Message, shift, "pattern"),
|
|
ForEachValidation: newForeachValidate,
|
|
}
|
|
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
|
|
}
|
|
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 == "" {
|
|
logger.V(5).Info("skip generateRuleForControllers")
|
|
return nil
|
|
}
|
|
logger.V(3).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 strings.Split(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",
|
|
strings.Split(controllers, ","),
|
|
func(r kyvernov1.ResourceFilters, kinds []string) kyvernov1.ResourceFilters {
|
|
return getAnyAllAutogenRule(r, "Pod", kinds)
|
|
},
|
|
)
|
|
}
|
|
|
|
func generateCronJobRule(rule *kyvernov1.Rule, controllers string) *kyvernov1.Rule {
|
|
hasCronJob := strings.Contains(controllers, PodControllerCronJob) || strings.Contains(controllers, "all")
|
|
if !hasCronJob {
|
|
return nil
|
|
}
|
|
logger.V(3).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 {
|
|
return getAnyAllAutogenRule(r, "Job", kinds)
|
|
},
|
|
)
|
|
}
|
|
|
|
func updateGenRuleByte(pbyte []byte, kind string) (obj []byte) {
|
|
if kind == "Pod" {
|
|
obj = []byte(strings.ReplaceAll(string(pbyte), "request.object.spec", "request.object.spec.template.spec"))
|
|
}
|
|
if kind == "Cronjob" {
|
|
obj = []byte(strings.ReplaceAll(string(pbyte), "request.object.spec", "request.object.spec.jobTemplate.spec.template.spec"))
|
|
}
|
|
obj = []byte(strings.ReplaceAll(string(obj), "request.object.metadata", "request.object.spec.template.metadata"))
|
|
return obj
|
|
}
|
|
|
|
func updateRestrictedFields(pbyte []byte, kind string) (obj []byte) {
|
|
if kind == "Pod" {
|
|
obj = []byte(strings.ReplaceAll(string(pbyte), `"restrictedField":"spec`, `"restrictedField":"spec.template.spec`))
|
|
}
|
|
if kind == "Cronjob" {
|
|
obj = []byte(strings.ReplaceAll(string(pbyte), `"restrictedField":"spec`, `"restrictedField":"spec.jobTemplate.spec.template.spec`))
|
|
}
|
|
obj = []byte(strings.ReplaceAll(string(obj), "metadata", "spec.template.metadata"))
|
|
return obj
|
|
}
|