istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/gateway/gateway.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 gateway
    16  
    17  import (
    18  	"fmt"
    19  
    20  	v1 "k8s.io/api/core/v1"
    21  	klabels "k8s.io/apimachinery/pkg/labels"
    22  
    23  	"istio.io/api/networking/v1alpha3"
    24  	"istio.io/istio/pkg/config"
    25  	"istio.io/istio/pkg/config/analysis"
    26  	"istio.io/istio/pkg/config/analysis/analyzers/util"
    27  	"istio.io/istio/pkg/config/analysis/msg"
    28  	"istio.io/istio/pkg/config/resource"
    29  	"istio.io/istio/pkg/config/schema/gvk"
    30  )
    31  
    32  // IngressGatewayPortAnalyzer checks a gateway's ports against the gateway's Kubernetes service ports.
    33  type IngressGatewayPortAnalyzer struct{}
    34  
    35  // (compile-time check that we implement the interface)
    36  var _ analysis.Analyzer = &IngressGatewayPortAnalyzer{}
    37  
    38  // Metadata implements analysis.Analyzer
    39  func (*IngressGatewayPortAnalyzer) Metadata() analysis.Metadata {
    40  	return analysis.Metadata{
    41  		Name:        "gateway.IngressGatewayPortAnalyzer",
    42  		Description: "Checks a gateway's ports against the gateway's Kubernetes service ports",
    43  		Inputs: []config.GroupVersionKind{
    44  			gvk.Gateway,
    45  			gvk.Pod,
    46  			gvk.Service,
    47  		},
    48  	}
    49  }
    50  
    51  // Analyze implements analysis.Analyzer
    52  func (s *IngressGatewayPortAnalyzer) Analyze(c analysis.Context) {
    53  	c.ForEach(gvk.Gateway, func(r *resource.Instance) bool {
    54  		s.analyzeGateway(r, c)
    55  		return true
    56  	})
    57  }
    58  
    59  func (*IngressGatewayPortAnalyzer) analyzeGateway(r *resource.Instance, c analysis.Context) {
    60  	gw := r.Message.(*v1alpha3.Gateway)
    61  
    62  	// Typically there will be a single istio-ingressgateway service, which will select
    63  	// the same ingress gateway pod workload as the Gateway resource.  If there are multiple
    64  	// Kubernetes services, and they offer different TCP port combinations, this validator will
    65  	// not report a problem if *any* selecting service exposes the Gateway's port.
    66  	servicePorts := map[uint32]bool{}
    67  	gwSelectorMatches := 0
    68  
    69  	// For pods selected by gw.Selector, find Services that select them and remember those ports
    70  	gwSelector := klabels.SelectorFromSet(gw.Selector)
    71  	c.ForEach(gvk.Pod, func(rPod *resource.Instance) bool {
    72  		podLabels := klabels.Set(rPod.Metadata.Labels)
    73  		if gwSelector.Matches(podLabels) {
    74  			gwSelectorMatches++
    75  			c.ForEach(gvk.Service, func(rSvc *resource.Instance) bool {
    76  				nsSvc := string(rSvc.Metadata.FullName.Namespace)
    77  				if nsSvc != rPod.Metadata.FullName.Namespace.String() {
    78  					return true // Services only select pods in their namespace
    79  				}
    80  
    81  				service := rSvc.Message.(*v1.ServiceSpec)
    82  				// TODO I want to match service.Namespace to pod.ObjectMeta.Namespace
    83  				svcSelector := klabels.SelectorFromSet(service.Selector)
    84  				if svcSelector.Matches(podLabels) {
    85  					for _, port := range service.Ports {
    86  						if port.Protocol == "TCP" {
    87  							// Because the Gateway's server port is the port on which the proxy should listen for incoming connections,
    88  							// the actual port associated with the service is the `TargetPort` that reaches the sidecar *workload instances*.
    89  							if tp := port.TargetPort.IntValue(); tp != 0 {
    90  								servicePorts[uint32(tp)] = true
    91  							} else {
    92  								servicePorts[uint32(port.Port)] = true
    93  							}
    94  						}
    95  					}
    96  				}
    97  				return true
    98  			})
    99  		}
   100  		return true
   101  	})
   102  
   103  	// Report if we found no pods matching this gateway's selector
   104  	if gwSelectorMatches == 0 {
   105  		m := msg.NewReferencedResourceNotFound(r, "selector", gwSelector.String())
   106  
   107  		label := util.ExtractLabelFromSelectorString(gwSelector.String())
   108  		if line, ok := util.ErrorLine(r, fmt.Sprintf(util.GatewaySelector, label)); ok {
   109  			m.Line = line
   110  		}
   111  
   112  		c.Report(gvk.Gateway, m)
   113  		return
   114  	}
   115  
   116  	// Check each Gateway port against what the workload ingress service offers
   117  	for _, server := range gw.Servers {
   118  		if server.Port != nil {
   119  			_, ok := servicePorts[server.Port.Number]
   120  			if !ok {
   121  				m := msg.NewGatewayPortNotDefinedOnService(r, int(server.Port.Number), gwSelector.String())
   122  
   123  				label := util.ExtractLabelFromSelectorString(gwSelector.String())
   124  				if line, ok := util.ErrorLine(r, fmt.Sprintf(util.GatewaySelector, label)); ok {
   125  					m.Line = line
   126  				}
   127  
   128  				c.Report(gvk.Gateway, m)
   129  			}
   130  		}
   131  	}
   132  }