diff --git a/apis/v1alpha1/gateway_types.go b/apis/v1alpha1/gateway_types.go index a09d38ba4b..10135f9f24 100644 --- a/apis/v1alpha1/gateway_types.go +++ b/apis/v1alpha1/gateway_types.go @@ -298,15 +298,6 @@ type TLSOverridePolicy struct { } // GatewayTLSConfig describes a TLS configuration. -// -// References: -// -// - nginx: https://nginx.org/en/docs/http/configuring_https_servers.html -// - envoy: https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/auth/cert.proto -// - haproxy: https://www.haproxy.com/documentation/aloha/9-5/traffic-management/lb-layer7/tls/ -// - gcp: https://cloud.google.com/load-balancing/docs/use-ssl-policies#creating_an_ssl_policy_with_a_custom_profile -// - aws: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html#describe-ssl-policies -// - azure: https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-bindings#enforce-tls-1112 type GatewayTLSConfig struct { // Mode defines the TLS behavior for the TLS session initiated by the client. // There are two possible modes: diff --git a/apis/v1alpha1/shared_types.go b/apis/v1alpha1/shared_types.go index 458145fa57..ca9d0b6a5f 100644 --- a/apis/v1alpha1/shared_types.go +++ b/apis/v1alpha1/shared_types.go @@ -169,10 +169,21 @@ type RouteForwardTo struct { // RouteConditionType is a type of condition for a route. type RouteConditionType string +// RouteConditionReason is a reason for a route condition. +type RouteConditionReason string + const ( // This condition indicates whether the route has been admitted - // or rejected by a Gateway, and why. + // or refused by a Gateway. ConditionRouteAdmitted RouteConditionType = "Admitted" + + // This reason is used with the "Admitted" condition when the Route has been + // admitted by the Gateway. + RouteReasonAdmitted RouteConditionReason = "Admitted" + + // This reason is used with the "Admitted" condition when the Route has been + // refused by the Gateway. + RouteReasonRefused RouteConditionReason = "Refused" ) // RouteGatewayStatus describes the status of a route with respect to an diff --git a/apis/v1alpha1/validation/validation.go b/apis/v1alpha1/validation/validation.go index 30ec53f0f8..4d1315c2d6 100644 --- a/apis/v1alpha1/validation/validation.go +++ b/apis/v1alpha1/validation/validation.go @@ -122,3 +122,18 @@ func validateHTTPRouteUniqueFilters(rules []gatewayv1a1.HTTPRouteRule, path *fie return errs } + +// ValidateGatewayClassUpdate validates an update to oldClass according to the +// Gateway API specification. For additional details of the GatewayClass spec, refer to: +// https://gateway-api.sigs.k8s.io/spec/#networking.x-k8s.io/v1alpha2.GatewayClass +func ValidateGatewayClassUpdate(oldClass, newClass *gatewayv1a1.GatewayClass) field.ErrorList { + if oldClass == nil || newClass == nil { + return nil + } + var errs field.ErrorList + if oldClass.Spec.Controller != newClass.Spec.Controller { + errs = append(errs, field.Invalid(field.NewPath("spec.controller"), newClass.Spec.Controller, + "cannot update an immutable field")) + } + return errs +} diff --git a/apis/v1alpha1/validation/validation_test.go b/apis/v1alpha1/validation/validation_test.go index f9e2475bfc..88060c6ba4 100644 --- a/apis/v1alpha1/validation/validation_test.go +++ b/apis/v1alpha1/validation/validation_test.go @@ -17,11 +17,13 @@ limitations under the License. package validation import ( + "reflect" "testing" gatewayv1a1 "sigs.k8s.io/gateway-api/apis/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" utilpointer "k8s.io/utils/pointer" ) @@ -424,3 +426,76 @@ func portNumberPtr(p int) *gatewayv1a1.PortNumber { result := gatewayv1a1.PortNumber(p) return &result } + +func TestValidateGatewayClassUpdate(t *testing.T) { + type args struct { + oldClass *gatewayv1a1.GatewayClass + newClass *gatewayv1a1.GatewayClass + } + tests := []struct { + name string + args args + want field.ErrorList + }{ + { + name: "changing parameters reference is allowed", + args: args{ + oldClass: &gatewayv1a1.GatewayClass{ + Spec: gatewayv1a1.GatewayClassSpec{ + Controller: "foo", + }, + }, + newClass: &gatewayv1a1.GatewayClass{ + Spec: gatewayv1a1.GatewayClassSpec{ + Controller: "foo", + ParametersRef: &gatewayv1a1.ParametersReference{ + Group: "example.com", + Kind: "GatewayClassConfig", + Name: "foo", + }, + }, + }, + }, + want: nil, + }, + { + name: "changing controller field results in an error", + args: args{ + oldClass: &gatewayv1a1.GatewayClass{ + Spec: gatewayv1a1.GatewayClassSpec{ + Controller: "foo", + }, + }, + newClass: &gatewayv1a1.GatewayClass{ + Spec: gatewayv1a1.GatewayClassSpec{ + Controller: "bar", + }, + }, + }, + want: field.ErrorList{ + { + Type: field.ErrorTypeInvalid, + Field: "spec.controller", + Detail: "cannot update an immutable field", + BadValue: "bar", + }, + }, + }, + { + name: "nil input result in no errors", + args: args{ + oldClass: nil, + newClass: nil, + }, + want: nil, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + if got := ValidateGatewayClassUpdate(tt.args.oldClass, tt.args.newClass); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ValidateGatewayClassUpdate() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/apis/v1alpha1/zz_generated.deepcopy.go b/apis/v1alpha1/zz_generated.deepcopy.go index 64399d898f..81696264ad 100644 --- a/apis/v1alpha1/zz_generated.deepcopy.go +++ b/apis/v1alpha1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* diff --git a/apis/v1alpha2/gateway_types.go b/apis/v1alpha2/gateway_types.go index 11cf42387d..45cfc61d68 100644 --- a/apis/v1alpha2/gateway_types.go +++ b/apis/v1alpha2/gateway_types.go @@ -41,7 +41,7 @@ type Gateway struct { // Status defines the current state of Gateway. // - // +kubebuilder:default={conditions: {{type: "Scheduled", status: "False", reason:"NotReconciled", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}}} + // +kubebuilder:default={conditions: {{type: "Scheduled", status: "Unknown", reason:"NotReconciled", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}}} Status GatewayStatus `json:"status,omitempty"` } @@ -72,6 +72,9 @@ type GatewaySpec struct { // logical endpoints that are bound on this Gateway's addresses. // At least one Listener MUST be specified. // + // Each listener in a Gateway must have a unique combination of Hostname, + // Port, and Protocol. + // // An implementation MAY group Listeners by Port and then collapse each // group of Listeners into a single Listener if the implementation // determines that the Listeners in the group are "compatible". An @@ -142,10 +145,8 @@ type GatewaySpec struct { Addresses []GatewayAddress `json:"addresses,omitempty"` } -// Listener embodies the concept of a logical endpoint where a Gateway can -// accept network connections. Each listener in a Gateway must have a unique -// combination of Hostname, Port, and Protocol. This will be enforced by a -// validating webhook. +// Listener embodies the concept of a logical endpoint where a Gateway accepts +// network connections. type Listener struct { // Name is the name of the Listener. // @@ -157,10 +158,20 @@ type Listener struct { // field is ignored for protocols that don't require hostname based // matching. // + // Implementations MUST apply Hostname matching appropriately for each of + // the following protocols: + // + // * TLS: The Listener Hostname MUST match the SNI. + // * HTTP: The Listener Hostname MUST match the Host header of the request. + // * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP + // protocol layers as described above. If an implementation does not + // ensure that both the SNI and Host header match the Listener hostname, + // it MUST clearly document that. + // // For HTTPRoute and TLSRoute resources, there is an interaction with the // `spec.hostnames` array. When both listener and route specify hostnames, - // there must be an intersection between the values for a Route to be - // admitted. For more information, refer to the Route specific Hostnames + // there MUST be an intersection between the values for a Route to be + // accepted. For more information, refer to the Route specific Hostnames // documentation. // // Support: Core @@ -175,18 +186,6 @@ type Listener struct { Port PortNumber `json:"port"` // Protocol specifies the network protocol this listener expects to receive. - // The GatewayClass MUST apply the Hostname match appropriately for each - // protocol: - // - // * For the "TLS" protocol, the Hostname match MUST be - // applied to the [SNI](https://tools.ietf.org/html/rfc6066#section-3) - // server name offered by the client. - // * For the "HTTP" protocol, the Hostname match MUST be - // applied to the host portion of the - // [effective request URI](https://tools.ietf.org/html/rfc7230#section-5.5) - // or the [:authority pseudo-header](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) - // * For the "HTTPS" protocol, the Hostname match MUST be - // applied at both the TLS and HTTP protocol layers. // // Support: Core Protocol ProtocolType `json:"protocol"` @@ -251,7 +250,10 @@ type Listener struct { type ProtocolType string const ( - // Accepts cleartext HTTP/1.1 sessions over TCP. + // Accepts cleartext HTTP/1.1 sessions over TCP. Implementations MAY also + // support HTTP/2 over cleartext. If implementations support HTTP/2 over + // cleartext on "HTTP" listeners, that MUST be clearly documented by the + // implementation. HTTPProtocolType ProtocolType = "HTTP" // Accepts HTTP/1.1 or HTTP/2 sessions over TLS. @@ -271,13 +273,14 @@ const ( type GatewayTLSConfig struct { // Mode defines the TLS behavior for the TLS session initiated by the client. // There are two possible modes: + // // - Terminate: The TLS session between the downstream client // and the Gateway is terminated at the Gateway. This mode requires - // certificateRef to be set. + // certificateRefs to be set and contain at least one element. // - Passthrough: The TLS session is NOT terminated by the Gateway. This // implies that the Gateway can't decipher the TLS stream except for // the ClientHello message of the TLS protocol. - // CertificateRef field is ignored in this mode. + // CertificateRefs field is ignored in this mode. // // Support: Core // @@ -285,10 +288,14 @@ type GatewayTLSConfig struct { // +kubebuilder:default=Terminate Mode *TLSModeType `json:"mode,omitempty"` - // CertificateRef is a reference to a Kubernetes object that contains a TLS - // certificate and private key. This certificate is used to establish a TLS - // handshake for requests that match the hostname of the associated - // listener. + // CertificateRefs contains a series of references to Kubernetes objects that + // contains TLS certificates and private keys. These certificates are used to + // establish a TLS handshake for requests that match the hostname of the + // associated listener. + // + // A single CertificateRef to a Kubernetes Secret has "Core" support. + // Implementations MAY choose to support attaching multiple certificates to + // a Listener, but this behavior is implementation-specific. // // References to a resource in different namespace are invalid UNLESS there // is a ReferencePolicy in the target namespace that allows the certificate @@ -296,18 +303,19 @@ type GatewayTLSConfig struct { // "ResolvedRefs" condition MUST be set to False for this listener with the // "InvalidCertificateRef" reason. // - // This field is required when mode is set to "Terminate" (default) and - // optional otherwise. + // This field is required to have at least one element when the mode is set + // to "Terminate" (default) and is optional otherwise. // - // CertificateRef can reference a standard Kubernetes resource, i.e. Secret, - // or an implementation-specific custom resource. + // CertificateRefs can reference to standard Kubernetes resources, i.e. + // Secret, or implementation-specific custom resources. // - // Support: Core (Kubernetes Secrets) + // Support: Core - A single reference to a Kubernetes Secret // - // Support: Implementation-specific (Other resource types) + // Support: Implementation-specific (More than one reference or other resource types) // // +optional - CertificateRef *SecretObjectReference `json:"certificateRef,omitempty"` + // +kubebuilder:validation:MaxItems=64 + CertificateRefs []*SecretObjectReference `json:"certificateRefs,omitempty"` // Options are a list of key/value pairs to give extended options // to the provider. @@ -333,9 +341,12 @@ const ( // In this mode, TLS session between the downstream client // and the Gateway is terminated at the Gateway. TLSModeTerminate TLSModeType = "Terminate" + // In this mode, the TLS session is NOT terminated by the Gateway. This // implies that the Gateway can't decipher the TLS stream except for // the ClientHello message of the TLS protocol. + // + // Note that SSL passthrough is only supported by TLSRoute. TLSModePassthrough TLSModeType = "Passthrough" ) @@ -425,8 +436,6 @@ type RouteGroupKind struct { type GatewayAddress struct { // Type of the address. // - // Support: Extended - // // +optional // +kubebuilder:default=IPAddress Type *AddressType `json:"type,omitempty"` @@ -502,7 +511,7 @@ type GatewayStatus struct { // +listType=map // +listMapKey=type // +kubebuilder:validation:MaxItems=8 - // +kubebuilder:default={{type: "Scheduled", status: "False", reason:"NotReconciled", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}} + // +kubebuilder:default={{type: "Scheduled", status: "Unknown", reason:"NotReconciled", message:"Waiting for controller", lastTransitionTime: "1970-01-01T00:00:00Z"}} Conditions []metav1.Condition `json:"conditions,omitempty"` // Listeners provide status for each unique listener port defined in the Spec. @@ -595,12 +604,11 @@ const ( // more Listeners are not ready to serve traffic. GatewayReasonListenersNotReady GatewayConditionReason = "ListenersNotReady" - // This reason is used with the "Ready" condition when the requested - // address has not been assigned to the Gateway. This reason - // can be used to express a range of circumstances, including - // (but not limited to) IPAM address exhaustion, invalid - // or unsupported address requests, or a named address not - // being found. + // This reason is used with the "Ready" condition when none of the requested + // addresses have been assigned to the Gateway. This reason can be used to + // express a range of circumstances, including (but not limited to) IPAM + // address exhaustion, invalid or unsupported address requests, or a named + // address not being found. GatewayReasonAddressNotAssigned GatewayConditionReason = "AddressNotAssigned" ) @@ -610,17 +618,14 @@ type ListenerStatus struct { Name SectionName `json:"name"` // SupportedKinds is the list indicating the Kinds supported by this - // listener. When this is not specified on the Listener, this MUST represent - // the kinds an implementation supports for the specified protocol. When - // there are kinds specified on the Listener, this MUST represent the - // intersection of those kinds and the kinds supported by the implementation - // for the specified protocol. - // - // If kinds are specified in Spec that are not supported, an implementation - // MUST set the "ResolvedRefs" condition to "False" with the - // "InvalidRouteKinds" reason. If both valid and invalid Route kinds are - // specified, the implementation should support the valid Route kinds that - // have been specified. + // listener. This MUST represent the kinds an implementation supports for + // that Listener configuration. + // + // If kinds are specified in Spec that are not supported, they MUST NOT + // appear in this list and an implementation MUST set the "ResolvedRefs" + // condition to "False" with the "InvalidRouteKinds" reason. If both valid + // and invalid Route kinds are specified, the implementation MUST + // reference the valid Route kinds that have been specified. // // +kubebuilder:validation:MaxItems=8 SupportedKinds []RouteGroupKind `json:"supportedKinds"` @@ -717,8 +722,12 @@ const ( // interoperability. ListenerConditionDetached ListenerConditionType = "Detached" - // This reason is used with the "Detached" condition when the - // Listener requests a port that cannot be used on the Gateway. + // This reason is used with the "Detached" condition when the Listener + // requests a port that cannot be used on the Gateway. This reason could be + // used in a number of instances, including: + // + // * The port is already in use. + // * The port is not supported by the implementation. ListenerReasonPortUnavailable ListenerConditionReason = "PortUnavailable" // This reason is used with the "Detached" condition when the @@ -732,9 +741,12 @@ const ( // protocol type is not supported. ListenerReasonUnsupportedProtocol ListenerConditionReason = "UnsupportedProtocol" - // This reason is used with the "Detached" condition when - // the Listener could not be attached to the Gateway because the - // requested address is not supported. + // This reason is used with the "Detached" condition when the Listener could + // not be attached to the Gateway because the requested address is not + // supported. This reason could be used in a number of instances, including: + // + // * The address is already in use. + // * The type of address is not supported by the implementation. ListenerReasonUnsupportedAddress ListenerConditionReason = "UnsupportedAddress" // This reason is used with the "Detached" condition when the condition is @@ -766,7 +778,7 @@ const ( ListenerReasonResolvedRefs ListenerConditionReason = "ResolvedRefs" // This reason is used with the "ResolvedRefs" condition when the - // Listener has a TLS configuration with a TLS CertificateRef + // Listener has a TLS configuration with at least one TLS CertificateRef // that is invalid or cannot be resolved. ListenerReasonInvalidCertificateRef ListenerConditionReason = "InvalidCertificateRef" diff --git a/apis/v1alpha2/gatewayclass_types.go b/apis/v1alpha2/gatewayclass_types.go index 8a77e854c1..829de318cc 100644 --- a/apis/v1alpha2/gatewayclass_types.go +++ b/apis/v1alpha2/gatewayclass_types.go @@ -56,7 +56,7 @@ type GatewayClass struct { // Status defines the current state of GatewayClass. // - // +kubebuilder:default={conditions: {{type: "Admitted", status: "False", message: "Waiting for controller", reason: "Waiting", lastTransitionTime: "1970-01-01T00:00:00Z"}}} + // +kubebuilder:default={conditions: {{type: "Accepted", status: "Unknown", message: "Waiting for controller", reason: "Waiting", lastTransitionTime: "1970-01-01T00:00:00Z"}}} Status GatewayClassStatus `json:"status,omitempty"` } @@ -69,19 +69,15 @@ const ( // GatewayClassSpec reflects the configuration of a class of Gateways. type GatewayClassSpec struct { - // Controller is a domain/path string that indicates the - // controller that is managing Gateways of this class. + // ControllerName is the name of the controller that is managing Gateways of + // this class. The value of this field MUST be a domain prefixed path. // // Example: "example.net/gateway-controller". // // This field is not mutable and cannot be empty. // - // The format of this field is DOMAIN "/" PATH, where DOMAIN is a valid - // Kubernetes name (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names) - // and PATH is a valid HTTP path as defined by RFC 3986. - // // Support: Core - Controller GatewayController `json:"controller"` + ControllerName GatewayController `json:"controllerName"` // ParametersRef is a reference to a resource that contains the configuration // parameters corresponding to the GatewayClass. This is optional if the @@ -130,8 +126,8 @@ type ParametersReference struct { Scope *string `json:"scope,omitempty"` // Namespace is the namespace of the referent. - // This field is required when scope is set to "Namespace" and ignored when - // scope is set to "Cluster". + // This field is required when scope is set to "Namespace" and must be unset + // when scope is set to "Cluster". // // +optional Namespace *Namespace `json:"namespace,omitempty"` @@ -147,19 +143,19 @@ type GatewayClassConditionType string type GatewayClassConditionReason string const ( - // This condition indicates whether the GatewayClass has been admitted by + // This condition indicates whether the GatewayClass has been accepted by // the controller requested in the `spec.controller` field. // - // This condition defaults to False, and MUST be set by a controller when it - // sees a GatewayClass using its controller string. The status of this - // condition MUST be set to true if the controller will support provisioning + // This condition defaults to Unknown, and MUST be set by a controller when + // it sees a GatewayClass using its controller string. The status of this + // condition MUST be set to True if the controller will support provisioning // Gateways using this class. Otherwise, this status MUST be set to False. // If the status is set to False, the controller SHOULD set a Message and // Reason as an explanation. // // Possible reasons for this condition to be true are: // - // * "Admitted" + // * "Accepted" // // Possible reasons for this condition to be False are: // @@ -168,18 +164,18 @@ const ( // // Controllers should prefer to use the values of GatewayClassConditionReason // for the corresponding Reason, where appropriate. - GatewayClassConditionStatusAdmitted GatewayClassConditionType = "Admitted" + GatewayClassConditionStatusAccepted GatewayClassConditionType = "Accepted" - // This reason is used with the "Admitted" condition when the condition is + // This reason is used with the "Accepted" condition when the condition is // true. - GatewayClassReasonAdmitted GatewayClassConditionReason = "Admitted" + GatewayClassReasonAccepted GatewayClassConditionReason = "Accepted" - // This reason is used with the "Admitted" condition when the - // GatewayClass was not admitted because the parametersRef field + // This reason is used with the "Accepted" condition when the + // GatewayClass was not accepted because the parametersRef field // was invalid, with more detail in the message. GatewayClassReasonInvalidParameters GatewayClassConditionReason = "InvalidParameters" - // This reason is used with the "Admitted" condition when the + // This reason is used with the "Accepted" condition when the // requested controller has not yet made a decision about whether // to admit the GatewayClass. It is the default Reason on a new // GatewayClass. @@ -198,7 +194,7 @@ type GatewayClassStatus struct { // +listType=map // +listMapKey=type // +kubebuilder:validation:MaxItems=8 - // +kubebuilder:default={{type: "Admitted", status: "False", message: "Waiting for controller", reason: "Waiting", lastTransitionTime: "1970-01-01T00:00:00Z"}} + // +kubebuilder:default={{type: "Accepted", status: "Unknown", message: "Waiting for controller", reason: "Waiting", lastTransitionTime: "1970-01-01T00:00:00Z"}} Conditions []metav1.Condition `json:"conditions,omitempty"` } diff --git a/apis/v1alpha2/httproute_types.go b/apis/v1alpha2/httproute_types.go index 518b926430..c788921315 100644 --- a/apis/v1alpha2/httproute_types.go +++ b/apis/v1alpha2/httproute_types.go @@ -28,7 +28,10 @@ import ( // +kubebuilder:printcolumn:name="Hostnames",type=string,JSONPath=`.spec.hostnames` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` -// HTTPRoute is the Schema for the HTTPRoute resource. +// HTTPRoute provides a way to route HTTP requests. This includes the capability +// to match requests by hostname, path, header, or query param. Filters can be +// used to specify additional processing steps. Backends specify where matching +// requests should be routed. type HTTPRoute struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -53,14 +56,13 @@ type HTTPRouteList struct { type HTTPRouteSpec struct { CommonRouteSpec `json:",inline"` - // Hostnames defines a set of hostname that should match against - // the HTTP Host header to select a HTTPRoute to process the request. - // Hostname is the fully qualified domain name of a network host, - // as defined by RFC 3986. Note the following deviations from the - // "host" part of the URI as defined in the RFC: + // Hostnames defines a set of hostname that should match against the HTTP + // Host header to select a HTTPRoute to process the request. This matches + // the RFC 1123 definition of a hostname with 2 notable exceptions: // // 1. IPs are not allowed. - // 2. The `:` delimiter is not respected because ports are not allowed. + // 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + // label must appear by itself as the first label. // // If a hostname is specified by both the Listener and HTTPRoute, there // must be at least one intersecting hostname for the HTTPRoute to be @@ -69,12 +71,11 @@ type HTTPRouteSpec struct { // * A Listener with `test.example.com` as the hostname matches HTTPRoutes // that have either not specified any hostnames, or have specified at // least one of `test.example.com` or `*.example.com`. - // * A Listener with `*.example.com` as as the hostname matches HTTPRoutes + // * A Listener with `*.example.com` as the hostname matches HTTPRoutes // that have either not specified any hostnames or have specified at least // one hostname that matches the Listener hostname. For example, // `test.example.com` and `*.example.com` would both match. On the other - // hand, `a.b.example.com`, `example.com`, and `test.example.net` would - // not match. + // hand, `example.com` and `test.example.net` would not match. // // If both the Listener and HTTPRoute have specified hostnames, any // HTTPRoute hostnames that do not match the Listener hostname MUST be @@ -82,9 +83,10 @@ type HTTPRouteSpec struct { // HTTPRoute specified `test.example.com` and `test.example.net`, // `test.example.net` must not be considered for a match. // - // If hostnames do not match with the criteria above, then the HTTPRoute is - // not admitted, and the implementation must raise an 'Admitted' Condition - // with a status of `False` for the target Listener(s). + // If both the Listener and HTTPRoute have specified hostnames, and none + // match with the criteria above, then the HTTPRoute is not accepted. The + // implementation must raise an 'Accepted' Condition with a status of + // `False` in the corresponding RouteParentStatus. // // Support: Core // @@ -101,8 +103,8 @@ type HTTPRouteSpec struct { } // HTTPRouteRule defines semantics for matching an HTTP request based on -// conditions, optionally executing additional processing steps, and forwarding -// the request to an API object. +// conditions (matches), processing it (filters), and forwarding the request to +// an API object (backendRefs). type HTTPRouteRule struct { // Matches define conditions used for matching the rule against incoming // HTTP requests. Each match is independent, i.e. this rule will be matched @@ -115,16 +117,16 @@ type HTTPRouteRule struct { // - path: // value: "/foo" // headers: - // values: - // version: "2" + // - name: "version" + // value: "v2" // - path: // value: "/v2/foo" // ``` // - // For a request to match against this rule, a request should satisfy + // For a request to match against this rule, a request must satisfy // EITHER of the two conditions: // - // - path prefixed with `/foo` AND contains the header `version: "2"` + // - path prefixed with `/foo` AND contains the header `version: v2` // - path prefix of `/v2/foo` // // See the documentation for HTTPRouteMatch on how to specify multiple @@ -134,26 +136,23 @@ type HTTPRouteRule struct { // path match on "/", which has the effect of matching every // HTTP request. // + // Proxy or Load Balancer routing configuration generated from HTTPRoutes + // MUST prioritize rules based on the following criteria, continuing on + // ties. Precedence must be given to the the Rule with the largest number + // of: // - // Each client request MUST map to a maximum of one route rule. If a request - // matches multiple rules, matching precedence MUST be determined in order - // of the following criteria, continuing on ties: - // - // * The longest matching hostname. - // * The longest matching non-wildcard hostname. - // * The longest matching path. - // * The largest number of header matches. - // * The largest number of query param matches. + // * Characters in a matching non-wildcard hostname. + // * Characters in a matching hostname. + // * Characters in a matching path. + // * Header matches. + // * Query param matches. // // If ties still exist across multiple Routes, matching precedence MUST be // determined in order of the following criteria, continuing on ties: // - // * The oldest Route based on creation timestamp. For example, a Route with - // a creation timestamp of "2020-09-08 01:02:03" is given precedence over - // a Route with a creation timestamp of "2020-09-08 01:02:04". + // * The oldest Route based on creation timestamp. // * The Route appearing first in alphabetical order by - // "/". For example, foo/bar is given precedence over - // foo/baz. + // "/". // // If ties still exist within the Route that has been given precedence, // matching precedence MUST be granted to the first matching rule meeting @@ -177,7 +176,8 @@ type HTTPRouteRule struct { // - Implementation-specific custom filters have no API guarantees across // implementations. // - // Specifying a core filter multiple times has unspecified or custom conformance. + // Specifying a core filter multiple times has unspecified or custom + // conformance. // // Support: Core // @@ -211,24 +211,37 @@ type HTTPRouteRule struct { // * "Exact" // * "Prefix" // * "RegularExpression" -// * "ImplementationSpecific" // // Prefix and Exact paths must be syntactically valid: // -// - Must begin with the '/' character -// - Must not contain consecutive '/' characters (e.g. /foo///, //). -// - For prefix paths, a trailing '/' character in the Path is ignored, -// e.g. /abc and /abc/ specify the same match. +// - Must begin with the `/` character +// - Must not contain consecutive `/` characters (e.g. `/foo///`, `//`). // -// +kubebuilder:validation:Enum=Exact;Prefix;RegularExpression;ImplementationSpecific +// +kubebuilder:validation:Enum=Exact;Prefix;RegularExpression type PathMatchType string -// PathMatchType constants. const ( - PathMatchExact PathMatchType = "Exact" - PathMatchPrefix PathMatchType = "Prefix" - PathMatchRegularExpression PathMatchType = "RegularExpression" - PathMatchImplementationSpecific PathMatchType = "ImplementationSpecific" + // Matches the URL path exactly and with case sensitivity. + PathMatchExact PathMatchType = "Exact" + + // Matches based on a URL path prefix split by `/`. Matching is + // case sensitive and done on a path element by element basis. A + // path element refers to the list of labels in the path split by + // the `/` separator. A request is a match for path _p_ if every + // _p_ is an element-wise prefix of the request path. + // + // For example, `/abc`, `/abc/` and `/abc/def` match the prefix + // `/abc`, but `/abcd` does not. + PathMatchPrefix PathMatchType = "Prefix" + + // Matches if the URL path matches the given regular expression with + // case sensitivity. + // + // Since `"RegularExpression"` has custom conformance, implementations + // can support POSIX, PCRE, RE2 or any other regular expression dialect. + // Please read the implementation's documentation to determine the supported + // dialect. + PathMatchRegularExpression PathMatchType = "RegularExpression" ) // HTTPPathMatch describes how to select a HTTP route by matching the HTTP request path. @@ -237,12 +250,7 @@ type HTTPPathMatch struct { // // Support: Core (Exact, Prefix) // - // Support: Custom (RegularExpression, ImplementationSpecific) - // - // Since RegularExpression PathType has custom conformance, implementations - // can support POSIX, PCRE or any other dialects of regular expressions. - // Please read the implementation's documentation to determine the supported - // dialect. + // Support: Custom (RegularExpression) // // +optional // +kubebuilder:default=Prefix @@ -261,16 +269,14 @@ type HTTPPathMatch struct { // // * "Exact" // * "RegularExpression" -// * "ImplementationSpecific" // -// +kubebuilder:validation:Enum=Exact;RegularExpression;ImplementationSpecific +// +kubebuilder:validation:Enum=Exact;RegularExpression type HeaderMatchType string // HeaderMatchType constants. const ( - HeaderMatchExact HeaderMatchType = "Exact" - HeaderMatchRegularExpression HeaderMatchType = "RegularExpression" - HeaderMatchImplementationSpecific HeaderMatchType = "ImplementationSpecific" + HeaderMatchExact HeaderMatchType = "Exact" + HeaderMatchRegularExpression HeaderMatchType = "RegularExpression" ) // HTTPHeaderName is the name of an HTTP header. @@ -298,7 +304,7 @@ type HTTPHeaderMatch struct { // // Support: Core (Exact) // - // Support: Custom (RegularExpression, ImplementationSpecific) + // Support: Custom (RegularExpression) // // Since RegularExpression PathType has custom conformance, implementations // can support POSIX, PCRE or any other dialects of regular expressions. @@ -337,16 +343,14 @@ type HTTPHeaderMatch struct { // // * "Exact" // * "RegularExpression" -// * "ImplementationSpecific" // -// +kubebuilder:validation:Enum=Exact;RegularExpression;ImplementationSpecific +// +kubebuilder:validation:Enum=Exact;RegularExpression type QueryParamMatchType string // QueryParamMatchType constants. const ( - QueryParamMatchExact QueryParamMatchType = "Exact" - QueryParamMatchRegularExpression QueryParamMatchType = "RegularExpression" - QueryParamMatchImplementationSpecific QueryParamMatchType = "ImplementationSpecific" + QueryParamMatchExact QueryParamMatchType = "Exact" + QueryParamMatchRegularExpression QueryParamMatchType = "RegularExpression" ) // HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP @@ -356,7 +360,7 @@ type HTTPQueryParamMatch struct { // // Support: Extended (Exact) // - // Support: Custom (RegularExpression, ImplementationSpecific) + // Support: Custom (RegularExpression) // // Since RegularExpression QueryParamMatchType has custom conformance, // implementations can support POSIX, PCRE or any other dialects of regular @@ -407,15 +411,15 @@ const ( // evaluate to true only if all conditions are satisfied. // // For example, the match below will match a HTTP request only if its path -// starts with `/foo` AND it contains the `version: "1"` header: +// starts with `/foo` AND it contains the `version: v1` header: // // ``` // match: // path: // value: "/foo" // headers: -// values: -// version: "1" +// - name: "version" +// value "v1" // ``` type HTTPRouteMatch struct { // Path specifies a HTTP request path matcher. If this field is not @@ -453,29 +457,14 @@ type HTTPRouteMatch struct { // // +optional Method *HTTPMethod `json:"method,omitempty"` - - // ExtensionRef is an optional, implementation-specific extension to the - // "match" behavior. For example, resource "myroutematcher" in group - // "networking.example.net". If the referent cannot be found, the rule is - // not included in the route. The controller must ensure the "ResolvedRefs" - // condition on the Route status is set to `status: False`. - // - // Support: Custom - // - // +optional - ExtensionRef *LocalObjectReference `json:"extensionRef,omitempty"` } -// HTTPRouteFilter defines additional processing steps that must be completed -// during the request or response lifecycle. HTTPRouteFilters are meant as an -// extension point to express additional processing that may be done in Gateway -// implementations. Some examples include request or response modification, -// implementing authentication strategies, rate-limiting, and traffic shaping. -// API guarantee/conformance is defined based on the type of the filter. -// TODO(hbagdi): re-render CRDs once controller-tools supports union tags: -// - https://github.com/kubernetes-sigs/controller-tools/pull/298 -// - https://github.com/kubernetes-sigs/controller-tools/issues/461 -// +union +// HTTPRouteFilter defines processing steps that must be completed during the +// request or response lifecycle. HTTPRouteFilters are meant as an extension +// point to express processing that may be done in Gateway implementations. Some +// examples include request or response modification, implementing +// authentication strategies, rate-limiting, and traffic shaping. API +// guarantee/conformance is defined based on the type of the filter. type HTTPRouteFilter struct { // Type identifies the type of filter to apply. As with other API fields, // types are classified into three conformance levels: @@ -498,6 +487,10 @@ type HTTPRouteFilter struct { // Implementers are encouraged to define custom implementation types to // extend the core API with implementation-specific behavior. // + // If a reference to a custom filter type cannot be resolved, the filter + // MUST NOT be skipped. Instead, requests that would have been processed by + // that filter MUST receive a HTTP error response. + // // +unionDiscriminator Type HTTPRouteFilterType `json:"type"` @@ -510,6 +503,8 @@ type HTTPRouteFilter struct { RequestHeaderModifier *HTTPRequestHeaderFilter `json:"requestHeaderModifier,omitempty"` // RequestMirror defines a schema for a filter that mirrors requests. + // Requests are sent to the specified destination, but responses from + // that destination are ignored. // // Support: Extended // @@ -522,7 +517,7 @@ type HTTPRouteFilter struct { // Support: Core // // +optional - RequestRedirect *HTTPRequestRedirect `json:"requestRedirect,omitempty"` + RequestRedirect *HTTPRequestRedirectFilter `json:"requestRedirect,omitempty"` // ExtensionRef is an optional, implementation-specific extension to the // "filter" behavior. For example, resource "myroutefilter" in group @@ -580,9 +575,9 @@ type HTTPHeader struct { // Name is the name of the HTTP Header to be matched. Name matching MUST be // case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). // - // If multiple entries specify equivalent header names, only the first entry - // with an equivalent name MUST be considered for a match. Subsequent - // entries with an equivalent header name MUST be ignored. Due to the + // If multiple entries specify equivalent header names, the first entry with + // an equivalent name MUST be considered for a match. Subsequent entries + // with an equivalent header name MUST be ignored. Due to the // case-insensitivity of header names, "foo" and "Foo" are considered // equivalent. Name HTTPHeaderName `json:"name"` @@ -605,7 +600,9 @@ type HTTPRequestHeaderFilter struct { // my-header: foo // // Config: - // set: {"my-header": "bar"} + // set: + // - name: "my-header" + // value: "bar" // // Output: // GET /foo HTTP/1.1 @@ -626,7 +623,9 @@ type HTTPRequestHeaderFilter struct { // my-header: foo // // Config: - // add: {"my-header": "bar"} + // add: + // - name: "my-header" + // value: "bar" // // Output: // GET /foo HTTP/1.1 @@ -662,17 +661,17 @@ type HTTPRequestHeaderFilter struct { Remove []string `json:"remove,omitempty"` } -// HTTPRequestRedirect defines configuration for the RequestRedirect filter. -type HTTPRequestRedirect struct { - // Protocol is the protocol to be used in the value of the `Location` +// HTTPRequestRedirectFilter defines configuration for the RequestRedirect filter. +type HTTPRequestRedirectFilter struct { + // Scheme is the scheme to be used in the value of the `Location` // header in the response. - // When empty, the protocol of the request is used. + // When empty, the scheme of the request is used. // // Support: Extended // // +optional - // +kubebuilder:validation:Enum=HTTP;HTTPS - Protocol *string `json:"protocol,omitempty"` + // +kubebuilder:validation:Enum=http;https + Scheme *string `json:"scheme,omitempty"` // Hostname is the hostname to be used in the value of the `Location` // header in the response. @@ -722,9 +721,7 @@ type HTTPRequestMirrorFilter struct { // // Support: Extended for Kubernetes Service // Support: Custom for any other resource - // - // +optional - BackendRef *BackendObjectReference `json:"backendRef,omitempty"` + BackendRef BackendObjectReference `json:"backendRef"` } // HTTPBackendRef defines how a HTTPRoute should forward an HTTP request. diff --git a/apis/v1alpha2/object_reference_types.go b/apis/v1alpha2/object_reference_types.go index 50bad17ff5..0077f0a1e7 100644 --- a/apis/v1alpha2/object_reference_types.go +++ b/apis/v1alpha2/object_reference_types.go @@ -111,7 +111,7 @@ type BackendObjectReference struct { // Port specifies the destination port number to use for this resource. // Port is required when the referent is a Kubernetes Service. - // For other resources, destination port can be derived from the referent + // For other resources, destination port might be derived from the referent // resource or this field. // // +optional diff --git a/apis/v1alpha2/referencepolicy_types.go b/apis/v1alpha2/referencepolicy_types.go index 6bfc62eb1b..149ae89b65 100644 --- a/apis/v1alpha2/referencepolicy_types.go +++ b/apis/v1alpha2/referencepolicy_types.go @@ -86,7 +86,7 @@ type ReferencePolicySpec struct { // ReferencePolicyFrom describes trusted namespaces and kinds. type ReferencePolicyFrom struct { // Group is the group of the referent. - // When empty, the "core" API group is inferred. + // When empty, the Kubernetes core API group is inferred. // // Support: Core Group Group `json:"group"` @@ -111,7 +111,7 @@ type ReferencePolicyFrom struct { // references. type ReferencePolicyTo struct { // Group is the group of the referent. - // When empty, the "core" API group is inferred. + // When empty, the Kubernetes core API group is inferred. // // Support: Core Group Group `json:"group"` diff --git a/apis/v1alpha2/shared_types.go b/apis/v1alpha2/shared_types.go index 8710768eea..9feee27b31 100644 --- a/apis/v1alpha2/shared_types.go +++ b/apis/v1alpha2/shared_types.go @@ -96,7 +96,7 @@ type ParentRef struct { SectionName *SectionName `json:"sectionName,omitempty"` } -// CommonRouteSpec defines the common attributes that all Routes should include +// CommonRouteSpec defines the common attributes that all Routes MUST include // within their spec. type CommonRouteSpec struct { // ParentRefs references the resources (usually Gateways) that a Route wants @@ -166,9 +166,9 @@ type BackendRef struct { type RouteConditionType string const ( - // This condition indicates whether the route has been admitted or rejected + // This condition indicates whether the route has been accepted or rejected // by a Gateway, and why. - ConditionRouteAdmitted RouteConditionType = "Admitted" + ConditionRouteAccepted RouteConditionType = "Accepted" // This condition indicates whether the controller was able to resolve all // the object references for the Route. @@ -182,16 +182,16 @@ type RouteParentStatus struct { // RouteParentStatus struct describes the status of. ParentRef ParentRef `json:"parentRef"` - // Controller is a domain/path string that indicates the controller that - // wrote this status. This corresponds with the controller field on - // GatewayClass. + // ControllerName is a domain/path string that indicates the name of the + // controller that wrote this status. This corresponds with the + // controllerName field on GatewayClass. // // Example: "example.net/gateway-controller". // // The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are // valid Kubernetes names // (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). - Controller GatewayController `json:"controller"` + ControllerName GatewayController `json:"controllerName"` // Conditions describes the status of the route with respect to the Gateway. // Note that the route's availability is also subject to the Gateway's own @@ -199,14 +199,14 @@ type RouteParentStatus struct { // // If the Route's ParentRef specifies an existing Gateway that supports // Routes of this kind AND that Gateway's controller has sufficient access, - // then that Gateway's controller MUST set the "Admitted" condition on the - // Route, to indicate whether the route has been admitted or rejected by the + // then that Gateway's controller MUST set the "Accepted" condition on the + // Route, to indicate whether the route has been accepted or rejected by the // Gateway, and why. // - // A Route MUST be considered "Admitted" if at least one of the Route's + // A Route MUST be considered "Accepted" if at least one of the Route's // rules is implemented by the Gateway. // - // There are a number of cases where the "Admitted" condition may not be set + // There are a number of cases where the "Accepted" condition may not be set // due to lack of controller visibility, that includes when: // // * The Route refers to a non-existent parent. @@ -220,8 +220,8 @@ type RouteParentStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty"` } -// RouteStatus defines the observed state that is required across -// all route types. +// RouteStatus defines the common attributes that all Routes MUST include within +// their status. type RouteStatus struct { // Parents is a list of parent resources (usually Gateways) that are // associated with the route, and the status of the route with respect to @@ -230,6 +230,11 @@ type RouteStatus struct { // first sees the route and should update the entry as appropriate when the // route or gateway is modified. // + // Note that parent references that cannot be resolved by an implementation + // of this API will not be added to this list. Implementations of this API + // can only populate Route status for the Gateways/parent resources they are + // responsible for. + // // A maximum of 32 Gateways will be represented in this list. An empty list // means the route has not been attached to any Gateway. // @@ -237,12 +242,12 @@ type RouteStatus struct { Parents []RouteParentStatus `json:"parents"` } -// Hostname is the fully qualified domain name of a network host, as defined -// by RFC 3986. Note the following deviations from the "host" part of the -// URI as defined in the RFC: +// Hostname is the fully qualified domain name of a network host. This matches +// the RFC 1123 definition of a hostname with 2 notable exceptions: // -// 1. IP literals are not allowed. -// 2. The `:` delimiter is not respected because ports are not allowed. +// 1. IPs are not allowed. +// 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard +// label must appear by itself as the first label. // // Hostname can be "precise" which is a domain name without the terminating // dot of a network host (e.g. "foo.example.com") or "wildcard", which is a @@ -314,8 +319,7 @@ type Kind string // +kubebuilder:validation:MaxLength=63 type Namespace string -// SectionName is the name of a section in a Kubernetes resource. It must be a -// RFC 1123 subdomain. +// SectionName is the name of a section in a Kubernetes resource. // // This validation is based off of the corresponding Kubernetes validation: // https://github.com/kubernetes/apimachinery/blob/02cfb53916346d085a6c6c7c66f882e3c6b0eca6/pkg/util/validation/validation.go#L208 diff --git a/apis/v1alpha2/tcproute_types.go b/apis/v1alpha2/tcproute_types.go index 5c9b615b28..60d805787d 100644 --- a/apis/v1alpha2/tcproute_types.go +++ b/apis/v1alpha2/tcproute_types.go @@ -27,7 +27,9 @@ import ( // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` -// TCPRoute is the Schema for the TCPRoute resource. +// TCPRoute provides a way to route TCP requests. When combined with a Gateway +// listener, it can be used to forward connections on the port specified by the +// listener to a set of backends specified by the TCPRoute. type TCPRoute struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -57,37 +59,6 @@ type TCPRouteStatus struct { // TCPRouteRule is the configuration for a given rule. type TCPRouteRule struct { - // Matches define conditions used for matching the rule against incoming TCP - // connections. Each match is independent, i.e. this rule will be matched if - // **any** one of the matches is satisfied. If unspecified (i.e. empty), - // this Rule will match all requests for the associated Listener. - // - // Each client request MUST map to a maximum of one route rule. If a request - // matches multiple rules, matching precedence MUST be determined in order - // of the following criteria, continuing on ties: - // - // * The most specific match specified by ExtensionRef. Each implementation - // that supports ExtensionRef may have different ways of determining the - // specificity of the referenced extension. - // - // If ties still exist across multiple Routes, matching precedence MUST be - // determined in order of the following criteria, continuing on ties: - // - // * The oldest Route based on creation timestamp. For example, a Route with - // a creation timestamp of "2020-09-08 01:02:03" is given precedence over - // a Route with a creation timestamp of "2020-09-08 01:02:04". - // * The Route appearing first in alphabetical order by - // "/". For example, foo/bar is given precedence over - // foo/baz. - // - // If ties still exist within the Route that has been given precedence, - // matching precedence MUST be granted to the first matching rule meeting - // the above criteria. - // - // +optional - // +kubebuilder:validation:MaxItems=8 - Matches []TCPRouteMatch `json:"matches,omitempty"` - // BackendRefs defines the backend(s) where matching requests should be // sent. If unspecified or invalid (refers to a non-existent resource or a // Service with no endpoints), the underlying implementation MUST actively @@ -105,21 +76,6 @@ type TCPRouteRule struct { BackendRefs []BackendRef `json:"backendRefs,omitempty"` } -// TCPRouteMatch defines the predicate used to match connections to a -// given action. -type TCPRouteMatch struct { - // ExtensionRef is an optional, implementation-specific extension to the - // "match" behavior. For example, resource "mytcproutematcher" in group - // "networking.example.net". If the referent cannot be found, the rule MUST - // not be included in the route. The controller must ensure the - // "ResolvedRefs" condition on the Route status is set to `status: False`. - // - // Support: Custom - // - // +optional - ExtensionRef *LocalObjectReference `json:"extensionRef,omitempty"` -} - // +kubebuilder:object:root=true // TCPRouteList contains a list of TCPRoute diff --git a/apis/v1alpha2/tlsroute_types.go b/apis/v1alpha2/tlsroute_types.go index 94b6913acb..e8f3faa7f1 100644 --- a/apis/v1alpha2/tlsroute_types.go +++ b/apis/v1alpha2/tlsroute_types.go @@ -49,26 +49,36 @@ type TLSRouteSpec struct { CommonRouteSpec `json:",inline"` // Hostnames defines a set of SNI names that should match against the - // SNI attribute of TLS ClientHello message in TLS handshake. - // - // SNI can be "precise" which is a domain name without the terminating - // dot of a network host (e.g. "foo.example.com") or "wildcard", which is - // a domain name prefixed with a single wildcard label (e.g. `*.example.com`). - // The wildcard character `*` must appear by itself as the first DNS label - // and matches only a single label. You cannot have a wildcard label by - // itself (e.g. Host == `*`). - // - // Requests will be matched against the SNI attribute in the following - // order: - // - // 1. If SNI is precise, the request matches this Route if the SNI in - // ClientHello is equal to one of the defined SNIs. - // 2. If SNI is a wildcard, then the request matches this Route if the - // SNI is to equal to the suffix (removing the first label) of the - // wildcard. - // 3. If SNIs are unspecified, all requests associated with the gateway TLS - // listener will match. This can be used to define a default backend - // for a TLS listener. + // SNI attribute of TLS ClientHello message in TLS handshake. This matches + // the RFC 1123 definition of a hostname with 2 notable exceptions: + // + // 1. IPs are not allowed in SNI names per RFC 6066. + // 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard + // label must appear by itself as the first label. + // + // If a hostname is specified by both the Listener and TLSRoute, there + // must be at least one intersecting hostname for the TLSRoute to be + // attached to the Listener. For example: + // + // * A Listener with `test.example.com` as the hostname matches TLSRoutes + // that have either not specified any hostnames, or have specified at + // least one of `test.example.com` or `*.example.com`. + // * A Listener with `*.example.com` as the hostname matches TLSRoutes + // that have either not specified any hostnames or have specified at least + // one hostname that matches the Listener hostname. For example, + // `test.example.com` and `*.example.com` would both match. On the other + // hand, `example.com` and `test.example.net` would not match. + // + // If both the Listener and TLSRoute have specified hostnames, any + // TLSRoute hostnames that do not match the Listener hostname MUST be + // ignored. For example, if a Listener specified `*.example.com`, and the + // TLSRoute specified `test.example.com` and `test.example.net`, + // `test.example.net` must not be considered for a match. + // + // If both the Listener and TLSRoute have specified hostnames, and none + // match with the criteria above, then the TLSRoute is not accepted. The + // implementation must raise an 'Accepted' Condition with a status of + // `False` in the corresponding RouteParentStatus. // // Support: Core // @@ -90,40 +100,6 @@ type TLSRouteStatus struct { // TLSRouteRule is the configuration for a given rule. type TLSRouteRule struct { - // Matches define conditions used for matching the rule against incoming TLS - // connections. Each match is independent, i.e. this rule will be matched if - // **any** one of the matches is satisfied. If unspecified (i.e. empty), - // this Rule will match all requests for the associated Listener. - // - // Each client request MUST map to a maximum of one route rule. If a request - // matches multiple rules, matching precedence MUST be determined in order - // of the following criteria, continuing on ties: - // - // * The longest matching SNI. - // * The longest matching precise SNI (without a wildcard). This means that - // "b.example.com" should be given precedence over "*.example.com". - // * The most specific match specified by ExtensionRef. Each implementation - // that supports ExtensionRef may have different ways of determining the - // specificity of the referenced extension. - // - // If ties still exist across multiple Routes, matching precedence MUST be - // determined in order of the following criteria, continuing on ties: - // - // * The oldest Route based on creation timestamp. For example, a Route with - // a creation timestamp of "2020-09-08 01:02:03" is given precedence over - // a Route with a creation timestamp of "2020-09-08 01:02:04". - // * The Route appearing first in alphabetical order by - // "/". For example, foo/bar is given precedence over - // foo/baz. - // - // If ties still exist within the Route that has been given precedence, - // matching precedence MUST be granted to the first matching rule meeting - // the above criteria. - // - // +optional - // +kubebuilder:validation:MaxItems=8 - Matches []TLSRouteMatch `json:"matches,omitempty"` - // BackendRefs defines the backend(s) where matching requests should be // sent. If unspecified or invalid (refers to a non-existent resource or // a Service with no endpoints), the rule performs no forwarding; if no @@ -144,21 +120,6 @@ type TLSRouteRule struct { BackendRefs []BackendRef `json:"backendRefs,omitempty"` } -// TLSRouteMatch defines the predicate used to match connections to a -// given action. -type TLSRouteMatch struct { - // ExtensionRef is an optional, implementation-specific extension to the - // "match" behavior. For example, resource "mytcproutematcher" in group - // "networking.example.net". If the referent cannot be found, the rule MUST - // not be included in the route. The controller must ensure the - // "ResolvedRefs" condition on the Route status is set to `status: False`. - // - // Support: Custom - // - // +optional - ExtensionRef *LocalObjectReference `json:"extensionRef,omitempty"` -} - // +kubebuilder:object:root=true // TLSRouteList contains a list of TLSRoute diff --git a/apis/v1alpha2/udproute_types.go b/apis/v1alpha2/udproute_types.go index 4a590c23c3..361713afb2 100644 --- a/apis/v1alpha2/udproute_types.go +++ b/apis/v1alpha2/udproute_types.go @@ -27,7 +27,9 @@ import ( // +kubebuilder:storageversion // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` -// UDPRoute is a resource that specifies how a Gateway should forward UDP traffic. +// UDPRoute provides a way to route UDP traffic. When combined with a Gateway +// listener, it can be used to forward traffic on the port specified by the +// listener to a set of backends specified by the UDPRoute. type UDPRoute struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -57,43 +59,12 @@ type UDPRouteStatus struct { // UDPRouteRule is the configuration for a given rule. type UDPRouteRule struct { - // Matches define conditions used for matching the rule against incoming UDP - // connections. Each match is independent, i.e. this rule will be matched if - // **any** one of the matches is satisfied. If unspecified (i.e. empty), - // this Rule will match all requests for the associated Listener. - // - // Each client request MUST map to a maximum of one route rule. If a request - // matches multiple rules, matching precedence MUST be determined in order - // of the following criteria, continuing on ties: - // - // * The most specific match specified by ExtensionRef. Each implementation - // that supports ExtensionRef may have different ways of determining the - // specificity of the referenced extension. - // - // If ties still exist across multiple Routes, matching precedence MUST be - // determined in order of the following criteria, continuing on ties: - // - // * The oldest Route based on creation timestamp. For example, a Route with - // a creation timestamp of "2020-09-08 01:02:03" is given precedence over - // a Route with a creation timestamp of "2020-09-08 01:02:04". - // * The Route appearing first in alphabetical order by - // "/". For example, foo/bar is given precedence over - // foo/baz. - // - // If ties still exist within the Route that has been given precedence, - // matching precedence MUST be granted to the first matching rule meeting - // the above criteria. - // - // +optional - // +kubebuilder:validation:MaxItems=8 - Matches []UDPRouteMatch `json:"matches,omitempty"` - // BackendRefs defines the backend(s) where matching requests should be // sent. If unspecified or invalid (refers to a non-existent resource or a // Service with no endpoints), the underlying implementation MUST actively - // reject connection attempts to this backend. Connection rejections must + // reject connection attempts to this backend. Packet drops must // respect weight; if an invalid backend is requested to have 80% of - // connections, then 80% of connections must be rejected instead. + // the packets, then 80% of packets must be dropped instead. // // Support: Core for Kubernetes Service // Support: Custom for any other resource @@ -105,21 +76,6 @@ type UDPRouteRule struct { BackendRefs []BackendRef `json:"backendRefs,omitempty"` } -// UDPRouteMatch defines the predicate used to match packets to a -// given action. -type UDPRouteMatch struct { - // ExtensionRef is an optional, implementation-specific extension to the - // "match" behavior. For example, resource "mytcproutematcher" in group - // "networking.example.net". If the referent cannot be found, the rule MUST - // not be included in the route. The controller must ensure the - // "ResolvedRefs" condition on the Route status is set to `status: False`. - // - // Support: Custom - // - // +optional - ExtensionRef *LocalObjectReference `json:"extensionRef,omitempty"` -} - // +kubebuilder:object:root=true // UDPRouteList contains a list of UDPRoute diff --git a/apis/v1alpha2/validation/gateway.go b/apis/v1alpha2/validation/gateway.go index 1524c89c78..7e7bc27695 100644 --- a/apis/v1alpha2/validation/gateway.go +++ b/apis/v1alpha2/validation/gateway.go @@ -17,10 +17,6 @@ limitations under the License. package validation import ( - "net" - "strings" - - "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" gatewayv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -29,49 +25,9 @@ import ( // ValidateGateway validates gw according to the Gateway API specification. // For additional details of the Gateway spec, refer to: // https://gateway-api.sigs.k8s.io/spec/#gateway.networking.k8s.io/v1alpha2.Gateway +// +// Validation that is not possible with CRD annotations may be added here in the future. +// See https://github.com/kubernetes-sigs/gateway-api/issues/868 for more information. func ValidateGateway(gw *gatewayv1a2.Gateway) field.ErrorList { - return validateGatewaySpec(&gw.Spec, field.NewPath("spec")) -} - -// validateGatewaySpec validates whether required fields of spec are set according to the -// Gateway API specification. -func validateGatewaySpec(spec *gatewayv1a2.GatewaySpec, path *field.Path) field.ErrorList { - // TODO [danehans]: Add additional validation of spec fields. - return validateGatewayListeners(spec.Listeners, path.Child("listeners")) -} - -// validateGatewayListeners validates whether required fields of listeners are set according -// to the Gateway API specification. -func validateGatewayListeners(listeners []gatewayv1a2.Listener, path *field.Path) field.ErrorList { - // TODO [danehans]: Add additional validation of listener fields. - return validateListenerHostname(listeners, path) -} - -// validateListenerHostname validates each listener hostname is not an IP address and is one -// of the following: -// - A fully qualified domain name of a network host, as defined by RFC 3986. -// - A DNS subdomain as defined by RFC 1123. -// - A wildcard DNS subdomain as defined by RFC 1034 (section 4.3.3). -func validateListenerHostname(listeners []gatewayv1a2.Listener, path *field.Path) field.ErrorList { - var errs field.ErrorList - for i, h := range listeners { - // When unspecified, “”, or *, all hostnames are matched. - if h.Hostname == nil || (*h.Hostname == "" || *h.Hostname == "*") { - continue - } - hostname := string(*h.Hostname) - if ip := net.ParseIP(hostname); ip != nil { - errs = append(errs, field.Invalid(path.Index(i).Child("hostname"), hostname, "must be a DNS hostname, not an IP address")) - } - if strings.Contains(hostname, "*") { - for _, msg := range validation.IsWildcardDNS1123Subdomain(hostname) { - errs = append(errs, field.Invalid(path.Index(i).Child("hostname"), hostname, msg)) - } - } else { - for _, msg := range validation.IsDNS1123Subdomain(hostname) { - errs = append(errs, field.Invalid(path.Index(i).Child("hostname"), hostname, msg)) - } - } - } - return errs + return nil } diff --git a/apis/v1alpha2/validation/gatewayclass.go b/apis/v1alpha2/validation/gatewayclass.go index 5f496da0cc..19667aaac2 100644 --- a/apis/v1alpha2/validation/gatewayclass.go +++ b/apis/v1alpha2/validation/gatewayclass.go @@ -30,8 +30,8 @@ func ValidateGatewayClassUpdate(oldClass, newClass *gatewayv1a2.GatewayClass) fi return nil } var errs field.ErrorList - if oldClass.Spec.Controller != newClass.Spec.Controller { - errs = append(errs, field.Invalid(field.NewPath("spec.controller"), newClass.Spec.Controller, + if oldClass.Spec.ControllerName != newClass.Spec.ControllerName { + errs = append(errs, field.Invalid(field.NewPath("spec.controllerName"), newClass.Spec.ControllerName, "cannot update an immutable field")) } return errs diff --git a/apis/v1alpha2/validation/gatewayclass_test.go b/apis/v1alpha2/validation/gatewayclass_test.go index 323ca6a71c..0518ddb1f9 100644 --- a/apis/v1alpha2/validation/gatewayclass_test.go +++ b/apis/v1alpha2/validation/gatewayclass_test.go @@ -40,12 +40,12 @@ func TestValidateGatewayClassUpdate(t *testing.T) { args: args{ oldClass: &gatewayv1a2.GatewayClass{ Spec: gatewayv1a2.GatewayClassSpec{ - Controller: "foo", + ControllerName: "foo", }, }, newClass: &gatewayv1a2.GatewayClass{ Spec: gatewayv1a2.GatewayClassSpec{ - Controller: "foo", + ControllerName: "foo", ParametersRef: &gatewayv1a2.ParametersReference{ Group: "example.com", Kind: "GatewayClassConfig", @@ -61,19 +61,19 @@ func TestValidateGatewayClassUpdate(t *testing.T) { args: args{ oldClass: &gatewayv1a2.GatewayClass{ Spec: gatewayv1a2.GatewayClassSpec{ - Controller: "example.com/gateway", + ControllerName: "example.com/gateway", }, }, newClass: &gatewayv1a2.GatewayClass{ Spec: gatewayv1a2.GatewayClassSpec{ - Controller: "example.org/gateway", + ControllerName: "example.org/gateway", }, }, }, want: field.ErrorList{ { Type: field.ErrorTypeInvalid, - Field: "spec.controller", + Field: "spec.controllerName", Detail: "cannot update an immutable field", BadValue: gatewayv1a2.GatewayController("example.org/gateway"), }, diff --git a/apis/v1alpha2/validation/httproute_test.go b/apis/v1alpha2/validation/httproute_test.go index db22fbd92e..3923f75fdc 100644 --- a/apis/v1alpha2/validation/httproute_test.go +++ b/apis/v1alpha2/validation/httproute_test.go @@ -81,7 +81,7 @@ func TestValidateHTTPRoute(t *testing.T) { { Type: gatewayv1a2.HTTPRouteFilterRequestMirror, RequestMirror: &gatewayv1a2.HTTPRequestMirrorFilter{ - BackendRef: &gatewayv1a2.BackendObjectReference{ + BackendRef: gatewayv1a2.BackendObjectReference{ Name: testService, Port: portNumberPtr(8081), }, @@ -112,7 +112,7 @@ func TestValidateHTTPRoute(t *testing.T) { { Type: gatewayv1a2.HTTPRouteFilterRequestMirror, RequestMirror: &gatewayv1a2.HTTPRequestMirrorFilter{ - BackendRef: &gatewayv1a2.BackendObjectReference{ + BackendRef: gatewayv1a2.BackendObjectReference{ Name: testService, Port: portNumberPtr(8080), }, @@ -121,7 +121,7 @@ func TestValidateHTTPRoute(t *testing.T) { { Type: gatewayv1a2.HTTPRouteFilterRequestMirror, RequestMirror: &gatewayv1a2.HTTPRequestMirrorFilter{ - BackendRef: &gatewayv1a2.BackendObjectReference{ + BackendRef: gatewayv1a2.BackendObjectReference{ Name: specialService, Port: portNumberPtr(8080), }, @@ -163,7 +163,7 @@ func TestValidateHTTPRoute(t *testing.T) { { Type: gatewayv1a2.HTTPRouteFilterRequestMirror, RequestMirror: &gatewayv1a2.HTTPRequestMirrorFilter{ - BackendRef: &gatewayv1a2.BackendObjectReference{ + BackendRef: gatewayv1a2.BackendObjectReference{ Name: testService, Port: portNumberPtr(8080), }, @@ -205,7 +205,7 @@ func TestValidateHTTPRoute(t *testing.T) { { Type: gatewayv1a2.HTTPRouteFilterRequestMirror, RequestMirror: &gatewayv1a2.HTTPRequestMirrorFilter{ - BackendRef: &gatewayv1a2.BackendObjectReference{ + BackendRef: gatewayv1a2.BackendObjectReference{ Name: testService, Port: portNumberPtr(8080), }, @@ -225,7 +225,7 @@ func TestValidateHTTPRoute(t *testing.T) { { Type: gatewayv1a2.HTTPRouteFilterRequestMirror, RequestMirror: &gatewayv1a2.HTTPRequestMirrorFilter{ - BackendRef: &gatewayv1a2.BackendObjectReference{ + BackendRef: gatewayv1a2.BackendObjectReference{ Name: testService, Port: portNumberPtr(8080), }, @@ -245,7 +245,7 @@ func TestValidateHTTPRoute(t *testing.T) { { Type: gatewayv1a2.HTTPRouteFilterRequestMirror, RequestMirror: &gatewayv1a2.HTTPRequestMirrorFilter{ - BackendRef: &gatewayv1a2.BackendObjectReference{ + BackendRef: gatewayv1a2.BackendObjectReference{ Name: specialService, Port: portNumberPtr(8080), }, @@ -287,7 +287,7 @@ func TestValidateHTTPRoute(t *testing.T) { { Type: gatewayv1a2.HTTPRouteFilterRequestMirror, RequestMirror: &gatewayv1a2.HTTPRequestMirrorFilter{ - BackendRef: &gatewayv1a2.BackendObjectReference{ + BackendRef: gatewayv1a2.BackendObjectReference{ Name: testService, Port: portNumberPtr(8080), }, diff --git a/apis/v1alpha2/zz_generated.deepcopy.go b/apis/v1alpha2/zz_generated.deepcopy.go index 169e84c1ae..58ce1839b2 100644 --- a/apis/v1alpha2/zz_generated.deepcopy.go +++ b/apis/v1alpha2/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* @@ -388,10 +389,16 @@ func (in *GatewayTLSConfig) DeepCopyInto(out *GatewayTLSConfig) { *out = new(TLSModeType) **out = **in } - if in.CertificateRef != nil { - in, out := &in.CertificateRef, &out.CertificateRef - *out = new(SecretObjectReference) - (*in).DeepCopyInto(*out) + if in.CertificateRefs != nil { + in, out := &in.CertificateRefs, &out.CertificateRefs + *out = make([]*SecretObjectReference, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(SecretObjectReference) + (*in).DeepCopyInto(*out) + } + } } if in.Options != nil { in, out := &in.Options, &out.Options @@ -548,11 +555,7 @@ func (in *HTTPRequestHeaderFilter) DeepCopy() *HTTPRequestHeaderFilter { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPRequestMirrorFilter) DeepCopyInto(out *HTTPRequestMirrorFilter) { *out = *in - if in.BackendRef != nil { - in, out := &in.BackendRef, &out.BackendRef - *out = new(BackendObjectReference) - (*in).DeepCopyInto(*out) - } + in.BackendRef.DeepCopyInto(&out.BackendRef) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestMirrorFilter. @@ -566,10 +569,10 @@ func (in *HTTPRequestMirrorFilter) DeepCopy() *HTTPRequestMirrorFilter { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HTTPRequestRedirect) DeepCopyInto(out *HTTPRequestRedirect) { +func (in *HTTPRequestRedirectFilter) DeepCopyInto(out *HTTPRequestRedirectFilter) { *out = *in - if in.Protocol != nil { - in, out := &in.Protocol, &out.Protocol + if in.Scheme != nil { + in, out := &in.Scheme, &out.Scheme *out = new(string) **out = **in } @@ -590,12 +593,12 @@ func (in *HTTPRequestRedirect) DeepCopyInto(out *HTTPRequestRedirect) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestRedirect. -func (in *HTTPRequestRedirect) DeepCopy() *HTTPRequestRedirect { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRequestRedirectFilter. +func (in *HTTPRequestRedirectFilter) DeepCopy() *HTTPRequestRedirectFilter { if in == nil { return nil } - out := new(HTTPRequestRedirect) + out := new(HTTPRequestRedirectFilter) in.DeepCopyInto(out) return out } @@ -642,7 +645,7 @@ func (in *HTTPRouteFilter) DeepCopyInto(out *HTTPRouteFilter) { } if in.RequestRedirect != nil { in, out := &in.RequestRedirect, &out.RequestRedirect - *out = new(HTTPRequestRedirect) + *out = new(HTTPRequestRedirectFilter) (*in).DeepCopyInto(*out) } if in.ExtensionRef != nil { @@ -721,11 +724,6 @@ func (in *HTTPRouteMatch) DeepCopyInto(out *HTTPRouteMatch) { *out = new(HTTPMethod) **out = **in } - if in.ExtensionRef != nil { - in, out := &in.ExtensionRef, &out.ExtensionRef - *out = new(LocalObjectReference) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPRouteMatch. @@ -1274,36 +1272,9 @@ func (in *TCPRouteList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TCPRouteMatch) DeepCopyInto(out *TCPRouteMatch) { - *out = *in - if in.ExtensionRef != nil { - in, out := &in.ExtensionRef, &out.ExtensionRef - *out = new(LocalObjectReference) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TCPRouteMatch. -func (in *TCPRouteMatch) DeepCopy() *TCPRouteMatch { - if in == nil { - return nil - } - out := new(TCPRouteMatch) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPRouteRule) DeepCopyInto(out *TCPRouteRule) { *out = *in - if in.Matches != nil { - in, out := &in.Matches, &out.Matches - *out = make([]TCPRouteMatch, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } if in.BackendRefs != nil { in, out := &in.BackendRefs, &out.BackendRefs *out = make([]BackendRef, len(*in)) @@ -1421,36 +1392,9 @@ func (in *TLSRouteList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TLSRouteMatch) DeepCopyInto(out *TLSRouteMatch) { - *out = *in - if in.ExtensionRef != nil { - in, out := &in.ExtensionRef, &out.ExtensionRef - *out = new(LocalObjectReference) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSRouteMatch. -func (in *TLSRouteMatch) DeepCopy() *TLSRouteMatch { - if in == nil { - return nil - } - out := new(TLSRouteMatch) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSRouteRule) DeepCopyInto(out *TLSRouteRule) { *out = *in - if in.Matches != nil { - in, out := &in.Matches, &out.Matches - *out = make([]TLSRouteMatch, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } if in.BackendRefs != nil { in, out := &in.BackendRefs, &out.BackendRefs *out = make([]BackendRef, len(*in)) @@ -1573,36 +1517,9 @@ func (in *UDPRouteList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *UDPRouteMatch) DeepCopyInto(out *UDPRouteMatch) { - *out = *in - if in.ExtensionRef != nil { - in, out := &in.ExtensionRef, &out.ExtensionRef - *out = new(LocalObjectReference) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UDPRouteMatch. -func (in *UDPRouteMatch) DeepCopy() *UDPRouteMatch { - if in == nil { - return nil - } - out := new(UDPRouteMatch) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UDPRouteRule) DeepCopyInto(out *UDPRouteRule) { *out = *in - if in.Matches != nil { - in, out := &in.Matches, &out.Matches - *out = make([]UDPRouteMatch, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } if in.BackendRefs != nil { in, out := &in.BackendRefs, &out.BackendRefs *out = make([]BackendRef, len(*in))