Skip to content

Commit 8123bd7

Browse files
committed
fix: Preserve init image precedence for packaged repositories
Merge current master after feast-dev#6598 and resolve init container image selection as services.initImage, packaged.image, RELATED_IMAGE_FEATURE_SERVER, then DefaultImage. Preserve the upstream onlineStore.disabled defaults and regenerate operator artifacts. Signed-off-by: Shumin <[email protected]>
2 parents 509f8d7 + c8628eb commit 8123bd7

98 files changed

Lines changed: 3585 additions & 456 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.secrets.baseline

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

docs/getting-started/components/authz_manager.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ The server, in turn, uses the same OIDC server to validate the token and extract
4545
Some assumptions are made in the OIDC server configuration:
4646
* The OIDC token refers to a client with roles matching the RBAC roles of the configured `Permission`s (*)
4747
* The roles are exposed in the access token under `resource_access.<client_id>.roles` (Keycloak) or in the top-level `roles` claim (Entra ID app roles). Roles found in both are merged.
48-
* The JWT token is expected to have a verified signature and not be expired. The Feast OIDC token parser logic validates for `verify_signature` and `verify_exp` so make sure that the given OIDC provider is configured to meet these requirements.
48+
* The JWT token is expected to have a verified signature and not be expired. The Feast OIDC token parser logic validates for `verify_signature` and `verify_exp` so make sure that the given OIDC provider is configured to meet these requirements. The token's audience and issuer claims are **not** verified by default; both checks can be enabled with the `audience` and `issuer` options (see [Server-Side Configuration](#server-side-configuration)).
4949
* The username is read from the first of `preferred_username`, `upn`, `azp`, `appid`, `sub` present in the token. Entra ID client-credentials (app-only) tokens carry no user claim, so they authenticate as the calling application.
5050
* For `GroupBasedPolicy` support, the `groups` claim should be present in the access token (requires a "Group Membership" protocol mapper in Keycloak).
5151
* **Entra ID limitation**: Group claims use object IDs (GUIDs) instead of names, and are omitted entirely when a user exceeds the group overage threshold. GroupBasedPolicy must reference GUIDs and cannot be used for principals with large group memberships.
@@ -105,6 +105,23 @@ auth:
105105
Setting `verify_ssl: false` disables TLS certificate verification for all OIDC provider communication (discovery, JWKS, token endpoint). Only use this in development or internal environments where you accept the security risk.
106106
{% endhint %}
107107

108+
By default the server verifies only the token's signature and expiry: any validly-signed, unexpired token from the configured provider is accepted regardless of the audience it was minted for, and authorization (role matching) is the only remaining gate. For defense in depth, set `audience` and/or `issuer` to additionally require a matching `aud` / `iss` claim:
109+
110+
```yaml
111+
auth:
112+
type: oidc
113+
client_id: _CLIENT_ID_
114+
auth_discovery_url: https://login.example.com/.well-known/openid-configuration
115+
audience: api://feast-feature-server
116+
issuer: https://login.example.com/realms/master
117+
```
118+
119+
A token whose `aud` (or `iss`) claim does not match is rejected at authentication. The two options are independent; leave one unset to skip that check.
120+
121+
{% hint style="warning" %}
122+
Set these to the values your IdP puts **in the token itself**, which are not always the ones in the discovery document. For example, Microsoft Entra ID commonly issues v1.0 tokens (`iss: https://sts.windows.net/<tenant-id>/`, `aud: api://<app-id-uri>`) even when `auth_discovery_url` points at the v2.0 endpoint. That setup keeps working with these options unset, or set to the v1.0 values — but copying the v2.0 issuer from the discovery document would reject every v1.0 token.
123+
{% endhint %}
124+
108125
#### Client-Side Configuration
109126

110127
The client supports multiple token source modes. The SDK resolves tokens in the following priority order:

docs/how-to-guides/feast-operator/01-project-provisioning.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,10 @@ so `feast apply` must be handled separately—for example, by CI/CD or a separat
218218
Kubernetes Job or CronJob—whenever the packaged feature definitions change.
219219

220220
The packaged `image` is optional. When set, it is the default for repository initialization,
221-
`feast apply`, and Feast services. An explicit image on an individual service takes
222-
precedence. When omitted, the operator uses `RELATED_IMAGE_FEATURE_SERVER` or its built-in
223-
feature-server image fallback.
221+
`feast apply`, and Feast services. `services.initImage` takes precedence for the
222+
`feast-init` and `feast-apply` init containers, while an explicit image on an individual
223+
service takes precedence for that service. When the packaged image is omitted, the operator
224+
uses `RELATED_IMAGE_FEATURE_SERVER` or its built-in feature-server image fallback.
224225

