Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/kyverno/v1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ type Condition struct {
// or can be variables declared using JMESPath.
// +optional
RawValue *apiextv1.JSON `json:"value,omitempty" yaml:"value,omitempty"`

// Message is an optional display message
Message string `json:"message,omitempty" yaml:"message,omitempty"`
}

func (c *Condition) GetKey() apiextensions.JSON {
Expand Down
192 changes: 192 additions & 0 deletions charts/kyverno/templates/crds/crds.yaml

Large diffs are not rendered by default.

96 changes: 96 additions & 0 deletions config/crds/kyverno.io_clusterpolicies.yaml

Large diffs are not rendered by default.

96 changes: 96 additions & 0 deletions config/crds/kyverno.io_policies.yaml

Large diffs are not rendered by default.

192 changes: 192 additions & 0 deletions config/install-latest-testing.yaml

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions docs/user/crd/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,17 @@ <h3 id="kyverno.io/v1.Condition">Condition
or can be variables declared using JMESPath.</p>
</td>
</tr>
<tr>
<td>
<code>message</code><br/>
<em>
string
</em>
</td>
<td>
<p>Message is an optional display message</p>
</td>
</tr>
</tbody>
</table>
<hr />
Expand Down
2 changes: 1 addition & 1 deletion pkg/engine/attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func Test_Conditions(t *testing.T) {
err := json.Unmarshal([]byte(scanPredicate), &dataMap)
assert.NilError(t, err)

pass, err := internal.EvaluateConditions(conditions, ctx, dataMap, logr.Discard())
pass, _, err := internal.EvaluateConditions(conditions, ctx, dataMap, logr.Discard())
assert.NilError(t, err)
assert.Equal(t, pass, true)
}
7 changes: 3 additions & 4 deletions pkg/engine/background.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/kyverno/kyverno/pkg/autogen"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/utils"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/variables"
)
Expand Down Expand Up @@ -104,15 +103,15 @@ func (e *engine) filterRule(
}

// operate on the copy of the conditions, as we perform variable substitution
copyConditions, err := utils.TransformConditions(ruleCopy.GetAnyAllConditions())
copyConditions, err := engineutils.TransformConditions(ruleCopy.GetAnyAllConditions())
if err != nil {
logger.V(4).Info("cannot copy AnyAllConditions", "reason", err.Error())
return nil
}

// evaluate pre-conditions
if !variables.EvaluateConditions(logger, ctx, copyConditions) {
logger.V(4).Info("skip rule as preconditions are not met", "rule", ruleCopy.Name)
if val, msg := variables.EvaluateConditions(logger, ctx, copyConditions); !val {
logger.V(4).Info("skip rule as preconditions are not met", "rule", ruleCopy.Name, "message", msg)
return engineapi.RuleSkip(ruleCopy.Name, ruleType, "")
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/kyverno/kyverno/pkg/metrics"
"github.com/kyverno/kyverno/pkg/registryclient"
"github.com/kyverno/kyverno/pkg/tracing"
stringutils "github.com/kyverno/kyverno/pkg/utils/strings"
"go.opentelemetry.io/otel/metric/global"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/trace"
Expand Down Expand Up @@ -256,12 +257,13 @@ func (e *engine) invokeRuleHandler(
return resource, handlers.WithError(rule, ruleType, "failed to load context", err)
}
// check preconditions
preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), rule.GetAnyAllConditions())
preconditionsPassed, msg, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), rule.GetAnyAllConditions())
if err != nil {
return resource, handlers.WithError(rule, ruleType, "failed to evaluate preconditions", err)
}
if !preconditionsPassed {
return resource, handlers.WithSkip(rule, ruleType, "preconditions not met")
s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ")
return resource, handlers.WithSkip(rule, ruleType, s)
}
// process handler
return handler.Process(ctx, logger, policyContext, resource, rule, contextLoader)
Expand Down
4 changes: 2 additions & 2 deletions pkg/engine/handlers/mutation/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
return mutate.NewErrorResponse(fmt.Sprintf("failed to load to mutate.foreach[%d].context", index), err)
}

preconditionsPassed, err := internal.CheckPreconditions(f.logger, policyContext.JSONContext(), foreach.AnyAllConditions)
preconditionsPassed, msg, err := internal.CheckPreconditions(f.logger, policyContext.JSONContext(), foreach.AnyAllConditions)
if err != nil {
return mutate.NewErrorResponse(fmt.Sprintf("failed to evaluate mutate.foreach[%d].preconditions", index), err)
}

