smolnar82 opened a new pull request, #1460: URL: https://github.com/apache/knox/pull/1460
[KNOX-3520](https://issues.apache.org/jira/browse/KNOX-3520) - Support multi-hop RFC 8693 delegation via policy-bound subject audiences ## What changes were proposed in this pull request? A token-exchange topology validates the `subject_token` and the `actor_token` against the same `knox.token.audiences` list. A token minted by a delegated exchange carries only the requested target audience, so it can never be presented again as a `subject_token`, and on-behalf-of chains stop after the first hop, even though `delegation.max.actor.chain.depth` allows deeper chains. The workarounds either break recipient binding (adding the token-service audience to every downstream token) or open the gate (dropping `knox.token.audiences`, which lets any stolen SA token, minted for any audience, act as a delegation actor). This PR adds one narrow relaxation that is bound to the actor: a re-exchanged `subject_token` is accepted only by the service it was addressed to. **New delegation policy field: `subjectAudiences`** - The audiences that identify the actor *as a recipient*. This is the inbound counterpart of `resourcePolicy`, which lists the services the actor may mint tokens for. - Optional, defaults to empty. An empty set never matches, so existing policies behave exactly as before. - Stored in a new `DELEGATION_POLICY_SUBJECT_AUDIENCES` child table, added to both the default and the Oracle DDL scripts, with `ON DELETE CASCADE`. `createTablesIfNotExist` creates missing tables at startup, so existing databases pick up the new table. - Exposed through the KNOXIDF_ADMIN delegation-policy API (request and response). Blank values are rejected with `400 invalid_request`. - `JdbcDelegationPolicyService#evaluate` denies with the new audit reason `subject_audience_not_allowed` when the check is required and the subject token's `aud` does not intersect `subjectAudiences`. The client still gets the generic `400 invalid_request` "rejected by policy". **New JWTProvider parameters (both off by default)** | Parameter | Effect | |---|---| | `token.exchange.subject.token.policy.audiences.issuers` | Comma-separated issuers. On a delegated exchange (`actor_token` present), a `subject_token` from one of these issuers that fails the `knox.token.audiences` check is accepted only if its `aud` intersects the actor policy's `subjectAudiences`. Never applies to actor tokens, headless or same-subject exchanges. Unset: today's behavior. | | `delegation.headless.reject.subject.with.act` | When `true`, a headless `subject_token` (the acting party) that carries an `act` claim is rejected with `400 invalid_request` (audit reason `headless_subject_has_act`). | **Resulting audience rules** | # | Token | Rule | Status | |---|---|---|---| | 1 | `actor_token`, and the headless `subject_token` | `aud` must contain a `knox.token.audiences` value | Unchanged | | 2 | First-hop delegated `subject_token` | `aud` must contain a `knox.token.audiences` value | Unchanged | | 3 | Re-exchanged delegated `subject_token` | Without a `knox.token.audiences` value, accepted only if an `actor_token` is present, `iss` is listed, and `aud` ∩ `subjectAudiences` ≠ ∅ | **New** | | 4 | Same-subject `subject_token` | `aud` must contain a `knox.token.audiences` value | Unchanged | | 5 | Headless `subject_token` | Must not carry `act` (when enabled) | **New** | **Implementation** - `PolicyBoundSubjectAudienceValidator` (new) wraps the topology's `RequestAudienceValidator`. When the `knox.token.audiences` check fails for a token from a listed issuer, it defers the decision to the policy instead of rejecting. Signature, issuer, expiry and not-before checks are unchanged and still enforced. - `TokenExchangeHandler` uses it only for delegated exchanges (`createSubjectAudienceValidator`), and passes the subject token's audiences, plus whether the policy match is required, to the policy check through `PolicyCheckRequest`. - `JWTFederationFilter` reads the two new parameters. **Docs** `knox-site/docs/knoxidf/`: - `token_exchange.md`: the new `subjectAudiences` field, evaluation order, a new "Multi-hop delegation" section, and the configuration table. - `configuration.md`: the 2 new parameters. - `endpoints.md`: the admin API field and validation. - `security.md`: 2 new bullets. - `operations.md`: the new deny reason. ## How was this patch tested? - `mvn -pl gateway-spi,gateway-server,gateway-provider-security-jwt,gateway-service-knoxidf verify`. All modules pass, including checkstyle, PMD, SpotBugs and forbiddenapis. - New and updated unit and integration tests: - `PolicyBoundSubjectAudienceValidatorTest` (new): a delegate pass returns no policy match; a delegate failure is accepted provisionally only for a listed issuer (not for unlisted or null issuers); the configured audiences are passed to the delegate; the flag is reset on each validation. - `TokenExchangeHandlerTest`: - rule 3: a policy match is required only for a listed issuer whose token fails the configured audience check; a mismatch is rejected with `invalid_request` and audited; - rule 3 never applies without an actor token, to headless exchanges, when no issuers are configured, or to the actor token; - rule 5: enabled and disabled, with and without `act`, and not applied to delegated exchanges. - `JWTFederationFilterDelegationConfigTest`: parsing of the new parameters. - `JWTFederationFilterTokenExchangeTest`, full filter path: - a re-exchanged token from a listed issuer requires the policy audience match; - a bad signature is still rejected (`401`) even when the issuer is listed; - with no issuers configured, the token is rejected with "missing required token audience"; - actor tokens and same-subject exchanges are not relaxed. - `JdbcDelegationPolicyServiceTest`: - `subject_audience_not_allowed` cases: match, mismatch, empty policy set, token without `aud`, ignored when not required; - checked before the subject and resource checks; - register, update, registerOrUpdate and delete of `subjectAudiences`. - `DelegationPolicySchemaTest` and `KnoxDatabaseTest`: the new table is queryable, enforces its FK and cascades on delete; a pre-KNOX-3520 schema gets the table on upgrade. - `DelegationPolicyResourceTest`: field omitted, persisted, null elements filtered; empty or whitespace values rejected on register, update and registerOrUpdate. - `DelegationPolicyDtoImmutabilityTest`: defensive copies in the request and response DTOs. - `DelegationPolicyTest` and `PolicyCheckRequestTest`: new fields, defaults, immutability. - Docker Compose integration suite run locally: 149 passed. ## Integration Tests - New topology `.github/workflows/build/conf/topologies/knoxidf-token-multi-hop.xml`, added to the Dockerfile. It sets `knox.token.audiences=https://recipient1`, its own issuer (listed in both `jwt.expected.issuer` and `token.exchange.subject.token.policy.audiences.issuers`), `delegation.headless.reject.subject.with.act=true`, `delegation.max.actor.chain.depth=3`, nested `act` claims, and the `passthrough` audience validator. - New suite `.github/workflows/tests/test_multi_hop_delegation.py` (11 tests). Policies are seeded through the KNOXIDF_ADMIN API. tom is the subject; recursiveUser and sam alternate as actors. - 2-hop and 3-hop chains succeed. Each minted token has `sub=tom`, the multi-hop issuer, only the requested `aud`, and the full nested `act` chain. - A 4th hop is rejected by `delegation.max.actor.chain.depth`. - A re-exchange is rejected by policy when the actor's `subjectAudiences` do not match, or are empty. - A delegated token from an unlisted issuer is rejected for the missing audience, even though it is addressed to the actor. - A re-exchanged token is rejected in a same-subject exchange and as a headless subject. - A delegated token that does carry the token-service audience is still rejected as a headless subject (rule 5). - Actor tokens and first-hop subject tokens without the token-service audience are rejected. - `delegation_helpers.py`: - `DelegationPolicyAdmin.register()`/`update()` accept `subject_audiences`. - New shared `assert_issued_jwt()` helper. ``` tests-1 | ------------------------------------ tests-1 | Your code has been rated at 10.00/10 tests-1 | tests-1 | Waiting for knox... tests-1 | ============================= test session starts ============================== tests-1 | platform linux -- Python 3.10.20, pytest-9.0.3, pluggy-1.7.0 tests-1 | rootdir: /tests tests-1 | plugins: platformdirs-4.12.4 tests-1 | collected 149 items tests-1 | tests-1 | test_clientid_credentials.py ....... [ 4%] tests-1 | test_delegation.py ......... [ 10%] tests-1 | test_health.py ..... [ 14%] tests-1 | test_k8s_delegation.py ........ [ 19%] tests-1 | test_k8s_jwks_ca.py ........... [ 26%] tests-1 | test_k8s_serviceaccount_validation.py ...... [ 30%] tests-1 | test_knox_admin_path_traversal.py ... [ 32%] tests-1 | test_knox_auth_service_and_ldap.py ... [ 34%] tests-1 | test_knox_configs.py . [ 35%] tests-1 | test_knox_ldap_backend_password_alias_retry.py .. [ 36%] tests-1 | test_knox_ldap_cache.py ... [ 38%] tests-1 | test_knox_ldap_injection.py ....... [ 43%] tests-1 | test_knox_ldap_proxy_search.py ......... [ 49%] tests-1 | test_knoxauth_preauth_and_paths.py ...... [ 53%] tests-1 | test_knoxauth_token_forwarding.py ........... [ 61%] tests-1 | test_knoxidf.py ....... [ 65%] tests-1 | test_knoxsso_redirect.py . [ 66%] tests-1 | test_knoxtoken_jwt.py .................... [ 79%] tests-1 | test_multi_hop_delegation.py ........... [ 87%] tests-1 | test_remote_auth.py ... [ 89%] tests-1 | test_remoteauth_extauthz_additional_path.py .... [ 91%] tests-1 | test_token_exchange.py ............ [100%] tests-1 | tests-1 | =============================== warnings summary =============================== ... tests-1 | ----------------- generated xml file: /tests/test-results.xml ------------------ tests-1 | ================= 149 passed, 139 warnings in 83.73s (0:01:23) ================= tests-1 exited with code 0 Aborting on container exit... Container compose-tests-1 Stopping Container compose-tests-1 Stopped Config w Enable Watch d Detach ``` -- 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]
