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 }