istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/validation/agent/validation.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 agent
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"net"
    21  	"net/netip"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/hashicorp/go-multierror"
    27  	"google.golang.org/protobuf/types/known/durationpb"
    28  
    29  	meshconfig "istio.io/api/mesh/v1alpha1"
    30  	networking "istio.io/api/networking/v1alpha3"
    31  	"istio.io/istio/pilot/pkg/features"
    32  	"istio.io/istio/pilot/pkg/serviceregistry/util/label"
    33  	"istio.io/istio/pkg/config/labels"
    34  	"istio.io/istio/pkg/config/security"
    35  	"istio.io/istio/pkg/log"
    36  	netutil "istio.io/istio/pkg/util/net"
    37  	"istio.io/istio/pkg/util/sets"
    38  )
    39  
    40  // Constants for duration fields
    41  const (
    42  	// Set some high upper bound to avoid weird configurations
    43  	// nolint: revive
    44  	connectTimeoutMax = time.Hour
    45  	// nolint: revive
    46  	connectTimeoutMin = time.Millisecond
    47  )
    48  
    49  var scope = log.RegisterScope("validation", "CRD validation debugging")
    50  
    51  type Warning error
    52  
    53  // Validation holds errors and warnings. They can be joined with additional errors by called AppendValidation
    54  type Validation struct {
    55  	Err     error
    56  	Warning Warning
    57  }
    58  
    59  func (v Validation) Unwrap() (Warning, error) {
    60  	return v.Warning, v.Err
    61  }
    62  
    63  func (v Validation) Error() string {
    64  	if v.Err == nil {
    65  		return ""
    66  	}
    67  	return v.Err.Error()
    68  }
    69  
    70  // WrapWarning turns an error into a Validation as a warning
    71  func WrapWarning(e error) Validation {
    72  	return Validation{Warning: e}
    73  }
    74  
    75  // wrapper around multierror.Append that enforces the invariant that if all input errors are nil, the output
    76  // error is nil (allowing validation without branching).
    77  func AppendValidation(v Validation, vs ...error) Validation {
    78  	appendError := func(err, err2 error) error {
    79  		if err == nil {
    80  			return err2
    81  		} else if err2 == nil {
    82  			return err
    83  		}
    84  		return multierror.Append(err, err2)
    85  	}
    86  
    87  	for _, nv := range vs {
    88  		switch t := nv.(type) {
    89  		case Validation:
    90  			v.Err = appendError(v.Err, t.Err)
    91  			v.Warning = appendError(v.Warning, t.Warning)
    92  		default:
    93  			v.Err = appendError(v.Err, t)
    94  		}
    95  	}
    96  	return v
    97  }
    98  
    99  // AppendWarningf appends a formatted warning string
   100  // nolint: unparam
   101  func AppendWarningf(v Validation, format string, a ...any) Validation {
   102  	return AppendValidation(v, Warningf(format, a...))
   103  }
   104  
   105  // Warningf formats according to a format specifier and returns the string as a
   106  // value that satisfies error. Like Errorf, but for warnings.
   107  func Warningf(format string, a ...any) Validation {
   108  	return WrapWarning(fmt.Errorf(format, a...))
   109  }
   110  
   111  // wrapper around multierror.Append that enforces the invariant that if all input errors are nil, the output
   112  // error is nil (allowing validation without branching).
   113  func AppendErrors(err error, errs ...error) error {
   114  	appendError := func(err, err2 error) error {
   115  		if err == nil {
   116  			return err2
   117  		} else if err2 == nil {
   118  			return err
   119  		}
   120  		return multierror.Append(err, err2)
   121  	}
   122  
   123  	for _, err2 := range errs {
   124  		switch t := err2.(type) {
   125  		case Validation:
   126  			err = appendError(err, t.Err)
   127  		default:
   128  			err = appendError(err, err2)
   129  		}
   130  	}
   131  	return err
   132  }
   133  
   134  // ValidateDuration checks that a proto duration is well-formed
   135  func ValidateDuration(pd *durationpb.Duration) error {
   136  	dur := pd.AsDuration()
   137  	if dur < time.Millisecond {
   138  		return errors.New("duration must be greater than 1ms")
   139  	}
   140  	if dur%time.Millisecond != 0 {
   141  		return errors.New("only durations to ms precision are supported")
   142  	}
   143  	return nil
   144  }
   145  
   146  // ValidateDurationRange verifies range is in specified duration
   147  func ValidateDurationRange(dur, min, max time.Duration) error {
   148  	if dur > max || dur < min {
   149  		return fmt.Errorf("time %v must be >%v and <%v", dur.String(), min.String(), max.String())
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  // ValidateDrainDuration checks that parent and drain durations are valid
   156  func ValidateDrainDuration(drainTime *durationpb.Duration) (errs error) {
   157  	if err := ValidateDuration(drainTime); err != nil {
   158  		errs = multierror.Append(errs, multierror.Prefix(err, "invalid drain duration:"))
   159  	}
   160  	if errs != nil {
   161  		return
   162  	}
   163  
   164  	drainDuration := drainTime.AsDuration()
   165  
   166  	if drainDuration%time.Second != 0 {
   167  		errs = multierror.Append(errs,
   168  			errors.New("drain time only supports durations to seconds precision"))
   169  	}
   170  	return
   171  }
   172  
   173  // ValidatePort checks that the network port is in range
   174  func ValidatePort(port int) error {
   175  	if 1 <= port && port <= 65535 {
   176  		return nil
   177  	}
   178  	return fmt.Errorf("port number %d must be in the range 1..65535", port)
   179  }
   180  
   181  // encapsulates DNS 1123 checks common to both wildcarded hosts and FQDNs
   182  func CheckDNS1123Preconditions(name string) error {
   183  	if len(name) > 255 {
   184  		return fmt.Errorf("domain name %q too long (max 255)", name)
   185  	}
   186  	if len(name) == 0 {
   187  		return fmt.Errorf("empty domain name not allowed")
   188  	}
   189  	return nil
   190  }
   191  
   192  func ValidateDNS1123Labels(domain string) error {
   193  	parts := strings.Split(domain, ".")
   194  	topLevelDomain := parts[len(parts)-1]
   195  	if _, err := strconv.Atoi(topLevelDomain); err == nil {
   196  		return fmt.Errorf("domain name %q invalid (top level domain %q cannot be all-numeric)", domain, topLevelDomain)
   197  	}
   198  	for i, label := range parts {
   199  		// Allow the last part to be empty, for unambiguous names like `istio.io.`
   200  		if i == len(parts)-1 && label == "" {
   201  			return nil
   202  		}
   203  		if !labels.IsDNS1123Label(label) {
   204  			return fmt.Errorf("domain name %q invalid (label %q invalid)", domain, label)
   205  		}
   206  	}
   207  	return nil
   208  }
   209  
   210  // validateCustomTags validates that tracing CustomTags map does not contain any nil items
   211  func validateCustomTags(tags map[string]*meshconfig.Tracing_CustomTag) error {
   212  	for tagName, tagVal := range tags {
   213  		if tagVal == nil {
   214  			return fmt.Errorf("encountered nil value for custom tag: %s", tagName)
   215  		}
   216  	}
   217  	return nil
   218  }
   219  
   220  // ValidateFQDN checks a fully-qualified domain name
   221  func ValidateFQDN(fqdn string) error {
   222  	if err := CheckDNS1123Preconditions(fqdn); err != nil {
   223  		return err
   224  	}
   225  	return ValidateDNS1123Labels(fqdn)
   226  }
   227  
   228  // ValidateProxyAddress checks that a network address is well-formed
   229  func ValidateProxyAddress(hostAddr string) error {
   230  	hostname, p, err := net.SplitHostPort(hostAddr)
   231  	if err != nil {
   232  		return fmt.Errorf("unable to split %q: %v", hostAddr, err)
   233  	}
   234  	port, err := strconv.Atoi(p)
   235  	if err != nil {
   236  		return fmt.Errorf("port (%s) is not a number: %v", p, err)
   237  	}
   238  	if err = ValidatePort(port); err != nil {
   239  		return err
   240  	}
   241  	if err = ValidateFQDN(hostname); err != nil {
   242  		if !netutil.IsValidIPAddress(hostname) {
   243  			return fmt.Errorf("%q is not a valid hostname or an IP address", hostname)
   244  		}
   245  	}
   246  
   247  	return nil
   248  }
   249  
   250  // ValidateLightstepCollector validates the configuration for sending envoy spans to LightStep
   251  func ValidateLightstepCollector(ls *meshconfig.Tracing_Lightstep) error {
   252  	var errs error
   253  	if ls.GetAddress() == "" {
   254  		errs = multierror.Append(errs, errors.New("address is required"))
   255  	}
   256  	if err := ValidateProxyAddress(ls.GetAddress()); err != nil {
   257  		errs = multierror.Append(errs, multierror.Prefix(err, "invalid lightstep address:"))
   258  	}
   259  	if ls.GetAccessToken() == "" {
   260  		errs = multierror.Append(errs, errors.New("access token is required"))
   261  	}
   262  	return errs
   263  }
   264  
   265  // ValidateZipkinCollector validates the configuration for sending envoy spans to Zipkin
   266  func ValidateZipkinCollector(z *meshconfig.Tracing_Zipkin) error {
   267  	return ValidateProxyAddress(strings.Replace(z.GetAddress(), "$(HOST_IP)", "127.0.0.1", 1))
   268  }
   269  
   270  // ValidateDatadogCollector validates the configuration for sending envoy spans to Datadog
   271  func ValidateDatadogCollector(d *meshconfig.Tracing_Datadog) error {
   272  	// If the address contains $(HOST_IP), replace it with a valid IP before validation.
   273  	return ValidateProxyAddress(strings.Replace(d.GetAddress(), "$(HOST_IP)", "127.0.0.1", 1))
   274  }
   275  
   276  func ValidateTLS(settings *networking.ClientTLSSettings) (errs error) {
   277  	if settings == nil {
   278  		return
   279  	}
   280  
   281  	if settings.GetInsecureSkipVerify().GetValue() {
   282  		if settings.Mode == networking.ClientTLSSettings_SIMPLE {
   283  			// In tls simple mode, we can specify ca cert by CaCertificates or CredentialName.
   284  			if settings.CaCertificates != "" || settings.CredentialName != "" || settings.SubjectAltNames != nil {
   285  				errs = AppendErrors(errs, fmt.Errorf("cannot specify CaCertificates or CredentialName or SubjectAltNames when InsecureSkipVerify is set true"))
   286  			}
   287  		}
   288  
   289  		if settings.Mode == networking.ClientTLSSettings_MUTUAL {
   290  			// In tls mutual mode, we can specify both client cert and ca cert by CredentialName.
   291  			// However, here we can not distinguish whether user specify ca cert by CredentialName or not.
   292  			if settings.CaCertificates != "" || settings.SubjectAltNames != nil {
   293  				errs = AppendErrors(errs, fmt.Errorf("cannot specify CaCertificates or SubjectAltNames when InsecureSkipVerify is set true"))
   294  			}
   295  		}
   296  	}
   297  
   298  	if (settings.Mode == networking.ClientTLSSettings_SIMPLE || settings.Mode == networking.ClientTLSSettings_MUTUAL) &&
   299  		settings.CredentialName != "" {
   300  		if settings.ClientCertificate != "" || settings.CaCertificates != "" || settings.PrivateKey != "" || settings.CaCrl != "" {
   301  			errs = AppendErrors(errs,
   302  				fmt.Errorf("cannot specify client certificates or CA certificate or CA CRL If credentialName is set"))
   303  		}
   304  
   305  		// If tls mode is SIMPLE or MUTUAL, and CredentialName is specified, credentials are fetched
   306  		// remotely. ServerCertificate and CaCertificates fields are not required.
   307  		return
   308  	}
   309  
   310  	if settings.Mode == networking.ClientTLSSettings_MUTUAL {
   311  		if settings.ClientCertificate == "" {
   312  			errs = AppendErrors(errs, fmt.Errorf("client certificate required for mutual tls"))
   313  		}
   314  		if settings.PrivateKey == "" {
   315  			errs = AppendErrors(errs, fmt.Errorf("private key required for mutual tls"))
   316  		}
   317  	}
   318  
   319  	return
   320  }
   321  
   322  // ValidateMeshConfigProxyConfig checks that the mesh config is well-formed
   323  func ValidateMeshConfigProxyConfig(config *meshconfig.ProxyConfig) (errs error) {
   324  	if config.ConfigPath == "" {
   325  		errs = multierror.Append(errs, errors.New("config path must be set"))
   326  	}
   327  
   328  	if config.BinaryPath == "" {
   329  		errs = multierror.Append(errs, errors.New("binary path must be set"))
   330  	}
   331  
   332  	clusterName := config.GetClusterName()
   333  	switch naming := clusterName.(type) {
   334  	case *meshconfig.ProxyConfig_ServiceCluster:
   335  		if naming.ServiceCluster == "" {
   336  			errs = multierror.Append(errs, errors.New("service cluster must be specified"))
   337  		}
   338  	case *meshconfig.ProxyConfig_TracingServiceName_: // intentionally left empty for now
   339  	default:
   340  		errs = multierror.Append(errs, errors.New("oneof service cluster or tracing service name must be specified"))
   341  	}
   342  
   343  	if err := ValidateDrainDuration(config.DrainDuration); err != nil {
   344  		errs = multierror.Append(errs, err)
   345  	}
   346  
   347  	// discovery address is mandatory since mutual TLS relies on CDS.
   348  	// strictly speaking, proxies can operate without RDS/CDS and with hot restarts
   349  	// but that requires additional test validation
   350  	if config.DiscoveryAddress == "" {
   351  		errs = multierror.Append(errs, errors.New("discovery address must be set to the proxy discovery service"))
   352  	} else if err := ValidateProxyAddress(config.DiscoveryAddress); err != nil {
   353  		errs = multierror.Append(errs, multierror.Prefix(err, "invalid discovery address:"))
   354  	}
   355  
   356  	if tracer := config.GetTracing().GetLightstep(); tracer != nil {
   357  		if err := ValidateLightstepCollector(tracer); err != nil {
   358  			errs = multierror.Append(errs, multierror.Prefix(err, "invalid lightstep config:"))
   359  		}
   360  	}
   361  
   362  	if tracer := config.GetTracing().GetZipkin(); tracer != nil {
   363  		if err := ValidateZipkinCollector(tracer); err != nil {
   364  			errs = multierror.Append(errs, multierror.Prefix(err, "invalid zipkin config:"))
   365  		}
   366  	}
   367  
   368  	if tracer := config.GetTracing().GetDatadog(); tracer != nil {
   369  		if err := ValidateDatadogCollector(tracer); err != nil {
   370  			errs = multierror.Append(errs, multierror.Prefix(err, "invalid datadog config:"))
   371  		}
   372  	}
   373  
   374  	if tracer := config.GetTracing().GetTlsSettings(); tracer != nil {
   375  		if err := ValidateTLS(tracer); err != nil {
   376  			errs = multierror.Append(errs, multierror.Prefix(err, "invalid tracing TLS config:"))
   377  		}
   378  	}
   379  
   380  	if tracerCustomTags := config.GetTracing().GetCustomTags(); tracerCustomTags != nil {
   381  		if err := validateCustomTags(tracerCustomTags); err != nil {
   382  			errs = multierror.Append(errs, multierror.Prefix(err, "invalid tracing custom tags:"))
   383  		}
   384  	}
   385  
   386  	if config.StatsdUdpAddress != "" {
   387  		if err := ValidateProxyAddress(config.StatsdUdpAddress); err != nil {
   388  			errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("invalid statsd udp address %q:", config.StatsdUdpAddress)))
   389  		}
   390  	}
   391  
   392  	// nolint: staticcheck
   393  	if config.EnvoyMetricsServiceAddress != "" {
   394  		if err := ValidateProxyAddress(config.EnvoyMetricsServiceAddress); err != nil {
   395  			errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("invalid envoy metrics service address %q:", config.EnvoyMetricsServiceAddress)))
   396  		} else {
   397  			scope.Warnf("EnvoyMetricsServiceAddress is deprecated, use EnvoyMetricsService instead.") // nolint: stylecheck
   398  		}
   399  	}
   400  
   401  	if config.EnvoyMetricsService != nil && config.EnvoyMetricsService.Address != "" {
   402  		if err := ValidateProxyAddress(config.EnvoyMetricsService.Address); err != nil {
   403  			errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("invalid envoy metrics service address %q:", config.EnvoyMetricsService.Address)))
   404  		}
   405  	}
   406  
   407  	if config.EnvoyAccessLogService != nil && config.EnvoyAccessLogService.Address != "" {
   408  		if err := ValidateProxyAddress(config.EnvoyAccessLogService.Address); err != nil {
   409  			errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("invalid envoy access log service address %q:", config.EnvoyAccessLogService.Address)))
   410  		}
   411  	}
   412  
   413  	if err := ValidatePort(int(config.ProxyAdminPort)); err != nil {
   414  		errs = multierror.Append(errs, multierror.Prefix(err, "invalid proxy admin port:"))
   415  	}
   416  
   417  	if err := ValidateControlPlaneAuthPolicy(config.ControlPlaneAuthPolicy); err != nil {
   418  		errs = multierror.Append(errs, multierror.Prefix(err, "invalid authentication policy:"))
   419  	}
   420  
   421  	if err := ValidatePort(int(config.StatusPort)); err != nil {
   422  		errs = multierror.Append(errs, multierror.Prefix(err, "invalid status port:"))
   423  	}
   424  
   425  	if pkpConf := config.GetPrivateKeyProvider(); pkpConf != nil {
   426  		if err := validatePrivateKeyProvider(pkpConf); err != nil {
   427  			errs = multierror.Append(errs, multierror.Prefix(err, "invalid private key provider configuration:"))
   428  		}
   429  	}
   430  
   431  	return
   432  }
   433  
   434  func ValidateControlPlaneAuthPolicy(policy meshconfig.AuthenticationPolicy) error {
   435  	if policy == meshconfig.AuthenticationPolicy_NONE || policy == meshconfig.AuthenticationPolicy_MUTUAL_TLS {
   436  		return nil
   437  	}
   438  	return fmt.Errorf("unrecognized control plane auth policy %q", policy)
   439  }
   440  
   441  func validatePrivateKeyProvider(pkpConf *meshconfig.PrivateKeyProvider) error {
   442  	var errs error
   443  	if pkpConf.GetProvider() == nil {
   444  		errs = multierror.Append(errs, errors.New("private key provider configuration is required"))
   445  	}
   446  
   447  	switch pkpConf.GetProvider().(type) {
   448  	case *meshconfig.PrivateKeyProvider_Cryptomb:
   449  		cryptomb := pkpConf.GetCryptomb()
   450  		if cryptomb == nil {
   451  			errs = multierror.Append(errs, errors.New("cryptomb configuration is required"))
   452  		} else {
   453  			pollDelay := cryptomb.GetPollDelay()
   454  			if pollDelay == nil {
   455  				errs = multierror.Append(errs, errors.New("pollDelay is required"))
   456  			} else if pollDelay.GetSeconds() == 0 && pollDelay.GetNanos() == 0 {
   457  				errs = multierror.Append(errs, errors.New("pollDelay must be non zero"))
   458  			}
   459  		}
   460  	case *meshconfig.PrivateKeyProvider_Qat:
   461  		qatConf := pkpConf.GetQat()
   462  		if qatConf == nil {
   463  			errs = multierror.Append(errs, errors.New("qat configuration is required"))
   464  		} else {
   465  			pollDelay := qatConf.GetPollDelay()
   466  			if pollDelay == nil {
   467  				errs = multierror.Append(errs, errors.New("pollDelay is required"))
   468  			} else if pollDelay.GetSeconds() == 0 && pollDelay.GetNanos() == 0 {
   469  				errs = multierror.Append(errs, errors.New("pollDelay must be non zero"))
   470  			}
   471  		}
   472  	default:
   473  		errs = multierror.Append(errs, errors.New("unknown private key provider"))
   474  	}
   475  
   476  	return errs
   477  }
   478  
   479  // ValidateConnectTimeout validates the envoy connection timeout
   480  func ValidateConnectTimeout(timeout *durationpb.Duration) error {
   481  	if err := ValidateDuration(timeout); err != nil {
   482  		return err
   483  	}
   484  
   485  	err := ValidateDurationRange(timeout.AsDuration(), connectTimeoutMin, connectTimeoutMax)
   486  	return err
   487  }
   488  
   489  // ValidateProtocolDetectionTimeout validates the envoy protocol detection timeout
   490  func ValidateProtocolDetectionTimeout(timeout *durationpb.Duration) error {
   491  	dur := timeout.AsDuration()
   492  	// 0s is a valid value if trying to disable protocol detection timeout
   493  	if dur == time.Second*0 {
   494  		return nil
   495  	}
   496  	if dur%time.Millisecond != 0 {
   497  		return errors.New("only durations to ms precision are supported")
   498  	}
   499  
   500  	return nil
   501  }
   502  
   503  // ValidateLocalityLbSetting checks the LocalityLbSetting of MeshConfig
   504  func ValidateLocalityLbSetting(lb *networking.LocalityLoadBalancerSetting, outlier *networking.OutlierDetection) (errs Validation) {
   505  	if lb == nil {
   506  		return
   507  	}
   508  
   509  	if len(lb.GetDistribute()) > 0 && len(lb.GetFailover()) > 0 {
   510  		errs = AppendValidation(errs, fmt.Errorf("can not simultaneously specify 'distribute' and 'failover'"))
   511  		return
   512  	}
   513  
   514  	if len(lb.GetFailover()) > 0 && len(lb.GetFailoverPriority()) > 0 {
   515  		for _, priorityLabel := range lb.GetFailoverPriority() {
   516  			switch priorityLabel {
   517  			case label.LabelTopologyRegion, label.LabelTopologyZone, label.LabelTopologySubzone:
   518  				errs = AppendValidation(errs, fmt.Errorf("can not simultaneously set 'failover' and topology label '%s' in 'failover_priority'", priorityLabel))
   519  				return
   520  			}
   521  		}
   522  	}
   523  
   524  	srcLocalities := make([]string, 0, len(lb.GetDistribute()))
   525  	for _, locality := range lb.GetDistribute() {
   526  		srcLocalities = append(srcLocalities, locality.From)
   527  		var totalWeight uint32
   528  		destLocalities := make([]string, 0)
   529  		for loc, weight := range locality.To {
   530  			destLocalities = append(destLocalities, loc)
   531  			if weight <= 0 || weight > 100 {
   532  				errs = AppendValidation(errs, fmt.Errorf("locality weight must be in range [1, 100]"))
   533  				return
   534  			}
   535  			totalWeight += weight
   536  		}
   537  		if totalWeight != 100 {
   538  			errs = AppendValidation(errs, fmt.Errorf("total locality weight %v != 100", totalWeight))
   539  			return
   540  		}
   541  		errs = AppendValidation(errs, validateLocalities(destLocalities))
   542  	}
   543  
   544  	errs = AppendValidation(errs, validateLocalities(srcLocalities))
   545  
   546  	if (len(lb.GetFailover()) != 0 || len(lb.GetFailoverPriority()) != 0) && outlier == nil {
   547  		errs = AppendValidation(errs, WrapWarning(fmt.Errorf("outlier detection policy must be provided for failover")))
   548  	}
   549  
   550  	for _, failover := range lb.GetFailover() {
   551  		if failover.From == failover.To {
   552  			errs = AppendValidation(errs, fmt.Errorf("locality lb failover settings must specify different regions"))
   553  		}
   554  		if strings.Contains(failover.From, "/") || strings.Contains(failover.To, "/") {
   555  			errs = AppendValidation(errs, fmt.Errorf("locality lb failover only specify region"))
   556  		}
   557  		if strings.Contains(failover.To, "*") || strings.Contains(failover.From, "*") {
   558  			errs = AppendValidation(errs, fmt.Errorf("locality lb failover region should not contain '*' wildcard"))
   559  		}
   560  	}
   561  
   562  	return
   563  }
   564  
   565  const (
   566  	regionIndex int = iota
   567  	zoneIndex
   568  	subZoneIndex
   569  )
   570  
   571  func validateLocalities(localities []string) error {
   572  	regionZoneSubZoneMap := map[string]map[string]map[string]bool{}
   573  	for _, locality := range localities {
   574  		if n := strings.Count(locality, "*"); n > 0 {
   575  			if n > 1 || !strings.HasSuffix(locality, "*") {
   576  				return fmt.Errorf("locality %s wildcard '*' number can not exceed 1 and must be in the end", locality)
   577  			}
   578  		}
   579  		if _, exist := regionZoneSubZoneMap["*"]; exist {
   580  			return fmt.Errorf("locality %s overlap with previous specified ones", locality)
   581  		}
   582  
   583  		region, zone, subZone, localityIndex, err := getLocalityParam(locality)
   584  		if err != nil {
   585  			return fmt.Errorf("locality %s must not contain empty region/zone/subzone info", locality)
   586  		}
   587  
   588  		switch localityIndex {
   589  		case regionIndex:
   590  			if _, exist := regionZoneSubZoneMap[region]; exist {
   591  				return fmt.Errorf("locality %s overlap with previous specified ones", locality)
   592  			}
   593  			regionZoneSubZoneMap[region] = map[string]map[string]bool{"*": {"*": true}}
   594  		case zoneIndex:
   595  			if _, exist := regionZoneSubZoneMap[region]; exist {
   596  				if _, exist := regionZoneSubZoneMap[region]["*"]; exist {
   597  					return fmt.Errorf("locality %s overlap with previous specified ones", locality)
   598  				}
   599  				if _, exist := regionZoneSubZoneMap[region][zone]; exist {
   600  					return fmt.Errorf("locality %s overlap with previous specified ones", locality)
   601  				}
   602  				regionZoneSubZoneMap[region][zone] = map[string]bool{"*": true}
   603  			} else {
   604  				regionZoneSubZoneMap[region] = map[string]map[string]bool{zone: {"*": true}}
   605  			}
   606  		case subZoneIndex:
   607  			if _, exist := regionZoneSubZoneMap[region]; exist {
   608  				if _, exist := regionZoneSubZoneMap[region]["*"]; exist {
   609  					return fmt.Errorf("locality %s overlap with previous specified ones", locality)
   610  				}
   611  				if _, exist := regionZoneSubZoneMap[region][zone]; exist {
   612  					if regionZoneSubZoneMap[region][zone]["*"] {
   613  						return fmt.Errorf("locality %s overlap with previous specified ones", locality)
   614  					}
   615  					if regionZoneSubZoneMap[region][zone][subZone] {
   616  						return fmt.Errorf("locality %s overlap with previous specified ones", locality)
   617  					}
   618  					regionZoneSubZoneMap[region][zone][subZone] = true
   619  				} else {
   620  					regionZoneSubZoneMap[region][zone] = map[string]bool{subZone: true}
   621  				}
   622  			} else {
   623  				regionZoneSubZoneMap[region] = map[string]map[string]bool{zone: {subZone: true}}
   624  			}
   625  		}
   626  	}
   627  
   628  	return nil
   629  }
   630  
   631  func getLocalityParam(locality string) (string, string, string, int, error) {
   632  	var region, zone, subZone string
   633  	items := strings.SplitN(locality, "/", 3)
   634  	for i, item := range items {
   635  		if item == "" {
   636  			return "", "", "", -1, errors.New("item is nil")
   637  		}
   638  		switch i {
   639  		case regionIndex:
   640  			region = items[i]
   641  		case zoneIndex:
   642  			zone = items[i]
   643  		case subZoneIndex:
   644  			subZone = items[i]
   645  		}
   646  	}
   647  	return region, zone, subZone, len(items) - 1, nil
   648  }
   649  
   650  func validateServiceSettings(config *meshconfig.MeshConfig) (errs error) {
   651  	for sIndex, s := range config.ServiceSettings {
   652  		for _, h := range s.Hosts {
   653  			if err := ValidateWildcardDomain(h); err != nil {
   654  				errs = multierror.Append(errs, fmt.Errorf("serviceSettings[%d], host `%s`: %v", sIndex, h, err))
   655  			}
   656  		}
   657  	}
   658  	return
   659  }
   660  
   661  // ValidateWildcardDomain checks that a domain is a valid FQDN, but also allows wildcard prefixes.
   662  func ValidateWildcardDomain(domain string) error {
   663  	if err := CheckDNS1123Preconditions(domain); err != nil {
   664  		return err
   665  	}
   666  	// We only allow wildcards in the first label; split off the first label (parts[0]) from the rest of the host (parts[1])
   667  	parts := strings.SplitN(domain, ".", 2)
   668  	if !labels.IsWildcardDNS1123Label(parts[0]) {
   669  		return fmt.Errorf("domain name %q invalid (label %q invalid)", domain, parts[0])
   670  	} else if len(parts) > 1 {
   671  		return ValidateDNS1123Labels(parts[1])
   672  	}
   673  	return nil
   674  }
   675  
   676  // validate the trust domain format
   677  func ValidateTrustDomain(domain string) error {
   678  	if len(domain) == 0 {
   679  		return fmt.Errorf("empty domain name not allowed")
   680  	}
   681  	parts := strings.Split(domain, ".")
   682  	for i, label := range parts {
   683  		// Allow the last part to be empty, for unambiguous names like `istio.io.`
   684  		if i == len(parts)-1 && label == "" {
   685  			return nil
   686  		}
   687  		if !labels.IsDNS1123Label(label) {
   688  			return fmt.Errorf("trust domain name %q invalid", domain)
   689  		}
   690  	}
   691  	return nil
   692  }
   693  
   694  func validateTrustDomainConfig(config *meshconfig.MeshConfig) (errs error) {
   695  	if err := ValidateTrustDomain(config.TrustDomain); err != nil {
   696  		errs = multierror.Append(errs, fmt.Errorf("trustDomain: %v", err))
   697  	}
   698  	for i, tda := range config.TrustDomainAliases {
   699  		if err := ValidateTrustDomain(tda); err != nil {
   700  			errs = multierror.Append(errs, fmt.Errorf("trustDomainAliases[%d], domain `%s` : %v", i, tda, err))
   701  		}
   702  	}
   703  	return
   704  }
   705  
   706  func ValidateMeshTLSConfig(mesh *meshconfig.MeshConfig) (errs error) {
   707  	if meshMTLS := mesh.MeshMTLS; meshMTLS != nil {
   708  		if meshMTLS.EcdhCurves != nil {
   709  			errs = multierror.Append(errs, errors.New("mesh TLS does not support ECDH curves configuration"))
   710  		}
   711  	}
   712  	return errs
   713  }
   714  
   715  func ValidateMeshTLSDefaults(mesh *meshconfig.MeshConfig) (v Validation) {
   716  	unrecognizedECDHCurves := sets.New[string]()
   717  	validECDHCurves := sets.New[string]()
   718  	duplicateECDHCurves := sets.New[string]()
   719  	if tlsDefaults := mesh.TlsDefaults; tlsDefaults != nil {
   720  		for _, cs := range tlsDefaults.EcdhCurves {
   721  			if !security.IsValidECDHCurve(cs) {
   722  				unrecognizedECDHCurves.Insert(cs)
   723  			} else if validECDHCurves.InsertContains(cs) {
   724  				duplicateECDHCurves.Insert(cs)
   725  			}
   726  		}
   727  	}
   728  
   729  	if len(unrecognizedECDHCurves) > 0 {
   730  		v = AppendWarningf(v, "detected unrecognized ECDH curves: %v", sets.SortedList(unrecognizedECDHCurves))
   731  	}
   732  
   733  	if len(duplicateECDHCurves) > 0 {
   734  		v = AppendWarningf(v, "detected duplicate ECDH curves: %v", sets.SortedList(duplicateECDHCurves))
   735  	}
   736  	return
   737  }
   738  
   739  // ValidateMeshConfig checks that the mesh config is well-formed
   740  func ValidateMeshConfig(mesh *meshconfig.MeshConfig) (Warning, error) {
   741  	v := Validation{}
   742  	if err := ValidatePort(int(mesh.ProxyListenPort)); err != nil {
   743  		v = AppendValidation(v, multierror.Prefix(err, "invalid proxy listen port:"))
   744  	}
   745  
   746  	if err := ValidateConnectTimeout(mesh.ConnectTimeout); err != nil {
   747  		v = AppendValidation(v, multierror.Prefix(err, "invalid connect timeout:"))
   748  	}
   749  
   750  	if err := ValidateProtocolDetectionTimeout(mesh.ProtocolDetectionTimeout); err != nil {
   751  		v = AppendValidation(v, multierror.Prefix(err, "invalid protocol detection timeout:"))
   752  	}
   753  
   754  	if mesh.DefaultConfig == nil {
   755  		v = AppendValidation(v, errors.New("missing default config"))
   756  	} else {
   757  		v = AppendValidation(v, ValidateMeshConfigProxyConfig(mesh.DefaultConfig))
   758  	}
   759  
   760  	v = AppendValidation(v, ValidateLocalityLbSetting(mesh.LocalityLbSetting, &networking.OutlierDetection{}))
   761  	v = AppendValidation(v, validateServiceSettings(mesh))
   762  	v = AppendValidation(v, validateTrustDomainConfig(mesh))
   763  
   764  	if err := validateExtensionProvider(mesh); err != nil {
   765  		scope.Warnf("found invalid extension provider (can be ignored if the given extension provider is not used): %v", err)
   766  	}
   767  
   768  	v = AppendValidation(v, ValidateMeshTLSConfig(mesh))
   769  
   770  	v = AppendValidation(v, ValidateMeshTLSDefaults(mesh))
   771  
   772  	return v.Unwrap()
   773  }
   774  
   775  // ValidateIPAddress validates that a string in "CIDR notation" or "Dot-decimal notation"
   776  func ValidateIPAddress(addr string) error {
   777  	if _, err := netip.ParseAddr(addr); err != nil {
   778  		return fmt.Errorf("%v is not a valid IP", addr)
   779  	}
   780  
   781  	return nil
   782  }
   783  
   784  func ValidatePartialWildCard(host string) error {
   785  	if strings.Contains(host, "*") && len(host) != 1 && !strings.HasPrefix(host, "*.") {
   786  		return fmt.Errorf("partial wildcard %q not allowed", host)
   787  	}
   788  	return nil
   789  }
   790  
   791  // validates that hostname in ns/<hostname> is a valid hostname according to
   792  // API specs
   793  func validateSidecarOrGatewayHostnamePart(hostname string, isGateway bool) (errs error) {
   794  	// short name hosts are not allowed
   795  	if hostname != "*" && !strings.Contains(hostname, ".") {
   796  		errs = AppendErrors(errs, fmt.Errorf("short names (non FQDN) are not allowed"))
   797  	}
   798  
   799  	if err := ValidateWildcardDomain(hostname); err != nil {
   800  		if !isGateway {
   801  			errs = AppendErrors(errs, err)
   802  		}
   803  
   804  		// Gateway allows IP as the host string, as well
   805  		if !netutil.IsValidIPAddress(hostname) {
   806  			errs = AppendErrors(errs, err)
   807  		}
   808  	}
   809  	// partial wildcard is not allowed
   810  	// More details please refer to:
   811  	// Gateway: https://istio.io/latest/docs/reference/config/networking/gateway/
   812  	// SideCar: https://istio.io/latest/docs/reference/config/networking/sidecar/#IstioEgressListener
   813  	errs = AppendErrors(errs, ValidatePartialWildCard(hostname))
   814  	return
   815  }
   816  
   817  func ValidateNamespaceSlashWildcardHostname(hostname string, isGateway bool, gatewaySemantics bool) (errs error) {
   818  	parts := strings.SplitN(hostname, "/", 2)
   819  	if len(parts) != 2 {
   820  		if isGateway {
   821  			// Old style host in the gateway
   822  			return validateSidecarOrGatewayHostnamePart(hostname, true)
   823  		}
   824  		errs = AppendErrors(errs, fmt.Errorf("host must be of form namespace/dnsName"))
   825  		return
   826  	}
   827  
   828  	if len(parts[0]) == 0 || len(parts[1]) == 0 {
   829  		errs = AppendErrors(errs, fmt.Errorf("config namespace and dnsName in host entry cannot be empty"))
   830  	}
   831  
   832  	if !isGateway {
   833  		// namespace can be * or . or ~ or a valid DNS label in sidecars
   834  		if parts[0] != "*" && parts[0] != "." && parts[0] != "~" {
   835  			if !labels.IsDNS1123Label(parts[0]) {
   836  				errs = AppendErrors(errs, fmt.Errorf("invalid namespace value %q in sidecar", parts[0]))
   837  			}
   838  		}
   839  	} else {
   840  		// namespace can be * or . or a valid DNS label in gateways
   841  		// namespace can be ~ in gateways converted from Gateway API when no routes match
   842  		if parts[0] != "*" && parts[0] != "." && (parts[0] != "~" || !gatewaySemantics) {
   843  			if !labels.IsDNS1123Label(parts[0]) {
   844  				errs = AppendErrors(errs, fmt.Errorf("invalid namespace value %q in gateway", parts[0]))
   845  			}
   846  		}
   847  	}
   848  	errs = AppendErrors(errs, validateSidecarOrGatewayHostnamePart(parts[1], isGateway))
   849  	return
   850  }
   851  
   852  // ValidateIPSubnet checks that a string is in "CIDR notation" or "Dot-decimal notation"
   853  func ValidateIPSubnet(subnet string) error {
   854  	// We expect a string in "CIDR notation" or "Dot-decimal notation"
   855  	// E.g., a.b.c.d/xx form or just a.b.c.d or 2001:1::1/64
   856  	if strings.Count(subnet, "/") == 1 {
   857  		// We expect a string in "CIDR notation", i.e. a.b.c.d/xx or 2001:1::1/64 form
   858  		if _, err := netip.ParsePrefix(subnet); err != nil {
   859  			return fmt.Errorf("%v is not a valid CIDR block", subnet)
   860  		}
   861  
   862  		return nil
   863  	}
   864  	return ValidateIPAddress(subnet)
   865  }
   866  
   867  func validateNetwork(network *meshconfig.Network) (errs error) {
   868  	for _, n := range network.Endpoints {
   869  		switch e := n.Ne.(type) {
   870  		case *meshconfig.Network_NetworkEndpoints_FromCidr:
   871  			if err := ValidateIPSubnet(e.FromCidr); err != nil {
   872  				errs = multierror.Append(errs, err)
   873  			}
   874  		case *meshconfig.Network_NetworkEndpoints_FromRegistry:
   875  			if ok := labels.IsDNS1123Label(e.FromRegistry); !ok {
   876  				errs = multierror.Append(errs, fmt.Errorf("invalid registry name: %v", e.FromRegistry))
   877  			}
   878  		}
   879  	}
   880  	for _, n := range network.Gateways {
   881  		switch g := n.Gw.(type) {
   882  		case *meshconfig.Network_IstioNetworkGateway_RegistryServiceName:
   883  			if err := ValidateFQDN(g.RegistryServiceName); err != nil {
   884  				errs = multierror.Append(errs, err)
   885  			}
   886  		case *meshconfig.Network_IstioNetworkGateway_Address:
   887  			if ipErr := ValidateIPAddress(g.Address); ipErr != nil {
   888  				if !features.ResolveHostnameGateways {
   889  					err := fmt.Errorf("%v (hostname is allowed if RESOLVE_HOSTNAME_GATEWAYS is enabled)", ipErr)
   890  					errs = multierror.Append(errs, err)
   891  				} else if fqdnErr := ValidateFQDN(g.Address); fqdnErr != nil {
   892  					errs = multierror.Append(fmt.Errorf("%v is not a valid IP address or DNS name", g.Address))
   893  				}
   894  			}
   895  		}
   896  		if err := ValidatePort(int(n.Port)); err != nil {
   897  			errs = multierror.Append(errs, err)
   898  		}
   899  	}
   900  	return
   901  }
   902  
   903  // ValidateMeshNetworks validates meshnetworks.
   904  func ValidateMeshNetworks(meshnetworks *meshconfig.MeshNetworks) (errs error) {
   905  	// TODO validate using the same gateway on multiple networks?
   906  	for name, network := range meshnetworks.Networks {
   907  		if err := validateNetwork(network); err != nil {
   908  			errs = multierror.Append(errs, multierror.Prefix(err, fmt.Sprintf("invalid network %v:", name)))
   909  		}
   910  	}
   911  	return
   912  }