istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/util/service_lookup.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package util
    16  
    17  import (
    18  	"strings"
    19  
    20  	corev1 "k8s.io/api/core/v1"
    21  
    22  	"istio.io/api/annotation"
    23  	"istio.io/api/networking/v1alpha3"
    24  	"istio.io/istio/pkg/config/analysis"
    25  	"istio.io/istio/pkg/config/resource"
    26  	"istio.io/istio/pkg/config/schema/gvk"
    27  )
    28  
    29  func InitServiceEntryHostMap(ctx analysis.Context) map[ScopedFqdn]*v1alpha3.ServiceEntry {
    30  	result := make(map[ScopedFqdn]*v1alpha3.ServiceEntry)
    31  
    32  	ctx.ForEach(gvk.ServiceEntry, func(r *resource.Instance) bool {
    33  		s := r.Message.(*v1alpha3.ServiceEntry)
    34  		hostsNamespaceScope := string(r.Metadata.FullName.Namespace)
    35  
    36  		exportsToAll := false
    37  		for _, h := range s.GetHosts() {
    38  			// ExportToAll scenario
    39  			if len(s.ExportTo) == 0 || exportsToAll {
    40  				result[NewScopedFqdn(ExportToAllNamespaces, r.Metadata.FullName.Namespace, h)] = s
    41  				continue // If exports to all, we can skip adding to each namespace
    42  			}
    43  
    44  			for _, ns := range s.ExportTo {
    45  				switch ns {
    46  				case ExportToAllNamespaces:
    47  					result[NewScopedFqdn(ExportToAllNamespaces, r.Metadata.FullName.Namespace, h)] = s
    48  					exportsToAll = true
    49  				case ExportToNamespaceLocal:
    50  					result[NewScopedFqdn(hostsNamespaceScope, r.Metadata.FullName.Namespace, h)] = s
    51  				default:
    52  					result[NewScopedFqdn(ns, r.Metadata.FullName.Namespace, h)] = s
    53  				}
    54  
    55  				// If exports to all, we don't need to check other namespaces
    56  				if exportsToAll {
    57  					break
    58  				}
    59  			}
    60  		}
    61  		return true
    62  	})
    63  
    64  	// converts k8s service to serviceEntry since destinationHost
    65  	// validation is performed against serviceEntry
    66  	ctx.ForEach(gvk.Service, func(r *resource.Instance) bool {
    67  		s := r.Message.(*corev1.ServiceSpec)
    68  		var se *v1alpha3.ServiceEntry
    69  		var ports []*v1alpha3.ServicePort
    70  		for _, p := range s.Ports {
    71  			ports = append(ports, &v1alpha3.ServicePort{
    72  				Number:   uint32(p.Port),
    73  				Name:     p.Name,
    74  				Protocol: string(p.Protocol),
    75  			})
    76  		}
    77  		host := ConvertHostToFQDN(r.Metadata.FullName.Namespace, r.Metadata.FullName.Name.String())
    78  		se = &v1alpha3.ServiceEntry{
    79  			Hosts: []string{host},
    80  			Ports: ports,
    81  		}
    82  		visibleNamespaces := getVisibleNamespacesFromExportToAnno(
    83  			r.Metadata.Annotations[annotation.NetworkingExportTo.Name], r.Metadata.FullName.Namespace.String())
    84  		for _, scope := range visibleNamespaces {
    85  			result[NewScopedFqdn(scope, r.Metadata.FullName.Namespace, r.Metadata.FullName.Name.String())] = se
    86  		}
    87  		return true
    88  	})
    89  	return result
    90  }
    91  
    92  func getVisibleNamespacesFromExportToAnno(anno, resourceNamespace string) []string {
    93  	scopes := make([]string, 0)
    94  	if anno == "" {
    95  		scopes = append(scopes, ExportToAllNamespaces)
    96  	} else {
    97  		for _, ns := range strings.Split(anno, ",") {
    98  			if ns == ExportToNamespaceLocal {
    99  				scopes = append(scopes, resourceNamespace)
   100  			} else {
   101  				scopes = append(scopes, ns)
   102  			}
   103  		}
   104  	}
   105  	return scopes
   106  }
   107  
   108  func GetDestinationHost(sourceNs resource.Namespace, exportTo []string, host string,
   109  	serviceEntryHosts map[ScopedFqdn]*v1alpha3.ServiceEntry,
   110  ) *v1alpha3.ServiceEntry {
   111  	// Check explicitly defined ServiceEntries as well as services discovered from the platform
   112  
   113  	// Check ServiceEntries which are exposed to all namespaces
   114  	allNsScopedFqdn := NewScopedFqdn(ExportToAllNamespaces, sourceNs, host)
   115  	if s, ok := serviceEntryHosts[allNsScopedFqdn]; ok {
   116  		return s
   117  	}
   118  
   119  	// ServiceEntries can be either namespace scoped or exposed to different/all namespaces
   120  	if len(exportTo) == 0 {
   121  		nsScopedFqdn := NewScopedFqdn(string(sourceNs), sourceNs, host)
   122  		if s, ok := serviceEntryHosts[nsScopedFqdn]; ok {
   123  			return s
   124  		}
   125  	} else {
   126  		for _, e := range exportTo {
   127  			if e == ExportToNamespaceLocal {
   128  				e = sourceNs.String()
   129  			}
   130  			nsScopedFqdn := NewScopedFqdn(e, sourceNs, host)
   131  			if s, ok := serviceEntryHosts[nsScopedFqdn]; ok {
   132  				return s
   133  			}
   134  		}
   135  	}
   136  
   137  	// Now check wildcard matches, namespace scoped or all namespaces
   138  	// (This more expensive checking left for last)
   139  	// Assumes the wildcard entries are correctly formatted ("*<dns suffix>")
   140  	for seHostScopedFqdn, s := range serviceEntryHosts {
   141  		scope, seHost := seHostScopedFqdn.GetScopeAndFqdn()
   142  
   143  		// Skip over non-wildcard entries
   144  		if !strings.HasPrefix(seHost, Wildcard) {
   145  			continue
   146  		}
   147  
   148  		// Skip over entries not visible to the current virtual service namespace
   149  		if scope != ExportToAllNamespaces && scope != string(sourceNs) {
   150  			continue
   151  		}
   152  
   153  		seHostWithoutWildcard := strings.TrimPrefix(seHost, Wildcard)
   154  		hostWithoutWildCard := strings.TrimPrefix(host, Wildcard)
   155  
   156  		if strings.HasSuffix(hostWithoutWildCard, seHostWithoutWildcard) {
   157  			return s
   158  		}
   159  	}
   160  
   161  	return nil
   162  }