1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

add applyRules to control whether one or all rules are applied (#4196)

* add ruleSelector

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix selector logic for skipped rules

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* change names

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix generated paths

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix linter issues

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* add image variable to context when rule processing starts

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix messages

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update generate rules

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* fix tests

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
Jim Bugwadia 2022-07-29 00:02:26 -07:00 committed by GitHub
parent 03cec01fb5
commit 4aa0767728
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 545 additions and 125 deletions

View file

@ -18,6 +18,17 @@ const (
Fail FailurePolicyType = "Fail"
)
// ApplyRulesType controls whether processing stops after one rule is applied or all rules are applied.
// +kubebuilder:validation:Enum=All;One
type ApplyRulesType string
const (
// AllMatchingRules applies all rules in a policy that match.
ApplyAll ApplyRulesType = "All"
// FirstMatchingRule applies only the first matching rule in the policy.
ApplyOne ApplyRulesType = "One"
)
// AnyAllConditions consists of conditions wrapped denoting a logical criteria to be fulfilled.
// AnyConditions get fulfilled when at least one of its sub-conditions passes.
// AllConditions get fulfilled only when all of its sub-conditions pass.

View file

@ -30,6 +30,13 @@ type Spec struct {
// each rule can validate, mutate, or generate resources.
Rules []Rule `json:"rules,omitempty" yaml:"rules,omitempty"`
// ApplyRules controls how rules in a policy are applied. Rule are processed in
// the order of declaration. When set to `One` processing stops after a rule has
// been applied i.e. the rule matches and results in a pass, fail, or error. When
// set to `All` all rules in the policy are processed. The default is `All`.
// +optional
ApplyRules *ApplyRulesType `json:"applyRules,omitempty" yaml:"applyRules,omitempty"`
// FailurePolicy defines how unrecognized errors from the admission endpoint are handled.
// Rules within the same policy share the same failure behavior.
// Allowed values are Ignore or Fail. Defaults to Fail.
@ -69,7 +76,7 @@ type Spec struct {
// +optional
MutateExistingOnPolicyUpdate bool `json:"mutateExistingOnPolicyUpdate,omitempty" yaml:"mutateExistingOnPolicyUpdate,omitempty"`
// GenerateExistingOnPolicyUpdate controls wether to trigger generate rule in existing resources
// GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources
// If is set to "true" generate rule will be triggered and applied to existing matched resources.
// Defaults to "false" if not specified.
// +optional
@ -191,6 +198,14 @@ func (s *Spec) GetValidationFailureAction() ValidationFailureAction {
return s.ValidationFailureAction
}
// GetFailurePolicy returns the failure policy to be applied
func (s *Spec) GetApplyRules() ApplyRulesType {
if s.ApplyRules == nil {
return ApplyAll
}
return *s.ApplyRules
}
// ValidateRuleNames checks if the rule names are unique across a policy
func (s *Spec) ValidateRuleNames(path *field.Path) (errs field.ErrorList) {
names := sets.NewString()

View file

@ -1085,6 +1085,11 @@ func (in *Spec) DeepCopyInto(out *Spec) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ApplyRules != nil {
in, out := &in.ApplyRules, &out.ApplyRules
*out = new(ApplyRulesType)
**out = **in
}
if in.FailurePolicy != nil {
in, out := &in.FailurePolicy, &out.FailurePolicy
*out = new(FailurePolicyType)

View file

@ -55,6 +55,12 @@ spec:
spec:
description: Spec declares policy behaviors.
properties:
applyRules:
description: ApplyRules controls how rules in a policy are applied. Rule are processed in the order of declaration. When set to `One` processing stops after a rule has been applied i.e. the rule matches and results in a pass, fail, or error. When set to `All` all rules in the policy are processed. The default is `All`.
enum:
- All
- One
type: string
background:
description: Background controls if rules are applied to existing resources during a background scan. Optional. Default value is "true". The value must be set to "false" if the policy rule uses variables that are only available in the admission review request (e.g. user name).
type: boolean
@ -65,7 +71,7 @@ spec:
- Fail
type: string
generateExistingOnPolicyUpdate:
description: GenerateExistingOnPolicyUpdate controls wether to trigger generate rule in existing resources If is set to "true" generate rule will be triggered and applied to existing matched resources. Defaults to "false" if not specified.
description: GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources If is set to "true" generate rule will be triggered and applied to existing matched resources. Defaults to "false" if not specified.
type: boolean
mutateExistingOnPolicyUpdate:
description: MutateExistingOnPolicyUpdate controls if a mutateExisting policy is applied on policy events. Default value is "false".
@ -3874,6 +3880,12 @@ spec:
spec:
description: Spec defines policy behaviors and contains one or more rules.
properties:
applyRules:
description: ApplyRules controls how rules in a policy are applied. Rule are processed in the order of declaration. When set to `One` processing stops after a rule has been applied i.e. the rule matches and results in a pass, fail, or error. When set to `All` all rules in the policy are processed. The default is `All`.
enum:
- All
- One
type: string
background:
description: Background controls if rules are applied to existing resources during a background scan. Optional. Default value is "true". The value must be set to "false" if the policy rule uses variables that are only available in the admission review request (e.g. user name).
type: boolean
@ -3884,7 +3896,7 @@ spec:
- Fail
type: string
generateExistingOnPolicyUpdate:
description: GenerateExistingOnPolicyUpdate controls wether to trigger generate rule in existing resources If is set to "true" generate rule will be triggered and applied to existing matched resources. Defaults to "false" if not specified.
description: GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources If is set to "true" generate rule will be triggered and applied to existing matched resources. Defaults to "false" if not specified.
type: boolean
mutateExistingOnPolicyUpdate:
description: MutateExistingOnPolicyUpdate controls if a mutateExisting policy is applied on policy events. Default value is "false".

View file

@ -52,6 +52,16 @@ spec:
spec:
description: Spec declares policy behaviors.
properties:
applyRules:
description: ApplyRules controls how rules in a policy are applied.
Rule are processed in the order of declaration. When set to `One`
processing stops after a rule has been applied i.e. the rule matches
and results in a pass, fail, or error. When set to `All` all rules
in the policy are processed. The default is `All`.
enum:
- All
- One
type: string
background:
description: Background controls if rules are applied to existing
resources during a background scan. Optional. Default value is "true".
@ -69,7 +79,7 @@ spec:
- Fail
type: string
generateExistingOnPolicyUpdate:
description: GenerateExistingOnPolicyUpdate controls wether to trigger
description: GenerateExistingOnPolicyUpdate controls whether to trigger
generate rule in existing resources If is set to "true" generate
rule will be triggered and applied to existing matched resources.
Defaults to "false" if not specified.

View file

@ -53,6 +53,16 @@ spec:
spec:
description: Spec defines policy behaviors and contains one or more rules.
properties:
applyRules:
description: ApplyRules controls how rules in a policy are applied.
Rule are processed in the order of declaration. When set to `One`
processing stops after a rule has been applied i.e. the rule matches
and results in a pass, fail, or error. When set to `All` all rules
in the policy are processed. The default is `All`.
enum:
- All
- One
type: string
background:
description: Background controls if rules are applied to existing
resources during a background scan. Optional. Default value is "true".
@ -70,7 +80,7 @@ spec:
- Fail
type: string
generateExistingOnPolicyUpdate:
description: GenerateExistingOnPolicyUpdate controls wether to trigger
description: GenerateExistingOnPolicyUpdate controls whether to trigger
generate rule in existing resources If is set to "true" generate
rule will be triggered and applied to existing matched resources.
Defaults to "false" if not specified.

View file

@ -69,6 +69,16 @@ spec:
spec:
description: Spec declares policy behaviors.
properties:
applyRules:
description: ApplyRules controls how rules in a policy are applied.
Rule are processed in the order of declaration. When set to `One`
processing stops after a rule has been applied i.e. the rule matches
and results in a pass, fail, or error. When set to `All` all rules
in the policy are processed. The default is `All`.
enum:
- All
- One
type: string
background:
description: Background controls if rules are applied to existing
resources during a background scan. Optional. Default value is "true".
@ -86,7 +96,7 @@ spec:
- Fail
type: string
generateExistingOnPolicyUpdate:
description: GenerateExistingOnPolicyUpdate controls wether to trigger
description: GenerateExistingOnPolicyUpdate controls whether to trigger
generate rule in existing resources If is set to "true" generate
rule will be triggered and applied to existing matched resources.
Defaults to "false" if not specified.
@ -6050,6 +6060,16 @@ spec:
spec:
description: Spec defines policy behaviors and contains one or more rules.
properties:
applyRules:
description: ApplyRules controls how rules in a policy are applied.
Rule are processed in the order of declaration. When set to `One`
processing stops after a rule has been applied i.e. the rule matches
and results in a pass, fail, or error. When set to `All` all rules
in the policy are processed. The default is `All`.
enum:
- All
- One
type: string
background:
description: Background controls if rules are applied to existing
resources during a background scan. Optional. Default value is "true".
@ -6067,7 +6087,7 @@ spec:
- Fail
type: string
generateExistingOnPolicyUpdate:
description: GenerateExistingOnPolicyUpdate controls wether to trigger
description: GenerateExistingOnPolicyUpdate controls whether to trigger
generate rule in existing resources If is set to "true" generate
rule will be triggered and applied to existing matched resources.
Defaults to "false" if not specified.

View file

@ -67,6 +67,16 @@ spec:
spec:
description: Spec declares policy behaviors.
properties:
applyRules:
description: ApplyRules controls how rules in a policy are applied.
Rule are processed in the order of declaration. When set to `One`
processing stops after a rule has been applied i.e. the rule matches
and results in a pass, fail, or error. When set to `All` all rules
in the policy are processed. The default is `All`.
enum:
- All
- One
type: string
background:
description: Background controls if rules are applied to existing
resources during a background scan. Optional. Default value is "true".
@ -84,7 +94,7 @@ spec:
- Fail
type: string
generateExistingOnPolicyUpdate:
description: GenerateExistingOnPolicyUpdate controls wether to trigger
description: GenerateExistingOnPolicyUpdate controls whether to trigger
generate rule in existing resources If is set to "true" generate
rule will be triggered and applied to existing matched resources.
Defaults to "false" if not specified.
@ -6044,6 +6054,16 @@ spec:
spec:
description: Spec defines policy behaviors and contains one or more rules.
properties:
applyRules:
description: ApplyRules controls how rules in a policy are applied.
Rule are processed in the order of declaration. When set to `One`
processing stops after a rule has been applied i.e. the rule matches
and results in a pass, fail, or error. When set to `All` all rules
in the policy are processed. The default is `All`.
enum:
- All
- One
type: string
background:
description: Background controls if rules are applied to existing
resources during a background scan. Optional. Default value is "true".
@ -6061,7 +6081,7 @@ spec:
- Fail
type: string
generateExistingOnPolicyUpdate:
description: GenerateExistingOnPolicyUpdate controls wether to trigger
description: GenerateExistingOnPolicyUpdate controls whether to trigger
generate rule in existing resources If is set to "true" generate
rule will be triggered and applied to existing matched resources.
Defaults to "false" if not specified.

View file

@ -104,6 +104,23 @@ each rule can validate, mutate, or generate resources.</p>
</tr>
<tr>
<td>
<code>applyRules</code></br>
<em>
<a href="#kyverno.io/v1.ApplyRulesType">
ApplyRulesType
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ApplyRules controls how rules in a policy are applied. Rule are processed in
the order of declaration. When set to <code>One</code> processing stops after a rule has
been applied i.e. the rule matches and results in a pass, fail, or error. When
set to <code>All</code> all rules in the policy are processed. The default is <code>All</code>.</p>
</td>
</tr>
<tr>
<td>
<code>failurePolicy</code></br>
<em>
<a href="#kyverno.io/v1.FailurePolicyType">
@ -211,7 +228,7 @@ bool
</td>
<td>
<em>(Optional)</em>
<p>GenerateExistingOnPolicyUpdate controls wether to trigger generate rule in existing resources
<p>GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources
If is set to &ldquo;true&rdquo; generate rule will be triggered and applied to existing matched resources.
Defaults to &ldquo;false&rdquo; if not specified.</p>
</td>
@ -311,6 +328,23 @@ each rule can validate, mutate, or generate resources.</p>
</tr>
<tr>
<td>
<code>applyRules</code></br>
<em>
<a href="#kyverno.io/v1.ApplyRulesType">
ApplyRulesType
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ApplyRules controls how rules in a policy are applied. Rule are processed in
the order of declaration. When set to <code>One</code> processing stops after a rule has
been applied i.e. the rule matches and results in a pass, fail, or error. When
set to <code>All</code> all rules in the policy are processed. The default is <code>All</code>.</p>
</td>
</tr>
<tr>
<td>
<code>failurePolicy</code></br>
<em>
<a href="#kyverno.io/v1.FailurePolicyType">
@ -418,7 +452,7 @@ bool
</td>
<td>
<em>(Optional)</em>
<p>GenerateExistingOnPolicyUpdate controls wether to trigger generate rule in existing resources
<p>GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources
If is set to &ldquo;true&rdquo; generate rule will be triggered and applied to existing matched resources.
Defaults to &ldquo;false&rdquo; if not specified.</p>
</td>
@ -598,6 +632,15 @@ Here, all of the conditions need to pass</p>
</tbody>
</table>
<hr />
<h3 id="kyverno.io/v1.ApplyRulesType">ApplyRulesType
(<code>string</code> alias)</p></h3>
<p>
(<em>Appears on:</em>
<a href="#kyverno.io/v1.Spec">Spec</a>)
</p>
<p>
<p>ApplyRulesType controls whether processing stops after one rule is applied or all rules are applied.</p>
</p>
<h3 id="kyverno.io/v1.Attestation">Attestation
</h3>
<p>
@ -2892,6 +2935,23 @@ each rule can validate, mutate, or generate resources.</p>
</tr>
<tr>
<td>
<code>applyRules</code></br>
<em>
<a href="#kyverno.io/v1.ApplyRulesType">
ApplyRulesType
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>ApplyRules controls how rules in a policy are applied. Rule are processed in
the order of declaration. When set to <code>One</code> processing stops after a rule has
been applied i.e. the rule matches and results in a pass, fail, or error. When
set to <code>All</code> all rules in the policy are processed. The default is <code>All</code>.</p>
</td>
</tr>
<tr>
<td>
<code>failurePolicy</code></br>
<em>
<a href="#kyverno.io/v1.FailurePolicyType">
@ -2999,7 +3059,7 @@ bool
</td>
<td>
<em>(Optional)</em>
<p>GenerateExistingOnPolicyUpdate controls wether to trigger generate rule in existing resources
<p>GenerateExistingOnPolicyUpdate controls whether to trigger generate rule in existing resources
If is set to &ldquo;true&rdquo; generate rule will be triggered and applied to existing matched resources.
Defaults to &ldquo;false&rdquo; if not specified.</p>
</td>

View file

@ -309,8 +309,10 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
jsonContext := policyContext.JSONContext
// To manage existing resources, we compare the creation time for the default resource to be generated and policy creation time
ruleNameToProcessingTime := make(map[string]time.Duration)
applyRules := policyContext.Policy.GetSpec().GetApplyRules()
applyCount := 0
for _, rule := range autogen.ComputeRules(policy) {
var err error
if !rule.HasGenerate() {
@ -333,6 +335,10 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
}
}
if applyRules == kyvernov1.ApplyOne && applyCount > 0 {
break
}
// add configmap json data to context
if err := engine.LoadContext(log, rule.Context, policyContext, rule.Name); err != nil {
log.Error(err, "cannot add configmaps to context")
@ -358,6 +364,8 @@ func (c *GenerateController) ApplyGeneratePolicy(log logr.Logger, policyContext
if policy.GetSpec().IsGenerateExistingOnPolicyUpdate() {
processExisting = false
}
applyCount++
}
return genResources, processExisting, nil

View file

@ -258,7 +258,7 @@ func Test_Conditions(t *testing.T) {
err := json.Unmarshal([]byte(scanPredicate), &dataMap)
assert.NilError(t, err)
pass, err := evaluateConditions(conditions, ctx, dataMap, img, log.Log)
pass, err := evaluateConditions(conditions, ctx, dataMap, log.Log)
assert.NilError(t, err)
assert.Equal(t, pass, true)
}

View file

@ -48,9 +48,13 @@ func filterRules(policyContext *PolicyContext, startTime time.Time) *response.En
return resp
}
applyRules := policyContext.Policy.GetSpec().GetApplyRules()
for _, rule := range autogen.ComputeRules(policyContext.Policy) {
if ruleResp := filterRule(rule, policyContext); ruleResp != nil {
resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, *ruleResp)
if applyRules == kyvernov1.ApplyOne && ruleResp.Status != response.RuleStatusSkip {
break
}
}
}

View file

@ -241,12 +241,13 @@ func (ctx *context) AddElement(data interface{}, index int) error {
func (ctx *context) AddImageInfo(info apiutils.ImageInfo) error {
data := map[string]interface{}{
"image": info.String(),
"registry": info.Registry,
"path": info.Path,
"name": info.Name,
"tag": info.Tag,
"digest": info.Digest,
"reference": info.String(),
"referenceWithTag": info.ReferenceWithTag(),
"registry": info.Registry,
"path": info.Path,
"name": info.Name,
"tag": info.Tag,
"digest": info.Digest,
}
return addToContext(ctx, data, "image")
}

View file

@ -50,6 +50,8 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (*response.EngineRespons
ivm := &ImageVerificationMetadata{}
rules := autogen.ComputeRules(policyContext.Policy)
applyRules := policy.GetSpec().GetApplyRules()
for i := range rules {
rule := &rules[i]
if len(rule.VerifyImages) == 0 {
@ -60,8 +62,9 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (*response.EngineRespons
continue
}
policyContext.JSONContext.Restore()
logger.V(3).Info("processing image verification rule", "ruleSelector", applyRules)
policyContext.JSONContext.Restore()
if err := LoadContext(logger, rule.Context, policyContext, rule.Name); err != nil {
appendError(resp, rule, fmt.Sprintf("failed to load context: %s", err.Error()), response.RuleStatusError)
continue
@ -97,6 +100,10 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (*response.EngineRespons
for _, imageVerify := range ruleCopy.VerifyImages {
iv.verify(imageVerify, ruleImages)
}
if applyRules == kyvernov1.ApplyOne && resp.PolicyResponse.RulesAppliedCount > 0 {
break
}
}
return resp, ivm
@ -264,14 +271,20 @@ func (iv *imageVerifier) verifyImage(imageVerify kyvernov1.ImageVerification, im
iv.logger.V(2).Info("verifying image signatures", "image", image,
"attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations))
if err := iv.policyContext.JSONContext.AddImageInfo(imageInfo); err != nil {
iv.logger.Error(err, "failed to add image to context")
msg := fmt.Sprintf("failed to add image to context %s: %s", image, err.Error())
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusError, nil), ""
}
var cosignResponse *cosign.Response
for i, attestorSet := range imageVerify.Attestors {
var err error
path := fmt.Sprintf(".attestors[%d]", i)
cosignResponse, err = iv.verifyAttestorSet(attestorSet, imageVerify, imageInfo, path)
if err != nil {
iv.logger.Error(err, "failed to verify signature")
msg := fmt.Sprintf("failed to verify signature for %s: %s", image, err.Error())
iv.logger.Error(err, "failed to verify image")
msg := fmt.Sprintf("failed to verify image %s: %s", image, err.Error())
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil), ""
}
}
@ -458,10 +471,10 @@ func (iv *imageVerifier) verifyAttestations(statements []map[string]interface{},
return fmt.Errorf("predicate type %s not found", ac.PredicateType)
}
iv.logger.Info("checking attestation predicate type %s", "predicates", types, "image", imageInfo.String())
iv.logger.Info("checking attestation", "predicates", types, "image", imageInfo.String())
for _, s := range statements {
val, err := iv.checkAttestations(ac, s, imageInfo)
val, err := iv.checkAttestations(ac, s)
if err != nil {
return errors.Wrap(err, "failed to check attestations")
}
@ -493,7 +506,7 @@ func buildStatementMap(statements []map[string]interface{}) (map[string][]map[st
return results, predicateTypes
}
func (iv *imageVerifier) checkAttestations(a kyvernov1.Attestation, s map[string]interface{}, img apiutils.ImageInfo) (bool, error) {
func (iv *imageVerifier) checkAttestations(a kyvernov1.Attestation, s map[string]interface{}) (bool, error) {
if len(a.Conditions) == 0 {
return true, nil
}
@ -501,14 +514,13 @@ func (iv *imageVerifier) checkAttestations(a kyvernov1.Attestation, s map[string
iv.policyContext.JSONContext.Checkpoint()
defer iv.policyContext.JSONContext.Restore()
return evaluateConditions(a.Conditions, iv.policyContext.JSONContext, s, img, iv.logger)
return evaluateConditions(a.Conditions, iv.policyContext.JSONContext, s, iv.logger)
}
func evaluateConditions(
conditions []kyvernov1.AnyAllConditions,
ctx context.Interface,
s map[string]interface{},
img apiutils.ImageInfo,
log logr.Logger) (bool, error) {
predicate, ok := s["predicate"].(map[string]interface{})
@ -520,10 +532,6 @@ func evaluateConditions(
return false, errors.Wrapf(err, fmt.Sprintf("failed to add Statement to the context %v", s))
}
if err := ctx.AddImageInfo(img); err != nil {
return false, errors.Wrapf(err, fmt.Sprintf("failed to add image to the context %v", s))
}
c, err := variables.SubstituteAllInConditions(log, ctx, conditions)
if err != nil {
return false, errors.Wrapf(err, "failed to substitute variables in attestation conditions")

View file

@ -382,9 +382,63 @@ func Test_SignaturesMultiKeyZeroGoodKey(t *testing.T) {
policy = strings.Replace(policy, "KEY2", testOtherKey, -1)
policy = strings.Replace(policy, "COUNT", "1", -1)
policyContext := buildContext(t, policy, testSampleResource, "")
err, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusFail, err.PolicyResponse.Rules[0].Message)
resp, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(resp.PolicyResponse.Rules), 1)
assert.Equal(t, resp.PolicyResponse.Rules[0].Status, response.RuleStatusFail, resp.PolicyResponse.Rules[0].Message)
}
func Test_RuleSelectorImageVerify(t *testing.T) {
cosign.ClearMock()
policyContext := buildContext(t, testSampleSingleKeyPolicy, testSampleResource, "")
rule := newStaticKeyRule("match-all", "*", testOtherKey)
spec := policyContext.Policy.GetSpec()
spec.Rules = append(spec.Rules, *rule)
applyAll := kyverno.ApplyAll
spec.ApplyRules = &applyAll
resp, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(resp.PolicyResponse.Rules), 2)
assert.Equal(t, resp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, resp.PolicyResponse.Rules[0].Message)
assert.Equal(t, resp.PolicyResponse.Rules[1].Status, response.RuleStatusFail, resp.PolicyResponse.Rules[1].Message)
applyOne := kyverno.ApplyOne
spec.ApplyRules = &applyOne
resp, _ = VerifyAndPatchImages(policyContext)
assert.Equal(t, len(resp.PolicyResponse.Rules), 1)
assert.Equal(t, resp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, resp.PolicyResponse.Rules[0].Message)
}
func newStaticKeyRule(name, imageReference, key string) *kyverno.Rule {
return &kyverno.Rule{
Name: name,
MatchResources: kyverno.MatchResources{
All: kyverno.ResourceFilters{
{
ResourceDescription: kyverno.ResourceDescription{
Kinds: []string{"Pod"},
},
},
},
},
VerifyImages: []kyverno.ImageVerification{
{
ImageReferences: []string{"*"},
Attestors: []kyverno.AttestorSet{
{
Entries: []kyverno.Attestor{
{
Keys: &kyverno.StaticKeyAttestor{
PublicKeys: key,
},
},
},
},
},
},
},
}
}
var testNestedAttestorPolicy = `

View file

@ -39,6 +39,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
defer policyContext.JSONContext.Restore()
var err error
applyRules := policy.GetSpec().GetApplyRules()
for _, rule := range autogen.ComputeRules(policy) {
if !rule.HasMutate() {
@ -57,8 +58,7 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
continue
}
logger.V(3).Info("matched mutate rule")
logger.V(3).Info("processing mutate rule", "applyRules", applyRules)
resource, err := policyContext.JSONContext.Query("request.object")
policyContext.JSONContext.Reset()
if err == nil && resource != nil {
@ -124,6 +124,10 @@ func Mutate(policyContext *PolicyContext) (resp *response.EngineResponse) {
}
}
}
if applyRules == kyvernov1.ApplyOne && resp.PolicyResponse.RulesAppliedCount > 0 {
break
}
}
for _, r := range resp.PolicyResponse.Rules {

View file

@ -20,53 +20,54 @@ import (
func Test_VariableSubstitutionPatchStrategicMerge(t *testing.T) {
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "add-label"
},
"spec": {
"rules": [
{
"name": "add-name-label",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"patchStrategicMerge": {
"metadata": {
"labels": {
"appname": "{{request.object.metadata.name}}"
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "add-label"
},
"spec": {
"rules": [
{
"name": "add-name-label",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"patchStrategicMerge": {
"metadata": {
"labels": {
"appname": "{{request.object.metadata.name}}"
}
}
}
}
}
}
]
}
}`)
]
}
}`)
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "check-root-user"
},
"spec": {
"containers": [
{
"name": "check-root-user",
"image": "nginxinc/nginx-unprivileged",
"securityContext": {
"runAsNonRoot": true
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "check-root-user"
},
"spec": {
"containers": [
{
"name": "check-root-user",
"image": "nginxinc/nginx-unprivileged",
"securityContext": {
"runAsNonRoot": true
}
}
}
]
}
}`)
]
}
}`)
expectedPatch := []byte(`{"op":"add","path":"/metadata/labels","value":{"appname":"check-root-user"}}`)
var policy kyverno.ClusterPolicy
@ -1465,3 +1466,124 @@ func Test_mutate_existing_resources(t *testing.T) {
}
}
}
func Test_RuleSelectorMutate(t *testing.T) {
policyRaw := []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "add-label"
},
"spec": {
"rules": [
{
"name": "add-app-label",
"match": {
"resources": {
"name": "check-root-user",
"kinds": [
"Pod"
]
}
},
"mutate": {
"patchStrategicMerge": {
"metadata": {
"labels": {
"app": "root"
}
}
}
}
},
{
"name": "add-appname-label",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"mutate": {
"patchStrategicMerge": {
"metadata": {
"labels": {
"appname": "{{request.object.metadata.name}}"
}
}
}
}
}
]
}
}`)
resourceRaw := []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "check-root-user"
},
"spec": {
"containers": [
{
"name": "check-root-user",
"image": "nginxinc/nginx-unprivileged",
"securityContext": {
"runAsNonRoot": true
}
}
]
}
}`)
expectedPatch1 := []byte(`{"op":"add","path":"/metadata/labels","value":{"app":"root"}}`)
expectedPatch2 := []byte(`{"op":"add","path":"/metadata/labels/appname","value":"check-root-user"}`)
var policy kyverno.ClusterPolicy
err := json.Unmarshal(policyRaw, &policy)
if err != nil {
t.Error(err)
}
resourceUnstructured, err := utils.ConvertToUnstructured(resourceRaw)
assert.NilError(t, err)
ctx := context.NewContext()
err = context.AddResource(ctx, resourceRaw)
if err != nil {
t.Error(err)
}
_, err = ctx.Query("request.object.metadata.name")
assert.NilError(t, err)
policyContext := &PolicyContext{
Policy: &policy,
JSONContext: ctx,
NewResource: *resourceUnstructured,
}
er := Mutate(policyContext)
assert.Equal(t, len(er.PolicyResponse.Rules), 2)
assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
assert.Equal(t, len(er.PolicyResponse.Rules[1].Patches), 1)
if !reflect.DeepEqual(expectedPatch1, er.PolicyResponse.Rules[0].Patches[0]) {
t.Error("rule 1 patches dont match")
}
if !reflect.DeepEqual(expectedPatch2, er.PolicyResponse.Rules[1].Patches[0]) {
t.Errorf("rule 2 patches dont match")
}
applyOne := kyverno.ApplyOne
policyContext.Policy.GetSpec().ApplyRules = &applyOne
er = Mutate(policyContext)
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Equal(t, len(er.PolicyResponse.Rules[0].Patches), 1)
if !reflect.DeepEqual(expectedPatch1, er.PolicyResponse.Rules[0].Patches[0]) {
t.Error("rule 1 patches dont match")
}
}

View file

@ -90,6 +90,9 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo
defer ctx.JSONContext.Restore()
rules := autogen.ComputeRules(ctx.Policy)
matchCount := 0
applyRules := ctx.Policy.GetSpec().GetApplyRules()
for i := range rules {
rule := &rules[i]
hasValidate := rule.HasValidate()
@ -103,7 +106,7 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo
continue
}
log.V(3).Info("matched validate rule")
log.V(3).Info("processing validation rule", "matchCount", matchCount, "applyRules", applyRules)
ctx.JSONContext.Reset()
startTime := time.Now()
@ -116,6 +119,9 @@ func validateResource(log logr.Logger, ctx *PolicyContext) *response.EngineRespo
if ruleResp != nil {
addRuleResponse(log, resp, ruleResp, startTime)
if applyRules == kyvernov1.ApplyOne && resp.PolicyResponse.RulesAppliedCount > 0 {
break
}
}
}

View file

@ -33,6 +33,10 @@ func (i *ImageInfo) String() string {
}
}
func (i *ImageInfo) ReferenceWithTag() string {
return i.Registry + "/" + i.Path + ":" + i.Tag
}
func GetImageInfo(image string) (*ImageInfo, error) {
image = addDefaultDomain(image)
ref, err := reference.Parse(image)

View file

@ -64,9 +64,7 @@ func (v *validationHandler) handleValidation(
continue
}
// registering the kyverno_policy_results_total metric concurrently
go registerPolicyResultsMetricValidation(logger, metricsConfig, string(request.Operation), policyContext.Policy, *engineResponse)
// registering the kyverno_policy_execution_duration_seconds metric concurrently
go registerPolicyExecutionDurationMetricValidate(logger, metricsConfig, string(request.Operation), policyContext.Policy, *engineResponse)
engineResponses = append(engineResponses, engineResponse)
@ -80,20 +78,7 @@ func (v *validationHandler) handleValidation(
}
}
// If Validation fails then reject the request
// no violations will be created on "enforce"
blocked := toBlockResource(engineResponses, logger)
// REPORTING EVENTS
// Scenario 1:
// resource is blocked, as there is a policy in "enforce" mode that failed.
// create an event on the policy to inform the resource request was blocked
// Scenario 2:
// some/all policies failed to apply on the resource. a policy violation is generated.
// create an event on the resource and the policy that failed
// Scenario 3:
// all policies were applied successfully.
// create an event on the resource
if deletionTimeStamp == nil {
events := generateEvents(engineResponses, blocked, logger)
v.eventGen.Add(events...)
@ -101,10 +86,8 @@ func (v *validationHandler) handleValidation(
if blocked {
logger.V(4).Info("resource blocked")
// registering the kyverno_admission_review_duration_seconds metric concurrently
admissionReviewLatencyDuration := int64(time.Since(time.Unix(admissionRequestTimestamp, 0)))
go registerAdmissionReviewDurationMetricValidate(logger, metricsConfig, string(request.Operation), engineResponses, admissionReviewLatencyDuration)
//registering the kyverno_admission_requests_total metric concurrently
go registerAdmissionRequestsMetricValidate(logger, metricsConfig, string(request.Operation), engineResponses)
return false, getEnforceFailureErrorMsg(engineResponses)
}
@ -130,10 +113,8 @@ func (v *validationHandler) handleValidation(
prInfos := policyreport.GeneratePRsFromEngineResponse(engineResponses, logger)
v.prGenerator.Add(prInfos...)
// registering the kyverno_admission_review_duration_seconds metric concurrently
admissionReviewLatencyDuration := int64(time.Since(time.Unix(admissionRequestTimestamp, 0)))
go registerAdmissionReviewDurationMetricValidate(logger, metricsConfig, string(request.Operation), engineResponses, admissionReviewLatencyDuration)
//registering the kyverno_admission_requests_total metric concurrently
go registerAdmissionRequestsMetricValidate(logger, metricsConfig, string(request.Operation), engineResponses)
return true, ""
}

View file

@ -5,9 +5,9 @@ import (
"fmt"
"testing"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
log "sigs.k8s.io/controller-runtime/pkg/log"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
@ -21,6 +21,7 @@ func TestValidate_failure_action_overrides(t *testing.T) {
rawPolicy []byte
rawResource []byte
blocked bool
messages map[string]string
}{
{
rawPolicy: []byte(`
@ -473,25 +474,25 @@ func TestValidate_failure_action_overrides(t *testing.T) {
],
"rules": [
{
"name": "check-label-app",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "The label 'app' is required.",
"pattern": {
"metadata": {
"labels": {
"app": "?*"
}
}
}
}
}
"name": "check-label-app",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"validate": {
"message": "The label 'app' is required.",
"pattern": {
"metadata": {
"labels": {
"app": "?*"
}
}
}
}
}
]
}
}
@ -515,24 +516,25 @@ func TestValidate_failure_action_overrides(t *testing.T) {
}
`),
blocked: true,
messages: map[string]string{
"check-label-app": "validation error: The label 'app' is required. rule check-label-app failed at path /metadata/labels/",
},
},
}
for i, tc := range testcases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
var policy kyverno.ClusterPolicy
var policy kyvernov1.ClusterPolicy
err := json.Unmarshal(tc.rawPolicy, &policy)
assert.NilError(t, err)
resourceUnstructured, err := utils.ConvertToUnstructured(tc.rawResource)
assert.NilError(t, err)
msgs := []string{
"validation error: The label 'app' is required. rule check-label-app failed at path /metadata/labels/",
}
er := engine.Validate(&engine.PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()})
if tc.blocked {
for index, r := range er.PolicyResponse.Rules {
assert.Equal(t, r.Message, msgs[index])
if tc.blocked && tc.messages != nil {
for _, r := range er.PolicyResponse.Rules {
msg := tc.messages[r.Name]
assert.Equal(t, r.Message, msg)
}
}
@ -541,3 +543,66 @@ func TestValidate_failure_action_overrides(t *testing.T) {
})
}
}
func Test_RuleSelector(t *testing.T) {
var rawPolicy = []byte(`{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {"name": "check-label-app"},
"spec": {
"validationFailureAction": "enforce",
"rules": [
{
"name": "check-label-test",
"match": {"name": "test-*", "resources": {"kinds": ["Pod"]}},
"validate": {
"message": "The label 'app' is required.",
"pattern": { "metadata": { "labels": { "app": "?*" } } }
}
},
{
"name": "check-labels",
"match": {"name": "*", "resources": {"kinds": ["Pod"]}},
"validate": {
"message": "The label 'app' is required.",
"pattern": { "metadata": { "labels": { "app": "?*", "test" : "?*" } } }
}
}
]
}
}`)
var rawResource = []byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {"name": "test-pod", "namespace": "", "labels": { "app" : "test-pod" }},
"spec": {"containers": [{"name": "nginx", "image": "nginx:latest"}]}
}`)
var policy kyvernov1.ClusterPolicy
err := json.Unmarshal(rawPolicy, &policy)
assert.NilError(t, err)
resourceUnstructured, err := utils.ConvertToUnstructured(rawResource)
assert.NilError(t, err)
assert.Assert(t, resourceUnstructured != nil)
ctx := &engine.PolicyContext{Policy: &policy, NewResource: *resourceUnstructured, JSONContext: context.NewContext()}
resp := engine.Validate(ctx)
assert.Assert(t, resp.PolicyResponse.RulesAppliedCount == 2)
assert.Assert(t, resp.PolicyResponse.RulesErrorCount == 0)
log := log.Log.WithName("Test_RuleSelector")
blocked := toBlockResource([]*response.EngineResponse{resp}, log)
assert.Assert(t, blocked == true)
applyOne := kyvernov1.ApplyOne
policy.Spec.ApplyRules = &applyOne
resp = engine.Validate(ctx)
assert.Assert(t, resp.PolicyResponse.RulesAppliedCount == 1)
assert.Assert(t, resp.PolicyResponse.RulesErrorCount == 0)
blocked = toBlockResource([]*response.EngineResponse{resp}, log)
assert.Assert(t, blocked == false)
}