hsheinblatt opened a new pull request, #1448:
URL: https://github.com/apache/knox/pull/1448

   KNOX-3509 - Add k8s service DNS audience form, non-delegation audience 
validation, any-path matching, and actor-subject binding to the k8s destination 
audience validator
   
   ## What changes were proposed in this pull request?
   
   Extends K8sDestinationAudienceValidator with a second accepted aud
   format, extends destination validation to tokens with no act claim,
   allows an empty path to mean service-only matching (any destination
   path), and adds an opt-in check binding a delegation token's actor
   to the service account presenting it. Also adds a parameter to
   disable the new format for a deployment that cannot use it safely.
   
   A second accepted aud entry shape, the "DNS form", is
   https://<service>[.<namespace>[.svc[.<cluster-domain>]]][:port]
   [/resource-path] -- the k8s service DNS name a client already calls,
   shortened from the right the way a DNS resolver search path allows.
   The previously-supported shape, now called the "custom form", is
   unchanged: https://cluster-domain[:port]/[skipped-segments/]namespace
   /service-name[/resource-path]. Each aud entry is tried as the DNS form
   first and the custom form second, under the new AudienceResource
   record shared by both parse methods (replacing the single custom-form
   parser), and is accepted if either matches; the two forms produce the
   same AudienceResource fields, so the rest of the validator's matching
   logic does not need to know which form a candidate came from. A host
   of three or more labels must have "svc" as its third label, and any
   labels past it must be a label-boundary prefix of the configured
   request.audience.k8s.dns.format.cluster.domain (default cluster.local)
   taken from the left; a trailing dot on the host is rejected, as a
   deliberate restriction rather than an oversight -- no normal client
   request produces one. A one-label (service-only) DNS-form host has no
   namespace of its own; its namespace is instead taken from the source
   workload's SPIFFE id (see below) and must equal the destination
   namespace, which is the one case in this validator where a segment
   with no configured source is still compared against rather than
   skipped. The custom form's cluster-domain allow-list,
   request.audience.k8s.cluster-domains, now applies only to custom-form
   entries; the DNS form's cluster domain is checked separately, against
   the new single-value request.audience.k8s.dns.format.cluster.domain,
   named apart from cluster-domains specifically so the two are not
   mistaken for each other.
   
   An aud entry with no resource path, or a resource path of exactly "/",
   now matches any request path for either form, even when the path
   header is configured and present -- it is a statement about which
   service a token may be presented to, not about which operation on that
   service it may invoke.
   
   request.audience.k8s.dns.format.enabled (default true) controls
   whether the DNS form is tried at all. A deployment fronting more than
   one Kubernetes cluster or trust domain behind a single validator
   instance should set it false: a DNS-form host carries no cluster or
   trust-domain identifier of its own, unlike the custom form's explicit
   cluster-domain allow-list, so there is no way to tell which cluster a
   bare <service>.<namespace> host belongs to. When false, every entry is
   evaluated only as the custom form and dns.format.cluster.domain is
   unused.
   
   request.audience.k8s.validate.audiences.without.act.claim (default
   true) extends destination validation to a token with no act claim,
   i.e. one that is not a delegation token: its aud claim is now checked
   against the request's actual destination the same way a delegation
   token's is, and the request is rejected if it carries no aud claim or
   one that is not a valid destination for this request. Setting it to
   false sends such a token to AbstractJWTFilter.matchesConfiguredAudiences
   instead, the same handling every token had before this change. This
   parameter has no effect on a token that does carry an act claim; that
   token is unconditionally destination-validated regardless of its
   value.
   
   request.audience.k8s.enforce.act.sub.service-account.matches.source.spiffeid
   (default false) binds a delegation token's actor to the service account
   presenting it: when true, the most recent actor in the token's act
   chain, read via the existing TokenUtils.extractActorChain, must be a
   Kubernetes service-account subject (system:serviceaccount:<namespace>
   :<service-account-name>, parsed by the new ServiceAccountSubject
   record, which ActorIdentity now also uses for its own prefix constant)
   matching the namespace and service-account name in the SPIFFE id named
   by the new request.audience.k8s.source.spiffe-id.header.name. That header
   also supplies the namespace for a namespace-less DNS-form host, as
   above. Enabling this parameter without configuring that header fails
   topology startup rather than leaving the check silently inactive.
   request.audience.k8s.enforce.act.sub.is.service.account (default
   false), consulted only when the parameter above is true, controls
   whether an actor subject that cannot be read in that form fails
   validation (true) or passes unchecked (false). An act claim present
   but not a JSON object -- so no actor subject can be read from it at
   all -- fails validation when the matches-source-spiffeid check is
   enabled, the same as any other rejected actor subject; this is
   distinct from a token with no act claim at all, which the check does
   not apply to.
   
   Elsewhere, parsing a comma-separated list (currently only
   cluster-domains) no longer silently drops an entry that trims to
   empty: a stray ",," or trailing "," now produces an empty entry that
   is passed through to parsing and rejected there, the same as any other
   malformed entry, since this is operator-supplied configuration and not
   request data.
   
   knox-site/docs/config_request_audience_validator.md is rewritten to
   describe both aud formats, the dns.format.cluster.domain vs.
   cluster-domains split, the path-wildcard behavior, non-delegation
   validation, and actor-subject binding, including a worked
   provider-descriptor example and an item-by-item table of what that
   example configuration enforces.
   
   What changed:
   - gateway-provider-security-jwt: AudienceResource (DNS-form parsing
     added alongside the existing custom-form parsing, both producing
     the same record); K8sDestinationAudienceValidator
     (dns.format.enabled, dns.format.cluster.domain,
     source.spiffe-id.header.name,
     validate.audiences.without.act.claim,
     enforce.act.sub.service-account.matches.source.spiffeid,
     enforce.act.sub.is.service.account params; dual-format resolution;
     path-wildcard matching; non-delegation validation; actor-subject
     binding; stricter comma-separated-list parsing); ServiceAccountSubject
     (new record); ActorIdentity (reuses ServiceAccountSubject's prefix
     constant instead of its own copy, no behavior change).
   - knox-site: config_request_audience_validator.md rewritten for the
     above.
   
   ## How was this patch tested?
   
   - AudienceResourceTest (63 cases, up from 19): all prior custom-form
     cases retained; new DNS-form cases covering one/two/three/four/five
     label hosts, the literal "svc" requirement and its absence, the
     label-boundary cluster-domain prefix rule and rejecting a
     non-prefix or lookalike suffix or more labels than the cluster
     domain has, a configured cluster-domain override, a trailing dot
     and an empty label, case-insensitivity, port parsed but discarded,
     path-is-always-the-whole-path (including the "/" and no-path
     wildcard cases), query/fragment stripping, dot/dot-dot/empty path
     segments, wrong scheme or userinfo present, null/blank input, and a
     two-label base domain that also happens to parse as DNS form.
   - ServiceAccountSubjectTest (7 cases, new): valid subject parsing,
     null input, wrong prefix, missing separator, empty namespace or
     name, an extra colon in the name, and a bare prefix with nothing
     after it.
   - ActorIdentityTest (6 cases, new): a service-account subject tagged
     K8S_SA, a non-service-account subject and a null subject both
     tagged USER, a prefixed subject with no second colon still tagged
     K8S_SA, a bare prefix still tagged K8S_SA, and resourceName
     combining authority and id.
   - K8sDnsAudienceFormatTest (30 cases, new): end-to-end validator-level
     coverage of the DNS form across one/two/three/four/five label
     hosts, trailing-dot rejection with custom-form fallback also
     failing, the DNS form never consulting the custom form's
     cluster-domains list and vice versa, dns.format.cluster.domain being
     independent of server.name.cluster-suffix, dual-format fallback in
     both directions (a value parsing under both forms at once, a
     base domain that happens to also be DNS-shaped), the combined
     failure message naming both formats, require-all-audiences-match
     across a mix of DNS and custom-form entries, and
     dns.format.enabled false disabling DNS-form matching, falling back
     to custom form, and unwrapping the failure message to mention only
     the custom form.
   - K8sActorSubjectBindingTest (22 cases, new): the check disabled
     (default) passing regardless of actor/source mismatch; enabled and
     matching passes; enabled and a namespace or service-account-name
     mismatch fails; case-insensitive comparison; is.service.account
     true/false behavior for a non-service-account actor subject, and
     that is.service.account alone without the enabling parameter has no
     effect; the source header missing or unparseable failing
     regardless of whether the check itself is enabled; only the most
     recent actor in a multi-actor chain being checked; an act claim
     that is not a JSON object failing when the check is enabled and
     passing when it is not; a non-delegation token never running this
     check regardless of either flag; and several malformed-subject
     shapes (missing sub, sub not a string, missing colon separator,
     wrong prefix).
   - K8sAudiencePathMatchingTest (18 cases, new): the no-path and
     explicit-root-path wildcard for both forms; exact match and
     mismatch; raw vs. percent-encoded request-path candidates; the
     audience.path.prefix search applying to the custom form and never
     to the DNS form; the path header missing, empty, or (with
     path.header.from.url) malformed; and path.header.from.url
     extracting the path component and stripping query/fragment in both
     its true and (default) false modes.
   - K8sDestinationAudienceValidatorTest (72 cases, up from 51): all
     prior cases retained, now covering both aud forms where applicable;
     new cases for the non-delegation token being validated against the
     destination by default and the init-time checks for
     enforce.act.sub.service-account.matches.source.spiffeid requiring
     the source SPIFFE header, and for cluster-domains entries with a
     stray interior comma failing init.
   
   ## Integration Tests
   None were added, they will follow on.
   
   
   ## 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]

Reply via email to