1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-09 17:37:12 +00:00
kyverno/pkg/controllers/webhook/utils.go
Florian Hopfensperger 8781a38849
feat: configure webhook scope based on resource and policy type (#8065)
* feat: configure webhook scope based on policy type

Signed-off-by: Florian Hopfensperger <florian.hopfensperger@allianz.de>

* Update pkg/controllers/webhook/controller.go

Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Signed-off-by: shuting <shuting@nirmata.com>

* feat: configure webhook scope based on resource type

Signed-off-by: Florian Hopfensperger <florian.hopfensperger@allianz.de>

* review comments

Signed-off-by: Florian Hopfensperger <florian.hopfensperger@allianz.de>

* sorting of webhooks

Signed-off-by: Florian Hopfensperger <florian.hopfensperger@allianz.de>

* Update pkg/controllers/webhook/utils.go

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix imports

Signed-off-by: Florian Hopfensperger <florian.hopfensperger@allianz.de>

---------

Signed-off-by: Florian Hopfensperger <florian.hopfensperger@allianz.de>
Signed-off-by: shuting <shuting@nirmata.com>
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: shuting <shuting@nirmata.com>
Co-authored-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
2024-01-26 15:07:42 +00:00

169 lines
4.9 KiB
Go

package webhook
import (
"cmp"
"slices"
"strings"
"github.com/kyverno/kyverno/api/kyverno"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"golang.org/x/exp/maps"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/ptr"
)
// webhook is the instance that aggregates the GVK of existing policies
// based on group, kind, scopeType, failurePolicy and webhookTimeout
type webhook struct {
maxWebhookTimeout int32
failurePolicy admissionregistrationv1.FailurePolicyType
rules map[groupVersionScope]sets.Set[string]
}
// groupVersionScope contains the GV and scopeType of a resource
type groupVersionScope struct {
schema.GroupVersion
scopeType admissionregistrationv1.ScopeType
}
// String puts / between group/version and scope
func (gvs groupVersionScope) String() string {
return gvs.GroupVersion.String() + "/" + string(gvs.scopeType)
}
func newWebhook(timeout int32, failurePolicy admissionregistrationv1.FailurePolicyType) *webhook {
return &webhook{
maxWebhookTimeout: timeout,
failurePolicy: failurePolicy,
rules: map[groupVersionScope]sets.Set[string]{},
}
}
func (wh *webhook) buildRulesWithOperations(ops ...admissionregistrationv1.OperationType) []admissionregistrationv1.RuleWithOperations {
var rules []admissionregistrationv1.RuleWithOperations
for gv, resources := range wh.rules {
// if we have pods, we add pods/ephemeralcontainers by default
if (gv.Group == "" || gv.Group == "*") && (gv.Version == "v1" || gv.Version == "*") && (resources.Has("pods") || resources.Has("*")) {
resources.Insert("pods/ephemeralcontainers")
}
rules = append(rules, admissionregistrationv1.RuleWithOperations{
Rule: admissionregistrationv1.Rule{
APIGroups: []string{gv.Group},
APIVersions: []string{gv.Version},
Resources: sets.List(resources),
Scope: ptr.To(gv.scopeType),
},
Operations: ops,
})
}
less := func(a []string, b []string) (int, bool) {
if x := cmp.Compare(len(a), len(b)); x != 0 {
return x, true
}
for i := range a {
if x := cmp.Compare(a[i], b[i]); x != 0 {
return x, true
}
}
return 0, false
}
slices.SortFunc(rules, func(a admissionregistrationv1.RuleWithOperations, b admissionregistrationv1.RuleWithOperations) int {
if x, match := less(a.APIGroups, b.APIGroups); match {
return x
}
if x, match := less(a.APIVersions, b.APIVersions); match {
return x
}
if x, match := less(a.Resources, b.Resources); match {
return x
}
if x := strings.Compare(string(*a.Scope), string(*b.Scope)); x != 0 {
return x
}
return 0
})
return rules
}
func (wh *webhook) set(gvrs GroupVersionResourceScope) {
gvs := groupVersionScope{
GroupVersion: gvrs.GroupVersion(),
scopeType: gvrs.Scope,
}
// check if the resource contains wildcard and is already added as all scope
// in that case, we do not need to add it again as namespaced scope
if (gvrs.Resource == "*" || gvrs.Group == "*") && gvs.scopeType == admissionregistrationv1.NamespacedScope {
allScopeResource := groupVersionScope{
GroupVersion: gvs.GroupVersion,
scopeType: admissionregistrationv1.AllScopes,
}
resources := wh.rules[allScopeResource]
if resources != nil {
// explicitly do nothing as the resource is already added as all scope
return
}
}
// check if the resource is already added
resources := wh.rules[gvs]
if resources == nil {
wh.rules[gvs] = sets.New(gvrs.Resource)
} else {
resources.Insert(gvrs.Resource)
}
}
func (wh *webhook) isEmpty() bool {
return len(wh.rules) == 0
}
func objectMeta(name string, annotations map[string]string, labels map[string]string, owner ...metav1.OwnerReference) metav1.ObjectMeta {
desiredLabels := make(map[string]string)
defaultLabels := map[string]string{
kyverno.LabelWebhookManagedBy: kyverno.ValueKyvernoApp,
}
maps.Copy(desiredLabels, labels)
maps.Copy(desiredLabels, defaultLabels)
return metav1.ObjectMeta{
Name: name,
Labels: desiredLabels,
Annotations: annotations,
OwnerReferences: owner,
}
}
func setRuleCount(rules []kyvernov1.Rule, status *kyvernov1.PolicyStatus) {
validateCount, generateCount, mutateCount, verifyImagesCount := 0, 0, 0, 0
for _, rule := range rules {
if !strings.HasPrefix(rule.Name, "autogen-") {
if rule.HasGenerate() {
generateCount += 1
}
if rule.HasValidate() {
validateCount += 1
}
if rule.HasMutate() {
mutateCount += 1
}
if rule.HasVerifyImages() {
verifyImagesCount += 1
}
}
}
status.RuleCount.Validate = validateCount
status.RuleCount.Generate = generateCount
status.RuleCount.Mutate = mutateCount
status.RuleCount.VerifyImages = verifyImagesCount
}
func capTimeout(maxWebhookTimeout int32) int32 {
if maxWebhookTimeout > 30 {
return 30
}
return maxWebhookTimeout
}