Well-known Attributes¶
- Feature Name:
well-known-attributes
- Start Date: 2023-06-13
- RFC PR: Kuadrant/architecture#17
- Issue tracking: Kuadrant/architecture#53
Summary¶
Define a well-known structure for users to declare request data selectors in their RateLimitPolicies and AuthPolicies. This structure is referred to as the Kuadrant Well-known Attributes.
Motivation¶
The well-known attributes let users write policy rules – conditions and, in general, dynamic values that refer to attributes in the data plane - in a concise and seamless way.
Decoupled from the policy CRDs, the well-known attributes:
- define a common language for referring to values of the data plane in the Kuadrant policies;
- allow dynamically evolving the policy APIs regarding how they admit references to data plane attributes;
- encompass all common and component-specific selectors for data plane attributes;
- have a single and unified specification, although this specification may occasionally link to additional, component-specific, external docs.
Guide-level explanation¶
One who writes a Kuadrant policy and wants to build policy constructs such as conditions, qualifiers, variables, etc, based on dynamic values of the data plane, must refer the attributes that carry those values, using the declarative language of Kuadrant's Well-known Attributes.
A dynamic data plane value is typically a value of an attribute of the request or an Envoy Dynamic Metadata entry. It can be a value of the outer request being handled by the API gateway or proxy that is managed by Kuadrant ("context request") or an attribute of the direct request to the Kuadrant component that delivers the functionality in the data plane (rate-limiting or external auth).
A Well-known Selector is a construct of a policy API whose value contains a direct reference to a well-known attribute. The language of the well-known attributes and therefore what one would declare within a well-known selector resembles a JSON path for navigating a possibly complex JSON object.
Example 1. Well-known selector used in a condition
apiGroup: examples.kuadrant.io
kind: PaintPolicy
spec:
rules:
- when:
- selector: auth.identity.group
operator: eq
value: admin
color: red
In the example, auth.identity.group
is a well-known selector of an attribute group
, known to be injected by the external authorization service (auth
) to describe the group the user (identity
) belongs to. In the data plane, whenever this value is equal to admin
, the abstract PaintPolicy
policy states that the traffic must be painted red
.
Example 2. Well-known selector used in a variable
apiGroup: examples.kuadrant.io
kind: PaintPolicy
spec:
rules:
- color: red
alpha:
dynamic: request.headers.x-color-alpha
In the example, request.headers.x-color-alpha
is a selector of a well-known attribute request.headers
that gives access to the headers of the context HTTP request. The selector retrieves the value of the x-color-alpha
request header to dynamically fill the alpha
property of the abstract PaintPolicy
policy at each request.
Reference-level explanation¶
The Well-known Attributes are a compilation inspired by some of the Envoy attributes and Authorino's Authorization JSON and its related JSON paths.
From the Envoy attributes, only attributes that are available before establishing connection with the upstream server qualify as a Kuadrant well-known attribute. This excludes attributes such as the response attributes and the upstream attributes.
As for the attributes inherited from Authorino, these are either based on Envoy's AttributeContext
type of the external auth request API or from internal types defined by Authorino to fulfill the Auth Pipeline.
These two subsets of attributes are unified into a single set of well-known attributes. For each attribute that exists in both subsets, the name of the attribute as specified in the Envoy attributes subset prevails. Example of such is request.id
(to refer to the ID of the request) superseding context.request.http.id
(as the same attribute is referred in an Authorino AuthConfig
).
The next sections specify the well-known attributes organized in the following groups:
- Request attributes
- Connection attributes
- Metadata and filter state attributes
- Auth attributes
- Rate-limit attributes
Request attributes¶
The following attributes are related to the context HTTP request that is handled by the API gateway or proxy managed by Kuadrant.
Attribute |
Type |
Description |
Auth |
RL |
---|---|---|---|---|
request.id |
String |
Request ID corresponding to |
✓ |
✓ |
request.time |
Timestamp |
Time of the first byte received |
✓ |
✓ |
request.protocol |
String |
Request protocol (“HTTP/1.0”, “HTTP/1.1”, “HTTP/2”, or “HTTP/3”) |
✓ |
✓ |
request.scheme |
String |
The scheme portion of the URL e.g. “http” |
✓ |
✓ |
request.host |
String |
The host portion of the URL |
✓ |
✓ |
request.method |
String |
Request method e.g. “GET” |
✓ |
✓ |
request.path |
String |
The path portion of the URL |
✓ |
✓ |
request.url_path |
String |
The path portion of the URL without the query string |
✓ |
|
request.query |
String |
The query portion of the URL in the format of “name1=value1&name2=value2” |
✓ |
✓ |
request.headers |
Map<String, String> |
All request headers indexed by the lower-cased header name |
✓ |
✓ |
request.referer |
String |
Referer request header |
✓ |
|
request.useragent |
String |
User agent request header |
✓ |
|
request.size |
Number |
The HTTP request size in bytes. If unknown, it must be -1 |
✓ |
|
request.body |
String |
The HTTP request body. (Disabled by default. Requires additional proxy configuration to enabled it.) |
✓ |
|
request.raw_body |
Array<Number> |
The HTTP request body in bytes. This is sometimes used instead of |
✓ |
|
request.context_extensions |
Map<String, String> |
This is analogous to |
✓ |
Connection attributes¶
The following attributes are available once the downstream connection with the API gateway or proxy managed by Kuadrant is established. They apply to HTTP requests (L7) as well, but also to proxied connections limited at L3/L4.
Attribute |
Type |
Description |
Auth |
RL |
---|---|---|---|---|
source.address |
String |
Downstream connection remote address |
✓ |
✓ |
source.port |
Number |
Downstream connection remote port |
✓ |
✓ |
source.service |
String |
The canonical service name of the peer |
✓ |
|
source.labels |
Map<String, String> |
The labels associated with the peer. These could be pod labels for Kubernetes or tags for VMs. The source of the labels could be an X.509 certificate or other configuration. |
✓ |
|
source.principal |
String |
The authenticated identity of this peer. If an X.509 certificate is used to assert the identity in the proxy, this field is sourced from “URI Subject Alternative Names“, “DNS Subject Alternate Names“ or “Subject“ in that order. The format is issuer specific – e.g. SPIFFE format is |
✓ |
|
source.certificate |
String |
The X.509 certificate used to authenticate the identify of this peer. When present, the certificate contents are encoded in URL and PEM format. |
✓ |
|
destination.address |
String |
Downstream connection local address |
✓ |
✓ |
destination.port |
Number |
Downstream connection local port |
✓ |
✓ |
destination.service |
String |
The canonical service name of the peer |
✓ |
|
destination.labels |
Map<String, String> |
The labels associated with the peer. These could be pod labels for Kubernetes or tags for VMs. The source of the labels could be an X.509 certificate or other configuration. |
✓ |
|
destination.principal |
String |
The authenticated identity of this peer. If an X.509 certificate is used to assert the identity in the proxy, this field is sourced from “URI Subject Alternative Names“, “DNS Subject Alternate Names“ or “Subject“ in that order. The format is issuer specific – e.g. SPIFFE format is |
✓ |
|
destination.certificate |
String |
The X.509 certificate used to authenticate the identify of this peer. When present, the certificate contents are encoded in URL and PEM format. |
✓ |
|
connection.id |
Number |
Downstream connection ID |
✓ |
|
connection.mtls |
Boolean |
Indicates whether TLS is applied to the downstream connection and the peer ceritificate is presented |
✓ |
|
connection.requested_server_name |
String |
Requested server name in the downstream TLS connection |
✓ |
|
connection.tls_session.sni |
String |
SNI used for TLS session |
✓ |
|
connection.tls_version |
String |
TLS version of the downstream TLS connection |
✓ |
|
connection.subject_local_certificate |
String |
The subject field of the local certificate in the downstream TLS connection |
✓ |
|
connection.subject_peer_certificate |
String |
The subject field of the peer certificate in the downstream TLS connection |
✓ |
|
connection.dns_san_local_certificate |
String |
The first DNS entry in the SAN field of the local certificate in the downstream TLS connection |
✓ |
|
connection.dns_san_peer_certificate |
String |
The first DNS entry in the SAN field of the peer certificate in the downstream TLS connection |
✓ |
|
connection.uri_san_local_certificate |
String |
The first URI entry in the SAN field of the local certificate in the downstream TLS connection |
✓ |
|
connection.uri_san_peer_certificate |
String |
The first URI entry in the SAN field of the peer certificate in the downstream TLS connection |
✓ |
|
connection.sha256_peer_certificate_digest |
String | SHA256 digest of the peer certificate in the downstream TLS connection if present |
✓ |
Metadata and filter state attributes¶
The following attributes are related to the Envoy proxy filter chain. They include metadata exported by the proxy throughout the filters and information about the states of the filters themselves.
Attribute |
Type |
Description |
Auth |
RL |
---|---|---|---|---|
metadata |
Dynamic request metadata |
✓ |
✓ |
|
filter_state |
Map<String, String> |
Mapping from a filter state name to its serialized string value |
✓ |
Auth attributes¶
The following attributes are exclusive of the external auth service (Authorino).
Attribute |
Type |
Description |
Auth |
RL |
---|---|---|---|---|
auth.identity |
Any |
Single resolved identity object, post-identity verification |
✓ |
|
auth.metadata |
Map<String, Any> |
External metadata fetched |
✓ |
|
auth.authorization |
Map<String, Any> |
Authorization results resolved by each authorization rule, access granted only |
✓ |
|
auth.response |
Map<String, Any> |
Response objects exported by the auth service post-access granted |
✓ |
|
auth.callbacks |
Map<String, Any> |
Response objects returned by the callback requests issued by the auth service |
✓ |
The auth service also supports modifying selected values by chaining modifiers in the path.
Rate-limit attributes¶
The following attributes are exclusive of the rate-limiting service (Limitador).
Attribute |
Type |
Description |
Auth |
RL |
---|---|---|---|---|
ratelimit.domain |
String |
The rate limit domain. This enables the configuration to be namespaced per application (multi-tenancy). |
✓ |
|
ratelimit.hits_addend |
Number |
Specifies the number of hits a request adds to the matched limit. Fixed value: `1`. Reserved for future usage. |
✓ |
Drawbacks¶
The decoupling of the well-known attributes and the language of well-known attributes and selectors from the individual policy CRDs is what makes it somewhat flexible and common across the components (rate-limiting and auth). However, it's less structured and it introduces another syntax for users to get familiar with.
This additional language competes with the language of the route selectors (RFC 0001), based on Gateway API's HTTPRouteMatch
type.
Being "soft-coded" in the policy specs (as opposed to a hard-coded sub-structure inside of each policy type) does not mean it's completely decoupled from implementation in the control plane and/or intermediary data plane components. Although many attributes can be supported almost as a pass-through, from being used in a selector in a policy, to a corresponding value requested by the wasm-shim to its host, that is not always the case. Some translation may be required for components not integrated via wasm-shim (e.g. Authorino), as well as for components integrated via wasm-shim (e.g. Limitador) in special cases of composite or abstraction well-known attributes (i.e. attributes not available as-is via ABI, e.g. auth.identity
in a RLP). Either way, some validation of the values introduced by users in the selectors may be needed at some point in the control plane, thus requiring arguably a level of awaresness and coupling between the well-known selectors specification and the control plane (policy controllers) or intermediary data plane (wasm-shim) components.
Rationale and alternatives¶
As an alternative to JSON path-like selectors based on a well-known structure that induces the proposed language of well-known attributes, these same attributes could be defined as sub-types of each policy CRD. The Golang packages defining the common attributes across CRDs could be shared by the policy type definitions to reduce repetition. However, that approach would possibly involve a staggering number of new type definitions to cover all the cases for all the groups of attributes to be supported. These are constructs that not only need to be understood by the policy controllers, but also known by the user who writes a policy.
Additionally, all attributes, including new attributes occasionally introduced by Envoy and made available to the wasm-shim via ABI, would always require translation from the user-level abstraction how it's represented in a policy, to the actual form how it's used in the wasm-shim configuration and Authorino AuthConfigs.
Not implementing this proposal and keeping the current state of things mean little consistency between these common constructs for rules and conditions on how they are represented in each type of policy. This lack of consistency has a direct impact on the overhead faced by users to learn how to interact with Kuadrant and write different kinds of policies, as well as for the maintainers on tasks of coding for policy validation and reconciliation of data plane configurations.
Prior art¶
Authorino's dynamic JSON paths, related to Authorino's Authorization JSON and used in when
conditions and inside of multiple other constructs of the AuthConfig, are an example of feature of very similar approach to the one proposed here.
Arguably, Authorino's perceived flexibility would not have been possible with the Authorization JSON selectors. Users can write quite sophisticated policy rules (conditions, variable references, etc) by leveraging the those dynamic selectors. Because they are backed by JSON-based machinery in the code, Authorino's selectors have very little to, in some cases, none at all variation compared Open Policy Agent's Rego policy language, which is often used side by side in the same AuthConfigs.
Authorino's Authorization JSON selectors are, in one hand, more restrict to the structure of the CheckRequest
payload (context.*
attributes). At the same time, they are very open in the part associated with the internal attributes built along the Auth Pipeline (i.e. auth.*
attributes). That makes Authorino's Authorization JSON selectors more limited, compared to the Envoy attributes made available to the wasm-shim via ABI, but also harder to validate. In some cases, such as of deep references to inside objects fetched from external sources of metadata, resolved OPA objects, JWT claims, etc, it is impossible to validate for correct references.
Another experience learned from Authorino's Authorization JSON selectors is that they depend substantially on the so-called "modifiers". Many use cases involving parsing and breaking down attributes that are originally available in a more complex form would not be possible without the modifiers. Examples of such cases are: extracting portions of the path and/or query string parameters (e.g. collection and resource identifiers), applying translations on HTTP verbs into corresponding operations, base64-decoding values from the context HTTP request, amongst several others.
Unresolved questions¶
-
How to deal with the differences regarding the availability and data types of the attributes across clients/hosts?
-
Can we make more attributes that are currently available to only one of the components common to both?
-
Will we need some kind of global support for modifiers (functions) in the well-known selectors or those can continue to be an Authorino-only feature?
-
Does Authorino, which is more strict regarding the data structure that induces the selectors, need to implement this specification or could/should it keep its current selectors and a translation be performed by the AuthPolicy controller?
Future possibilities¶
- Extend with more well-known attributes that abstract common patterns and/or for rather opinioned use cases. Examples:
auth.*
attributes supported in the rate limit servicerequest.authenticated
request.operation.(read|write)
request.param.my-param
-
connection.secure
-
Other Envoy attributes
Wasm attributes
Attribute |
Type |
Description |
Auth |
RL |
---|---|---|---|---|
wasm.plugin_name |
String |
Plugin name |
✓ |
|
wasm.plugin_root_id |
String |
Plugin root ID |
✓ |
|
wasm.plugin_vm_id |
String |
Plugin VM ID |
✓ |
|
wasm.node |
Local node description |
✓ |
||
wasm.cluster_name |
String |
Upstream cluster name |
✓ |
|
wasm.cluster_metadata |
Upstream cluster metadata |
✓ |
||
wasm.listener_direction |
Number |
Enumeration value of the listener traffic direction |
✓ |
|
wasm.listener_metadata |
Listener metadata |
✓ |
||
wasm.route_name |
String |
Route name |
✓ |
|
wasm.route_metadata |
Route metadata |
✓ |
||
wasm.upstream_host_metadata |
Upstream host metadata |
✓ |
Proxy configuration attributes
Attribute |
Type |
Description |
Auth |
RL |
---|---|---|---|---|
xds.cluster_name |
String |
Upstream cluster name |
✓ |
|
xds.cluster_metadata |
Upstream cluster metadata |
✓ |
||
xds.route_name |
String |
Route name |
✓ |
|
xds.route_metadata |
Route metadata |
✓ |
||
xds.upstream_host_metadata |
Upstream host metadata |
✓ |
||
xds.filter_chain_name |
String |
Listener filter chain name |
✓ |
- Add some support for value modifiers (functions), along the lines of Authorino's JSON path modifiers and/or Envoy attributes' path expressions.