225226
### Full `packaged` field reference
226227

docs/how-to-guides/production-deployment-topologies.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,9 +1154,10 @@ spec:
11541154
imagePullPolicy: IfNotPresent
11551155
```
11561156

1157-
The packaged image is the default for every Feast service. A per-service `image` still takes
1158-
precedence when a service needs a specialized image. Remove `disableInitContainers: true`
1159-
to use operator-managed staging and startup apply instead.
1157+
The packaged image is the default for every Feast service and for the `feast-init` and
1158+
`feast-apply` init containers. A per-service `image` still takes precedence for that
1159+
service, and `services.initImage` takes precedence for both init containers. Remove
1160+
`disableInitContainers: true` to use operator-managed staging and startup apply instead.
11601161

11611162
{% hint style="info" %}
11621163
**Pre-populating the registry:** With init containers disabled, `feast apply` does not run on pod startup. You can populate the registry by:

infra/feast-operator/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ COPY --chown=1001:0 go.sum go.sum
1212
RUN go mod download
1313

1414
# Copy the go source
15-
COPY --chown=1001:0 cmd/main.go cmd/main.go
15+
COPY --chown=1001:0 cmd/ cmd/
1616
COPY --chown=1001:0 api/ api/
1717
COPY --chown=1001:0 internal/controller/ internal/controller/
1818

@@ -21,7 +21,7 @@ COPY --chown=1001:0 internal/controller/ internal/controller/
2121
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
2222
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
2323
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
24-
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
24+
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager ./cmd/
2525

2626
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.8
2727
WORKDIR /

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,10 @@ type FeatureStoreServices struct {
419419
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`
420420
// Disable the 'feast repo initialization' initContainer
421421
DisableInitContainers bool `json:"disableInitContainers,omitempty"`
422+
// InitImage overrides the image for init containers (feast-init, feast-apply).
423+
// Resolution order: InitImage → FeastProjectDir.Packaged.Image → RELATED_IMAGE_FEATURE_SERVER → DefaultImage.
424+
// +optional
425+
InitImage *string `json:"initImage,omitempty"`
422426
// Runs feast apply on pod start to populate the registry. Defaults to true. Ignored when DisableInitContainers is true.
423427
RunFeastApplyOnInit *bool `json:"runFeastApplyOnInit,omitempty"`
424428
// Volumes specifies the volumes to mount in the FeatureStore deployment. A corresponding `VolumeMount` should be added to whichever feast service(s) require access to said volume(s).
@@ -556,6 +560,11 @@ type OnlineStore struct {
556560
// Controls metrics granularity, offline push batching, and MCP.
557561
// +optional
558562
Serving *ServingConfig `json:"serving,omitempty"`
563+
// Disabled skips deploying the online store service entirely, including its
564+
// serving pod and persistence. Omitting the online store block, or setting
565+
// this to false, deploys the online store with defaults as before.
566+
// +optional
567+
Disabled bool `json:"disabled,omitempty"`
559568
}
560569

561570
// ServingConfig configures the feature_server section of the generated feature_store.yaml.

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

Lines changed: 5 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.dev_featurestores.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,6 +1745,10 @@ spec:
17451745
disableInitContainers:
17461746
description: Disable the 'feast repo initialization' initContainer
17471747
type: boolean
1748+
initImage:
1749+
description: InitImage overrides the image for init containers
1750+
(feast-init, feast-apply).
1751+
type: string
17481752
offlineStore:
17491753
description: OfflineStore configures the offline store service
17501754
properties:
@@ -2280,6 +2284,11 @@ spec:
22802284
onlineStore:
22812285
description: OnlineStore configures the online store service
22822286
properties:
2287+
disabled:
2288+
description: |-
2289+
Disabled skips deploying the online store service entirely, including its
2290+
serving pod and persistence.
2291+
type: boolean
22832292
persistence:
22842293
description: OnlineStorePersistence configures the persistence
22852294
settings for the online store service
@@ -8064,6 +8073,10 @@ spec:
80648073
disableInitContainers:
80658074
description: Disable the 'feast repo initialization' initContainer
80668075
type: boolean
8076+
initImage:
8077+
description: InitImage overrides the image for init containers
8078+
(feast-init, feast-apply).
8079+
type: string
80678080
offlineStore:
80688081
description: OfflineStore configures the offline store service
80698082
properties:
@@ -8606,6 +8619,11 @@ spec:
86068619
onlineStore:
86078620
description: OnlineStore configures the online store service
86088621
properties:
8622+
disabled:
8623+
description: |-
8624+
Disabled skips deploying the online store service entirely, including its
8625+
serving pod and persistence.
8626+
type: boolean
86098627
persistence:
86108628
description: OnlineStorePersistence configures the persistence
86118629
settings for the online store service

