В 22:00 МСК будет объявлен перерыв - 10 минут. Вы отдыхаете - мы обновляем!

kubelatte-ce

Форк от sbertech/kubelatte-ce
Форк
2
/
validator.go 
271 строка · 6.5 Кб
1
package validation
2

3
import (
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"
13
	match "gitverse.ru/synapse/kubelatte/pkg/util/match"
14
	"reflect"
15
	"regexp"
16
)
17

18
var ErrCheckingRegoRule = errors.New("err checking rego rule")
19
var ErrCheckingSimplesRule = errors.New("err checking simples rule")
20

21
type ValidatorI interface {
22
	Validate(ctx context.Context, obj map[string]interface{}, requestData common.ARFields) error
23
	ValidateByScope(ctx context.Context, requestData common.ARFields, item v1alpha1.Item, original map[string]interface{}) error
24
}
25

26
type Validator struct {
27
	matcher match.MatcherI
28
	act     ValidatorI
29
}
30

31
func NewValidator(matcher match.MatcherI, act ValidatorI) ValidatorI {
32
	validator := &Validator{matcher: matcher}
33
	if act == nil {
34
		act = validator
35
	}
36
	validator.act = act
37
	return validator
38
}
39

40
func iterator(ctx context.Context, elem interface{}, rulePath, ruleValue string, isReverse bool) bool {
41
	log := logger.FromContext(ctx)
42
	val := reflect.ValueOf(elem)
43

44
	tp := reflect.TypeOf(elem)
45
	if !val.IsValid() || val.IsZero() {
46
		return false
47
	}
48
	switch tp.Kind() {
49
	case reflect.Slice:
50
		length := val.Len()
51
		for i := 0; i < length; i++ {
52
			if isReverse {
53
				if iterator(ctx, val.Index(i).Interface(), rulePath, ruleValue, isReverse) {
54
					return true
55
				}
56
			} else {
57
				if !iterator(ctx, val.Index(i).Interface(), rulePath, ruleValue, isReverse) {
58
					return false
59
				}
60
			}
61
		}
62
	case reflect.Map:
63
		keys := val.MapKeys()
64
		for _, v := range keys {
65
			value := val.MapIndex(v)
66
			if isReverse {
67
				if iterator(ctx, value.Interface(), rulePath, ruleValue, isReverse) {
68
					return true
69
				}
70
			} else {
71
				if !iterator(ctx, value.Interface(), rulePath, ruleValue, isReverse) {
72
					return false
73
				}
74
			}
75
		}
76
	case reflect.Struct:
77
		for i := 0; i < val.NumField(); i++ {
78
			f := val.Field(i)
79
			log.Error(f.Interface())
80
		}
81
	case reflect.String:
82
		scope := ruleValue
83

84
		str, ok := elem.(string)
85

86
		if !ok {
87
			return false
88
		}
89
		o, err := regexp.Match(ruleValue, []byte(str))
90
		if err != nil {
91
			log.Errorf("Scope validation error %s", err.Error())
92
		}
93
		if isReverse {
94
			if o {
95
				log.Debugf("value: %s match scope: %s, ok\n", str, scope)
96
				return true
97
			}
98
		} else {
99
			if !o {
100
				log.Debugf("value: %s not match with scope: %s, path: %s failed\n", str, scope, rulePath)
101
				return false
102
			}
103
			log.Debugf("value: %s match scope: %s, ok\n", str, scope)
104
		}
105

106
	default:
107
		return isReverse
108
	}
109

110
	return !isReverse
111
}
112

113
// checkRule return result, detailedMessages
114
func checkRule(ctx context.Context, name string, parameters, inputObject map[string]interface{}) error {
115
	rules := storage.Storage.GetRegoRules()
116
	rule, ok := rules[name]
117
	if !ok {
118
		return fmt.Errorf("rego rule %s not found", name)
119
	}
120
	input := map[string]interface{}{
121
		"review":     inputObject,
122
		"parameters": parameters,
123
	}
124
	results, err := rule.Eval(ctx, rego.EvalInput(input))
125
	if err != nil {
126
		// Handle evaluation error.
127
		return fmt.Errorf("check rego rule %s failed %s", name, err)
128
	}
129
	if len(results) == 0 {
130
		// Handle undefined result.
131
		return fmt.Errorf("check rego rule %s failed, result zero. (exec failed?)", name)
132
	}
133
	result, ok := results[0].Bindings["x"].([]interface{})
134
	if !ok {
135
		return fmt.Errorf("check rego rule %s failed, result exist, but unexpected. (exec failed?)", name)
136
	}
137
	if len(result) > 0 {
138
		var details []string
139

140
		for _, r := range result {
141
			details = append(details, r.(map[string]interface{})["msg"].(string))
142
		}
143

144
		return fmt.Errorf("validation failed: rego rule %s details %v", name, details)
145
	}
146
	return nil
147

148
}
149

