1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

fix: Support subresources as the trigger in generate rules (#6760)

* fix: check background scanning only for validation policies

Signed-off-by: Vyom-Yadav <jackhammervyom@gmail.com>

* fix: Support subresources as the trigger in generate rules

Signed-off-by: Vyom-Yadav <jackhammervyom@gmail.com>

---------

Signed-off-by: Vyom-Yadav <jackhammervyom@gmail.com>
This commit is contained in:
Vyom Yadav 2023-04-04 16:31:06 +05:30 committed by GitHub
parent 4634760e9e
commit 80fc3013d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 177 additions and 25 deletions

View file

@ -33,6 +33,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
)
@ -123,22 +124,63 @@ func (c *GenerateController) ProcessUR(ur *kyvernov1beta1.UpdateRequest) error {
const doesNotApply = "policy does not apply to resource"
func (c *GenerateController) getTrigger(spec kyvernov1beta1.UpdateRequestSpec) (*unstructured.Unstructured, error) {
if spec.Context.AdmissionRequestInfo.Operation == admissionv1.Delete {
request := spec.Context.AdmissionRequestInfo.AdmissionRequest
_, oldResource, err := admissionutils.ExtractResources(nil, *request)
if err != nil {
return nil, fmt.Errorf("failed to load resource from context: %w", err)
}
labels := oldResource.GetLabels()
if labels[common.GeneratePolicyLabel] != "" {
// non-trigger deletion, get trigger from ur spec
c.log.V(4).Info("non-trigger resource is deleted, fetching the trigger from the UR spec", "trigger", spec.Resource.String())
return common.GetResource(c.client, spec, c.log)
}
return &oldResource, nil
admissionRequest := spec.Context.AdmissionRequestInfo.AdmissionRequest
if admissionRequest == nil {
return common.GetResource(c.client, spec, c.log)
} else {
operation := spec.Context.AdmissionRequestInfo.Operation
if operation == admissionv1.Delete {
return getTriggerForDeleteOperation(spec, c)
} else if operation == admissionv1.Create {
return getTriggerForCreateOperation(spec, c)
} else {
newResource, oldResource, err := admissionutils.ExtractResources(nil, *admissionRequest)
if err != nil {
c.log.Error(err, "failed to extract resources from admission review request")
return nil, err
}
trigger := &newResource
if newResource.Object == nil {
trigger = &oldResource
}
return trigger, nil
}
}
}
func getTriggerForDeleteOperation(spec kyvernov1beta1.UpdateRequestSpec, c *GenerateController) (*unstructured.Unstructured, error) {
request := spec.Context.AdmissionRequestInfo.AdmissionRequest
_, oldResource, err := admissionutils.ExtractResources(nil, *request)
if err != nil {
return nil, fmt.Errorf("failed to load resource from context: %w", err)
}
labels := oldResource.GetLabels()
if labels[common.GeneratePolicyLabel] != "" {
// non-trigger deletion, get trigger from ur spec
c.log.V(4).Info("non-trigger resource is deleted, fetching the trigger from the UR spec", "trigger", spec.Resource.String())
return common.GetResource(c.client, spec, c.log)
}
return &oldResource, nil
}
func getTriggerForCreateOperation(spec kyvernov1beta1.UpdateRequestSpec, c *GenerateController) (*unstructured.Unstructured, error) {
admissionRequest := spec.Context.AdmissionRequestInfo.AdmissionRequest
trigger, err := common.GetResource(c.client, spec, c.log)
if err != nil || trigger == nil {
if admissionRequest.SubResource == "" {
return nil, err
} else {
c.log.V(4).Info("trigger resource not found for subresource, reverting to resource in AdmissionReviewRequest", "subresource", admissionRequest.SubResource)
newResource, _, err := admissionutils.ExtractResources(nil, *admissionRequest)
if err != nil {
c.log.Error(err, "failed to extract resources from admission review request")
return nil, err
}
trigger = &newResource
}
}
return trigger, err
}
func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, ur kyvernov1beta1.UpdateRequest, namespaceLabels map[string]string) ([]kyvernov1.ResourceSpec, error) {
@ -161,6 +203,16 @@ func (c *GenerateController) applyGenerate(resource unstructured.Unstructured, u
return nil, err
}
admissionRequest := ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest
if admissionRequest != nil {
var gvk schema.GroupVersionKind
gvk, err = c.client.Discovery().GetGVKFromGVR(schema.GroupVersionResource(admissionRequest.Resource))
if err != nil {
return nil, err
}
policyContext = policyContext.WithResourceKind(gvk, admissionRequest.SubResource)
}
// check if the policy still applies to the resource
engineResponse := c.engine.Generate(context.Background(), policyContext)
if len(engineResponse.PolicyResponse.Rules) == 0 {

View file

@ -15,6 +15,7 @@ import (
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/utils"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
"go.uber.org/multierr"
yamlv2 "gopkg.in/yaml.v2"
@ -104,26 +105,27 @@ func (c *MutateExistingController) ProcessUR(ur *kyvernov1beta1.UpdateRequest) e
continue
} else {
logger.WithName(rule.Name).Info("trigger resource not found for subresource, reverting to resource in AdmissionReviewRequest", "subresource", admissionRequest.SubResource)
triggerBytes := admissionRequest.Object.Raw
trigger = &unstructured.Unstructured{}
if err := trigger.UnmarshalJSON(triggerBytes); err != nil {
logger.WithName(rule.Name).Error(err, "failed to convert trigger resource")
newResource, _, err := admissionutils.ExtractResources(nil, *admissionRequest)
if err != nil {
logger.WithName(rule.Name).Error(err, "failed to extract resources from admission review request")
errs = append(errs, err)
continue
}
trigger = &newResource
}
}
} else {
triggerBytes := admissionRequest.Object.Raw
if triggerBytes == nil {
triggerBytes = admissionRequest.OldObject.Raw
}
trigger = &unstructured.Unstructured{}
if err := trigger.UnmarshalJSON(triggerBytes); err != nil {
logger.WithName(rule.Name).Error(err, "failed to convert trigger resource")
newResource, oldResource, err := admissionutils.ExtractResources(nil, *admissionRequest)
if err != nil {
logger.WithName(rule.Name).Error(err, "failed to extract resources from admission review request")
errs = append(errs, err)
continue
}
trigger = &newResource
if newResource.Object == nil {
trigger = &oldResource
}
}
}

View file

@ -1251,7 +1251,7 @@ func validateKinds(kinds []string, mock, backgroundScanningEnabled, isValidation
if len(gvrss) == 0 {
return fmt.Errorf("unable to convert GVK to GVR for kinds %s", k)
}
if backgroundScanningEnabled {
if isValidationPolicy && backgroundScanningEnabled {
for gvrs := range gvrss {
if gvrs.SubResource != "" {
return fmt.Errorf("background scan enabled with subresource %s", k)

View file

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

View file

@ -0,0 +1,6 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- command: kubectl run nginx --image=nginx -n test-generate-exec
- command: kubectl wait --for=condition=Ready pod/nginx -n test-generate-exec
- command: kubectl exec -n test-generate-exec nginx -it -- ls

View file

@ -0,0 +1,18 @@
apiVersion: v1
data:
KAFKA_ADDRESS: 192.168.10.13:9092,192.168.10.14:9092,192.168.10.15:9092
ZK_ADDRESS: 192.168.10.10:2181,192.168.10.11:2181,192.168.10.12:2181
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
generate.kyverno.io/policy-name: zk-kafka-address
generate.kyverno.io/policy-namespace: ""
generate.kyverno.io/rule-name: k-kafka-address
generate.kyverno.io/trigger-apiversion: v1
generate.kyverno.io/trigger-kind: PodExecOptions
generate.kyverno.io/trigger-name: ""
generate.kyverno.io/trigger-namespace: test-generate-exec
somekey: somevalue
name: zk-kafka-address
namespace: test-generate-exec

View file

@ -0,0 +1,7 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- command: kubectl delete cpol zk-kafka-address --force --wait=true --ignore-not-found=true
- command: kubectl delete pod nginx -n test-generate-exec --wait=true --ignore-not-found=true
- command: kubectl delete cm zk-kafka-address -n test-generate-exec --wait=true --ignore-not-found=true
- command: kubectl delete ns test-generate-exec --wait=true --ignore-not-found=true

View file

@ -0,0 +1,11 @@
## Description
This test assures generation of resource with a sub-resource acting as a trigger.
## Expected Behavior
The test passes and `configmap` `zk-kafka-address` is created.
## Reference Issue(s)
6399

View file

@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-generate-exec
status:
phase: Active

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-generate-exec

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v2beta1
kind: ClusterPolicy
metadata:
name: zk-kafka-address
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,29 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: zk-kafka-address
spec:
# generateExisting does not work for sub-resources
generateExisting: false
rules:
- name: k-kafka-address
match:
any:
- resources:
kinds:
- "Pod/exec"
generate:
# synchronization does not work for sub-resources
synchronize: false
apiVersion: v1
kind: ConfigMap
name: zk-kafka-address
namespace: "{{request.namespace}}"
data:
kind: ConfigMap
metadata:
labels:
somekey: somevalue
data:
ZK_ADDRESS: "192.168.10.10:2181,192.168.10.11:2181,192.168.10.12:2181"
KAFKA_ADDRESS: "192.168.10.13:9092,192.168.10.14:9092,192.168.10.15:9092"