Skip to content

KNOX-3534 - Add ingress source routing to the k8s destination audience validator - #1476

Open
hsheinblatt wants to merge 5 commits into
apache:masterfrom
hsheinblatt:KNOX-3534
Open

hsheinblatt wants to merge 5 commits into
apache:masterfrom
hsheinblatt:KNOX-3534

Conversation

@hsheinblatt

Copy link
Copy Markdown
Contributor

KNOX-3534 - Add ingress source routing to the k8s destination audience validator

What changes were proposed in this pull request?

K8sDestinationAudienceValidator can now route a request that arrives from a
configured ingress gateway to a different audience check than validation
against the request destination. It is off by default. It is controlled by
four new request.audience.k8s.* parameters:

ingress.source.spiffe-id.routing.enabled (default false)
ingress.source.spiffe-ids (no default)
ingress.source.spiffe-ids.routing.skip.trust.domain.match (default false)
ingress.source.spiffe-ids.routing.skip.validation (default false)

A request is from an ingress source when the SPIFFE id in the configured
source header matches an entry of ingress.source.spiffe-ids. Namespace,
service account and trust domain are compared ignoring case; the trust domain
is left out of the comparison only when skip.trust.domain.match is true. A
missing or unparseable header is never an ingress source. With routing
enabled, such a request is routed by its token:

  • a token with an act claim is rejected (400, with a logged message naming
    the header and its value), regardless of skip.validation, before any other
    check;
  • a token with no act claim is not destination-validated: it is checked
    against the fixed knox.token.audiences list, or, with skip.validation true,
    not audience-checked at all. The destination, server-name and path headers
    are not required for it.

Every other request is handled as before. Startup fails if routing is enabled
without the source SPIFFE id header or without ingress.source.spiffe-ids, or
if any ingress id entry is not a full parseable SPIFFE id (checked even when
routing is disabled).

The change also has a test-only part: regression coverage that pins the
existing routing of tokens with no act claim and the order of early
rejections before the new behavior was added (first commit of the series).
The parameters were later renamed and the javadoc, class and test names
updated (follow-up commits). knox-site/docs/config_request_audience_validator.md
documents the parameters, the outcome table, trust and security notes, and
corrects existing statements the feature made inaccurate.

How was this patch tested?

  • K8sIngressSourceRoutingTest (new, 44 tests): startup validation, every
    row of the outcome table in the class javadoc, trust-domain skip, case
    insensitivity, multiple ingress ids, missing/unparseable headers, fixed
    list semantics (null list, NONE entry), skip.validation, and ordering
    (rejection before actor-subject binding, skip before path header check).
  • K8sNonDelegationTokenRoutingTest (new, 13 tests): behavior for tokens
    with no act claim with routing off, including fallback to the fixed list
    and the ordering of the destination and source header rejections.
  • MatchesConfiguredAudiencesTest (+3 tests): fixed-list edge cases.
  • Existing K8s validator tests pass unchanged.
  • mvn -pl gateway-provider-security-jwt -am verify -Drat.skip=true
    (checkstyle, PMD, SpotBugs, forbiddenapis and unit tests): 769 tests in
    the module, 0 failures, BUILD SUCCESS.

Integration Tests

None

UI changes

N/A

Harrison added 5 commits October 27, 2026 23:19
… routing and early-rejection ordering

Pins how K8sDestinationAudienceValidator routes a token by the presence or
absence of its act claim, and the order in which its early rejections fire
relative to one another. Several of those rejections differ only in their
message, and nothing in the suite asserted which one wins when more than one
applies.

No production change.
…dience validator

A single topology can now authenticate both tokens arriving at the API
through an ingress gateway and delegation tokens used for service-to-service
calls. Four new parameters, all defaulting to the previous behavior:

  request.audience.k8s.ingress.source.spiffe-id.exception.enabled
  request.audience.k8s.ingress.source.spiffe-ids
  request.audience.k8s.ingress.source.spiffe-ids.skip.trust.domain.match
  request.audience.k8s.ingress.source.spiffe-ids.skip.validation

