kubelatte-ce
Форк от sbertech/kubelatte-ce
271 строка · 6.5 Кб
1package validation2
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) error23ValidateByScope(ctx context.Context, requestData common.ARFields, item v1alpha1.Item, original map[string]interface{}) error24}
25
26type Validator struct {27matcher match.MatcherI28act ValidatorI
29}
30
31func NewValidator(matcher match.MatcherI, act ValidatorI) ValidatorI {32validator := &Validator{matcher: matcher}33if act == nil {34act = validator35}36validator.act = act37return validator38}
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 false47}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 true55}56} else {57if !iterator(ctx, val.Index(i).Interface(), rulePath, ruleValue, isReverse) {58return false59}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 true69}70} else {71if !iterator(ctx, value.Interface(), rulePath, ruleValue, isReverse) {72return false73}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 := ruleValue83
84str, ok := elem.(string)85
86if !ok {87return false88}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 true97}98} else {99if !o {100log.Debugf("value: %s not match with scope: %s, path: %s failed\n", str, scope, rulePath)101return false102}103log.Debugf("value: %s match scope: %s, ok\n", str, scope)104}105
106default:107return isReverse108}109
110return !isReverse111}
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 []string139
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 nil147
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 err156}157}158return nil159}
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 nil165}166
167err := CheckRegoRule(ctx, original, item)168if err != nil {169return errors.Join(err, ErrCheckingRegoRule)170}171
172if item.Rule.Simples == nil {173return nil174}175
176err = CheckSimplesRule(ctx, item, requestData)177if err != nil {178return err179}180return nil181}
182
183func CheckSimplesRule(ctx context.Context, item v1alpha1.Item, requestData common.ARFields) error {184var simplesErr error185
186for _, simple := range item.Rule.Simples {187
188var object map[string]interface{}189if requestData.Operation != "DELETE" {190object = requestData.Object191} else {192object = requestData.OldObject193}194
195result, err := jmespath.Search(simple.Path, object)196if err != nil {197continue198}199
200validationSuccess := validateValue(ctx, result, simple.Path, simple.Value, simple.Action)201if !validationSuccess {202
203message := simple.Message204if simple.Message == "" {205message = fmt.Sprintf("validation failed: rule %s path %s", simple.Value, simple.Path)206}207
208if simplesErr == nil {209simplesErr = errors.New(message)210continue211}212
213simplesErr = fmt.Errorf("%s; %s", simplesErr.Error(), message)214}215}216if simplesErr != nil {217return simplesErr218}219return nil220}
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 err227}228
229err = checkRule(ctx, item.Rule.Rego.Template, params, original)230if err != nil {231return err232}233}234return nil235}
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 false243}244if action == Allow {245return true246}247}248if isSlice && len(resultSlice) != 0 && value == ".*" {249if action == Deny {250return false251}252if action == Allow {253return true254}255}256
257if action == Deny {258if iterator(ctx, result, path, value, true) {259log.Debugf("validation failed: rule %s path %s", value, path)260return false261}262}263if action == Allow {264if !iterator(ctx, result, path, value, false) {265log.Debugf("validation failed: rule %s path %s", value, path)266return false267}268}269
270return true271}
272