1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00
kyverno/pkg/autogen/autogen.go

268 lines
8.6 KiB
Go
Raw Normal View History

package autogen
import (
"encoding/json"
"strings"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"golang.org/x/exp/slices"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
)
const (
// PodControllerCronJob represent CronJob string
PodControllerCronJob = "CronJob"
// PodControllers stores the list of Pod-controllers in csv string
PodControllers = "DaemonSet,Deployment,Job,StatefulSet,ReplicaSet,ReplicationController,CronJob"
)
var podControllersKindsSet = sets.New(append(strings.Split(PodControllers, ","), "Pod")...)
func isKindOtherthanPod(kinds []string) bool {
if len(kinds) > 1 && kubeutils.ContainsKind(kinds, "Pod") {
return true
}
return false
}
func checkAutogenSupport(needed *bool, subjects ...kyvernov1.ResourceDescription) bool {
for _, subject := range subjects {
if subject.Name != "" || len(subject.Names) > 0 || subject.Selector != nil || subject.Annotations != nil || isKindOtherthanPod(subject.Kinds) {
return false
}
if needed != nil {
*needed = *needed || podControllersKindsSet.HasAny(subject.Kinds...)
}
}
return true
}
// stripCronJob removes CronJob from controllers
func stripCronJob(controllers string) string {
var newControllers []string
controllerArr := strings.Split(controllers, ",")
for _, c := range controllerArr {
if c == PodControllerCronJob {
continue
}
newControllers = append(newControllers, c)
}
if len(newControllers) == 0 {
return ""
}
return strings.Join(newControllers, ",")
}
// CanAutoGen checks whether the rule(s) (in policy) can be applied to Pod controllers
// returns controllers as:
// - "" if:
// - name or selector is defined
// - mixed kinds (Pod + pod controller) is defined
// - Pod and PodControllers are not defined
// - mutate.Patches/mutate.PatchesJSON6902/validate.deny/generate rule is defined
//
// - otherwise it returns all pod controllers
func CanAutoGen(spec *kyvernov1.Spec) (applyAutoGen bool, controllers string) {
needed := false
for _, rule := range spec.Rules {
if rule.Mutation.PatchesJSON6902 != "" || rule.HasGenerate() {
return false, "none"
}
for _, foreach := range rule.Mutation.ForEachMutation {
if foreach.PatchesJSON6902 != "" {
return false, "none"
}
}
match, exclude := rule.MatchResources, rule.ExcludeResources
if !checkAutogenSupport(&needed, match.ResourceDescription, exclude.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in resource description may not be applicable.", "rule", rule.Name)
return false, ""
}
for _, value := range match.Any {
if !checkAutogenSupport(&needed, value.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in match any block is not applicable.", "rule", rule.Name)
return false, ""
}
}
for _, value := range match.All {
if !checkAutogenSupport(&needed, value.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in match all block is not applicable.", "rule", rule.Name)
return false, ""
}
}
for _, value := range exclude.Any {
if !checkAutogenSupport(&needed, value.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in exclude any block is not applicable.", "rule", rule.Name)
return false, ""
}
}
for _, value := range exclude.All {
if !checkAutogenSupport(&needed, value.ResourceDescription) {
debug.Info("skip generating rule on pod controllers: Name / Selector in exclud all block is not applicable.", "rule", rule.Name)
return false, ""
}
}
}
if !needed {
return false, ""
}
return true, PodControllers
}
// GetSupportedControllers returns the supported autogen controllers for a given spec.
func GetSupportedControllers(spec *kyvernov1.Spec) []string {
apply, controllers := CanAutoGen(spec)
if !apply || controllers == "none" {
return nil
}
return strings.Split(controllers, ",")
}
// GetRequestedControllers returns the requested autogen controllers based on object annotations.
func GetRequestedControllers(meta *metav1.ObjectMeta) []string {
annotations := meta.GetAnnotations()
if annotations == nil {
return nil
}
controllers, ok := annotations[kyvernov1.PodControllersAnnotation]
if !ok || controllers == "" {
return nil
}
if controllers == "none" {
return []string{}
}
return strings.Split(controllers, ",")
}
// GetControllers computes the autogen controllers that should be applied to a policy.
// It returns the requested, supported and effective controllers (intersection of requested and supported ones).
func GetControllers(meta *metav1.ObjectMeta, spec *kyvernov1.Spec) ([]string, []string, []string) {
// compute supported and requested controllers
supported, requested := GetSupportedControllers(spec), GetRequestedControllers(meta)
// no specific request, we can return supported controllers without further filtering
if requested == nil {
return requested, supported, supported
}
// filter supported controllers, keeping only those that have been requested
var activated []string
for _, controller := range supported {
if slices.Contains(requested, controller) {
activated = append(activated, controller)
}
}
return requested, supported, activated
}
// podControllersKey annotation could be:
// scenario A: not exist, set default to "all", which generates on all pod controllers
// - if name / selector exist in resource description -> skip
// as these fields may not be applicable to pod controllers
// scenario B: "none", user explicitly disable this feature -> skip
// scenario C: some certain controllers that user set -> generate on defined controllers
// copy entire match / exclude block, it's users' responsibility to
// make sure all fields are applicable to pod controllers
// generateRules generates rule for podControllers based on scenario A and C
func generateRules(spec *kyvernov1.Spec, controllers string) []kyvernov1.Rule {
var rules []kyvernov1.Rule
for i := range spec.Rules {
// handle all other controllers other than CronJob
if genRule := createRule(generateRuleForControllers(&spec.Rules[i], stripCronJob(controllers))); genRule != nil {
if convRule, err := convertRule(*genRule, "Pod"); err == nil {
rules = append(rules, *convRule)
} else {
logger.Error(err, "failed to create rule")
}
}
// handle CronJob, it appends an additional rule
if genRule := createRule(generateCronJobRule(&spec.Rules[i], controllers)); genRule != nil {
if convRule, err := convertRule(*genRule, "Cronjob"); err == nil {
rules = append(rules, *convRule)
} else {
logger.Error(err, "failed to create Cronjob rule")
}
}
}
return rules
}
func convertRule(rule kyvernoRule, kind string) (*kyvernov1.Rule, error) {
if bytes, err := json.Marshal(rule); err != nil {
return nil, err
} else {
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 09:16:31 +00:00
if rule.Validation != nil && rule.Validation.PodSecurity != nil {
bytes = updateRestrictedFields(bytes, kind)
if err := json.Unmarshal(bytes, &rule); err != nil {
return nil, err
}
} else {
bytes = updateGenRuleByte(bytes, kind)
if err := json.Unmarshal(bytes, &rule); err != nil {
return nil, err
}
}
}
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 09:16:31 +00:00
out := kyvernov1.Rule{
Name: rule.Name,
VerifyImages: rule.VerifyImages,
}
if rule.MatchResources != nil {
out.MatchResources = *rule.MatchResources
}
if rule.ExcludeResources != nil {
out.ExcludeResources = *rule.ExcludeResources
}
if rule.Context != nil {
out.Context = *rule.Context
}
if rule.AnyAllConditions != nil {
out.SetAnyAllConditions(*rule.AnyAllConditions)
}
if rule.Mutation != nil {
out.Mutation = *rule.Mutation
}
if rule.Validation != nil {
out.Validation = *rule.Validation
}
return &out, nil
}
func ComputeRules(p kyvernov1.PolicyInterface) []kyvernov1.Rule {
return computeRules(p)
}
func computeRules(p kyvernov1.PolicyInterface) []kyvernov1.Rule {
spec := p.GetSpec()
applyAutoGen, desiredControllers := CanAutoGen(spec)
if !applyAutoGen {
desiredControllers = "none"
}
ann := p.GetAnnotations()
actualControllers, ok := ann[kyvernov1.PodControllersAnnotation]
if !ok || !applyAutoGen {
actualControllers = desiredControllers
} else {
if !applyAutoGen {
actualControllers = desiredControllers
}
}
if actualControllers == "none" {
return spec.Rules
}
genRules := generateRules(spec.DeepCopy(), actualControllers)
if len(genRules) == 0 {
return spec.Rules
}
var out []kyvernov1.Rule
for _, rule := range spec.Rules {
if !isAutogenRuleName(rule.Name) {
out = append(out, rule)
}
}
out = append(out, genRules...)
return out
}