When the exception is enabled and a request's source SPIFFE id names one of
the configured ingress identities, a token with no act claim is either
accepted without any audience check or validated against the fixed
configured allowed-audience list, rather than against the request
destination. A token that does carry an act claim is never exempted; such a
token arriving from an ingress identity is rejected, since it authorizes a
service-to-service call and does not legitimately arrive through the
ingress. Enabling the exception without the source SPIFFE id header and the
ingress identity list fails topology startup.

With the exception left disabled, every token is validated exactly as
before.

Comments:
Issues with test naming and structure
Naming is confusing, switch from 'exception'.
Refactor tests with better named helpers.
KNOX-3534 - Add ingress source routing to the k8s destination audience validator

Tokens that reach the destination validator through an ingress gateway carry
an audience that does not name an in-cluster destination, so the destination
checks cannot be applied to them. Add an opt-in routing step, decided by the
source SPIFFE id header, that sends such tokens to a different audience check.

New parameters (all read by K8sDestinationAudienceValidator.init):
- ingress.source.spiffe-id.routing.enabled (default false): master switch.
- ingress.source.spiffe-ids: comma-separated ingress SPIFFE ids. Each entry
  is trimmed and must parse; an unparseable entry fails init even when
  routing is disabled, so a typo cannot lie dormant until routing is enabled.
- ingress.source.spiffe-ids.routing.skip.trust.domain.match (default false):
  match an ingress id on namespace and service account only, ignoring trust
  domain.
- ingress.source.spiffe-ids.routing.skip.validation (default false): see below.

init() requirements: enabling routing without the source SPIFFE id header
parameter, or without a non-blank ingress id list, fails topology startup
naming the missing parameter. The two skip flags are inert when routing is
disabled; with routing disabled every token is validated exactly as before.

Behavior when routing is enabled and the request's source SPIFFE id matches
a configured ingress id (matching is case-insensitive):
- Token without an act claim, skip.validation=false: accepted only if its
  audience is in the configured knox.token.audiences list, using
  AbstractJWTFilter.matchesConfiguredAudiences (a null list accepts all, NONE
  admits a token with no aud, NONE is not a wildcard). A rejection from this
  check carries no message, unlike destination rejections.
- Token without an act claim, skip.validation=true: accepted with no
  audience check.
- Token with an act claim: rejected with a message naming the act claim, the
  ingress source and the source header. A delegation token authorizes a must
  not arrive through the ingress. Skip flags do not exempt it, and it is
  rejected before the act-sub binding check. A malformed act claim from an
  ingress source is rejected the same way.
Both ingress routes run before the aud-presence and header-presence checks
because the configured-list check has its own handling of a token with no aud.

A request whose source header is missing or unparseable is not an ingress
source and takes the existing flow, including the existing missing-source
rejection. With validate.audiences.without.act.claim=false that flow already
returns the configured-list result first, and this change does not alter it.

Security note: the source SPIFFE id header is trusted at face value. Whoever
can set it can have a request routed as ingress, so it must be set or
overwritten by a trusted proxy. The configured-list route is only as strong
as knox.token.audiences. The source SPIFFE id header is preexisting, just
now used in another optional flow.

Tests: K8sIngressSourceRoutingTest covers init requirements, each route
proved by inputs that only that route accepts, trust-domain matching, the act
claim rejection and its ordering, and that require-all-audiences-match and the
namespace agreement check still apply to non-ingress sources.
MatchesConfiguredAudiencesTest gains cases for a null configured list with a
token without aud, a single matching audience, and NONE not matching a
non-matching audience.
…alidator

Describe the four request.audience.k8s.ingress.source.* parameters of the
Kubernetes destination audience validator in the request audience validator
docs: when a request's source SPIFFE id matches a configured ingress source,
a token with an act claim is rejected, and a token with no act claim is checked
only against the fixed knox.token.audiences list, or not audience-checked at
all when skip.validation is true.

Adds a parameter table, an "Ingress source routing" section with a full
outcome table, startup failure conditions and a trust warning for the source
header, and updates the example topology, enforcement table, example request,
prerequisites and failure modes. Existing sentences that assumed every token
reaches destination validation are corrected.

Docs only.
@github-actions

Copy link
Copy Markdown

Test Results

  4 files    4 suites   1m 20s ⏱️
149 tests 149 ✅ 0 💤 0 ❌
173 runs  173 ✅ 0 💤 0 ❌

Results for commit b2b41dc.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant