Skip to content

Commit fef4e78

Browse files
feat: Expose the OIDC JWKS tunables through the operator (#6690)
* feat: Expose the OIDC JWKS tunables through the operator Follow-up to #6683, requested in its review. Add jwksCacheLifespanSeconds and jwksRequestTimeoutSeconds to OidcAuthz as CR fields rather than OIDC Secret keys: these are non-secret operational knobs, so they belong with verifySSL and caCertConfigMap rather than in the Secret bag that carries IdP-coupled credentials. Both are optional pointers with a Minimum=1 constraint mirroring the SDK's validation, and are omitted from the generated feature_store.yaml when unset so the SDK defaults apply rather than the operator asserting its own. Regenerates deepcopy, CRD bases, dist/install.yaml, and the API reference. Documents that the cache lifespan is not purely a performance setting: it also bounds how long a key the provider revoked keeps validating tokens. Signed-off-by: Larry Singleton <166439969+larrysingleton007@users.noreply.github.com> * fix: Regenerate the OLM bundle and close review gaps Code review findings on the JWKS tunables change. The OLM bundle CRD was not regenerated, so it lacked both new fields while config/crd/bases and dist/install.yaml carried them. Every other OidcAuthz field is present in all three copies, and no PR workflow runs make bundle, so CI would not have caught it: an OLM install would have pruned the settings silently rather than failing. Regenerated with make bundle; operator-sdk bundle validate passes. Assert the client repo config omits both keys. OidcClientAuthConfig inherits the same strict validation, so mirroring the forwarding into the client path would break every client pod, and nothing tested it. Also restore the neighbouring blocks' length assertion so a leaked parameter fails. Add CRD validation tests for the Minimum=1 constraints the docs promise. Docs: caCertConfigMap was documented as a bare string but the CRD requires an object with a name key, so the whole snippet failed to apply, including the lines added here. Name the required Feast version instead of implying any newer image works. Signed-off-by: Larry Singleton <166439969+larrysingleton007@users.noreply.github.com> * docs: State that the OIDC authz options require apiVersion v1 The CRD serves v1alpha1 alongside v1 with no conversion webhook, so a resource submitted as v1alpha1 is validated against the v1alpha1 schema and any field outside it is pruned without error. Under v1alpha1, authz.oidc accepts only secretRef, so every other option is silently dropped. This predates the JWKS settings: v1alpha1 has never carried issuerUrl, secretKeyName, tokenEnvVar, verifySSL or caCertConfigMap either, all of which landed v1-only in 7c04026. Documenting the whole section rather than the two new fields keeps the guidance consistent with that. The v1alpha1 schema already enforces this - the fields cannot be expressed there - so no validation change is needed, only the missing warning. Signed-off-by: Larry Singleton <166439969+larrysingleton007@users.noreply.github.com> --------- Signed-off-by: Larry Singleton <166439969+larrysingleton007@users.noreply.github.com>
1 parent ea17419 commit fef4e78

13 files changed

Lines changed: 172 additions & 6 deletions

File tree

.secrets.baseline

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -957,7 +957,7 @@
957957
"filename": "infra/feast-operator/api/v1/featurestore_types.go",
958958
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
959959
"is_verified": false,
960-
"line_number": 958
960+
"line_number": 969
961961
}
962962
],
963963
"infra/feast-operator/api/v1/zz_generated.deepcopy.go": [
@@ -980,7 +980,7 @@
980980
"filename": "infra/feast-operator/api/v1/zz_generated.deepcopy.go",
981981
"hashed_secret": "c2028031c154bbe86fd69bef740855c74b927dcf",
982982
"is_verified": false,
983-
"line_number": 1595
983+
"line_number": 1605
984984
}
985985
],
986986
"infra/feast-operator/api/v1alpha1/featurestore_types.go": [
@@ -1172,7 +1172,7 @@
11721172
"filename": "infra/feast-operator/internal/controller/services/repo_config.go",
11731173
"hashed_secret": "e2fb052132fd6a07a56af2013e0b62a1f510572c",
11741174
"is_verified": false,
1175-
"line_number": 235
1175+
"line_number": 241
11761176
}
11771177
],
11781178
"infra/feast-operator/internal/controller/services/services.go": [
@@ -1564,5 +1564,5 @@
15641564
}
15651565
]
15661566
},
1567-
"generated_at": "2026-07-31T05:29:18Z"
1567+
"generated_at": "2026-07-31T18:15:46Z"
15681568
}

docs/how-to-guides/feast-operator/05-security.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ spec:
9393

9494
### Advanced OIDC options
9595

