Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 83 additions & 1 deletion apis/projectcontour/v1/httpproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,15 +239,39 @@ type ExtensionServiceReference struct {
Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"`
}

// AuthorizationServiceType indicates the protocol
// implemented by the external authorization server.
type AuthorizationServiceType string

const (
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"`

// ServiceType sets the protocol used to communicate with
// the external authorization server.
//
// +optional
// +kubebuilder:validation:Enum=http;grpc
// +kubebuilder:default=grpc
ServiceType AuthorizationServiceType `json:"serviceType,omitempty"`

// HTTPAuthorizationServerSettings defines configurations for interacting with an external HTTP authorization server.
//
// +optional
HTTPServerSettings *HTTPAuthorizationServerSettings `json:"httpSettings,omitempty"`
Comment on lines +270 to +273
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment stating that httpSettings is used only for HTTP based authorization service.

To enforce this and prevent confusion, we can add a CEL validation rule to the AuthorizationServer struct. Something like below but note that I did not test this yet

// +kubebuilder:validation:XValidation:message="httpSettings can only be set when serviceAPIType is 'http'",rule="!has(self.httpSettings) || self.serviceAPIType == 'http'"


// AuthPolicy sets a default authorization policy for client requests.
// This policy will be used unless overridden by individual routes.
//
Expand Down Expand Up @@ -276,6 +300,64 @@ 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"`
Comment on lines +310 to +314
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should move this to the top level (directly to AuthorizationServer struct) to benefit from it for both gRPC and HTTP authorization services.

Envoy had deprecated the HTTP service level field AllowedHeaders and in listener.go you already use the top ExtAuthz level setting, which is good, but currently it is set only in case of HTTP authorization service. Supporting both gRPC and HTTP makes the description more complex since the behavior differs for each, but it is better to align with current Envoy behavior in my opinion. The field description in the top-level section of the Envoy documentation:

Check request to authorization server will include the client request headers that have a correspondent match in the list. If this option isn’t specified, then all client request headers are included in the check request to a gRPC authorization server, whereas no client request headers (besides the ones allowed by default - see note below) are included in the check request to an HTTP authorization server. This inconsistency between gRPC and HTTP servers is to maintain backwards compatibility with legacy behavior.

I would prefer the name allowedRequestHeaders for the field to match Envoy more closely and avoid confusion. Since the context is httpproxy.spec.virtualhost.authorization, we do not need to repeat the word Authorization in the field name in my opinion.


// 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"`
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also add field allowedClientHeadersOnSuccess that would map to response headers allowed_client_headers_on_success, what do you think?

The use case for this is in #7367.


// 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.
Copy link
Copy Markdown
Member

@tsaarni tsaarni Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also define CEL validation for early detection to enforce this at creation time to avoid any possible confusion. Something like below but note that I did not test this yet

+// +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"

// +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.
//
// +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.
Expand Down
45 changes: 45 additions & 0 deletions apis/projectcontour/v1/zz_generated.deepcopy.go

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

10 changes: 5 additions & 5 deletions apis/projectcontour/v1alpha1/extensionservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"`
Comment on lines 80 to 85
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ExtensionService abstraction applies to envoy.filters.http.ext_authz, envoy.filters.http.opentelemetry, and envoy.filters.http.ratelimit. In Envoy, the first two filters support both gRPC and HTTP for application-level protocols, while the rate limit filter only supports gRPC. In Contour we obviously have supported just gRPC for all of them until now.

Adding the http/1.1 transport protocol creates a possibility of configuration errors when extension service is used with a filter that supports gRPC. These errors should be caught and error message should be surfaced somehow to the user. Since rate limit and OpenTelemetry filters are currently defined globally in configuration and parsed only at startup, managing these errors might be straightforward.

I have also question regarding how the http/1.1 should interact with services that use HTTP based authorization server over TLS and enable upstream validation settings. I think at least validation will fail currently.

Finally, we should consider whether the ServiceAPIType (the application protocol, gRPC vs. HTTP) belongs here as well, instead of in HTTPProxy. It is unlikely that authorization service would use gRPC for one virtual host and HTTP for another?


// 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"`
Expand Down
5 changes: 5 additions & 0 deletions changelogs/unreleased/7418-therealak12-minor.md
Original file line number Diff line number Diff line change
@@ -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.
21 changes: 9 additions & 12 deletions cmd/contour/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,20 +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)
}

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 {
Expand Down
5 changes: 5 additions & 0 deletions cmd/contour/servecontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -457,10 +457,15 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_v1alpha1.Co
Name: nsedName.Name,
Namespace: nsedName.Namespace,
},
ServiceType: ctx.Config.GlobalExternalAuthorization.ServiceType,
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,
Expand Down
Loading
Loading