Visitar URL original
feat: Expose the OIDC JWKS tunables through the operator by larrysingleton007 · Pull Request #6690 · feast-dev/feast · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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>
  • Loading branch information
larrysingleton007 authored and ntkathole committed Aug 14, 2026
commit c8fdd6562b8ae17d3f2a51d15ad4276f55315fa7
7 changes: 4 additions & 3 deletions docs/how-to-guides/feast-operator/05-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,16 @@ authz:
secretKeyName: client_id # override the default Secret key name
tokenEnvVar: FEAST_TOKEN # env var from which servers read the Bearer token
verifySSL: false # disable SSL verification (dev only)
caCertConfigMap: oidc-ca-cert # ConfigMap with CA cert for SSL verification
caCertConfigMap: # ConfigMap with CA cert for SSL verification
name: oidc-ca-cert
jwksCacheLifespanSeconds: 300 # how long servers reuse the fetched JWK set
jwksRequestTimeoutSeconds: 10 # network timeout for the JWKS fetch
```

`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, and both are omitted from the generated configuration when unset so the SDK defaults apply.
`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).

{% hint style="warning" %}
These two options require a feature server image that supports them. Setting them while running an older image causes the server to reject its configuration at startup, so upgrade the image alongside the operator before enabling them.
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.
{% endhint %}

**SDK docs**: [Feast OIDC Auth](../../getting-started/components/authz_manager.md#oidc-authorization)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ metadata:
}
]
capabilities: Basic Install
createdAt: "2026-07-20T13:27:58Z"
createdAt: "2026-07-31T18:28:04Z"
operators.operatorframework.io/builder: operator-sdk-v1.41.0
operators.operatorframework.io/project_layout: go.kubebuilder.io/v4
name: feast-operator.v0.65.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ spec:
to derive the discovery endpoint.
pattern: ^https://\S+$
type: string
jwksCacheLifespanSeconds:
description: Seconds the servers reuse the provider's fetched
JWK set before refetching. Defaults to 300.
format: int32
minimum: 1
type: integer
jwksRequestTimeoutSeconds:
description: Seconds before a JWKS fetch times out. Defaults
to 10.
format: int32
minimum: 1
type: integer
secretKeyName:
description: Key in the Secret containing all OIDC properties
as a YAML value. If unset, each key is a property.
Expand Down Expand Up @@ -6390,6 +6402,18 @@ spec:
to derive the discovery endpoint.
pattern: ^https://\S+$
type: string
jwksCacheLifespanSeconds:
description: Seconds the servers reuse the provider's
fetched JWK set before refetching. Defaults to 300.
format: int32
minimum: 1
type: integer
jwksRequestTimeoutSeconds:
description: Seconds before a JWKS fetch times out. Defaults
to 10.
format: int32
minimum: 1
type: integer
secretKeyName:
description: Key in the Secret containing all OIDC properties
as a YAML value. If unset, each key is a property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,15 @@ var _ = Describe("Repo Config", func() {
ApplyDefaultsToStatus(featureStore)
repoConfig, err = getServiceRepoConfig(featureStore, emptyMockExtractConfigFromSecret, emptyMockExtractConfigFromConfigMap, false)
Expect(err).NotTo(HaveOccurred())
Expect(repoConfig.AuthzConfig.OidcParameters).To(HaveLen(3))
Expect(repoConfig.AuthzConfig.OidcParameters).To(HaveKeyWithValue(string(OidcJwksCacheLifespanSeconds), int32(60)))
Expect(repoConfig.AuthzConfig.OidcParameters).To(HaveKeyWithValue(string(OidcJwksRequestTimeoutSeconds), int32(5)))

By("Keeping the JWKS tunables out of the client config, which does not accept them")
clientRepoConfig := getClientRepoConfig(featureStore, nil)
Expect(clientRepoConfig.AuthzConfig.OidcParameters).NotTo(HaveKey(string(OidcJwksCacheLifespanSeconds)))
Expect(clientRepoConfig.AuthzConfig.OidcParameters).NotTo(HaveKey(string(OidcJwksRequestTimeoutSeconds)))

By("Having oidc with issuerUrl on CR and auth_discovery_url in Secret — CR wins")
featureStore.Spec.AuthzConfig = &feastdevv1.AuthzConfig{
OidcAuthz: &feastdevv1.OidcAuthz{
Expand Down
15 changes: 15 additions & 0 deletions infra/feast-operator/test/api/featurestore_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,13 @@ func authzConfigWithOidc(featureStore *feastdevv1.FeatureStore) *feastdevv1.Feat
return fsCopy
}

func authzConfigWithOidcJwksTunables(jwksCacheLifespanSeconds *int32, jwksRequestTimeoutSeconds *int32, featureStore *feastdevv1.FeatureStore) *feastdevv1.FeatureStore {
fsCopy := authzConfigWithOidc(featureStore)
fsCopy.Spec.AuthzConfig.OidcAuthz.JwksCacheLifespanSeconds = jwksCacheLifespanSeconds
fsCopy.Spec.AuthzConfig.OidcAuthz.JwksRequestTimeoutSeconds = jwksRequestTimeoutSeconds
return fsCopy
}

func onlineStoreWithDBPersistenceType(dbPersistenceType string, featureStore *feastdevv1.FeatureStore) *feastdevv1.FeatureStore {
fsCopy := featureStore.DeepCopy()
fsCopy.Spec.Services = &feastdevv1.FeatureStoreServices{
Expand Down Expand Up @@ -610,6 +617,14 @@ var _ = Describe("FeatureStore API", func() {
It("should fail when both kubernetes and oidc settings are given", func() {
attemptInvalidCreationAndAsserts(ctx, authzConfigWithOidc(authzConfigWithKubernetes(featurestore)), "One selection required between kubernetes or oidc")
})
It("should fail when the oidc JWKS cache lifespan is below one second", func() {
zero := int32(0)
attemptInvalidCreationAndAsserts(ctx, authzConfigWithOidcJwksTunables(&zero, nil, featurestore), "should be greater than or equal to 1")
})
It("should fail when the oidc JWKS request timeout is below one second", func() {
zero := int32(0)
attemptInvalidCreationAndAsserts(ctx, authzConfigWithOidcJwksTunables(nil, &zero, featurestore), "should be greater than or equal to 1")
})
})

Context("When creating a Registry", func() {
Expand Down