96+
{% hint style="warning" %}
97+
Every option in this section requires `apiVersion: feast.dev/v1`. Under the deprecated
98+
`feast.dev/v1alpha1`, `authz.oidc` accepts only `secretRef`. The CRD has no conversion
99+
webhook, so a resource submitted as v1alpha1 is validated against the v1alpha1 schema and
100+
any other field is pruned without error rather than rejected. Applying the example below
101+
as v1alpha1 therefore leaves OIDC configured by Secret alone, with none of these settings
102+
taking effect and nothing in the output to say so. Use v1, which is the storage version.
103+
{% endhint %}
104+
96105
```yaml
97106
authz:
98107
oidc:
@@ -101,9 +110,18 @@ authz:
101110
secretKeyName: client_id # override the default Secret key name
102111
tokenEnvVar: FEAST_TOKEN # env var from which servers read the Bearer token
103112
verifySSL: false # disable SSL verification (dev only)
104-
caCertConfigMap: oidc-ca-cert # ConfigMap with CA cert for SSL verification
113+
caCertConfigMap: # ConfigMap with CA cert for SSL verification
114+
name: oidc-ca-cert
115+
jwksCacheLifespanSeconds: 300 # how long servers reuse the fetched JWK set
116+
jwksRequestTimeoutSeconds: 10 # network timeout for the JWKS fetch
105117
```
106118

119+
`jwksCacheLifespanSeconds` is not only a performance setting: it also bounds how long a key the provider has **revoked** continues to validate tokens. Lower it if your provider rotates or revokes aggressively, at the cost of proportionally more JWKS fetches. Key rotations that introduce a new key id are picked up immediately regardless, because an unknown key id forces a refetch. `jwksRequestTimeoutSeconds` bounds how long an unresponsive provider can block request serving. Both must be at least 1. When unset, neither key is written to the generated configuration and the feature server applies its own defaults (300 and 10 seconds respectively).
120+
121+
{% hint style="warning" %}
122+
These two options require a feature server image that recognizes them. The operator deploys a matching image by default, so this only applies if you pin an older one explicitly, through a container `image` override or the operator's `RELATED_IMAGE_FEATURE_SERVER` setting. An image that predates these options rejects its configuration at startup, so leave them unset until the pinned image is updated.
123+
{% endhint %}
124+
107125
**SDK docs**: [Feast OIDC Auth](../../getting-started/components/authz_manager.md#oidc-authorization)
108126

109127
---

infra/feast-operator/api/v1/featurestore_types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,17 @@ type OidcAuthz struct {
929929
// ConfigMap with the CA certificate for self-signed OIDC providers. Auto-detected on RHOAI/ODH.
930930
// +optional
931931
CACertConfigMap *OidcCACertConfigMap `json:"caCertConfigMap,omitempty"`
932+
// Seconds the servers reuse the provider's fetched JWK set before refetching. Defaults to 300.
933+
// Also bounds how long a key the provider revoked keeps validating tokens, so lower it if the
934+
// provider rotates or revokes aggressively, at the cost of more JWKS fetches.
935+
// +optional
936+
// +kubebuilder:validation:Minimum=1
937+
JwksCacheLifespanSeconds *int32 `json:"jwksCacheLifespanSeconds,omitempty"`
938+
// Seconds before a JWKS fetch times out. Defaults to 10. The fetch happens inline on the request
939+
// path, so an unresponsive provider blocks serving for at most this long.
940+
// +optional
941+
// +kubebuilder:validation:Minimum=1
942+
JwksRequestTimeoutSeconds *int32 `json:"jwksRequestTimeoutSeconds,omitempty"`
932943
}
933944

934945
// OidcCACertConfigMap references a ConfigMap containing a CA certificate for OIDC provider TLS.

infra/feast-operator/api/v1/zz_generated.deepcopy.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ metadata:
147147
}
148148
]
149149
capabilities: Basic Install
150-
createdAt: "2026-07-20T13:27:58Z"
150+
createdAt: "2026-07-31T18:28:04Z"
151151
operators.operatorframework.io/builder: operator-sdk-v1.41.0
152152
operators.operatorframework.io/project_layout: go.kubebuilder.io/v4
153153
name: feast-operator.v0.65.0

infra/feast-operator/bundle/manifests/feast.dev_featurestores.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ spec:
8181
to derive the discovery endpoint.
8282
pattern: ^https://\S+$
8383
type: string
84+
jwksCacheLifespanSeconds:
85+
description: Seconds the servers reuse the provider's fetched
86+
JWK set before refetching. Defaults to 300.
87+
format: int32
88+
minimum: 1
89+
type: integer
90+
jwksRequestTimeoutSeconds:
91+
description: Seconds before a JWKS fetch times out. Defaults
92+
to 10.
93+
format: int32
94+
minimum: 1
95+
type: integer
8496
secretKeyName:
8597
description: Key in the Secret containing all OIDC properties
8698
as a YAML value. If unset, each key is a property.
@@ -6390,6 +6402,18 @@ spec:
63906402
to derive the discovery endpoint.
63916403
pattern: ^https://\S+$
63926404
type: string
6405+
jwksCacheLifespanSeconds:
6406+
description: Seconds the servers reuse the provider's
6407+
fetched JWK set before refetching. Defaults to 300.
6408+
format: int32
6409+
minimum: 1
6410+
type: integer
6411+
jwksRequestTimeoutSeconds:
6412+
description: Seconds before a JWKS fetch times out. Defaults
6413+
to 10.
6414+
format: int32
6415+
minimum: 1
6416+
type: integer
63936417
secretKeyName:
63946418
description: Key in the Secret containing all OIDC properties
63956419
as a YAML value. If unset, each key is a property.

