github.com/kiali/kiali@v1.84.0/business/checkers/destinationrules/no_dest_checker.go (about) 1 package destinationrules 2 3 import ( 4 "strconv" 5 6 networking_v1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" 7 "k8s.io/apimachinery/pkg/labels" 8 9 "github.com/kiali/kiali/kubernetes" 10 "github.com/kiali/kiali/models" 11 ) 12 13 type NoDestinationChecker struct { 14 Namespaces models.Namespaces 15 WorkloadsPerNamespace map[string]models.WorkloadList 16 DestinationRule *networking_v1beta1.DestinationRule 17 VirtualServices []*networking_v1beta1.VirtualService 18 ServiceEntries []*networking_v1beta1.ServiceEntry 19 RegistryServices []*kubernetes.RegistryService 20 PolicyAllowAny bool 21 } 22 23 // Check parses the DestinationRule definitions and verifies that they point to an existing service, including any subset definitions 24 func (n NoDestinationChecker) Check() ([]*models.IstioCheck, bool) { 25 valid := true 26 validations := make([]*models.IstioCheck, 0) 27 labelValidations := make([]*models.IstioCheck, 0) 28 29 namespace := n.DestinationRule.Namespace 30 31 fqdn := kubernetes.GetHost(n.DestinationRule.Spec.Host, namespace, n.Namespaces.GetNames()) 32 // Testing Kubernetes Services + Istio ServiceEntries + Istio Runtime Registry (cross namespace) 33 if !n.hasMatchingService(fqdn, namespace) { 34 validation := models.Build("destinationrules.nodest.matchingregistry", "spec/host") 35 if n.PolicyAllowAny { 36 validation.Severity = models.WarningSeverity 37 } 38 valid = false 39 validations = append(validations, &validation) 40 } else if len(n.DestinationRule.Spec.Subsets) > 0 { 41 // Check that each subset has a matching workload somewhere.. 42 hasLabel := false 43 for i, subset := range n.DestinationRule.Spec.Subsets { 44 if len(subset.Labels) > 0 { 45 if !n.hasMatchingWorkload(fqdn, subset.Labels) { 46 validation := models.Build("destinationrules.nodest.subsetlabels", 47 "spec/subsets["+strconv.Itoa(i)+"]") 48 if n.isSubsetReferenced(n.DestinationRule.Spec.Host, subset.Name) { 49 valid = false 50 } else { 51 validation.Severity = models.Unknown 52 } 53 validations = append(validations, &validation) 54 } else { 55 hasLabel = true 56 } 57 } else { 58 validation := models.Build("destinationrules.nodest.subsetnolabels", 59 "spec/subsets["+strconv.Itoa(i)+"]") 60 labelValidations = append(labelValidations, &validation) 61 // Not changing valid value, if other subset is on error, a valid = false has priority 62 } 63 } 64 for _, v := range labelValidations { 65 if hasLabel { 66 v.Severity = models.Unknown 67 } 68 validations = append(validations, v) 69 } 70 } 71 return validations, valid 72 } 73 74 func (n NoDestinationChecker) hasMatchingWorkload(host kubernetes.Host, subsetLabels map[string]string) bool { 75 // Check wildcard hosts - needs to match "*" and "*.suffix" also.. 76 if host.IsWildcard() { 77 return true 78 } 79 80 // Covering 'servicename.namespace' host format scenario 81 localSvc, localNs := kubernetes.ParseTwoPartHost(host) 82 83 var selectors map[string]string 84 85 // Find the correct service 86 for _, s := range n.RegistryServices { 87 if s.Attributes.Name == localSvc && s.Attributes.Namespace == localNs { 88 selectors = s.Attributes.LabelSelectors 89 break 90 } 91 } 92 93 subsetLabelSet := labels.Set(subsetLabels) 94 subsetSelector := labels.SelectorFromSet(subsetLabelSet) 95 96 // Check workloads 97 if len(selectors) != 0 { 98 selector := labels.SelectorFromSet(labels.Set(selectors)) 99 100 for _, wl := range n.WorkloadsPerNamespace[localNs].Workloads { 101 wlLabelSet := labels.Set(wl.Labels) 102 if selector.Matches(wlLabelSet) { 103 if subsetSelector.Matches(wlLabelSet) { 104 return true 105 } 106 } 107 } 108 } else { 109 // Check Service Entries 110 for _, se := range n.ServiceEntries { 111 for _, ep := range se.Spec.Endpoints { 112 epLabelSet := labels.Set(ep.Labels) 113 if subsetSelector.Matches(epLabelSet) { 114 return true 115 } 116 } 117 } 118 } 119 return false 120 } 121 122 func (n NoDestinationChecker) hasMatchingService(host kubernetes.Host, itemNamespace string) bool { 123 // Check wildcard hosts - needs to match "*" and "*.suffix" also.. 124 if host.IsWildcard() { 125 return true 126 } 127 128 // Covering 'servicename.namespace' host format scenario 129 localSvc, localNs := kubernetes.ParseTwoPartHost(host) 130 131 if localNs == itemNamespace { 132 // Check Workloads 133 if matches := kubernetes.HasMatchingWorkloads(localSvc, n.WorkloadsPerNamespace[localNs].GetLabels()); matches { 134 return matches 135 } 136 } 137 138 // Check ServiceEntries 139 if kubernetes.HasMatchingServiceEntries(host.String(), kubernetes.ServiceEntryHostnames(n.ServiceEntries)) { 140 return true 141 } 142 143 // Use RegistryService to check destinations that may not be covered with previous check 144 // i.e. Multi-cluster or Federation validations 145 if kubernetes.HasMatchingRegistryService(itemNamespace, host.String(), n.RegistryServices) { 146 return true 147 } 148 return false 149 } 150 151 func (n NoDestinationChecker) isSubsetReferenced(host string, subset string) bool { 152 virtualServices, ok := n.getVirtualServices(host, subset) 153 if ok && len(virtualServices) > 0 { 154 return true 155 } 156 157 return false 158 } 159 160 func (n NoDestinationChecker) getVirtualServices(virtualServiceHost string, virtualServiceSubset string) ([]*networking_v1beta1.VirtualService, bool) { 161 vss := make([]*networking_v1beta1.VirtualService, 0, len(n.VirtualServices)) 162 163 for _, virtualService := range n.VirtualServices { 164 165 if len(virtualService.Spec.Http) > 0 { 166 for _, httpRoute := range virtualService.Spec.Http { 167 if httpRoute == nil { 168 continue 169 } 170 if len(httpRoute.Route) > 0 { 171 for _, dest := range httpRoute.Route { 172 if dest == nil || dest.Destination == nil { 173 continue 174 } 175 host := dest.Destination.Host 176 subset := dest.Destination.Subset 177 drHost := kubernetes.GetHost(host, n.DestinationRule.Namespace, n.Namespaces.GetNames()) 178 vsHost := kubernetes.GetHost(virtualServiceHost, virtualService.Namespace, n.Namespaces.GetNames()) 179 // Host could be in another namespace (FQDN) 180 if kubernetes.FilterByHost(vsHost.String(), vsHost.Namespace, drHost.Service, drHost.Namespace) && subset == virtualServiceSubset { 181 vss = append(vss, virtualService) 182 } 183 } 184 } 185 } 186 } 187 188 if len(virtualService.Spec.Tcp) > 0 { 189 for _, tcpRoute := range virtualService.Spec.Tcp { 190 if tcpRoute == nil { 191 continue 192 } 193 if len(tcpRoute.Route) > 0 { 194 for _, dest := range tcpRoute.Route { 195 if dest == nil || dest.Destination == nil { 196 continue 197 } 198 host := dest.Destination.Host 199 subset := dest.Destination.Subset 200 drHost := kubernetes.GetHost(host, n.DestinationRule.Namespace, n.Namespaces.GetNames()) 201 vsHost := kubernetes.GetHost(virtualServiceHost, virtualService.Namespace, n.Namespaces.GetNames()) 202 // Host could be in another namespace (FQDN) 203 if kubernetes.FilterByHost(vsHost.String(), vsHost.Namespace, drHost.Service, drHost.Namespace) && subset == virtualServiceSubset { 204 vss = append(vss, virtualService) 205 } 206 } 207 } 208 } 209 } 210 211 if len(virtualService.Spec.Tls) > 0 { 212 for _, tlsRoute := range virtualService.Spec.Tls { 213 if tlsRoute == nil { 214 continue 215 } 216 if len(tlsRoute.Route) > 0 { 217 for _, dest := range tlsRoute.Route { 218 if dest == nil || dest.Destination == nil { 219 continue 220 } 221 host := dest.Destination.Host 222 subset := dest.Destination.Subset 223 drHost := kubernetes.GetHost(host, n.DestinationRule.Namespace, n.Namespaces.GetNames()) 224 vsHost := kubernetes.GetHost(virtualServiceHost, virtualService.Namespace, n.Namespaces.GetNames()) 225 // Host could be in another namespace (FQDN) 226 if kubernetes.FilterByHost(vsHost.String(), vsHost.Namespace, drHost.Service, drHost.Namespace) && subset == virtualServiceSubset { 227 vss = append(vss, virtualService) 228 } 229 } 230 } 231 } 232 } 233 } 234 235 return vss, len(vss) > 0 236 }