From 16f1b43aa8f65e8a9ead7b5ec1a5890a8cbc7f53 Mon Sep 17 00:00:00 2001 From: Guy Daich Date: Wed, 15 May 2024 16:52:47 -0500 Subject: [PATCH] API: Backend (#3063) * start backend Signed-off-by: Guy Daich * update design Signed-off-by: Guy Daich * add section name Signed-off-by: Guy Daich * update doc Signed-off-by: Guy Daich * fix cel Signed-off-by: Guy Daich * set MaxItems=1 Signed-off-by: Guy Daich * fix links Signed-off-by: Guy Daich * remove support for named policy attachment Signed-off-by: Guy Daich * rename address proto to proto Signed-off-by: Guy Daich * fix review comments, change ApplicationProto scope and enum Signed-off-by: Guy Daich * add CEL test, optionals, list, init and hide from docs Signed-off-by: Guy Daich * remove IPv6 Signed-off-by: Guy Daich * add port validation Signed-off-by: Guy Daich * rm protocol, rename, update design Signed-off-by: Guy Daich * code review fixes Signed-off-by: Guy Daich * fix design and app protos json Signed-off-by: Guy Daich * fix test Signed-off-by: Guy Daich * rename backendAddresses to endpoints, ip to ipv4 Signed-off-by: Guy Daich * fix doc string to use address Signed-off-by: Guy Daich * improve all doc strings Signed-off-by: Guy Daich * fix ip => ipv4 Signed-off-by: Guy Daich * rename backendendpoints => endpoits Signed-off-by: Guy Daich * renamed fqdn.address => fqdn.hostname Signed-off-by: Guy Daich * add conditions, doc fixes Signed-off-by: Guy Daich --------- Signed-off-by: Guy Daich --- api/v1alpha1/backend_types.go | 193 +++++++++++++ api/v1alpha1/envoygateway_types.go | 3 + api/v1alpha1/zz_generated.deepcopy.go | 245 ++++++++++++++-- .../gateway.envoyproxy.io_backends.yaml | 224 +++++++++++++++ .../en/contributions/design/backend.md | 162 +++++++++++ site/content/en/latest/api/extension_types.md | 138 +++++++++ test/cel-validation/backend_test.go | 266 ++++++++++++++++++ 7 files changed, 1200 insertions(+), 31 deletions(-) create mode 100644 api/v1alpha1/backend_types.go create mode 100644 charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml create mode 100644 site/content/en/contributions/design/backend.md create mode 100644 test/cel-validation/backend_test.go diff --git a/api/v1alpha1/backend_types.go b/api/v1alpha1/backend_types.go new file mode 100644 index 00000000000..a35cd62b6e6 --- /dev/null +++ b/api/v1alpha1/backend_types.go @@ -0,0 +1,193 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + // KindBackend is the name of the Backend kind. + KindBackend = "Backend" +) + +// AppProtocolType defines various backend applications protocols supported by Envoy Gateway +// +// +kubebuilder:validation:Enum=gateway.envoyproxy.io/h2c;gateway.envoyproxy.io/ws;gateway.envoyproxy.io/wss +// +notImplementedHide +type AppProtocolType string + +const ( + // AppProtocolTypeH2C defines the HTTP/2 application protocol. + AppProtocolTypeH2C AppProtocolType = "gateway.envoyproxy.io/h2c" + // AppProtocolTypeWS defines the WebSocket over HTTP protocol. + AppProtocolTypeWS AppProtocolType = "gateway.envoyproxy.io/ws" + // AppProtocolTypeWSS defines the WebSocket over HTTPS protocol. + AppProtocolTypeWSS AppProtocolType = "gateway.envoyproxy.io/wss" +) + +// Backend allows the user to configure the endpoints of a backend and +// the behavior of the connection from Envoy Proxy to the backend. +// +// +kubebuilder:object:root=true +// +kubebuilder:resource:categories=envoy-gateway,shortName=be +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Accepted")].reason` +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` +// +notImplementedHide +type Backend struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Spec defines the desired state of Backend. + Spec BackendSpec `json:"spec"` + + // Status defines the current status of Backend. + Status BackendStatus `json:"status,omitempty"` +} + +// BackendEndpoint describes a backend endpoint, which can be either a fully-qualified domain name, IPv4 address or unix domain socket +// corresponding to Envoy's Address: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-address +// +// +kubebuilder:validation:XValidation:rule="(has(self.fqdn) || has(self.ipv4) || has(self.unix))",message="one of fqdn, ipv4 or unix must be specified" +// +kubebuilder:validation:XValidation:rule="((has(self.fqdn) && !(has(self.ipv4) || has(self.unix))) || (has(self.ipv4) && !(has(self.fqdn) || has(self.unix))) || (has(self.unix) && !(has(self.ipv4) || has(self.fqdn))))",message="only one of fqdn, ipv4 or unix can be specified" +// +notImplementedHide +type BackendEndpoint struct { + // FQDN defines a FQDN endpoint + // + // +optional + FQDN *FQDNEndpoint `json:"fqdn,omitempty"` + + // IPv4 defines an IPv4 endpoint + // + // +optional + IPv4 *IPv4Endpoint `json:"ipv4,omitempty"` + + // Unix defines the unix domain socket endpoint + // + // +optional + Unix *UnixSocket `json:"unix,omitempty"` +} + +// IPv4Endpoint describes TCP/UDP socket address, corresponding to Envoy's Socket Address +// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-socketaddress +// +// +notImplementedHide +type IPv4Endpoint struct { + // Address defines the IPv4 address of the backend endpoint. + // + // +kubebuilder:validation:MinLength=7 + // +kubebuilder:validation:MaxLength=15 + // +kubebuilder:validation:Pattern=`^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` + Address string `json:"address"` + + // Port defines the port of the backend endpoint. + // + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 + Port int32 `json:"port"` +} + +// FQDNEndpoint describes TCP/UDP socket address, corresponding to Envoy's Socket Address +// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-socketaddress +// +// +notImplementedHide +type FQDNEndpoint struct { + // Hostname defines the FQDN hostname of the backend endpoint. + // + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$` + Hostname string `json:"hostname"` + + // Port defines the port of the backend endpoint. + // + // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Maximum=65535 + Port int32 `json:"port"` +} + +// UnixSocket describes TCP/UDP unix domain socket address, corresponding to Envoy's Pipe +// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-pipe +// +// +notImplementedHide +type UnixSocket struct { + // Path defines the unix domain socket path of the backend endpoint. + Path string `json:"path"` +} + +// BackendSpec describes the desired state of BackendSpec. +// +// +notImplementedHide +type BackendSpec struct { + // Endpoints defines the endpoints to be used when connecting to the backend. + // + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=4 + // +kubebuilder:validation:XValidation:rule="self.all(f, has(f.fqdn)) || !self.exists(f, has(f.fqdn))",message="fqdn addresses cannot be mixed with other address types" + Endpoints []BackendEndpoint `json:"endpoints,omitempty"` + + // AppProtocols defines the application protocols to be supported when connecting to the backend. + // + // +optional + AppProtocols []AppProtocolType `json:"appProtocols,omitempty"` +} + +// BackendConditionType is a type of condition for a backend. This type should be +// used with a Backend resource Status.Conditions field. +type BackendConditionType string + +// BackendConditionReason is a reason for a backend condition. +type BackendConditionReason string + +const ( + // BackendConditionAccepted indicates whether the backend has been accepted or + // rejected by a targeted resource, and why. + // + // Possible reasons for this condition to be True are: + // + // * "Accepted" + // + // Possible reasons for this condition to be False are: + // + // * "Invalid" + // + BackendConditionAccepted BackendConditionType = "Accepted" + + // BackendReasonAccepted is used with the "Accepted" condition when the backend + // has been accepted by the targeted resource. + BackendReasonAccepted BackendConditionReason = "Accepted" + + // BackendReasonInvalid is used with the "Accepted" condition when the backend + // is syntactically or semantically invalid. + BackendReasonInvalid BackendConditionReason = "Invalid" +) + +// BackendStatus defines the state of Backend +// +notImplementedHide +type BackendStatus struct { + // Conditions describe the current conditions of the Backend. + // + // +optional + // +listType=map + // +listMapKey=type + // +kubebuilder:validation:MaxItems=8 + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// BackendList contains a list of Backend resources. +// +// +kubebuilder:object:root=true +// +notImplementedHide +type BackendList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Backend `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Backend{}, &BackendList{}) +} diff --git a/api/v1alpha1/envoygateway_types.go b/api/v1alpha1/envoygateway_types.go index b1de1937c61..29b816c5fcb 100644 --- a/api/v1alpha1/envoygateway_types.go +++ b/api/v1alpha1/envoygateway_types.go @@ -166,6 +166,9 @@ type ExtensionAPISettings struct { // EnableEnvoyPatchPolicy enables Envoy Gateway to // reconcile and implement the EnvoyPatchPolicy resources. EnableEnvoyPatchPolicy bool `json:"enableEnvoyPatchPolicy"` + // EnableBackend enables Envoy Gateway to + // reconcile and implement the Backend resources. + EnableBackend bool `json:"enableBackend"` } // EnvoyGatewayProvider defines the desired configuration of a provider. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c584ae2d62a..f3fd72c38d1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -14,9 +14,9 @@ import ( "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/gateway-api/apis/v1" + apisv1 "sigs.k8s.io/gateway-api/apis/v1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -86,12 +86,12 @@ func (in *ActiveHealthCheck) DeepCopyInto(out *ActiveHealthCheck) { *out = *in if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.Interval != nil { in, out := &in.Interval, &out.Interval - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.UnhealthyThreshold != nil { @@ -183,12 +183,12 @@ func (in *BackOffPolicy) DeepCopyInto(out *BackOffPolicy) { *out = *in if in.BaseInterval != nil { in, out := &in.BaseInterval, &out.BaseInterval - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.MaxInterval != nil { in, out := &in.MaxInterval, &out.MaxInterval - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } } @@ -203,6 +203,95 @@ func (in *BackOffPolicy) DeepCopy() *BackOffPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Backend) DeepCopyInto(out *Backend) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backend. +func (in *Backend) DeepCopy() *Backend { + if in == nil { + return nil + } + out := new(Backend) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Backend) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendEndpoint) DeepCopyInto(out *BackendEndpoint) { + *out = *in + if in.FQDN != nil { + in, out := &in.FQDN, &out.FQDN + *out = new(FQDNEndpoint) + **out = **in + } + if in.IPv4 != nil { + in, out := &in.IPv4, &out.IPv4 + *out = new(IPv4Endpoint) + **out = **in + } + if in.Unix != nil { + in, out := &in.Unix, &out.Unix + *out = new(UnixSocket) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendEndpoint. +func (in *BackendEndpoint) DeepCopy() *BackendEndpoint { + if in == nil { + return nil + } + out := new(BackendEndpoint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendList) DeepCopyInto(out *BackendList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Backend, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendList. +func (in *BackendList) DeepCopy() *BackendList { + if in == nil { + return nil + } + out := new(BackendList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackendList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackendRef) DeepCopyInto(out *BackendRef) { *out = *in @@ -219,12 +308,61 @@ func (in *BackendRef) DeepCopy() *BackendRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendSpec) DeepCopyInto(out *BackendSpec) { + *out = *in + if in.Endpoints != nil { + in, out := &in.Endpoints, &out.Endpoints + *out = make([]BackendEndpoint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AppProtocols != nil { + in, out := &in.AppProtocols, &out.AppProtocols + *out = make([]AppProtocolType, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendSpec. +func (in *BackendSpec) DeepCopy() *BackendSpec { + if in == nil { + return nil + } + out := new(BackendSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackendStatus) DeepCopyInto(out *BackendStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendStatus. +func (in *BackendStatus) DeepCopy() *BackendStatus { + if in == nil { + return nil + } + out := new(BackendStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackendTLSConfig) DeepCopyInto(out *BackendTLSConfig) { *out = *in if in.ClientCertificateRef != nil { in, out := &in.ClientCertificateRef, &out.ClientCertificateRef - *out = new(v1.SecretObjectReference) + *out = new(apisv1.SecretObjectReference) (*in).DeepCopyInto(*out) } in.TLSSettings.DeepCopyInto(&out.TLSSettings) @@ -417,7 +555,7 @@ func (in *CORS) DeepCopyInto(out *CORS) { } if in.MaxAge != nil { in, out := &in.MaxAge, &out.MaxAge - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.AllowCredentials != nil { @@ -693,7 +831,7 @@ func (in *ClientValidationContext) DeepCopyInto(out *ClientValidationContext) { *out = *in if in.CACertificateRefs != nil { in, out := &in.CACertificateRefs, &out.CACertificateRefs - *out = make([]v1.SecretObjectReference, len(*in)) + *out = make([]apisv1.SecretObjectReference, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -760,7 +898,7 @@ func (in *ConnectionLimit) DeepCopyInto(out *ConnectionLimit) { *out = *in if in.CloseDelay != nil { in, out := &in.CloseDelay, &out.CloseDelay - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -1692,7 +1830,7 @@ func (in *ExtProc) DeepCopyInto(out *ExtProc) { } if in.MessageTimeout != nil { in, out := &in.MessageTimeout, &out.MessageTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.FailOpen != nil { @@ -1843,6 +1981,21 @@ func (in *ExtensionTLS) DeepCopy() *ExtensionTLS { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FQDNEndpoint) DeepCopyInto(out *FQDNEndpoint) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FQDNEndpoint. +func (in *FQDNEndpoint) DeepCopy() *FQDNEndpoint { + if in == nil { + return nil + } + out := new(FQDNEndpoint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FaultInjection) DeepCopyInto(out *FaultInjection) { *out = *in @@ -1903,7 +2056,7 @@ func (in *FaultInjectionDelay) DeepCopyInto(out *FaultInjectionDelay) { *out = *in if in.FixedDelay != nil { in, out := &in.FixedDelay, &out.FixedDelay - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.Percentage != nil { @@ -2176,12 +2329,12 @@ func (in *HTTPClientTimeout) DeepCopyInto(out *HTTPClientTimeout) { *out = *in if in.RequestReceivedTimeout != nil { in, out := &in.RequestReceivedTimeout, &out.RequestReceivedTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.IdleTimeout != nil { in, out := &in.IdleTimeout, &out.IdleTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -2227,12 +2380,12 @@ func (in *HTTPTimeout) DeepCopyInto(out *HTTPTimeout) { *out = *in if in.ConnectionIdleTimeout != nil { in, out := &in.ConnectionIdleTimeout, &out.ConnectionIdleTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.MaxConnectionDuration != nil { in, out := &in.MaxConnectionDuration, &out.MaxConnectionDuration - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -2352,6 +2505,21 @@ func (in *HealthCheck) DeepCopy() *HealthCheck { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IPv4Endpoint) DeepCopyInto(out *IPv4Endpoint) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPv4Endpoint. +func (in *IPv4Endpoint) DeepCopy() *IPv4Endpoint { + if in == nil { + return nil + } + out := new(IPv4Endpoint) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageWasmCodeSource) DeepCopyInto(out *ImageWasmCodeSource) { *out = *in @@ -2846,7 +3014,7 @@ func (in *KubernetesWatchMode) DeepCopyInto(out *KubernetesWatchMode) { } if in.NamespaceSelector != nil { in, out := &in.NamespaceSelector, &out.NamespaceSelector - *out = new(metav1.LabelSelector) + *out = new(v1.LabelSelector) (*in).DeepCopyInto(*out) } } @@ -2866,17 +3034,17 @@ func (in *LeaderElection) DeepCopyInto(out *LeaderElection) { *out = *in if in.LeaseDuration != nil { in, out := &in.LeaseDuration, &out.LeaseDuration - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.RenewDeadline != nil { in, out := &in.RenewDeadline, &out.RenewDeadline - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.RetryPeriod != nil { in, out := &in.RetryPeriod, &out.RetryPeriod - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.Disable != nil { @@ -3064,7 +3232,7 @@ func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) { } if in.Interval != nil { in, out := &in.Interval, &out.Interval - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.ConsecutiveLocalOriginFailures != nil { @@ -3084,7 +3252,7 @@ func (in *PassiveHealthCheck) DeepCopyInto(out *PassiveHealthCheck) { } if in.BaseEjectionTime != nil { in, out := &in.BaseEjectionTime, &out.BaseEjectionTime - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.MaxEjectionPercent != nil { @@ -3134,7 +3302,7 @@ func (in *PerRetryPolicy) DeepCopyInto(out *PerRetryPolicy) { *out = *in if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.BackOff != nil { @@ -3518,7 +3686,7 @@ func (in *RateLimit) DeepCopyInto(out *RateLimit) { in.Backend.DeepCopyInto(&out.Backend) if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.Telemetry != nil { @@ -3778,7 +3946,7 @@ func (in *RedisTLSSettings) DeepCopyInto(out *RedisTLSSettings) { *out = *in if in.CertificateRef != nil { in, out := &in.CertificateRef, &out.CertificateRef - *out = new(v1.SecretObjectReference) + *out = new(apisv1.SecretObjectReference) (*in).DeepCopyInto(*out) } } @@ -4009,12 +4177,12 @@ func (in *ShutdownConfig) DeepCopyInto(out *ShutdownConfig) { *out = *in if in.DrainTimeout != nil { in, out := &in.DrainTimeout, &out.DrainTimeout - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } if in.MinDrainDuration != nil { in, out := &in.MinDrainDuration, &out.MinDrainDuration - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } } @@ -4054,7 +4222,7 @@ func (in *SlowStart) DeepCopyInto(out *SlowStart) { *out = *in if in.Window != nil { in, out := &in.Window, &out.Window - *out = new(metav1.Duration) + *out = new(v1.Duration) **out = **in } } @@ -4144,12 +4312,12 @@ func (in *TCPKeepalive) DeepCopyInto(out *TCPKeepalive) { } if in.IdleTime != nil { in, out := &in.IdleTime, &out.IdleTime - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } if in.Interval != nil { in, out := &in.Interval, &out.Interval - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -4169,7 +4337,7 @@ func (in *TCPTimeout) DeepCopyInto(out *TCPTimeout) { *out = *in if in.ConnectTimeout != nil { in, out := &in.ConnectTimeout, &out.ConnectTimeout - *out = new(v1.Duration) + *out = new(apisv1.Duration) **out = **in } } @@ -4281,6 +4449,21 @@ func (in *TracingProvider) DeepCopy() *TracingProvider { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UnixSocket) DeepCopyInto(out *UnixSocket) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnixSocket. +func (in *UnixSocket) DeepCopy() *UnixSocket { + if in == nil { + return nil + } + out := new(UnixSocket) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Wasm) DeepCopyInto(out *Wasm) { *out = *in diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml new file mode 100644 index 00000000000..cb3538d9874 --- /dev/null +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backends.yaml @@ -0,0 +1,224 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: backends.gateway.envoyproxy.io +spec: + group: gateway.envoyproxy.io + names: + categories: + - envoy-gateway + kind: Backend + listKind: BackendList + plural: backends + shortNames: + - be + singular: backend + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].reason + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Backend allows the user to configure the endpoints of a backend and + the behavior of the connection from Envoy Proxy to the backend. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Backend. + properties: + appProtocols: + description: AppProtocols defines the application protocols to be + supported when connecting to the backend. + items: + description: AppProtocolType defines various backend applications + protocols supported by Envoy Gateway + enum: + - gateway.envoyproxy.io/h2c + - gateway.envoyproxy.io/ws + - gateway.envoyproxy.io/wss + type: string + type: array + endpoints: + description: Endpoints defines the endpoints to be used when connecting + to the backend. + items: + description: |- + BackendEndpoint describes a backend endpoint, which can be either a fully-qualified domain name, IPv4 address or unix domain socket + corresponding to Envoy's Address: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-address + properties: + fqdn: + description: FQDN defines a FQDN endpoint + properties: + hostname: + description: Hostname defines the FQDN hostname of the backend + endpoint. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: Port defines the port of the backend endpoint. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - hostname + - port + type: object + ipv4: + description: IPv4 defines an IPv4 endpoint + properties: + address: + description: Address defines the IPv4 address of the backend + endpoint. + maxLength: 15 + minLength: 7 + pattern: ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ + type: string + port: + description: Port defines the port of the backend endpoint. + format: int32 + maximum: 65535 + minimum: 0 + type: integer + required: + - address + - port + type: object + unix: + description: Unix defines the unix domain socket endpoint + properties: + path: + description: Path defines the unix domain socket path of + the backend endpoint. + type: string + required: + - path + type: object + type: object + x-kubernetes-validations: + - message: one of fqdn, ipv4 or unix must be specified + rule: (has(self.fqdn) || has(self.ipv4) || has(self.unix)) + - message: only one of fqdn, ipv4 or unix can be specified + rule: ((has(self.fqdn) && !(has(self.ipv4) || has(self.unix))) + || (has(self.ipv4) && !(has(self.fqdn) || has(self.unix))) || + (has(self.unix) && !(has(self.ipv4) || has(self.fqdn)))) + maxItems: 4 + minItems: 1 + type: array + x-kubernetes-validations: + - message: fqdn addresses cannot be mixed with other address types + rule: self.all(f, has(f.fqdn)) || !self.exists(f, has(f.fqdn)) + type: object + status: + description: Status defines the current status of Backend. + properties: + conditions: + description: Conditions describe the current conditions of the Backend. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/site/content/en/contributions/design/backend.md b/site/content/en/contributions/design/backend.md new file mode 100644 index 00000000000..32596bffd32 --- /dev/null +++ b/site/content/en/contributions/design/backend.md @@ -0,0 +1,162 @@ +--- +title: "Backend" +--- + +## Overview + +This design document introduces the `Backend` API allowing system administrators to represent backends without the use +of a K8s `Service` resource. + +Common use cases for non-Service backends in the K8s and Envoy ecosystem include: +- Cluster-external endpoints, which are currently second-class citizens in Gateway-API + (supported using [Services and FQDN endpoints][]). +- Host-local endpoints, such as sidecars or daemons that listen on [unix domain sockets][] or envoy [internal listeners][], + that cannot be represented by a K8s service at all. + +Several projects currently support backends that are not registered in the infrastructure-specific service registry. +- K8s Ingress: [Resource Backends][] +- Istio: [Service Entry][] +- Gloo Edge: [Upstream][] +- Consul: [External Services][] + +## Goals +* Add an API definition to hold settings for configuring Unix Domain Socket, FQDN and IP. +* Determine which resources may reference the new backend resource. +* Determine which existing Gateway-API and Envoy Gateway policies may attach to the new backend resource. + +## Non Goals +* Support specific backend types, such as S3 Bucket, Redis, AMQP, InfluxDB, etc. + +## Implementation + +The `Backend` resource is an implementation-specific Gateway-API [BackendObjectReference Extension][]. + +### Example +Here is an example highlighting how a user can configure a route that forwards traffic to both a K8s Service and a Backend +that has both unix domain socket and ipv4 endpoints. A [BackendTLSPolicy][] is attached to the backend resource, enabling TLS. + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: backend +spec: + ports: + - name: http + port: 3000 + targetPort: 3000 + selector: + app: backend +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: Backend +metadata: + name: backend-mixed-ip-uds +spec: + appProtocols: + - gateway.envoyproxy.io/h2c + endpoints: + - unix: + path: /var/run/backend.sock + - ipv4: + address: 10.244.0.28 + port: 3000 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backend +spec: + parentRefs: + - name: eg + hostnames: + - "www.example.com" + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-mixed-ip-uds + weight: 1 + - group: "" + kind: Service + name: backend + port: 3000 + weight: 1 + matches: + - path: + type: PathPrefix + value: / +--- +apiVersion: gateway.networking.k8s.io/v1alpha2 +kind: BackendTLSPolicy +metadata: + name: policy-btls +spec: + targetRef: + group: gateway.envoyproxy.io + kind: Backend + name: backend-mixed-ip-uds + tls: + caCertRefs: + - name: backend-tls-checks-certificate + group: '' + kind: ConfigMap + hostname: example.com +``` + +## Design Decisions +* All instances of `BackendObjectReference` in Envoy Gateway MAY support referencing the `Backend` kind. +* For security reasons, Envoy Gateway MUST reject references to a `Backend` in xRoute resources. For example, UDS and + localhost references will not be supported for xRoutes. +* All attributes of the Envoy Gateway extended `BackendRef` resource MUST be implemented for the `Backend` resource. +* A `Backend` resource referenced by `BackendObjectReference` will be translated to Envoy Gateway's IR DestinationSetting. + As such, all `BackendAdresses` are treated as equivalent endpoints with identical weights, TLS settings, etc. +* Gateway-API and Envoy Gateway policies that attach to Services ([BackendTLSPolicy][], [BackendLBPolicy][]) + MUST support attachment to the `Backend` resource in Envoy Gateway. +* Policy attachment to a named section of the `Backend` resource is not supported at this time. Currently, + `BackendObjectReference` can only select ports, and not generic section names. Hence, a named section of `Backend` + cannot be referenced by routes, and so attachment of policies to named sections will create translation ambiguity. + Users that wish to attach policies to some of the `BackendAddresses` in a `Backend` resource can use multiple `Backend` + resources and pluralized `BackendRefs` instead. +* The `Backend` API SHOULD support other Gateway-API backend features, such as [Backend Protocol Selection][]. + Translation of explicit upstream application protocol setting SHOULD be consistent with the existing implementation for + `Service` resources. +* The `Backend` upstream transport protocol (TCP, UDP) is inferred from the xRoute kind: TCP is inferred for all routes + except for `UDPRoute` which is resolved to UDP. +* This API resource MUST be part of same namespace as the targetRef resource. The `Backend` API MUST be subject to + the same cross-namespace reference restriction as referenced `Service` resources. +* The `Backend` resource translation MUST NOT modify Infrastructure. Any change to infrastructure that is required to + achieve connectivity to a backend (mounting a socket, adding a sidecar container, modifying a network policy, ...) + MUST be implemented with an appropriate infrastructure patch in the [EnvoyProxy][] API. +* To limit the overall maintenance effort related to supporting of non-Service backends, the `Backend` API SHOULD + support multiple generic address types (UDS, FQDN, IPv4, IPv6), and MUST NOT support vendor-specific backend types. +* Both `Backend` and `Service` resources may appear in the same `BackendRefs` list. +* The Optional `Port` field SHOULD NOT be evaluated when referencing a `Backend`. +* Referenced `Backend` resources MUST be translated to envoy endpoints, similar to the current `Service` translation. +* Certain combinations of `Backend` and `Service` are incompatible. For example, a Unix Domain Socket and a FQDN service + require different cluster service discovery types (Static/EDS and Strict-DNS accordingly). +* If a Backend that is referenced by a route cannot be translated, the `Route` resource will have an `Accepted=False` + condition with a `UnsupportedValue` reason. +* This API needs to be explicitly enabled using the [EnvoyGateway][] API + +## Alternatives +* The project can indefinitely wait for these configuration parameters to be part of the [Gateway API][]. +* Users can leverage the existing [Envoy Patch Policy][] or [Envoy Extension Manager][] to inject custom envoy clusters + and route configuration. However, these features require a high level of envoy expertise, investment and maintenance. + +[BackendObjectReference Extension]: https://gateway-api.sigs.k8s.io/guides/migrating-from-ingress/?h=extensi#approach-to-extensibility +[internal listeners]: https://www.envoyproxy.io/docs/envoy/latest/configuration/other_features/internal_listener +[unix domain sockets]: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#envoy-v3-api-msg-config-core-v3-pipe +[Resource Backends]: https://kubernetes.io/docs/concepts/services-networking/ingress/#resource-backend +[Services and FQDN endpoints]: ./../../latest/tasks/traffic/routing-outside-kubernetes.md +[Service Entry]: https://istio.io/latest/docs/reference/config/networking/service-entry/ +[Upstream]: https://docs.solo.io/gloo-edge/1.7.23/reference/api/github.com/solo-io/gloo/projects/gloo/api/v1/upstream.proto.sk/ +[External Services]: https://developer.hashicorp.com/consul/tutorials/developer-mesh/terminating-gateways-connect-external-services +[BackendTLSPolicy]: https://gateway-api.sigs.k8s.io/geps/gep-1897/ +[BackendLBPolicy]: https://gateway-api.sigs.k8s.io/geps/gep-1619/ +[Backend Protocol Selection]: https://gateway-api.sigs.k8s.io/geps/gep-1911/ +[EnvoyProxy]:../../latest/api/extension_types#envoyproxy +[EnvoyGateway]: ../../latest/api/extension_types#envoygateway +[Gateway API]: https://gateway-api.sigs.k8s.io/ +[Envoy Patch Policy]: ../../latest/api/extension_types#envoypatchpolicy +[Envoy Extension Manager]: ./extending-envoy-gateway diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index cde865d9ab2..716ad0bc78d 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -14,6 +14,8 @@ API group. ### Resource Types +- [Backend](#backend) +- [BackendList](#backendlist) - [BackendTrafficPolicy](#backendtrafficpolicy) - [BackendTrafficPolicyList](#backendtrafficpolicylist) - [ClientTrafficPolicy](#clienttrafficpolicy) @@ -171,6 +173,22 @@ _Appears in:_ | `TCP` | ActiveHealthCheckerTypeTCP defines the TCP type of health checking.
| +#### AppProtocolType + +_Underlying type:_ _string_ + +AppProtocolType defines various backend applications protocols supported by Envoy Gateway + +_Appears in:_ +- [BackendSpec](#backendspec) + +| Value | Description | +| ----- | ----------- | +| `gateway.envoyproxy.io/h2c` | AppProtocolTypeH2C defines the HTTP/2 application protocol.
| +| `gateway.envoyproxy.io/ws` | AppProtocolTypeWS defines the WebSocket over HTTP protocol.
| +| `gateway.envoyproxy.io/wss` | AppProtocolTypeWSS defines the WebSocket over HTTPS protocol.
| + + #### Authorization @@ -201,6 +219,61 @@ _Appears in:_ | `maxInterval` | _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#duration-v1-meta)_ | false | MaxInterval is the maximum interval between retries. This parameter is optional, but must be greater than or equal to the base_interval if set.
The default is 10 times the base_interval | +#### Backend + + + +Backend allows the user to configure the endpoints of a backend and +the behavior of the connection from Envoy Proxy to the backend. + +_Appears in:_ +- [BackendList](#backendlist) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `apiVersion` | _string_ | |`gateway.envoyproxy.io/v1alpha1` +| `kind` | _string_ | |`Backend` +| `metadata` | _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#objectmeta-v1-meta)_ | true | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` | _[BackendSpec](#backendspec)_ | true | Spec defines the desired state of Backend. | + + + + + + +#### BackendEndpoint + + + +BackendEndpoint describes a backend endpoint, which can be either a fully-qualified domain name, IPv4 address or unix domain socket +corresponding to Envoy's Address: https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-address + +_Appears in:_ +- [BackendSpec](#backendspec) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `fqdn` | _[FQDNEndpoint](#fqdnendpoint)_ | false | FQDN defines a FQDN endpoint | +| `ipv4` | _[IPv4Endpoint](#ipv4endpoint)_ | false | IPv4 defines an IPv4 endpoint | +| `unix` | _[UnixSocket](#unixsocket)_ | false | Unix defines the unix domain socket endpoint | + + +#### BackendList + + + +BackendList contains a list of Backend resources. + + + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `apiVersion` | _string_ | |`gateway.envoyproxy.io/v1alpha1` +| `kind` | _string_ | |`BackendList` +| `metadata` | _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#listmeta-v1-meta)_ | true | Refer to Kubernetes API documentation for fields of `metadata`. | +| `items` | _[Backend](#backend) array_ | true | | + + #### BackendRef @@ -223,6 +296,23 @@ _Appears in:_ | `port` | _[PortNumber](#portnumber)_ | false | Port specifies the destination port number to use for this resource.
Port is required when the referent is a Kubernetes Service. In this
case, the port number is the service port number, not the target port.
For other resources, destination port might be derived from the referent
resource or this field. | +#### BackendSpec + + + +BackendSpec describes the desired state of BackendSpec. + +_Appears in:_ +- [Backend](#backend) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `endpoints` | _[BackendEndpoint](#backendendpoint) array_ | true | Endpoints defines the endpoints to be used when connecting to the backend. | +| `appProtocols` | _[AppProtocolType](#appprotocoltype) array_ | false | AppProtocols defines the application protocols to be supported when connecting to the backend. | + + + + #### BackendTLSConfig @@ -1280,6 +1370,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `enableEnvoyPatchPolicy` | _boolean_ | true | EnableEnvoyPatchPolicy enables Envoy Gateway to
reconcile and implement the EnvoyPatchPolicy resources. | +| `enableBackend` | _boolean_ | true | EnableBackend enables Envoy Gateway to
reconcile and implement the Backend resources. | #### ExtensionHooks @@ -1344,6 +1435,22 @@ _Appears in:_ | `certificateRef` | _[SecretObjectReference](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1.SecretObjectReference)_ | true | CertificateRef contains a references to objects (Kubernetes objects or otherwise) that
contains a TLS certificate and private keys. These certificates are used to
establish a TLS handshake to the extension server.

CertificateRef can only reference a Kubernetes Secret at this time. | +#### FQDNEndpoint + + + +FQDNEndpoint describes TCP/UDP socket address, corresponding to Envoy's Socket Address +https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-socketaddress + +_Appears in:_ +- [BackendEndpoint](#backendendpoint) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `hostname` | _string_ | true | Hostname defines the FQDN hostname of the backend endpoint. | +| `port` | _integer_ | true | Port defines the port of the backend endpoint. | + + #### FaultInjection @@ -1707,6 +1814,22 @@ _Appears in:_ | `passive` | _[PassiveHealthCheck](#passivehealthcheck)_ | false | Passive passive check configuration | +#### IPv4Endpoint + + + +IPv4Endpoint describes TCP/UDP socket address, corresponding to Envoy's Socket Address +https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-socketaddress + +_Appears in:_ +- [BackendEndpoint](#backendendpoint) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `address` | _string_ | true | Address defines the IPv4 address of the backend endpoint. | +| `port` | _integer_ | true | Port defines the port of the backend endpoint. | + + #### ImageWasmCodeSource @@ -3283,6 +3406,21 @@ _Appears in:_ | `unavailable` | The gRPC status code in the response headers is “unavailable”.
| +#### UnixSocket + + + +UnixSocket describes TCP/UDP unix domain socket address, corresponding to Envoy's Pipe +https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/address.proto#config-core-v3-pipe + +_Appears in:_ +- [BackendEndpoint](#backendendpoint) + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `path` | _string_ | true | Path defines the unix domain socket path of the backend endpoint. | + + #### Wasm diff --git a/test/cel-validation/backend_test.go b/test/cel-validation/backend_test.go new file mode 100644 index 00000000000..ab8efd7e629 --- /dev/null +++ b/test/cel-validation/backend_test.go @@ -0,0 +1,266 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +//go:build celvalidation +// +build celvalidation + +package celvalidation + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" +) + +func TestBackend(t *testing.T) { + ctx := context.Background() + baseBackend := egv1a1.Backend{ + ObjectMeta: metav1.ObjectMeta{ + Name: "backend", + Namespace: metav1.NamespaceDefault, + }, + Spec: egv1a1.BackendSpec{}, + } + + cases := []struct { + desc string + mutate func(backend *egv1a1.Backend) + mutateStatus func(backend *egv1a1.Backend) + wantErrors []string + }{ + { + desc: "Valid static", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + Unix: &egv1a1.UnixSocket{ + Path: "/path/to/service.sock", + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "1.1.1.1", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "Valid DNS", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example.com", + Port: 443, + }, + }, + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example2.com", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{}, + }, + { + desc: "unsupported application protocol type", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{"HTTP7"}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example.com", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{"spec.appProtocols[0]: Unsupported value: \"HTTP7\": supported values: \"gateway.envoyproxy.io/h2c\", \"gateway.envoyproxy.io/ws\", \"gateway.envoyproxy.io/wss\""}, + }, + { + desc: "No address", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{{}}, + } + }, + wantErrors: []string{"spec.endpoints[0]: Invalid value: \"object\": one of fqdn, ipv4 or unix must be specified"}, + }, + { + desc: "Multiple addresses", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example.com", + Port: 443, + }, + Unix: &egv1a1.UnixSocket{ + Path: "/path/to/service.sock", + }, + }, + }, + } + }, + wantErrors: []string{"spec.endpoints[0]: Invalid value: \"object\": only one of fqdn, ipv4 or unix can be specified"}, + }, + { + desc: "Mixed types", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "example.com", + Port: 443, + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "1.1.1.1", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{"spec.endpoints: Invalid value: \"array\": FQDN addresses cannot be mixed with other address types"}, + }, + { + desc: "Invalid hostname", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "host name", + Port: 443, + }, + }, + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "host_name", + Port: 443, + }, + }, + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "hostname:443", + Port: 443, + }, + }, + { + FQDN: &egv1a1.FQDNEndpoint{ + Hostname: "host.*.name", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{ + "spec.endpoints[0].fqdn.hostname: Invalid value: \"host name\": spec.endpoints[0].fqdn.hostname in body should match '^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'", + "spec.endpoints[1].fqdn.hostname: Invalid value: \"host_name\": spec.endpoints[1].fqdn.hostname in body should match '^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'", + "spec.endpoints[2].fqdn.hostname: Invalid value: \"hostname:443\": spec.endpoints[2].fqdn.hostname in body should match '^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'", + "spec.endpoints[3].fqdn.hostname: Invalid value: \"host.*.name\": spec.endpoints[3].fqdn.hostname in body should match '^(\\*\\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'", + }, + }, + { + desc: "Invalid IPv4", + mutate: func(backend *egv1a1.Backend) { + backend.Spec = egv1a1.BackendSpec{ + AppProtocols: []egv1a1.AppProtocolType{egv1a1.AppProtocolTypeH2C}, + Endpoints: []egv1a1.BackendEndpoint{ + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "300.0.0.0", + Port: 443, + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "0.0.0.0:443", + Port: 443, + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "0.0.0.0/12", + Port: 443, + }, + }, + { + IPv4: &egv1a1.IPv4Endpoint{ + Address: "a.b.c.e", + Port: 443, + }, + }, + }, + } + }, + wantErrors: []string{ + "spec.endpoints[0].ipv4.address: Invalid value: \"300.0.0.0\": spec.endpoints[0].ipv4.address in body should match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'", + "spec.endpoints[1].ipv4.address: Invalid value: \"0.0.0.0:443\": spec.endpoints[1].ipv4.address in body should match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'", + "spec.endpoints[2].ipv4.address: Invalid value: \"0.0.0.0/12\": spec.endpoints[2].ipv4.address in body should match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'", + "spec.endpoints[3].ipv4.address: Invalid value: \"a.b.c.e\": spec.endpoints[3].ipv4.address in body should match '^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'", + }, + }, + } + + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + backend := baseBackend.DeepCopy() + backend.Name = fmt.Sprintf("backend-%v", time.Now().UnixNano()) + + if tc.mutate != nil { + tc.mutate(backend) + } + err := c.Create(ctx, backend) + + if tc.mutateStatus != nil { + tc.mutateStatus(backend) + err = c.Status().Update(ctx, backend) + } + + if (len(tc.wantErrors) != 0) != (err != nil) { + t.Fatalf("Unexpected response while creating Backend; got err=\n%v\n;want error=%v", err, tc.wantErrors) + } + + var missingErrorStrings []string + for _, wantError := range tc.wantErrors { + if !strings.Contains(strings.ToLower(err.Error()), strings.ToLower(wantError)) { + missingErrorStrings = append(missingErrorStrings, wantError) + } + } + if len(missingErrorStrings) != 0 { + t.Errorf("Unexpected response while creating Backend; got err=\n%v\n;missing strings within error=%q", err, missingErrorStrings) + } + }) + } +}