smolnar82 commented on code in PR #1476: URL: https://github.com/apache/knox/pull/1476#discussion_r4442232445
########## knox-site/docs/config_request_audience_validator.md: ########## @@ -143,21 +143,67 @@ request.audience.k8s.audience.path.prefix | Optional path prefix searched for in 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.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, nor on a token with no `act` claim that ingress source routing routes (see Ingress source routing below). | `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` rejects a token if its `aud` claim is empty or if any entry in it fails to match this request's destination. It does not validate that the token carries exactly one audience, and it does not make a token single-use. A token with several `aud` entries is accepted when every entry matches this destination, for example the same service written in both the full and the shortened DNS form, a repeated entry, or an entry with no path alongside one with a specific path. What it does do is narrow where a multi-audience token can be used: only at a destination that all of its audiences name, so an entry naming some other destination can no longer be carried along behind a matching one. Limiting how many audiences a token carries is a minting-side concern. `JWTFederationFilter` 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 must set both of those; `require-all-audiences-match` is not a substitute for them. Setting it as well is still worthwhile as a destination-side backstop: a multi-audience token that is minted anyway, for example by an older or misconfigured issuer, is accepted only where every one of its audiences names that destination. +###### Ingress source routing parameters ###### + +These control ingress source routing, described in Ingress source routing below. The other three are inert unless `request.audience.k8s.ingress.source.spiffe-id.routing.enabled` is `true`, except that every entry of `request.audience.k8s.ingress.source.spiffe-ids` is validated at startup whether or not routing is enabled. + +Name | Description | Default +---------|-------------|-------- +request.audience.k8s.ingress.source.spiffe-id.routing.enabled | Whether a request that arrives from a configured ingress source is routed to a different audience check than validation against the request destination. When `true`, both `request.audience.k8s.source.spiffe-id.header.name` and `request.audience.k8s.ingress.source.spiffe-ids` must be configured, or topology startup fails. | `false` +request.audience.k8s.ingress.source.spiffe-ids | Comma-separated list of full SPIFFE ids that identify a request as arriving from an ingress source, e.g. `spiffe://example.org/ns/ingress/sa/gateway`. A request matches if the SPIFFE id in the source SPIFFE-id header matches any one entry. Each entry must be a full SPIFFE id of the form `spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>`, even when `skip.trust.domain.match` is `true`; an entry that does not parse, including the empty entry left by a stray, leading or trailing comma, fails topology startup whether or not routing is enabled. Surrounding whitespace on an entry is ignored. | n/a (unset; required when routing is enabled) +request.audience.k8s.ingress.source.spiffe-ids.routing.skip.trust.domain.match | Whether the trust domain of a configured ingress source id is left out of the comparison, so that only the namespace and the service account must match. This relaxes security: any trust domain is then accepted for that namespace and service-account pair, so identities in different trust domains that share the pair become indistinguishable to this check. | `false` (all three portions must match) +request.audience.k8s.ingress.source.spiffe-ids.routing.skip.validation | Whether a token with no `act` claim that arrives from a configured ingress source gets no audience validation at all (`true`), or is checked against the fixed `knox.token.audiences` list (`false`). With `false`, the fixed-list check accepts any `aud` if `knox.token.audiences` is not configured. Has no effect on a token with an `act` claim, on a token that does not arrive from a configured ingress source, or when routing is disabled. | `false` + ##### 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. +`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 destination-validated by this validator, or rejected if ingress source routing applies to it. Nor does it affect a token with no `act` claim that ingress source routing routes. + +##### Ingress source routing ##### + +A request arrives from an *ingress source* when the SPIFFE id in the header named by `request.audience.k8s.source.spiffe-id.header.name` matches an entry of `request.audience.k8s.ingress.source.spiffe-ids`. An entry matches when the namespace and the service account are equal, and the trust domain is equal too unless `request.audience.k8s.ingress.source.spiffe-ids.routing.skip.trust.domain.match` is `true`. Each portion is compared ignoring case. No wildcard or partial form is supported in any portion. A header that is missing, or does not parse as a SPIFFE id, matches no entry, so such a request is not from an ingress source. + +Ingress source routing is off by default (`request.audience.k8s.ingress.source.spiffe-id.routing.enabled` is `false`), and with it off nothing in this section applies: a request from an ingress source is handled like a request from any other source. When it is on, a request from an ingress source is routed by whether its token carries an `act` claim: + +* **A token with an `act` claim is rejected.** A delegation token authorizes one in-cluster workload to call another, and is not expected to arrive from an ingress source. The rejection does not depend on `skip.validation`, and is decided before any other check, so the token's `aud` claim and the other configured headers are not examined. The request gets the bad-request response, and the log message names the source SPIFFE-id header and its value. +* **A token with no `act` claim is not validated against the request destination.** What it is checked against depends on `request.audience.k8s.ingress.source.spiffe-ids.routing.skip.validation`. When it is `false` (the default), the token's `aud` claim is checked against the fixed `knox.token.audiences` list, the same check `validate.audiences.without.act.claim=false` uses. When it is `true`, no audience check of any kind is made, so a token with no `aud` claim is accepted, and so is one whose `aud` names something unrelated to this request. + +Either way a token with no `act` claim from an ingress source does not reach destination validation, so the destination SPIFFE-id, server-name and path headers are not required for it, none of the matching described under How this validator matches applies, and `validate.audiences.without.act.claim` and `require-all-audiences-match` have no effect on it. + +If `knox.token.audiences` is not configured, the fixed-list check accepts every token, including one with no `aud` claim. If it is configured, a token with an `aud` claim must share at least one entry with it, and a token with no `aud` claim is accepted only if the list contains `NONE`. With routing enabled and `skip.validation` left `false`, a token with no `act` claim from an ingress source is therefore checked only against `knox.token.audiences`; configure that list on the provider to restrict it at all. Review Comment: This is the most important operational caveat of the feature, but it's buried mid-section. Could it go into the `skip.validation` row of the param table as an explicit **must configure `knox.token.audiences`** note, or a warning callout? Otherwise the default looks safe while actually accepting every `aud`. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