if !preconditionsPassed {
f.logger.Info("mutate.foreach.preconditions not met", "elementIndex", index)
f.logger.Info("mutate.foreach.preconditions not met", "elementIndex", index, "message", msg)
continue
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/engine/handlers/mutation/mutate_existing.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/mutate"
stringutils "github.com/kyverno/kyverno/pkg/utils/strings"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

Expand Down Expand Up @@ -57,14 +58,15 @@ func (h mutateExistingHandler) Process(
continue
}
// load target specific preconditions
preconditionsPassed, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), target.preconditions)
preconditionsPassed, msg, err := internal.CheckPreconditions(logger, policyContext.JSONContext(), target.preconditions)
if err != nil {
rr := engineapi.RuleError(rule.Name, engineapi.Mutation, "failed to evaluate preconditions", err)
responses = append(responses, *rr)
continue
}
if !preconditionsPassed {
rr := engineapi.RuleSkip(rule.Name, engineapi.Mutation, "preconditions not met")
s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ")
rr := engineapi.RuleSkip(rule.Name, engineapi.Mutation, s)
responses = append(responses, *rr)
continue
}
Expand Down
23 changes: 14 additions & 9 deletions pkg/engine/handlers/validation/validate_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/utils/api"
datautils "github.com/kyverno/kyverno/pkg/utils/data"
stringutils "github.com/kyverno/kyverno/pkg/utils/strings"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
Expand Down Expand Up @@ -101,12 +102,13 @@ func (v *validator) validate(ctx context.Context) *engineapi.RuleResponse {
if err := v.loadContext(ctx); err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to load context", err)
}
preconditionsPassed, err := internal.CheckPreconditions(v.log, v.policyContext.JSONContext(), v.anyAllConditions)
preconditionsPassed, msg, err := internal.CheckPreconditions(v.log, v.policyContext.JSONContext(), v.anyAllConditions)
if err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to evaluate preconditions", err)
}
if !preconditionsPassed {
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, "preconditions not met")
s := stringutils.JoinNonEmpty([]string{"preconditions not met", msg}, "; ")
return engineapi.RuleSkip(v.rule.Name, engineapi.Validation, s)
}

if v.deny != nil {
Expand Down Expand Up @@ -217,28 +219,31 @@ func (v *validator) loadContext(ctx context.Context) error {
}

func (v *validator) validateDeny() *engineapi.RuleResponse {
if deny, err := internal.CheckDenyPreconditions(v.log, v.policyContext.JSONContext(), v.deny.GetAnyAllConditions()); err != nil {
if deny, msg, err := internal.CheckDenyPreconditions(v.log, v.policyContext.JSONContext(), v.deny.GetAnyAllConditions()); err != nil {
return engineapi.RuleError(v.rule.Name, engineapi.Validation, "failed to check deny preconditions", err)
} else {
if deny {
return engineapi.RuleFail(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny))
return engineapi.RuleFail(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny, msg))
}
return engineapi.RulePass(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny))
return engineapi.RulePass(v.rule.Name, engineapi.Validation, v.getDenyMessage(deny, msg))
}
}

