validation

package
v0.0.0-...-b33cfc3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 30, 2024 License: Apache-2.0 Imports: 41 Imported by: 1

Documentation

Index

Constants

View Source
const (
	IndependentRoute = iota
	RootRoute
	DelegateRoute
)
View Source
const (
	// UnixAddressPrefix is the prefix used to indicate an address is for a Unix Domain socket. It is used in
	// ServiceEntry.Endpoint.Address message.
	UnixAddressPrefix = "unix://"
)

Constants for duration fields

Variables

View Source
var (

	// EmptyValidate is a Validate that does nothing and returns no error.
	EmptyValidate = RegisterValidateFunc("EmptyValidate",
		func(config.Config) (Warning, error) {
			return nil, nil
		})
)
View Source
var ValidateAuthorizationPolicy = RegisterValidateFunc("ValidateAuthorizationPolicy",
	func(cfg config.Config) (Warning, error) {
		in, ok := cfg.Spec.(*security_beta.AuthorizationPolicy)
		if !ok {
			return nil, fmt.Errorf("cannot cast to AuthorizationPolicy")
		}

		var errs error
		var warnings Warning
		selectorTypeValidation := validateOneOfSelectorType(in.GetSelector(), in.GetTargetRef(), in.GetTargetRefs())
		workloadSelectorValidation := validateWorkloadSelector(in.GetSelector())
		targetRefValidation := validatePolicyTargetReference(in.GetTargetRef())
		targetRefsValidation := validatePolicyTargetReferences(in.GetTargetRefs())
		errs = appendErrors(errs, selectorTypeValidation, workloadSelectorValidation, targetRefValidation, targetRefsValidation)
		warnings = appendErrors(warnings, workloadSelectorValidation.Warning)

		if in.Action == security_beta.AuthorizationPolicy_CUSTOM {
			if in.Rules == nil {
				errs = appendErrors(errs, fmt.Errorf("CUSTOM action without `rules` is meaningless as it will never be triggered, "+
					"add an empty rule `{}` if you want it be triggered for every request"))
			} else {
				if in.GetProvider() == nil || in.GetProvider().GetName() == "" {
					errs = appendErrors(errs, fmt.Errorf("`provider.name` must not be empty"))
				}
			}

			for _, rule := range in.GetRules() {
				check := func(invalid bool, name string) error {
					if invalid {
						return fmt.Errorf("%s is currently not supported with CUSTOM action", name)
					}
					return nil
				}
				for _, from := range rule.GetFrom() {
					if src := from.GetSource(); src != nil {
						errs = appendErrors(errs, check(len(src.Namespaces) != 0, "From.Namespaces"))
						errs = appendErrors(errs, check(len(src.NotNamespaces) != 0, "From.NotNamespaces"))
						errs = appendErrors(errs, check(len(src.Principals) != 0, "From.Principals"))
						errs = appendErrors(errs, check(len(src.NotPrincipals) != 0, "From.NotPrincipals"))
						errs = appendErrors(errs, check(len(src.RequestPrincipals) != 0, "From.RequestPrincipals"))
						errs = appendErrors(errs, check(len(src.NotRequestPrincipals) != 0, "From.NotRequestPrincipals"))
					}
				}
				for _, when := range rule.GetWhen() {
					if when == nil {
						errs = appendErrors(errs, fmt.Errorf("when field cannot be nil"))
						continue
					}
					errs = appendErrors(errs, check(when.Key == "source.namespace", when.Key))
					errs = appendErrors(errs, check(when.Key == "source.principal", when.Key))
					errs = appendErrors(errs, check(strings.HasPrefix(when.Key, "request.auth."), when.Key))
				}
			}
		}
		if in.GetProvider() != nil && in.Action != security_beta.AuthorizationPolicy_CUSTOM {
			errs = appendErrors(errs, fmt.Errorf("`provider` must not be with non CUSTOM action, found %s", in.Action))
		}

		if in.Action == security_beta.AuthorizationPolicy_DENY && in.Rules == nil {
			errs = appendErrors(errs, fmt.Errorf("DENY action without `rules` is meaningless as it will never be triggered, "+
				"add an empty rule `{}` if you want it be triggered for every request"))
		}

		for i, rule := range in.GetRules() {
			if rule == nil {
				errs = appendErrors(errs, fmt.Errorf("`rule` must not be nil, found at rule %d", i))
				continue
			}
			if rule.From != nil && len(rule.From) == 0 {
				errs = appendErrors(errs, fmt.Errorf("`from` must not be empty, found at rule %d", i))
			}
			tcpRulesInFrom := false
			tcpRulesInTo := false
			fromRuleExist := false
			toRuleExist := false
			for _, from := range rule.From {
				if from == nil {
					errs = appendErrors(errs, fmt.Errorf("`from` must not be nil, found at rule %d", i))
					continue
				}
				if from.Source == nil {
					errs = appendErrors(errs, fmt.Errorf("`from.source` must not be nil, found at rule %d", i))
				} else {
					fromRuleExist = true
					src := from.Source
					if len(src.Principals) == 0 && len(src.RequestPrincipals) == 0 && len(src.Namespaces) == 0 && len(src.IpBlocks) == 0 &&
						len(src.RemoteIpBlocks) == 0 && len(src.NotPrincipals) == 0 && len(src.NotRequestPrincipals) == 0 && len(src.NotNamespaces) == 0 &&
						len(src.NotIpBlocks) == 0 && len(src.NotRemoteIpBlocks) == 0 {
						errs = appendErrors(errs, fmt.Errorf("`from.source` must not be empty, found at rule %d", i))
					}
					errs = appendErrors(errs, security.ValidateIPs(from.Source.GetIpBlocks()))
					errs = appendErrors(errs, security.ValidateIPs(from.Source.GetNotIpBlocks()))
					errs = appendErrors(errs, security.ValidateIPs(from.Source.GetRemoteIpBlocks()))
					errs = appendErrors(errs, security.ValidateIPs(from.Source.GetNotRemoteIpBlocks()))
					errs = appendErrors(errs, security.CheckEmptyValues("Principals", src.Principals))
					errs = appendErrors(errs, security.CheckEmptyValues("RequestPrincipals", src.RequestPrincipals))
					errs = appendErrors(errs, security.CheckEmptyValues("Namespaces", src.Namespaces))
					errs = appendErrors(errs, security.CheckEmptyValues("IpBlocks", src.IpBlocks))
					errs = appendErrors(errs, security.CheckEmptyValues("RemoteIpBlocks", src.RemoteIpBlocks))
					errs = appendErrors(errs, security.CheckEmptyValues("NotPrincipals", src.NotPrincipals))
					errs = appendErrors(errs, security.CheckEmptyValues("NotRequestPrincipals", src.NotRequestPrincipals))
					errs = appendErrors(errs, security.CheckEmptyValues("NotNamespaces", src.NotNamespaces))
					errs = appendErrors(errs, security.CheckEmptyValues("NotIpBlocks", src.NotIpBlocks))
					errs = appendErrors(errs, security.CheckEmptyValues("NotRemoteIpBlocks", src.NotRemoteIpBlocks))
					if src.NotPrincipals != nil || src.Principals != nil || src.IpBlocks != nil ||
						src.NotIpBlocks != nil || src.Namespaces != nil ||
						src.NotNamespaces != nil || src.RemoteIpBlocks != nil || src.NotRemoteIpBlocks != nil {
						tcpRulesInFrom = true
					}
				}
			}
			if rule.To != nil && len(rule.To) == 0 {
				errs = appendErrors(errs, fmt.Errorf("`to` must not be empty, found at rule %d", i))
			}
			for _, to := range rule.To {
				if to == nil {
					errs = appendErrors(errs, fmt.Errorf("`to` must not be nil, found at rule %d", i))
					continue
				}
				if to.Operation == nil {
					errs = appendErrors(errs, fmt.Errorf("`to.operation` must not be nil, found at rule %d", i))
				} else {
					toRuleExist = true
					op := to.Operation
					if len(op.Ports) == 0 && len(op.Methods) == 0 && len(op.Paths) == 0 && len(op.Hosts) == 0 &&
						len(op.NotPorts) == 0 && len(op.NotMethods) == 0 && len(op.NotPaths) == 0 && len(op.NotHosts) == 0 {
						errs = appendErrors(errs, fmt.Errorf("`to.operation` must not be empty, found at rule %d", i))
					}
					errs = appendErrors(errs, security.ValidatePorts(to.Operation.GetPorts()))
					errs = appendErrors(errs, security.ValidatePorts(to.Operation.GetNotPorts()))
					errs = appendErrors(errs, security.CheckEmptyValues("Ports", op.Ports))
					errs = appendErrors(errs, security.CheckEmptyValues("Methods", op.Methods))
					errs = appendErrors(errs, security.CheckEmptyValues("Paths", op.Paths))
					errs = appendErrors(errs, security.CheckEmptyValues("Hosts", op.Hosts))
					errs = appendErrors(errs, security.CheckEmptyValues("NotPorts", op.NotPorts))
					errs = appendErrors(errs, security.CheckEmptyValues("NotMethods", op.NotMethods))
					errs = appendErrors(errs, security.CheckEmptyValues("NotPaths", op.NotPaths))
					errs = appendErrors(errs, security.CheckEmptyValues("NotHosts", op.NotHosts))
					errs = appendErrors(errs, security.CheckValidPathTemplate("Paths", op.Paths))
					errs = appendErrors(errs, security.CheckValidPathTemplate("NotPaths", op.NotPaths))
					if op.Ports != nil || op.NotPorts != nil {
						tcpRulesInTo = true
					}
				}
			}
			for _, condition := range rule.GetWhen() {
				key := condition.GetKey()
				if key == "" {
					errs = appendErrors(errs, fmt.Errorf("`key` must not be empty"))
				} else {
					if len(condition.GetValues()) == 0 && len(condition.GetNotValues()) == 0 {
						errs = appendErrors(errs, fmt.Errorf("at least one of `values` or `notValues` must be set for key %s",
							key))
					} else {
						if err := security.ValidateAttribute(key, condition.GetValues()); err != nil {
							errs = appendErrors(errs, fmt.Errorf("invalid `value` for `key` %s: %v", key, err))
						}
						if err := security.ValidateAttribute(key, condition.GetNotValues()); err != nil {
							errs = appendErrors(errs, fmt.Errorf("invalid `notValue` for `key` %s: %v", key, err))
						}
					}
				}
			}

			if ((fromRuleExist && !toRuleExist && !tcpRulesInFrom) || (toRuleExist && !tcpRulesInTo)) &&
				in.Action == security_beta.AuthorizationPolicy_DENY {
				warning := fmt.Errorf("configured AuthorizationPolicy will deny all traffic " +
					"to TCP ports under its scope due to the use of only HTTP attributes in a DENY rule; " +
					"it is recommended to explicitly specify the port")
				warnings = appendErrors(warnings, warning)

			}
		}
		return warnings, multierror.Prefix(errs, fmt.Sprintf("invalid policy %s.%s:", cfg.Name, cfg.Namespace))
	})

