# Cookie Rewriting in Contour

Status: Accepted

## Abstract
This proposal describes how Contour will provide users the ability to rewrite HTTP cookie attributes.

## Background
The `Set-Cookie` HTTP response header is used by an HTTP server to send a piece of data that a client can return to the server.
HTTP cookies can be used to correlate multiple requests from a single client, usually a browser.
Cookies are often used to implement a user "session."
The `Set-Cookie` header must contain a cookie name and value to set, as well as attributes that determine how a client should determine whether it should send it along with a particular request.

Contour's ["cookie" load balancing strategy](https://projectcontour.io/docs/v1.18.1/config/request-routing/#session-affinity) enables users to implement "session affinity" by configuring Envoy to generate a cookie that can be returned in subsequent requests to route to a consistent backend app instance.
A commonly requested feature for Contour is to make attributes of this cookie configurable.
Users may want to apply security or other settings to ensure browsers treat these cookies appropriately.
For example, the `SameSite` and `Secure` attributes are currently not set by Envoy when it generates the `X-Contour-Session-Affinity`.

In addition, some users may want to ensure certain attributes are set on application generated cookies.
These attributes may be things an application may not be able to accurately set, without prior knowledge of how the application is deployed.
For example, if Contour is in use to rewrite the path or hostname of a request before it reaches an application backend, the application may not be able to accurately set the `Path` and `Domain` attributes in a `Set-Cookie` response header.

