From e0bce55e569b30746c9c62fa8e9747074b733201 Mon Sep 17 00:00:00 2001 From: therealak12 Date: Mon, 9 Feb 2026 15:48:03 +0330 Subject: [PATCH 1/2] add support for http authz server Signed-off-by: therealak12 --- apis/projectcontour/v1/httpproxy.go | 79 +++ .../v1/zz_generated.deepcopy.go | 45 ++ .../v1alpha1/extensionservice.go | 10 +- .../unreleased/7418-therealak12-minor.md | 5 + cmd/contour/serve.go | 28 ++ cmd/contour/servecontext.go | 5 + examples/contour/01-crds.yaml | 255 +++++++++- examples/render/contour-deployment.yaml | 255 +++++++++- .../render/contour-gateway-provisioner.yaml | 255 +++++++++- examples/render/contour-gateway.yaml | 255 +++++++++- examples/render/contour.yaml | 255 +++++++++- internal/dag/conditions.go | 38 ++ internal/dag/dag.go | 24 + internal/dag/httpproxy_processor.go | 34 ++ internal/envoy/v3/cluster.go | 10 +- internal/envoy/v3/listener.go | 91 +++- .../featuretests/v3/authorization_test.go | 475 +++++++++++++++++- internal/featuretests/v3/envoy.go | 14 + .../featuretests/v3/extensionservice_test.go | 34 +- .../v3/global_authorization_test.go | 6 +- internal/xdscache/v3/listener.go | 16 +- pkg/config/parameters.go | 10 + .../docs/main/config/api-reference.html | 220 +++++++- 23 files changed, 2360 insertions(+), 59 deletions(-) create mode 100644 changelogs/unreleased/7418-therealak12-minor.md diff --git a/apis/projectcontour/v1/httpproxy.go b/apis/projectcontour/v1/httpproxy.go index 8e407a02b7c..c30a8b243f8 100644 --- a/apis/projectcontour/v1/httpproxy.go +++ b/apis/projectcontour/v1/httpproxy.go @@ -239,6 +239,15 @@ type ExtensionServiceReference struct { Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"` } +// AuthorizationServiceAPIType indicates the protocol +// implemented by the external authorization server. +type AuthorizationServiceAPIType string + +const ( + AuthorizationGRPCService AuthorizationServiceAPIType = "grpc" + AuthorizationHTTPService AuthorizationServiceAPIType = "http" +) + // AuthorizationServer configures an external server to authenticate // client requests. The external server must implement the v3 Envoy // external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto). @@ -248,6 +257,19 @@ type AuthorizationServer struct { // +optional ExtensionServiceRef ExtensionServiceReference `json:"extensionRef,omitempty"` + // ServiceAPIType sets the protocol used to communicate with + // the external authorization server. + // + // +optional + // +kubebuilder:validation:Enum=http;grpc + // +kubebuilder:default=grpc + ServiceAPIType AuthorizationServiceAPIType `json:"serviceAPIType,omitempty"` + + // HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server. + // + // +optional + HTTPServerSettings *HTTPAuthorizationServerSettings `json:"httpSettings,omitempty"` + // AuthPolicy sets a default authorization policy for client requests. // This policy will be used unless overridden by individual routes. // @@ -276,6 +298,63 @@ type AuthorizationServer struct { WithRequestBody *AuthorizationServerBufferSettings `json:"withRequestBody,omitempty"` } +// HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server. +type HTTPAuthorizationServerSettings struct { + // PathPrefix Sets a prefix to the value of authorization request header Path. + // + // +optional + PathPrefix string `json:"pathPrefix,omitempty"` + + // AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + // Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + // + // +optional + AllowedAuthorizationHeaders []HTTPAuthorizationServerAllowedHeaders `json:"allowedAuthorizationHeaders,omitempty"` + + // AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + // Coexistent headers will be overridden. + // + // +optional + AllowedUpstreamHeaders []HTTPAuthorizationServerAllowedHeaders `json:"allowedUpstreamHeaders,omitempty"` +} + +// HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers +// in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user +// experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. +type HTTPAuthorizationServerAllowedHeaders struct { + // Exact specifies a string that the header name must be equal to. + // + // +optional + Exact string `json:"exact,omitempty"` + + // Prefix defines a prefix match for the header name. + // + // +optional + Prefix string `json:"prefix,omitempty"` + + // Suffix defines a suffix match for a header name. + // + // +optional + Suffix string `json:"suffix,omitempty"` + + // To streamline user experience and mitigate potential issues, we do not support regex. + // Additionally, it's essential to ensure that any regex patterns adhere to the configured runtime key, re2.max_program_size.error_level + // by verifying that the program size is smaller than the specified value. + // This necessitates thorough validation of user input. + // + // Regex string `json:"regex,omitempty"` + + // Contains specifies a substring that must be present in the header name. + // + // +optional + Contains string `json:"contains,omitempty"` + + // IgnoreCase specifies whether string matching should be case-insensitive. + // + // +optional + IgnoreCase bool `json:"ignoreCase,omitempty"` +} + // AuthorizationServerBufferSettings enables ExtAuthz filter to buffer client request data and send it as part of authorization request type AuthorizationServerBufferSettings struct { // MaxRequestBytes sets the maximum size of message body ExtAuthz filter will hold in-memory. diff --git a/apis/projectcontour/v1/zz_generated.deepcopy.go b/apis/projectcontour/v1/zz_generated.deepcopy.go index 30cd9f1cbea..67a09799161 100644 --- a/apis/projectcontour/v1/zz_generated.deepcopy.go +++ b/apis/projectcontour/v1/zz_generated.deepcopy.go @@ -50,6 +50,11 @@ func (in *AuthorizationPolicy) DeepCopy() *AuthorizationPolicy { func (in *AuthorizationServer) DeepCopyInto(out *AuthorizationServer) { *out = *in out.ExtensionServiceRef = in.ExtensionServiceRef + if in.HTTPServerSettings != nil { + in, out := &in.HTTPServerSettings, &out.HTTPServerSettings + *out = new(HTTPAuthorizationServerSettings) + (*in).DeepCopyInto(*out) + } if in.AuthPolicy != nil { in, out := &in.AuthPolicy, &out.AuthPolicy *out = new(AuthorizationPolicy) @@ -324,6 +329,46 @@ func (in *GlobalRateLimitPolicy) DeepCopy() *GlobalRateLimitPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPAuthorizationServerAllowedHeaders) DeepCopyInto(out *HTTPAuthorizationServerAllowedHeaders) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPAuthorizationServerAllowedHeaders. +func (in *HTTPAuthorizationServerAllowedHeaders) DeepCopy() *HTTPAuthorizationServerAllowedHeaders { + if in == nil { + return nil + } + out := new(HTTPAuthorizationServerAllowedHeaders) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HTTPAuthorizationServerSettings) DeepCopyInto(out *HTTPAuthorizationServerSettings) { + *out = *in + if in.AllowedAuthorizationHeaders != nil { + in, out := &in.AllowedAuthorizationHeaders, &out.AllowedAuthorizationHeaders + *out = make([]HTTPAuthorizationServerAllowedHeaders, len(*in)) + copy(*out, *in) + } + if in.AllowedUpstreamHeaders != nil { + in, out := &in.AllowedUpstreamHeaders, &out.AllowedUpstreamHeaders + *out = make([]HTTPAuthorizationServerAllowedHeaders, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPAuthorizationServerSettings. +func (in *HTTPAuthorizationServerSettings) DeepCopy() *HTTPAuthorizationServerSettings { + if in == nil { + return nil + } + out := new(HTTPAuthorizationServerSettings) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPDirectResponsePolicy) DeepCopyInto(out *HTTPDirectResponsePolicy) { *out = *in diff --git a/apis/projectcontour/v1alpha1/extensionservice.go b/apis/projectcontour/v1alpha1/extensionservice.go index c074de546e7..c936f98c786 100644 --- a/apis/projectcontour/v1alpha1/extensionservice.go +++ b/apis/projectcontour/v1alpha1/extensionservice.go @@ -62,7 +62,7 @@ type ExtensionServiceTarget struct { // ExtensionServiceSpec defines the desired state of an ExtensionService resource. type ExtensionServiceSpec struct { // Services specifies the set of Kubernetes Service resources that - // receive GRPC extension API requests. + // receive extension API requests. // If no weights are specified for any of the entries in // this array, traffic will be spread evenly across all the // services. @@ -78,15 +78,15 @@ type ExtensionServiceSpec struct { UpstreamValidation *contour_v1.UpstreamValidation `json:"validation,omitempty"` // Protocol may be used to specify (or override) the protocol used to reach this Service. - // Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + // Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. // // +optional - // +kubebuilder:validation:Enum=h2;h2c + // +kubebuilder:validation:Enum=http/1.1;h2;h2c Protocol *string `json:"protocol,omitempty"` - // The policy for load balancing GRPC service requests. Note that the + // The policy for load balancing service requests. Note that the // `Cookie` and `RequestHash` load balancing strategies cannot be used - // here. + // here for GRPC service requests. // // +optional LoadBalancerPolicy *contour_v1.LoadBalancerPolicy `json:"loadBalancerPolicy,omitempty"` diff --git a/changelogs/unreleased/7418-therealak12-minor.md b/changelogs/unreleased/7418-therealak12-minor.md new file mode 100644 index 00000000000..8b33df50489 --- /dev/null +++ b/changelogs/unreleased/7418-therealak12-minor.md @@ -0,0 +1,5 @@ +## Contour now supports HTTP external authorization services + +With this change, Contour supports HTTP external authorization services in addition to gRPC. +This expands compatibility with a broader range of authorization providers and +allows operators to choose the protocol that best fits their environment. diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 16a00bdd283..9b291279a73 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -836,6 +836,34 @@ func (s *Server) setupGlobalExternalAuthentication(contourConfiguration contour_ Context: context, } + switch contourConfiguration.GlobalExternalAuthorization.ServiceAPIType { + case contour_v1.AuthorizationGRPCService: + globalExternalAuthConfig.ServiceAPIType = contour_v1.AuthorizationGRPCService + case contour_v1.AuthorizationHTTPService: + globalExternalAuthConfig.ServiceAPIType = contour_v1.AuthorizationHTTPService + if contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings != nil { + globalExternalAuthConfig.HTTPPathPrefix = contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.PathPrefix + + // globalExternalAuthConfig.HttpServerURI = contourConfiguration.GlobalExternalAuthorization.HttpServerSettings.ServerURI + + if len(contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedAuthorizationHeaders) > 0 { + if err := dag.ExternalAuthAllowedHeadersValid(contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedAuthorizationHeaders); err != nil { + return nil, err + } + + globalExternalAuthConfig.HTTPAllowedAuthorizationHeaders = contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedAuthorizationHeaders + } + + if len(contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedUpstreamHeaders) > 0 { + if err := dag.ExternalAuthAllowedHeadersValid(contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedUpstreamHeaders); err != nil { + return nil, err + } + + globalExternalAuthConfig.HTTPAllowedUpstreamHeaders = contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedUpstreamHeaders + } + } + } + if contourConfiguration.GlobalExternalAuthorization.WithRequestBody != nil { globalExternalAuthConfig.WithRequestBody = &dag.AuthorizationServerBufferSettings{ PackAsBytes: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.PackAsBytes, diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index 8c4ee27d73b..9ddb8e4a5cf 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -457,10 +457,15 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co Name: nsedName.Name, Namespace: nsedName.Namespace, }, + ServiceAPIType: ctx.Config.GlobalExternalAuthorization.ServiceAPIType, ResponseTimeout: ctx.Config.GlobalExternalAuthorization.ResponseTimeout, FailOpen: ctx.Config.GlobalExternalAuthorization.FailOpen, } + if ctx.Config.GlobalExternalAuthorization.HTTPServerSettings != nil { + globalExtAuth.HTTPServerSettings = ctx.Config.GlobalExternalAuthorization.HTTPServerSettings + } + if ctx.Config.GlobalExternalAuthorization.AuthPolicy != nil { globalExtAuth.AuthPolicy = &contour_v1.AuthorizationPolicy{ Disabled: ctx.Config.GlobalExternalAuthorization.AuthPolicy.Disabled, diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml index bd251103c8b..8348305dee8 100644 --- a/examples/contour/01-crds.yaml +++ b/examples/contour/01-crds.yaml @@ -727,6 +727,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -735,6 +808,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -4675,6 +4757,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -4683,6 +4838,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -5298,9 +5462,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -5374,8 +5538,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -5391,7 +5556,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -7650,6 +7815,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -7658,6 +7896,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index d89221430ae..b9c74917f28 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -946,6 +946,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -954,6 +1027,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -4894,6 +4976,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -4902,6 +5057,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -5517,9 +5681,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -5593,8 +5757,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -5610,7 +5775,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -7869,6 +8034,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -7877,6 +8115,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index 2496dac11ac..4e34ef1d20e 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -738,6 +738,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -746,6 +819,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -4686,6 +4768,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -4694,6 +4849,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -5309,9 +5473,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -5385,8 +5549,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -5402,7 +5567,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -7661,6 +7826,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -7669,6 +7907,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index 8c125a49e2c..b9d01fb463c 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -763,6 +763,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -771,6 +844,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -4711,6 +4793,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -4719,6 +4874,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -5334,9 +5498,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -5410,8 +5574,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -5427,7 +5592,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -7686,6 +7851,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -7694,6 +7932,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index 837cf752cef..15f3614f10c 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -946,6 +946,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that must + be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string matching + should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the header + name. + type: string + suffix: + description: Suffix defines a suffix match for a header + name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of authorization + request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -954,6 +1027,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -4894,6 +4976,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -4902,6 +5057,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. @@ -5517,9 +5681,9 @@ spec: type: object loadBalancerPolicy: description: |- - The policy for load balancing GRPC service requests. Note that the + The policy for load balancing service requests. Note that the `Cookie` and `RequestHash` load balancing strategies cannot be used - here. + here for GRPC service requests. properties: requestHashPolicies: description: |- @@ -5593,8 +5757,9 @@ spec: protocol: description: |- Protocol may be used to specify (or override) the protocol used to reach this Service. - Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations. + Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations. enum: + - http/1.1 - h2 - h2c type: string @@ -5610,7 +5775,7 @@ spec: services: description: |- Services specifies the set of Kubernetes Service resources that - receive GRPC extension API requests. + receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -7869,6 +8034,79 @@ spec: set in most cases. It is intended for use only while migrating applications from internal authorization to Contour external authorization. type: boolean + httpSettings: + description: HTTPAuthorizationServerSettings defines configurations + for interacting with an external HTTP authorization server. + properties: + allowedAuthorizationHeaders: + description: |- + AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + allowedUpstreamHeaders: + description: |- + AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + Coexistent headers will be overridden. + items: + description: |- + HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers + in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user + experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. + properties: + contains: + description: Contains specifies a substring that + must be present in the header name. + type: string + exact: + description: Exact specifies a string that the header + name must be equal to. + type: string + ignoreCase: + description: IgnoreCase specifies whether string + matching should be case-insensitive. + type: boolean + prefix: + description: Prefix defines a prefix match for the + header name. + type: string + suffix: + description: Suffix defines a suffix match for a + header name. + type: string + type: object + type: array + pathPrefix: + description: PathPrefix Sets a prefix to the value of + authorization request header Path. + type: string + type: object responseTimeout: description: |- ResponseTimeout configures maximum time to wait for a check response from the authorization server. @@ -7877,6 +8115,15 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string + serviceAPIType: + default: grpc + description: |- + ServiceAPIType sets the protocol used to communicate with + the external authorization server. + enum: + - http + - grpc + type: string withRequestBody: description: WithRequestBody specifies configuration for sending the client request's body to authorization server. diff --git a/internal/dag/conditions.go b/internal/dag/conditions.go index 8b4f542ce9c..5fa207f0866 100644 --- a/internal/dag/conditions.go +++ b/internal/dag/conditions.go @@ -407,6 +407,44 @@ func queryParameterMatchConditionsValid(conditions []contour_v1.MatchCondition) return nil } +// ExternalAuthAllowedHeadersValid validates that the allowed header conditions within a +// slice of HTTPAuthorizationServerAllowedHeaders are valid. Specifically, it returns an error for +// any of the following scenarios: +// - no conditions are set +// - more than one condition is set in the same allowed header condition branch +// - invalid regular expression is specified for the Regex condition +func ExternalAuthAllowedHeadersValid(allowedHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders) error { + for _, allowedHeader := range allowedHeaders { + sum := 0 + + if allowedHeader.Exact != "" { + sum++ + } + + if allowedHeader.Prefix != "" { + sum++ + } + + if allowedHeader.Suffix != "" { + sum++ + } + + if allowedHeader.Contains != "" { + sum++ + } + + if sum == 0 { + return errors.New("one of prefix, suffix, exact or contains is required for each allowedHeader") + } + + if sum > 1 { + return errors.New("only one of prefix, suffix, exact, and contains should be set in the allowedHeader") + } + } + + return nil +} + // ValidateRegex returns an error if the supplied // RE2 regex syntax is invalid. func ValidateRegex(regex string) error { diff --git a/internal/dag/dag.go b/internal/dag/dag.go index 0e75755abfd..8d9134ffce5 100644 --- a/internal/dag/dag.go +++ b/internal/dag/dag.go @@ -27,6 +27,7 @@ import ( core_v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + contour_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" "github.com/projectcontour/contour/internal/status" "github.com/projectcontour/contour/internal/timeout" ) @@ -861,6 +862,29 @@ type IPFilterRule struct { // ExternalAuthorization contains the configuration for enabling // the ExtAuthz filter. type ExternalAuthorization struct { + // ServiceAPIType defines the external authorization service API type. + // It indicates the protocol implemented by the external server, specifying whether it's a raw HTTP authorization server + // or a gRPC authorization server. + ServiceAPIType contour_v1.AuthorizationServiceAPIType + + // HTTPAllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. + // Note that in addition to the the user’s supplied matchers, Host, Method, Path, Content-Length, and Authorization are additionally included in the list. + HTTPAllowedAuthorizationHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders + + // HTTPAllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. + // Note that coexistent headers will be overridden. + HTTPAllowedUpstreamHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders + + // HTTPPathPrefix Sets a prefix to the value of authorization request header Path. + HTTPPathPrefix string + + // Note: This field is not used by Envoy + // https://github.com/envoyproxy/envoy/issues/5357 + // + // HttpServerURI sets the URI of the external HTTP authorization server to which authorization requests must be sent. + // Only required for http services. + // HttpServerURI string + // AuthorizationService points to the extension that client // requests are forwarded to for authorization. If nil, no // authorization is enabled for this host. diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index 8a61bc5f132..9044ad568c0 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -1403,6 +1403,40 @@ func (p *HTTPProxyProcessor) computeVirtualHostAuthorization(auth *contour_v1.Au AuthorizationResponseTimeout: *respTimeout, } + switch auth.ServiceAPIType { + case contour_v1.AuthorizationGRPCService: + globalExternalAuthorization.ServiceAPIType = contour_v1.AuthorizationGRPCService + case contour_v1.AuthorizationHTTPService: + globalExternalAuthorization.ServiceAPIType = contour_v1.AuthorizationHTTPService + if auth.HTTPServerSettings != nil { + globalExternalAuthorization.HTTPPathPrefix = auth.HTTPServerSettings.PathPrefix + + // globalExternalAuthorization.HttpServerURI = auth.HttpServerSettings.ServerURI + + if len(auth.HTTPServerSettings.AllowedAuthorizationHeaders) > 0 { + if err := ExternalAuthAllowedHeadersValid(auth.HTTPServerSettings.AllowedAuthorizationHeaders); err != nil { + validCond.AddErrorf(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", + "Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedAuthorizationHeaders is invalid: %s", err) + + return nil + } + + globalExternalAuthorization.HTTPAllowedAuthorizationHeaders = auth.HTTPServerSettings.AllowedAuthorizationHeaders + } + + if len(auth.HTTPServerSettings.AllowedUpstreamHeaders) > 0 { + if err := ExternalAuthAllowedHeadersValid(auth.HTTPServerSettings.AllowedUpstreamHeaders); err != nil { + validCond.AddErrorf(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", + "Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedUpstreamHeaders is invalid: %s", err) + + return nil + } + + globalExternalAuthorization.HTTPAllowedUpstreamHeaders = auth.HTTPServerSettings.AllowedUpstreamHeaders + } + } + } + if auth.WithRequestBody != nil { maxRequestBytes := defaultMaxRequestBytes if auth.WithRequestBody.MaxRequestBytes != 0 { diff --git a/internal/envoy/v3/cluster.go b/internal/envoy/v3/cluster.go index 4717b966f8b..c6b876a32b5 100644 --- a/internal/envoy/v3/cluster.go +++ b/internal/envoy/v3/cluster.go @@ -162,10 +162,10 @@ func (e *EnvoyGen) ExtensionCluster(ext *dag.ExtensionCluster) *envoy_config_clu // TODO(jpeach): Externalname service support in https://github.com/projectcontour/contour/issues/2875 - http2Version := HTTPVersionAuto + httpVersion := HTTPVersionAuto switch ext.Protocol { case "h2": - http2Version = HTTPVersion2 + httpVersion = HTTPVersion2 cluster.TransportSocket = UpstreamTLSTransportSocket( e.UpstreamTLSContext( ext.UpstreamValidation, @@ -176,13 +176,15 @@ func (e *EnvoyGen) ExtensionCluster(ext *dag.ExtensionCluster) *envoy_config_clu ), ) case "h2c": - http2Version = HTTPVersion2 + httpVersion = HTTPVersion2 + case "http/1.1": + httpVersion = HTTPVersion1 } if ext.ClusterTimeoutPolicy.ConnectTimeout > time.Duration(0) { cluster.ConnectTimeout = durationpb.New(ext.ClusterTimeoutPolicy.ConnectTimeout) } - cluster.TypedExtensionProtocolOptions = protocolOptions(http2Version, ext.ClusterTimeoutPolicy.IdleConnectionTimeout, nil) + cluster.TypedExtensionProtocolOptions = protocolOptions(httpVersion, ext.ClusterTimeoutPolicy.IdleConnectionTimeout, nil) applyCircuitBreakers(cluster, ext.CircuitBreakers) diff --git a/internal/envoy/v3/listener.go b/internal/envoy/v3/listener.go index 51a8a53bebe..511edad2f3f 100644 --- a/internal/envoy/v3/listener.go +++ b/internal/envoy/v3/listener.go @@ -41,12 +41,14 @@ import ( envoy_filter_network_http_connection_manager_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" envoy_filter_network_tcp_proxy_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3" envoy_transport_socket_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" + envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" "github.com/envoyproxy/go-control-plane/pkg/wellknown" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/wrapperspb" + contour_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "github.com/projectcontour/contour/internal/dag" "github.com/projectcontour/contour/internal/envoy" @@ -64,7 +66,7 @@ const ( HTTPVersion3 HTTPVersionType = envoy_filter_network_http_connection_manager_v3.HttpConnectionManager_HTTP3 ) -// ProtoNamesForVersions returns the slice of ALPN protocol names for the give HTTP versions. +// ProtoNamesForVersions returns the slice of ALPN protocol names for the given HTTP versions. func ProtoNamesForVersions(versions ...HTTPVersionType) []string { protocols := map[HTTPVersionType]string{ HTTPVersion1: "http/1.1", @@ -837,6 +839,46 @@ end } } +// ExternalAuthzAllowedHeaders returns the slice of StringMatcher for a given slice of HTTPAuthorizationServerAllowedHeaders. +func ExternalAuthzAllowedHeaders(allowedHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders) []*envoy_matcher_v3.StringMatcher { + var allowedHeaderPatterns []*envoy_matcher_v3.StringMatcher + + for _, allowedHeader := range allowedHeaders { + switch { + case allowedHeader.Exact != "": + allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{ + Exact: allowedHeader.Exact, + }, + IgnoreCase: allowedHeader.IgnoreCase, + }) + case allowedHeader.Prefix != "": + allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{ + Prefix: allowedHeader.Prefix, + }, + IgnoreCase: allowedHeader.IgnoreCase, + }) + case allowedHeader.Suffix != "": + allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Suffix{ + Suffix: allowedHeader.Suffix, + }, + IgnoreCase: allowedHeader.IgnoreCase, + }) + case allowedHeader.Contains != "": + allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ + MatchPattern: &envoy_matcher_v3.StringMatcher_Contains{ + Contains: allowedHeader.Contains, + }, + IgnoreCase: allowedHeader.IgnoreCase, + }) + } + } + + return allowedHeaderPatterns +} + // FilterExternalAuthz returns an `ext_authz` filter configured with the // requested parameters. func FilterExternalAuthz(externalAuthorization *dag.ExternalAuthorization) *envoy_filter_network_http_connection_manager_v3.HttpFilter { @@ -852,11 +894,52 @@ func FilterExternalAuthz(externalAuthorization *dag.ExternalAuthorization) *envo StatusOnError: &envoy_type_v3.HttpStatus{ Code: envoy_type_v3.StatusCode_Forbidden, }, - MetadataContextNamespaces: []string{}, - IncludePeerCertificate: true, // TODO(jpeach): When we move to the Envoy v4 API, propagate the // `transport_api_version` from ExtensionServiceSpec ProtocolVersion. - TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + IncludePeerCertificate: true, + } + + switch externalAuthorization.ServiceAPIType { + case contour_v1.AuthorizationGRPCService: + authConfig.Services = &envoy_filter_http_ext_authz_v3.ExtAuthz_GrpcService{ + GrpcService: grpcService(externalAuthorization.AuthorizationService.Name, externalAuthorization.AuthorizationService.SNI, externalAuthorization.AuthorizationResponseTimeout), + } + authConfig.MetadataContextNamespaces = []string{} + + case contour_v1.AuthorizationHTTPService: + extAuthzService := &envoy_filter_http_ext_authz_v3.ExtAuthz_HttpService{ + HttpService: &envoy_filter_http_ext_authz_v3.HttpService{ + ServerUri: &envoy_config_core_v3.HttpUri{ + // Uri: externalAuthorization.HttpServerURI, + Uri: "http://dummy/", + HttpUpstreamType: &envoy_config_core_v3.HttpUri_Cluster{ + Cluster: externalAuthorization.AuthorizationService.Name, + }, + Timeout: envoy.Timeout(externalAuthorization.AuthorizationResponseTimeout), + }, + }, + } + + if pathPrefix := externalAuthorization.HTTPPathPrefix; pathPrefix != "" { + extAuthzService.HttpService.PathPrefix = pathPrefix + } + + if len(externalAuthorization.HTTPAllowedAuthorizationHeaders) > 0 { + authConfig.AllowedHeaders = &envoy_matcher_v3.ListStringMatcher{ + Patterns: ExternalAuthzAllowedHeaders(externalAuthorization.HTTPAllowedAuthorizationHeaders), + } + } + + if len(externalAuthorization.HTTPAllowedUpstreamHeaders) > 0 { + extAuthzService.HttpService.AuthorizationResponse = &envoy_filter_http_ext_authz_v3.AuthorizationResponse{ + AllowedUpstreamHeaders: &envoy_matcher_v3.ListStringMatcher{ + Patterns: ExternalAuthzAllowedHeaders(externalAuthorization.HTTPAllowedUpstreamHeaders), + }, + } + } + + authConfig.Services = extAuthzService } if externalAuthorization.AuthorizationServerWithRequestBody != nil { diff --git a/internal/featuretests/v3/authorization_test.go b/internal/featuretests/v3/authorization_test.go index efbb9ae21e0..981da972ad1 100644 --- a/internal/featuretests/v3/authorization_test.go +++ b/internal/featuretests/v3/authorization_test.go @@ -24,6 +24,7 @@ import ( envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" envoy_filter_http_ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3" envoy_service_discovery_v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" + envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" "google.golang.org/protobuf/types/known/durationpb" core_v1 "k8s.io/api/core/v1" @@ -51,6 +52,20 @@ func grpcCluster(name string) *envoy_filter_http_ext_authz_v3.ExtAuthz_GrpcServi } } +func httpCluster(name string) *envoy_filter_http_ext_authz_v3.ExtAuthz_HttpService { + return &envoy_filter_http_ext_authz_v3.ExtAuthz_HttpService{ + HttpService: &envoy_filter_http_ext_authz_v3.HttpService{ + ServerUri: &envoy_config_core_v3.HttpUri{ + Uri: "http://dummy/", + HttpUpstreamType: &envoy_config_core_v3.HttpUri_Cluster{ + Cluster: name, + }, + Timeout: durationpb.New(defaultResponseTimeout), + }, + }, + } +} + func authzResponseTimeout(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { const fqdn = "failopen.projectcontour.io" @@ -62,6 +77,7 @@ func authzResponseTimeout(t *testing.T, rh ResourceEventHandlerWrapper, c *Conto Namespace: "auth", Name: "extension", }, + ServiceAPIType: contour_v1.AuthorizationGRPCService, ResponseTimeout: "10m", }). WithSpec(contour_v1.HTTPProxySpec{ @@ -120,6 +136,7 @@ func authzInvalidResponseTimeout(t *testing.T, rh ResourceEventHandlerWrapper, c Namespace: "auth", Name: "extension", }, + ServiceAPIType: contour_v1.AuthorizationGRPCService, ResponseTimeout: "invalid-timeout", }). WithSpec(contour_v1.HTTPProxySpec{ @@ -147,7 +164,8 @@ func authzFailOpen(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { Namespace: "auth", Name: "extension", }, - FailOpen: true, + ServiceAPIType: contour_v1.AuthorizationGRPCService, + FailOpen: true, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -199,6 +217,7 @@ func authzFallbackIncompat(t *testing.T, rh ResourceEventHandlerWrapper, c *Cont Namespace: "auth", Name: "extension", }, + ServiceAPIType: contour_v1.AuthorizationGRPCService, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -230,6 +249,7 @@ func authzOverrideDisabled(t *testing.T, rh ResourceEventHandlerWrapper, c *Cont WithCertificate("certificate"). WithAuthServer(contour_v1.AuthorizationServer{ ExtensionServiceRef: extensionRef, + ServiceAPIType: contour_v1.AuthorizationGRPCService, AuthPolicy: &contour_v1.AuthorizationPolicy{Disabled: false}, }). WithSpec(contour_v1.HTTPProxySpec{ @@ -340,6 +360,7 @@ func authzMergeRouteContext(t *testing.T, rh ResourceEventHandlerWrapper, c *Con Namespace: "auth", Name: "extension", }, + ServiceAPIType: contour_v1.AuthorizationGRPCService, AuthPolicy: &contour_v1.AuthorizationPolicy{ Context: map[string]string{ "root-element": "root", @@ -419,7 +440,9 @@ func authzInvalidReference(t *testing.T, rh ResourceEventHandlerWrapper, c *Cont invalid := fixture.NewProxy("proxy"). WithFQDN(fqdn). WithCertificate("certificate"). - WithAuthServer(contour_v1.AuthorizationServer{}). + WithAuthServer(contour_v1.AuthorizationServer{ + ServiceAPIType: contour_v1.AuthorizationGRPCService, + }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ Services: []contour_v1.Service{{ @@ -509,7 +532,8 @@ func authzWithRequestBodyBufferSettings(t *testing.T, rh ResourceEventHandlerWra Namespace: "auth", Name: "extension", }, - FailOpen: true, + ServiceAPIType: contour_v1.AuthorizationGRPCService, + FailOpen: true, WithRequestBody: &contour_v1.AuthorizationServerBufferSettings{ MaxRequestBytes: 100, AllowPartialMessage: true, @@ -562,16 +586,445 @@ func authzWithRequestBodyBufferSettings(t *testing.T, rh ResourceEventHandlerWra }).Status(p).IsValid() } +func AuthzTypeGRPC(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typegrpc.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceAPIType: contour_v1.AuthorizationGRPCService, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: grpcCluster("extension/auth/extension"), + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func authzTypeHTTP(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceAPIType: contour_v1.AuthorizationHTTPService, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: httpCluster("extension/auth/extension"), + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func AuthzTypeHTTPWithPathPrefix(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceAPIType: contour_v1.AuthorizationHTTPService, + HTTPServerSettings: &contour_v1.HTTPAuthorizationServerSettings{ + PathPrefix: "/auth?", + }, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + cluster := httpCluster("extension/auth/extension") + cluster.HttpService.PathPrefix = "/auth?" + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: cluster, + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func AuthzTypeHTTPWithAllowedAuthorizationHeaders(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceAPIType: contour_v1.AuthorizationHTTPService, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedAuthorizationHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {IgnoreCase: false}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", `Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedAuthorizationHeaders is invalid: one of prefix, suffix, exact or contains is required for each allowedHeader`) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedAuthorizationHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {Exact: "test", Prefix: "test", IgnoreCase: false}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", `Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedAuthorizationHeaders is invalid: only one of prefix, suffix, exact, and contains should be set in the allowedHeader`) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedAuthorizationHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {Prefix: "test1", IgnoreCase: false}, + {Exact: "test2", IgnoreCase: true}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: httpCluster("extension/auth/extension"), + AllowedHeaders: &envoy_matcher_v3.ListStringMatcher{ + Patterns: []*envoy_matcher_v3.StringMatcher{ + {MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{Prefix: "test1"}, IgnoreCase: false}, + {MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{Exact: "test2"}, IgnoreCase: true}, + }, + }, + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func AuthzTypeHTTPWithAllowedUpstreamHeaders(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceAPIType: contour_v1.AuthorizationHTTPService, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedUpstreamHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {IgnoreCase: false}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", `Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedUpstreamHeaders is invalid: one of prefix, suffix, exact or contains is required for each allowedHeader`) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedUpstreamHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {Exact: "test", Prefix: "test", IgnoreCase: false}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", `Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedUpstreamHeaders is invalid: only one of prefix, suffix, exact, and contains should be set in the allowedHeader`) + + p.Spec.VirtualHost.Authorization.HTTPServerSettings = &contour_v1.HTTPAuthorizationServerSettings{ + AllowedUpstreamHeaders: []contour_v1.HTTPAuthorizationServerAllowedHeaders{ + {Prefix: "test1", IgnoreCase: false}, + {Exact: "test2", IgnoreCase: true}, + }, + } + + rh.OnDelete(p) + rh.OnAdd(p) + + cluster := httpCluster("extension/auth/extension") + cluster.HttpService.AuthorizationResponse = &envoy_filter_http_ext_authz_v3.AuthorizationResponse{ + AllowedUpstreamHeaders: &envoy_matcher_v3.ListStringMatcher{ + Patterns: []*envoy_matcher_v3.StringMatcher{ + {MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{Prefix: "test1"}, IgnoreCase: false}, + {MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{Exact: "test2"}, IgnoreCase: true}, + }, + }, + } + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: cluster, + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + +func AuthzTypeHTTPWithContext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typehttp.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + ServiceAPIType: contour_v1.AuthorizationHTTPService, + AuthPolicy: &contour_v1.AuthorizationPolicy{ + Context: map[string]string{ + "k1": "v1", + "k2": "v2", + }, + }, + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, statsListener()), + }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthContextForHTTP", `Spec.Virtualhost.Authorization.AuthPolicy.Context are only applied to grpc service type`) +} + func TestAuthorization(t *testing.T) { subtests := map[string]func(*testing.T, ResourceEventHandlerWrapper, *Contour){ - "MissingExtension": authzInvalidReference, - "MergeRouteContext": authzMergeRouteContext, - "OverrideDisabled": authzOverrideDisabled, - "FallbackIncompat": authzFallbackIncompat, - "FailOpen": authzFailOpen, - "ResponseTimeout": authzResponseTimeout, - "InvalidResponseTimeout": authzInvalidResponseTimeout, - "AuthzWithRequestBodyBufferSettings": authzWithRequestBodyBufferSettings, + "MissingExtension": authzInvalidReference, + "MergeRouteContext": authzMergeRouteContext, + "OverrideDisabled": authzOverrideDisabled, + "FallbackIncompat": authzFallbackIncompat, + "FailOpen": authzFailOpen, + "ResponseTimeout": authzResponseTimeout, + "InvalidResponseTimeout": authzInvalidResponseTimeout, + "AuthzWithRequestBodyBufferSettings": authzWithRequestBodyBufferSettings, + "AuthzTypeGRPC": AuthzTypeGRPC, + "AuthzTypeHTTP": authzTypeHTTP, + "AuthzTypeHTTPWithPathPrefix": AuthzTypeHTTPWithPathPrefix, + "AuthzTypeHTTPWithAllowedAuthorizationHeaders": AuthzTypeHTTPWithAllowedAuthorizationHeaders, + "AuthzTypeHTTPWithAllowedUpstreamHeaders": AuthzTypeHTTPWithAllowedUpstreamHeaders, } for n, f := range subtests { diff --git a/internal/featuretests/v3/envoy.go b/internal/featuretests/v3/envoy.go index e9b8a5995e1..d434381f923 100644 --- a/internal/featuretests/v3/envoy.go +++ b/internal/featuretests/v3/envoy.go @@ -249,6 +249,20 @@ func h2cCluster(c *envoy_config_cluster_v3.Cluster) *envoy_config_cluster_v3.Clu return c } +func http1Cluster(c *envoy_config_cluster_v3.Cluster) *envoy_config_cluster_v3.Cluster { + c.TypedExtensionProtocolOptions = map[string]*anypb.Any{ + "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": protobuf.MustMarshalAny( + &envoy_upstream_http_v3.HttpProtocolOptions{ + UpstreamProtocolOptions: &envoy_upstream_http_v3.HttpProtocolOptions_ExplicitHttpConfig_{ + ExplicitHttpConfig: &envoy_upstream_http_v3.HttpProtocolOptions_ExplicitHttpConfig{ + ProtocolConfig: &envoy_upstream_http_v3.HttpProtocolOptions_ExplicitHttpConfig_HttpProtocolOptions{}, + }, + }, + }), + } + return c +} + func withConnectionTimeout(c *envoy_config_cluster_v3.Cluster, timeout time.Duration, httpVersion envoy_v3.HTTPVersionType) *envoy_config_cluster_v3.Cluster { var config *envoy_upstream_http_v3.HttpProtocolOptions_ExplicitHttpConfig diff --git a/internal/featuretests/v3/extensionservice_test.go b/internal/featuretests/v3/extensionservice_test.go index a82f1fd5726..94e3f4be64c 100644 --- a/internal/featuretests/v3/extensionservice_test.go +++ b/internal/featuretests/v3/extensionservice_test.go @@ -78,7 +78,7 @@ func extBasic(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { } func extCleartext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { - rh.OnAdd(&contour_v1alpha1.ExtensionService{ + es := &contour_v1alpha1.ExtensionService{ ObjectMeta: fixture.ObjectMeta("ns/ext"), Spec: contour_v1alpha1.ExtensionServiceSpec{ Protocol: ptr.To("h2c"), @@ -87,7 +87,9 @@ func extCleartext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { {Name: "svc2", Port: 8082}, }, }, - }) + } + + rh.OnAdd(es) c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ TypeUrl: clusterType, @@ -97,6 +99,20 @@ func extCleartext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { ), ), }) + + es.Spec.Protocol = ptr.To("http/1.1") + + rh.OnDelete(es) + rh.OnAdd(es) + + c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: clusterType, + Resources: resources(t, + DefaultCluster( + http1Cluster(cluster("extension/ns/ext", "extension/ns/ext", "extension_ns_ext")), + ), + ), + }) } func extUpstreamValidation(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { @@ -313,7 +329,7 @@ func extInvalidTimeout(_ *testing.T, rh ResourceEventHandlerWrapper, c *Contour) } func extInconsistentProto(_ *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { - rh.OnAdd(&contour_v1alpha1.ExtensionService{ + es := &contour_v1alpha1.ExtensionService{ ObjectMeta: fixture.ObjectMeta("ns/ext"), Spec: contour_v1alpha1.ExtensionServiceSpec{ Services: []contour_v1alpha1.ExtensionServiceTarget{ @@ -325,8 +341,20 @@ func extInconsistentProto(_ *testing.T, rh ResourceEventHandlerWrapper, c *Conto SubjectName: "ext.projectcontour.io", }, }, + } + + rh.OnAdd(es) + + // Should have no clusters because Protocol and UpstreamValidation is inconsistent. + c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: clusterType, }) + es.Spec.Protocol = ptr.To("h1") + + rh.OnDelete(es) + rh.OnAdd(es) + // Should have no clusters because Protocol and UpstreamValidation is inconsistent. c.Request(clusterType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ TypeUrl: clusterType, diff --git a/internal/featuretests/v3/global_authorization_test.go b/internal/featuretests/v3/global_authorization_test.go index c2b35f58cae..4c134a6073d 100644 --- a/internal/featuretests/v3/global_authorization_test.go +++ b/internal/featuretests/v3/global_authorization_test.go @@ -47,6 +47,7 @@ var ( Name: "extension", Namespace: "auth", }, + ServiceAPIType: contour_v1.AuthorizationGRPCService, FailOpen: false, ResponseTimeout: defaultResponseTimeout.String(), AuthPolicy: &contour_v1.AuthorizationPolicy{ @@ -62,6 +63,7 @@ var ( Name: "extension", Namespace: "auth", }, + ServiceAPIType: contour_v1.AuthorizationGRPCService, FailOpen: false, ResponseTimeout: defaultResponseTimeout.String(), AuthPolicy: &contour_v1.AuthorizationPolicy{ @@ -580,6 +582,7 @@ func globalExternalAuthorizationWithTLSAuthOverride(t *testing.T, rh ResourceEve Namespace: "auth", Name: "extension", }, + ServiceAPIType: contour_v1.AuthorizationGRPCService, ResponseTimeout: defaultResponseTimeout.String(), FailOpen: true, WithRequestBody: &contour_v1.AuthorizationServerBufferSettings{ @@ -800,7 +803,8 @@ func TestGlobalAuthorization(t *testing.T) { ExtensionService: k8s.NamespacedNameFrom("auth/extension"), Timeout: timeout.DurationSetting(defaultResponseTimeout), }, - FailOpen: false, + ServiceAPIType: contour_v1.AuthorizationGRPCService, + FailOpen: false, Context: map[string]string{ "header_type": "root_config", "header_1": "message_1", diff --git a/internal/xdscache/v3/listener.go b/internal/xdscache/v3/listener.go index a0e62004d80..a9d68c347c5 100644 --- a/internal/xdscache/v3/listener.go +++ b/internal/xdscache/v3/listener.go @@ -25,6 +25,7 @@ import ( "google.golang.org/protobuf/proto" "k8s.io/apimachinery/pkg/types" + contour_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "github.com/projectcontour/contour/internal/contourconfig" "github.com/projectcontour/contour/internal/dag" @@ -202,9 +203,14 @@ type RateLimitConfig struct { type GlobalExternalAuthConfig struct { ExtensionServiceConfig - FailOpen bool - Context map[string]string - WithRequestBody *dag.AuthorizationServerBufferSettings + FailOpen bool + Context map[string]string + ServiceAPIType contour_v1.AuthorizationServiceAPIType + HTTPAllowedAuthorizationHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders + HTTPAllowedUpstreamHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders + HTTPPathPrefix string + WithRequestBody *dag.AuthorizationServerBufferSettings + // HttpServerURI string } // httpAccessLog returns the access log for the HTTP (non TLS) @@ -604,6 +610,10 @@ func httpGlobalExternalAuthConfig(config *GlobalExternalAuthConfig) *envoy_filte Name: dag.ExtensionClusterName(config.ExtensionService), SNI: config.SNI, }, + ServiceAPIType: config.ServiceAPIType, + HTTPAllowedAuthorizationHeaders: config.HTTPAllowedAuthorizationHeaders, + HTTPAllowedUpstreamHeaders: config.HTTPAllowedUpstreamHeaders, + HTTPPathPrefix: config.HTTPPathPrefix, AuthorizationFailOpen: config.FailOpen, AuthorizationResponseTimeout: config.Timeout, AuthorizationServerWithRequestBody: config.WithRequestBody, diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index 1e4c397d513..4af63ad86dc 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -761,6 +761,16 @@ type GlobalExternalAuthorization struct { // ExtensionService identifies the extension service defining the RLS, // formatted as /. ExtensionService string `yaml:"extensionService,omitempty"` + // ServiceAPIType defines the external authorization service API type. + // It indicates the protocol implemented by the external server, specifying whether it's a raw HTTP authorization server + // or a gRPC authorization server. + // + // +optional + ServiceAPIType contour_v1.AuthorizationServiceAPIType `json:"serviceAPIType,omitempty"` + // HttpAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server. + // + // +optional + HTTPServerSettings *contour_v1.HTTPAuthorizationServerSettings `json:"httpSettings,omitempty"` // AuthPolicy sets a default authorization policy for client requests. // This policy will be used unless overridden by individual routes. // diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html index f985ae33037..2c024e8cf55 100644 --- a/site/content/docs/main/config/api-reference.html +++ b/site/content/docs/main/config/api-reference.html @@ -361,6 +361,37 @@

AuthorizationServer +serviceAPIType +
+ + +AuthorizationServiceAPIType + + + + +(Optional) +

ServiceAPIType sets the protocol used to communicate with +the external authorization server.

+ + + + +httpSettings +
+ + +HTTPAuthorizationServerSettings + + + + +(Optional) +

HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server.

+ + + + authPolicy
@@ -482,6 +513,29 @@

AuthorizationSer +

AuthorizationServiceAPIType +(string alias)

+

+(Appears on: +AuthorizationServer) +

+

+

AuthorizationServiceAPIType indicates the protocol +implemented by the external authorization server.

+

+ + + + + + + + + + + + +
ValueDescription

"grpc"

"http"

CORSHeaderValue (string alias)

@@ -1281,6 +1335,156 @@

GlobalRateLimitPolicy +

HTTPAuthorizationServerAllowedHeaders +

+

+(Appears on: +HTTPAuthorizationServerSettings) +

+

+

HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers +in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user +experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+exact +
+ +string + +
+(Optional) +

Exact specifies a string that the header name must be equal to.

+
+prefix +
+ +string + +
+(Optional) +

Prefix defines a prefix match for the header name.

+
+suffix +
+ +string + +
+(Optional) +

Suffix defines a suffix match for a header name.

+
+contains +
+ +string + +
+(Optional) +

Contains specifies a substring that must be present in the header name.

+
+ignoreCase +
+ +bool + +
+(Optional) +

IgnoreCase specifies whether string matching should be case-insensitive.

+
+

HTTPAuthorizationServerSettings +

+

+(Appears on: +AuthorizationServer) +

+

+

HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+pathPrefix +
+ +string + +
+(Optional) +

PathPrefix Sets a prefix to the value of authorization request header Path.

+
+allowedAuthorizationHeaders +
+ + +[]HTTPAuthorizationServerAllowedHeaders + + +
+(Optional) +

AllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. +Host, Method, Path, Content-Length, and Authorization headers are additionally included in the list.

+
+allowedUpstreamHeaders +
+ + +[]HTTPAuthorizationServerAllowedHeaders + + +
+(Optional) +

AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. +Coexistent headers will be overridden.

+

HTTPDirectResponsePolicy

@@ -5437,7 +5641,7 @@

ExtensionService

Services specifies the set of Kubernetes Service resources that -receive GRPC extension API requests. +receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -5471,7 +5675,7 @@

ExtensionService (Optional)

Protocol may be used to specify (or override) the protocol used to reach this Service. -Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations.

+Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations.

@@ -5486,9 +5690,9 @@

ExtensionService (Optional) -

The policy for load balancing GRPC service requests. Note that the +

The policy for load balancing service requests. Note that the Cookie and RequestHash load balancing strategies cannot be used -here.

+here for GRPC service requests.

@@ -7662,7 +7866,7 @@

ExtensionServiceSpec

Services specifies the set of Kubernetes Service resources that -receive GRPC extension API requests. +receive extension API requests. If no weights are specified for any of the entries in this array, traffic will be spread evenly across all the services. @@ -7696,7 +7900,7 @@

ExtensionServiceSpec (Optional)

Protocol may be used to specify (or override) the protocol used to reach this Service. -Values may be h2 or h2c. If omitted, protocol-selection falls back on Service annotations.

+Values may be h2, h2c or http/1.1. If omitted, protocol-selection falls back on Service annotations.

@@ -7711,9 +7915,9 @@

ExtensionServiceSpec (Optional) -

The policy for load balancing GRPC service requests. Note that the +

The policy for load balancing service requests. Note that the Cookie and RequestHash load balancing strategies cannot be used -here.

+here for GRPC service requests.

From 0a50822f2145f6f334b6e142aa56d60f9cd0629f Mon Sep 17 00:00:00 2001 From: therealak12 Date: Sat, 11 Apr 2026 10:37:29 +0330 Subject: [PATCH 2/2] Apply Tero Saarni's suggestions Signed-off-by: therealak12 --- apis/projectcontour/v1/httpproxy.go | 21 ++-- cmd/contour/serve.go | 49 ++------ cmd/contour/servecontext.go | 2 +- examples/contour/01-crds.yaml | 69 +++++++++-- examples/render/contour-deployment.yaml | 69 +++++++++-- .../render/contour-gateway-provisioner.yaml | 69 +++++++++-- examples/render/contour-gateway.yaml | 69 +++++++++-- examples/render/contour.yaml | 69 +++++++++-- internal/dag/dag.go | 39 ++++++- internal/dag/httpproxy_processor.go | 59 ++-------- internal/envoy/v3/listener.go | 44 ++++--- .../featuretests/v3/authorization_test.go | 110 ++++++++++++++---- .../v3/global_authorization_test.go | 10 +- internal/xdscache/v3/listener.go | 15 +-- pkg/config/parameters.go | 6 +- .../docs/main/config/api-reference.html | 19 +-- 16 files changed, 481 insertions(+), 238 deletions(-) diff --git a/apis/projectcontour/v1/httpproxy.go b/apis/projectcontour/v1/httpproxy.go index c30a8b243f8..e0ca3f53b46 100644 --- a/apis/projectcontour/v1/httpproxy.go +++ b/apis/projectcontour/v1/httpproxy.go @@ -239,31 +239,33 @@ type ExtensionServiceReference struct { Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"` } -// AuthorizationServiceAPIType indicates the protocol +// AuthorizationServiceType indicates the protocol // implemented by the external authorization server. -type AuthorizationServiceAPIType string +type AuthorizationServiceType string const ( - AuthorizationGRPCService AuthorizationServiceAPIType = "grpc" - AuthorizationHTTPService AuthorizationServiceAPIType = "http" + AuthorizationGRPCService AuthorizationServiceType = "grpc" + AuthorizationHTTPService AuthorizationServiceType = "http" ) // AuthorizationServer configures an external server to authenticate // client requests. The external server must implement the v3 Envoy -// external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto). +// external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto) +// or the HTTP authorization server protocol. +// +kubebuilder:validation:XValidation:message="httpSettings can only be set when serviceType is 'http'",rule="!has(self.httpSettings) || self.serviceType == 'http'" type AuthorizationServer struct { // ExtensionServiceRef specifies the extension resource that will authorize client requests. // // +optional ExtensionServiceRef ExtensionServiceReference `json:"extensionRef,omitempty"` - // ServiceAPIType sets the protocol used to communicate with + // ServiceType sets the protocol used to communicate with // the external authorization server. // // +optional // +kubebuilder:validation:Enum=http;grpc // +kubebuilder:default=grpc - ServiceAPIType AuthorizationServiceAPIType `json:"serviceAPIType,omitempty"` + ServiceType AuthorizationServiceType `json:"serviceType,omitempty"` // HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server. // @@ -311,8 +313,8 @@ type HTTPAuthorizationServerSettings struct { // +optional AllowedAuthorizationHeaders []HTTPAuthorizationServerAllowedHeaders `json:"allowedAuthorizationHeaders,omitempty"` - // AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - // Coexistent headers will be overridden. + // AllowedUpstreamHeaders specifies response headers from the authorization server + // that may be added to the original client request before sending it to the upstream. // // +optional AllowedUpstreamHeaders []HTTPAuthorizationServerAllowedHeaders `json:"allowedUpstreamHeaders,omitempty"` @@ -321,6 +323,7 @@ type HTTPAuthorizationServerSettings struct { // HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers // in the context of HTTP authorization. Regex support is intentionally excluded to simplify the user // experience and prevent potential issues. Only one of Prefix, Exact, Suffix or Contains must be provided. +// +kubebuilder:validation:XValidation:message="only one of prefix, suffix, exact, and contains should be set in the allowedHeader",rule="(has(self.exact) ? 1 : 0) + (has(self.prefix) ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) ? 1 : 0) == 1" type HTTPAuthorizationServerAllowedHeaders struct { // Exact specifies a string that the header name must be equal to. // diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 9b291279a73..1e449f0a9c1 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -830,48 +830,17 @@ func (s *Server) setupGlobalExternalAuthentication(contourConfiguration contour_ context = contourConfiguration.GlobalExternalAuthorization.AuthPolicy.Context } - globalExternalAuthConfig := &xdscache_v3.GlobalExternalAuthConfig{ - ExtensionServiceConfig: extensionSvcConfig, - FailOpen: contourConfiguration.GlobalExternalAuthorization.FailOpen, - Context: context, + var validCond contour_v1.DetailedCondition + extAuth := dag.NewExternalAuthorization(contourConfiguration.GlobalExternalAuthorization, &validCond) + if len(validCond.Errors) > 0 { + return nil, fmt.Errorf("%s", validCond.Errors[0].Message) } - switch contourConfiguration.GlobalExternalAuthorization.ServiceAPIType { - case contour_v1.AuthorizationGRPCService: - globalExternalAuthConfig.ServiceAPIType = contour_v1.AuthorizationGRPCService - case contour_v1.AuthorizationHTTPService: - globalExternalAuthConfig.ServiceAPIType = contour_v1.AuthorizationHTTPService - if contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings != nil { - globalExternalAuthConfig.HTTPPathPrefix = contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.PathPrefix - - // globalExternalAuthConfig.HttpServerURI = contourConfiguration.GlobalExternalAuthorization.HttpServerSettings.ServerURI - - if len(contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedAuthorizationHeaders) > 0 { - if err := dag.ExternalAuthAllowedHeadersValid(contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedAuthorizationHeaders); err != nil { - return nil, err - } - - globalExternalAuthConfig.HTTPAllowedAuthorizationHeaders = contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedAuthorizationHeaders - } - - if len(contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedUpstreamHeaders) > 0 { - if err := dag.ExternalAuthAllowedHeadersValid(contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedUpstreamHeaders); err != nil { - return nil, err - } - - globalExternalAuthConfig.HTTPAllowedUpstreamHeaders = contourConfiguration.GlobalExternalAuthorization.HTTPServerSettings.AllowedUpstreamHeaders - } - } - } - - if contourConfiguration.GlobalExternalAuthorization.WithRequestBody != nil { - globalExternalAuthConfig.WithRequestBody = &dag.AuthorizationServerBufferSettings{ - PackAsBytes: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.PackAsBytes, - AllowPartialMessage: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.AllowPartialMessage, - MaxRequestBytes: contourConfiguration.GlobalExternalAuthorization.WithRequestBody.MaxRequestBytes, - } - } - return globalExternalAuthConfig, nil + return &xdscache_v3.GlobalExternalAuthConfig{ + ExtensionServiceConfig: extensionSvcConfig, + ExternalAuthorization: *extAuth, + Context: context, + }, nil } func (s *Server) setupDebugService(debugConfig contour_v1alpha1.DebugConfig, builder *dag.Builder) error { diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index 9ddb8e4a5cf..d4f09ce5b4a 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -457,7 +457,7 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co Name: nsedName.Name, Namespace: nsedName.Namespace, }, - ServiceAPIType: ctx.Config.GlobalExternalAuthorization.ServiceAPIType, + ServiceType: ctx.Config.GlobalExternalAuthorization.ServiceType, ResponseTimeout: ctx.Config.GlobalExternalAuthorization.ResponseTimeout, FailOpen: ctx.Config.GlobalExternalAuthorization.FailOpen, } diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml index 8348305dee8..c4c8f39fc9b 100644 --- a/examples/contour/01-crds.yaml +++ b/examples/contour/01-crds.yaml @@ -762,11 +762,17 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -794,6 +800,12 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of authorization @@ -808,10 +820,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -838,6 +850,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -4792,11 +4807,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -4824,6 +4845,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -4838,10 +4865,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -4868,6 +4895,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -7850,11 +7880,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -7882,6 +7918,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -7896,10 +7938,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -7926,6 +7968,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index b9c74917f28..264be05fbe3 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -981,11 +981,17 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -1013,6 +1019,12 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of authorization @@ -1027,10 +1039,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -1057,6 +1069,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -5011,11 +5026,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -5043,6 +5064,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -5057,10 +5084,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -5087,6 +5114,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -8069,11 +8099,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -8101,6 +8137,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -8115,10 +8157,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -8145,6 +8187,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index 4e34ef1d20e..3a175d8ca03 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -773,11 +773,17 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -805,6 +811,12 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of authorization @@ -819,10 +831,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -849,6 +861,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -4803,11 +4818,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -4835,6 +4856,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -4849,10 +4876,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -4879,6 +4906,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -7861,11 +7891,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -7893,6 +7929,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -7907,10 +7949,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -7937,6 +7979,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index b9d01fb463c..77142750f4c 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -798,11 +798,17 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -830,6 +836,12 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of authorization @@ -844,10 +856,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -874,6 +886,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -4828,11 +4843,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -4860,6 +4881,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -4874,10 +4901,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -4904,6 +4931,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -7886,11 +7916,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -7918,6 +7954,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -7932,10 +7974,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -7962,6 +8004,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index 15f3614f10c..60521a03de8 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -981,11 +981,17 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -1013,6 +1019,12 @@ spec: name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) ? + 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of authorization @@ -1027,10 +1039,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -1057,6 +1069,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -5011,11 +5026,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -5043,6 +5064,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -5057,10 +5084,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -5087,6 +5114,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' health: description: |- Health defines the endpoints Contour uses to serve health checks. @@ -8069,11 +8099,17 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array allowedUpstreamHeaders: description: |- - AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. - Coexistent headers will be overridden. + AllowedUpstreamHeaders specifies response headers from the authorization server + that may be added to the original client request before sending it to the upstream. items: description: |- HTTPAuthorizationServerAllowedHeaders specifies how to conditionally match against allowed headers @@ -8101,6 +8137,12 @@ spec: header name. type: string type: object + x-kubernetes-validations: + - message: only one of prefix, suffix, exact, and contains + should be set in the allowedHeader + rule: '(has(self.exact) ? 1 : 0) + (has(self.prefix) + ? 1 : 0) + (has(self.suffix) ? 1 : 0) + (has(self.contains) + ? 1 : 0) == 1' type: array pathPrefix: description: PathPrefix Sets a prefix to the value of @@ -8115,10 +8157,10 @@ spec: The string "infinity" is also a valid input and specifies no timeout. pattern: ^(((\d*(\.\d*)?h)|(\d*(\.\d*)?m)|(\d*(\.\d*)?s)|(\d*(\.\d*)?ms)|(\d*(\.\d*)?us)|(\d*(\.\d*)?µs)|(\d*(\.\d*)?ns))+|infinity|infinite)$ type: string - serviceAPIType: + serviceType: default: grpc description: |- - ServiceAPIType sets the protocol used to communicate with + ServiceType sets the protocol used to communicate with the external authorization server. enum: - http @@ -8145,6 +8187,9 @@ spec: type: boolean type: object type: object + x-kubernetes-validations: + - message: httpSettings can only be set when serviceType is 'http' + rule: '!has(self.httpSettings) || self.serviceType == ''http''' corsPolicy: description: Specifies the cross-origin policy to apply to the VirtualHost. diff --git a/internal/dag/dag.go b/internal/dag/dag.go index 8d9134ffce5..5b1fed32f93 100644 --- a/internal/dag/dag.go +++ b/internal/dag/dag.go @@ -27,7 +27,6 @@ import ( core_v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" - contour_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" "github.com/projectcontour/contour/internal/status" "github.com/projectcontour/contour/internal/timeout" ) @@ -163,6 +162,35 @@ func (hc *HeaderMatchCondition) String() string { return "header: " + details } +// AuthorizationServiceType defines whether the external authorization server +// uses HTTP or gRPC protocol. +type AuthorizationServiceType int + +const ( + // AuthorizationServiceGRPC indicates the server implements the gRPC ext_authz protocol. + AuthorizationServiceGRPC AuthorizationServiceType = iota + // AuthorizationServiceHTTP indicates the server implements the raw HTTP ext_authz protocol. + AuthorizationServiceHTTP +) + +const ( + // HeaderNameMatchTypeExact matches a header name exactly. + HeaderNameMatchTypeExact = "exact" + // HeaderNameMatchTypePrefix matches a header name by prefix. + HeaderNameMatchTypePrefix = "prefix" + // HeaderNameMatchTypeSuffix matches a header name by suffix. + HeaderNameMatchTypeSuffix = "suffix" + // HeaderNameMatchTypeContains matches a header name if it contains the provided value. + HeaderNameMatchTypeContains = "contains" +) + +// HeaderNameMatchCondition matches an HTTP header name by MatchType. +type HeaderNameMatchCondition struct { + MatchType string + Value string + IgnoreCase bool +} + const ( // QueryParamMatchTypeExact matches a querystring parameter value exactly. QueryParamMatchTypeExact = "exact" @@ -865,15 +893,14 @@ type ExternalAuthorization struct { // ServiceAPIType defines the external authorization service API type. // It indicates the protocol implemented by the external server, specifying whether it's a raw HTTP authorization server // or a gRPC authorization server. - ServiceAPIType contour_v1.AuthorizationServiceAPIType + ServiceAPIType AuthorizationServiceType - // HTTPAllowedAuthorizationHeaders specifies client request headers that will be sent to the authorization server. - // Note that in addition to the the user’s supplied matchers, Host, Method, Path, Content-Length, and Authorization are additionally included in the list. - HTTPAllowedAuthorizationHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders + // Note that in addition to the user’s supplied matchers, Host, Method, Path, Content-Length, and Authorization are additionally included in the list. + HTTPAllowedAuthorizationHeaders []HeaderNameMatchCondition // HTTPAllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. // Note that coexistent headers will be overridden. - HTTPAllowedUpstreamHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders + HTTPAllowedUpstreamHeaders []HeaderNameMatchCondition // HTTPPathPrefix Sets a prefix to the value of authorization request header Path. HTTPPathPrefix string diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index 9044ad568c0..2d9c95f0248 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -1392,63 +1392,18 @@ func (p *HTTPProxyProcessor) computeVirtualHostAuthorization(auth *contour_v1.Au return nil } - ok, respTimeout := determineExternalAuthTimeout(auth.ResponseTimeout, validCond, ext) - if !ok { + extAuth := NewExternalAuthorization(auth, validCond) + if extAuth == nil { return nil } - globalExternalAuthorization := &ExternalAuthorization{ - AuthorizationService: ext, - AuthorizationFailOpen: auth.FailOpen, - AuthorizationResponseTimeout: *respTimeout, - } - - switch auth.ServiceAPIType { - case contour_v1.AuthorizationGRPCService: - globalExternalAuthorization.ServiceAPIType = contour_v1.AuthorizationGRPCService - case contour_v1.AuthorizationHTTPService: - globalExternalAuthorization.ServiceAPIType = contour_v1.AuthorizationHTTPService - if auth.HTTPServerSettings != nil { - globalExternalAuthorization.HTTPPathPrefix = auth.HTTPServerSettings.PathPrefix - - // globalExternalAuthorization.HttpServerURI = auth.HttpServerSettings.ServerURI - - if len(auth.HTTPServerSettings.AllowedAuthorizationHeaders) > 0 { - if err := ExternalAuthAllowedHeadersValid(auth.HTTPServerSettings.AllowedAuthorizationHeaders); err != nil { - validCond.AddErrorf(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", - "Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedAuthorizationHeaders is invalid: %s", err) - - return nil - } - - globalExternalAuthorization.HTTPAllowedAuthorizationHeaders = auth.HTTPServerSettings.AllowedAuthorizationHeaders - } - - if len(auth.HTTPServerSettings.AllowedUpstreamHeaders) > 0 { - if err := ExternalAuthAllowedHeadersValid(auth.HTTPServerSettings.AllowedUpstreamHeaders); err != nil { - validCond.AddErrorf(contour_v1.ConditionTypeAuthError, "AuthBadAllowedHeader", - "Spec.Virtualhost.Authorization.HTTPServerSettings.AllowedUpstreamHeaders is invalid: %s", err) - - return nil - } - - globalExternalAuthorization.HTTPAllowedUpstreamHeaders = auth.HTTPServerSettings.AllowedUpstreamHeaders - } - } + // If no explicit timeout was configured, fall back to the extension service's own timeout. + if extAuth.AuthorizationResponseTimeout.UseDefault() { + extAuth.AuthorizationResponseTimeout = ext.RouteTimeoutPolicy.ResponseTimeout } - if auth.WithRequestBody != nil { - maxRequestBytes := defaultMaxRequestBytes - if auth.WithRequestBody.MaxRequestBytes != 0 { - maxRequestBytes = auth.WithRequestBody.MaxRequestBytes - } - globalExternalAuthorization.AuthorizationServerWithRequestBody = &AuthorizationServerBufferSettings{ - MaxRequestBytes: maxRequestBytes, - AllowPartialMessage: auth.WithRequestBody.AllowPartialMessage, - PackAsBytes: auth.WithRequestBody.PackAsBytes, - } - } - return globalExternalAuthorization + extAuth.AuthorizationService = ext + return extAuth } func validateExternalAuthExtensionService(ref contour_v1.ExtensionServiceReference, validCond *contour_v1.DetailedCondition, httpproxy *contour_v1.HTTPProxy, getExtensionCluster func(name string) *ExtensionCluster) (bool, *ExtensionCluster) { diff --git a/internal/envoy/v3/listener.go b/internal/envoy/v3/listener.go index 511edad2f3f..ca3e83b0d83 100644 --- a/internal/envoy/v3/listener.go +++ b/internal/envoy/v3/listener.go @@ -48,7 +48,6 @@ import ( "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/wrapperspb" - contour_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "github.com/projectcontour/contour/internal/dag" "github.com/projectcontour/contour/internal/envoy" @@ -839,37 +838,37 @@ end } } -// ExternalAuthzAllowedHeaders returns the slice of StringMatcher for a given slice of HTTPAuthorizationServerAllowedHeaders. -func ExternalAuthzAllowedHeaders(allowedHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders) []*envoy_matcher_v3.StringMatcher { +// ExternalAuthzAllowedHeaders returns the slice of StringMatcher for a given slice of HeaderNameMatchCondition. +func ExternalAuthzAllowedHeaders(allowedHeaders []dag.HeaderNameMatchCondition) []*envoy_matcher_v3.StringMatcher { var allowedHeaderPatterns []*envoy_matcher_v3.StringMatcher for _, allowedHeader := range allowedHeaders { - switch { - case allowedHeader.Exact != "": + switch allowedHeader.MatchType { + case dag.HeaderNameMatchTypeExact: allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{ - Exact: allowedHeader.Exact, + Exact: allowedHeader.Value, }, IgnoreCase: allowedHeader.IgnoreCase, }) - case allowedHeader.Prefix != "": + case dag.HeaderNameMatchTypePrefix: allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ MatchPattern: &envoy_matcher_v3.StringMatcher_Prefix{ - Prefix: allowedHeader.Prefix, + Prefix: allowedHeader.Value, }, IgnoreCase: allowedHeader.IgnoreCase, }) - case allowedHeader.Suffix != "": + case dag.HeaderNameMatchTypeSuffix: allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ MatchPattern: &envoy_matcher_v3.StringMatcher_Suffix{ - Suffix: allowedHeader.Suffix, + Suffix: allowedHeader.Value, }, IgnoreCase: allowedHeader.IgnoreCase, }) - case allowedHeader.Contains != "": + case dag.HeaderNameMatchTypeContains: allowedHeaderPatterns = append(allowedHeaderPatterns, &envoy_matcher_v3.StringMatcher{ MatchPattern: &envoy_matcher_v3.StringMatcher_Contains{ - Contains: allowedHeader.Contains, + Contains: allowedHeader.Value, }, IgnoreCase: allowedHeader.IgnoreCase, }) @@ -883,9 +882,6 @@ func ExternalAuthzAllowedHeaders(allowedHeaders []contour_v1.HTTPAuthorizationSe // requested parameters. func FilterExternalAuthz(externalAuthorization *dag.ExternalAuthorization) *envoy_filter_network_http_connection_manager_v3.HttpFilter { authConfig := envoy_filter_http_ext_authz_v3.ExtAuthz{ - Services: &envoy_filter_http_ext_authz_v3.ExtAuthz_GrpcService{ - GrpcService: grpcService(externalAuthorization.AuthorizationService.Name, externalAuthorization.AuthorizationService.SNI, externalAuthorization.AuthorizationResponseTimeout), - }, // Pretty sure we always want this. Why have an // external auth service if it is not going to affect // routing decisions? @@ -901,22 +897,23 @@ func FilterExternalAuthz(externalAuthorization *dag.ExternalAuthorization) *envo } switch externalAuthorization.ServiceAPIType { - case contour_v1.AuthorizationGRPCService: + case dag.AuthorizationServiceGRPC: authConfig.Services = &envoy_filter_http_ext_authz_v3.ExtAuthz_GrpcService{ GrpcService: grpcService(externalAuthorization.AuthorizationService.Name, externalAuthorization.AuthorizationService.SNI, externalAuthorization.AuthorizationResponseTimeout), } authConfig.MetadataContextNamespaces = []string{} - case contour_v1.AuthorizationHTTPService: + case dag.AuthorizationServiceHTTP: extAuthzService := &envoy_filter_http_ext_authz_v3.ExtAuthz_HttpService{ HttpService: &envoy_filter_http_ext_authz_v3.HttpService{ ServerUri: &envoy_config_core_v3.HttpUri{ - // Uri: externalAuthorization.HttpServerURI, + // Uri is required by the Envoy API but routing is determined by the Cluster field, + // so we use a dummy value here. Uri: "http://dummy/", HttpUpstreamType: &envoy_config_core_v3.HttpUri_Cluster{ Cluster: externalAuthorization.AuthorizationService.Name, }, - Timeout: envoy.Timeout(externalAuthorization.AuthorizationResponseTimeout), + Timeout: httpURITimeout(externalAuthorization.AuthorizationResponseTimeout), }, }, } @@ -1058,6 +1055,15 @@ func FilterChainTLSFallback(downstream *envoy_transport_socket_tls_v3.Downstream return fc } +// httpURITimeout returns a duration for the HttpUri.Timeout field. +// It returns 0 (infinite) if the timeout is not set. +func httpURITimeout(d timeout.Setting) *durationpb.Duration { + if t := envoy.Timeout(d); t != nil { + return t + } + return durationpb.New(0) +} + // grpcService returns a envoy_config_core_v3.GrpcService for the given parameters. func grpcService(clusterName, sni string, timeout timeout.Setting) *envoy_config_core_v3.GrpcService { authority := strings.ReplaceAll(clusterName, "/", ".") diff --git a/internal/featuretests/v3/authorization_test.go b/internal/featuretests/v3/authorization_test.go index 981da972ad1..4c98a3b121c 100644 --- a/internal/featuretests/v3/authorization_test.go +++ b/internal/featuretests/v3/authorization_test.go @@ -77,7 +77,7 @@ func authzResponseTimeout(t *testing.T, rh ResourceEventHandlerWrapper, c *Conto Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, ResponseTimeout: "10m", }). WithSpec(contour_v1.HTTPProxySpec{ @@ -136,7 +136,7 @@ func authzInvalidResponseTimeout(t *testing.T, rh ResourceEventHandlerWrapper, c Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, ResponseTimeout: "invalid-timeout", }). WithSpec(contour_v1.HTTPProxySpec{ @@ -164,8 +164,8 @@ func authzFailOpen(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, - FailOpen: true, + ServiceType: contour_v1.AuthorizationGRPCService, + FailOpen: true, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -217,7 +217,7 @@ func authzFallbackIncompat(t *testing.T, rh ResourceEventHandlerWrapper, c *Cont Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -249,7 +249,7 @@ func authzOverrideDisabled(t *testing.T, rh ResourceEventHandlerWrapper, c *Cont WithCertificate("certificate"). WithAuthServer(contour_v1.AuthorizationServer{ ExtensionServiceRef: extensionRef, - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, AuthPolicy: &contour_v1.AuthorizationPolicy{Disabled: false}, }). WithSpec(contour_v1.HTTPProxySpec{ @@ -360,7 +360,7 @@ func authzMergeRouteContext(t *testing.T, rh ResourceEventHandlerWrapper, c *Con Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, AuthPolicy: &contour_v1.AuthorizationPolicy{ Context: map[string]string{ "root-element": "root", @@ -441,7 +441,7 @@ func authzInvalidReference(t *testing.T, rh ResourceEventHandlerWrapper, c *Cont WithFQDN(fqdn). WithCertificate("certificate"). WithAuthServer(contour_v1.AuthorizationServer{ - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -532,8 +532,8 @@ func authzWithRequestBodyBufferSettings(t *testing.T, rh ResourceEventHandlerWra Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, - FailOpen: true, + ServiceType: contour_v1.AuthorizationGRPCService, + FailOpen: true, WithRequestBody: &contour_v1.AuthorizationServerBufferSettings{ MaxRequestBytes: 100, AllowPartialMessage: true, @@ -597,7 +597,7 @@ func AuthzTypeGRPC(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -656,7 +656,7 @@ func authzTypeHTTP(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationHTTPService, + ServiceType: contour_v1.AuthorizationHTTPService, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -704,7 +704,7 @@ func authzTypeHTTP(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { }).Status(p).IsValid() } -func AuthzTypeHTTPWithPathPrefix(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { +func authzTypeHTTPWithPathPrefix(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { const fqdn = "typehttp.projectcontour.io" p := fixture.NewProxy("proxy"). @@ -715,7 +715,7 @@ func AuthzTypeHTTPWithPathPrefix(t *testing.T, rh ResourceEventHandlerWrapper, c Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationHTTPService, + ServiceType: contour_v1.AuthorizationHTTPService, HTTPServerSettings: &contour_v1.HTTPAuthorizationServerSettings{ PathPrefix: "/auth?", }, @@ -769,7 +769,7 @@ func AuthzTypeHTTPWithPathPrefix(t *testing.T, rh ResourceEventHandlerWrapper, c }).Status(p).IsValid() } -func AuthzTypeHTTPWithAllowedAuthorizationHeaders(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { +func authzTypeHTTPWithAllowedAuthorizationHeaders(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { const fqdn = "typehttp.projectcontour.io" p := fixture.NewProxy("proxy"). @@ -780,7 +780,7 @@ func AuthzTypeHTTPWithAllowedAuthorizationHeaders(t *testing.T, rh ResourceEvent Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationHTTPService, + ServiceType: contour_v1.AuthorizationHTTPService, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -869,7 +869,7 @@ func AuthzTypeHTTPWithAllowedAuthorizationHeaders(t *testing.T, rh ResourceEvent }).Status(p).IsValid() } -func AuthzTypeHTTPWithAllowedUpstreamHeaders(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { +func authzTypeHTTPWithAllowedUpstreamHeaders(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { const fqdn = "typehttp.projectcontour.io" p := fixture.NewProxy("proxy"). @@ -880,7 +880,7 @@ func AuthzTypeHTTPWithAllowedUpstreamHeaders(t *testing.T, rh ResourceEventHandl Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationHTTPService, + ServiceType: contour_v1.AuthorizationHTTPService, }). WithSpec(contour_v1.HTTPProxySpec{ Routes: []contour_v1.Route{{ @@ -973,7 +973,7 @@ func AuthzTypeHTTPWithAllowedUpstreamHeaders(t *testing.T, rh ResourceEventHandl }).Status(p).IsValid() } -func AuthzTypeHTTPWithContext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { +func authzTypeHTTPWithContext(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { const fqdn = "typehttp.projectcontour.io" p := fixture.NewProxy("proxy"). @@ -984,7 +984,7 @@ func AuthzTypeHTTPWithContext(t *testing.T, rh ResourceEventHandlerWrapper, c *C Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationHTTPService, + ServiceType: contour_v1.AuthorizationHTTPService, AuthPolicy: &contour_v1.AuthorizationPolicy{ Context: map[string]string{ "k1": "v1", @@ -1010,6 +1010,68 @@ func AuthzTypeHTTPWithContext(t *testing.T, rh ResourceEventHandlerWrapper, c *C }).Status(p).HasError(contour_v1.ConditionTypeAuthError, "AuthContextForHTTP", `Spec.Virtualhost.Authorization.AuthPolicy.Context are only applied to grpc service type`) } +// authzTypeUnset tests backwards compatibility: an HTTPProxy with no +// serviceAPIType set (field absent in older stored objects, Go zero-value "") +// must be treated as gRPC. +func authzTypeUnset(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + const fqdn = "typegrpc.projectcontour.io" + + p := fixture.NewProxy("proxy"). + WithFQDN(fqdn). + WithCertificate("certificate"). + WithAuthServer(contour_v1.AuthorizationServer{ + ExtensionServiceRef: contour_v1.ExtensionServiceReference{ + Namespace: "auth", + Name: "extension", + }, + // ServiceAPIType intentionally absent to simulate upgrade from older version where this field didn't exist. + }). + WithSpec(contour_v1.HTTPProxySpec{ + Routes: []contour_v1.Route{{ + Services: []contour_v1.Service{{ + Name: "app-server", + Port: 80, + }}, + }}, + }) + + rh.OnDelete(p) + rh.OnAdd(p) + + c.Request(listenerType).Equals(&envoy_service_discovery_v3.DiscoveryResponse{ + TypeUrl: listenerType, + Resources: resources(t, + defaultHTTPListener(), + &envoy_config_listener_v3.Listener{ + Name: "ingress_https", + Address: envoy_v3.SocketAddress("0.0.0.0", 8443), + ListenerFilters: envoy_v3.ListenerFilters( + envoy_v3.TLSInspector(), + ), + FilterChains: []*envoy_config_listener_v3.FilterChain{ + filterchaintls(fqdn, + featuretests.TLSSecret(t, "certificate", &featuretests.ServerCertificate), + authzFilterFor( + fqdn, + &envoy_filter_http_ext_authz_v3.ExtAuthz{ + Services: grpcCluster("extension/auth/extension"), + ClearRouteCache: true, + FailureModeAllow: false, + IncludePeerCertificate: true, + StatusOnError: &envoy_type_v3.HttpStatus{ + Code: envoy_type_v3.StatusCode_Forbidden, + }, + TransportApiVersion: envoy_config_core_v3.ApiVersion_V3, + }, + ), + nil, "h2", "http/1.1"), + }, + SocketOptions: envoy_v3.NewSocketOptions().TCPKeepalive().Build(), + }, + statsListener()), + }).Status(p).IsValid() +} + func TestAuthorization(t *testing.T) { subtests := map[string]func(*testing.T, ResourceEventHandlerWrapper, *Contour){ "MissingExtension": authzInvalidReference, @@ -1022,9 +1084,11 @@ func TestAuthorization(t *testing.T) { "AuthzWithRequestBodyBufferSettings": authzWithRequestBodyBufferSettings, "AuthzTypeGRPC": AuthzTypeGRPC, "AuthzTypeHTTP": authzTypeHTTP, - "AuthzTypeHTTPWithPathPrefix": AuthzTypeHTTPWithPathPrefix, - "AuthzTypeHTTPWithAllowedAuthorizationHeaders": AuthzTypeHTTPWithAllowedAuthorizationHeaders, - "AuthzTypeHTTPWithAllowedUpstreamHeaders": AuthzTypeHTTPWithAllowedUpstreamHeaders, + "AuthzTypeHTTPWithPathPrefix": authzTypeHTTPWithPathPrefix, + "AuthzTypeHTTPWithAllowedAuthorizationHeaders": authzTypeHTTPWithAllowedAuthorizationHeaders, + "AuthzTypeHTTPWithAllowedUpstreamHeaders": authzTypeHTTPWithAllowedUpstreamHeaders, + "AuthzTypeHTTPWithContext": authzTypeHTTPWithContext, + "AuthzTypeUnset": authzTypeUnset, } for n, f := range subtests { diff --git a/internal/featuretests/v3/global_authorization_test.go b/internal/featuretests/v3/global_authorization_test.go index 4c134a6073d..1b1f6a16789 100644 --- a/internal/featuretests/v3/global_authorization_test.go +++ b/internal/featuretests/v3/global_authorization_test.go @@ -47,7 +47,7 @@ var ( Name: "extension", Namespace: "auth", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, FailOpen: false, ResponseTimeout: defaultResponseTimeout.String(), AuthPolicy: &contour_v1.AuthorizationPolicy{ @@ -63,7 +63,7 @@ var ( Name: "extension", Namespace: "auth", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, FailOpen: false, ResponseTimeout: defaultResponseTimeout.String(), AuthPolicy: &contour_v1.AuthorizationPolicy{ @@ -582,7 +582,7 @@ func globalExternalAuthorizationWithTLSAuthOverride(t *testing.T, rh ResourceEve Namespace: "auth", Name: "extension", }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, + ServiceType: contour_v1.AuthorizationGRPCService, ResponseTimeout: defaultResponseTimeout.String(), FailOpen: true, WithRequestBody: &contour_v1.AuthorizationServerBufferSettings{ @@ -803,13 +803,13 @@ func TestGlobalAuthorization(t *testing.T) { ExtensionService: k8s.NamespacedNameFrom("auth/extension"), Timeout: timeout.DurationSetting(defaultResponseTimeout), }, - ServiceAPIType: contour_v1.AuthorizationGRPCService, - FailOpen: false, Context: map[string]string{ "header_type": "root_config", "header_1": "message_1", }, } + cfg.GlobalExternalAuthConfig.ServiceAPIType = dag.AuthorizationServiceGRPC + cfg.GlobalExternalAuthConfig.AuthorizationFailOpen = false }, func(b *dag.Builder) { for _, processor := range b.Processors { diff --git a/internal/xdscache/v3/listener.go b/internal/xdscache/v3/listener.go index a9d68c347c5..b1cf42294c3 100644 --- a/internal/xdscache/v3/listener.go +++ b/internal/xdscache/v3/listener.go @@ -25,7 +25,6 @@ import ( "google.golang.org/protobuf/proto" "k8s.io/apimachinery/pkg/types" - contour_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "github.com/projectcontour/contour/internal/contourconfig" "github.com/projectcontour/contour/internal/dag" @@ -203,14 +202,8 @@ type RateLimitConfig struct { type GlobalExternalAuthConfig struct { ExtensionServiceConfig - FailOpen bool - Context map[string]string - ServiceAPIType contour_v1.AuthorizationServiceAPIType - HTTPAllowedAuthorizationHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders - HTTPAllowedUpstreamHeaders []contour_v1.HTTPAuthorizationServerAllowedHeaders - HTTPPathPrefix string - WithRequestBody *dag.AuthorizationServerBufferSettings - // HttpServerURI string + dag.ExternalAuthorization + Context map[string]string } // httpAccessLog returns the access log for the HTTP (non TLS) @@ -614,9 +607,9 @@ func httpGlobalExternalAuthConfig(config *GlobalExternalAuthConfig) *envoy_filte HTTPAllowedAuthorizationHeaders: config.HTTPAllowedAuthorizationHeaders, HTTPAllowedUpstreamHeaders: config.HTTPAllowedUpstreamHeaders, HTTPPathPrefix: config.HTTPPathPrefix, - AuthorizationFailOpen: config.FailOpen, + AuthorizationFailOpen: config.AuthorizationFailOpen, AuthorizationResponseTimeout: config.Timeout, - AuthorizationServerWithRequestBody: config.WithRequestBody, + AuthorizationServerWithRequestBody: config.AuthorizationServerWithRequestBody, }) } diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index 4af63ad86dc..645b33cf053 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -761,16 +761,16 @@ type GlobalExternalAuthorization struct { // ExtensionService identifies the extension service defining the RLS, // formatted as /. ExtensionService string `yaml:"extensionService,omitempty"` - // ServiceAPIType defines the external authorization service API type. + // ServiceType defines the external authorization service API type. // It indicates the protocol implemented by the external server, specifying whether it's a raw HTTP authorization server // or a gRPC authorization server. // // +optional - ServiceAPIType contour_v1.AuthorizationServiceAPIType `json:"serviceAPIType,omitempty"` + ServiceType contour_v1.AuthorizationServiceType `yaml:"serviceType,omitempty"` // HttpAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server. // // +optional - HTTPServerSettings *contour_v1.HTTPAuthorizationServerSettings `json:"httpSettings,omitempty"` + HTTPServerSettings *contour_v1.HTTPAuthorizationServerSettings `yaml:"httpSettings,omitempty"` // AuthPolicy sets a default authorization policy for client requests. // This policy will be used unless overridden by individual routes. // diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html index 2c024e8cf55..7cc7b7a6c97 100644 --- a/site/content/docs/main/config/api-reference.html +++ b/site/content/docs/main/config/api-reference.html @@ -334,7 +334,8 @@

AuthorizationServer

AuthorizationServer configures an external server to authenticate client requests. The external server must implement the v3 Envoy -external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto).

+external authorization GRPC protocol (https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto) +or the HTTP authorization server protocol.

@@ -361,17 +362,17 @@

AuthorizationServer

@@ -513,14 +514,14 @@

AuthorizationSer

-serviceAPIType +serviceType
- -AuthorizationServiceAPIType + +AuthorizationServiceType
(Optional) -

ServiceAPIType sets the protocol used to communicate with +

ServiceType sets the protocol used to communicate with the external authorization server.

-

AuthorizationServiceAPIType +

AuthorizationServiceType (string alias)

(Appears on: AuthorizationServer)

-

AuthorizationServiceAPIType indicates the protocol +

AuthorizationServiceType indicates the protocol implemented by the external authorization server.

@@ -1479,8 +1480,8 @@

HTTPAuthorizationS

(Optional) -

AllowedUpstreamHeaders specifies authorization response headers that will be added to the original client request. -Coexistent headers will be overridden.

+

AllowedUpstreamHeaders specifies response headers from the authorization server +that may be added to the original client request before sending it to the upstream.