[ 
https://issues.apache.org/jira/browse/KNOX-3512?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Sandor Molnar updated KNOX-3512:
--------------------------------
    Description: 
*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>{}}}. 
Namespace defaults to "knox", the other 2 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.

  was:
*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.


> 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
>            Priority: Major
>             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>{}}}. 
> Namespace defaults to "knox", the other 2 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)

Reply via email to