istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/externalcontrolplane/externalcontrolplane.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 externalcontrolplane
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"net/url"
    21  	"strings"
    22  
    23  	v1 "k8s.io/api/admissionregistration/v1"
    24  
    25  	"istio.io/istio/pkg/config"
    26  	"istio.io/istio/pkg/config/analysis"
    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  type ExternalControlPlaneAnalyzer struct{}
    33  
    34  // Compile-time check that this Analyzer correctly implements the interface
    35  var _ analysis.Analyzer = &ExternalControlPlaneAnalyzer{}
    36  
    37  // Metadata implements Analyzer
    38  func (s *ExternalControlPlaneAnalyzer) Metadata() analysis.Metadata {
    39  	return analysis.Metadata{
    40  		Name:        "externalcontrolplane.ExternalControlPlaneAnalyzer",
    41  		Description: "Checks that the remote IstioOperator resources reference an external control plane",
    42  		Inputs: []config.GroupVersionKind{
    43  			gvk.ValidatingWebhookConfiguration,
    44  			gvk.MutatingWebhookConfiguration,
    45  		},
    46  	}
    47  }
    48  
    49  const (
    50  	defaultIstioValidatingWebhookName = "istiod-default-validator"
    51  	istioValidatingWebhookNamePrefix  = "istio-validator"
    52  	istioMutatingWebhookNamePrefix    = "istio-sidecar-injector"
    53  )
    54  
    55  // Analyze implements Analyzer
    56  func (s *ExternalControlPlaneAnalyzer) Analyze(c analysis.Context) {
    57  	reportWebhookURL := func(r *resource.Instance, hName string, clientConf v1.WebhookClientConfig) {
    58  		// If defined, it means that an external istiod has been adopted
    59  		if clientConf.URL != nil {
    60  			result, err := lintWebhookURL(*clientConf.URL)
    61  			if err != nil {
    62  				c.Report(gvk.ValidatingWebhookConfiguration, msg.NewInvalidExternalControlPlaneConfig(r, *clientConf.URL, hName, err.Error()))
    63  				return
    64  			}
    65  			if result.isIP() {
    66  				c.Report(gvk.ValidatingWebhookConfiguration, msg.NewExternalControlPlaneAddressIsNotAHostname(r, *clientConf.URL, hName))
    67  			}
    68  		} else if clientConf.Service == nil {
    69  			c.Report(gvk.ValidatingWebhookConfiguration, msg.NewInvalidExternalControlPlaneConfig(r, "", hName, "is blank"))
    70  		}
    71  	}
    72  
    73  	c.ForEach(gvk.ValidatingWebhookConfiguration, func(resource *resource.Instance) bool {
    74  		webhookConfig := resource.Message.(*v1.ValidatingWebhookConfiguration)
    75  
    76  		// 1. ValidatingWebhookConfiguration: istio-validator or istiod-default-validator(default)
    77  		//			istio-validator{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }}-{{ .Values.global.istioNamespace }}
    78  		if webhookConfig.GetName() != "" &&
    79  			(webhookConfig.Name == defaultIstioValidatingWebhookName ||
    80  				strings.HasPrefix(webhookConfig.Name, istioValidatingWebhookNamePrefix)) {
    81  			for _, hook := range webhookConfig.Webhooks {
    82  				reportWebhookURL(resource, hook.Name, hook.ClientConfig)
    83  			}
    84  		}
    85  
    86  		return true
    87  	})
    88  
    89  	c.ForEach(gvk.MutatingWebhookConfiguration, func(resource *resource.Instance) bool {
    90  		webhookConfig := resource.Message.(*v1.MutatingWebhookConfiguration)
    91  
    92  		// 2. MutatingWebhookConfiguration: istio-sidecar-injector
    93  		//            {{- if eq .Release.Namespace "istio-system"}}
    94  		//              name: istio-sidecar-injector{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }}
    95  		//            {{- else }}
    96  		//              name: istio-sidecar-injector{{- if not (eq .Values.revision "") }}-{{ .Values.revision }}{{- end }}-{{ .Release.Namespace }}
    97  		//            {{- end }}
    98  		if strings.HasPrefix(webhookConfig.Name, istioMutatingWebhookNamePrefix) {
    99  			for _, hook := range webhookConfig.Webhooks {
   100  				reportWebhookURL(resource, hook.Name, hook.ClientConfig)
   101  			}
   102  		}
   103  
   104  		return true
   105  	})
   106  }
   107  
   108  type webhookURLResult struct {
   109  	ip       net.IP
   110  	hostName string
   111  }
   112  
   113  func (r *webhookURLResult) isIP() bool {
   114  	if r == nil {
   115  		return false
   116  	}
   117  	return r.ip != nil
   118  }
   119  
   120  func lintWebhookURL(webhookURL string) (result *webhookURLResult, err error) {
   121  	result = &webhookURLResult{}
   122  	parsedWebhookURL, err := url.Parse(webhookURL)
   123  	if err != nil {
   124  		return result, fmt.Errorf("was provided in an invalid format")
   125  	}
   126  
   127  	parsedHostname := parsedWebhookURL.Hostname()
   128  	if ip := net.ParseIP(parsedHostname); ip != nil {
   129  		result.ip = ip
   130  		return result, nil
   131  	}
   132  
   133  	result.hostName = parsedHostname
   134  
   135  	return result, nil
   136  }