ValidateAuthorizationPolicy checks that AuthorizationPolicy is well-formed.

View Source
var ValidateDestinationRule = RegisterValidateFunc("ValidateDestinationRule",
	func(cfg config.Config) (Warning, error) {
		rule, ok := cfg.Spec.(*networking.DestinationRule)
		if !ok {
			return nil, fmt.Errorf("cannot cast to destination rule")
		}
		v := Validation{}
		v = AppendValidation(v,
			agent.ValidateWildcardDomain(rule.Host),
			validateTrafficPolicy(rule.TrafficPolicy))

		subsets := sets.String{}
		for _, subset := range rule.Subsets {
			if subset == nil {
				v = AppendValidation(v, errors.New("subset may not be null"))
				continue
			}
			if subsets.InsertContains(subset.Name) {
				v = AppendValidation(v, fmt.Errorf("duplicate subset names: %s", subset.Name))
			}
			v = AppendValidation(v, validateSubset(subset))
		}
		v = AppendValidation(v,
			validateExportTo(cfg.Namespace, rule.ExportTo, false, rule.GetWorkloadSelector() != nil))

		v = AppendValidation(v, validateWorkloadSelector(rule.GetWorkloadSelector()))

		return v.Unwrap()
	})

ValidateDestinationRule checks proxy policies

