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

feat: add support for background scanning of existing resource in image verification (#10287)

* feat: add support for background scanning of existing resource in image verification

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* fix: change rule response type to image verify

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

* chore: fix nilptr reference

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>

---------

Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
This commit is contained in:
Vishal Choudhary 2024-05-24 15:11:04 +05:30 committed by GitHub
parent 1923a6f789
commit 47adea6f1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 419 additions and 5 deletions

View file

@ -66,6 +66,17 @@ func (s *scanner) ScanResource(ctx context.Context, resource unstructured.Unstru
} }
spec := pol.GetSpec() spec := pol.GetSpec()
if spec.HasVerifyImages() && len(errors) == 0 { if spec.HasVerifyImages() && len(errors) == 0 {
if response != nil {
// remove responses of verify image rules
ruleResponses := make([]engineapi.RuleResponse, 0, len(response.PolicyResponse.Rules))
for _, v := range response.PolicyResponse.Rules {
if v.RuleType() != engineapi.ImageVerify {
ruleResponses = append(ruleResponses, v)
}
}
response.PolicyResponse.Rules = ruleResponses
}
ivResponse, err := s.validateImages(ctx, resource, nsLabels, pol) ivResponse, err := s.validateImages(ctx, resource, nsLabels, pol)
if err != nil { if err != nil {
logger.Error(err, "failed to scan images") logger.Error(err, "failed to scan images")
@ -106,6 +117,9 @@ func (s *scanner) validateResource(ctx context.Context, resource unstructured.Un
WithPolicy(policy). WithPolicy(policy).
WithNamespaceLabels(nsLabels) WithNamespaceLabels(nsLabels)
response := s.engine.Validate(ctx, policyCtx) response := s.engine.Validate(ctx, policyCtx)
if len(response.PolicyResponse.Rules) > 0 {
s.logger.Info("validateResource", "policy", policy, "response", response)
}
return &response, nil return &response, nil
} }

View file

@ -53,11 +53,11 @@ func (h validateImageHandler) Process(
key, err := cache.MetaNamespaceKeyFunc(exception) key, err := cache.MetaNamespaceKeyFunc(exception)
if err != nil { if err != nil {
logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName()) logger.Error(err, "failed to compute policy exception key", "namespace", exception.GetNamespace(), "name", exception.GetName())
return resource, handlers.WithError(rule, engineapi.Validation, "failed to compute exception key", err) return resource, handlers.WithError(rule, engineapi.ImageVerify, "failed to compute exception key", err)
} else { } else {
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key) logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
return resource, handlers.WithResponses( return resource, handlers.WithResponses(
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule skipped due to policy exception "+key).WithException(exception), engineapi.RuleSkip(rule.Name, engineapi.ImageVerify, "rule skipped due to policy exception "+key).WithException(exception),
) )
} }
} }
@ -90,11 +90,11 @@ func (h validateImageHandler) Process(
logger.V(4).Info("validated image", "rule", rule.Name) logger.V(4).Info("validated image", "rule", rule.Name)
if len(passedImages) > 0 || len(passedImages)+len(skippedImages) == 0 { if len(passedImages) > 0 || len(passedImages)+len(skippedImages) == 0 {
if len(skippedImages) > 0 { if len(skippedImages) > 0 {
return resource, handlers.WithPass(rule, engineapi.Validation, strings.Join(append([]string{"image verified, skipped images:"}, skippedImages...), " ")) return resource, handlers.WithPass(rule, engineapi.ImageVerify, strings.Join(append([]string{"image verified, skipped images:"}, skippedImages...), " "))
} }
return resource, handlers.WithPass(rule, engineapi.Validation, "image verified") return resource, handlers.WithPass(rule, engineapi.ImageVerify, "image verified")
} else { } else {
return resource, handlers.WithSkip(rule, engineapi.Validation, strings.Join(append([]string{"image skipped, skipped images:"}, skippedImages...), " ")) return resource, handlers.WithSkip(rule, engineapi.ImageVerify, strings.Join(append([]string{"image skipped, skipped images:"}, skippedImages...), " "))
} }
} }

View file

@ -0,0 +1,8 @@
## Description
This test performs a simple verification of an image and creates a policy report for it
## Expected Behavior
Pod creation should pass and report should be generated.

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-verify-images

View file

@ -0,0 +1,45 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: keyed-basic-policy
spec:
background: true
failurePolicy: Fail
webhookTimeoutSeconds: 30
validationFailureAction: Audit
rules:
- match:
any:
- resources:
kinds:
- Pod
name: keyed-basic-rule
verifyImages:
- attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----
rekor:
ignoreTlog: true
url: https://rekor.sigstore.dev
imageReferences:
- ghcr.io/kyverno/test-verify-image:*
mutateDigest: false
verifyDigest: false
- name: require-ns-purpose-label
match:
any:
- resources:
kinds:
- Pod
validate:
message: "You must have label `purpose` with value `production` set on all new namespaces."
pattern:
metadata:
labels:
foo: bar

View file

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

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: Pod
metadata:
name: test-secret-pod
namespace: test-verify-images
spec:
containers:
- image: ghcr.io/kyverno/test-verify-image:signed
name: test-secret

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: Pod
metadata:
name: test-secret-pod
namespace: test-verify-images

View file

