2025-01-15 15:04:18 +02:00
|
|
|
package admissionpolicy
|
2024-02-02 12:04:02 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"slices"
|
|
|
|
|
|
|
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
|
|
|
"github.com/kyverno/kyverno/pkg/clients/dclient"
|
2025-02-25 14:27:29 +02:00
|
|
|
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
2024-02-02 12:04:02 +02:00
|
|
|
controllerutils "github.com/kyverno/kyverno/pkg/utils/controller"
|
|
|
|
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
|
|
|
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
)
|
|
|
|
|
|
|
|
// BuildValidatingAdmissionPolicy is used to build a Kubernetes ValidatingAdmissionPolicy from a Kyverno policy
|
2024-07-16 18:06:58 +03:00
|
|
|
func BuildValidatingAdmissionPolicy(
|
|
|
|
discoveryClient dclient.IDiscovery,
|
2025-01-31 16:21:43 +02:00
|
|
|
vap *admissionregistrationv1.ValidatingAdmissionPolicy,
|
2025-02-25 14:27:29 +02:00
|
|
|
policy engineapi.GenericPolicy,
|
2025-02-27 15:08:35 +02:00
|
|
|
exceptions []engineapi.GenericException,
|
2024-07-16 18:06:58 +03:00
|
|
|
) error {
|
2025-01-31 16:21:43 +02:00
|
|
|
var matchResources admissionregistrationv1.MatchResources
|
2025-02-25 14:27:29 +02:00
|
|
|
var matchConditions []admissionregistrationv1.MatchCondition
|
|
|
|
var paramKind *admissionregistrationv1.ParamKind
|
|
|
|
var validations []admissionregistrationv1.Validation
|
|
|
|
var auditAnnotations []admissionregistrationv1.AuditAnnotation
|
|
|
|
var variables []admissionregistrationv1.Variable
|
|
|
|
|
|
|
|
if cpol := policy.AsKyvernoPolicy(); cpol != nil {
|
|
|
|
// construct the rules
|
|
|
|
var matchRules, excludeRules []admissionregistrationv1.NamedRuleWithOperations
|
|
|
|
|
|
|
|
rule := cpol.GetSpec().Rules[0]
|
|
|
|
|
|
|
|
// convert the match block
|
|
|
|
match := rule.MatchResources
|
|
|
|
if !match.ResourceDescription.IsEmpty() {
|
|
|
|
if err := translateResource(discoveryClient, &matchResources, &matchRules, match.ResourceDescription, true); err != nil {
|
2024-09-10 13:14:49 +02:00
|
|
|
return err
|
|
|
|
}
|
2024-07-16 18:06:58 +03:00
|
|
|
}
|
|
|
|
|
2025-02-25 14:27:29 +02:00
|
|
|
if match.Any != nil {
|
|
|
|
if err := translateResourceFilters(discoveryClient, &matchResources, &matchRules, match.Any, true); err != nil {
|
2024-09-10 13:14:49 +02:00
|
|
|
return err
|
|
|
|
}
|
2024-07-16 18:06:58 +03:00
|
|
|
}
|
2025-02-25 14:27:29 +02:00
|
|
|
if match.All != nil {
|
|
|
|
if err := translateResourceFilters(discoveryClient, &matchResources, &matchRules, match.All, true); err != nil {
|
2024-09-10 13:14:49 +02:00
|
|
|
return err
|
|
|
|
}
|
2024-02-02 12:04:02 +02:00
|
|
|
}
|
|
|
|
|
2025-02-25 14:27:29 +02:00
|
|
|
// convert the exclude block
|
|
|
|
if exclude := rule.ExcludeResources; exclude != nil {
|
|
|
|
if !exclude.ResourceDescription.IsEmpty() {
|
|
|
|
if err := translateResource(discoveryClient, &matchResources, &excludeRules, exclude.ResourceDescription, false); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if exclude.Any != nil {
|
|
|
|
if err := translateResourceFilters(discoveryClient, &matchResources, &excludeRules, exclude.Any, false); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if exclude.All != nil {
|
|
|
|
if err := translateResourceFilters(discoveryClient, &matchResources, &excludeRules, exclude.All, false); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-08-13 14:55:22 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-25 14:27:29 +02:00
|
|
|
// convert the exceptions if exist
|
|
|
|
for _, exception := range exceptions {
|
2025-02-27 15:08:35 +02:00
|
|
|
if polex := exception.AsException(); polex != nil {
|
|
|
|
match := polex.Spec.Match
|
|
|
|
if match.Any != nil {
|
|
|
|
if err := translateResourceFilters(discoveryClient, &matchResources, &excludeRules, match.Any, false); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2025-02-25 14:27:29 +02:00
|
|
|
}
|
|
|
|
|
2025-02-27 15:08:35 +02:00
|
|
|
if match.All != nil {
|
|
|
|
if err := translateResourceFilters(discoveryClient, &matchResources, &excludeRules, match.All, false); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2025-02-25 14:27:29 +02:00
|
|
|
}
|
2024-08-13 14:55:22 +03:00
|
|
|
}
|
|
|
|
}
|
2025-02-25 14:27:29 +02:00
|
|
|
|
|
|
|
matchConditions = rule.CELPreconditions
|
|
|
|
paramKind = rule.Validation.CEL.ParamKind
|
|
|
|
validations = rule.Validation.CEL.Expressions
|
|
|
|
auditAnnotations = rule.Validation.CEL.AuditAnnotations
|
|
|
|
variables = rule.Validation.CEL.Variables
|
|
|
|
} else if vpol := policy.AsValidatingPolicy(); vpol != nil {
|
|
|
|
matchResources = *vpol.Spec.MatchConstraints
|
|
|
|
matchConditions = vpol.Spec.MatchConditions
|
|
|
|
validations = vpol.Spec.Validations
|
|
|
|
auditAnnotations = vpol.Spec.AuditAnnotations
|
|
|
|
variables = vpol.Spec.Variables
|
2025-02-27 15:08:35 +02:00
|
|
|
|
|
|
|
// convert celexceptions if exist
|
|
|
|
for _, exception := range exceptions {
|
|
|
|
if celpolex := exception.AsCELException(); celpolex != nil {
|
|
|
|
for _, matchCondition := range celpolex.Spec.MatchConditions {
|
|
|
|
// negate the match condition
|
|
|
|
expression := "!(" + matchCondition.Expression + ")"
|
|
|
|
matchConditions = append(matchConditions, admissionregistrationv1.MatchCondition{
|
|
|
|
Name: matchCondition.Name,
|
|
|
|
Expression: expression,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-08-13 14:55:22 +03:00
|
|
|
}
|
|
|
|
|
2025-02-25 14:27:29 +02:00
|
|
|
// set owner reference
|
|
|
|
vap.OwnerReferences = []metav1.OwnerReference{
|
|
|
|
{
|
|
|
|
APIVersion: policy.GetAPIVersion(),
|
|
|
|
Kind: policy.GetKind(),
|
|
|
|
Name: policy.GetName(),
|
|
|
|
UID: policy.GetUID(),
|
|
|
|
},
|
|
|
|
}
|
2024-07-16 18:06:58 +03:00
|
|
|
// set policy spec
|
2025-01-31 16:21:43 +02:00
|
|
|
vap.Spec = admissionregistrationv1.ValidatingAdmissionPolicySpec{
|
2024-02-02 12:04:02 +02:00
|
|
|
MatchConstraints: &matchResources,
|
2025-02-25 14:27:29 +02:00
|
|
|
ParamKind: paramKind,
|
|
|
|
Variables: variables,
|
|
|
|
Validations: validations,
|
|
|
|
AuditAnnotations: auditAnnotations,
|
|
|
|
MatchConditions: matchConditions,
|
2024-02-02 12:04:02 +02:00
|
|
|
}
|
|
|
|
// set labels
|
|
|
|
controllerutils.SetManagedByKyvernoLabel(vap)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// BuildValidatingAdmissionPolicyBinding is used to build a Kubernetes ValidatingAdmissionPolicyBinding from a Kyverno policy
|
2024-07-16 18:06:58 +03:00
|
|
|
func BuildValidatingAdmissionPolicyBinding(
|
2025-01-31 16:21:43 +02:00
|
|
|
vapbinding *admissionregistrationv1.ValidatingAdmissionPolicyBinding,
|
2025-02-25 14:27:29 +02:00
|
|
|
policy engineapi.GenericPolicy,
|
2024-07-16 18:06:58 +03:00
|
|
|
) error {
|
2025-02-25 14:27:29 +02:00
|
|
|
var validationActions []admissionregistrationv1.ValidationAction
|
|
|
|
var paramRef *admissionregistrationv1.ParamRef
|
|
|
|
var policyName string
|
|
|
|
|
|
|
|
if cpol := policy.AsKyvernoPolicy(); cpol != nil {
|
|
|
|
rule := cpol.GetSpec().Rules[0]
|
|
|
|
validateAction := rule.Validation.FailureAction
|
|
|
|
if validateAction != nil {
|
|
|
|
if validateAction.Enforce() {
|
|
|
|
validationActions = append(validationActions, admissionregistrationv1.Deny)
|
|
|
|
} else if validateAction.Audit() {
|
|
|
|
validationActions = append(validationActions, admissionregistrationv1.Audit)
|
|
|
|
validationActions = append(validationActions, admissionregistrationv1.Warn)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
validateAction := cpol.GetSpec().ValidationFailureAction
|
|
|
|
if validateAction.Enforce() {
|
|
|
|
validationActions = append(validationActions, admissionregistrationv1.Deny)
|
|
|
|
} else if validateAction.Audit() {
|
|
|
|
validationActions = append(validationActions, admissionregistrationv1.Audit)
|
|
|
|
validationActions = append(validationActions, admissionregistrationv1.Warn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
paramRef = rule.Validation.CEL.ParamRef
|
|
|
|
policyName = "cpol-" + cpol.GetName()
|
|
|
|
} else if vpol := policy.AsValidatingPolicy(); vpol != nil {
|
|
|
|
validationActions = vpol.Spec.ValidationAction
|
|
|
|
policyName = "vpol-" + vpol.GetName()
|
|
|
|
}
|
|
|
|
|
2024-02-02 12:04:02 +02:00
|
|
|
// set owner reference
|
|
|
|
vapbinding.OwnerReferences = []metav1.OwnerReference{
|
|
|
|
{
|
2025-02-25 14:27:29 +02:00
|
|
|
APIVersion: policy.GetAPIVersion(),
|
|
|
|
Kind: policy.GetKind(),
|
|
|
|
Name: policy.GetName(),
|
|
|
|
UID: policy.GetUID(),
|
2024-02-02 12:04:02 +02:00
|
|
|
},
|
|
|
|
}
|
2025-02-25 14:27:29 +02:00
|
|
|
// set binding spec
|
2025-01-31 16:21:43 +02:00
|
|
|
vapbinding.Spec = admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{
|
2025-02-25 14:27:29 +02:00
|
|
|
PolicyName: policyName,
|
|
|
|
ParamRef: paramRef,
|
2024-02-02 12:04:02 +02:00
|
|
|
ValidationActions: validationActions,
|
|
|
|
}
|
|
|
|
// set labels
|
|
|
|
controllerutils.SetManagedByKyvernoLabel(vapbinding)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-16 18:06:58 +03:00
|
|
|
func translateResourceFilters(discoveryClient dclient.IDiscovery,
|
2025-01-31 16:21:43 +02:00
|
|
|
matchResources *admissionregistrationv1.MatchResources,
|
|
|
|
rules *[]admissionregistrationv1.NamedRuleWithOperations,
|
2024-07-16 18:06:58 +03:00
|
|
|
resFilters kyvernov1.ResourceFilters,
|
|
|
|
isMatch bool,
|
|
|
|
) error {
|
2024-02-02 12:04:02 +02:00
|
|
|
for _, filter := range resFilters {
|
2024-07-16 18:06:58 +03:00
|
|
|
err := translateResource(discoveryClient, matchResources, rules, filter.ResourceDescription, isMatch)
|
2024-02-02 12:04:02 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-16 18:06:58 +03:00
|
|
|
func translateResource(
|
|
|
|
discoveryClient dclient.IDiscovery,
|
2025-01-31 16:21:43 +02:00
|
|
|
matchResources *admissionregistrationv1.MatchResources,
|
|
|
|
rules *[]admissionregistrationv1.NamedRuleWithOperations,
|
2024-07-16 18:06:58 +03:00
|
|
|
res kyvernov1.ResourceDescription,
|
|
|
|
isMatch bool,
|
|
|
|
) error {
|
|
|
|
err := constructValidatingAdmissionPolicyRules(discoveryClient, rules, res, isMatch)
|
2024-02-02 12:04:02 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-07-16 18:06:58 +03:00
|
|
|
if isMatch {
|
|
|
|
matchResources.ResourceRules = *rules
|
|
|
|
if len(res.Namespaces) > 0 {
|
|
|
|
namespaceSelector := &metav1.LabelSelector{
|
|
|
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
|
|
|
{
|
|
|
|
Key: "kubernetes.io/metadata.name",
|
|
|
|
Operator: "In",
|
|
|
|
Values: res.Namespaces,
|
|
|
|
},
|
2024-05-08 19:09:47 +08:00
|
|
|
},
|
2024-07-16 18:06:58 +03:00
|
|
|
}
|
|
|
|
matchResources.NamespaceSelector = namespaceSelector
|
|
|
|
} else {
|
|
|
|
matchResources.NamespaceSelector = res.NamespaceSelector
|
2024-05-08 19:09:47 +08:00
|
|
|
}
|
2024-07-16 18:06:58 +03:00
|
|
|
matchResources.ObjectSelector = res.Selector
|
2024-05-08 19:09:47 +08:00
|
|
|
} else {
|
2024-07-16 18:06:58 +03:00
|
|
|
matchResources.ExcludeResourceRules = *rules
|
2024-05-08 19:09:47 +08:00
|
|
|
}
|
2024-02-02 12:04:02 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-16 18:06:58 +03:00
|
|
|
func constructValidatingAdmissionPolicyRules(
|
|
|
|
discoveryClient dclient.IDiscovery,
|
2025-01-31 16:21:43 +02:00
|
|
|
rules *[]admissionregistrationv1.NamedRuleWithOperations,
|
2024-07-16 18:06:58 +03:00
|
|
|
res kyvernov1.ResourceDescription,
|
|
|
|
isMatch bool,
|
|
|
|
) error {
|
2024-02-02 12:04:02 +02:00
|
|
|
// translate operations to their corresponding values in validating admission policy.
|
2024-05-07 20:19:12 +08:00
|
|
|
ops := translateOperations(res.GetOperations())
|
|
|
|
|
|
|
|
resourceNames := res.Names
|
|
|
|
if res.Name != "" {
|
|
|
|
resourceNames = append(resourceNames, res.Name)
|
|
|
|
}
|
2024-02-02 12:04:02 +02:00
|
|
|
|
|
|
|
// get kinds from kyverno policies and translate them to rules in validating admission policies.
|
|
|
|
// matched resources in kyverno policies are written in the following format:
|
|
|
|
// group/version/kind/subresource
|
|
|
|
// whereas matched resources in validating admission policies are written in the following format:
|
|
|
|
// apiGroups: ["group"]
|
|
|
|
// apiVersions: ["version"]
|
|
|
|
// resources: ["resource"]
|
2024-05-07 20:19:12 +08:00
|
|
|
for _, kind := range res.Kinds {
|
2025-01-31 16:21:43 +02:00
|
|
|
var r admissionregistrationv1.NamedRuleWithOperations
|
2024-05-09 14:46:10 +08:00
|
|
|
|
|
|
|
if kind == "*" {
|
|
|
|
r = buildNamedRuleWithOperations(resourceNames, "*", "*", ops, "*")
|
|
|
|
*rules = append(*rules, r)
|
|
|
|
} else {
|
|
|
|
group, version, kind, subresource := kubeutils.ParseKindSelector(kind)
|
|
|
|
gvrss, err := discoveryClient.FindResources(group, version, kind, subresource)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(gvrss) != 1 {
|
|
|
|
return fmt.Errorf("no unique match for kind %s", kind)
|
2024-05-06 16:29:55 +08:00
|
|
|
}
|
|
|
|
|
2024-05-09 14:46:10 +08:00
|
|
|
for topLevelApi, apiResource := range gvrss {
|
|
|
|
resources := []string{apiResource.Name}
|
|
|
|
|
|
|
|
// Add pods/ephemeralcontainers if pods resource.
|
|
|
|
if apiResource.Name == "pods" {
|
|
|
|
resources = append(resources, "pods/ephemeralcontainers")
|
2024-02-02 12:04:02 +02:00
|
|
|
}
|
2024-05-09 14:46:10 +08:00
|
|
|
|
|
|
|
// Check if there's an existing rule for the same group and version.
|
|
|
|
var isNewRule bool = true
|
|
|
|
for i := range *rules {
|
|
|
|
if slices.Contains((*rules)[i].APIGroups, topLevelApi.Group) && slices.Contains((*rules)[i].APIVersions, topLevelApi.Version) {
|
|
|
|
(*rules)[i].Resources = append((*rules)[i].Resources, resources...)
|
|
|
|
isNewRule = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no existing rule found, create a new one.
|
|
|
|
if isNewRule {
|
|
|
|
r = buildNamedRuleWithOperations(resourceNames, topLevelApi.Group, topLevelApi.Version, ops, resources...)
|
|
|
|
*rules = append(*rules, r)
|
2024-02-02 12:04:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-07-16 18:06:58 +03:00
|
|
|
|
|
|
|
// if exclude block has namespaces but no kinds, we need to add a rule for the namespaces
|
|
|
|
if !isMatch && len(res.Namespaces) > 0 && len(res.Kinds) == 0 {
|
2025-01-31 16:21:43 +02:00
|
|
|
r := admissionregistrationv1.NamedRuleWithOperations{
|
2024-07-16 18:06:58 +03:00
|
|
|
ResourceNames: res.Namespaces,
|
|
|
|
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
|
|
|
|
Rule: admissionregistrationv1.Rule{
|
|
|
|
Resources: []string{"namespaces"},
|
|
|
|
APIGroups: []string{""},
|
|
|
|
APIVersions: []string{"v1"},
|
|
|
|
},
|
|
|
|
Operations: ops,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
*rules = append(*rules, r)
|
|
|
|
}
|
2024-02-02 12:04:02 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-09 14:46:10 +08:00
|
|
|
func buildNamedRuleWithOperations(
|
|
|
|
resourceNames []string,
|
|
|
|
group, version string,
|
|
|
|
operations []admissionregistrationv1.OperationType,
|
|
|
|
resources ...string,
|
2025-01-31 16:21:43 +02:00
|
|
|
) admissionregistrationv1.NamedRuleWithOperations {
|
|
|
|
return admissionregistrationv1.NamedRuleWithOperations{
|
2024-05-09 14:46:10 +08:00
|
|
|
ResourceNames: resourceNames,
|
|
|
|
RuleWithOperations: admissionregistrationv1.RuleWithOperations{
|
|
|
|
Rule: admissionregistrationv1.Rule{
|
|
|
|
Resources: resources,
|
|
|
|
APIGroups: []string{group},
|
|
|
|
APIVersions: []string{version},
|
|
|
|
},
|
|
|
|
Operations: operations,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-02 12:04:02 +02:00
|
|
|
func translateOperations(operations []string) []admissionregistrationv1.OperationType {
|
|
|
|
var vapOperations []admissionregistrationv1.OperationType
|
|
|
|
for _, op := range operations {
|
|
|
|
if op == string(kyvernov1.Create) {
|
|
|
|
vapOperations = append(vapOperations, admissionregistrationv1.Create)
|
|
|
|
} else if op == string(kyvernov1.Update) {
|
|
|
|
vapOperations = append(vapOperations, admissionregistrationv1.Update)
|
|
|
|
} else if op == string(kyvernov1.Connect) {
|
|
|
|
vapOperations = append(vapOperations, admissionregistrationv1.Connect)
|
|
|
|
} else if op == string(kyvernov1.Delete) {
|
|
|
|
vapOperations = append(vapOperations, admissionregistrationv1.Delete)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-16 18:06:58 +03:00
|
|
|
// set default values for operations since it's a required field in ValidatingAdmissionPolicies
|
2024-02-02 12:04:02 +02:00
|
|
|
if len(vapOperations) == 0 {
|
|
|
|
vapOperations = append(vapOperations, admissionregistrationv1.Create)
|
|
|
|
vapOperations = append(vapOperations, admissionregistrationv1.Update)
|
2024-08-02 17:12:42 +03:00
|
|
|
vapOperations = append(vapOperations, admissionregistrationv1.Connect)
|
|
|
|
vapOperations = append(vapOperations, admissionregistrationv1.Delete)
|
2024-02-02 12:04:02 +02:00
|
|
|
}
|
|
|
|
return vapOperations
|
|
|
|
}
|