View Source
var ValidateGateway = RegisterValidateFunc("ValidateGateway",
	func(cfg config.Config) (Warning, error) {
		name := cfg.Name

		gatewaySemantics := cfg.Annotations[constants.InternalGatewaySemantics] == constants.GatewaySemanticsGateway

		v := Validation{}

		if !labels.IsDNS1123Label(name) && !gatewaySemantics {
			v = AppendValidation(v, fmt.Errorf("invalid gateway name: %q", name))
		}
		value, ok := cfg.Spec.(*networking.Gateway)
		if !ok {
			v = AppendValidation(v, fmt.Errorf("cannot cast to gateway: %#v", cfg.Spec))
			return v.Unwrap()
		}

		if len(value.Servers) == 0 {
			v = AppendValidation(v, fmt.Errorf("gateway must have at least one server"))
		} else {
			for _, server := range value.Servers {
				v = AppendValidation(v, validateServer(server, gatewaySemantics))
			}
		}

		portNames := make(map[string]bool)

		for _, s := range value.Servers {
			if s == nil {
				v = AppendValidation(v, fmt.Errorf("server may not be nil"))
				continue
			}
			if s.Port != nil {
				if portNames[s.Port.Name] {
					v = AppendValidation(v, fmt.Errorf("port names in servers must be unique: duplicate name %s", s.Port.Name))
				}
				portNames[s.Port.Name] = true
				if !protocol.Parse(s.Port.Protocol).IsHTTP() && s.GetTls().GetHttpsRedirect() {
					v = AppendValidation(v, WrapWarning(fmt.Errorf("tls.httpsRedirect should only be used with http servers")))
				}
			}
		}

		return v.Unwrap()
	})

ValidateGateway checks gateway specifications

View Source
var ValidatePeerAuthentication = RegisterValidateFunc("ValidatePeerAuthentication",
	func(cfg config.Config) (Warning, error) {
		in, ok := cfg.Spec.(*security_beta.PeerAuthentication)
		if !ok {
			return nil, errors.New("cannot cast to PeerAuthentication")
		}

		var errs error
		emptySelector := in.Selector == nil || len(in.Selector.MatchLabels) == 0

		if emptySelector && len(in.PortLevelMtls) != 0 {
			errs = appendErrors(errs,
				fmt.Errorf("mesh/namespace peer authentication cannot have port level mTLS"))
		}

		if in.PortLevelMtls != nil && len(in.PortLevelMtls) == 0 {
			errs = appendErrors(errs,
				fmt.Errorf("port level mTLS, if defined, must have at least one element"))
		}

		for port := range in.PortLevelMtls {
			if port <= 0 || port > 65535 {
				errs = appendErrors(errs, fmt.Errorf("port must be in range 1..65535"))
			}
		}

		validation := validateWorkloadSelector(in.Selector)
		errs = appendErrors(errs, validation)

		return validation.Warning, errs
	})

ValidatePeerAuthentication checks that peer authentication spec is well-formed.

View Source
var ValidateProxyConfig = RegisterValidateFunc("ValidateProxyConfig",
	func(cfg config.Config) (Warning, error) {
		spec, ok := cfg.Spec.(*networkingv1beta1.ProxyConfig)
		if !ok {
			return nil, fmt.Errorf("cannot cast to proxyconfig")
		}

		errs := Validation{}

		errs = AppendValidation(errs,
			validateWorkloadSelector(spec.Selector),
			validateConcurrency(spec.Concurrency.GetValue()),
		)
		return errs.Unwrap()
	})

ValidateProxyConfig validates a ProxyConfig CR (as opposed to the MeshConfig field).

View Source
var ValidateRequestAuthentication = RegisterValidateFunc("ValidateRequestAuthentication",
	func(cfg config.Config) (Warning, error) {
		in, ok := cfg.Spec.(*security_beta.RequestAuthentication)
		if !ok {
			return nil, errors.New("cannot cast to RequestAuthentication")
		}

		errs := Validation{}
		errs = AppendValidation(errs,
			validateOneOfSelectorType(in.GetSelector(), in.GetTargetRef(), in.GetTargetRefs()),
			validateWorkloadSelector(in.GetSelector()),
			validatePolicyTargetReference(in.GetTargetRef()),
			validatePolicyTargetReferences(in.GetTargetRefs()),
		)

		for _, rule := range in.JwtRules {
			errs = AppendValidation(errs, validateJwtRule(rule))
		}
		return errs.Unwrap()
	})

ValidateRequestAuthentication checks that request authentication spec is well-formed.

