|
9 | 9 | "github.com/kyverno/kyverno/pkg/admissionpolicy" |
10 | 10 | "github.com/kyverno/kyverno/pkg/cel/engine" |
11 | 11 | "github.com/kyverno/kyverno/pkg/cel/libs" |
| 12 | + "github.com/kyverno/kyverno/pkg/cel/libs/imageverify" |
12 | 13 | "github.com/kyverno/kyverno/pkg/cel/matching" |
13 | 14 | "github.com/kyverno/kyverno/pkg/config" |
14 | 15 | engineapi "github.com/kyverno/kyverno/pkg/engine/api" |
@@ -245,7 +246,9 @@ func (e *engineImpl) handleMutation( |
245 | 246 | if !matches { |
246 | 247 | continue |
247 | 248 | } |
248 | | - compiled, errList := c.Compile(ivpol.Policy, ivpol.Exceptions) |
| 249 | + // digest mutation performs no verification, so it takes no part in the |
| 250 | + // request-scoped verification results |
| 251 | + compiled, errList := c.Compile(ivpol.Policy, ivpol.Exceptions, nil) |
249 | 252 | if errList != nil { |
250 | 253 | // compile errors are surfaced by the validating webhook, skip mutation |
251 | 254 | continue |
@@ -369,14 +372,18 @@ func (e *engineImpl) evaluatePolicies( |
369 | 372 | return nil, err |
370 | 373 | } |
371 | 374 | c := eval.NewCompiler(ictx, e.lister, requestResource, e.ivCache) |
| 375 | + // shared by every policy compiled below, so required sees cross-policy evidence |
| 376 | + verifications := imageverify.NewImageVerificationResults() |
| 377 | + // resolved after the loop: evidence may come from a policy evaluated later |
| 378 | + var pendingRequired []pendingRequiredCheck |
372 | 379 | for _, ivpol := range policies { |
373 | 380 | response := eval.ImageVerifyPolicyResponse{ |
374 | 381 | Policy: ivpol.Policy, |
375 | 382 | Actions: ivpol.Actions, |
376 | 383 | Exceptions: ivpol.Exceptions, |
377 | 384 | } |
378 | 385 | startTime := time.Now() |
379 | | - compiled, errList := c.Compile(ivpol.Policy, ivpol.Exceptions) |
| 386 | + compiled, errList := c.Compile(ivpol.Policy, ivpol.Exceptions, verifications) |
380 | 387 | if errList != nil { |
381 | 388 | response.Result = *engineapi.RuleError("evaluation", engineapi.ImageVerify, "failed to compile policy", errList.ToAggregate(), nil) |
382 | 389 | response.Result = response.Result.WithStats(engineapi.NewExecutionStats(startTime, time.Now())) |
@@ -414,12 +421,48 @@ func (e *engineImpl) evaluatePolicies( |
414 | 421 | response.Result = *engineapi.RuleError(ruleName, engineapi.ImageVerify, "error", result.Error, nil) |
415 | 422 | } else if result.Result { |
416 | 423 | response.Result = *engineapi.RulePass(ruleName, engineapi.ImageVerify, "success", result.AuditAnnotations) |
| 424 | + pendingRequired = append(pendingRequired, pendingRequiredCheck{ |
| 425 | + name: ivpol.Policy.GetName(), |
| 426 | + compiled: compiled, |
| 427 | + images: result.MatchedImages, |
| 428 | + auditAnnotations: result.AuditAnnotations, |
| 429 | + startTime: startTime, |
| 430 | + }) |
417 | 431 | } else { |
418 | 432 | response.Result = *engineapi.RuleFail(ruleName, engineapi.ImageVerify, result.Message, result.AuditAnnotations) |
419 | 433 | } |
420 | 434 | } |
421 | 435 | response.Result = response.Result.WithStats(engineapi.NewExecutionStats(startTime, time.Now())) |
422 | 436 | responses[ivpol.Policy.GetName()] = response |
423 | 437 | } |
| 438 | + enforceRequired(pendingRequired, responses) |
424 | 439 | return responses, nil |
425 | 440 | } |
| 441 | + |
| 442 | +// pendingRequiredCheck defers a passing policy's required check until every |
| 443 | +// policy in the request has contributed its verifications. |
| 444 | +type pendingRequiredCheck struct { |
| 445 | + name string |
| 446 | + compiled eval.CompiledPolicy |
| 447 | + images []string |
| 448 | + auditAnnotations map[string]string |
| 449 | + startTime time.Time |
| 450 | +} |
| 451 | + |
| 452 | +// enforceRequired turns a policy that passed its validations into a failure when |
| 453 | +// one of the images it matched was never verified, by any policy in the request. |
| 454 | +func enforceRequired(checks []pendingRequiredCheck, responses map[string]eval.ImageVerifyPolicyResponse) { |
| 455 | + for _, check := range checks { |
| 456 | + err := check.compiled.EnforceRequired(check.images) |
| 457 | + if err == nil { |
| 458 | + continue |
| 459 | + } |
| 460 | + response, ok := responses[check.name] |
| 461 | + if !ok { |
| 462 | + continue |
| 463 | + } |
| 464 | + result := *engineapi.RuleFail(check.name, engineapi.ImageVerify, err.Error(), check.auditAnnotations) |
| 465 | + response.Result = result.WithStats(engineapi.NewExecutionStats(check.startTime, time.Now())) |
| 466 | + responses[check.name] = response |
| 467 | + } |
| 468 | +} |
0 commit comments