This is an automated email from the ASF dual-hosted git repository. smolnar82 pushed a commit to branch knox_idf in repository https://gitbox.apache.org/repos/asf/knox.git
commit 0bcb52a38b79d885463b999d35d816fc235fae05 Author: Sandor Molnar <[email protected]> AuthorDate: Wed Aug 12 01:27:31 2026 +0200 KNOX-3414: add Identity Federation (OIDC Provider) documentation book Add a new top-level knox-site navigation tab documenting KnoxIDF (Apache Knox as an OAuth2/OIDC Authorization Server), with seven chapters: Overview, Getting Started, Endpoint Reference, Configuration Reference, Security, Federation, and Operations. Enable Mermaid diagrams via pymdownx.superfences and add the architecture, federated-login, and consent images. Co-Authored-By: Claude Opus 4.8 <[email protected]> --- .../docs/assets/images/knoxidf/architecture.png | Bin 0 -> 319038 bytes .../docs/assets/images/knoxidf/consent_page.png | Bin 0 -> 87171 bytes .../assets/images/knoxidf/login_page_federated.png | Bin 0 -> 7329 bytes knox-site/docs/knoxidf/configuration.md | 174 ++++++++++++ knox-site/docs/knoxidf/endpoints.md | 299 +++++++++++++++++++++ knox-site/docs/knoxidf/federation.md | 221 +++++++++++++++ knox-site/docs/knoxidf/getting_started.md | 284 +++++++++++++++++++ knox-site/docs/knoxidf/index.md | 92 +++++++ knox-site/docs/knoxidf/operations.md | 182 +++++++++++++ knox-site/docs/knoxidf/security.md | 183 +++++++++++++ knox-site/mkdocs.yml | 14 +- 11 files changed, 1448 insertions(+), 1 deletion(-) diff --git a/knox-site/docs/assets/images/knoxidf/architecture.png b/knox-site/docs/assets/images/knoxidf/architecture.png new file mode 100644 index 000000000..38294a7d4 Binary files /dev/null and b/knox-site/docs/assets/images/knoxidf/architecture.png differ diff --git a/knox-site/docs/assets/images/knoxidf/consent_page.png b/knox-site/docs/assets/images/knoxidf/consent_page.png new file mode 100644 index 000000000..51b552dc4 Binary files /dev/null and b/knox-site/docs/assets/images/knoxidf/consent_page.png differ diff --git a/knox-site/docs/assets/images/knoxidf/login_page_federated.png b/knox-site/docs/assets/images/knoxidf/login_page_federated.png new file mode 100644 index 000000000..eac3689d8 Binary files /dev/null and b/knox-site/docs/assets/images/knoxidf/login_page_federated.png differ diff --git a/knox-site/docs/knoxidf/configuration.md b/knox-site/docs/knoxidf/configuration.md new file mode 100644 index 000000000..a832a0bbe --- /dev/null +++ b/knox-site/docs/knoxidf/configuration.md @@ -0,0 +1,174 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +# Configuration Reference + +KnoxIDF is configured through two layers: + +- **Topology `KNOXIDF` service parameters** — per-deployment behavior (token TTLs, consent, + federation, user attributes). These live inside the `<service><role>KNOXIDF</role>…</service>` + element of a topology file. +- **`gateway-site.xml` properties** — gateway-wide concerns shared with the rest of Knox (signing + keys, persistence/database, trusted-issuer cache tuning). + +This chapter lists every parameter, its default, and its effect. + +## How KNOXIDF service parameters are read + +### The `knoxidf.` → `knox.token.` prefix passthrough + +KnoxIDF reuses Knox's existing server-managed token machinery. Any `KNOXIDF` service parameter +written with the **`knoxidf.` prefix** has that prefix stripped and the remainder passed through +to the underlying token configuration. So the topology parameter: + +```xml +<param> + <name>knoxidf.knox.token.ttl</name> + <value>86400000</value> +</param> +``` + +is delivered to the token layer as `knox.token.ttl`. This lets you set any +[KnoxToken](../config_knox_token.md) parameter on the KnoxIDF service by prefixing it with +`knoxidf.`. + +!!! note "Access-token TTL is managed by Knox" + KnoxIDF issues **server-managed** tokens: the access-token lifetime is governed by the token + layer (`knoxidf.knox.token.ttl`) and, on the token-exchange topology, by the `JWTProvider`'s + `knox.token.exp.server-managed=true`. Clients cannot request an arbitrary lifetime. + +## Core `KNOXIDF` service parameters + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `knoxidf.knox.token.ttl` | (token-layer default) | Access-token lifetime in **milliseconds** (passthrough to `knox.token.ttl`). | +| `knoxidf.knox.token.limit.per.user` | (token-layer default) | Max concurrent server-managed tokens per user; `-1` = unlimited (passthrough to `knox.token.limit.per.user`). | +| `refresh.token.ttl` | `86400000` (24 h) | Refresh-token lifetime in milliseconds. Refresh tokens are issued only when the `offline_access` scope is granted, and are rotated on each use. | +| `knoxidf.auto.consent.enabled` | `false` | When `true`, the [consent screen](security.md#consent) is skipped. This is a **server-side** decision and is never read from a client request parameter. | +| `knoxidf.client.registration.anonymous.allowed` | `false` | When `true`, [dynamic client registration](security.md#dynamic-client-registration) accepts anonymous callers. Secure by default. | +| `token.exchange.topology.name` | (none) | Name of the token-exchange topology (fronted by `JWTProvider`) to which the `token_endpoint` and `userinfo_endpoint` are redirected in discovery. See the [two-topology model](getting_started.md#3-deploy-the-knoxidf-topologies). | +| `federated.op.names` | (none) | Comma-separated list of federated OP logical names to enable. See [Federated OP parameters](#federated-op-parameters). | + +!!! warning "Secure-by-default flags" + Both `knoxidf.auto.consent.enabled` and `knoxidf.client.registration.anonymous.allowed` + default to **`false`**. The sample topologies set them to `true` only to keep experimentation + frictionless — review them before any non-development deployment. + +## Federated OP parameters + +Each name listed in `federated.op.names` is configured with a block of +`federated.op.<name>.<suffix>` parameters. See [Federation](federation.md) for the full flow and a +complete example. + +| Suffix | Required | Default | Description | +|--------|----------|---------|-------------| +| `enabled` | — | `false` | Activates this OP. Only enabled OPs are offered on the login page. | +| `issuer` | **Yes** | — | Expected `iss` of the OP's id_token. Validated exactly — must match the OP's issuer. | +| `jwks.endpoint` | **Yes** | — | OP JWKS URL used to verify the id_token signature. | +| `clientId` | Yes | — | Knox's client id at the OP; must appear in the id_token `aud`. | +| `clientSecret` | Conditional | — | Knox's client secret at the OP (plaintext). Prefer `clientSecret.alias`. | +| `clientSecret.alias` | Conditional | — | Alias name resolved via `AliasService`. **Takes precedence** over `clientSecret` and **fails closed** if unresolvable. See [Security → Secret handling](security.md#secret-handling). | +| `authorize.endpoint` | Yes | — | OP authorization endpoint Knox redirects the user to. | +| `token.endpoint` | Yes | — | OP token endpoint for the back-channel code exchange. | +| `userinfo.endpoint` | No | — | OP UserInfo endpoint. | +| `discovery.endpoint` | No | — | OP discovery document URL (alternative to listing endpoints individually). | +| `authorize.callback` | Yes | — | The Knox callback URL registered at the OP (`…/knoxidf/api/v1/authorize/callback`). | +| `signature.algorithm` | No | `RS256` | Expected id_token signing algorithm. | + +## User parameters and claims + +KnoxIDF can enrich issued tokens with additional claims — statically configured claims and, for +local users, attributes resolved from a **user-parameter provider**. + +### Hard-coded claim mappings + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `knox.token.hardcoded.claim.mappings` | (none) | `;`-separated list of `key=value` pairs added as claims to every issued token. | + +### LDAP user-parameter provider + +If `user.params.provider.ldap.url` is set, KnoxIDF looks up the authenticated user in LDAP and adds +the resolved attributes to the token (and to the UserInfo response). If it is **absent**, an +`EmptyUserParamsProvider` is used and no LDAP lookup occurs. + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `user.params.provider.ldap.url` | (none) | LDAP(S) URL. Its presence selects the LDAP provider; absence selects the no-op provider. | +| `user.params.provider.ldap.baseDn` | `dc=hadoop,dc=apache,dc=org` | Search base DN. | +| `user.params.provider.ldap.userDnTemplate` | `uid=%s,ou=people,dc=hadoop,dc=apache,dc=org` | DN template; `%s` is replaced with the (escaped) username. | +| `user.params.provider.ldap.systemUser` | `uid=admin,ou=people,dc=hadoop,dc=apache,dc=org` | Bind DN for attribute lookups. | +| `user.params.provider.ldap.systemPasswordAlias` | — | **Required** alias for the system-user password. There is no plaintext fallback — if the alias is absent or unresolvable, initialization fails with an `IllegalStateException` (fail-closed). | + +## Gateway-site properties + +These are set in `$KNOX_HOME/conf/gateway-site.xml` and shared with the rest of Knox. + +### Signing keys + +| Property | Default | Description | +|----------|---------|-------------| +| `gateway.signing.key.alias` | `gateway-identity` | Alias of the primary key used to sign KnoxIDF-issued JWTs. | +| `gateway.signing.key.aliases.additional` | `none` | Comma-separated additional signing-key aliases to **also publish** on the JWKS endpoint. This is the mechanism behind zero-downtime [signing-key rotation](operations.md#signing-key-rotation). `none` means no additional keys. | +| `gateway.signing.keystore.name` | (gateway identity keystore) | Keystore holding the signing key(s). | +| `gateway.signing.keystore.type` | (gateway default) | Keystore type (e.g. `JKS`, `PKCS12`). | +| `gateway.signing.keystore.password.alias` | (gateway default) | Alias of the keystore password. | + +### Persistence and database + +KnoxIDF's [federated-identity store](operations.md#federated-identity-persistence) and the trusted-issuer +registry share Knox's database configuration. With no external database configured, KnoxIDF +self-provisions an **embedded Derby** database (the same physical store used by token state), so no +setup is required to get started. + +| Property | Default | Description | +|----------|---------|-------------| +| `gateway.database.type` | `none` | Database backend: `none` / `derbydb` select the embedded self-provisioning Derby store; a real external type (`postgresql`, `mysql`, `oracle`, …) selects the JDBC-backed store. | +| `gateway.database.connection.url` | (none) | Full JDBC URL (overrides host/port/name if set). | +| `gateway.database.host` | (none) | Database host (when not using a full connection URL). | +| `gateway.database.port` | (none) | Database port. | +| `gateway.database.name` | `GATEWAY_DATABASE` | Database/schema name. | +| `gateway.database.ssl.enabled` | `false` | Enable TLS to the database. | +| `gateway.database.ssl.truststore.path` / `.alias` | (none) | Truststore path / password alias for the database TLS connection. | + +Database credentials are supplied as aliases (`gateway_database_user`, +`gateway_database_password`) — see [Getting Started](getting_started.md#2-install-and-start-knox). + +### Trusted OIDC issuer registry + +| Property | Default | Description | +|----------|---------|-------------| +| `gateway.trustedoidcissuer.discovery.cache.ttl.secs` | `600` | How long a fetched issuer JWKS/discovery document is cached before re-fetch. | +| `gateway.trustedoidcissuer.discovery.connect.timeout.ms` | `3000` | Connect timeout when fetching an issuer's discovery/JWKS document. | +| `gateway.trustedoidcissuer.discovery.read.timeout.ms` | `10000` | Read timeout for the same fetch. | +| `gateway.trusted.oidc.issuer.max.issuers` | `10000` | Upper bound on the number of registered trusted issuers. Registration returns `409 issuer_limit_reached` once reached. | + +### Provider-related properties (sample topologies) + +These are not KnoxIDF parameters but appear in the sample federation topologies: + +| Property | Description | +|----------|-------------| +| `jwt.expected.issuer` | Expected issuer enforced by a `JWTProvider` fronting the token-exchange topology. | +| `sso.unauthenticated.path.list` | On an `SSOCookieProvider` front topology, the `;`-separated list of KnoxIDF paths reachable before login (callback, JWKS, discovery, registration). See [Federation](federation.md#front-topology-for-federation). | + +## See also + +- [Getting Started](getting_started.md) — worked topology examples. +- [Security](security.md) — what the secure-by-default flags protect against. +- [Federation](federation.md) — configuring external OPs. +- [Operations](operations.md) — persistence backends and signing-key rotation. diff --git a/knox-site/docs/knoxidf/endpoints.md b/knox-site/docs/knoxidf/endpoints.md new file mode 100644 index 000000000..c3c5f14fb --- /dev/null +++ b/knox-site/docs/knoxidf/endpoints.md @@ -0,0 +1,299 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +# Endpoint Reference + +KnoxIDF exposes a set of REST endpoints aligned with the standard OpenID Connect expectations, +so clients configured for a Keycloak-like provider work against Knox with minimal changes. + +## Base path and URL structure + +All KnoxIDF endpoints share the base path `knoxidf/api/v1`. As with every Knox service, the +gateway prefixes the topology name, so a fully-qualified URL looks like: + +``` +https://{knox-host}:8443/gateway/{topology}/knoxidf/api/v1/{endpoint} +``` + +The administrative endpoints (service role `KNOXIDF_ADMIN`) live under a separate base path, +`knoxidf/admin/v1`. + +!!! tip "Always read endpoint URLs from discovery" + Do not hard-code endpoint paths in clients. Fetch the + [discovery document](#discovery-endpoint) and use the URLs it advertises. When a + `token.exchange.topology.name` is configured, the `token_endpoint` and `userinfo_endpoint` + are deliberately rewritten to point at the token-exchange topology — discovery reflects + that, hard-coded paths will not. + +## Endpoint summary + +| Endpoint | Path | Methods | Role | +|----------|------|---------|------| +| [Discovery](#discovery-endpoint) | `knoxidf/api/v1/.well-known/openid-configuration` | GET | `KNOXIDF` | +| [Authorization](#authorization-endpoint) | `knoxidf/api/v1/authorize` | GET, POST | `KNOXIDF` | +| [Federated callback](#federated-callback) | `knoxidf/api/v1/authorize/callback` | GET | `KNOXIDF` | +| [Token](#token-endpoint) | `knoxidf/api/v1/token` | POST | `KNOXIDF` | +| [UserInfo](#userinfo-endpoint) | `knoxidf/api/v1/userinfo` | GET | `KNOXIDF` | +| [JWKS](#jwks-endpoint) | `knoxidf/api/v1/jwks` | GET | `KNOXIDF` | +| [Client Registration](#client-registration-endpoint) | `knoxidf/api/v1/client/register` | POST | `KNOXIDF` | +| [Consent page](#consent-page) | `authConsent` | GET, POST | `KNOXIDF` | +| [Trusted OIDC Issuers (admin)](#trusted-oidc-issuers-admin) | `knoxidf/admin/v1/trusted-oidc-issuers` | GET, POST, DELETE | `KNOXIDF_ADMIN` | + +--- + +## Discovery endpoint + +`GET /knoxidf/api/v1/.well-known/openid-configuration` + +Returns the OpenID Connect Discovery document. All endpoint URLs are built dynamically from the +request's base URI, so the document is always correct for the topology it is served from. + +Example document: + +```json +{ + "issuer": "https://knox:8443/gateway/knoxidf-ldap/knoxidf", + "authorization_endpoint": "https://knox:8443/gateway/knoxidf-ldap/knoxidf/api/v1/authorize", + "token_endpoint": "https://knox:8443/gateway/knoxidf-token/knoxidf/api/v1/token", + "userinfo_endpoint": "https://knox:8443/gateway/knoxidf-token/knoxidf/api/v1/userinfo", + "registration_endpoint": "https://knox:8443/gateway/knoxidf-ldap/knoxidf/api/v1/client", + "jwks_uri": "https://knox:8443/gateway/knoxidf-ldap/knoxidf/api/v1/jwks", + "response_types_supported": ["code"], + "subject_types_supported": ["public"], + "token_endpoint_auth_methods_supported": ["client_secret_post", "none"], + "client_id_metadata_document_supported": false, + "grant_types_supported": ["authorization_code", "refresh_token"], + "scopes_supported": ["openid", "profile", "email", "offline_access"], + "id_token_signing_alg_values_supported": ["RS256"], + "code_challenge_methods_supported": ["S256"] +} +``` + +Notable metadata: + +- **`subject_types_supported: ["public"]`** — Knox derives a shared (non-pairwise) `sub`, the + same value for every client. +- **`token_endpoint_auth_methods_supported: ["client_secret_post", "none"]`** — the token + endpoint reads client credentials only from the request body (`client_secret_post`); + public clients use PKCE with no secret (`none`). HTTP Basic (`client_secret_basic`) is + intentionally **not** advertised because it is not honored. +- **`code_challenge_methods_supported: ["S256"]`** — only S256 PKCE is accepted; `plain` is + rejected. +- **`client_id_metadata_document_supported: false`** — Knox does not resolve a URL-style + `client_id` as a Client ID Metadata Document (OAuth CIMD draft, referenced by the MCP + authorization spec); clients must use dynamic registration instead. + +--- + +## Authorization endpoint + +`GET|POST /knoxidf/api/v1/authorize` + +Begins the Authorization Code flow. Validates the request, checks (or collects) user consent, +and redirects back to the client `redirect_uri` with an authorization `code` and the echoed +`state`. If consent has not yet been granted for this (user, client, scopes), the browser is +first redirected to the [consent page](#consent-page). + +Request parameters: + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `response_type` | Yes | `code`, `id_token`, or `code id_token`. | +| `client_id` | Yes | A registered client identifier. | +| `redirect_uri` | Yes | Must match the client's registered `redirect_uris`. | +| `scope` | No | Space-delimited scopes. Defaults to `openid profile email offline_access`. | +| `state` | No | Opaque CSRF value, echoed back in the redirect. | +| `nonce` | No | Bound to the issued ID token. | +| `code_challenge` | No (PKCE) | Base64url S256 hash of the `code_verifier`. | +| `code_challenge_method` | Required if `code_challenge` present | Must be `S256`; `plain` is rejected. | + +**Success:** `302` redirect to `{redirect_uri}?code={auth_code}&state={state}`. + +**Errors:** a JSON body `{"error": "...", "error_description": "..."}` — `invalid_request` +(unknown `client_id`, bad `redirect_uri`, missing parameters, unsupported PKCE method) or +`invalid_scope` (a requested scope is not in the client's allowed scopes). When consent is +required, a `303` redirect to the consent page. + +### Federated callback + +`GET /knoxidf/api/v1/authorize/callback` + +Back-channel callback invoked by an external OIDC Provider during [federation](federation.md). +Exchanges the OP authorization `code` for the OP's tokens, **validates the OP `id_token`** +(signature via JWKS, issuer, audience, nonce, and `sub` presence), resolves or persists the +federated identity, then issues a Knox authorization code and redirects to the original client +`redirect_uri`. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `code` | Yes | Authorization code from the federated OP. | +| `state` | Yes | Must match a live entry in the authorize-request store. | + +This endpoint must be reachable without prior Knox authentication (wired as `anon`, or listed +in `sso.unauthenticated.path.list`). + +--- + +## Token endpoint + +`POST /knoxidf/api/v1/token` `Content-Type: application/x-www-form-urlencoded` + +Issues tokens. Supports the **Client Credentials**, **Authorization Code**, and **Refresh +Token** grants. Client authentication is enforced here — see +[Security](security.md#token-endpoint-client-authentication). + +### Authorization Code grant + +Redeems a one-time authorization code. The code is atomically consumed (single-use); a public +client proves possession via PKCE `code_verifier`, a confidential client via `client_secret`. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `grant_type` | Yes | `authorization_code`. | +| `code` | Yes | The authorization code from `/authorize`. | +| `redirect_uri` | Yes | Must match the URI stored with the code. | +| `client_id` | Yes | Must match the client that obtained the code. | +| `client_secret` | Conditional | Required when no `code_challenge` was stored. | +| `code_verifier` | Conditional | Required when a `code_challenge` was stored at authorize time. | + +### Refresh Token grant + +Rotates a refresh token: atomically consumes the presented token and issues a new +access-token / refresh-token pair. Requires `client_secret`. + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `grant_type` | Yes | `refresh_token`. | +| `refresh_token` | Yes | A previously issued refresh token. | +| `client_id` | Yes | Must match the client bound to the refresh token. | +| `client_secret` | Yes | Client secret. | + +**Success (`200`):** + +```json +{ + "access_token": "<JWT>", + "token_id": "<UUID>", + "token_type": "Bearer", + "expires_in": 1699999999999, + "managed_token": "true", + "id_token": "<JWT>", + "refresh_token": "<JWT>", + "passcode": "<Base64(tokenId)::Base64(passcode)>" +} +``` + +`refresh_token` is present only when the scope includes `offline_access`. The `id_token` +carries `sub`, `iss`, `aud` (= `client_id`), `exp`, `iat`, and `nonce` (when supplied); for +federated users it additionally carries `federated_idp`, `federated_sub`, and `federated_iss` +plus any allowed profile claims (`preferred_username`, `email`, `email_verified`, +`given_name`, `family_name`, `name`, `locale`). + +**Errors:** `invalid_grant` (missing/expired/replayed code, `redirect_uri` or `client_id` +mismatch, PKCE failure, bad `client_secret`, disabled/expired refresh token) or +`invalid_request` (unsupported `grant_type`). + +--- + +## UserInfo endpoint + +`GET /knoxidf/api/v1/userinfo` + +Returns OIDC UserInfo claims for a valid bearer access token. The upstream `JWTProvider` +validates the token and hands the token identity to this resource; the endpoint never reads the +raw `Authorization` header itself. For a federated user it returns the internal Knox `sub`, the +`idp` name, `federated_sub`, `federated_iss`, and any allowed profile claims; for a local user +it returns whatever the configured [user-parameter provider](configuration.md#user-parameters-and-claims) +resolves. + +**Errors:** `invalid_request` when no token identity is present; `401` with +`WWW-Authenticate: Bearer error="invalid_token"` for an expired, revoked, or unknown token. + +--- + +## JWKS endpoint + +`GET /knoxidf/api/v1/jwks` + +Publishes the gateway's public signing key(s) as a JWK Set so clients and resource servers can +verify KnoxIDF-issued JWTs. One JWK is published per configured signing-key alias, each keyed by +the SHA-256 thumbprint of its public key as the `kid`. This is what makes signing-key rotation +transparent to verifiers — see [Operations → Signing-key rotation](operations.md#signing-key-rotation). + +```json +{ + "keys": [ + { "kty": "RSA", "use": "sig", "alg": "RS256", "kid": "<sha-256 thumbprint>", "n": "...", "e": "AQAB" } + ] +} +``` + +--- + +## Client Registration endpoint + +`POST /knoxidf/api/v1/client/register` `Content-Type: application/x-www-form-urlencoded` + +Dynamically registers an OAuth2 client and returns a `client_id` and `client_secret`. Redirect +URIs must use HTTPS (plain HTTP is allowed only for loopback hosts, per RFC 8252). Anonymous +registration is **refused by default** and only permitted when +`knoxidf.client.registration.anonymous.allowed=true` is set on the topology — see +[Security](security.md#dynamic-client-registration). + +| Parameter | Required | Description | +|-----------|----------|-------------| +| `redirect_uris` | Yes | Comma-separated. HTTPS required (loopback HTTP allowed); a wildcard `*` is only permitted at the end of the path, never in the host. | +| `allowed_scopes` | No | Comma-separated; must include `openid`. Defaults to `openid,profile,email,offline_access`. | + +**Success (`200`):** returns `token_id` (the `client_id`), `passcode` (the `client_secret` to +use on `/token`), and the stored `redirect_uris` and `allowed_scopes`. + +**Errors:** `access_denied` (anonymous caller when disabled), `invalid_request` (missing/invalid +`redirect_uris`, wrong scheme), `invalid_scope` (`allowed_scopes` omits `openid`). + +--- + +## Consent page + +`GET|POST /{topology}/authConsent` + +An HTML consent page (a lightweight servlet, registered only when the topology includes the +`KNOXIDF` service). `GET` renders the requesting `client_id` and a human-readable description of +each requested scope with **Accept** / **Deny** buttons. `POST` records the decision and +redirects to `authorize/consentAccepted` (issues the code) or `authorize/consentDenied` +(`403`). Consent is one-time per (user, client, scopes) — see +[Security → Consent](security.md#consent). + + + +--- + +## Trusted OIDC Issuers (admin) + +Base path `knoxidf/admin/v1/trusted-oidc-issuers`, served by the `KNOXIDF_ADMIN` service role +(a separate, administrator-only topology). Manages the set of external issuers whose `id_token`s +Knox will accept during federated login. + +| Method | Path | Purpose | Success | +|--------|------|---------|---------| +| `POST` | `/trusted-oidc-issuers` | Register a trusted issuer (JSON body: `issuerUrl` (HTTPS, required), `dynamicJwks`, `clusterName`). | `201` | +| `GET` | `/trusted-oidc-issuers` | List registered issuers (with `registeredAt` / `registeredBy`). | `200` | +| `DELETE` | `/trusted-oidc-issuers?issuerUrl=...` | Deregister an issuer (idempotent). | `204` | +| `POST` | `/trusted-oidc-issuers/refresh-jwks?issuerUrl=...` | Force a JWKS cache refresh for a `dynamicJwks` issuer. | `204` | + +**Errors:** `400 invalid_request` (missing/non-HTTPS `issuerUrl`, malformed JSON), +`409 issuer_exists`, `409 issuer_limit_reached` (issuer cap), `500 storage_error`. diff --git a/knox-site/docs/knoxidf/federation.md b/knox-site/docs/knoxidf/federation.md new file mode 100644 index 000000000..ad17d74c6 --- /dev/null +++ b/knox-site/docs/knoxidf/federation.md @@ -0,0 +1,221 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +# Federation + +In addition to being a standalone OIDC Provider, KnoxIDF can **broker** login to one or more +external OpenID Providers (OPs) — Keycloak, Okta, Azure AD, Auth0, and so on. In this mode Knox +delegates the actual authentication to the external OP, validates the identity it returns, and +then re-issues **its own Knox-signed tokens** to the client. Downstream services still only need +to trust Knox, regardless of where the user actually authenticated. + +Federation is entirely optional and configured per topology. A topology with no +`federated.op.names` behaves as a pure Knox OP. + +## The login experience + +When a topology fronts `/authorize` with an SSO cookie provider and has one or more federated +OPs enabled, the Knox login page offers the external OP as an alternative to Knox's own +authentication providers (LDAP, PAM, SAML, Kerberos, …): + + + +The end user chooses whether to sign in with a Knox-native provider or to identify themselves +through the external OIDC Provider. + +## Broker flow + +Federation is implemented as a token-brokering mechanism: + +```mermaid +sequenceDiagram + autonumber + participant Client + participant Knox as Knox (KnoxIDF) + participant OP as External OP + Client->>Knox: GET /authorize (response_type=code, PKCE) + Note over Knox: Authenticate request, validate params, check consent + Knox->>OP: Redirect to OP /authorize (nonce, callback) + OP-->>Client: Prompt for login + Client->>OP: Authenticate + OP->>Knox: GET /authorize/callback (code, state) + Note over Knox: Exchange code at OP /token (back-channel) + Knox->>OP: POST /token (federated code, client_secret) + OP-->>Knox: OP tokens (id_token) + Note over Knox: Validate id_token (sig via JWKS, iss, aud, nonce, sub) + Note over Knox: Resolve / persist federated identity + Knox-->>Client: Redirect to redirect_uri (Knox code, state) + Client->>Knox: POST /token (code + client_secret / code_verifier) + Knox-->>Client: Knox access_token + id_token (+ refresh_token) +``` + +Step by step: + +1. **Client initiates.** The OIDC client calls `/authorize`. The topology's provider + authenticates the request and Knox validates the parameters and checks consent. +2. **Delegate to the OP.** With a federated OP enabled, Knox builds an authorization redirect to + the OP's `authorize.endpoint`, including a freshly generated `nonce` (stored server-side for + this session) and the callback URL `/knoxidf/api/v1/authorize/callback`. +3. **The OP authenticates the user** and redirects back to Knox's callback with an authorization + `code` and the `state`. +4. **Back-channel token exchange.** Knox exchanges the OP code at the OP's `token.endpoint`, + resolving the OP `client_secret` (from an [alias](security.md#secret-handling) if configured). +5. **Validate the OP `id_token`.** Knox verifies the signature (via the OP's JWKS), the issuer, + the audience, the `nonce`, and the presence of `sub` — see + [Security → Federated id_token validation](security.md#federated-id_token-validation). Nothing + in the token is trusted before this passes. +6. **Resolve or persist the federated identity.** Knox looks up `(provider, issuer, subject)`; + if not found, it persists a new federated identity, deriving a stable Knox `sub` as a + [UUIDv5](security.md#subject-derivation) over the OP issuer and subject. +7. **Issue a Knox authorization code**, then redirect the client to its `redirect_uri`. +8. **Client redeems the code** at Knox's `/token` endpoint (with PKCE or `client_secret`) and + receives Knox-signed tokens whose `id_token` carries the federated claims below. + +## Federated claims in the Knox id_token + +For a federated user, the Knox-issued `id_token` (and the UserInfo response) carry the origin of +the identity alongside the Knox subject: + +| Claim | Meaning | Example | +|-------|---------|---------| +| `sub` | Stable Knox subject (UUIDv5 over issuer + external subject). | `f47ac10b-58cc-45c8-...` | +| `federated_idp` | The federated provider name, **upper-cased**. | `KEYCLOAK` | +| `federated_sub` | The `sub` from the OP's id_token. | `248289761001` | +| `federated_iss` | The `iss` from the OP's id_token. | `https://op.example/realms/knox` | + +Allowed profile claims (`preferred_username`, `email`, `email_verified`, `given_name`, +`family_name`, `name`, `locale`) are included when present. The UserInfo response uses `idp` for +the provider name (also upper-cased) in place of `federated_idp`. + +!!! note + Because `federated_idp` and the stored provider are upper-cased, a configured OP name of + `keycloak` appears in tokens as `KEYCLOAK`. Match on the upper-cased value in downstream + authorization logic. + +## Configuring a federated OP + +Federated OPs are declared as `KNOXIDF` service parameters so they are exposed as servlet +context init-params (read by both `AuthorizeResource` and the SSO cookie federation filter in +the same webapp). First list the OP logical names, then provide a block of +`federated.op.<name>.*` parameters for each. The example below is the tested CI topology for a +Keycloak OP: + +```xml +<service> + <role>KNOXIDF</role> + <!-- ... core KnoxIDF params (ttl, consent, token-exchange topology) ... --> + + <param> + <name>federated.op.names</name> + <value>keycloak</value> + </param> + <param> + <name>federated.op.keycloak.enabled</name> + <value>true</value> + </param> + <param> + <name>federated.op.keycloak.clientId</name> + <value>knox-client</value> + </param> + <param> + <name>federated.op.keycloak.clientSecret</name> + <value>knox-client-secret</value> + </param> + <param> + <name>federated.op.keycloak.authorize.endpoint</name> + <value>http://keycloak:8080/realms/knox/protocol/openid-connect/auth</value> + </param> + <param> + <name>federated.op.keycloak.authorize.callback</name> + <value>https://knox:8443/gateway/knoxidf-sso/knoxidf/api/v1/authorize/callback</value> + </param> + <param> + <name>federated.op.keycloak.token.endpoint</name> + <value>http://keycloak:8080/realms/knox/protocol/openid-connect/token</value> + </param> + <param> + <name>federated.op.keycloak.jwks.endpoint</name> + <value>http://keycloak:8080/realms/knox/protocol/openid-connect/certs</value> + </param> + <param> + <name>federated.op.keycloak.issuer</name> + <value>http://keycloak:8080/realms/knox</value> + </param> + <param> + <name>federated.op.keycloak.userinfo.endpoint</name> + <value>http://keycloak:8080/realms/knox/protocol/openid-connect/userinfo</value> + </param> + <param> + <name>federated.op.keycloak.signature.algorithm</name> + <value>RS256</value> + </param> +</service> +``` + +!!! warning "Prefer an alias for the OP client secret" + The example above uses a plaintext `clientSecret` for brevity. In production, store the + secret in Knox's credential store and reference it with + `federated.op.keycloak.clientSecret.alias` instead — the alias takes precedence and + resolution fails closed if it cannot be resolved. See + [Security → Secret handling](security.md#secret-handling). See the + [Configuration Reference](configuration.md#federated-op-parameters) for every + `federated.op.<name>.*` parameter. + +### Front topology for federation + +The federation login experience requires a topology that fronts `/authorize` with an +`SSOCookieProvider` (so an unauthenticated `/authorize` is redirected to the Knox login +front-end), with the federation callback and other pre-login endpoints listed in +`sso.unauthenticated.path.list`: + +```xml +<provider> + <role>federation</role> + <name>SSOCookieProvider</name> + <enabled>true</enabled> + <param> + <name>sso.authentication.provider.url</name> + <value>https://knox:8443/gateway/knoxsso/api/v1/websso</value> + </param> + <param> + <name>sso.unauthenticated.path.list</name> + <value>/knoxidf/api/v1/authorize/callback;/knoxidf/api/v1/jwks;/knoxidf/api/v1/.well-known/openid-configuration;/knoxidf/api/v1/client/register</value> + </param> +</provider> +``` + +## Multiple OPs + +`federated.op.names` accepts a comma-separated list, and each named OP gets its own +`federated.op.<name>.*` block. Only OPs with `enabled=true` are activated; the login page offers +each enabled OP as a separate sign-in option. + +## Trusted issuer registry + +The external issuers whose tokens Knox will accept are administered through the +[Trusted OIDC Issuers admin API](endpoints.md#trusted-oidc-issuers-admin), served by the +`KNOXIDF_ADMIN` role on an administrator-restricted topology. Issuer JWKS documents are cached +(`gateway.trustedoidcissuer.discovery.cache.ttl.secs`, default 600s); the admin API's +`refresh-jwks` action forces an immediate re-fetch for issuers configured with dynamic JWKS. + +## Persistence + +Federated identities are persisted so the same upstream user maps to a stable Knox subject and +so their attributes can be reused. This store activates automatically when a `KNOXIDF` (or +`KNOXIDF_ADMIN`) topology is present — no explicit configuration is required. See +[Operations → Federated identity persistence](operations.md#federated-identity-persistence) for +the backend-selection rules and how to point KnoxIDF at an external database. diff --git a/knox-site/docs/knoxidf/getting_started.md b/knox-site/docs/knoxidf/getting_started.md new file mode 100644 index 000000000..7b17ff19c --- /dev/null +++ b/knox-site/docs/knoxidf/getting_started.md @@ -0,0 +1,284 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +# Getting Started + +This chapter walks through building Knox with KnoxIDF, deploying the topologies that expose +the OIDC endpoints, registering a client, and running a first Client Credentials flow. + +!!! note "Branch" + At the time of writing, KnoxIDF lives on the `knox_idf` development branch (kept in sync + with `master`). Build from that branch until it is merged. + +## 1. Build Knox + +```bash +git clone https://github.com/apache/knox.git +cd knox +git checkout knox_idf + +mvn -DskipTests -Dcheckstyle.skip=true -Dfindbugs.skip=true -Dpmd.skip=true \ + -Drat.skip -Dspotbugs.skip=true -Dforbiddenapis.skip=true \ + -Ppackage clean install +``` + +The build produces a Knox distribution archive under +`gateway-release/target/{version}/knox-{version}.zip`. + +## 2. Install and start Knox + +Unzip the distribution into a deployment directory (`$KNOX_HOME`), create the master secret +and the required aliases, then start the gateway. The signing-key hash alias +(`knox.token.hash.key`) backs server-managed (passcode) tokens; the database aliases back the +embedded/external persistence used by KnoxIDF and token state. + +```bash +export KNOX_HOME=/path/to/knoxGateway + +# Master secret (non-interactive) +$KNOX_HOME/bin/knoxcli.sh create-master --master gateway + +# Signing / passcode HMAC key +$KNOX_HOME/bin/knoxcli.sh create-alias knox.token.hash.key --value <a-strong-random-secret> + +# Database credential aliases (used by the embedded Derby store and any external DB) +$KNOX_HOME/bin/knoxcli.sh create-alias gateway_database_user --value knox +$KNOX_HOME/bin/knoxcli.sh create-alias gateway_database_password --value knox + +$KNOX_HOME/bin/gateway.sh start +``` + +!!! tip "Local (non-TLS) testing" + For local experimentation you can disable TLS by setting `ssl.enabled=false` in + `$KNOX_HOME/conf/gateway-site.xml`. **Do not do this in production** — OAuth 2.0 / OIDC + requires TLS for all token-bearing traffic. + +By default, KnoxIDF's federated-identity persistence uses an **embedded Derby** database that +Knox provisions automatically (the same physical DB used by token state). No external database +is required to get started. To point KnoxIDF at an external database (PostgreSQL, etc.), see +the [Configuration Reference](configuration.md) and [Operations](operations.md) chapters. + +## 3. Deploy the KnoxIDF topologies + +A typical KnoxIDF deployment uses **two topologies**: + +- **A "front" topology** that exposes the OIDC endpoints and authenticates the end user (for + example with LDAP Basic auth, or with an SSO cookie provider for federation). This is where + `/authorize`, `/client/register`, `/jwks`, and discovery live. +- **A "token" topology** fronted by Knox's `JWTProvider`, referenced by the front topology's + `token.exchange.topology.name`. The `/token` exchange is redirected here so that redeeming an + authorization code is authenticated by a Knox-issued JWT. + +Copy the topology files into `$KNOX_HOME/conf/topologies/`; Knox hot-deploys them within a few +seconds. + +### Front topology (LDAP Basic auth) — `knoxidf-ldap.xml` + +The `ShiroProvider` authenticates users against LDAP, and the OIDC endpoints that must be +reachable *before* login (discovery, registration, JWKS, and the federation callback) are +wired as `anon`: + +```xml +<topology> + <gateway> + <provider> + <role>authentication</role> + <name>ShiroProvider</name> + <enabled>true</enabled> + <param> + <name>main.ldapRealm</name> + <value>org.apache.knox.gateway.shirorealm.KnoxLdapRealm</value> + </param> + <param> + <name>main.ldapRealm.userDnTemplate</name> + <value>uid={0},ou=people,dc=hadoop,dc=apache,dc=org</value> + </param> + <param> + <name>main.ldapRealm.contextFactory.url</name> + <value>ldaps://localhost:33390</value> + </param> + <param> + <name>main.ldapRealm.contextFactory.authenticationMechanism</name> + <value>simple</value> + </param> + <param> + <name>urls./knoxidf/api/v1/.well-known/openid-configuration</name> + <value>anon</value> + </param> + <param> + <name>urls./knoxidf/api/v1/client/register</name> + <value>anon</value> + </param> + <param> + <name>urls./knoxidf/api/v1/authorize/callback</name> + <value>anon</value> + </param> + <param> + <name>urls./knoxidf/api/v1/jwks</name> + <value>anon</value> + </param> + <param> + <name>urls./**</name> + <value>authcBasic</value> + </param> + </provider> + <provider> + <role>identity-assertion</role> + <name>Default</name> + <enabled>true</enabled> + </provider> + </gateway> + + <service> + <role>KNOXIDF</role> + <param> + <name>knoxidf.knox.token.ttl</name> + <value>60000</value> + </param> + <param> + <name>knoxidf.knox.token.limit.per.user</name> + <value>-1</value> + </param> + <param> + <!-- Registration refuses anonymous callers unless this is explicitly true. --> + <name>knoxidf.client.registration.anonymous.allowed</name> + <value>true</value> + </param> + <param> + <!-- Skipping consent is a server-side deployment decision, never a client param. --> + <name>knoxidf.auto.consent.enabled</name> + <value>true</value> + </param> + <param> + <name>token.exchange.topology.name</name> + <value>knoxidf-token</value> + </param> + </service> +</topology> +``` + +!!! warning "Anonymous client registration is opt-in" + `knoxidf.client.registration.anonymous.allowed` defaults to **`false`** (secure by + default). The sample above sets it to `true` only to keep the endpoint open for + experimentation. See [Security](security.md#dynamic-client-registration). + +### Token topology (`JWTProvider`) — `knoxidf-token.xml` + +```xml +<topology> + <gateway> + <provider> + <role>federation</role> + <name>JWTProvider</name> + <enabled>true</enabled> + <param> + <name>knox.token.exp.server-managed</name> + <value>true</value> + </param> + </provider> + <provider> + <role>identity-assertion</role> + <name>Default</name> + <enabled>true</enabled> + </provider> + </gateway> + + <service> + <role>KNOXIDF</role> + <param> + <name>knoxidf.knox.token.ttl</name> + <value>86400000</value> + </param> + <param> + <name>knoxidf.knox.token.limit.per.user</name> + <value>-1</value> + </param> + <param> + <name>knoxidf.auto.consent.enabled</name> + <value>true</value> + </param> + </service> + <service> + <role>KNOXTOKEN</role> + <param> + <name>knox.token.ttl</name> + <value>60000</value> + </param> + <param> + <name>knox.token.limit.per.user</name> + <value>-1</value> + </param> + </service> +</topology> +``` + +## 4. Discover the endpoints + +Every subsequent step should read endpoint URLs from the discovery document rather than +hard-coding paths. Fetch it from the front topology: + +```bash +curl -sk https://knox:8443/gateway/knoxidf-ldap/knoxidf/api/v1/.well-known/openid-configuration | jq . +``` + +The response includes `issuer`, `authorization_endpoint`, `token_endpoint`, +`userinfo_endpoint`, `jwks_uri`, `registration_endpoint`, and the supported grant types, +scopes, response types, and PKCE methods. See the [Endpoint Reference](endpoints.md) for the +full document. + +## 5. Register a client + +```bash +curl -sk -X POST \ + https://knox:8443/gateway/knoxidf-ldap/knoxidf/api/v1/client/register \ + -H 'Content-Type: application/json' \ + -d '{ + "client_name": "my-first-client", + "redirect_uris": ["https://app.example.com/callback"], + "grant_types": ["authorization_code", "refresh_token"] + }' | jq . +``` + +The response contains a generated `client_id` and, for confidential clients, a +`client_secret`. Store the secret securely — it is required to redeem authorization codes on +the token endpoint (see [Security](security.md#token-endpoint-client-authentication)). + +## 6. Run a Client Credentials flow + +The Client Credentials grant issues a token to a confidential client with no interactive user +login. Post the client credentials to the token endpoint advertised by discovery: + +```bash +curl -sk -X POST \ + https://knox:8443/gateway/knoxidf-ldap/knoxidf/api/v1/token \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -d 'grant_type=client_credentials' \ + -d 'client_id=<client_id>' \ + -d 'client_secret=<client_secret>' \ + -d 'scope=openid' | jq . +``` + +You will receive a Knox-signed access token (and, when `openid` is requested, an ID token). +Verify it against the JWKS endpoint (`jwks_uri`). + +## Next steps + +- To drive an interactive login, use the **Authorization Code + PKCE** flow — see the + [Endpoint Reference](endpoints.md#authorization-endpoint) and [Security](security.md#pkce). +- To broker login to an external OIDC Provider (Keycloak, Okta, Azure AD, Auth0), see + **[Federation](federation.md)**. +- To tune tokens, persistence, and claims, see the **[Configuration Reference](configuration.md)**. diff --git a/knox-site/docs/knoxidf/index.md b/knox-site/docs/knoxidf/index.md new file mode 100644 index 000000000..3eac8e1b5 --- /dev/null +++ b/knox-site/docs/knoxidf/index.md @@ -0,0 +1,92 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +# Identity Federation (OIDC Provider) + +## Overview + +Historically, Apache Knox Gateway has acted as a *federation client* — it delegates +authentication to external Identity Providers (IdPs) such as CAS, SAML, OAuth 2.0, and +OpenID Connect (OIDC) providers (via pac4j). In that model Knox is strictly a relying +party and never an identity provider itself. + +**KnoxIDF** turns Apache Knox into an **OAuth 2.0 / OpenID Connect Provider (OP)** in its +own right, while retaining Knox's existing federation capabilities. With KnoxIDF: + +- Knox can **issue** OAuth 2.0 / OIDC tokens (access tokens and ID tokens) directly to clients. +- Knox can optionally **federate** identities and tokens from external, well-known OIDC + Providers (e.g. Keycloak, Okta, Azure AD, Auth0), brokering the login and then re-issuing + its own Knox-signed tokens. +- Downstream services integrate with Knox exactly as they would with any standard OIDC + provider — they only need to trust Knox, regardless of how authentication was performed + upstream. + +This makes Knox both an **OIDC Provider** and an **OIDC federation bridge**, enabling gradual +migration to — or a hybrid of — Knox-centric and external identity architectures. + + + +## Why KnoxIDF + +Many modern architectures expect a centralized OIDC Provider that issues tokens to +downstream services. Products like Okta, Azure AD, and Keycloak are commonly used for this, +but introducing and operating a separate IdP is not always desirable — especially in +Hadoop-centric or Knox-centric deployments where Knox is already the trusted edge. KnoxIDF +closes that gap: the gateway you already run at the perimeter becomes the token authority for +the services behind it. + +## Capabilities + +KnoxIDF is implemented as a new Knox service (role `KNOXIDF`) that can be attached to any +topology. It provides: + +| Capability | Description | +|------------|-------------| +| Standard OIDC endpoints | Discovery (`.well-known/openid-configuration`), authorization, token, userinfo, JWKS, and dynamic client registration. | +| Client Credentials flow | Machine-to-machine token issuance. | +| Authorization Code flow + PKCE | Interactive user login with PKCE (S256) for public clients and `client_secret` for confidential clients. | +| Refresh tokens | Refresh-token grant with rotation. | +| Consent | A one-time-per-(user, client) consent screen for the Authorization Code flow. | +| Federation (optional) | Broker login to one or more external OIDC Providers and re-issue Knox tokens. | +| Attribute enrichment | Hard-coded ID-token claims and pluggable user-parameter providers (e.g. LDAP attributes). | +| Persistence | Federated identity data persisted for traceability and attribute reuse (ID-token data only — no access/refresh tokens or secrets). | + +## How it fits into Knox + +KnoxIDF operates independently from pac4j-based inbound authentication and does not change +existing gateway authentication flows. A topology that includes the `KNOXIDF` service exposes +the OIDC endpoints; the topology's own authentication/federation providers (Shiro/LDAP, +SSOCookie, JWT, etc.) still govern how the caller is authenticated before KnoxIDF issues a +token. This keeps KnoxIDF modular and composable with the rest of Knox. + +## Where to go next + +- **[Getting Started](getting_started.md)** — build, deploy, register a client, and run your first flow. +- **[Endpoint Reference](endpoints.md)** — every REST endpoint KnoxIDF exposes. +- **[Configuration Reference](configuration.md)** — every configuration parameter. +- **[Security](security.md)** — client authentication, PKCE, consent, redirect-URI validation, and secret handling. +- **[Federation](federation.md)** — brokering login to external OIDC Providers. +- **[Operations](operations.md)** — high availability, rate limiting, signing-key rotation, and auditing. + +!!! note "Relationship to KIP-18" + KnoxIDF was originally proposed and prototyped in + [KIP-18 — Knox as OIDC Provider](https://cwiki.apache.org/confluence/spaces/KNOX/pages/406618787/KIP-18+-+Knox+as+OIDC+Provider). + KIP-18 describes the original design and proof-of-concept. The implementation has since + evolved (for example, refresh-token support, hardened client authentication, and + automatic federated-identity persistence were added after the initial proposal). Where the + KIP and this documentation differ, **this documentation reflects the current code and is + authoritative**; KIP-18 remains useful background on the motivation and design. diff --git a/knox-site/docs/knoxidf/operations.md b/knox-site/docs/knoxidf/operations.md new file mode 100644 index 000000000..ddd2ab210 --- /dev/null +++ b/knox-site/docs/knoxidf/operations.md @@ -0,0 +1,182 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +# Operations + +This chapter covers running KnoxIDF in production: where identity state is persisted, how to +rotate signing keys without disrupting clients, how requests are audited, and how KnoxIDF behaves +behind a highly available Knox deployment. + +## Federated identity persistence + +KnoxIDF persists federated-identity data so that the same upstream user maps to a stable Knox +subject across logins and restarts, and so a filtered set of profile attributes can be reused. It +stores **only ID-token–derived data** — no access tokens, refresh tokens, or OP client secrets +(see [Security → What is (and isn't) stored at rest](security.md#what-is-and-isnt-stored-at-rest)). + +### Backend selection + +The persistence backend activates automatically whenever a topology with the `KNOXIDF` or +`KNOXIDF_ADMIN` role is deployed. Which backend is used follows the gateway's database +configuration: + +| `gateway.database.type` | Backend | Notes | +|-------------------------|---------|-------| +| `none` (default) or `derbydb` | Self-provisioning **embedded Derby** | Uses the same physical embedded database as token state (under the gateway security directory). Zero setup. | +| A real external type (`postgresql`, `mysql`, `oracle`, …) | **JDBC-backed** store | Uses the operator-configured external database. Recommended for HA. | + +You can also pin the implementation explicitly with the service property +`gateway.service.KnoxIDFFederatedIdentityService.impl` (Empty / Derby / JDBC); an explicit value +always wins over auto-selection. Setting it to the empty (no-op) implementation disables +persistence. + +!!! note "Use an external database for multi-instance deployments" + The embedded Derby store is local to a single gateway process. For a clustered / HA + deployment where more than one Knox instance must share federated-identity state, configure an + external database (see [Configuration → Persistence](configuration.md#persistence-and-database)). + +### What is stored + +- **Identity mapping:** Knox subject (UUIDv5), provider name (upper-cased), external subject, + external issuer. +- **Attributes:** the allow-listed profile claims (`preferred_username`, `email`, `email_verified`, + `given_name`, `family_name`, `name`, `locale`). + +The identity row and its attributes are written in a **single transaction**, and a unique +constraint on `(provider, external_issuer, external_subject)` makes concurrent first-logins of the +same user converge on one row. + +## Signing-key rotation + +KnoxIDF signs issued JWTs with the gateway signing key (`gateway.signing.key.alias`, default +`gateway-identity`) and publishes the corresponding public key(s) on the +[JWKS endpoint](endpoints.md#jwks-endpoint). Each published key is identified by a `kid` equal to +the **SHA-256 thumbprint** of its public key, so verifiers select the right key by `kid` rather +than assuming a single static key. + +To rotate the signing key **without breaking tokens already in the wild**: + +1. **Provision the new key** in the signing keystore under a new alias. +2. **Publish both keys.** Add the *old* alias to `gateway.signing.key.aliases.additional` in + `gateway-site.xml` so the JWKS endpoint serves both the old and new public keys: + + ```xml + <property> + <name>gateway.signing.key.aliases.additional</name> + <value>gateway-identity-previous</value> + </property> + ``` + +3. **Cut over signing** by pointing `gateway.signing.key.alias` at the new alias. New tokens are + now signed with the new key; verifiers still find the old key (by its `kid`) on JWKS for + tokens signed before the cutover. +4. **Retire the old key** once all tokens signed with it have expired: remove it from + `gateway.signing.key.aliases.additional`. + +Because clients resolve keys from JWKS by `kid`, no client reconfiguration is needed at any step. + +!!! tip "Order matters" + Publish the new key on JWKS *before* you start signing with it, and keep the old key published + *until* the last token it signed has expired. Overlapping the two windows is what makes the + rotation seamless. + +## Auditing + +KnoxIDF actions are recorded through Knox's standard audit framework, so KnoxIDF audit records +appear in the same audit log as the rest of the gateway (`$KNOX_HOME/logs/gateway-audit.log` by +default) and follow the gateway's configured audit layout. Security-relevant operations — token +issuance, client registration, consent decisions, federated login, and trusted-issuer +administration — are audited with the acting principal and outcome. + +Audit output is configured through the gateway's Log4j2 configuration +(`$KNOX_HOME/conf/gateway-log4j2.xml`), the same as every other Knox audit stream; see +[Audit](../config_audit.md) for audit-appender and retention configuration. + +## High availability + +KnoxIDF adds no HA mechanism of its own — it inherits Knox's standard HA model. Run multiple Knox +instances behind a load balancer as you would for any other Knox service, with two +KnoxIDF-specific requirements: + +- **Shared persistence.** All instances must point at the **same external database** + (`gateway.database.*`) so a federated identity created on one instance is visible on the others. + The embedded Derby default is per-process and is not suitable for multi-instance HA. +- **Consistent signing keys.** All instances must share the same signing keystore and the same + `gateway.signing.key.alias` / `gateway.signing.key.aliases.additional` configuration, so a token + issued by one instance verifies against the JWKS served by any instance. + +Sticky sessions are recommended for the interactive Authorization Code / federation flow so that +the browser stays on the instance holding the in-flight authorize/consent state, though the issued +tokens themselves are verifiable on any instance. + +## Rate limiting + +KnoxIDF does not implement rate limiting of its own, but it does not need to — Knox's +**`WebAppSec` provider** ships a rate-limiting filter you can attach to a KnoxIDF topology to +throttle request flooding, whether malicious or from a misconfigured client. This protects the +high-value token, authorization, and registration endpoints without any external infrastructure. + +Add the provider to the KnoxIDF topology and enable rate limiting: + +```xml +<provider> + <role>webappsec</role> + <name>WebAppSec</name> + <enabled>true</enabled> + <param> + <name>rate.limiting.enabled</name> + <value>true</value> + </param> + <param> + <name>rate.limiting.maxRequestsPerSec</name> + <value>25</value> + </param> + <param> + <!-- -1 = reject over-limit requests; a non-negative value delays instead + and requires gateway.servlet.async.supported=true in gateway-site.xml --> + <name>rate.limiting.delayMs</name> + <value>-1</value> + </param> +</provider> +``` + +The filter tracks request rate per connection (or per session when +`rate.limiting.trackSessions=true`), delays or rejects requests over +`rate.limiting.maxRequestsPerSec`, and can exempt trusted callers via +`rate.limiting.ipWhitelist`. See the +[WebAppSec provider → Rate limiting](../config_webappsec_provider.md) documentation for the full +parameter set (`delayMs`, `maxWaitMs`, `throttledRequests`, `insertHeaders`, `ipWhitelist`, …). + +!!! note "Async support for non-rejecting modes" + A non-negative `rate.limiting.delayMs` (delay rather than reject) requires + `gateway.servlet.async.supported=true` in `gateway-site.xml` (it is `false` by default). + +You may still add rate limiting at the edge (load balancer / reverse proxy) as defense in depth. +Anonymous client registration in particular should either be left disabled (the default) or, when +enabled, fronted by the rate-limiting filter above. + +## Operational checklist + +- [ ] TLS enabled on every topology that exposes KnoxIDF endpoints. +- [ ] External database configured for any multi-instance / HA deployment. +- [ ] Signing keystore and `gateway.signing.key.alias*` identical across all instances. +- [ ] `knoxidf.client.registration.anonymous.allowed` reviewed (default `false`). +- [ ] `knoxidf.auto.consent.enabled` reviewed (default `false`). +- [ ] Federated OP client secrets stored as aliases, not plaintext. +- [ ] Trusted-issuer registry (`KNOXIDF_ADMIN`) exposed only on an administrator-restricted topology. +- [ ] Rate limiting enabled (WebAppSec provider on the topology, and/or at the edge) for the token / authorize / registration endpoints. +- [ ] Audit log retention configured. diff --git a/knox-site/docs/knoxidf/security.md b/knox-site/docs/knoxidf/security.md new file mode 100644 index 000000000..47ded7046 --- /dev/null +++ b/knox-site/docs/knoxidf/security.md @@ -0,0 +1,183 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +# Security + +This chapter describes the security controls KnoxIDF enforces as an OAuth 2.0 / OIDC provider. +Understanding them is important when hardening a deployment and when reasoning about the trust +boundary between clients, Knox, and any external OIDC Providers. + +!!! warning "Always run over TLS" + OAuth 2.0 and OIDC assume a confidential channel. Every token-bearing endpoint must be + served over HTTPS in production. The `ssl.enabled=false` option is for local development + only. + +## Token-endpoint client authentication + +The token endpoint independently authenticates the client when redeeming an authorization code — +it does not rely on the upstream `JWTProvider` (which authenticates the *request* but does not +verify the OAuth `client_secret`). The check is chosen by what was stored at authorize time: + +- **PKCE path** — if the authorization request included a `code_challenge`, the client must + present a matching `code_verifier`. See [PKCE](#pkce). +- **Client-secret path** — if no `code_challenge` was stored, the client must present a valid + `client_secret`. + +The two paths are mutually exclusive, and there is no path where neither applies. The +`refresh_token` grant always requires a `client_secret` (there is no PKCE bypass for refresh). + +Client-secret verification is **constant-time**. The secret on the wire encodes the token id and +a passcode; Knox recomputes an HMAC (keyed by the `knox.token.hash.key` alias, over the token id, +issue time, and user name as a per-token salt) and compares it to the stored value with a +constant-time comparison. The embedded token id must equal the request `client_id`, binding the +secret to the specific client. + +## PKCE + +Only the **S256** code-challenge method is accepted. `plain` (and an omitted method, which OAuth +would otherwise default to `plain`) is **rejected** — both at the authorization endpoint (which +refuses to store a non-S256 challenge) and at the token endpoint (which refuses to verify with +any other method). This is enforced as defense in depth at both ends of the flow. The S256 +challenge is computed as `BASE64URL(SHA-256(ASCII(code_verifier)))` without padding, per +RFC 7636. + +Public clients (no `client_secret`) must use PKCE; this is how a public client proves possession +of the authorization code at redemption time. + +## Single-use authorization codes + +Authorization codes are single-use. The code is **atomically consumed before any token is +issued**, so of any number of concurrent redemptions of the same code, exactly one succeeds and +the rest receive `invalid_grant`. This closes the check-then-issue replay window. + +A code that fails *validation* (bad `redirect_uri`, wrong `client_id`, PKCE/secret failure) is +deliberately **not** consumed — this prevents a denial-of-service in which an attacker replays a +victim's code with bad parameters to burn it before the legitimate client redeems it. + +## Redirect-URI validation + +Open redirects are prevented both at registration and at authorization time. + +**At registration** (`/client/register`): + +- The **host** component may not contain a wildcard. +- **HTTPS is required.** Plain `http://` is accepted only for loopback hosts (`localhost`, + `127.0.0.1`, `::1`), per RFC 8252 for native apps. +- A wildcard `*` is permitted only at the **end of the path**, never in the host, query, or + fragment. + +**At authorization** (`/authorize`): + +- Non-wildcard URIs are matched by exact string equality. +- Wildcard URIs are matched by first comparing the **origin** (scheme + host + port) so that a + registered `https://good.example*` cannot match `https://good.example.evil.com`. Only then is + the path prefix compared, after both paths are `URI.normalize()`d — collapsing traversal + segments (e.g. `/callback/../admin`) so a raw prefix match cannot be tricked into escaping the + registered prefix. + +## Consent + +For the Authorization Code flow, KnoxIDF presents a [consent page](endpoints.md#consent-page) +where the user approves the scopes a client is requesting. Consent is **one-time per (user, +client, scopes)**: once granted, subsequent authorization requests for the same scopes proceed +without re-prompting. + +Whether consent can be skipped is a **server-side deployment decision**, governed by the +topology parameter `knoxidf.auto.consent.enabled`. It is read from the topology configuration at +startup and is **never** read from the incoming HTTP request — a client cannot bypass the consent +screen by sending an `auto_consent=true` parameter. + +Consent records are stored as metadata on the client's token record, under a fixed-width key +derived as `"consent_"` + the first 20 hex characters of `SHA-256(subject)` (28 characters +total). Hashing the subject keeps the key within the storage column width regardless of how long +the username or federated UUID subject is, and the read and write paths derive the key +identically so they always agree. + +## Dynamic client registration + +Dynamic client registration is a deliberately supported deployment mode, but it is **not open by +default**. The endpoint refuses anonymous callers unless the topology explicitly sets: + +```xml +<param> + <name>knoxidf.client.registration.anonymous.allowed</name> + <value>true</value> +</param> +``` + +The default is **`false`** (secure by default). When open registration is enabled, the +token-endpoint client authentication described above is what still prevents a +registered-but-unauthenticated client from redeeming another client's authorization code. + +## Federated id_token validation + +When Knox brokers login to an external OIDC Provider, the OP's `id_token` is **fully validated +before any claim is trusted** — it is never decoded and trusted as-is. Validation fails closed at +each stage: + +1. **Signature + `exp`/`nbf`** — verified against the OP's JWKS (fetched from the configured + `jwks.endpoint`) using the configured signature algorithm (default `RS256`). +2. **Issuer** — must equal the statically configured `federated.op.<name>.issuer`, not a value + read from the token itself. +3. **Audience** — must contain the configured `federated.op.<name>.clientId` (Knox's client id + at the OP). +4. **Subject** — `sub` must be present and non-blank. +5. **Nonce** — the token's `nonce` must equal the nonce Knox generated for that login session, + binding the token to the specific authorization request. + +If the OP configuration is missing its `jwks.endpoint`, `issuer`, or `clientId`, validation is +refused outright — no OP token can be accepted. + +See [Federation](federation.md) for the full broker flow. + +## Trusted issuer registry + +The set of external issuers Knox will accept `id_token`s from is administered through the +[Trusted OIDC Issuers admin API](endpoints.md#trusted-oidc-issuers-admin) (`KNOXIDF_ADMIN` +role), which should be exposed only on an administrator-restricted topology. Registered issuer +URLs must be HTTPS, and there is a configurable upper bound on the number of trusted issuers. + +## Secret handling + +- **Federated OP client secrets** can be resolved from Knox's `AliasService` rather than being + written in plaintext in the topology. Set `federated.op.<name>.clientSecret.alias` to an alias + name; it takes precedence over the plaintext `clientSecret` parameter. If an alias is + configured but cannot be resolved, resolution **fails closed** — the request to the OP is not + made, rather than silently falling back to a plaintext value. +- **Federated access tokens are never persisted.** Only the federated *identity* (ID-token–derived + data) is stored; the OP's access token is discarded immediately after the token exchange. + Persisting an OP bearer token in plaintext token metadata would be a secret-at-rest exposure. + +## Subject derivation + +For federated users, the Knox `sub` is a deterministic **UUIDv5** over a fixed namespace UUID +(`6ba7b811-9dad-11d1-80b4-00c04fd430c8`, the RFC 4122 "URL" namespace) with the name +`issuer + "|" + subject`. The same upstream user therefore always maps to the same Knox subject +across logins and gateway restarts. + +!!! danger "Do not change the subject namespace" + Because the `sub` is derived from a fixed namespace UUID, changing that namespace would + rewrite the subject of **every** previously persisted federated user. The namespace is an + immutable part of the deployment's identity contract. + +## What is (and isn't) stored at rest + +KnoxIDF persists **only ID-token–derived federated identity data** — the core identity mapping +(Knox subject, provider, external subject, external issuer) and a filtered set of profile +attributes (`preferred_username`, `email`, `email_verified`, `given_name`, `family_name`, +`name`, `locale`). It does **not** store access tokens, refresh tokens, or OP client secrets. +This keeps the persisted footprint to what is needed for traceability and attribute reuse. diff --git a/knox-site/mkdocs.yml b/knox-site/mkdocs.yml index f147f0937..c0f91bdbb 100644 --- a/knox-site/mkdocs.yml +++ b/knox-site/mkdocs.yml @@ -114,6 +114,14 @@ nav: - General Troubleshooting: admin_troubleshooting.md - Authentication Issues: auth_troubleshooting.md - Service-Specific Issues: service_troubleshooting.md + - Identity Federation (OIDC Provider): + - Overview: knoxidf/index.md + - Getting Started: knoxidf/getting_started.md + - Endpoint Reference: knoxidf/endpoints.md + - Configuration Reference: knoxidf/configuration.md + - Security: knoxidf/security.md + - Federation: knoxidf/federation.md + - Operations: knoxidf/operations.md - Developer Guide: - Overview: dev-guide/book.md - Extending Knox: @@ -134,7 +142,11 @@ markdown_extensions: permalink: true - admonition - pymdownx.details - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.highlight: anchor_linenums: true - pymdownx.inlinehilite
