[
https://issues.apache.org/jira/browse/KNOX-3509?focusedWorklogId=1046883&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1046883
]
ASF GitHub Bot logged work on KNOX-3509:
----------------------------------------
Author: ASF GitHub Bot
Created on: 11/Oct/26 23:15
Start Date: 11/Oct/26 23:15
Worklog Time Spent: 10m
Work Description: lmccay commented on code in PR #1448:
URL: https://github.com/apache/knox/pull/1448#discussion_r4243653982
##########
knox-site/docs/config_request_audience_validator.md:
##########
@@ -30,116 +30,320 @@ Leaving `request.audience.validator` unset keeps the
fixed-list behavior describ
#### Kubernetes Destination Audience Validator ####
-The Kubernetes Destination Audience Validator
(`request.audience.k8s.destination.validation`) checks a token's `aud` claim
against the request's actual destination rather than against a fixed list. It
only ever replaces the audience check for a token that carries a delegation
`act` claim, i.e. a token that was produced by [RFC
8693](https://www.rfc-editor.org/rfc/rfc8693) token exchange; a token with no
`act` claim still goes through the fixed-list check regardless of whether this
validator is selected.
+The Kubernetes Destination Audience Validator
(`request.audience.k8s.destination.validation`) checks a token's `aud` claim
against the request's actual destination rather than against a fixed list.
+
+It always validates a token carrying a delegation `act` claim, i.e. a token
that was produced by [RFC 8693](https://www.rfc-editor.org/rfc/rfc8693) token
exchange. A token with no `act` claim is validated the same way by default, as
described in Non-delegated tokens below; setting
`request.audience.k8s.validate.audiences.without.act.claim` to `false` sends
such a token to the fixed `knox.token.audiences` check instead.
##### Audience format #####
-For a token in scope, each entry in its `aud` claim is expected to be a URL of
the form:
+Each entry in the `aud` claim of a token in scope is expected to be a URL in
one of two forms. By default, both forms are tried for every entry, in this
order:
- https://cluster-domain[:port]/namespace/service-name[/resource-path]
+1. The **DNS form**, the Kubernetes service DNS name a local caller uses to
reach the destination:
+
+
https://<service>[.<namespace>[.svc[.<cluster-domain>]]][:port][/resource-path]
+
+2. The **custom form**:
+
+
https://cluster-domain[:port]/[skipped-segments/]namespace/service-name[/resource-path]
+
+An entry is accepted as a match candidate if either form accepts it, and is
rejected only if neither does. `request.audience.k8s.dns.format.enabled`
(default `true`) controls whether the DNS form is tried at all; see DNS form
below for why a deployment might turn it off. When it is `false`, every entry
is evaluated only as the custom form, and
`request.audience.k8s.dns.format.cluster.domain` is unused.
+
+Neither form is validated on the token-minting side by Knox itself or by any
RFC; it is a deployment convention that an operator's delegation policies and
the callers requesting a delegated token are responsible for following. An
entry that does not parse as either shape it is tried against fails validation.
+
+###### DNS form ######
+
+The host may be shortened from the right, the same way an ordinary DNS
resolver search path allows. With a service named `reporting-svc`, a namespace
of `analytics`, and `request.audience.k8s.dns.format.cluster.domain` left at
its default `cluster.local`, all of the following hosts parse:
+
+Host | Meaning
+---------|-----------
+`reporting-svc` | Service name only. The entry carries no namespace of its
own; see below.
+`reporting-svc.analytics` | Service and namespace.
+`reporting-svc.analytics.svc` | Service and namespace, with the literal `svc`
label.
+`reporting-svc.analytics.svc.cluster` | As above, plus the first label of the
cluster domain.
+`reporting-svc.analytics.svc.cluster.local` | The full cluster domain.
-This is the simple case, and applies whenever
`request.audience.k8s.audience.path.prefix` (see below) is left unset:
namespace and service-name must begin straight after the authority.
+A host of three or more labels is rejected unless its third label is literally
`svc`, and the labels after `svc`, if any, must be a label-boundary prefix of
`dns.format.cluster.domain`'s own labels, taken from the left. For example,
with `dns.format.cluster.domain` left at `cluster.local`, a host ending in
`.svc.local` alone (skipping `cluster`) does not parse, even though `local` is
itself one of `cluster.local`'s labels. A trailing dot on the host, or an empty
label anywhere in it, is also rejected; a trailing dot is deliberately not
accepted, since no normal client request produces one.
-This format is a deployment convention, not something any RFC prescribes or
anything else in Knox validates on the token-minting side — an operator's
delegation policies and the callers requesting a delegated token are what make
the `aud` values actually take this shape. An entry that does not parse as this
shape (wrong scheme, missing authority, userinfo present, fewer than two path
segments, an unnormalized or empty path segment, and so on) is never treated as
a match candidate, even for a check this validator is not currently configured
to perform.
+A one-label host carries no namespace. For such an entry, the namespace
instead comes from the SOURCE workload's SPIFFE id (see
`request.audience.k8s.source.spiffe-id.header.name` below), and that value must
equal the destination namespace. If no destination namespace is configured at
all, or no source SPIFFE id is available, the entry does not match. This is the
one case in this validator where a segment with no configured source is still
compared against, rather than being skipped.
-###### Optional path prefix ######
+A port in the DNS-form entry is parsed only so it does not break parsing of
the rest of the host, then discarded: it is never compared against anything,
since a mesh or sidecar in front of the destination may rewrite the port in
flight.
-`request.audience.k8s.audience.path.prefix` is an optional parameter for
deployments that need an `aud` entry's path to carry extra leading segments
before namespace and service-name, for example for network routing. Left unset
(the default), an entry must take the simple form shown above. When set, an
entry instead takes the form:
+A deployment that fronts more than one Kubernetes cluster, or more than one
trust domain, behind a single validator instance should set
`request.audience.k8s.dns.format.enabled` to `false`. A DNS-form host carries
no cluster or trust-domain identifier of its own, so there is no way to tell,
from the entry alone, which cluster or trust domain a bare
`<service>.<namespace>` host belongs to. The custom form does not have this
ambiguity, since its cluster-domain segment is checked against an explicit
allow-list.
+
+###### Custom form ######
+
+ https://cluster-domain[:port]/namespace/service-name[/resource-path]
+
+This is the simple case, and applies whenever
`request.audience.k8s.audience.path.prefix` (see below) is left unset:
namespace and service-name must begin straight after the authority. The
cluster-domain segment (host and effective port) is always checked against the
allow-list in `request.audience.k8s.cluster-domains`, with no way to disable
the check. This is a separate check from the DNS form's
`request.audience.k8s.dns.format.cluster.domain` above, not the same check
applied to both forms: the custom form matches its authority segment literally
against this allow-list, while the DNS form instead matches a label-boundary
suffix of the whole host against a single configured domain. See How this
validator matches a request below for both side by side.
+
+###### Optional path prefix (custom form only) ######
+
+`request.audience.k8s.audience.path.prefix` is an optional parameter for
deployments that need a custom-form `aud` entry's path to carry extra leading
segments before namespace and service-name, for example for network routing.
Left unset (the default), a custom-form entry must take the simple form shown
above. When set, a custom-form entry instead takes the form:
https://cluster-domain[:port]/[skipped-segments/]path-prefix/namespace/service-name[/resource-path]
-The path is searched for the first (leftmost) occurrence of `/path-prefix/`;
namespace, service-name, and resource-path are then parsed starting right after
it. Everything before that point — the `skipped-segments`, if any — is skipped
over unparsed, not validated: `audience.path.prefix` only locates where parsing
resumes. It is ordinarily a single path segment, but it may itself contain `/`
to require several contiguous segments to appear together, as one indivisible
token, before parsing resumes. An entry whose path never contains
`/path-prefix/` does not parse, the same as any other ill-shaped entry — this
parameter relocates where namespace and service-name are read from, it does not
make the check more permissive.
+The path is searched for the first (leftmost) occurrence of `/path-prefix/`;
namespace, service-name, and resource-path are then parsed starting right after
it. Everything before that point, the `skipped-segments` if any, is skipped
over unparsed, not validated: `audience.path.prefix` only locates where parsing
resumes. It is ordinarily a single path segment, but it may itself contain `/`
to require several contiguous segments to appear together, as one indivisible
token, before parsing resumes. An entry whose path never contains
`/path-prefix/` does not parse, the same as any other ill-shaped entry: this
parameter relocates where namespace and service-name are read from, it does not
make the check more permissive.
-##### How it works #####
+A DNS-form entry has no leading segments to skip, since its whole path is
always the resource path, so `audience.path.prefix` plays no part in matching a
DNS-form entry.
-Which of the URL's segments are actually compared against the request depends
entirely on which of this validator's header parameters are configured — a
segment with no configured source is not compared at all, so an `aud` entry
that differs from the request only in an unconfigured segment is accepted:
+##### How this validator matches a request to an audience entry #####
-* **cluster-domain** — always compared, against the allow-list in
`request.audience.k8s.cluster-domains`. This is the only segment with no way to
disable the check.
-* **namespace** — compared when
`request.audience.k8s.namespace.from.destination.spiffe-id.header.name` and/or
`request.audience.k8s.server.name.header.name` is configured. Configuring both
is supported and gives two independent sources for the same value; when both
are configured, both are read and their namespaces must agree, or the request
is rejected.
-* **service-name** — compared only when
`request.audience.k8s.server.name.header.name` is configured.
-* **resource-path** — compared when `request.audience.k8s.path.header.name` is
configured.
+Which of the URL's segments are actually compared against the request depends
entirely on which of this validator's header parameters are configured. A
segment with no configured source is not compared at all (the DNS form's
namespace-less host is the one exception, described above), so an `aud` entry
that differs from the request only in an unconfigured segment is accepted. At
least one of the destination SPIFFE-id header, the server-name header, or the
path header must be configured, or the topology fails to start.
-A configuration that sets a SPIFFE-id header and a path header but no
server-name header — validating cluster-domain, namespace, and path, but not
service-name — is a supported and expected configuration. Under it, a
delegation token minted to authorize calling one service in a namespace at a
given path is also accepted when presented to a *different* service in that
same namespace at that same path. The token is still bound to one cluster, one
namespace, and one path, which is a large reduction from no destination binding
at all, but it is not a binding to one specific service; closing that gap
requires configuring the server-name header as well, which requires a trusted
source for it (see Prerequisites below).
+Cluster domain is checked differently for each form, since the two forms carry
it differently:
-At least one of the three header parameters must be configured, or the
topology fails to start.
+* **Cluster domain, DNS form.** Checked against
`request.audience.k8s.dns.format.cluster.domain`: the labels after the required
`svc` label must be a label-boundary prefix of it, taken from the left. See DNS
form above for the exact rule.
+* **Cluster domain, custom form.** Checked as a literal match of the authority
against the `request.audience.k8s.cluster-domains` allow-list. See Custom form
above.
-Namespace and service-name segments are compared case-insensitively. The
resource path is compared case-sensitively, and never percent-decoded on either
side — a raw and a percent-encoded candidate are both derived from the `aud`
entry's path and either is accepted, but an escaped `%2F` in either value is
never treated as equal to a literal `/`. A path taken from a header is required
to already be free of `.` and `..` segments and of empty segments; such a value
is rejected rather than normalized, since normalizing it here could disagree
with however the component that actually routes the request resolves it.
+Both checks are always enforced, with no way to disable either one. They are
two different mechanisms, not two configurations of a single mechanism, so
configuring one has no effect on the other.
-##### Prerequisites #####
+The remaining segments are compared the same way regardless of which form an
entry parsed as:
+
+* **Namespace.** Compared when
`request.audience.k8s.namespace.from.destination.spiffe-id.header.name` and/or
`request.audience.k8s.server.name.header.name` is configured. Configuring both
is supported and gives two independent sources for the same value; when both
are configured, both are read and their namespaces must agree, or the request
is rejected.
+* **Service name.** Compared only when
`request.audience.k8s.server.name.header.name` is configured.
+* **Resource path.** Compared when `request.audience.k8s.path.header.name` is
configured, and when the `aud` entry itself carries a path other than exactly
`/`. See Path matching below.
-Each of the three header parameters below names a header this validator trusts
at face value, with no independent verification. Every one of them **must** be
populated only by a component upstream of Knox that is trusted to set or
overwrite it correctly with a value it independently determined — never a
header an untrusted caller could set or influence. If that guarantee does not
hold for a configured header, an untrusted caller controls the corresponding
side of the comparison, and the destination binding this validator provides is
defeated. Establishing that guarantee is the responsibility of the deployment's
network/ingress layer, not something this validator can check.
+A configuration that sets a SPIFFE-id header and a path header but no
server-name header (validating cluster domain, namespace, and path, but not
service name) is supported. Under it, a delegation token minted to authorize
calling one service in a namespace at a given path is also accepted when
presented to a *different* service in that same namespace at that same path.
The token is still bound to one cluster, one namespace, and one path, which is
a large reduction from no destination binding at all, but it is not a binding
to one specific service; closing that gap requires configuring the server-name
header as well, which requires a trusted source for it (see Prerequisites
below).
-If the path this validator checks can be changed by the surrounding
infrastructure after this validator runs (for example, a routing rule that
rewrites the request path), configure the path header to carry the path as it
was *before* any such rewrite — the audience being validated describes the
resource the caller requested, not the path an internal rewrite happens to
route it to.
+Namespace and service-name segments are compared case-insensitively. The
resource path is compared case-sensitively, and never percent-decoded on either
side: a raw and a percent-encoded candidate are both derived from the `aud`
entry's path and either is accepted, but an escaped `%2F` in either value is
never treated as equal to a literal `/`. A path taken from a header is required
to already be free of `.` and `..` segments and of empty segments; such a value
is rejected rather than normalized, since normalizing it here could disagree
with however the component that actually routes the request resolves it.
##### Configuration parameters #####
-All parameters are set as `<param>` entries on the `JWTProvider` provider,
alongside `request.audience.validator`.
+All parameters are set as `<param>` entries on the `JWTProvider` provider,
alongside `request.audience.validator`. They are grouped below by what they
apply to.
+
+###### Trusted-header parameters ######
+
+These name the headers this validator reads; see Prerequisites below for why
each one must come from a trusted source. They apply regardless of which `aud`
form an entry takes.
Name | Description | Default
----------|-----------|--------
-request.audience.k8s.namespace.from.destination.spiffe-id.header.name | HTTP
header carrying the destination workload's SPIFFE id, e.g.
`spiffe://trust-domain/ns/namespace/sa/service-account`. When set, the
namespace inside the SPIFFE id is compared against the `aud` entry's namespace
segment. Trusted-header parameter — see Prerequisites. | n/a (unset; namespace
is then not matched from this source)
-request.audience.k8s.server.name.header.name | HTTP header carrying the
destination workload's FQDN, in the form
`server-name.namespace<cluster-suffix>`, optionally followed by `:port`. When
set, both the server-name and namespace segments are compared. Trusted-header
parameter — see Prerequisites. | n/a (unset; this header is then not read at
all)
-request.audience.k8s.server.name.cluster-suffix | Suffix that terminates the
FQDN read from the header above, e.g. `.svc.cluster.local`. Only read when that
header parameter is itself configured. | `.svc.cluster.local`
-request.audience.k8s.path.header.name | HTTP header carrying the path to match
against the `aud` entry's resource-path segment. Trusted-header parameter — see
Prerequisites. | n/a (unset; resource path is then not matched)
+---------|-------------|--------
+request.audience.k8s.namespace.from.destination.spiffe-id.header.name | HTTP
header carrying the destination workload's SPIFFE id, e.g.
`spiffe://trust-domain/ns/namespace/sa/service-account`. When set, the
namespace inside the SPIFFE id is compared against the `aud` entry's namespace
segment. | n/a (unset; namespace is then not matched from this source)
+request.audience.k8s.server.name.header.name | HTTP header carrying the
destination workload's FQDN, in the form
`service-name.namespace<cluster-suffix>`, optionally followed by `:port`. When
set, both the service-name and namespace segments are compared. | n/a (unset;
this header is then not read at all)
+request.audience.k8s.server.name.cluster-suffix | Suffix that terminates the
FQDN read from the header above, e.g. `.svc.cluster.local`. Only read when that
header parameter is itself configured. Independent of
`request.audience.k8s.dns.format.cluster.domain` below, which plays the same
role for a DNS-form `aud` entry rather than for this header. |
`.svc.cluster.local`
+request.audience.k8s.path.header.name | HTTP header carrying the path to match
against the `aud` entry's resource-path segment. | n/a (unset; resource path is
then not matched)
request.audience.k8s.path.header.from.url | Whether the header above carries a
full URL whose path component should be extracted, rather than already being
the bare path to match. Only read when that header parameter is itself
configured. | `false`
-request.audience.k8s.cluster-domains | Comma-separated allow-list of cluster
domains (host, with an optional `:port`, defaulting to `443`) an `aud` entry's
authority may match. Always enforced; there is no way to disable this check. |
`service.local` (a placeholder that fails closed until set to the deployment's
own cluster domain(s))
-request.audience.k8s.require-all-audiences-match | Whether at least one `aud`
entry matching the destination is sufficient (`false`, the ordinary "am I an
intended audience" semantic of [RFC 7519
§4.1.3](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3)), or whether the
claim must be non-empty and every entry must match (`true`). See the note below
on combining this with the minting-side flags. | `false`
-request.audience.k8s.audience.path.prefix | Optional path prefix searched for
in each `aud` entry's path before namespace and service-name are parsed out of
it — see Optional path prefix above. | n/a (unset; an entry's path must then
begin with namespace and service-name straight after the authority)
+request.audience.k8s.source.spiffe-id.header.name | HTTP header carrying the
calling (source) workload's SPIFFE id. Supplies the namespace for a
namespace-less DNS-form `aud` entry, and is the identity compared against the
actor subject when actor-subject binding is enabled. | n/a (unset; a
namespace-less DNS-form entry can then never match, and actor-subject binding
cannot be enabled)
-`request.audience.k8s.require-all-audiences-match=true` is independent of, and
does not by itself prevent, a delegation token being minted with more than one
audience in the first place — it only changes how this validator reacts to one
once presented. `JWTFederationFilter` separately exposes
`delegation.enforce.requested.audience.required` and
`delegation.enforce.requested.audience.max.one`, which constrain what a
delegation token may be minted with. A deployment that wants every delegation
token to carry exactly one audience should set all three: the two minting-side
flags stop a multi-audience token from being issued, and
`require-all-audiences-match` independently rejects one at the destination even
if it is minted anyway, for example by an older or misconfigured issuer.
+###### DNS-form parameters ######
+
+Name | Description | Default
+---------|-------------|--------
+request.audience.k8s.dns.format.enabled | Whether an `aud` entry written as a
k8s service DNS name is accepted at all. See DNS form above for why a
multi-cluster or multi-trust-domain deployment may want this `false`. When
`false`, `request.audience.k8s.dns.format.cluster.domain` is unused. | `true`
+request.audience.k8s.dns.format.cluster.domain | The cluster domain a DNS-form
`aud` entry's host is checked against, e.g. the `cluster.local` in
`https://reporting-svc.analytics.svc.cluster.local`. See DNS form above for the
exact shortening and matching rule. Only read when DNS-form entries are
enabled. Named apart from `cluster-domains` below specifically so the two are
not mistaken for each other. | `cluster.local`
+
+###### Custom-form parameters ######
+
+Name | Description | Default
+---------|-------------|--------
+request.audience.k8s.cluster-domains | Comma-separated allow-list of cluster
domains (host, with an optional `:port`, defaulting to `443`) a custom-form
`aud` entry's authority may match. Always enforced for a custom-form entry;
there is no way to disable this check. Does not apply to a DNS-form entry; see
`dns.format.cluster.domain` above. | `service.local` (a placeholder that fails
closed until set to the deployment's own cluster domain(s))
+request.audience.k8s.audience.path.prefix | Optional path prefix searched for
in a custom-form `aud` entry's path before namespace and service-name are
parsed out of it; see Optional path prefix above. Never applies to a DNS-form
entry. | n/a (unset; a custom-form entry's path must then begin with namespace
and service-name straight after the authority)
+
+###### Cross-cutting behavior parameters ######
+
+Name | Description | Default
+---------|-------------|--------
+request.audience.k8s.require-all-audiences-match | Whether at least one `aud`
entry matching the destination is sufficient (`false`, the ordinary "am I an
intended audience" semantic of RFC 7519 section 4.1.3), or whether the claim
must be non-empty and every entry must match (`true`). See the note below on
combining this with the minting-side flags. | `false`
+request.audience.k8s.validate.audiences.without.act.claim | Whether a token
with no `act` claim is validated against the request destination (`true`) or
against the fixed `knox.token.audiences` list (`false`). See Non-delegated
tokens below. Has no effect on a token that does carry an `act` claim. | `true`
+request.audience.k8s.enforce.act.sub.service-account.matches.source.spiffeid |
Whether the most recent actor in the token's `act` chain must match the source
workload's SPIFFE id. See Actor-subject binding below. Requires
`source.spiffe-id.header.name` to be configured; setting this `true` without it
fails topology startup. | `false`
+request.audience.k8s.enforce.act.sub.is.service.account | Whether an actor
subject that cannot be read as a k8s service-account subject fails validation
(`true`) or passes unchecked (`false`). Only consulted when the parameter above
is `true`. | `false`
+
+`request.audience.k8s.require-all-audiences-match=true` is independent of, and
does not by itself prevent, a delegation token being minted with more than one
audience in the first place; it only changes how this validator reacts to one
once presented. `JWTFederationFilter` separately exposes
`delegation.enforce.requested.audience.required` and
`delegation.enforce.requested.audience.max.one`, which constrain what a
delegation token may be minted with. A deployment that wants every delegation
token to carry exactly one audience should set all three: the two minting-side
flags stop a multi-audience token from being issued, and
`require-all-audiences-match` independently rejects one at the destination even
if it is minted anyway, for example by an older or misconfigured issuer.
+
+##### Path matching #####
+
+An `aud` entry with no resource path, or with a resource path of exactly `/`,
matches ANY request path, for either form, including when
`request.audience.k8s.path.header.name` is configured and the header is present
on the request. This is deliberate, not an oversight: an entry with an empty
path is a statement about which destination service a token may be presented
to, not about which operations on that service it may invoke. An audience meant
to be scoped to one endpoint must carry that endpoint's path.
+
+##### Non-delegated tokens #####
+
+`request.audience.k8s.validate.audiences.without.act.claim` controls how a
token with no `act` claim, that is, a token that is not a delegation token, is
handled. It defaults to `true`: such a token's `aud` claim is validated against
the request's actual destination in exactly the same way as a delegation
token's, and the request is rejected if the token carries no `aud` claim, or an
`aud` that is not a valid destination for this request. Setting it to `false`
sends a non-delegated token's `aud` claim to the fixed `knox.token.audiences`
check (`AbstractJWTFilter.matchesConfiguredAudiences`) instead, the same as if
this validator were not selected for it. This parameter has no effect on a
token that does carry an `act` claim: that token is always
destination-validated by this validator.
+
+##### Actor-subject binding #####
+
+Setting
`request.audience.k8s.enforce.act.sub.service-account.matches.source.spiffeid`
(default `false`) to `true` enables validating that the source SPIFFE id's
service-account identity matches the most recent actor's service-account
identity in the token's `act` chain, provided that actor's `sub` claim is of
the form `system:serviceaccount:<namespace>:<service-account-name>`. This
enforces that a delegated token can only be used by the service account that
exchanged for it. Trust domain and issuer are not compared, since the two
subject forms carry no comparable value for either, so this check is meaningful
only within a single trust domain. If the actor's `sub` claim is not of the
expected form, the check is skipped (the request passes), unless
`request.audience.k8s.enforce.act.sub.is.service.account` is also `true`, in
which case it fails. See below for that parameter.
+
+Enabling this parameter requires
`request.audience.k8s.source.spiffe-id.header.name` to also be configured;
leaving it unset while this is `true` fails the topology at startup, rather
than leaving a check the deployment asked for silently inactive.
+
+`request.audience.k8s.enforce.act.sub.is.service.account` (default `false`) is
only consulted when the parameter above is `true`. It controls what happens
when the actor subject cannot be read as a Kubernetes service-account subject
at all: `false` (the default) passes such an actor without comparing it, since
a legitimate actor subject may take another form; `true` fails validation
instead.
+
+A token whose `act` claim is present but is not a JSON object, so no actor
subject can be read from it at all, fails validation when
`enforce.act.sub.service-account.matches.source.spiffeid` is `true`, the same
as any other actor subject this check rejects. A token with no `act` claim
carries no actor at all, so this check does not apply to it regardless of
either parameter's value.
+
+##### Prerequisites #####
+
+Each of the four header parameters below (destination SPIFFE-id, server-name,
path, and source SPIFFE-id) names a header this validator trusts at face value,
with no independent verification. Every one of them **must** be populated only
by a component upstream of Knox that is trusted to set or overwrite it
correctly with a value it independently determined, never a header an untrusted
caller could set or influence. If that guarantee does not hold for a configured
header, an untrusted caller controls the corresponding side of the comparison,
and the destination binding this validator provides is defeated. Establishing
that guarantee is the responsibility of the deployment's network/ingress layer,
not something this validator can check.
+
+When a header parameter is configured, that header must be present and
parseable in the shape this validator requires for it on every request, or
validation fails, whether or not the `aud` entries actually presented on a
given request would have needed that value. This holds for all four header
parameters.
+
+If the path this validator checks can be changed by the surrounding
infrastructure after this validator runs (for example, a routing rule that
rewrites the request path), configure the path header to carry the path as it
was *before* any such rewrite: the audience being validated describes the
resource the caller requested, not the path an internal rewrite happens to
route it to.
##### Example topology #####
+This example is the combination that validates the destination namespace and
path for every token (including non-delegated ones), binds a delegation token's
actor to the workload presenting it, and deliberately does not validate
service-name or accept the custom `aud` form. Every parameter this validator
reads is shown, each marked `(default)` or `(non-default)`, so the full
configuration surface, and what it does and does not bind, is visible in one
place. Header names are examples; the real ones depend on what the service mesh
sets.
+
<provider>
<role>federation</role>
<name>JWTProvider</name>
<enabled>true</enabled>
+
+ <!
Issue Time Tracking
-------------------
Worklog Id: (was: 1046883)
Time Spent: 1.5h (was: 1h 20m)
> Extend RequestAudienceValidator for non-delegation, dns form, service-only
> matching, and for act source validation
> ------------------------------------------------------------------------------------------------------------------
>
> Key: KNOX-3509
> URL: https://issues.apache.org/jira/browse/KNOX-3509
> Project: Apache Knox
> Issue Type: Sub-task
> Components: JWT
> Reporter: Harrison Sheinblatt
> Priority: Major
> Time Spent: 1.5h
> Remaining Estimate: 0h
>
> Extend the RequestAudienceValidator:
> # Enable audience validation of non-delegation tokens
> # Allow audiences in dns service form, for use when there is one trust domain
> # Allow audiences with no path to match all paths: this indicates that the
> audience is intended to show only the allowed service destination, not the
> particular endpoints. When used, the capabilities of the service allowed can
> be specified otherwise, such as with scopes in the future.
> # Allow an opt in check for service account actors to validate that the most
> recent act sub service account matches the source spiffe id from a trusted
> header. This shows that the service account for which the token was issued is
> the one using it, and prevents a leaked token from being used by a different
> source.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)