150
func (v *Validator) Validate(ctx context.Context, obj map[string]interface{}, requestData common.ARFields) error {
151
	scopes := storage.Storage.GetScopeItems(Validation)
152
	for _, scope := range scopes {
153
		err := v.ValidateByScope(ctx, requestData, scope, obj)
154
		if err != nil {
155
			return err
156
		}
157
	}
158
	return nil
159
}
160

161
func (v *Validator) ValidateByScope(ctx context.Context, requestData common.ARFields, item v1alpha1.Item, original map[string]interface{}) error {
162
	allowed, _ := v.matcher.Match(ctx, requestData, item.Match)
163
	if !allowed {
164
		return nil
165
	}
166

167
	err := CheckRegoRule(ctx, original, item)
168
	if err != nil {
169
		return errors.Join(err, ErrCheckingRegoRule)
170
	}
171

172
	if item.Rule.Simples == nil {
173
		return nil
174
	}
175

176
	err = CheckSimplesRule(ctx, item, requestData)
177
	if err != nil {
178
		return err
179
	}
180
	return nil
181
}
182

183
func CheckSimplesRule(ctx context.Context, item v1alpha1.Item, requestData common.ARFields) error {
184
	var simplesErr error
185

186
	for _, simple := range item.Rule.Simples {
187

188
		var object map[string]interface{}
189
		if requestData.Operation != "DELETE" {
190
			object = requestData.Object
191
		} else {
192
			object = requestData.OldObject
193
		}
194

195
		result, err := jmespath.Search(simple.Path, object)
196
		if err != nil {
197
			continue
198
		}
199

200
		validationSuccess := validateValue(ctx, result, simple.Path, simple.Value, simple.Action)
201
		if !validationSuccess {
202

203
			message := simple.Message
204
			if simple.Message == "" {
205
				message = fmt.Sprintf("validation failed: rule %s path %s", simple.Value, simple.Path)
206
			}
207

208
			if simplesErr == nil {
209
				simplesErr = errors.New(message)
210
				continue
211
			}
212

213
			simplesErr = fmt.Errorf("%s; %s", simplesErr.Error(), message)
214
		}
215
	}
216
	if simplesErr != nil {
217
		return simplesErr
218
	}
219
	return nil
220
}
221

222
func CheckRegoRule(ctx context.Context, original map[string]interface{}, item v1alpha1.Item) error {
223
	if item.Rule.Rego.Template != "" {
224
		params, err := fromYAML([]byte(item.Rule.Rego.Parameters))
225
		if err != nil {
226
			return err
227
		}
228

229
		err = checkRule(ctx, item.Rule.Rego.Template, params, original)
230
		if err != nil {
231
			return err
232
		}
233
	}
234
	return nil
235
}
236

237
func validateValue(ctx context.Context, result interface{}, path, value, action string) bool {
238
	log := logger.FromContext(ctx)
239
	resultSlice, isSlice := result.([]interface{})
240
	if result != nil && value == ".*" && !isSlice {
241
		if action == Deny {
242
			return false
243
		}
244
		if action == Allow {
245
			return true
246
		}
247
	}
248
	if isSlice && len(resultSlice) != 0 && value == ".*" {
249
		if action == Deny {
250
			return false
251
		}
252
		if action == Allow {
253
			return true
254
		}
255
	}
256

257
	if action == Deny {
258
		if iterator(ctx, result, path, value, true) {
259
			log.Debugf("validation failed: rule %s path %s", value, path)
260
			return false
261
		}
262
	}
263
	if action == Allow {
264
		if !iterator(ctx, result, path, value, false) {
265
			log.Debugf("validation failed: rule %s path %s", value, path)
266
			return false
267
		}
268
	}
269

270
	return true
271
}
272

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.