/
validate.go
120 lines (100 loc) · 3.8 KB
/
validate.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
package mutate
import (
"context"
"fmt"
"strings"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/variables/regex"
"github.com/kyverno/kyverno/pkg/policy/auth"
"github.com/kyverno/kyverno/pkg/utils/api"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"go.uber.org/multierr"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
// Mutate provides implementation to validate 'mutate' rule
type Mutate struct {
mutation kyvernov1.Mutation
user string
authChecker auth.Operations
}
// NewMutateFactory returns a new instance of Mutate validation checker
func NewMutateFactory(m kyvernov1.Mutation, authChecker auth.Operations, user string) *Mutate {
return &Mutate{
mutation: m,
user: user,
authChecker: authChecker,
}
}
// Validate validates the 'mutate' rule
func (m *Mutate) Validate(ctx context.Context) (string, error) {
if m.hasForEach() {
if m.hasPatchStrategicMerge() || m.hasPatchesJSON6902() {
return "foreach", fmt.Errorf("only one of `foreach`, `patchStrategicMerge`, or `patchesJson6902` is allowed")
}
return m.validateForEach("", m.mutation.ForEachMutation)
}
if m.hasPatchesJSON6902() && m.hasPatchStrategicMerge() {
return "foreach", fmt.Errorf("only one of `patchStrategicMerge` or `patchesJson6902` is allowed")
}
if m.mutation.Targets != nil {
if err := m.validateAuth(ctx, m.mutation.Targets); err != nil {
return "targets", fmt.Errorf("auth check fails, additional privileges are required for the service account '%s': %v", m.user, err)
}
}
return "", nil
}
func (m *Mutate) validateForEach(tag string, foreach []kyvernov1.ForEachMutation) (string, error) {
for i, fe := range foreach {
tag = tag + fmt.Sprintf("foreach[%d]", i)
if fe.ForEachMutation != nil {
if fe.Context != nil || fe.AnyAllConditions != nil || fe.PatchesJSON6902 != "" || fe.RawPatchStrategicMerge != nil {
return tag, fmt.Errorf("a nested foreach cannot contain other declarations")
}
return m.validateNestedForEach(tag, fe.ForEachMutation)
}
psm := fe.GetPatchStrategicMerge()
if (fe.PatchesJSON6902 == "" && psm == nil) || (fe.PatchesJSON6902 != "" && psm != nil) {
return tag, fmt.Errorf("only one of `patchStrategicMerge` or `patchesJson6902` is allowed")
}
}
return "", nil
}
func (m *Mutate) validateNestedForEach(tag string, j *v1.JSON) (string, error) {
nestedForeach, err := api.DeserializeJSONArray[kyvernov1.ForEachMutation](j)
if err != nil {
return tag, fmt.Errorf("invalid foreach syntax: %w", err)
}
return m.validateForEach(tag, nestedForeach)
}
func (m *Mutate) hasForEach() bool {
return len(m.mutation.ForEachMutation) > 0
}
func (m *Mutate) hasPatchStrategicMerge() bool {
return m.mutation.GetPatchStrategicMerge() != nil
}
func (m *Mutate) hasPatchesJSON6902() bool {
return m.mutation.PatchesJSON6902 != ""
}
func (m *Mutate) validateAuth(ctx context.Context, targets []kyvernov1.TargetResourceSpec) error {
var errs []error
for _, target := range targets {
if !regex.IsVariable(target.Kind) {
_, _, k, sub := kubeutils.ParseKindSelector(target.Kind)
srcKey := k
if sub != "" {
srcKey = srcKey + "/" + sub
}
if ok, err := m.authChecker.CanIUpdate(ctx, strings.Join([]string{target.APIVersion, k}, "/"), target.Namespace, sub); err != nil {
errs = append(errs, err)
} else if !ok {
errs = append(errs, fmt.Errorf("cannot %s/%s/%s in namespace %s", "update", target.APIVersion, srcKey, target.Namespace))
}
if ok, err := m.authChecker.CanIGet(ctx, strings.Join([]string{target.APIVersion, k}, "/"), target.Namespace, sub); err != nil {
errs = append(errs, err)
} else if !ok {
errs = append(errs, fmt.Errorf("cannot %s/%s/%s in namespace %s", "get", target.APIVersion, srcKey, target.Namespace))
}
}
}
return multierr.Combine(errs...)
}