kubelatte-ce
Форк от sbertech/kubelatte-ce
271 строка · 6.5 Кб
1package validation
2
3import (
4"context"
5"errors"
6"fmt"
7"github.com/jmespath/go-jmespath"
8"github.com/open-policy-agent/opa/rego"
9"gitverse.ru/synapse/kubelatte/pkg/api/common"
10"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
11"gitverse.ru/synapse/kubelatte/pkg/observability/logger"
12"gitverse.ru/synapse/kubelatte/pkg/storage"
13match "gitverse.ru/synapse/kubelatte/pkg/util/match"
14"reflect"
15"regexp"
16)
17
18var ErrCheckingRegoRule = errors.New("err checking rego rule")
19var ErrCheckingSimplesRule = errors.New("err checking simples rule")
20
21type ValidatorI interface {
22Validate(ctx context.Context, obj map[string]interface{}, requestData common.ARFields) error
23ValidateByScope(ctx context.Context, requestData common.ARFields, item v1alpha1.Item, original map[string]interface{}) error
24}
25
26type Validator struct {
27matcher match.MatcherI
28act ValidatorI
29}
30
31func NewValidator(matcher match.MatcherI, act ValidatorI) ValidatorI {
32validator := &Validator{matcher: matcher}
33if act == nil {
34act = validator
35}
36validator.act = act
37return validator
38}
39
40func iterator(ctx context.Context, elem interface{}, rulePath, ruleValue string, isReverse bool) bool {
41log := logger.FromContext(ctx)
42val := reflect.ValueOf(elem)
43
44tp := reflect.TypeOf(elem)
45if !val.IsValid() || val.IsZero() {
46return false
47}
48switch tp.Kind() {
49case reflect.Slice:
50length := val.Len()
51for i := 0; i < length; i++ {
52if isReverse {
53if iterator(ctx, val.Index(i).Interface(), rulePath, ruleValue, isReverse) {
54return true
55}
56} else {
57if !iterator(ctx, val.Index(i).Interface(), rulePath, ruleValue, isReverse) {
58return false
59}
60}
61}
62case reflect.Map:
63keys := val.MapKeys()
64for _, v := range keys {
65value := val.MapIndex(v)
66if isReverse {
67if iterator(ctx, value.Interface(), rulePath, ruleValue, isReverse) {
68return true
69}
70} else {
71if !iterator(ctx, value.Interface(), rulePath, ruleValue, isReverse) {
72return false
73}
74}
75}
76case reflect.Struct:
77for i := 0; i < val.NumField(); i++ {
78f := val.Field(i)
79log.Error(f.Interface())
80}
81case reflect.String:
82scope := ruleValue
83
84str, ok := elem.(string)
85
86if !ok {
87return false
88}
89o, err := regexp.Match(ruleValue, []byte(str))
90if err != nil {
91log.Errorf("Scope validation error %s", err.Error())
92}
93if isReverse {
94if o {
95log.Debugf("value: %s match scope: %s, ok\n", str, scope)
96return true
97}
98} else {
99if !o {
100log.Debugf("value: %s not match with scope: %s, path: %s failed\n", str, scope, rulePath)
101return false
102}
103log.Debugf("value: %s match scope: %s, ok\n", str, scope)
104}
105
106default:
107return isReverse
108}
109
110return !isReverse
111}
112
113// checkRule return result, detailedMessages
114func checkRule(ctx context.Context, name string, parameters, inputObject map[string]interface{}) error {
115rules := storage.Storage.GetRegoRules()
116rule, ok := rules[name]
117if !ok {
118return fmt.Errorf("rego rule %s not found", name)
119}
120input := map[string]interface{}{
121"review": inputObject,
122"parameters": parameters,
123}
124results, err := rule.Eval(ctx, rego.EvalInput(input))
125if err != nil {
126// Handle evaluation error.
127return fmt.Errorf("check rego rule %s failed %s", name, err)
128}
129if len(results) == 0 {
130// Handle undefined result.
131return fmt.Errorf("check rego rule %s failed, result zero. (exec failed?)", name)
132}
133result, ok := results[0].Bindings["x"].([]interface{})
134if !ok {
135return fmt.Errorf("check rego rule %s failed, result exist, but unexpected. (exec failed?)", name)
136}
137if len(result) > 0 {
138var details []string
139
140for _, r := range result {
141details = append(details, r.(map[string]interface{})["msg"].(string))
142}
143
144return fmt.Errorf("validation failed: rego rule %s details %v", name, details)
145}
146return nil
147
148}
149
150func (v *Validator) Validate(ctx context.Context, obj map[string]interface{}, requestData common.ARFields) error {
151scopes := storage.Storage.GetScopeItems(Validation)
152for _, scope := range scopes {
153err := v.ValidateByScope(ctx, requestData, scope, obj)
154if err != nil {
155return err
156}
157}
158return nil
159}
160
161func (v *Validator) ValidateByScope(ctx context.Context, requestData common.ARFields, item v1alpha1.Item, original map[string]interface{}) error {
162allowed, _ := v.matcher.Match(ctx, requestData, item.Match)
163if !allowed {
164return nil
165}
166
167err := CheckRegoRule(ctx, original, item)
168if err != nil {
169return errors.Join(err, ErrCheckingRegoRule)
170}
171
172if item.Rule.Simples == nil {
173return nil
174}
175
176err = CheckSimplesRule(ctx, item, requestData)
177if err != nil {
178return err
179}
180return nil
181}
182
183func CheckSimplesRule(ctx context.Context, item v1alpha1.Item, requestData common.ARFields) error {
184var simplesErr error
185
186for _, simple := range item.Rule.Simples {
187
188var object map[string]interface{}
189if requestData.Operation != "DELETE" {
190object = requestData.Object
191} else {
192object = requestData.OldObject
193}
194
195result, err := jmespath.Search(simple.Path, object)
196if err != nil {
197continue
198}
199
200validationSuccess := validateValue(ctx, result, simple.Path, simple.Value, simple.Action)
201if !validationSuccess {
202
203message := simple.Message
204if simple.Message == "" {
205message = fmt.Sprintf("validation failed: rule %s path %s", simple.Value, simple.Path)
206}
207
208if simplesErr == nil {
209simplesErr = errors.New(message)
210continue
211}
212
213simplesErr = fmt.Errorf("%s; %s", simplesErr.Error(), message)
214}
215}
216if simplesErr != nil {
217return simplesErr
218}
219return nil
220}
221
222func CheckRegoRule(ctx context.Context, original map[string]interface{}, item v1alpha1.Item) error {
223if item.Rule.Rego.Template != "" {
224params, err := fromYAML([]byte(item.Rule.Rego.Parameters))
225if err != nil {
226return err
227}
228
229err = checkRule(ctx, item.Rule.Rego.Template, params, original)
230if err != nil {
231return err
232}
233}
234return nil
235}
236
237func validateValue(ctx context.Context, result interface{}, path, value, action string) bool {
238log := logger.FromContext(ctx)
239resultSlice, isSlice := result.([]interface{})
240if result != nil && value == ".*" && !isSlice {
241if action == Deny {
242return false
243}
244if action == Allow {
245return true
246}
247}
248if isSlice && len(resultSlice) != 0 && value == ".*" {
249if action == Deny {
250return false
251}
252if action == Allow {
253return true
254}
255}
256
257if action == Deny {
258if iterator(ctx, result, path, value, true) {
259log.Debugf("validation failed: rule %s path %s", value, path)
260return false
261}
262}
263if action == Allow {
264if !iterator(ctx, result, path, value, false) {
265log.Debugf("validation failed: rule %s path %s", value, path)
266return false
267}
268}
269
270return true
271}
272