infra/feast-operator/cmd/main.go

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ import (
3434
corev1 "k8s.io/api/core/v1"
3535
policyv1 "k8s.io/api/policy/v1"
3636
rbacv1 "k8s.io/api/rbac/v1"
37-
apierrors "k8s.io/apimachinery/pkg/api/errors"
38-
apimeta "k8s.io/apimachinery/pkg/api/meta"
3937
"k8s.io/apimachinery/pkg/labels"
4038
"k8s.io/apimachinery/pkg/runtime"
4139
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -102,7 +100,7 @@ func main() {
102100
var probeAddr string
103101
var secureMetrics bool
104102
var featureStoreMetrics bool
105-
var tlsOpts []func(*tls.Config)
103+
tlsOpts := make([]func(*tls.Config), 0, 2)
106104
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
107105
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
108106
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
@@ -130,46 +128,12 @@ func main() {
130128
os.Exit(1)
131129
}
132130

133-
tlsProfileFetched := false
134-
tlsProfile, err := tlspkg.FetchAPIServerTLSProfile(context.Background(), bootstrapClient)
131+
tlsResult, err := bootstrapTLS(context.Background(), bootstrapClient)
135132
if err != nil {
136-
switch {
137-
case apimeta.IsNoMatchError(err):
138-
setupLog.Info("TLS profile not available, using hardened defaults (non-OpenShift cluster)")
139-
case apierrors.IsNotFound(err):
140-
setupLog.Info("APIServer resource not found, using hardened defaults")
141-
default:
142-
setupLog.Error(err, "unable to read APIServer TLS profile, refusing to start with unknown TLS posture")
143-
os.Exit(1)
144-
}
145-
} else {
146-
tlsProfileFetched = true
147-
tlsConfigFn, unsupported := tlspkg.NewTLSConfigFromProfile(tlsProfile)
148-
if len(unsupported) > 0 {
149-
setupLog.Info("TLS profile contains ciphers unsupported by Go", "unsupported", unsupported)
150-
}
151-
tlsOpts = append(tlsOpts, tlsConfigFn)
152-
}
153-
154-
tlsAdherenceFetched := false
155-
tlsAdherence, err := tlspkg.FetchAPIServerTLSAdherencePolicy(context.Background(), bootstrapClient)
156-
if err != nil {
157-
switch {
158-
case apimeta.IsNoMatchError(err):
159-
setupLog.Info("TLS adherence policy not available (non-OpenShift cluster)")
160-
case apierrors.IsNotFound(err):
161-
setupLog.Info("APIServer resource not found, skipping adherence policy")
162-
default:
163-
setupLog.Error(err, "unable to read APIServer TLS adherence policy, refusing to start")
164-
os.Exit(1)
165-
}
166-
} else {
167-
tlsAdherenceFetched = true
133+
setupLog.Error(err, "TLS bootstrap failed")
134+
os.Exit(1)
168135
}
169-
170-
tlsOpts = append(tlsOpts, func(c *tls.Config) {
171-
c.NextProtos = []string{"h2", "http/1.1"}
172-
})
136+
tlsOpts = append(tlsOpts, tlsResult.TLSOpts...)
173137

174138
webhookServer := webhook.NewServer(webhook.Options{
175139
TLSOpts: tlsOpts,
@@ -271,17 +235,17 @@ func main() {
271235
ctx, cancel := context.WithCancel(ctrl.SetupSignalHandler())
272236
defer cancel()
273237

274-
if tlsProfileFetched {
238+
if tlsResult.ProfileFetched {
275239
watcher := &tlspkg.SecurityProfileWatcher{
276240
Client: mgr.GetClient(),
277-
InitialTLSProfileSpec: tlsProfile,
241+
InitialTLSProfileSpec: tlsResult.ProfileSpec,
278242
OnProfileChange: func(_ context.Context, _, _ configv1.TLSProfileSpec) {
279243
setupLog.Info("TLS profile changed, initiating shutdown to reload")
280244
cancel()
281245
},
282246
}
283-
if tlsAdherenceFetched {
284-
watcher.InitialTLSAdherencePolicy = tlsAdherence
247+
if tlsResult.AdherenceFetched {
248+
watcher.InitialTLSAdherencePolicy = tlsResult.AdherencePolicy
285249
watcher.OnAdherencePolicyChange = func(_ context.Context, _, _ configv1.TLSAdherencePolicy) {
286250
setupLog.Info("TLS adherence policy changed, initiating shutdown to reload")
287251
cancel()

0 commit comments

Comments
 (0)