View Source
var ValidateServiceEntry = RegisterValidateFunc("ValidateServiceEntry",
	func(cfg config.Config) (Warning, error) {
		serviceEntry, ok := cfg.Spec.(*networking.ServiceEntry)
		if !ok {
			return nil, fmt.Errorf("cannot cast to service entry")
		}

		errs := Validation{}

		warning, err := ValidateAlphaWorkloadSelector(serviceEntry.WorkloadSelector)
		if err != nil {
			return nil, err
		}

		if warning != nil {
			errs = AppendValidation(errs, WrapWarning(fmt.Errorf("service entry: %s, will be applied to all services in namespace",
				warning)))
		}

		if serviceEntry.WorkloadSelector != nil && serviceEntry.Endpoints != nil {
			errs = AppendValidation(errs, fmt.Errorf("only one of WorkloadSelector or Endpoints is allowed in Service Entry"))
		}

		if len(serviceEntry.Hosts) == 0 {
			errs = AppendValidation(errs, fmt.Errorf("service entry must have at least one host"))
		}
		for _, hostname := range serviceEntry.Hosts {

			if hostname == "*" {
				errs = AppendValidation(errs, fmt.Errorf("invalid host %s", hostname))
			} else {
				errs = AppendValidation(errs, agent.ValidateWildcardDomain(hostname))
				errs = AppendValidation(errs, WrapWarning(agent.ValidatePartialWildCard(hostname)))
			}
		}

		cidrFound := false
		for _, address := range serviceEntry.Addresses {
			cidrFound = cidrFound || strings.Contains(address, "/")
			errs = AppendValidation(errs, agent.ValidateIPSubnet(address))
		}

		if cidrFound {
			if serviceEntry.Resolution != networking.ServiceEntry_NONE && serviceEntry.Resolution != networking.ServiceEntry_STATIC {
				errs = AppendValidation(errs, fmt.Errorf("CIDR addresses are allowed only for NONE/STATIC resolution types"))
			}
		}

		autoAllocation := serviceentry.ShouldV2AutoAllocateIPFromConfig(cfg)
		servicePortNumbers := sets.New[uint32]()
		servicePorts := sets.NewWithLength[string](len(serviceEntry.Ports))
		for _, port := range serviceEntry.Ports {
			if port == nil {
				errs = AppendValidation(errs, fmt.Errorf("service entry port may not be null"))
				continue
			}
			if servicePorts.InsertContains(port.Name) {
				errs = AppendValidation(errs, fmt.Errorf("service entry port name %q already defined", port.Name))
			}
			if servicePortNumbers.InsertContains(port.Number) {
				errs = AppendValidation(errs, fmt.Errorf("service entry port %d already defined", port.Number))
			}
			if port.TargetPort != 0 {
				errs = AppendValidation(errs, agent.ValidatePort(int(port.TargetPort)))
			}
			if len(serviceEntry.Addresses) == 0 && !autoAllocation {
				if port.Protocol == "" || port.Protocol == "TCP" {
					errs = AppendValidation(errs, WrapWarning(fmt.Errorf("addresses are required for ports serving TCP (or unset) protocol "+
						"when ISTIO_META_DNS_AUTO_ALLOCATE is not set on a proxy")))
				}
			}
			errs = AppendValidation(errs,
				ValidatePortName(port.Name),
				ValidateProtocol(port.Protocol),
				agent.ValidatePort(int(port.Number)))
		}

		switch serviceEntry.Resolution {
		case networking.ServiceEntry_NONE:
			if len(serviceEntry.Endpoints) != 0 {
				errs = AppendValidation(errs, fmt.Errorf("no endpoints should be provided for resolution type none"))
			}
			if serviceEntry.WorkloadSelector != nil {
				errs = AppendWarningf(errs, "workloadSelector should not be set when resolution mode is NONE")
			}
		case networking.ServiceEntry_STATIC:
			for _, endpoint := range serviceEntry.Endpoints {
				if endpoint == nil {
					errs = AppendValidation(errs, errors.New("endpoint cannot be nil"))
					continue
				}
				errs = AppendValidation(errs, validateWorkloadEntry(endpoint, servicePorts, false))
			}
		case networking.ServiceEntry_DNS, networking.ServiceEntry_DNS_ROUND_ROBIN:
			if len(serviceEntry.Endpoints) == 0 {
				for _, hostname := range serviceEntry.Hosts {
					if err := agent.ValidateFQDN(hostname); err != nil {
						errs = AppendValidation(errs,
							fmt.Errorf("hosts must be FQDN if no endpoints are provided for resolution mode %s", serviceEntry.Resolution))
					}
				}
			}
			if serviceEntry.Resolution == networking.ServiceEntry_DNS_ROUND_ROBIN && len(serviceEntry.Endpoints) > 1 {
				errs = AppendValidation(errs,
					fmt.Errorf("there must only be 0 or 1 endpoint for resolution mode %s", serviceEntry.Resolution))
			}

			for _, endpoint := range serviceEntry.Endpoints {
				if endpoint == nil {
					errs = AppendValidation(errs, errors.New("endpoint cannot be nil"))
					continue
				}
				if !netutil.IsValidIPAddress(endpoint.Address) {
					if err := agent.ValidateFQDN(endpoint.Address); err != nil {
						errs = AppendValidation(errs,
							fmt.Errorf("endpoint address %q is not a valid FQDN or an IP address", endpoint.Address))
					}
				}
				errs = AppendValidation(errs,
					labels.Instance(endpoint.Labels).Validate())
				for name, port := range endpoint.Ports {
					if !servicePorts.Contains(name) {
						errs = AppendValidation(errs, fmt.Errorf("endpoint port %v is not defined by the service entry", port))
					}
					errs = AppendValidation(errs,
						ValidatePortName(name),
						agent.ValidatePort(int(port)))
				}
			}

			if serviceEntry.WorkloadSelector != nil {
				errs = AppendWarningf(errs, "workloadSelector should not be set when resolution mode is %v", serviceEntry.Resolution)
			}

			if len(serviceEntry.Addresses) > 0 {
				for _, port := range serviceEntry.Ports {
					if port == nil {
						errs = AppendValidation(errs, errors.New("ports cannot be nil"))
						continue
					}
					p := protocol.Parse(port.Protocol)
					if p.IsTCP() {
						if len(serviceEntry.Hosts) > 1 {

							errs = AppendValidation(errs, WrapWarning(fmt.Errorf("service entry can not have more than one host specified "+
								"simultaneously with address and tcp port")))
						}
						break
					}
				}
			}
		default:
			errs = AppendValidation(errs, fmt.Errorf("unsupported resolution type %s",
				networking.ServiceEntry_Resolution_name[int32(serviceEntry.Resolution)]))
		}

		if serviceEntry.Resolution != networking.ServiceEntry_NONE && len(serviceEntry.Hosts) > 1 {
			for _, port := range serviceEntry.Ports {
				if port == nil {
					errs = AppendValidation(errs, errors.New("ports cannot be nil"))
					continue
				}
				p := protocol.Parse(port.Protocol)
				if !p.IsHTTP() && !p.IsTLS() {
					errs = AppendValidation(errs, fmt.Errorf("multiple hosts provided with non-HTTP, non-TLS ports"))
					break
				}
			}
		}

		errs = AppendValidation(errs, validateExportTo(cfg.Namespace, serviceEntry.ExportTo, true, false))
		return errs.Unwrap()
	})

