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 }