hsheinblatt opened a new pull request, #1432: URL: https://github.com/apache/knox/pull/1432
KNOX-3491 - Add pluggable request audience validation, with a Kubernetes destination validator ## What changes were proposed in this pull request? Adds a pluggable mechanism for validating a JWT's aud claim, and one implementation that checks a delegation token's audience against the request's actual destination rather than against a fixed configured list. RequestAudienceValidator is the pluggable check, modeled on the existing PreAuthValidator pattern and discovered via ServiceLoader (RequestAudienceValidatorService), resolved by a filter's request.audience.validator init parameter. AbstractJWTFilter gains a validator-accepting overload of doFullTokenValidation()/ validateToken(); the existing methods become one-line forwarders onto the previous fixed-list behavior (matchesConfiguredAudiences()), so every call site is unaffected unless it opts in. JWTFederationFilter is the only call site wired to it; with request.audience.validator unset its behavior is unchanged. The one implementation added, K8sDestinationAudienceValidator (request.audience.k8s.destination.validation), only engages for a token carrying a delegation act claim. Each aud entry is expected to be a URL of the form https://cluster-domain[:port]/namespace/service-name[/resource-path], parsed by the new AudienceResource record. An optional request.audience.k8s.audience.path.prefix lets an entry carry extra leading path segments before namespace and service-name, e.g. for network routing; left unset, namespace and service-name must begin straight after the authority. Which segments are actually compared is driven by which trusted-header init params are configured: namespace from a destination SPIFFE-id header (SpiffeId, relocated from gateway-provider-security-k8s into gateway-spi) and/or a Kubernetes server-name header (parsed by the new DestinationServiceName record); service-name only from the server-name header; resource-path only from a dedicated path header. cluster-domain, checked against an allow-list, is always enforced. At least one header param must be configured or init fails fast; when both namespace sources are configured, they must agree or the request is rejected fail-closed. Every header this validator reads is trusted at face value with no independent verification -- each must be populated only by a component upstream of Knox, never one an untrusted caller could influence. Values pulled from these headers (namespace, service name, port) are parsed only as far as their shape requires and are not otherwise validated (e.g. a non-numeric or absent port suffix is simply stripped, not rejected): an incorrect value just fails to match the aud entry later, at comparison, rather than being rejected at parse time. Namespace and service-name are compared case-insensitively; the resource path is compared case-sensitively and never percent-decoded, with both a raw and a percent-encoded candidate accepted on the aud side. request.audience.k8s.require-all-audiences-match controls whether one matching aud entry is sufficient (default) or every entry must match; independent of, and not a substitute for, JWTFederationFilter's existing delegation.enforce.requested.audience.* minting-side controls. What changed: - gateway-spi: AudienceValidationResult; SpiffeId/SpiffeIdTest moved from gateway-provider-security-k8s into org.apache.knox.gateway.util. - gateway-provider-security-k8s: ServiceAccountValidator's import updated to follow the SpiffeId move; no behavior change. - gateway-provider-security-jwt: RequestAudienceValidator interface; RequestAudienceValidatorService; AbstractJWTFilter (matchesConfiguredAudiences() extraction, new validator-accepting overloads); JWTFederationFilter (validator resolution, lifecycle wiring, doFilter integration); DestinationServiceName and AudienceResource (aud/header parsing records, including query-string/fragment stripping and the path-prefix search); K8sDestinationAudienceValidator and its META-INF/services registration; new jetty-util dependency (URIUtil, for percent- encoding the resource path). - knox-site: config_request_audience_validator.md (new provider doc, including the path-prefix parameter) and its mkdocs.yml nav entry. ## How was this patch tested? - AudienceValidationResultTest (3), SpiffeIdTest (7, moved unchanged), RequestAudienceValidatorServiceTest (5): construction/lookup behavior, including init-param unset/known/unknown and the production META-INF/services registration. - AbstractJWTFilter/CommonJWTFilterTest, JWTFederationFilterTest: matchesConfiguredAudiences() extraction is behavior-preserving; no-validator-configured regression; init() rejecting an unknown validator name; a configured validator invoked from doFilter and its destroy() propagated. - DestinationServiceNameTest (13): valid FQDN parsing with/without port and with lower-casing/whitespace handling; a trailing ":<anything>" suffix always stripped rather than validated as a port; suffix/label-count mismatches and null/empty input rejected; a non-label (whitespace-containing) segment parsing through rather than being rejected. - AudienceResourceTest (40): URL shape parsing (scheme, authority, userinfo, path segment count, dot/dot-dot/empty segments); query string and fragment stripped from the path; port normalization (absent/explicit-default/bare-colon); an empty namespace or service-name segment parsing through rather than being rejected; raw vs. percent-encoded resource path candidates; null/blank input, trimming, and case-insensitive scheme/host/namespace/service-name handling; and the audience.path.prefix search (found immediately or after skipped segments, first-occurrence-wins, case- and segment-boundary-sensitive matching, a multi-segment prefix, and a missing or too-short match failing to parse). - K8sDestinationAudienceValidatorTest (55): init fail-fast (no header param configured; each of the three alone sufficient; cluster-domains entries with a path, userinfo, scheme, or unparseable port rejected, via java.net.URI-based parsing); no-act-claim fallthrough; a delegation token with a null/empty aud claim rejected; each header required and rejecting the request when missing; namespace-only, service-name-plus-namespace, and path-only checks passing on a match and failing on a mismatch; an unconfigured segment gap accepted; both namespace sources agreeing/disagreeing; aud entries with the wrong shape never matching; malformed/suffix-mismatched/wrong-label-count server-name headers rejected, trailing port and custom cluster suffix honored; cluster-domain port equivalence rules; dot/dot-dot/empty request-path segments rejected and a trailing slash not causing a mismatch; raw vs. encoded path candidates on both sides; path-header- from-url extraction; require-all-audiences-match accepting/rejecting on partial vs. full agreement; audience.path.prefix matching and its opt-in default; and getName(). ## UI changes N/A -- 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]