ValidateServiceEntry validates a service entry.

View Source
var ValidateSidecar = RegisterValidateFunc("ValidateSidecar",
	func(cfg config.Config) (Warning, error) {
		errs := Validation{}
		rule, ok := cfg.Spec.(*networking.Sidecar)
		if !ok {
			return nil, fmt.Errorf("cannot cast to Sidecar")
		}

		warning, err := ValidateAlphaWorkloadSelector(rule.WorkloadSelector)
		if err != nil {
			return nil, err
		}

		if warning != nil {
			errs = AppendValidation(errs, WrapWarning(fmt.Errorf("sidecar: %s, will be applied to all services in namespace",
				warning)))
		}

		if len(rule.Egress) == 0 && len(rule.Ingress) == 0 && rule.OutboundTrafficPolicy == nil && rule.InboundConnectionPool == nil {
			return nil, fmt.Errorf("sidecar: empty configuration provided")
		}

		portMap := sets.Set[uint32]{}
		for _, i := range rule.Ingress {
			if i == nil {
				errs = AppendValidation(errs, fmt.Errorf("sidecar: ingress may not be null"))
				continue
			}
			if i.Port == nil {
				errs = AppendValidation(errs, fmt.Errorf("sidecar: port is required for ingress listeners"))
				continue
			}

			if i.Port.TargetPort > 0 {
				errs = AppendValidation(errs, fmt.Errorf("targetPort has no impact on Sidecars"))
			}

			bind := i.GetBind()
			errs = AppendValidation(errs, validateSidecarIngressPortAndBind(i.Port, bind))

			if portMap.Contains(i.Port.Number) {
				errs = AppendValidation(errs, fmt.Errorf("sidecar: ports on IP bound listeners must be unique"))
			}
			portMap.Insert(i.Port.Number)

			if len(i.DefaultEndpoint) != 0 {
				if strings.HasPrefix(i.DefaultEndpoint, UnixAddressPrefix) {
					errs = AppendValidation(errs, ValidateUnixAddress(strings.TrimPrefix(i.DefaultEndpoint, UnixAddressPrefix)))
				} else {

					sHost, sPort, sErr := net.SplitHostPort(i.DefaultEndpoint)
					if sErr != nil {
						errs = AppendValidation(errs, sErr)
					}
					if sHost != "" && sHost != "127.0.0.1" && sHost != "0.0.0.0" && sHost != "::1" && sHost != "::" {
						errMsg := "sidecar: defaultEndpoint must be of form 127.0.0.1:<port>,0.0.0.0:<port>,[::1]:port,[::]:port,unix://filepath or unset"
						errs = AppendValidation(errs, errors.New(errMsg))
					}
					port, err := strconv.Atoi(sPort)
					if err != nil {
						errs = AppendValidation(errs, fmt.Errorf("sidecar: defaultEndpoint port (%s) is not a number: %v", sPort, err))
					} else {
						errs = AppendValidation(errs, agent.ValidatePort(port))
					}
				}
			}

			if i.Tls != nil {
				if len(i.Tls.SubjectAltNames) > 0 {
					errs = AppendValidation(errs, fmt.Errorf("sidecar: subjectAltNames is not supported in ingress tls"))
				}
				if i.Tls.HttpsRedirect {
					errs = AppendValidation(errs, fmt.Errorf("sidecar: httpsRedirect is not supported"))
				}
				if i.Tls.CredentialName != "" {
					errs = AppendValidation(errs, fmt.Errorf("sidecar: credentialName is not currently supported"))
				}
				if i.Tls.Mode == networking.ServerTLSSettings_ISTIO_MUTUAL || i.Tls.Mode == networking.ServerTLSSettings_AUTO_PASSTHROUGH {
					errs = AppendValidation(errs, fmt.Errorf("configuration is invalid: cannot set mode to %s in sidecar ingress tls", i.Tls.Mode.String()))
				}
				protocol := protocol.Parse(i.Port.Protocol)
				if !protocol.IsTLS() {
					errs = AppendValidation(errs, fmt.Errorf("server cannot have TLS settings for non HTTPS/TLS ports"))
				}
				errs = AppendValidation(errs, validateTLSOptions(i.Tls))
			}

			errs = AppendValidation(errs, validateConnectionPool(i.ConnectionPool))
			if i.ConnectionPool != nil && i.ConnectionPool.Http != nil && i.Port != nil && !protocol.Parse(i.Port.Protocol).IsHTTP() {
				errs = AppendWarningf(errs,
					"sidecar: HTTP connection pool settings are configured for port %d (%q) but its protocol is not HTTP (%s); only TCP settings will apply",
					i.Port.Number, i.Port.Name, i.Port.Protocol)
			}
		}

		errs = AppendValidation(errs, validateConnectionPool(rule.InboundConnectionPool))

		portMap = sets.Set[uint32]{}
		udsMap := sets.String{}
		catchAllEgressListenerFound := false
		for index, egress := range rule.Egress {
			if egress == nil {
				errs = AppendValidation(errs, errors.New("egress listener may not be null"))
				continue
			}

			if egress.Port == nil {
				if !catchAllEgressListenerFound {
					if index == len(rule.Egress)-1 {
						catchAllEgressListenerFound = true
					} else {
						errs = AppendValidation(errs, fmt.Errorf("sidecar: the egress listener with empty port should be the last listener in the list"))
					}
				} else {
					errs = AppendValidation(errs, fmt.Errorf("sidecar: egress can have only one listener with empty port"))
					continue
				}
			} else {

				if egress.Port.TargetPort > 0 {
					errs = AppendValidation(errs, fmt.Errorf("targetPort has no impact on Sidecars"))
				}
				bind := egress.GetBind()
				captureMode := egress.GetCaptureMode()
				errs = AppendValidation(errs, validateSidecarEgressPortBindAndCaptureMode(egress.Port, bind, captureMode))

				if egress.Port.Number == 0 {
					if _, found := udsMap[bind]; found {
						errs = AppendValidation(errs, fmt.Errorf("sidecar: unix domain socket values for listeners must be unique"))
					}
					udsMap[bind] = struct{}{}
				} else {
					if portMap.Contains(egress.Port.Number) {
						errs = AppendValidation(errs, fmt.Errorf("sidecar: ports on IP bound listeners must be unique"))
					}
					portMap.Insert(egress.Port.Number)
				}
			}

			if len(egress.Hosts) == 0 {
				errs = AppendValidation(errs, fmt.Errorf("sidecar: egress listener must contain at least one host"))
			} else {
				nssSvcs := map[string]map[string]bool{}
				for _, hostname := range egress.Hosts {
					parts := strings.SplitN(hostname, "/", 2)
					if len(parts) == 2 {
						ns := parts[0]
						svc := parts[1]
						if ns == "." {
							ns = cfg.Namespace
						}
						if _, ok := nssSvcs[ns]; !ok {
							nssSvcs[ns] = map[string]bool{}
						}

						if svc != "*" {
							if _, ok := nssSvcs[ns][svc]; ok || nssSvcs[ns]["*"] {

								errs = AppendValidation(errs, WrapWarning(fmt.Errorf("duplicated egress host: %s", hostname)))
							}
						} else {
							if len(nssSvcs[ns]) != 0 {
								errs = AppendValidation(errs, WrapWarning(fmt.Errorf("duplicated egress host: %s", hostname)))
							}
						}
						nssSvcs[ns][svc] = true
					}
					errs = AppendValidation(errs, agent.ValidateNamespaceSlashWildcardHostname(hostname, false, false))
				}

				if nssSvcs["*"]["*"] && len(nssSvcs) != 1 {
					errs = AppendValidation(errs, WrapWarning(fmt.Errorf("`*/*` host select all resources, no other hosts can be added")))
				}
			}
		}

		errs = AppendValidation(errs, validateSidecarOutboundTrafficPolicy(rule.OutboundTrafficPolicy))

		return errs.Unwrap()
	})

