1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-14 11:48:53 +00:00

feat: change webhook configuration to better support wildcards (#6534)

* feat: change webhook configuration to better support wildcards

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>

* kuttl

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>
This commit is contained in:
Charles-Edouard Brétéché 2023-03-13 10:27:49 +01:00 committed by GitHub
parent cc9b44eb19
commit 73d2063853
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 111 additions and 87 deletions

View file

@ -629,19 +629,13 @@ func (c *controller) buildResourceMutatingWebhookConfiguration(caBundle []byte)
return nil, err return nil, err
} }
c.recordPolicyState(config.MutatingWebhookConfigurationName, policies...) c.recordPolicyState(config.MutatingWebhookConfigurationName, policies...)
// TODO: shouldn't be per failure policy, depending of the policy/rules that apply ? for _, p := range policies {
if hasWildcard(policies...) { spec := p.GetSpec()
ignore.setWildcard() if spec.HasMutate() || spec.HasVerifyImages() {
fail.setWildcard() if spec.GetFailurePolicy() == kyvernov1.Ignore {
} else { c.mergeWebhook(ignore, p, false)
for _, p := range policies { } else {
spec := p.GetSpec() c.mergeWebhook(fail, p, false)
if spec.HasMutate() || spec.HasVerifyImages() {
if spec.GetFailurePolicy() == kyvernov1.Ignore {
c.mergeWebhook(ignore, p, false)
} else {
c.mergeWebhook(fail, p, false)
}
} }
} }
} }
@ -736,19 +730,13 @@ func (c *controller) buildResourceValidatingWebhookConfiguration(caBundle []byte
return nil, err return nil, err
} }
c.recordPolicyState(config.ValidatingWebhookConfigurationName, policies...) c.recordPolicyState(config.ValidatingWebhookConfigurationName, policies...)
// TODO: shouldn't be per failure policy, depending of the policy/rules that apply ? for _, p := range policies {
if hasWildcard(policies...) { spec := p.GetSpec()
ignore.setWildcard() if spec.HasValidate() || spec.HasGenerate() || spec.HasMutate() || spec.HasImagesValidationChecks() || spec.HasYAMLSignatureVerify() {
fail.setWildcard() if spec.GetFailurePolicy() == kyvernov1.Ignore {
} else { c.mergeWebhook(ignore, p, true)
for _, p := range policies { } else {
spec := p.GetSpec() c.mergeWebhook(fail, p, true)
if spec.HasValidate() || spec.HasGenerate() || spec.HasMutate() || spec.HasImagesValidationChecks() || spec.HasYAMLSignatureVerify() {
if spec.GetFailurePolicy() == kyvernov1.Ignore {
c.mergeWebhook(ignore, p, true)
} else {
c.mergeWebhook(fail, p, true)
}
} }
} }
} }
@ -848,14 +836,23 @@ func (c *controller) mergeWebhook(dst *webhook, policy kyvernov1.PolicyInterface
gvkMap[gvk] = 1 gvkMap[gvk] = 1
// NOTE: webhook stores GVR in its rules while policy stores GVK in its rules definition // NOTE: webhook stores GVR in its rules while policy stores GVK in its rules definition
group, version, kind, subresource := kubeutils.ParseKindSelector(gvk) group, version, kind, subresource := kubeutils.ParseKindSelector(gvk)
gvrs, err := c.discoveryClient.FindResources(group, version, kind, subresource) // if kind is `*` no need to lookup resources
if err != nil { if kind == "*" && subresource == "*" {
logger.Error(err, "unable to find resource", "group", group, "version", version, "kind", kind, "subresource", subresource) gvrList = append(gvrList, schema.GroupVersionResource{Group: group, Version: version, Resource: "*/*"})
continue } else if kind == "*" && subresource == "" {
} gvrList = append(gvrList, schema.GroupVersionResource{Group: group, Version: version, Resource: "*"})
for _, gvr := range gvrs { } else if kind == "*" && subresource != "" {
logger.V(4).Info("configuring webhook", "GVK", gvk, "GVR", gvr) gvrList = append(gvrList, schema.GroupVersionResource{Group: group, Version: version, Resource: "*/" + subresource})
gvrList = append(gvrList, gvr) } else {
gvrs, err := c.discoveryClient.FindResources(group, version, kind, subresource)
if err != nil {
logger.Error(err, "unable to find resource", "group", group, "version", version, "kind", kind, "subresource", subresource)
continue
}
for _, gvr := range gvrs {
logger.V(4).Info("configuring webhook", "GVK", gvk, "GVR", gvr)
gvrList = append(gvrList, gvr)
}
} }
} }
} }

View file

