/
mutate_image.go
157 lines (150 loc) · 5.78 KB
/
mutate_image.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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"
kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
"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 {
configuration config.Configuration
rclientFactory engineapi.RegistryClientFactory
ivCache imageverifycache.Client
ivm *engineapi.ImageVerificationMetadata
images []apiutils.ImageInfo
}
func NewMutateImageHandler(
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
configuration config.Configuration,
rclientFactory engineapi.RegistryClientFactory,
ivCache imageverifycache.Client,
ivm *engineapi.ImageVerificationMetadata,
) (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{
configuration: configuration,
rclientFactory: rclientFactory,
ivm: ivm,
ivCache: ivCache,
images: ruleImages,
}, nil
}
func (h mutateImageHandler) Process(
ctx context.Context,
logger logr.Logger,
policyContext engineapi.PolicyContext,
resource unstructured.Unstructured,
rule kyvernov1.Rule,
contextLoader engineapi.EngineContextLoader,
exceptions []kyvernov2beta1.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)
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
}