ValidateSidecar checks sidecar config supplied by user

View Source
var ValidateTelemetry = RegisterValidateFunc("ValidateTelemetry",
	func(cfg config.Config) (Warning, error) {
		spec, ok := cfg.Spec.(*telemetry.Telemetry)
		if !ok {
			return nil, fmt.Errorf("cannot cast to telemetry")
		}

		errs := Validation{}

		errs = AppendValidation(errs,
			validateOneOfSelectorType(spec.GetSelector(), spec.GetTargetRef(), spec.GetTargetRefs()),
			validateWorkloadSelector(spec.GetSelector()),
			validatePolicyTargetReference(spec.GetTargetRef()),
			validatePolicyTargetReferences(spec.GetTargetRefs()),
			validateTelemetryMetrics(spec.Metrics),
			validateTelemetryTracing(spec.Tracing),
			validateTelemetryAccessLogging(spec.AccessLogging),
		)
		return errs.Unwrap()
	})

ValidateTelemetry validates a Telemetry.

View Source
var ValidateVirtualService = RegisterValidateFunc("ValidateVirtualService",
	func(cfg config.Config) (Warning, error) {
		virtualService, ok := cfg.Spec.(*networking.VirtualService)
		if !ok {
			return nil, errors.New("cannot cast to virtual service")
		}
		errs := Validation{}
		if len(virtualService.Hosts) == 0 {

			if len(virtualService.Gateways) != 0 {

				errs = AppendValidation(errs, fmt.Errorf("delegate virtual service must have no gateways specified"))
			}
			if len(virtualService.Tls) != 0 {

				errs = AppendValidation(errs, fmt.Errorf("delegate virtual service must have no tls route specified"))
			}
			if len(virtualService.Tcp) != 0 {

				errs = AppendValidation(errs, fmt.Errorf("delegate virtual service must have no tcp route specified"))
			}
		}
		gatewaySemantics := cfg.Annotations[constants.InternalRouteSemantics] == constants.RouteSemanticsGateway

		appliesToMesh := false
		appliesToGateway := false
		if len(virtualService.Gateways) == 0 {
			appliesToMesh = true
		} else {
			errs = AppendValidation(errs, validateGatewayNames(virtualService.Gateways, gatewaySemantics))
			appliesToGateway = isGateway(virtualService)
			appliesToMesh = !appliesToGateway
		}

		if !appliesToGateway {
			validateJWTClaimRoute := func(headers map[string]*networking.StringMatch) {
				for key := range headers {
					if jwt.ToRoutingClaim(key).Match {
						msg := fmt.Sprintf("JWT claim based routing (key: %s) is only supported for gateway, found no gateways: %v", key, virtualService.Gateways)
						errs = AppendValidation(errs, errors.New(msg))
					}
				}
			}
			for _, http := range virtualService.GetHttp() {
				for _, m := range http.GetMatch() {
					validateJWTClaimRoute(m.GetHeaders())
					validateJWTClaimRoute(m.GetWithoutHeaders())
				}
			}
		}

		allHostsValid := true
		for _, virtualHost := range virtualService.Hosts {
			var err error
			if appliesToGateway {
				err = agent.ValidateWildcardDomainForVirtualServiceBoundToGateway(isSniHost(virtualService), virtualHost)
			} else {
				err = agent.ValidateWildcardDomain(virtualHost)
			}
			if err != nil {
				if !netutil.IsValidIPAddress(virtualHost) {
					errs = AppendValidation(errs, err)
					allHostsValid = false
				}
			} else if appliesToMesh && virtualHost == "*" {
				errs = AppendValidation(errs, fmt.Errorf("wildcard host * is not allowed for virtual services bound to the mesh gateway"))
				allHostsValid = false
			}
		}

		if allHostsValid {
			for i := 0; i < len(virtualService.Hosts); i++ {
				hostI := host.Name(virtualService.Hosts[i])
				for j := i + 1; j < len(virtualService.Hosts); j++ {
					hostJ := host.Name(virtualService.Hosts[j])
					if hostI.Matches(hostJ) {
						errs = AppendValidation(errs, fmt.Errorf("duplicate hosts in virtual service: %s & %s", hostI, hostJ))
					}
				}
			}
		}

		if len(virtualService.Http) == 0 && len(virtualService.Tcp) == 0 && len(virtualService.Tls) == 0 {
			errs = AppendValidation(errs, errors.New("http, tcp or tls must be provided in virtual service"))
		}
		for _, httpRoute := range virtualService.Http {
			if httpRoute == nil {
				errs = AppendValidation(errs, errors.New("http route may not be null"))
				continue
			}
			errs = AppendValidation(errs, validateHTTPRoute(httpRoute, len(virtualService.Hosts) == 0, gatewaySemantics))
		}
		for _, tlsRoute := range virtualService.Tls {
			errs = AppendValidation(errs, validateTLSRoute(tlsRoute, virtualService, gatewaySemantics))
		}
		for _, tcpRoute := range virtualService.Tcp {
			errs = AppendValidation(errs, validateTCPRoute(tcpRoute, gatewaySemantics))
		}

		errs = AppendValidation(errs, validateExportTo(cfg.Namespace, virtualService.ExportTo, false, false))

		warnUnused := func(ruleno, reason string) {
			errs = AppendValidation(errs, WrapWarning(&AnalysisAwareError{
				Type:       "VirtualServiceUnreachableRule",
				Msg:        fmt.Sprintf("virtualService rule %v not used (%s)", ruleno, reason),
				Parameters: []any{ruleno, reason},
			}))
		}
		warnIneffective := func(ruleno, matchno, dupno string) {
			errs = AppendValidation(errs, WrapWarning(&AnalysisAwareError{
				Type:       "VirtualServiceIneffectiveMatch",
				Msg:        fmt.Sprintf("virtualService rule %v match %v is not used (duplicate/overlapping match in rule %v)", ruleno, matchno, dupno),
				Parameters: []any{ruleno, matchno, dupno},
			}))
		}

		analyzeUnreachableHTTPRules(virtualService.Http, warnUnused, warnIneffective)
		analyzeUnreachableTCPRules(virtualService.Tcp, warnUnused, warnIneffective)
		analyzeUnreachableTLSRules(virtualService.Tls, warnUnused, warnIneffective)

		return errs.Unwrap()
	})