@ -0,0 +1,39 @@
apiVersion: v1
items:
- apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
namespace: test-verify-images
ownerReferences:
- apiVersion: v1
kind: Pod
name: test-secret-pod
results:
- policy: keyed-basic-policy
result: pass
rule: keyed-basic-rule
scored: true
source: kyverno
- message: 'validation error: You must have label `purpose` with value `production`
set on all new namespaces. rule require-ns-purpose-label failed at path /metadata/labels/'
policy: keyed-basic-policy
result: fail
rule: require-ns-purpose-label
scored: true
source: kyverno
scope:
apiVersion: v1
kind: Pod
name: test-secret-pod
namespace: test-verify-images
summary:
error: 0
fail: 1
pass: 1
skip: 0
warn: 0
kind: List
metadata:
resourceVersion: ""

View file

@ -0,0 +1,27 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: verify-image-background-audit
spec:
timeouts:
delete: 2m
steps:
- name: step-01
try:
- apply:
file: chainsaw-step-01-apply-1.yaml
- apply:
file: chainsaw-step-01-apply-2.yaml
- assert:
file: chainsaw-step-01-assert-1.yaml
- name: step-02
try:
- apply:
file: chainsaw-step-02-apply-1.yaml
- assert:
file: chainsaw-step-02-assert-1.yaml
- name: step-03
try:
- assert:
file: chainsaw-step-03-assert-1.yaml

View file

@ -0,0 +1,8 @@
## Description
This test performs a simple verification of an image and creates a policy report for it
## Expected Behavior
Pod creation should pass and report should be generated.

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-verify-images

View file

@ -0,0 +1,33 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: keyed-basic-policy
spec:
background: true
failurePolicy: Fail
webhookTimeoutSeconds: 30
validationFailureAction: Audit
rules:
- match:
any:
- resources:
kinds:
- Pod
name: keyed-basic-rule
verifyImages:
- attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----
rekor:
ignoreTlog: true
url: https://rekor.sigstore.dev
imageReferences:
- ghcr.io/kyverno/test-verify-image:*
mutateDigest: false
verifyDigest: false

View file

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

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: Pod
metadata:
name: test-secret-pod
namespace: test-verify-images
spec:
containers:
- image: ghcr.io/kyverno/test-verify-image:signed
name: test-secret

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: Pod
metadata:
name: test-secret-pod
namespace: test-verify-images

View file

@ -0,0 +1,32 @@
apiVersion: v1
items:
- apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
namespace: test-verify-images
ownerReferences:
- apiVersion: v1
kind: Pod
name: test-secret-pod
results:
- policy: keyed-basic-policy
result: pass
rule: keyed-basic-rule
scored: true
source: kyverno
scope:
apiVersion: v1
kind: Pod
name: test-secret-pod
namespace: test-verify-images
summary:
error: 0
fail: 0
pass: 1
skip: 0
warn: 0
kind: List
metadata:
resourceVersion: ""

View file

@ -0,0 +1,27 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: verify-image-background-basic
spec:
timeouts:
delete: 2m
steps:
- name: step-01
try:
- apply:
file: chainsaw-step-01-apply-1.yaml
- apply:
file: chainsaw-step-01-apply-2.yaml
- assert:
file: chainsaw-step-01-assert-1.yaml
- name: step-02
try:
- apply:
file: chainsaw-step-02-apply-1.yaml
- assert:
file: chainsaw-step-02-assert-1.yaml
- name: step-03
try:
- assert:
file: chainsaw-step-03-assert-1.yaml

View file

@ -0,0 +1,8 @@
## Description
This test creates background scan reports for image verification.
## Expected Behavior
Pod creation should pass as the image has been signed by the public key specified in the policy.

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: test-verify-images

View file

@ -0,0 +1,9 @@
apiVersion: v1
kind: Pod
metadata:
name: test-secret-pod
namespace: test-verify-images
spec:
containers:
- image: ghcr.io/kyverno/test-verify-image:signed
name: test-secret

View file

@ -0,0 +1,5 @@
apiVersion: v1
kind: Pod
metadata:
name: test-secret-pod
namespace: test-verify-images

View file

@ -0,0 +1,33 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: keyed-basic-policy
spec:
background: true
failurePolicy: Fail
webhookTimeoutSeconds: 30
validationFailureAction: Audit
rules:
- match:
any:
- resources:
kinds:
- Pod
name: keyed-basic-rule
verifyImages:
- attestors:
- entries:
- keys:
publicKeys: |-
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM
5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==
-----END PUBLIC KEY-----
rekor:
ignoreTlog: true
url: https://rekor.sigstore.dev
imageReferences:
- ghcr.io/kyverno/test-verify-image:*
mutateDigest: false
verifyDigest: false

View file

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

View file

@ -0,0 +1,32 @@
apiVersion: v1
items:
- apiVersion: wgpolicyk8s.io/v1alpha2
kind: PolicyReport
metadata:
labels:
app.kubernetes.io/managed-by: kyverno
namespace: test-verify-images
ownerReferences:
- apiVersion: v1
kind: Pod
name: test-secret-pod
results:
- policy: keyed-basic-policy
result: pass
rule: keyed-basic-rule
scored: true
source: kyverno
scope:
apiVersion: v1
kind: Pod
name: test-secret-pod
namespace: test-verify-images
summary:
error: 0
fail: 0
pass: 1
skip: 0
warn: 0
kind: List
metadata:
resourceVersion: ""

View file

@ -0,0 +1,27 @@
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
creationTimestamp: null
name: verify-image-background-existing
spec:
timeouts:
delete: 2m
steps:
- name: step-01
try:
- apply:
file: chainsaw-step-01-apply-1.yaml
- apply:
file: chainsaw-step-01-apply-2.yaml
- assert:
file: chainsaw-step-01-assert-1.yaml
- name: step-02
try:
- apply:
file: chainsaw-step-02-apply-1.yaml
- assert:
file: chainsaw-step-02-assert-1.yaml
- name: step-03
try:
- assert:
file: chainsaw-step-03-assert-1.yaml