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  }