ValidateVirtualService checks that a v1alpha3 route rule is well-formed.

View Source
var ValidateWasmPlugin = RegisterValidateFunc("ValidateWasmPlugin",
	func(cfg config.Config) (Warning, error) {
		spec, ok := cfg.Spec.(*extensions.WasmPlugin)
		if !ok {
			return nil, fmt.Errorf("cannot cast to wasmplugin")
		}

		errs := Validation{}
		errs = AppendValidation(errs,
			validateOneOfSelectorType(spec.GetSelector(), spec.GetTargetRef(), spec.GetTargetRefs()),
			validateWorkloadSelector(spec.GetSelector()),
			validatePolicyTargetReference(spec.GetTargetRef()),
			validatePolicyTargetReferences(spec.GetTargetRefs()),
			validateWasmPluginURL(spec.Url),
			validateWasmPluginSHA(spec),
			validateWasmPluginImagePullSecret(spec),
			validateWasmPluginName(spec),
			validateWasmPluginVMConfig(spec.VmConfig),
			validateWasmPluginMatch(spec.Match),
		)
		return errs.Unwrap()
	})

ValidateWasmPlugin validates a WasmPlugin.

View Source
var ValidateWorkloadEntry = RegisterValidateFunc("ValidateWorkloadEntry",
	func(cfg config.Config) (Warning, error) {
		we, ok := cfg.Spec.(*networking.WorkloadEntry)
		if !ok {
			return nil, fmt.Errorf("cannot cast to workload entry")
		}

		return validateWorkloadEntry(we, nil, true).Unwrap()
	})

