github.com/kiali/kiali@v1.84.0/business/checkers/common/multi_match_selector_checker.go (about)

     1  package common
     2  
     3  import (
     4  	networking_v1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1"
     5  	security_v1beta "istio.io/client-go/pkg/apis/security/v1beta1"
     6  	"k8s.io/apimachinery/pkg/labels"
     7  
     8  	"github.com/kiali/kiali/models"
     9  )
    10  
    11  type GenericMultiMatchChecker struct {
    12  	Cluster               string
    13  	SubjectType           string
    14  	Keys                  []models.IstioValidationKey
    15  	Selectors             map[int]map[string]string
    16  	WorkloadsPerNamespace map[string]models.WorkloadList
    17  	Path                  string
    18  	skipSelSubj           bool
    19  }
    20  
    21  func PeerAuthenticationMultiMatchChecker(cluster, subjectType string, pa []*security_v1beta.PeerAuthentication, workloadsPerNamespace map[string]models.WorkloadList) GenericMultiMatchChecker {
    22  	keys := []models.IstioValidationKey{}
    23  	selectors := make(map[int]map[string]string, len(pa))
    24  	for i, p := range pa {
    25  		key := models.IstioValidationKey{
    26  			ObjectType: subjectType,
    27  			Name:       p.Name,
    28  			Namespace:  p.Namespace,
    29  			Cluster:    cluster,
    30  		}
    31  		keys = append(keys, key)
    32  		selectors[i] = make(map[string]string)
    33  		if p.Spec.Selector != nil {
    34  
    35  			selectors[i] = p.Spec.Selector.MatchLabels
    36  		}
    37  	}
    38  	return GenericMultiMatchChecker{
    39  		Cluster:               cluster,
    40  		SubjectType:           subjectType,
    41  		Keys:                  keys,
    42  		Selectors:             selectors,
    43  		WorkloadsPerNamespace: workloadsPerNamespace,
    44  		Path:                  "spec/selector",
    45  		skipSelSubj:           false,
    46  	}
    47  }
    48  
    49  func RequestAuthenticationMultiMatchChecker(cluster, subjectType string, ra []*security_v1beta.RequestAuthentication, workloadsPerNamespace map[string]models.WorkloadList) GenericMultiMatchChecker {
    50  	keys := []models.IstioValidationKey{}
    51  	selectors := make(map[int]map[string]string, len(ra))
    52  	for i, r := range ra {
    53  		key := models.IstioValidationKey{
    54  			ObjectType: subjectType,
    55  			Name:       r.Name,
    56  			Namespace:  r.Namespace,
    57  			Cluster:    cluster,
    58  		}
    59  		keys = append(keys, key)
    60  		selectors[i] = make(map[string]string)
    61  		if r.Spec.Selector != nil {
    62  			selectors[i] = r.Spec.Selector.MatchLabels
    63  		}
    64  	}
    65  	// For RequestAuthentication, when more than one policy matches a workload, Istio combines all rules as if they were specified as a single policy.
    66  	// So skip multi match validation
    67  	return GenericMultiMatchChecker{
    68  		Cluster:               cluster,
    69  		SubjectType:           subjectType,
    70  		Keys:                  keys,
    71  		Selectors:             selectors,
    72  		WorkloadsPerNamespace: workloadsPerNamespace,
    73  		Path:                  "spec/selector",
    74  		skipSelSubj:           true,
    75  	}
    76  }
    77  
    78  func SidecarSelectorMultiMatchChecker(cluster, subjectType string, sc []*networking_v1beta1.Sidecar, workloadsPerNamespace map[string]models.WorkloadList) GenericMultiMatchChecker {
    79  	keys := []models.IstioValidationKey{}
    80  	selectors := make(map[int]map[string]string, len(sc))
    81  	i := 0
    82  	for _, s := range sc {
    83  		for _, wls := range workloadsPerNamespace {
    84  			if s.Namespace != wls.Namespace {
    85  				// Workloads from Sidecar's own Namespaces only are considered in Selector
    86  				continue
    87  			}
    88  			key := models.IstioValidationKey{
    89  				ObjectType: subjectType,
    90  				Name:       s.Name,
    91  				Namespace:  s.Namespace,
    92  				Cluster:    cluster,
    93  			}
    94  			keys = append(keys, key)
    95  			selectors[i] = make(map[string]string)
    96  			if s.Spec.WorkloadSelector != nil {
    97  				selectors[i] = s.Spec.WorkloadSelector.Labels
    98  			}
    99  			i++
   100  		}
   101  	}
   102  	return GenericMultiMatchChecker{
   103  		Cluster:               cluster,
   104  		SubjectType:           subjectType,
   105  		Keys:                  keys,
   106  		Selectors:             selectors,
   107  		WorkloadsPerNamespace: workloadsPerNamespace,
   108  		Path:                  "spec/workloadSelector",
   109  		skipSelSubj:           false,
   110  	}
   111  }
   112  
   113  type KeyWithIndex struct {
   114  	Index int
   115  	Key   *models.IstioValidationKey
   116  }
   117  
   118  type ReferenceMap map[models.IstioValidationKey][]models.IstioValidationKey
   119  
   120  func (ws ReferenceMap) Add(wk, sk models.IstioValidationKey) {
   121  	ws[wk] = append(ws[wk], sk)
   122  }
   123  
   124  func (ws ReferenceMap) Get(wk models.IstioValidationKey) []models.IstioValidationKey {
   125  	return ws[wk]
   126  }
   127  
   128  func (ws ReferenceMap) HasMultipleReferences(wk models.IstioValidationKey) bool {
   129  	return len(ws.Get(wk)) > 1
   130  }
   131  
   132  func (m GenericMultiMatchChecker) Check() models.IstioValidations {
   133  	validations := models.IstioValidations{}
   134  
   135  	validations.MergeValidations(m.analyzeSelectorLessSubjects())
   136  	if !m.skipSelSubj {
   137  		validations.MergeValidations(m.analyzeSelectorSubjects())
   138  	}
   139  
   140  	return validations
   141  }
   142  
   143  func (m GenericMultiMatchChecker) analyzeSelectorLessSubjects() models.IstioValidations {
   144  	return m.buildSelectorLessSubjectValidations(m.selectorLessSubjects())
   145  }
   146  
   147  func (m GenericMultiMatchChecker) selectorLessSubjects() []KeyWithIndex {
   148  	swi := make([]KeyWithIndex, 0, len(m.Keys))
   149  	for i, k := range m.Keys {
   150  		if len(m.Selectors[i]) == 0 {
   151  			swi = append(swi, KeyWithIndex{
   152  				Index: i,
   153  				Key: &models.IstioValidationKey{
   154  					ObjectType: k.ObjectType,
   155  					Name:       k.Name,
   156  					Namespace:  k.Namespace,
   157  					Cluster:    m.Cluster,
   158  				},
   159  			})
   160  		}
   161  	}
   162  	return swi
   163  }
   164  
   165  func (m GenericMultiMatchChecker) buildSelectorLessSubjectValidations(subjects []KeyWithIndex) models.IstioValidations {
   166  	validations := models.IstioValidations{}
   167  
   168  	if len(subjects) < 2 {
   169  		return validations
   170  	}
   171  	namespaceNumbers := make(map[string]int)
   172  	for _, subjectWithIndex := range subjects {
   173  		namespaceNumbers[subjectWithIndex.Key.Namespace]++
   174  	}
   175  
   176  	for _, subjectWithIndex := range subjects {
   177  		// skip subjects which do not have duplicates in same namespace
   178  		if namespaceNumbers[subjectWithIndex.Key.Namespace] < 2 {
   179  			continue
   180  		}
   181  		references := extractReferences(subjectWithIndex.Index, subjectWithIndex.Key.Namespace, subjects)
   182  		checks := models.Build("generic.multimatch.selectorless", m.Path)
   183  		validations.MergeValidations(
   184  			models.IstioValidations{
   185  				*subjectWithIndex.Key: &models.IstioValidation{
   186  					Name:       subjectWithIndex.Key.Name,
   187  					ObjectType: subjectWithIndex.Key.ObjectType,
   188  					Valid:      false,
   189  					References: references,
   190  					Checks: []*models.IstioCheck{
   191  						&checks,
   192  					},
   193  				},
   194  			},
   195  		)
   196  	}
   197  	return validations
   198  }
   199  
   200  func extractReferences(index int, namespace string, subjects []KeyWithIndex) []models.IstioValidationKey {
   201  	references := make([]models.IstioValidationKey, 0, len(subjects)-1)
   202  
   203  	for _, s := range subjects {
   204  		if s.Index != index && s.Key.Namespace == namespace {
   205  			references = append(references, *s.Key)
   206  		}
   207  	}
   208  
   209  	return references
   210  }
   211  
   212  func (m GenericMultiMatchChecker) analyzeSelectorSubjects() models.IstioValidations {
   213  	subjects := m.multiMatchSubjects()
   214  	return m.buildSubjectValidations(subjects)
   215  }
   216  
   217  func (m GenericMultiMatchChecker) multiMatchSubjects() ReferenceMap {
   218  	workloadSubjects := ReferenceMap{}
   219  
   220  	for i, s := range m.Keys {
   221  		subjectKey := models.BuildKey(m.SubjectType, s.Name, s.Namespace)
   222  
   223  		selector := labels.SelectorFromSet(m.Selectors[i])
   224  		if selector.Empty() {
   225  			continue
   226  		}
   227  
   228  		for _, wls := range m.WorkloadsPerNamespace {
   229  			for _, w := range wls.Workloads {
   230  				if !selector.Matches(labels.Set(w.Labels)) {
   231  					continue
   232  				}
   233  
   234  				workloadKey := models.BuildKey(w.Type, w.Name, wls.Namespace)
   235  				workloadSubjects.Add(workloadKey, subjectKey)
   236  			}
   237  		}
   238  	}
   239  
   240  	return workloadSubjects
   241  }
   242  
   243  func (m GenericMultiMatchChecker) buildSubjectValidations(workloadSubject ReferenceMap) models.IstioValidations {
   244  	validations := models.IstioValidations{}
   245  
   246  	for wk, scs := range workloadSubject {
   247  		if !workloadSubject.HasMultipleReferences(wk) {
   248  			continue
   249  		}
   250  
   251  		validations.MergeValidations(m.buildMultipleSubjectValidation(scs))
   252  	}
   253  
   254  	return validations
   255  }
   256  
   257  func (m GenericMultiMatchChecker) buildMultipleSubjectValidation(scs []models.IstioValidationKey) models.IstioValidations {
   258  	validations := models.IstioValidations{}
   259  
   260  	namespaceNumbers := make(map[string]int)
   261  	for _, sck := range scs {
   262  		namespaceNumbers[sck.Namespace]++
   263  	}
   264  
   265  	for i, sck := range scs {
   266  		// skip subjects which do not have duplicates in same namespace
   267  		if namespaceNumbers[sck.Namespace] < 2 {
   268  			continue
   269  		}
   270  		// Remove validation subject and other namespace subjects from references
   271  		refs := make([]models.IstioValidationKey, 0, len(scs)-1)
   272  		for refIndex, refSck := range scs {
   273  			if refIndex != i && refSck.Namespace == sck.Namespace {
   274  				refs = append(refs, refSck)
   275  			}
   276  		}
   277  
   278  		checks := models.Build("generic.multimatch.selector", m.Path)
   279  		validation := models.IstioValidations{
   280  			sck: &models.IstioValidation{
   281  				Name:       sck.Name,
   282  				ObjectType: m.SubjectType,
   283  				Valid:      false,
   284  				References: refs,
   285  				Checks: []*models.IstioCheck{
   286  					&checks,
   287  				},
   288  			},
   289  		}
   290  
   291  		validations.MergeValidations(validation)
   292  	}
   293  
   294  	return validations
   295  }