1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 15:37:19 +00:00
kyverno/pkg/autogen/v1/rule.go

407 lines
15 KiB
Go
Raw Normal View History

package v1
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"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
)
// 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"`
CELPreconditions *[]admissionregistrationv1beta1.MatchCondition `json:"celPreconditions,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"`
SkipBackgroundRequests *bool `json:"skipBackgroundRequests,omitempty"`
}
func createRule(rule *kyvernov1.Rule) *kyvernoRule {
if rule == nil {
return nil
}
jsonFriendlyStruct := kyvernoRule{
Name: rule.Name,
VerifyImages: rule.VerifyImages,
SkipBackgroundRequests: rule.SkipBackgroundRequests,
}
if !datautils.DeepEqual(rule.MatchResources, kyvernov1.MatchResources{}) {
jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy()
}
if rule.ExcludeResources != nil && !datautils.DeepEqual(*rule.ExcludeResources, kyvernov1.MatchResources{}) {
jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy()
}
if rule.Mutation != nil && !datautils.DeepEqual(*rule.Mutation, kyvernov1.Mutation{}) {
jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy()
}
if rule.Validation != nil && !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
}
if len(rule.CELPreconditions) > 0 {
jsonFriendlyStruct.CELPreconditions = &rule.DeepCopy().CELPreconditions
}
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 rule.ExcludeResources != nil {
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 rule.Mutation != nil {
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 rule.Validation != nil {
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,
AllowExistingViolations: rule.Validation.AllowExistingViolations,
}
newValidate.SetPattern(
map[string]interface{}{
"spec": map[string]interface{}{
tplKey: target,
},
},
)
rule.Validation = newValidate
return rule
Extend Pod Security Admission (#4364) * init commit for pss Signed-off-by: ShutingZhao <shuting@nirmata.com> * add test for Volume Type control * add test for App Armor control except ExemptProfile. Fix PSS profile check in EvaluatePSS() * remove unused code, still a JMESPATH problem with app armor ExemptProfile() * test for Host Process / Host Namespaces controls * test for Privileged containers controls * test for HostPathVolume control * test for HostPorts control * test for HostPorts control * test for SELinux control * test for Proc mount type control * Set to baseline * test for Seccomp control * test for Sysctl control * test for Privilege escalation control * test for Run as non root control * test for Restricted Seccomp control * Add problems to address * add solutions to problems * Add validate rule for PSA * api.Version --> string. latest by default * Exclude all values for a restrictedField * add tests for kyverno engine * code to be used to match kyverno rule's namespace * Refacto pkg/pss * fix multiple problems: not matching containers, add contains methods, select the right container when we have the same exclude.RestrictedField for multiple containers: * EvaluatePod * Use EvaluatePod in kyverno engine * Set pod instead of container in context to use full Jmespath. e.g.: securityContext.capabilities.add --> spec.containers[*].securityContext.capabilities.add * Check if PSSCheckResult matched at least one exclude value * add tests for engine * fix engine validation test * config * update go.mod and go.sum * crds * Check validate value: add PodSecurity * exclude all restrictedFields when we only specify the controlName * ExemptProfile(): check if exclud.RestrictedField matches at least one restrictedField.path * handle containers, initContainers, ephemeralContainers when we only specify the controlName (all restrictedFields are excluded) * refacto pks/pss/evaluate.go and add pkg/engine/validation_test.go * add all controls with containers in restrictedFields as comments * add tests for capabilities and privileged containers and fix some errors * add tests for host ports control * add tests for proc mount control * add tests for privilege escalation control * add tests for capabilities control * remove comments * new algo * refacto algo, working. Add test for hostProcess control * remove unused code * fix getPodWithNotMatchingContainers(), add tests for host namespaces control * refacto ExemptProfile() * get values for a specific container. add test for SELinuxOptions control * fix allowedValues for SELinuxOptions * add tests for seccompProfile_baseline control * refacto checkContainers(), add test for seccomp control * add test for running as non root control * add some tests for runAsUser control, have to update current PSA version * add sysctls control * add allowed values for restrictedVolumes control * add some tests for appArmor, volume types controls * add tests for volume types control * add tests for hostPath volume control * finish merge conflicts and add tests for runAsUser * update charts and crds * exclude.images optional * change volume types control exclude values * add appAmor control * fix: did not match any exclude value for pod-level restrictedFields * create autogen for validate.PodSecurity * clean code, remove logs * fix sonatype lift errors * fix sonatype lift errors: duplication * fix crash in pkg/policy/validate/ tests and unmarshall errors for pkg/engine tests * beginning of autogen implement for validate.exclude * Autogen for validation.PodSecurity * working autogen with simple tests * change validate.PodSecurity failure response format * make codegen * fix lint errors, remove debug prints * fix tags * fix tags * fix crash when deleting pods matching validate.podSecurity rule. Only check validatePodSecurity() when it's not a delete request * Changes requested * Changes requested 2 * Changes requested 3 * Changes requested 4 * Changes requested and make codegen * fix host namespaces control * fix lint * fix codegen error * update docs/crd/v1/index.html Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix path Signed-off-by: ShutingZhao <shuting@nirmata.com> * update crd schema Signed-off-by: ShutingZhao <shuting@nirmata.com> * update charts/kyverno/templates/crds.yaml Signed-off-by: ShutingZhao <shuting@nirmata.com> Signed-off-by: ShutingZhao <shuting@nirmata.com> Co-authored-by: ShutingZhao <shuting@nirmata.com>
2022-08-31 11:16:31 +02:00
}
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,
AllowExistingViolations: rule.Validation.AllowExistingViolations,
}
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,
AllowExistingViolations: rule.Validation.AllowExistingViolations,
}
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,
AllowExistingViolations: rule.Validation.AllowExistingViolations,
}
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,
AllowExistingViolations: rule.Validation.AllowExistingViolations,
}
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
}
}
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 == "" {
debug.Info("skip generateRuleForControllers")
return nil
}
debug.Info("processing rule", "rulename", rule.Name)
match := rule.MatchResources
matchKinds := match.GetKinds()
var excludeKinds []string
if exclude := rule.ExcludeResources; exclude != nil {
excludeKinds = 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])
}
}
}
Extend Pod Security Admission (#4364) * init commit for pss Signed-off-by: ShutingZhao <shuting@nirmata.com> * add test for Volume Type control * add test for App Armor control except ExemptProfile. Fix PSS profile check in EvaluatePSS() * remove unused code, still a JMESPATH problem with app armor ExemptProfile() * test for Host Process / Host Namespaces controls * test for Privileged containers controls * test for HostPathVolume control * test for HostPorts control * test for HostPorts control * test for SELinux control * test for Proc mount type control * Set to baseline * test for Seccomp control * test for Sysctl control * test for Privilege escalation control * test for Run as non root control * test for Restricted Seccomp control * Add problems to address * add solutions to problems * Add validate rule for PSA * api.Version --> string. latest by default * Exclude all values for a restrictedField * add tests for kyverno engine * code to be used to match kyverno rule's namespace * Refacto pkg/pss * fix multiple problems: not matching containers, add contains methods, select the right container when we have the same exclude.RestrictedField for multiple containers: * EvaluatePod * Use EvaluatePod in kyverno engine * Set pod instead of container in context to use full Jmespath. e.g.: securityContext.capabilities.add --> spec.containers[*].securityContext.capabilities.add * Check if PSSCheckResult matched at least one exclude value * add tests for engine * fix engine validation test * config * update go.mod and go.sum * crds * Check validate value: add PodSecurity * exclude all restrictedFields when we only specify the controlName * ExemptProfile(): check if exclud.RestrictedField matches at least one restrictedField.path * handle containers, initContainers, ephemeralContainers when we only specify the controlName (all restrictedFields are excluded) * refacto pks/pss/evaluate.go and add pkg/engine/validation_test.go * add all controls with containers in restrictedFields as comments * add tests for capabilities and privileged containers and fix some errors * add tests for host ports control * add tests for proc mount control * add tests for privilege escalation control * add tests for capabilities control * remove comments * new algo * refacto algo, working. Add test for hostProcess control * remove unused code * fix getPodWithNotMatchingContainers(), add tests for host namespaces control * refacto ExemptProfile() * get values for a specific container. add test for SELinuxOptions control * fix allowedValues for SELinuxOptions * add tests for seccompProfile_baseline control * refacto checkContainers(), add test for seccomp control * add test for running as non root control * add some tests for runAsUser control, have to update current PSA version * add sysctls control * add allowed values for restrictedVolumes control * add some tests for appArmor, volume types controls * add tests for volume types control * add tests for hostPath volume control * finish merge conflicts and add tests for runAsUser * update charts and crds * exclude.images optional * change volume types control exclude values * add appAmor control * fix: did not match any exclude value for pod-level restrictedFields * create autogen for validate.PodSecurity * clean code, remove logs * fix sonatype lift errors * fix sonatype lift errors: duplication * fix crash in pkg/policy/validate/ tests and unmarshall errors for pkg/engine tests * beginning of autogen implement for validate.exclude * Autogen for validation.PodSecurity * working autogen with simple tests * change validate.PodSecurity failure response format * make codegen * fix lint errors, remove debug prints * fix tags * fix tags * fix crash when deleting pods matching validate.podSecurity rule. Only check validatePodSecurity() when it's not a delete request * Changes requested * Changes requested 2 * Changes requested 3 * Changes requested 4 * Changes requested and make codegen * fix host namespaces control * fix lint * fix codegen error * update docs/crd/v1/index.html Signed-off-by: ShutingZhao <shuting@nirmata.com> * fix path Signed-off-by: ShutingZhao <shuting@nirmata.com> * update crd schema Signed-off-by: ShutingZhao <shuting@nirmata.com> * update charts/kyverno/templates/crds.yaml Signed-off-by: ShutingZhao <shuting@nirmata.com> Signed-off-by: ShutingZhao <shuting@nirmata.com> Co-authored-by: ShutingZhao <shuting@nirmata.com>
2022-08-31 11:16:31 +02:00
return data
}