ValidateWorkloadEntry validates a workload entry.

View Source
var ValidateWorkloadGroup = RegisterValidateFunc("ValidateWorkloadGroup",
	func(cfg config.Config) (warnings Warning, errs error) {
		wg, ok := cfg.Spec.(*networking.WorkloadGroup)
		if !ok {
			return nil, fmt.Errorf("cannot cast to workload entry")
		}

		if wg.Template == nil {
			return nil, fmt.Errorf("template is required")
		}

		if wg.Metadata != nil {
			if err := labels.Instance(wg.Metadata.Labels).Validate(); err != nil {
				return nil, fmt.Errorf("invalid labels: %v", err)
			}
		}

		return nil, validateReadinessProbe(wg.Probe)
	})

ValidateWorkloadGroup validates a workload group.

Functions

func IsNegativeDuration

func IsNegativeDuration(in time.Duration) error

IsNegativeDuration check if the duration is negative

func IsValidateFunc

func IsValidateFunc(name string) bool

IsValidateFunc indicates whether there is a validation function with the given name.

func ValidateCORSHTTPHeaderName

func ValidateCORSHTTPHeaderName(name string) error

ValidateCORSHTTPHeaderName validates a headers for CORS. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers#directives

func ValidateHTTPHeaderName

func ValidateHTTPHeaderName(name string) error

ValidateHTTPHeaderName validates a header name

func ValidateHTTPHeaderNameOrJwtClaimRoute

func ValidateHTTPHeaderNameOrJwtClaimRoute(name string) error

ValidateHTTPHeaderNameOrJwtClaimRoute validates a header name, allowing special @request.auth.claims syntax

func ValidateHTTPHeaderOperationName

func ValidateHTTPHeaderOperationName(name string) error

ValidateHTTPHeaderOperationName validates a header name when used to remove from request or modify response.

func ValidateHTTPHeaderValue

func ValidateHTTPHeaderValue(value string) error

ValidateHTTPHeaderValue validates a header value for Envoy Valid: "foo", "%HOSTNAME%", "100%%", "prefix %HOSTNAME% suffix" Invalid: "abc%123", "%START_TIME%%" We don't try to check that what is inside the %% is one of Envoy recognized values, we just prevent invalid config. See: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers.html#custom-request-response-headers

func ValidateHTTPHeaderWithAuthorityOperationName

func ValidateHTTPHeaderWithAuthorityOperationName(name string) error

ValidateHTTPHeaderWithAuthorityOperationName validates a header name when used to add/set in request.

func ValidateMaxServerConnectionAge

func ValidateMaxServerConnectionAge(in time.Duration) error

ValidateMaxServerConnectionAge validate negative duration

func ValidatePercent

func ValidatePercent(val int32) error

ValidatePercent checks that percent is in range

func ValidatePortName

func ValidatePortName(name string) error

ValidatePortName validates a port name to DNS-1123

func ValidateProtocol

func ValidateProtocol(protocolStr string) error

ValidateProtocol validates a portocol name is known

func ValidateUnixAddress

func ValidateUnixAddress(addr string) error

ValidateUnixAddress validates that the string is a valid unix domain socket path.

Types

type AnalysisAwareError

type AnalysisAwareError struct {
	Type       string
	Msg        string
	Parameters []any
}

func (*AnalysisAwareError) Error

func (aae *AnalysisAwareError) Error() string

type HTTPRouteType

type HTTPRouteType int

type OverlappingMatchValidationForHTTPRoute

type OverlappingMatchValidationForHTTPRoute struct {
	RouteStr         string
	MatchStr         string
	Prefix           string
	MatchPort        uint32
	MatchMethod      string
	MatchAuthority   string
	MatchHeaders     map[string]string
	MatchQueryParams map[string]string
	MatchNonHeaders  map[string]string
}

OverlappingMatchValidationForHTTPRoute holds necessary information from virtualservice to do such overlapping match validation

type ParserState

type ParserState int32
const (
	LiteralParserState                   ParserState = iota // processing literal data
	VariableNameParserState                                 // consuming a %VAR% name
	ExpectArrayParserState                                  // expect starting [ in %VAR([...])%
	ExpectStringParserState                                 // expect starting " in array of strings
	StringParserState                                       // consuming an array element string
	ExpectArrayDelimiterOrEndParserState                    // expect array delimiter (,) or end of array (])
	ExpectArgsEndParserState                                // expect closing ) in %VAR(...)%
	ExpectVariableEndParserState                            // expect closing % in %VAR(...)%
)

type ValidateFunc

type ValidateFunc func(config config.Config) (Warning, error)

ValidateFunc defines a validation func for an API proto.

func GetValidateFunc

func GetValidateFunc(name string) ValidateFunc

GetValidateFunc returns the validation function with the given name, or null if it does not exist.

func RegisterValidateFunc

func RegisterValidateFunc(name string, f ValidateFunc) ValidateFunc

type Validation

type Validation = agent.Validation

func AppendValidation

func AppendValidation(v Validation, vs ...error) Validation

func AppendWarningf

func AppendWarningf(v Validation, format string, a ...any) Validation

AppendWarningf appends a formatted warning string nolint: unparam

func WrapError

func WrapError(e error) Validation

WrapError turns an error into a Validation

func WrapWarning

func WrapWarning(e error) Validation

WrapWarning turns an error into a Validation as a warning

type Warning

type Warning = agent.Warning

func ValidateAlphaWorkloadSelector

func ValidateAlphaWorkloadSelector(selector *networking.WorkloadSelector) (Warning, error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL