mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-10 01:46:55 +00:00
161 lines
6 KiB
Go
161 lines
6 KiB
Go
package mutation
|
|||
|
|||
import (
|
|||
"context"
|
|||
|
|||
json_patch "github.com/evanphx/json-patch/v5"
|
|||
"github.com/go-logr/logr"
|
|||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
|||
feat: promote policy exceptions to v2 (#9208)
Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
|
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
|
||
"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"
|
|||
"github.com/kyverno/kyverno/pkg/engine/mutate/patch"
|
|||
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
|
|||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
|||
"github.com/kyverno/kyverno/pkg/imageverifycache"
|
|||
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
|
|||
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
|
|||
"gomodules.xyz/jsonpatch/v2"
|
|||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|||
"k8s.io/client-go/tools/cache"
|
|||
)
|
|||
|
|||
type mutateImageHandler struct {
|
|||
fix: cosign global var (#7397)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
|
configuration config.Configuration
|
||
rclientFactory engineapi.RegistryClientFactory
|
|||
ivCache imageverifycache.Client
|
|||
fix: cosign global var (#7397)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
|
ivm *engineapi.ImageVerificationMetadata
|
||
images []apiutils.ImageInfo
|
|||
imageSignatureRepository string
|
|||
}
|
|||
|
|||
func NewMutateImageHandler(
|
|||
policyContext engineapi.PolicyContext,
|
|||
resource unstructured.Unstructured,
|
|||
rule kyvernov1.Rule,
|
|||
configuration config.Configuration,
|
|||
rclientFactory engineapi.RegistryClientFactory,
|
|||
ivCache imageverifycache.Client,
|
|||
ivm *engineapi.ImageVerificationMetadata,
|
|||
fix: cosign global var (#7397)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
|
imageSignatureRepository string,
|
||
) (handlers.Handler, error) {
|
|||
if len(rule.VerifyImages) == 0 {
|
|||
return nil, nil
|
|||
}
|
|||
ruleImages, _, err := engineutils.ExtractMatchingImages(resource, policyContext.JSONContext(), rule, configuration)
|
|||
if err != nil {
|
|||
return nil, err
|
|||
}
|
|||
if len(ruleImages) == 0 {
|
|||
return nil, nil
|
|||
}
|
|||
return mutateImageHandler{
|
|||
fix: cosign global var (#7397)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
|
configuration: configuration,
|
||
rclientFactory: rclientFactory,
|
|||
fix: cosign global var (#7397)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
|
ivm: ivm,
|
||
ivCache: ivCache,
|
|||
fix: cosign global var (#7397)
Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
|
images: ruleImages,
|
||
imageSignatureRepository: imageSignatureRepository,
|
|||
}, nil
|
|||
}
|
|||
|
|||
func (h mutateImageHandler) Process(
|
|||
ctx context.Context,
|
|||
logger logr.Logger,
|
|||
policyContext engineapi.PolicyContext,
|
|||
resource unstructured.Unstructured,
|
|||
rule kyvernov1.Rule,
|
|||
contextLoader engineapi.EngineContextLoader,
|
|||
feat: promote policy exceptions to v2 (#9208)
Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com>
|
exceptions []kyvernov2.PolicyException,
|
||
) (unstructured.Unstructured, []engineapi.RuleResponse) {
|
|||
// check if there is a policy exception matches the incoming resource
|
|||
exception := engineutils.MatchesException(exceptions, policyContext, logger)
|
|||
if exception != nil {
|
|||
key, err := cache.MetaNamespaceKeyFunc(exception)
|
|||
if err != nil {
|
|||
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)
|
|||
} else {
|
|||
logger.V(3).Info("policy rule skipped due to policy exception", "exception", key)
|
|||
return resource, handlers.WithResponses(
|
|||
engineapi.RuleSkip(rule.Name, engineapi.Validation, "rule skipped due to policy exception "+key).WithException(exception),
|
|||
)
|
|||
}
|
|||
}
|
|||
|
|||
jsonContext := policyContext.JSONContext()
|
|||
ruleCopy, err := substituteVariables(rule, jsonContext, logger)
|
|||
if err != nil {
|
|||
return resource, handlers.WithResponses(
|
|||
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to substitute variables", err),
|
|||
)
|
|||
}
|
|||
var engineResponses []*engineapi.RuleResponse
|
|||
var patches []jsonpatch.JsonPatchOperation
|
|||
for _, imageVerify := range ruleCopy.VerifyImages {
|
|||
rclient, err := h.rclientFactory.GetClient(ctx, imageVerify.ImageRegistryCredentials)
|
|||
if err != nil {
|
|||
return resource, handlers.WithResponses(
|
|||
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to fetch secrets", err),
|
|||
)
|
|||
}
|
|||
iv := internal.NewImageVerifier(logger, rclient, h.ivCache, policyContext, *ruleCopy, h.ivm, h.imageSignatureRepository)
|
|||
patch, ruleResponse := iv.Verify(ctx, imageVerify, h.images, h.configuration)
|
|||
patches = append(patches, patch...)
|
|||
engineResponses = append(engineResponses, ruleResponse...)
|
|||
}
|
|||
if len(patches) != 0 {
|
|||
patch := jsonutils.JoinPatches(patch.ConvertPatches(patches...)...)
|
|||
decoded, err := json_patch.DecodePatch(patch)
|
|||
if err != nil {
|
|||
return resource, handlers.WithResponses(
|
|||
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to decode patch", err),
|
|||
)
|
|||
}
|
|||
options := &json_patch.ApplyOptions{SupportNegativeIndices: true, AllowMissingPathOnRemove: true, EnsurePathExistsOnAdd: true}
|
|||
resourceBytes, err := resource.MarshalJSON()
|
|||
if err != nil {
|
|||
return resource, handlers.WithResponses(
|
|||
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to marshal resource", err),
|
|||
)
|
|||
}
|
|||
patchedResourceBytes, err := decoded.ApplyWithOptions(resourceBytes, options)
|
|||
if err != nil {
|
|||
return resource, handlers.WithResponses(
|
|||
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to apply patch", err),
|
|||
)
|
|||
}
|
|||
if err := resource.UnmarshalJSON(patchedResourceBytes); err != nil {
|
|||
return resource, handlers.WithResponses(
|
|||
engineapi.RuleError(rule.Name, engineapi.ImageVerify, "failed to unmarshal resource", err),
|
|||
)
|
|||
}
|
|||
}
|
|||
return resource, handlers.WithResponses(engineResponses...)
|
|||
}
|
|||
|
|||
func substituteVariables(rule kyvernov1.Rule, ctx enginecontext.EvalInterface, logger logr.Logger) (*kyvernov1.Rule, error) {
|
|||
// remove attestations as variables are not substituted in them
|
|||
ruleCopy := *rule.DeepCopy()
|
|||
for i := range ruleCopy.VerifyImages {
|
|||
for j := range ruleCopy.VerifyImages[i].Attestations {
|
|||
ruleCopy.VerifyImages[i].Attestations[j].Conditions = nil
|
|||
}
|
|||
}
|
|||
var err error
|
|||
ruleCopy, err = variables.SubstituteAllInRule(logger, ctx, ruleCopy)
|
|||
if err != nil {
|
|||
return nil, err
|
|||
}
|
|||
// replace attestations
|
|||
for i := range ruleCopy.VerifyImages {
|
|||
for j := range ruleCopy.VerifyImages[i].Attestations {
|
|||
ruleCopy.VerifyImages[i].Attestations[j].Conditions = rule.VerifyImages[i].Attestations[j].Conditions
|
|||
}
|
|||
}
|
|||
return &ruleCopy, nil
|
|||
}
|