func (v *validator) getDenyMessage(deny bool) string {
func (v *validator) getDenyMessage(deny bool, msg string) string {
if !deny {
return fmt.Sprintf("validation rule '%s' passed.", v.rule.Name)
}
msg := v.rule.Validation.Message
if msg == "" {

if v.rule.Validation.Message == "" && msg == "" {
return fmt.Sprintf("validation error: rule %s failed", v.rule.Name)
}
raw, err := variables.SubstituteAll(v.log, v.policyContext.JSONContext(), msg)

s := stringutils.JoinNonEmpty([]string{v.rule.Validation.Message, msg}, "; ")
raw, err := variables.SubstituteAll(v.log, v.policyContext.JSONContext(), s)
if err != nil {
return msg
}

switch typed := raw.(type) {
case string:
return typed
Expand Down
3 changes: 1 addition & 2 deletions pkg/engine/image_verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/policycontext"
"github.com/kyverno/kyverno/pkg/engine/utils"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/registryclient"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
Expand Down Expand Up @@ -799,7 +798,7 @@ func Test_MarkImageVerified(t *testing.T) {
}

func testApplyPatches(t *testing.T, patches [][]byte) unstructured.Unstructured {
patchedResource, err := utils.ApplyPatches([]byte(testResource), patches)
patchedResource, err := engineutils.ApplyPatches([]byte(testResource), patches)
assert.NilError(t, err)
assert.Assert(t, patchedResource != nil)

Expand Down
20 changes: 10 additions & 10 deletions pkg/engine/internal/imageverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,20 +162,20 @@ func EvaluateConditions(
ctx enginecontext.Interface,
s map[string]interface{},
log logr.Logger,
) (bool, error) {
) (bool, string, error) {
predicate, ok := s["predicate"].(map[string]interface{})
if !ok {
return false, fmt.Errorf("failed to extract predicate from statement: %v", s)
return false, "", fmt.Errorf("failed to extract predicate from statement: %v", s)
}
if err := enginecontext.AddJSONObject(ctx, predicate); err != nil {
return false, fmt.Errorf("failed to add Statement to the context %v: %w", s, err)
return false, "", fmt.Errorf("failed to add Statement to the context %v: %w", s, err)
}
c, err := variables.SubstituteAllInConditions(log, ctx, conditions)
if err != nil {
return false, fmt.Errorf("failed to substitute variables in attestation conditions: %w", err)
return false, "", fmt.Errorf("failed to substitute variables in attestation conditions: %w", err)
}
pass := variables.EvaluateAnyAllConditions(log, ctx, c)
return pass, nil
pass, msg := variables.EvaluateAnyAllConditions(log, ctx, c)
return pass, msg, nil
}

// verify applies policy rules to each matching image. The policy rule results and annotation patches are
Expand Down Expand Up @@ -545,20 +545,20 @@ func (iv *ImageVerifier) verifyAttestation(statements []map[string]interface{},
}
for _, s := range statements {
iv.logger.Info("checking attestation", "predicates", types, "image", imageInfo.String())
val, err := iv.checkAttestations(attestation, s)
val, msg, err := iv.checkAttestations(attestation, s)
if err != nil {
return fmt.Errorf("failed to check attestations: %w", err)
}
if !val {
return fmt.Errorf("attestation checks failed for %s and predicate %s", imageInfo.String(), attestation.PredicateType)
return fmt.Errorf("attestation checks failed for %s and predicate %s: %s", imageInfo.String(), attestation.PredicateType, msg)
}
}
return nil
}

func (iv *ImageVerifier) checkAttestations(a kyvernov1.Attestation, s map[string]interface{}) (bool, error) {
func (iv *ImageVerifier) checkAttestations(a kyvernov1.Attestation, s map[string]interface{}) (bool, string, error) {
if len(a.Conditions) == 0 {
return true, nil
return true, "", nil
}
iv.policyContext.JSONContext().Checkpoint()
defer iv.policyContext.JSONContext().Restore()
Expand Down
20 changes: 12 additions & 8 deletions pkg/engine/internal/preconditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,30 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
)

func CheckPreconditions(logger logr.Logger, jsonContext enginecontext.Interface, anyAllConditions apiextensions.JSON) (bool, error) {
func CheckPreconditions(logger logr.Logger, jsonContext enginecontext.Interface, anyAllConditions apiextensions.JSON) (bool, string, error) {
preconditions, err := variables.SubstituteAllInPreconditions(logger, jsonContext, anyAllConditions)
if err != nil {
return false, fmt.Errorf("failed to substitute variables in preconditions: %w", err)
return false, "", fmt.Errorf("failed to substitute variables in preconditions: %w", err)
}
typeConditions, err := utils.TransformConditions(preconditions)
if err != nil {
return false, fmt.Errorf("failed to parse preconditions: %w", err)
return false, "", fmt.Errorf("failed to parse preconditions: %w", err)
}
return variables.EvaluateConditions(logger, jsonContext, typeConditions), nil

val, msg := variables.EvaluateConditions(logger, jsonContext, typeConditions)
return val, msg, nil
}

func CheckDenyPreconditions(logger logr.Logger, jsonContext enginecontext.Interface, anyAllConditions apiextensions.JSON) (bool, error) {
func CheckDenyPreconditions(logger logr.Logger, jsonContext enginecontext.Interface, anyAllConditions apiextensions.JSON) (bool, string, error) {
preconditions, err := variables.SubstituteAll(logger, jsonContext, anyAllConditions)
if err != nil {
return false, fmt.Errorf("failed to substitute variables in deny conditions: %w", err)
return false, "", fmt.Errorf("failed to substitute variables in deny conditions: %w", err)
}
typeConditions, err := utils.TransformConditions(preconditions)
if err != nil {
return false, fmt.Errorf("failed to parse deny conditions: %w", err)
return false, "", fmt.Errorf("failed to parse deny conditions: %w", err)
}
return variables.EvaluateConditions(logger, jsonContext, typeConditions), nil

val, msg := variables.EvaluateConditions(logger, jsonContext, typeConditions)
return val, msg, nil
}
Loading