kubelatte-ce
Форк от sbertech/kubelatte-ce
452 строки · 12.4 Кб
1package match2
3import (4"context"5"errors"6"fmt"7"github.com/jmespath/go-jmespath"8"gitverse.ru/synapse/kubelatte/pkg/api/common"9"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"10"gitverse.ru/synapse/kubelatte/pkg/observability/logger"11"gitverse.ru/synapse/kubelatte/pkg/storage"12"k8s.io/api/admission/v1beta1"13"reflect"14"regexp"15"strings"16)
17
18const Enabled = "enabled"19
20type selOperator string21
22const (23In selOperator = "In"24NotIn selOperator = "NotIn"25Exists selOperator = "Exists"26DoesNotExist selOperator = "DoesNotExist"27)
28
29const (30scopeCluster = "Cluster"31scopeNamespaced = "Namespaced"32scopeAny = "*"33)
34
35const (36mustExist = "MustExist"37mustNotExist = "MustNotExist"38)
39
40var ErrMatchFailed = errors.New("err match failed")41
42type MatcherI interface {43Match(ctx context.Context, data common.ARFields, match v1alpha1.Match) (bool, error)44}
45
46type Matcher struct{}47
48func NewMatcher() MatcherI {49return &Matcher{}50}
51
52func (m *Matcher) Match(ctx context.Context, data common.ARFields, match v1alpha1.Match) (bool, error) {53log := logger.FromContext(ctx)54
55if match.IsEmpty() {56return true, nil57}58
59var validationFuncs []func(ctx context.Context, data common.ARFields, match v1alpha1.Match) (matches bool, reason string)60validationFuncs = append(validationFuncs,61m.doesOperationsMatches,62m.doesScopeMatches,63m.doesUserInfoMatches,64m.doesKindsMatches,65m.doesNameMatches,66m.doesNamespaceSelectorMatches,67m.doesIncludeExcludeNssMatch,68m.doesAnnotationSelectorMatches,69m.doesLabelSelectorMatches,70m.doesMatchConditionsMatches,71)72var matches bool73var reason string74for _, f := range validationFuncs {75matches, reason = f(ctx, data, match)76
77if !matches {78obj := getObjectToValidate(ctx, data)79var name = getAdmissionReviewResourceName(obj)80log.Debugf("[NotMatched] resource kind %v name %v due to %s", data.Kind.Kind, name, reason)81return false, errors.New(reason)82}83}84
85return true, nil86}
87
88func getAdmissionReviewResourceName(obj map[string]interface{}) (name string) {89metadata, _ := obj["metadata"].(map[string]interface{})90if metadata["name"] != nil {91name = metadata["name"].(string)92} else if metadata["generateName"] != nil {93name = metadata["generateName"].(string)94} else {95name = "config_name_was_not_found"96}97return name98}
99
100func getObjectToValidate(ctx context.Context, data common.ARFields) map[string]interface{} {101log := logger.FromContext(ctx)102switch data.Operation {103case v1beta1.Create:104return data.Object105case v1beta1.Update:106return data.Object107case v1beta1.Delete:108return data.OldObject109}110
111log.Error("wrong operation")112return nil113}
114
115func (m *Matcher) doesOperationsMatches(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (matches bool, reason string) {116mOperation := match.Operations117arOperation := fields.Operation118
119if len(mOperation) == 0 {120return true, ""121}122
123for _, op := range mOperation {124if v1beta1.Operation(op) == arOperation {125return true, ""126}127}128return false, "request.operation"129}
130
131func (m *Matcher) doesScopeMatches(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (matches bool, reason string) {132mScope := match.Scope133if mScope == scopeAny || mScope == "" {134return true, ""135}136var isNs bool137isNs = isNamespaced(fields)138if (isNs && mScope == scopeNamespaced) || (!isNs && mScope == scopeCluster) {139return true, ""140} else {141return false, "request.namespace"142}143}
144
145func (m *Matcher) doesUserInfoMatches(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (matches bool, reason string) {146if reflect.DeepEqual(match.UserInfo, v1alpha1.UserInfo{}) {147return true, ""148}149mUsername := match.UserInfo.Username150arUsername := fields.UserInfo.Username151if mUsername == "" {152mUsername = match.UserInfo.UsernameRegex153b, err := regexp.Match(mUsername, []byte(arUsername))154if err != nil {155return false, "wrong match.userInfo.usernameRegex: " + fmt.Sprint(err)156}157if !b {158return false, "request.userInfo.username"159}160return b, ""161} else {162if match.UserInfo.Username != arUsername {163return false, "request.userInfo.username"164}165
166return true, ""167}168}
169
170func (m *Matcher) doesKindsMatches(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (matches bool, reason string) {171mKinds := match.Kinds172arKinds := fields.Kind173
174kMatch, vMatch := false, false175
176for _, kind := range mKinds {177if !kMatch {178kMatch = existsStringInSlice(kind.Kind, arKinds.Kind)179}180if !vMatch {181vMatch = checkStringsMatchingWithSliceRegexp(kind.ApiGroups, arKinds.Group)182}183}184if kMatch && vMatch {185return true, ""186}187
188if !kMatch {189reason = "request.kind.kind"190}191if !vMatch {192reason = "request.kind.group"193}194
195return false, reason196}
197
198func (m *Matcher) doesNameMatches(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (matches bool, reason string) {199reason = "request.oldObject.metadata.name"200
201namePattern := match.Name202if reflect.DeepEqual(namePattern, v1alpha1.Name{}) { //nolint:exhaustruct203return true, ""204}205object := getObjectToValidate(ctx, fields)206name := getAdmissionReviewResourceName(object)207
208if namePattern.Value != "" {209if namePattern.Value == name {210return true, ""211} else {212return false, reason213}214} else {215matchRes, err := regexp.Match(namePattern.Regex, []byte(name))216if err != nil {217return false, "wrong match.name.regex: " + fmt.Sprint(err)218}219return matchRes, reason220}221}
222
223func (m *Matcher) doesIncludeExcludeNssMatch(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (bool, string) {224
225var matches = true226var reason = ""227
228var excluded = match.ExcludedNamespaces229var included = match.IncludedNamespaces230
231ns := fields.Namespace232if ns == "" {233return true, ""234}235
236if !reflect.DeepEqual(excluded, v1alpha1.Namespace{Values: nil, Regex: nil}) {237matches, reason = m.doesNsNamesMatch(excluded, ns, false)238if !matches {239return matches, reason240}241} else if !reflect.DeepEqual(included, v1alpha1.Namespace{Values: nil, Regex: nil}) {242matches, reason = m.doesNsNamesMatch(included, ns, true)243if !matches {244return matches, reason245}246}247
248return true, ""249}
250
251func (m *Matcher) doesNsNamesMatch(matchNs v1alpha1.Namespace, ns string, include bool) (bool, string) {252
253var reason = ""254var reasonPrefix string255var match bool256
257if include {258reasonPrefix = "included "259} else {260reasonPrefix = "excluded "261}262
263if matchNs.Values != nil {264match, reason = findNonRegexNsMatch(ns, matchNs.Values)265return include == match, reasonPrefix + reason266} else {267match, reason = findRegexNsMatch(ns, matchNs.Regex)268if reason != "namespace invalid regex" {269return include == match, reasonPrefix + reason270} else {271return match, reason272}273
274}275}
276
277func findNonRegexNsMatch(ns string, matchVal []string) (bool, string) {278for _, matchNs := range matchVal {279if ns == matchNs {280return true, "namespace value"281}282}283return false, "namespace value"284}
285
286func findRegexNsMatch(ns string, matchVal []string) (bool, string) {287log := logger.FromContext(context.Background())288for _, matchNs := range matchVal {289match, err := regexp.Match(matchNs, []byte(ns))290if err != nil {291log.Errorf("Namespace regex err: %s", err.Error())292return false, "namespace invalid regex"293}294
295if match {296return true, "namespace regex"297}298}299return false, "namespace regex"300}
301
302func (m *Matcher) doesNamespaceSelectorMatches(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (bool, string) {303log := logger.FromContext(ctx)304if reflect.DeepEqual(match.NamespaceSelector, v1alpha1.NamespaceSelector{}) {305return true, "namespace selector"306}307
308if fields.Namespace == "" { // non namespaced object309log.Debugf("[Skipped selector namespace] resource %s escaped NamespaceSelectorMatch check because it's Cluster Object", fields.Kind.Kind)310return true, "namespace selector"311}312
313namespaces := storage.Storage.GetNamespaces()314for _, ns := range namespaces {315if ns.ObjectMeta.Name == fields.Namespace {316matches := doesMatchExpressionsMatch(ns.ObjectMeta.Labels, match.NamespaceSelector.MatchExpressions)317if matches {318return true, ""319}320}321}322return false, "namespace selector"323}
324
325func (m *Matcher) doesAnnotationSelectorMatches(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (matches bool, reason string) {326log := logger.FromContext(ctx)327if reflect.DeepEqual(match.AnnotationSelector, v1alpha1.AnnotationSelector{}) {328return true, ""329}330matchExpr := match.AnnotationSelector.MatchExpressions331
332object := getObjectToValidate(ctx, fields)333annotations, _ := jmespath.Search("metadata.annotations", object)334
335arAnnotations, ok := annotations.(map[string]interface{})336if !ok {337log.Info("perhaps the configuration being processed does not have annotations. " +338"an error was detected when trying to extract the metadata.annotations section")339return false, "request.metadata.annotations"340}341arAnnotationsStr := parseMapValuesToString(arAnnotations)342
343return doesMatchExpressionsMatch(arAnnotationsStr, matchExpr), "annotation selector"344}
345
346func (m *Matcher) doesLabelSelectorMatches(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (matches bool, reason string) {347log := logger.FromContext(ctx)348selector := match.LabelSelector349object := getObjectToValidate(ctx, fields)350
351if reflect.DeepEqual(selector, v1alpha1.LabelSelector{}) {352return true, ""353}354
355labels, _ := jmespath.Search("metadata.labels", object)356
357lbs, ok := labels.(map[string]interface{})358if !ok {359log.Info("perhaps the configuration being processed does not have labels. " +360"an error was detected when trying to extract the metadata.labels section")361return false, "wrong metadata.labels format"362}363arLabelsStr := parseMapValuesToString(lbs)364
365return doesMatchExpressionsMatch(arLabelsStr, selector.MatchExpressions), "metadata.labels"366}
367
368func (m *Matcher) doesMatchConditionsMatches(ctx context.Context, fields common.ARFields, match v1alpha1.Match) (bool, string) {369if reflect.DeepEqual(match.MatchConditions, v1alpha1.MatchConditions{}) {370return true, ""371}372
373var matches = true374var reason = ""375
376switch fields.Operation {377case v1beta1.Create:378if fields.Object != nil && len(match.MatchConditions.Object) != 0 {379matches, reason = m.doesObjectSectionMatches(fields.Object, match.MatchConditions.Object)380if !matches {381return matches, reason382}383}384case v1beta1.Delete:385if fields.OldObject != nil && len(match.MatchConditions.OldObject) != 0 {386matches, reason = m.doesObjectSectionMatches(fields.OldObject, match.MatchConditions.OldObject)387if !matches {388return matches, reason389}390}391case v1beta1.Update:392if fields.Object != nil && len(match.MatchConditions.Object) != 0 {393matches, reason = m.doesObjectSectionMatches(fields.Object, match.MatchConditions.Object)394if !matches {395return matches, reason396}397}398if fields.OldObject != nil && len(match.MatchConditions.OldObject) != 0 {399matches, reason = m.doesObjectSectionMatches(fields.OldObject, match.MatchConditions.OldObject)400if !matches {401return matches, reason402}403}404}405return true, ""406
407}
408
409func (m *Matcher) doesObjectSectionMatches(obj map[string]interface{}, matchObjs []v1alpha1.Obj) (bool, string) {410log := logger.FromContext(context.Background())411if len(matchObjs) == 0 {412return false, "match conditions doesn't have enough of arguments"413}414for _, mObj := range matchObjs {415var path = strings.ReplaceAll(mObj.Path, "`", "'")416res, err := jmespath.Search(path, obj)417if err != nil {418log.Errorf("when processing MatchConditions jmespath: %s, value: %s an error was detected: %s ", mObj.Path, mObj.Condition, err.Error())419return false, "match conditions jmespath processing error"420}421switch v := res.(type) {422case []interface{}:423if (len(v) == 0 && mObj.Condition == mustNotExist) || (len(v) != 0 && mObj.Condition == mustExist) {424continue425}426return false, "match conditions"427case bool:428if (v == true && mObj.Condition == mustExist) || (v == false && mObj.Condition == mustNotExist) {429continue430}431return false, "match conditions"432case interface{}:433if (v == nil && mObj.Condition == mustNotExist) || (v != nil && mObj.Condition == mustExist) {434continue435}436return false, "match conditions"437default:438if v == nil {439if mObj.Condition == mustNotExist {440continue441}442return false, "match conditions"443}444
445log.Errorf("when processing MatchConditions jmespath: %s, value: %s an error occurred with unexpected type: %T! ", mObj.Path, mObj.Condition, v)446return false, "match conditions"447
448}449
450}451return true, ""452}
453