@@ -52,7 +52,8 @@ func (feast *FeastServices) getServiceRepoConfig() (RepoConfig, error) {
5252}
5353
5454func (feast * FeastServices ) getLineageFeatureStoreYamlBase64 () (string , error ) {
55- repoConfig , err := feast .getLineageRepoConfig ()
55+ odhCaBundleExists := feast .GetCustomCertificatesBundle ().IsDefined
56+ repoConfig , err := feast .getLineageRepoConfig (odhCaBundleExists )
5657 if err != nil {
5758 return "" , err
5859 }
@@ -63,7 +64,7 @@ func (feast *FeastServices) getLineageFeatureStoreYamlBase64() (string, error) {
6364 return base64 .StdEncoding .EncodeToString (yamlBytes ), nil
6465}
6566
66- func (feast * FeastServices ) getLineageRepoConfig () (RepoConfig , error ) {
67+ func (feast * FeastServices ) getLineageRepoConfig (odhCaBundleExists bool ) (RepoConfig , error ) {
6768 cr := feast .Handler .FeatureStore
6869 applied := cr .Status .Applied
6970
@@ -108,6 +109,28 @@ func (feast *FeastServices) getLineageRepoConfig() (RepoConfig, error) {
108109 }
109110 }
110111
112+ // Apply auth configuration so the lineage server enforces the same
113+ // authentication as the other Feast servers. Without this the
114+ // standalone lineage endpoints would be unauthenticated even when
115+ // Kubernetes or OIDC auth is configured on the FeatureStore CR.
116+ if applied .AuthzConfig != nil && applied .AuthzConfig .OidcAuthz != nil {
117+ authzConfig , err := resolveOidcServerAuthzConfig (applied .AuthzConfig .OidcAuthz , feast .extractConfigFromSecret , odhCaBundleExists )
118+ if err != nil {
119+ return repoConfig , err
120+ }
121+ repoConfig .AuthzConfig = authzConfig
122+ } else if applied .AuthzConfig != nil {
123+ if applied .AuthzConfig .NoAuth != nil && * applied .AuthzConfig .NoAuth {
124+ repoConfig .AuthzConfig = AuthzConfig {Type : NoAuthAuthType }
125+ } else if applied .AuthzConfig .KubernetesAuthz != nil {
126+ repoConfig .AuthzConfig = AuthzConfig {Type : KubernetesAuthType }
127+ } else {
128+ return repoConfig , fmt .Errorf ("authzConfig must specify OIDC, Kubernetes, or no-auth" )
129+ }
130+ } else {
131+ repoConfig .AuthzConfig = defaultAuthzConfig
132+ }
133+
111134 return repoConfig , nil
112135}
113136
@@ -190,61 +213,77 @@ func getBaseServiceRepoConfig(
190213 }
191214 appliedSpec := featureStore .Status .Applied
192215 if appliedSpec .AuthzConfig != nil && appliedSpec .AuthzConfig .OidcAuthz != nil {
193- repoConfig .AuthzConfig = AuthzConfig {Type : OidcAuthType }
194- oidcAuthz := appliedSpec .AuthzConfig .OidcAuthz
195- oidcParameters := map [string ]interface {}{}
196-
197- var secretProperties map [string ]interface {}
198- if oidcAuthz .SecretRef != nil {
199- var err error
200- secretProperties , err = secretExtractionFunc ("" , oidcAuthz .SecretRef .Name , oidcAuthz .SecretKeyName )
201- if err != nil {
202- return repoConfig , err
203- }
204- for _ , prop := range OidcOptionalSecretProperties {
205- if val , exists := secretProperties [string (prop )]; exists {
206- // Secret values are YAML-parsed on extraction, so an
207- // all-digits audience or issuer arrives as an int and
208- // would render unquoted, which the SDK's OidcAuthConfig
209- // rejects (Optional[str]). Coerce the claim keys back to
210- // strings; the five original keys keep their historical
211- // typing.
212- if prop == OidcAudience || prop == OidcIssuer {
213- if _ , isString := val .(string ); ! isString {
214- val = fmt .Sprintf ("%v" , val )
215- }
216- }
217- oidcParameters [string (prop )] = val
218- }
219- }
220- }
221-
222- discoveryUrl , err := resolveAuthDiscoveryUrl (oidcAuthz , secretProperties )
216+ authzConfig , err := resolveOidcServerAuthzConfig (appliedSpec .AuthzConfig .OidcAuthz , secretExtractionFunc , odhCaBundleExists )
223217 if err != nil {
224218 return repoConfig , err
225219 }
226- oidcParameters [string (OidcAuthDiscoveryUrl )] = discoveryUrl
227-
228- if oidcAuthz .VerifySSL != nil {
229- oidcParameters [string (OidcVerifySsl )] = * oidcAuthz .VerifySSL
230- }
231- if oidcAuthz .JwksCacheLifespanSeconds != nil {
232- oidcParameters [string (OidcJwksCacheLifespanSeconds )] = * oidcAuthz .JwksCacheLifespanSeconds
233- }
234- if oidcAuthz .JwksRequestTimeoutSeconds != nil {
235- oidcParameters [string (OidcJwksRequestTimeoutSeconds )] = * oidcAuthz .JwksRequestTimeoutSeconds
236- }
237- if caCertPath := resolveOidcCACertPath (oidcAuthz , odhCaBundleExists ); caCertPath != "" {
238- oidcParameters [string (OidcCaCertPath )] = caCertPath
239- }
240- repoConfig .AuthzConfig .OidcParameters = oidcParameters
220+ repoConfig .AuthzConfig = authzConfig
241221 } else {
242222 repoConfig .AuthzConfig = clientRepoConfig .AuthzConfig
243223 }
244224
245225 return repoConfig , nil
246226}
247227
228+ // resolveOidcServerAuthzConfig builds the server-side OIDC AuthzConfig from the
229+ // CR's OidcAuthz spec. Used by both getBaseServiceRepoConfig and
230+ // getLineageRepoConfig to avoid duplicating the secret extraction, discovery
231+ // URL resolution, and TLS/CA logic.
232+ func resolveOidcServerAuthzConfig (
233+ oidcAuthz * feastdevv1.OidcAuthz ,
234+ secretExtractionFunc func (storeType string , secretRef string , secretKeyName string ) (map [string ]interface {}, error ),
235+ odhCaBundleExists bool ,
236+ ) (AuthzConfig , error ) {
237+ authzConfig := AuthzConfig {Type : OidcAuthType }
238+ oidcParameters := map [string ]interface {}{}
239+
240+ var secretProperties map [string ]interface {}
241+ if oidcAuthz .SecretRef != nil {
242+ var err error
243+ secretProperties , err = secretExtractionFunc ("" , oidcAuthz .SecretRef .Name , oidcAuthz .SecretKeyName )
244+ if err != nil {
245+ return authzConfig , err
246+ }
247+ for _ , prop := range OidcOptionalSecretProperties {
248+ if val , exists := secretProperties [string (prop )]; exists {
249+ // Secret values are YAML-parsed on extraction, so an
250+ // all-digits audience or issuer arrives as an int and
251+ // would render unquoted, which the SDK's OidcAuthConfig
252+ // rejects (Optional[str]). Coerce the claim keys back to
253+ // strings; the five original keys keep their historical
254+ // typing.
255+ if prop == OidcAudience || prop == OidcIssuer {
256+ if _ , isString := val .(string ); ! isString {
257+ val = fmt .Sprintf ("%v" , val )
258+ }
259+ }
260+ oidcParameters [string (prop )] = val
261+ }
262+ }
263+ }
264+
265+ discoveryUrl , err := resolveAuthDiscoveryUrl (oidcAuthz , secretProperties )
266+ if err != nil {
267+ return authzConfig , err
268+ }
269+ oidcParameters [string (OidcAuthDiscoveryUrl )] = discoveryUrl
270+
271+ if oidcAuthz .VerifySSL != nil {
272+ oidcParameters [string (OidcVerifySsl )] = * oidcAuthz .VerifySSL
273+ }
274+ if oidcAuthz .JwksCacheLifespanSeconds != nil {
275+ oidcParameters [string (OidcJwksCacheLifespanSeconds )] = * oidcAuthz .JwksCacheLifespanSeconds
276+ }
277+ if oidcAuthz .JwksRequestTimeoutSeconds != nil {
278+ oidcParameters [string (OidcJwksRequestTimeoutSeconds )] = * oidcAuthz .JwksRequestTimeoutSeconds
279+ }
280+ if caCertPath := resolveOidcCACertPath (oidcAuthz , odhCaBundleExists ); caCertPath != "" {
281+ oidcParameters [string (OidcCaCertPath )] = caCertPath
282+ }
283+ authzConfig .OidcParameters = oidcParameters
284+ return authzConfig , nil
285+ }
286+
248287// resolveAuthDiscoveryUrl determines the OIDC discovery URL from the first available source.
249288// Priority: CR issuerUrl > Secret auth_discovery_url > OIDC_ISSUER_URL env var.
250289func resolveAuthDiscoveryUrl (oidcAuthz * feastdevv1.OidcAuthz , secretProperties map [string ]interface {}) (string , error ) {
0 commit comments