Sandor Molnar created KNOX-3512:
-----------------------------------
Summary: KnoxIDF: per-issuer CA bundle (jwksCa) for trusted OIDC
issuer discovery and JWKS fetches
Key: KNOX-3512
URL: https://issues.apache.org/jira/browse/KNOX-3512
Project: Apache Knox
Issue Type: Task
Components: JWT, Server
Affects Versions: 3.1.0
Reporter: Sandor Molnar
Assignee: Sandor Molnar
Fix For: 3.1.0
*Problem*
Knox fetches each trusted OIDC issuer's discovery document and JWKS over HTTPS
using the global JVM/Knox truststore. Kubernetes apiservers acting as issuers
often use self-signed certificates, each with its own CA. Today the only way to
trust them is to import every CA into the global truststore at startup. That
has two costs:
- Every CA rotation needs a restart.
- Trust widens for all TLS clients in Knox (LDAP and the rest).
TrustedOidcIssuer has no way to carry a CA.
*Proposal*
Add an optional {{jwksCa}} field to trusted OIDC issuer registrations. It takes
one of two forms:
- Inline PEM: one or more {{----{-}BEGIN CERTIFICATE{-}----}} blocks.
- Kubernetes Secret reference: {{{}secret:<namespace>/<name>#<key>{}}}. All
three parts are required. Knox reads it through the in-cluster Kubernetes API.
+Behaviour:+
- When {{jwksCa}} is set, that issuer's discovery and JWKS TLS connections
trust only the given bundle, and hostname verification stays on. The global
truststore is never changed, so other TLS clients in Knox do not trust the
issuer CA.
- When {{jwksCa}} is not set, nothing changes.
- {{jwksCa}} is only accepted together with {{{}dynamicJwks: true{}}}.
- If {{jwksCa}} is configured but cannot be loaded (Secret or key missing,
unparsable PEM), verification fails closed and does not fall back to the global
truststore.
- CA changes take effect without a restart:
-- Inline: re-registering the issuer replaces its trust material straight away.
-- Secret reference: Knox reads the Secret lazily, with no background refresh.
It reads it:
--- on the first discovery or JWKS fetch for the issuer.
--- on access, when the cached CA is older than
{{gateway.trusted.oidc.issuer.ca.secret.max.age.secs}} (default {{{}1800{}}}).
This picks up CAs removed from the bundle.
--- after a fetch fails with a TLS trust error (handshake or certificate
validation). Knox re-reads the Secret and retries once, at most once every 30s
per issuer. Timeouts and non-200 responses don't trigger a re-read.
--- on {{{}POST /trusted-oidc-issuers/refresh-jwks{}}}, which drops the cached
CA so the next fetch reads it again.
-- If the Secret can't be read the first time, verification fails closed. If a
max-age re-read fails, Knox keeps the last working CA and logs the failure.
- Rotating the issuer's signing key still needs no config change. When a
token's kid is not in the cached JWKS, the JWKS is fetched again.
*Changes*
- {{{}TrustedOidcIssuer{}}}, {{RegisterIssuerRequest}} and the admin REST
resource get the new jwksCa field. Registration validates it and returns 400
invalid_request when:
-- it is set but dynamicJwks is false
-- it is too long
-- a secret: value has the wrong syntax
-- inline PEM does not parse
- {{TRUSTED_OIDC_ISSUERS}} table: new {{jwks_ca}} column ({{{}TEXT{}}}, or
{{CLOB}} on Oracle).
- {{TrustedOidcIssuerService}} exposes per-issuer TLS trust. It is used by
OIDC discovery (Apache HttpClient) and by the JWKS fetch (Nimbus
{{DefaultResourceRetriever}} with a per-issuer {{{}SSLSocketFactory{}}}).
- JWTokenAuthority gets an overload of verifyToken that takes the JWKS
SSLSocketFactory.
- Docs: new field, config properties and RBAC requirements. Also a shared-root
option: if every cluster chains to one root CA, use the same bundle or Secret
for every issuer.
Acceptance criteria
- Issuers with different self-signed CAs all validate.
- Replacing an issuer's CA bundle (inline or Secret) takes effect without a
Knox restart.
- An unrelated TLS client in Knox does not trust the issuer CA.
- Rotating an apiserver signing key needs no Knox config change.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)