github.com/kiali/kiali@v1.84.0/business/checkers/destinationrules/multi_match_checker.go (about) 1 package destinationrules 2 3 import ( 4 "fmt" 5 "strings" 6 7 networking_v1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" 8 9 "github.com/kiali/kiali/kubernetes" 10 "github.com/kiali/kiali/models" 11 ) 12 13 const DestinationRulesCheckerType = "destinationrule" 14 15 type MultiMatchChecker struct { 16 Cluster string 17 DestinationRules []*networking_v1beta1.DestinationRule 18 ServiceEntries map[string][]string 19 Namespaces models.Namespaces 20 } 21 22 type subset struct { 23 Name string 24 Namespace string 25 RuleName string 26 } 27 28 type rule struct { 29 Name string 30 Namespace string 31 } 32 33 // Check validates that no two destinationRules target the same host+subset combination 34 func (m MultiMatchChecker) Check() models.IstioValidations { 35 validations := models.IstioValidations{} 36 37 // Equality search is: [fqdn.String()][subset] except for ServiceEntry targets which use [host][subset] 38 seenHostSubsets := make(map[string]map[string][]rule) 39 40 for _, dr := range m.DestinationRules { 41 destinationRulesName := dr.Name 42 destinationRulesNamespace := dr.Namespace 43 fqdn := kubernetes.GetHost(dr.Spec.Host, dr.Namespace, m.Namespaces.GetNames()) 44 45 // Skip DR validation if it enables mTLS either namespace or mesh-wide 46 if isNonLocalmTLSForServiceEnabled(dr, fqdn.String()) { 47 continue 48 } 49 50 foundSubsets := extractSubsets(dr, destinationRulesName, destinationRulesNamespace) 51 52 if fqdn.IsWildcard() { 53 // We need to check the matching subsets from all hosts now 54 for _, h := range seenHostSubsets { 55 checkCollisions(validations, destinationRulesNamespace, destinationRulesName, foundSubsets, h, m.Cluster) 56 } 57 // We add * later 58 } 59 // Search "*" first and then exact name 60 if previous, found := seenHostSubsets[fmt.Sprintf("*.%s.%s", fqdn.Namespace, fqdn.Cluster)]; found { 61 // Need to check subsets of "*" 62 checkCollisions(validations, destinationRulesNamespace, destinationRulesName, foundSubsets, previous, m.Cluster) 63 } 64 65 if previous, found := seenHostSubsets[fqdn.String()]; found { 66 // Host found, need to check underlying subsets 67 checkCollisions(validations, destinationRulesNamespace, destinationRulesName, foundSubsets, previous, m.Cluster) 68 } 69 // Nothing threw an error, so add these 70 if _, found := seenHostSubsets[fqdn.String()]; !found { 71 seenHostSubsets[fqdn.String()] = make(map[string][]rule) 72 } 73 for _, s := range foundSubsets { 74 seenHostSubsets[fqdn.String()][s.Name] = append(seenHostSubsets[fqdn.String()][s.Name], rule{destinationRulesName, destinationRulesNamespace}) 75 } 76 77 } 78 79 return validations 80 } 81 82 func isNonLocalmTLSForServiceEnabled(dr *networking_v1beta1.DestinationRule, service string) bool { 83 return strings.HasPrefix(service, "*") && ismTLSEnabled(dr) 84 } 85 86 func ismTLSEnabled(dr *networking_v1beta1.DestinationRule) bool { 87 if dr.Spec.TrafficPolicy != nil && dr.Spec.TrafficPolicy.Tls != nil { 88 mode := dr.Spec.TrafficPolicy.Tls.Mode.String() 89 return mode == "ISTIO_MUTUAL" 90 } 91 return false 92 } 93 94 func extractSubsets(dr *networking_v1beta1.DestinationRule, destinationRulesName string, destinationRulesNamespace string) []subset { 95 if len(dr.Spec.Subsets) > 0 { 96 foundSubsets := []subset{} 97 for _, ss := range dr.Spec.Subsets { 98 foundSubsets = append(foundSubsets, subset{ 99 Name: ss.Name, 100 Namespace: destinationRulesNamespace, 101 RuleName: destinationRulesName, 102 }) 103 } 104 return foundSubsets 105 } 106 // Matches all the subsets:~ 107 return []subset{{"~", destinationRulesNamespace, destinationRulesName}} 108 } 109 110 func checkCollisions(validations models.IstioValidations, namespace, destinationRulesName string, foundSubsets []subset, existing map[string][]rule, cluster string) { 111 // If current subset is ~ 112 if len(foundSubsets) == 1 && foundSubsets[0].Name == "~" { 113 // This should match any subset in the same hostname 114 for _, v := range existing { 115 for _, e := range v { 116 addError(validations, []string{namespace, e.Namespace}, []string{destinationRulesName, e.Name}, cluster) 117 } 118 } 119 } 120 121 // If we have existing subset with ~ 122 if rules, found := existing["~"]; found { 123 for _, rule := range rules { 124 addError(validations, []string{namespace, rule.Namespace}, []string{destinationRulesName, rule.Name}, cluster) 125 } 126 } 127 128 for _, s := range foundSubsets { 129 if rules, found := existing[s.Name]; found { 130 for _, rule := range rules { 131 addError(validations, []string{namespace, rule.Namespace}, []string{destinationRulesName, rule.Name}, cluster) 132 } 133 } 134 } 135 } 136 137 // addError links new validation errors to the validations. namespaces nad destinationRuleNames must always be a pair 138 func addError(validations models.IstioValidations, namespaces []string, destinationRuleNames []string, cluster string) models.IstioValidations { 139 key0, rrValidation0 := createError("destinationrules.multimatch", namespaces[0], destinationRuleNames[0], cluster, true) 140 key1, rrValidation1 := createError("destinationrules.multimatch", namespaces[1], destinationRuleNames[1], cluster, true) 141 142 rrValidation0.References = append(rrValidation0.References, key1) 143 rrValidation1.References = append(rrValidation1.References, key0) 144 145 validations.MergeValidations(models.IstioValidations{key0: rrValidation0}) 146 validations.MergeValidations(models.IstioValidations{key1: rrValidation1}) 147 148 return validations 149 } 150 151 func createError(errorText, namespace, destinationRuleName, cluster string, valid bool) (models.IstioValidationKey, *models.IstioValidation) { 152 key := models.IstioValidationKey{Name: destinationRuleName, Namespace: namespace, ObjectType: DestinationRulesCheckerType, Cluster: cluster} 153 checks := models.Build(errorText, "spec/host") 154 rrValidation := &models.IstioValidation{ 155 Name: destinationRuleName, 156 ObjectType: DestinationRulesCheckerType, 157 Valid: valid, 158 Checks: []*models.IstioCheck{ 159 &checks, 160 }, 161 References: make([]models.IstioValidationKey, 0), 162 } 163 164 return key, rrValidation 165 }