infra/feast-operator/config/crd/bases/feast.dev_featurestores.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ spec:
8181
to derive the discovery endpoint.
8282
pattern: ^https://\S+$
8383
type: string
84+
jwksCacheLifespanSeconds:
85+
description: Seconds the servers reuse the provider's fetched
86+
JWK set before refetching. Defaults to 300.
87+
format: int32
88+
minimum: 1
89+
type: integer
90+
jwksRequestTimeoutSeconds:
91+
description: Seconds before a JWKS fetch times out. Defaults
92+
to 10.
93+
format: int32
94+
minimum: 1
95+
type: integer
8496
secretKeyName:
8597
description: Key in the Secret containing all OIDC properties
8698
as a YAML value. If unset, each key is a property.
@@ -6390,6 +6402,18 @@ spec:
63906402
to derive the discovery endpoint.
63916403
pattern: ^https://\S+$
63926404
type: string
6405+
jwksCacheLifespanSeconds:
6406+
description: Seconds the servers reuse the provider's
6407+
fetched JWK set before refetching. Defaults to 300.
6408+
format: int32
6409+
minimum: 1
6410+
type: integer
6411+
jwksRequestTimeoutSeconds:
6412+
description: Seconds before a JWKS fetch times out. Defaults
6413+
to 10.
6414+
format: int32
6415+
minimum: 1
6416+
type: integer
63936417
secretKeyName:
63946418
description: Key in the Secret containing all OIDC properties
63956419
as a YAML value. If unset, each key is a property.

infra/feast-operator/dist/install.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ spec:
8989
to derive the discovery endpoint.
9090
pattern: ^https://\S+$
9191
type: string
92+
jwksCacheLifespanSeconds:
93+
description: Seconds the servers reuse the provider's fetched
94+
JWK set before refetching. Defaults to 300.
95+
format: int32
96+
minimum: 1
97+
type: integer
98+
jwksRequestTimeoutSeconds:
99+
description: Seconds before a JWKS fetch times out. Defaults
100+
to 10.
101+
format: int32
102+
minimum: 1
103+
type: integer
92104
secretKeyName:
93105
description: Key in the Secret containing all OIDC properties
94106
as a YAML value. If unset, each key is a property.
@@ -6398,6 +6410,18 @@ spec:
63986410
to derive the discovery endpoint.
63996411
pattern: ^https://\S+$
64006412
type: string
6413+
jwksCacheLifespanSeconds:
6414+
description: Seconds the servers reuse the provider's
6415+
fetched JWK set before refetching. Defaults to 300.
6416+
format: int32
6417+
minimum: 1
6418+
type: integer
6419+
jwksRequestTimeoutSeconds:
6420+
description: Seconds before a JWKS fetch times out. Defaults
6421+
to 10.
6422+
format: int32
6423+
minimum: 1
6424+
type: integer
64016425
secretKeyName:
64026426
description: Key in the Secret containing all OIDC properties
64036427
as a YAML value. If unset, each key is a property.

infra/feast-operator/docs/api/markdown/ref.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,11 @@ _Appears in:_
642642
| `tokenEnvVar` _string_ | Env var name for client pods to read an OIDC token from. Sets token_env_var in client config. |
643643
| `verifySSL` _boolean_ | Verify SSL certificates for the OIDC provider. Defaults to true. |
644644
| `caCertConfigMap` _[OidcCACertConfigMap](#oidccacertconfigmap)_ | ConfigMap with the CA certificate for self-signed OIDC providers. Auto-detected on RHOAI/ODH. |
645+
| `jwksCacheLifespanSeconds` _integer_ | Seconds the servers reuse the provider's fetched JWK set before refetching. Defaults to 300.
646+
Also bounds how long a key the provider revoked keeps validating tokens, so lower it if the
647+
provider rotates or revokes aggressively, at the cost of more JWKS fetches. |
648+
| `jwksRequestTimeoutSeconds` _integer_ | Seconds before a JWKS fetch times out. Defaults to 10. The fetch happens inline on the request
649+
path, so an unresponsive provider blocks serving for at most this long. |
645650

646651

647652
#### OidcCACertConfigMap

infra/feast-operator/internal/controller/services/repo_config.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ func getBaseServiceRepoConfig(
163163
if oidcAuthz.VerifySSL != nil {
164164
oidcParameters[string(OidcVerifySsl)] = *oidcAuthz.VerifySSL
165165
}
166+
if oidcAuthz.JwksCacheLifespanSeconds != nil {
167+
oidcParameters[string(OidcJwksCacheLifespanSeconds)] = *oidcAuthz.JwksCacheLifespanSeconds
168+
}
169+
if oidcAuthz.JwksRequestTimeoutSeconds != nil {
170+
oidcParameters[string(OidcJwksRequestTimeoutSeconds)] = *oidcAuthz.JwksRequestTimeoutSeconds
171+
}
166172
if caCertPath := resolveOidcCACertPath(oidcAuthz, odhCaBundleExists); caCertPath != "" {
167173
oidcParameters[string(OidcCaCertPath)] = caCertPath
168174
}

0 commit comments

Comments
 (0)