Skip to content

Commit

Permalink
Merge branch 'main' into feat-early-header-mutation
Browse files Browse the repository at this point in the history
  • Loading branch information
guydc authored Aug 6, 2024
2 parents 8e546e7 + 3497600 commit a9032ca
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 94 deletions.
21 changes: 21 additions & 0 deletions api/v1alpha1/connection_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,46 @@ type ClientConnection struct {
// +optional
ConnectionLimit *ConnectionLimit `json:"connectionLimit,omitempty"`
// BufferLimit provides configuration for the maximum buffer size in bytes for each incoming connection.
// BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space.
// For example, 20Mi, 1Gi, 256Ki etc.
// Note that when the suffix is not provided, the value is interpreted as bytes.
// Default: 32768 bytes.
//
// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches(r\"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\") : type(self) == int",message="bufferLimit must be of the format \"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\""
// +optional
BufferLimit *resource.Quantity `json:"bufferLimit,omitempty"`
// SocketBufferLimit provides configuration for the maximum buffer size in bytes for each incoming socket.
// SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space.
// For example, 20Mi, 1Gi, 256Ki etc.
// Note that when the suffix is not provided, the value is interpreted as bytes.
//
// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches(r\"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\") : type(self) == int",message="socketBufferLimit must be of the format \"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\""
// +optional
// +notImplementedHide
SocketBufferLimit *resource.Quantity `json:"socketBufferLimit,omitempty"`
}

// BackendConnection allows users to configure connection-level settings of backend
type BackendConnection struct {
// BufferLimit Soft limit on size of the cluster’s connections read and write buffers.
// BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space.
// If unspecified, an implementation defined default is applied (32768 bytes).
// For example, 20Mi, 1Gi, 256Ki etc.
// Note: that when the suffix is not provided, the value is interpreted as bytes.
//
// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches(r\"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\") : type(self) == int",message="BufferLimit must be of the format \"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\""
// +optional
BufferLimit *resource.Quantity `json:"bufferLimit,omitempty"`
// SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket
// to backend.
// SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space.
// For example, 20Mi, 1Gi, 256Ki etc.
// Note that when the suffix is not provided, the value is interpreted as bytes.
//
// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches(r\"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\") : type(self) == int",message="socketBufferLimit must be of the format \"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\""
// +optional
// +notImplementedHide
SocketBufferLimit *resource.Quantity `json:"socketBufferLimit,omitempty"`
}