@ -32,7 +32,7 @@ func (wh *webhook) buildRulesWithOperations(ops ...admissionregistrationv1.Opera
var rules []admissionregistrationv1.RuleWithOperations var rules []admissionregistrationv1.RuleWithOperations
for gv, resources := range wh.rules { for gv, resources := range wh.rules {
// if we have pods, we add pods/ephemeralcontainers by default // if we have pods, we add pods/ephemeralcontainers by default
if gv.Group == "" && gv.Version == "v1" && resources.Has("pods") { if (gv.Group == "" || gv.Group == "*") && (gv.Version == "v1" || gv.Version == "*") && (resources.Has("pods") || resources.Has("*")) {
resources.Insert("pods/ephemeralcontainers") resources.Insert("pods/ephemeralcontainers")
} }
rules = append(rules, admissionregistrationv1.RuleWithOperations{ rules = append(rules, admissionregistrationv1.RuleWithOperations{
@ -84,24 +84,6 @@ func (wh *webhook) isEmpty() bool {
return len(wh.rules) == 0 return len(wh.rules) == 0
} }
func (wh *webhook) setWildcard() {
wh.rules = map[schema.GroupVersion]sets.Set[string]{
{Group: "*", Version: "*"}: sets.New("*/*"),
}
}
func hasWildcard(policies ...kyvernov1.PolicyInterface) bool {
for _, policy := range policies {
spec := policy.GetSpec()
for _, rule := range spec.Rules {
if kinds := rule.MatchResources.GetKinds(); slices.Contains(kinds, "*") {
return true
}
}
}
return false
}
func objectMeta(name string, owner ...metav1.OwnerReference) metav1.ObjectMeta { func objectMeta(name string, owner ...metav1.OwnerReference) metav1.ObjectMeta {
return metav1.ObjectMeta{ return metav1.ObjectMeta{
Name: name, Name: name,

View file

@ -9,13 +9,14 @@ import (
"gotest.tools/assert" "gotest.tools/assert"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1" admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
) )
func Test_webhook_isEmpty(t *testing.T) { func Test_webhook_isEmpty(t *testing.T) {
empty := newWebhook(DefaultWebhookTimeout, admissionregistrationv1.Ignore) empty := newWebhook(DefaultWebhookTimeout, admissionregistrationv1.Ignore)
assert.Equal(t, empty.isEmpty(), true) assert.Equal(t, empty.isEmpty(), true)
notEmpty := newWebhook(DefaultWebhookTimeout, admissionregistrationv1.Ignore) notEmpty := newWebhook(DefaultWebhookTimeout, admissionregistrationv1.Ignore)
notEmpty.setWildcard() notEmpty.set(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"})
assert.Equal(t, notEmpty.isEmpty(), false) assert.Equal(t, notEmpty.isEmpty(), false)
} }

View file

@ -7,28 +7,14 @@ metadata:
webhooks: webhooks:
- rules: - rules:
- apiGroups: - apiGroups:
- "" - '*'
apiVersions: apiVersions:
- v1 - '*'
operations: operations:
- CREATE - CREATE
- UPDATE - UPDATE
- DELETE - DELETE
- CONNECT - CONNECT
resources: resources:
- replicationcontrollers/scale - '*/scale'
scope: '*'
- apiGroups:
- apps
apiVersions:
- v1
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- deployments/scale
- replicasets/scale
- statefulsets/scale
scope: '*' scope: '*'

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
apply:
- policy.yaml
assert:
- policy-assert.yaml

View file

@ -0,0 +1,4 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
assert:
- webhooks.yaml

View file

@ -0,0 +1,9 @@
## Description
This test verifies the resource validation webhook is configured correctly when a policy targets all `*/*` resources and subresources.
## Steps
1. - Create a policy targeting `*/*`
- Assert policy gets ready
1. - Assert that the resource validation webhook is configured correctly

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,22 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
annotations:
pod-policies.kyverno.io/autogen-controllers: none
spec:
validationFailureAction: Audit
background: false
rules:
- name: require-team
match:
any:
- resources:
kinds:
- '*/*'
validate:
message: 'The label `team` is required.'
pattern:
metadata:
labels:
team: '?*'

View file

@ -0,0 +1,21 @@
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
labels:
webhook.kyverno.io/managed-by: kyverno
name: kyverno-resource-validating-webhook-cfg
webhooks:
- failurePolicy: Fail
rules:
- apiGroups:
- '*'
apiVersions:
- '*'
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- '*/*'
scope: '*'

View file

@ -1,6 +1,6 @@
## Description ## Description
This test verifies the resource validation webhook is configured correctly when a policy targets all `*` resource. This test verifies the resource validation webhook is configured correctly when a policy targets all `*` resources.
## Steps ## Steps

View file

@ -5,20 +5,6 @@ metadata:
webhook.kyverno.io/managed-by: kyverno webhook.kyverno.io/managed-by: kyverno
name: kyverno-resource-validating-webhook-cfg name: kyverno-resource-validating-webhook-cfg
webhooks: webhooks:
- failurePolicy: Ignore
rules:
- apiGroups:
- '*'
apiVersions:
- '*'
operations:
- CREATE
- UPDATE
- DELETE
- CONNECT
resources:
- '*/*'
scope: '*'
- failurePolicy: Fail - failurePolicy: Fail
rules: rules:
- apiGroups: - apiGroups:
@ -31,5 +17,6 @@ webhooks:
- DELETE - DELETE
- CONNECT - CONNECT
resources: resources:
- '*/*' - '*'
- pods/ephemeralcontainers
scope: '*' scope: '*'