Envoy does not at the moment provide a native mechanism with which HTTP cookies can be modified.
Envoy does of course provide the ability to write Lua code to implement custom logic for processing HTTP requests and responses.
It appears most community solutions to cookie rewriting utilize this method to customize HTTP cookies.
However, the Envoy project does appear to be open to an API change to allow some form of [cookie rewriting](https://github.com/envoyproxy/envoy/issues/15612).

## Goals
- Provide users ability to follow best practices in cookie attribute settings
- Offer features in Contour/Envoy provided by other L7 proxies
  - [Nginx](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cookie_domain)
  - [Apache HTTPD](http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxypassreversecookiedomain)
- Address various open issues related to cookie attributes
  - https://github.com/projectcontour/contour/issues/2965
  - https://github.com/projectcontour/contour/issues/3142

## Non Goals
- Provide full ability to configure Contour/Envoy with arbitrary Lua code
- Allow users to customize all aspects of an HTTP cookie (value, other attributes not mentioned below, etc.)

## High-Level Design
`HTTPProxy` will have new fields to configure cookie rewrite policy.
Cookie attributes related to infrastructure and security considerations will be customizable.
Other fields, cookie names, and values will not be made customizable.
The fields we will offer as customizable are as follows:
- Path
- Domain
- Secure
- SameSite

Initially, the added field for cookie rewriting will be added to the `Route` and `Service` elements of the `HTTPProxy`.
Envoy provides configuration of Lua using the `typed_per_filter_config` on routes, virtual hosts, or weighted clusters.
See [here](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter#per-route-configuration) for more.

Additionally, a future step may be to allow customization of the Contour/Envoy generated session affinity cookie directly in the "cookie" load balancing strategy.
This design will initially require users to customize that via the general setting on the `HTTPProxy` `Route` element.
We can possibly add some cookie manipulation parameters to the "cookie" load balancing strategy settings so the `X-Contour-Session-Affinity` cookie can be customized more cleanly.
If/when we move/add the cookie-based hash to the request hash load balancing strategy we provide, we can provide cookie manipulation parameters there to customize any cookies generated by Contour/Envoy.
See [this draft PR](https://github.com/projectcontour/contour/pull/3993) for an example of how we can move cookie-hashing to the request hash load balancing strategy.
We can include cookie manipulation parameters in the new `CookieHashOptions` struct.

## Detailed Design

### HTTPProxy API Changes

Below are the changes to the `HTTPProxy` `Route` and `Service` elements and accompanying types:

```golang
type Route struct {
  ...

  // +optional
  CookieRewritePolicy []CookieRewritePolicy `json:"cookieRewritePolicy,omitempty"`
}

type Service struct {
  ...

  // +optional
  CookieRewritePolicy []CookieRewritePolicy `json:"cookieRewritePolicy,omitempty"`
}

type CookieRewritePolicy struct {
	// Name is the name of the cookie for which attributes will be rewritten.
	Name string `json:"name"`

	// +optional
	PathRewrite *CookiePathRewrite `json:"pathRewrite,omitempty"`

	// +optional
	DomainRewrite *CookieDomainRewrite `json:"domainRewrite,omitempty"`

	// +optional
	Secure *bool `json:"secure,omitempty"`

	// +optional
	// +kubebuilder:validation:Enum=Strict;Lax;None
	SameSite *string `json:"sameSite,omitempty"`
}

type CookiePathRewrite struct {
	// Value is the value to rewrite the Path attribute to.
	// For now this is required.
	// +required
	// +kubebuilder:validation:Minimum=1
	Value string `json:"value"`
}

type CookieDomainRewrite struct {
	// Value is the value to rewrite the Domain attribute to.
	// For now this is required.
	// +required
	// +kubebuilder:validation:Minimum=1
	Value string `json:"value"`
}
```

Users will be able to optionally specify a list of cookies they would like to rewrite.
Cookies will be found in the set of `Set-Cookie` headers set by the server's response by name and be rewritten accordingly.
For now only a direct replacement rather than regex rewrite or the like is provided but more advanced features could be developed in the future.
Each attribute provided is optional to change, not setting a value will mean the attribute if present will be left alone from the original response.
If an attribute that is requested to be rewritten is not present in the relevant `Set-Cookie` header, it will be added to it.

## Implementation Details

### Validation
The various fields users may configure need to validated based on [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265).
Particularly the cookie name is of importance as we are matching on that to find the correct cookie attributes to update.

In addition, cookie rewrite rules for a `Route` and `Service` will need to be collected and deduplicated.
One restriction that we may want to make is that multiple rewrite policies may not be supplied for the same cookie name on a given `Route` or `Service`.

In line with recent work around surfacing status and return status codes on `HTTPProxy` misconfiguration, we should set detailed status conditions when there are configuration errors, similar to the processing that is done for request/response header policies.
We may first choose the method that currently exists, which is to invalidate the whole `HTTPProxy` but move forward in the future to programming an HTTP 502 response code.

### Lua Filter
Initially at least, this feature will be implemented with [Envoy's Lua filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter) using the `envoy_on_response` hook.
We can use the fact that Envoy allows configuring Lua scripts per-route and per-cluster to ensure we only manipulate cookies in appropriate responses and do not have to do complicated matching in the global Lua scripts.

We will parse `Set-Cookie` headers into a table keyed by the cookie name.
Each entry will be a table keyed by attribute name.
We should be able to generate code to simply iterate through and rewrite attributes accordingly.

We may choose to do everything with inline Lua or use the Lua filter's ability to supply a map of named source codes for the various scripts we need to generate.

In addition, parallel implementation on a [native Envoy feature](https://github.com/envoyproxy/envoy/issues/15612) may provide us the ability to remove Lua completely in the future.

### Cookie load balancing
If/when we want to provide convenience fields to customize Contour/Envoy generated session affinity cookies, we can provide them in to the relevant configuration structs for those features.
Implementing that should be as simple as matching on the correct cookies and performing the same manipulations as described above.

## Alternatives Considered

### Allow configurability of all cookie attributes
An addition to this design that is not present is to be provide the ability to configure all cookie attributes and possibly even names and values.
This would give users a very powerful tool.

However, currently the design for this feature is such that we are only allowing configuration of things an app or app developer might not know to be able to program correctly.
Attributes that are related to web security and the infrastructure an app runs with are currently made available to users only.

Other things like `Max-Age` and `Expires` are harder for a platform to justify setting and seem like they should be under control of the app, as they are likely application logic dependent.

However, this design does not limit us from adding the ability to manipulate these fields in the future.

### Always auto-rewrite Domain and Path attributes
One alternative could be to always automatically rewrite the `Domain` and `Path` elements to the correct values.
Hostname and path rewrite rules should be known by Contour and it could rewrite these values accordingly.

However, if we do default to this, it may not work for all cases, e.g. as a site may be returning a cookie for a specific or more general path and rewriting it automatically could be incorrect.
Rather than provide a restrictive API, we can start out with giving users full manual flexibility.
We may look to add an option to do automatic rewrites if users desire.

## Security Considerations
With this feature, we are allowing users to set security settings on cookies, which is an improvement on what Contour does today.

In terms of security and other considerations around Lua scripting, we are not letting users supply Lua code, and should be generating Lua scripts scoped to this feature.
Input will be validated before Lua code is generated so we should not allow arbitrary code to be passed to Envoy.

## Compatibility
Initially using the Envoy Lua filter, there should be no compatibility issues as this feature has existed for a while.

If we do end up getting the Envoy native feature merged, we can wait to merge the Lua implementation until a couple releases with the Envoy feature.
- In Contour version X, Envoy version Y we can implement feature with Lua.
- In Contour version X+1, Envoy version Y+1 (with native feature), we will still implement this feature with Lua in Contour
- In Contour version X+2, Envoy version Y+2, we can implement cookie reqriting with the Envoy native feature

## Open Issues

### Future Gateway API Extension
An open question may be where this will fit into the Gateway API `HTTPRoute` configuration.
Should this be an extension or some other mechanism which Contour can use to extend the base Gateway API fields?

### Global Cookie configuration
Is it useful for a Contour administrator to be able to set a global policy on cookie attributes?

Is it useful for a `HTTPProxy` "tree" to have a global set of policies on cookie attributes?
