1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/engine/engine.go
Charles-Edouard Brétéché 784ca07419
refactor: engine rule response creation (#6784)
* refactor: engine rule response creation

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* private fields

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* more private

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix unit tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
2023-04-05 10:35:38 +00:00

262 lines
9.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package engine
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
gojmespath "github.com/jmespath/go-jmespath"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/handlers"
"github.com/kyverno/kyverno/pkg/engine/internal"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/metrics"
"github.com/kyverno/kyverno/pkg/registryclient"
"github.com/kyverno/kyverno/pkg/tracing"
"go.opentelemetry.io/otel/metric/global"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/trace"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type engine struct {
configuration config.Configuration
metricsConfiguration config.MetricsConfiguration
client dclient.Interface
rclient registryclient.Client
contextLoader engineapi.ContextLoaderFactory
exceptionSelector engineapi.PolicyExceptionSelector
// metrics
resultCounter instrument.Int64Counter
durationHistogram instrument.Float64Histogram
}
type handlerFactory = func() (handlers.Handler, error)
func NewEngine(
configuration config.Configuration,
metricsConfiguration config.MetricsConfiguration,
client dclient.Interface,
rclient registryclient.Client,
contextLoader engineapi.ContextLoaderFactory,
exceptionSelector engineapi.PolicyExceptionSelector,
) engineapi.Engine {
meter := global.MeterProvider().Meter(metrics.MeterName)
resultCounter, err := meter.Int64Counter(
"kyverno_policy_results",
instrument.WithDescription("can be used to track the results associated with the policies applied in the users cluster, at the level from rule to policy to admission requests"),
)
if err != nil {
logging.Error(err, "failed to register metric kyverno_policy_results")
}
durationHistogram, err := meter.Float64Histogram(
"kyverno_policy_execution_duration_seconds",
instrument.WithDescription("can be used to track the latencies (in seconds) associated with the execution/processing of the individual rules under Kyverno policies whenever they evaluate incoming resource requests"),
)
if err != nil {
logging.Error(err, "failed to register metric kyverno_policy_execution_duration_seconds")
}
return &engine{
configuration: configuration,
metricsConfiguration: metricsConfiguration,
client: client,
rclient: rclient,
contextLoader: contextLoader,
exceptionSelector: exceptionSelector,
resultCounter: resultCounter,
durationHistogram: durationHistogram,
}
}
func (e *engine) Validate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.validate"), policyContext)
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse := e.validate(ctx, logger, policyContext)
response = response.WithPolicyResponse(policyResponse)
}
response = response.Done(time.Now())
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), response)
return response
}
func (e *engine) Mutate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.mutate"), policyContext)
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse, patchedResource := e.mutate(ctx, logger, policyContext)
response = response.
WithPolicyResponse(policyResponse).
WithPatchedResource(patchedResource)
}
response = response.Done(time.Now())
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), response)
return response
}
func (e *engine) Generate(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.generate"), policyContext)
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse := e.generateResponse(ctx, logger, policyContext)
response = response.WithPolicyResponse(policyResponse)
}
response = response.Done(time.Now())
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), response)
return response
}
func (e *engine) VerifyAndPatchImages(
ctx context.Context,
policyContext engineapi.PolicyContext,
) (engineapi.EngineResponse, engineapi.ImageVerificationMetadata) {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
ivm := engineapi.ImageVerificationMetadata{}
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.verify"), policyContext)
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse, innerIvm := e.verifyAndPatchImages(ctx, logger, policyContext)
response, ivm = response.WithPolicyResponse(policyResponse), innerIvm
}
response = response.Done(time.Now())
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), response)
return response, ivm
}
func (e *engine) ApplyBackgroundChecks(
ctx context.Context,
policyContext engineapi.PolicyContext,
) engineapi.EngineResponse {
response := engineapi.NewEngineResponseFromPolicyContext(policyContext, time.Now())
logger := internal.LoggerWithPolicyContext(logging.WithName("engine.background"), policyContext)
if internal.MatchPolicyContext(logger, policyContext, e.configuration) {
policyResponse := e.applyBackgroundChecks(ctx, logger, policyContext)
response = response.WithPolicyResponse(policyResponse)
}
response = response.Done(time.Now())
e.reportMetrics(ctx, logger, policyContext.Operation(), policyContext.AdmissionOperation(), response)
return response
}
func (e *engine) ContextLoader(
policy kyvernov1.PolicyInterface,
rule kyvernov1.Rule,
) engineapi.EngineContextLoader {
loader := e.contextLoader(policy, rule)
return func(ctx context.Context, contextEntries []kyvernov1.ContextEntry, jsonContext enginecontext.Interface) error {
return loader.Load(
ctx,
e.client,
e.rclient,
contextEntries,
jsonContext,
)
}
}
// matches checks if either the new or old resource satisfies the filter conditions defined in the rule
func matches(
rule kyvernov1.Rule,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
) error {
gvk, subresource := policyContext.ResourceKind()
err := engineutils.MatchesResourceDescription(
resource,
rule,
policyContext.AdmissionInfo(),
policyContext.NamespaceLabels(),
policyContext.Policy().GetNamespace(),
gvk,
subresource,
policyContext.Operation(),
)
if err == nil {
return nil
}
oldResource := policyContext.OldResource()
if oldResource.Object != nil {
err := engineutils.MatchesResourceDescription(
policyContext.OldResource(),
rule,
policyContext.AdmissionInfo(),
policyContext.NamespaceLabels(),
policyContext.Policy().GetNamespace(),
gvk,
subresource,
policyContext.Operation(),
)
if err == nil {
return nil
}
}
return err
}
func (e *engine) invokeRuleHandler(
ctx context.Context,
logger logr.Logger,
handlerFactory handlerFactory,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
ruleType engineapi.RuleType,
) (unstructured.Unstructured, []engineapi.RuleResponse) {
return tracing.ChildSpan2(
ctx,
"pkg/engine",
fmt.Sprintf("RULE %s", rule.Name),
func(ctx context.Context, span trace.Span) (unstructured.Unstructured, []engineapi.RuleResponse) {
// check if resource and rule match
if err := matches(rule, policyContext, resource); err != nil {
logger.V(4).Info("rule not matched", "reason", err.Error())
return resource, nil
}
if handlerFactory == nil {
return resource, handlers.WithError(rule, ruleType, "failed to instantiate handler", nil)
} else if handler, err := handlerFactory(); err != nil {
return resource, handlers.WithError(rule, ruleType, "failed to instantiate handler", err)
} else if handler != nil {
// check if there's an exception
if ruleResp := e.hasPolicyExceptions(logger, ruleType, policyContext, rule); ruleResp != nil {
return resource, handlers.WithResponses(ruleResp)
}
// load rule context
contextLoader := e.ContextLoader(policyContext.Policy(), rule)
if err := contextLoader(ctx, rule.Context, policyContext.JSONContext()); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
logger.V(3).Info("failed to load context", "reason", err.Error())
} else {
logger.Error(err, "failed to load context")
}
return resource, handlers.WithError(rule, ruleType, "failed to load context", err)
}
// check preconditions
preconditionsPassed, 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")
}
// process handler
return handler.Process(ctx, logger, policyContext, resource, rule, contextLoader)
}
return resource, nil
},
)
}