type ConnectionLimit struct {
Expand Down
231 changes: 144 additions & 87 deletions api/v1alpha1/validation/envoygateway_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,115 +6,172 @@
package validation

import (
"errors"
"fmt"
"net/url"

gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
)

// ValidateEnvoyGateway validates the provided EnvoyGateway.
func ValidateEnvoyGateway(eg *egv1a1.EnvoyGateway) error {
switch {
case eg == nil:
return errors.New("envoy gateway config is unspecified")
case eg.Gateway == nil:
return errors.New("gateway is unspecified")
case len(eg.Gateway.ControllerName) == 0:
return errors.New("gateway controllerName is unspecified")
case eg.Provider == nil:
return errors.New("provider is unspecified")
case eg.Provider.Type != egv1a1.ProviderTypeKubernetes:
return fmt.Errorf("unsupported provider %v", eg.Provider.Type)
case eg.Provider.Kubernetes != nil && eg.Provider.Kubernetes.Watch != nil:
watch := eg.Provider.Kubernetes.Watch
switch watch.Type {
case egv1a1.KubernetesWatchModeTypeNamespaces:
if len(watch.Namespaces) == 0 {
return errors.New("namespaces should be specified when envoy gateway watch mode is 'Namespaces'")
}
case egv1a1.KubernetesWatchModeTypeNamespaceSelector:
if watch.NamespaceSelector == nil {
return errors.New("namespaceSelector should be specified when envoy gateway watch mode is 'NamespaceSelector'")
}
default:
return errors.New("envoy gateway watch mode invalid, should be 'Namespaces' or 'NamespaceSelector'")
}
case eg.Logging != nil && len(eg.Logging.Level) != 0:
level := eg.Logging.Level
for component, logLevel := range level {
switch component {
case egv1a1.LogComponentGatewayDefault,
egv1a1.LogComponentProviderRunner,
egv1a1.LogComponentGatewayAPIRunner,
egv1a1.LogComponentXdsTranslatorRunner,
egv1a1.LogComponentXdsServerRunner,
egv1a1.LogComponentInfrastructureRunner,
egv1a1.LogComponentGlobalRateLimitRunner:
switch logLevel {
case egv1a1.LogLevelDebug, egv1a1.LogLevelError, egv1a1.LogLevelWarn, egv1a1.LogLevelInfo:
default:
return errors.New("envoy gateway logging level invalid. valid options: info/debug/warn/error")
}
default:
return errors.New("envoy gateway logging components invalid. valid options: system/provider/gateway-api/xds-translator/xds-server/infrastructure")
}
}
case eg.RateLimit != nil:
if eg.RateLimit.Backend.Type != egv1a1.RedisBackendType {
return fmt.Errorf("unsupported ratelimit backend %v", eg.RateLimit.Backend.Type)
}
if eg.RateLimit.Backend.Redis == nil || eg.RateLimit.Backend.Redis.URL == "" {
return fmt.Errorf("empty ratelimit redis settings")
if eg == nil {
return fmt.Errorf("envoy gateway config is unspecified")
}

if eg.Gateway == nil {
return fmt.Errorf("gateway is unspecified")
}

if len(eg.Gateway.ControllerName) == 0 {
return fmt.Errorf("gateway controllerName is unspecified")
}

if eg.Provider == nil {
return fmt.Errorf("provider is unspecified")
}

switch eg.Provider.Type {
case egv1a1.ProviderTypeKubernetes:
if err := validateEnvoyGatewayKubernetesProvider(eg.Provider.Kubernetes); err != nil {
return err
}
if _, err := url.Parse(eg.RateLimit.Backend.Redis.URL); err != nil {
return fmt.Errorf("unknown ratelimit redis url format: %w", err)
default:
return fmt.Errorf("unsupported provider type")
}

if err := validateEnvoyGatewayLogging(eg.Logging); err != nil {
return err
}

if err := validateEnvoyGatewayRateLimit(eg.RateLimit); err != nil {
return err
}

if err := validateEnvoyGatewayExtensionManager(eg.ExtensionManager); err != nil {
return err
}

if err := validateEnvoyGatewayTelemetry(eg.Telemetry); err != nil {
return err
}

return nil
}

func validateEnvoyGatewayKubernetesProvider(provider *egv1a1.EnvoyGatewayKubernetesProvider) error {
if provider == nil || provider.Watch == nil {
return nil
}

watch := provider.Watch
switch watch.Type {
case egv1a1.KubernetesWatchModeTypeNamespaces:
if len(watch.Namespaces) == 0 {
return fmt.Errorf("namespaces should be specified when envoy gateway watch mode is 'Namespaces'")
}
case eg.ExtensionManager != nil:
if eg.ExtensionManager.Hooks == nil || eg.ExtensionManager.Hooks.XDSTranslator == nil {
return fmt.Errorf("registered extension has no hooks specified")
case egv1a1.KubernetesWatchModeTypeNamespaceSelector:
if watch.NamespaceSelector == nil {
return fmt.Errorf("namespaceSelector should be specified when envoy gateway watch mode is 'NamespaceSelector'")
}
default:
return fmt.Errorf("envoy gateway watch mode invalid, should be 'Namespaces' or 'NamespaceSelector'")
}
return nil
}

if len(eg.ExtensionManager.Hooks.XDSTranslator.Pre) == 0 && len(eg.ExtensionManager.Hooks.XDSTranslator.Post) == 0 {
return fmt.Errorf("registered extension has no hooks specified")
}
func validateEnvoyGatewayLogging(logging *egv1a1.EnvoyGatewayLogging) error {
if logging == nil || len(logging.Level) == 0 {
return nil
}

if eg.ExtensionManager.Service == nil {
return fmt.Errorf("extension service config is empty")
for component, logLevel := range logging.Level {
switch component {
case egv1a1.LogComponentGatewayDefault,
egv1a1.LogComponentProviderRunner,
egv1a1.LogComponentGatewayAPIRunner,
egv1a1.LogComponentXdsTranslatorRunner,
egv1a1.LogComponentXdsServerRunner,
egv1a1.LogComponentInfrastructureRunner,
egv1a1.LogComponentGlobalRateLimitRunner:
switch logLevel {
case egv1a1.LogLevelDebug, egv1a1.LogLevelError, egv1a1.LogLevelWarn, egv1a1.LogLevelInfo:
default:
return fmt.Errorf("envoy gateway logging level invalid. valid options: info/debug/warn/error")
}
default:
return fmt.Errorf("envoy gateway logging components invalid. valid options: system/provider/gateway-api/xds-translator/xds-server/infrastructure")
}
}
return nil
}

switch {
case eg.ExtensionManager.Service.Host == "" && eg.ExtensionManager.Service.FQDN == nil && eg.ExtensionManager.Service.Unix == nil && eg.ExtensionManager.Service.IP == nil:
return fmt.Errorf("extension service must contain a configured target")
func validateEnvoyGatewayRateLimit(rateLimit *egv1a1.RateLimit) error {
if rateLimit == nil {
return nil
}
if rateLimit.Backend.Type != egv1a1.RedisBackendType {
return fmt.Errorf("unsupported ratelimit backend %v", rateLimit.Backend.Type)
}
if rateLimit.Backend.Redis == nil || rateLimit.Backend.Redis.URL == "" {
return fmt.Errorf("empty ratelimit redis settings")
}
if _, err := url.Parse(rateLimit.Backend.Redis.URL); err != nil {
return fmt.Errorf("unknown ratelimit redis url format: %w", err)
}
return nil
}

case eg.ExtensionManager.Service.FQDN != nil && (eg.ExtensionManager.Service.IP != nil || eg.ExtensionManager.Service.Unix != nil || eg.ExtensionManager.Service.Host != ""),
eg.ExtensionManager.Service.IP != nil && (eg.ExtensionManager.Service.FQDN != nil || eg.ExtensionManager.Service.Unix != nil || eg.ExtensionManager.Service.Host != ""),
eg.ExtensionManager.Service.Unix != nil && (eg.ExtensionManager.Service.IP != nil || eg.ExtensionManager.Service.FQDN != nil || eg.ExtensionManager.Service.Host != ""):
func validateEnvoyGatewayExtensionManager(extensionManager *egv1a1.ExtensionManager) error {
if extensionManager == nil {
return nil
}

return fmt.Errorf("only one backend target can be configured for the extension manager")
if extensionManager.Hooks == nil || extensionManager.Hooks.XDSTranslator == nil {
return fmt.Errorf("registered extension has no hooks specified")
}

}
if len(extensionManager.Hooks.XDSTranslator.Pre) == 0 && len(extensionManager.Hooks.XDSTranslator.Post) == 0 {
return fmt.Errorf("registered extension has no hooks specified")
}

if eg.ExtensionManager.Service.TLS != nil {
certificateRefKind := eg.ExtensionManager.Service.TLS.CertificateRef.Kind
if extensionManager.Service == nil {
return fmt.Errorf("extension service config is empty")
}

if certificateRefKind == nil {
return fmt.Errorf("certificateRef empty in extension service server TLS settings")
}
switch {
case extensionManager.Service.Host == "" && extensionManager.Service.FQDN == nil && extensionManager.Service.Unix == nil && extensionManager.Service.IP == nil:
return fmt.Errorf("extension service must contain a configured target")

if *certificateRefKind != gwapiv1.Kind("Secret") {
return fmt.Errorf("unsupported extension server TLS certificateRef %v", certificateRefKind)
}
case extensionManager.Service.FQDN != nil && (extensionManager.Service.IP != nil || extensionManager.Service.Unix != nil || extensionManager.Service.Host != ""),
extensionManager.Service.IP != nil && (extensionManager.Service.FQDN != nil || extensionManager.Service.Unix != nil || extensionManager.Service.Host != ""),
extensionManager.Service.Unix != nil && (extensionManager.Service.IP != nil || extensionManager.Service.FQDN != nil || extensionManager.Service.Host != ""):
return fmt.Errorf("only one backend target can be configured for the extension manager")
}

if extensionManager.Service.TLS != nil {
certificateRefKind := extensionManager.Service.TLS.CertificateRef.Kind

if certificateRefKind == nil {
return fmt.Errorf("certificateRef empty in extension service server TLS settings")
}
case eg.Telemetry != nil:
if eg.Telemetry.Metrics != nil {
for _, sink := range eg.Telemetry.Metrics.Sinks {
if sink.Type == egv1a1.MetricSinkTypeOpenTelemetry {
if sink.OpenTelemetry == nil {
return fmt.Errorf("OpenTelemetry is required when sink Type is OpenTelemetry")
}

if *certificateRefKind != "Secret" {
return fmt.Errorf("unsupported extension server TLS certificateRef %v", certificateRefKind)
}
}
return nil
}

func validateEnvoyGatewayTelemetry(telemetry *egv1a1.EnvoyGatewayTelemetry) error {
if telemetry == nil {
return nil
}

if telemetry.Metrics != nil {
for _, sink := range telemetry.Metrics.Sinks {
if sink.Type == egv1a1.MetricSinkTypeOpenTelemetry {
if sink.OpenTelemetry == nil {
return fmt.Errorf("OpenTelemetry is required when sink Type is OpenTelemetry")
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ spec:
- type: string
description: |-
BufferLimit Soft limit on size of the cluster’s connections read and write buffers.
BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space.
If unspecified, an implementation defined default is applied (32768 bytes).
For example, 20Mi, 1Gi, 256Ki etc.
Note: that when the suffix is not provided, the value is interpreted as bytes.
Expand All @@ -137,6 +138,22 @@ spec:
- message: BufferLimit must be of the format "^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$"
rule: 'type(self) == string ? self.matches(r"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$")
: type(self) == int'
socketBufferLimit:
anyOf:
- type: integer
- type: string
description: |-
SocketBufferLimit provides configuration for the maximum buffer size in bytes for each socket
to backend.
SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space.
For example, 20Mi, 1Gi, 256Ki etc.
Note that when the suffix is not provided, the value is interpreted as bytes.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
x-kubernetes-validations:
- message: socketBufferLimit must be of the format "^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$"
rule: 'type(self) == string ? self.matches(r"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$")
: type(self) == int'
type: object
dns:
description: DNS includes dns resolution settings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ spec:
- type: string
description: |-
BufferLimit provides configuration for the maximum buffer size in bytes for each incoming connection.
BufferLimit applies to connection streaming (maybe non-streaming) channel between processes, it's in user space.
For example, 20Mi, 1Gi, 256Ki etc.
Note that when the suffix is not provided, the value is interpreted as bytes.
Default: 32768 bytes.
Expand Down Expand Up @@ -131,6 +132,21 @@ spec:
minimum: 0
type: integer
type: object
socketBufferLimit:
anyOf:
- type: integer
- type: string
description: |-
SocketBufferLimit provides configuration for the maximum buffer size in bytes for each incoming socket.
SocketBufferLimit applies to socket streaming channel between TCP/IP stacks, it's in kernel space.
For example, 20Mi, 1Gi, 256Ki etc.
Note that when the suffix is not provided, the value is interpreted as bytes.
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
x-kubernetes-validations:
- message: socketBufferLimit must be of the format "^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$"
rule: 'type(self) == string ? self.matches(r"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$")
: type(self) == int'
type: object
enableProxyProtocol:
description: |-
Expand Down
Loading

0 comments on commit a9032ca

Please sign in to comment.