Skip to content

Commit d4d7b0d

Browse files
committed
feat: Operator mounts the odh-trusted-ca-bundle configmap when deployed on RHOAI or ODH
Signed-off-by: Abdul Hameed <[email protected]>
1 parent 2f3bcf5 commit d4d7b0d

File tree

5 files changed

+215
-9
lines changed

5 files changed

+215
-9
lines changed

infra/feast-operator/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ require (
2121
github.com/cespare/xxhash/v2 v2.2.0 // indirect
2222
github.com/davecgh/go-spew v1.1.1 // indirect
2323
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
24+
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
2425
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
2526
github.com/felixge/httpsnoop v1.0.3 // indirect
2627
github.com/fsnotify/fsnotify v1.7.0 // indirect

infra/feast-operator/internal/controller/featurestore_controller_tls_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,3 +441,132 @@ var _ = Describe("FeatureStore Controller - Feast service TLS", func() {
441441
})
442442
})
443443
})
444+
445+
var _ = Describe("Test mountCustomCABundle functionality", func() {
446+
const resourceName = "test-cabundle"
447+
const feastProject = "test_cabundle"
448+
const configMapName = "odh-trusted-ca-bundle"
449+
const caBundleAnnotation = "config.openshift.io/inject-trusted-cabundle"
450+
const tlsPathCustomCABundle = "/etc/pki/tls/custom-certs/ca-bundle.crt"
451+
452+
ctx := context.Background()
453+
nsName := types.NamespacedName{
454+
Name: resourceName,
455+
Namespace: "default",
456+
}
457+
458+
fs := &feastdevv1alpha1.FeatureStore{
459+
ObjectMeta: metav1.ObjectMeta{
460+
Name: resourceName,
461+
Namespace: nsName.Namespace,
462+
},
463+
Spec: feastdevv1alpha1.FeatureStoreSpec{
464+
FeastProject: feastProject,
465+
Services: &feastdevv1alpha1.FeatureStoreServices{
466+
Registry: &feastdevv1alpha1.Registry{Local: &feastdevv1alpha1.LocalRegistryConfig{Server: &feastdevv1alpha1.ServerConfigs{}}},
467+
OnlineStore: &feastdevv1alpha1.OnlineStore{Server: &feastdevv1alpha1.ServerConfigs{}},
468+
OfflineStore: &feastdevv1alpha1.OfflineStore{Server: &feastdevv1alpha1.ServerConfigs{}},
469+
UI: &feastdevv1alpha1.ServerConfigs{},
470+
},
471+
},
472+
}
473+
474+
AfterEach(func() {
475+
By("cleaning up FeatureStore and ConfigMap")
476+
_ = k8sClient.Delete(ctx, &feastdevv1alpha1.FeatureStore{ObjectMeta: metav1.ObjectMeta{Name: resourceName, Namespace: nsName.Namespace}})
477+
_ = k8sClient.Delete(ctx, &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: configMapName, Namespace: nsName.Namespace}})
478+
})
479+
480+
It("should mount CA bundle volume and mounts in containers when ConfigMap exists", func() {
481+
cm := &corev1.ConfigMap{
482+
ObjectMeta: metav1.ObjectMeta{
483+
Name: configMapName,
484+
Namespace: nsName.Namespace,
485+
Labels: map[string]string{
486+
caBundleAnnotation: "true",
487+
},
488+
},
489+
}
490+
Expect(k8sClient.Create(ctx, cm.DeepCopy())).To(Succeed())
491+
Expect(k8sClient.Create(ctx, fs.DeepCopy())).To(Succeed())
492+
493+
controllerReconciler := &FeatureStoreReconciler{
494+
Client: k8sClient,
495+
Scheme: k8sClient.Scheme(),
496+
}
497+
498+
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
499+
NamespacedName: nsName,
500+
})
501+
Expect(err).NotTo(HaveOccurred())
502+
503+
resource := &feastdevv1alpha1.FeatureStore{}
504+
err = k8sClient.Get(ctx, nsName, resource)
505+
Expect(err).NotTo(HaveOccurred())
506+
507+
feast := services.FeastServices{
508+
Handler: handler.FeastHandler{
509+
Client: controllerReconciler.Client,
510+
Context: ctx,
511+
Scheme: controllerReconciler.Scheme,
512+
FeatureStore: resource,
513+
},
514+
}
515+
516+
deploy := &appsv1.Deployment{}
517+
objMeta := feast.GetObjectMeta()
518+
err = k8sClient.Get(ctx, types.NamespacedName{
519+
Name: objMeta.Name,
520+
Namespace: objMeta.Namespace,
521+
}, deploy)
522+
Expect(err).NotTo(HaveOccurred())
523+
524+
Expect(deploy.Spec.Template.Spec.Volumes).To(ContainElement(HaveField("Name", configMapName)))
525+
for _, container := range deploy.Spec.Template.Spec.Containers {
526+
Expect(container.VolumeMounts).To(ContainElement(SatisfyAll(
527+
HaveField("Name", configMapName),
528+
HaveField("MountPath", tlsPathCustomCABundle),
529+
)))
530+
}
531+
})
532+
533+
It("should not mount CA bundle volume or container mounts when ConfigMap is absent", func() {
534+
Expect(k8sClient.Create(ctx, fs.DeepCopy())).To(Succeed())
535+
536+
controllerReconciler := &FeatureStoreReconciler{
537+
Client: k8sClient,
538+
Scheme: k8sClient.Scheme(),
539+
}
540+
541+
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
542+
NamespacedName: nsName,
543+
})
544+
Expect(err).NotTo(HaveOccurred())
545+
546+
resource := &feastdevv1alpha1.FeatureStore{}
547+
err = k8sClient.Get(ctx, nsName, resource)
548+
Expect(err).NotTo(HaveOccurred())
549+
550+
feast := services.FeastServices{
551+
Handler: handler.FeastHandler{
552+
Client: controllerReconciler.Client,
553+
Context: ctx,
554+
Scheme: controllerReconciler.Scheme,
555+
FeatureStore: resource,
556+
},
557+
}
558+
559+
deploy := &appsv1.Deployment{}
560+
objMeta := feast.GetObjectMeta()
561+
err = k8sClient.Get(ctx, types.NamespacedName{
562+
Name: objMeta.Name,
563+
Namespace: objMeta.Namespace,
564+
}, deploy)
565+
Expect(err).NotTo(HaveOccurred())
566+
567+
Expect(deploy.Spec.Template.Spec.Volumes).NotTo(ContainElement(HaveField("Name", configMapName)))
568+
for _, container := range deploy.Spec.Template.Spec.Containers {
569+
Expect(container.VolumeMounts).NotTo(ContainElement(HaveField("Name", configMapName)))
570+
}
571+
})
572+
})

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,16 @@ const (
3434
DefaultOnlineStorePath = "online_store.db"
3535
svcDomain = ".svc.cluster.local"
3636

37-
HttpPort = 80
38-
HttpsPort = 443
39-
HttpScheme = "http"
40-
HttpsScheme = "https"
41-
tlsPath = "/tls/"
42-
tlsNameSuffix = "-tls"
37+
HttpPort = 80
38+
HttpsPort = 443
39+
HttpScheme = "http"
40+
HttpsScheme = "https"
41+
tlsPath = "/tls/"
42+
tlsPathCustomCABundle = "/etc/pki/tls/custom-certs/ca-bundle.crt"
43+
tlsNameSuffix = "-tls"
44+
45+
caBundleAnnotation = "config.openshift.io/inject-trusted-cabundle"
46+
caBundleName = "odh-trusted-ca-bundle"
4347

4448
DefaultOfflineStorageRequest = "20Gi"
4549
DefaultOnlineStorageRequest = "5Gi"
@@ -268,3 +272,10 @@ type deploymentSettings struct {
268272
TargetHttpPort int32
269273
TargetHttpsPort int32
270274
}
275+
276+
// CustomCertificatesBundle represents a custom CA bundle configuration
277+
type CustomCertificatesBundle struct {
278+
IsDefined bool
279+
VolumeName string
280+
ConfigMapName string
281+
}

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ package services
1919
import (
2020
"strconv"
2121

22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
24+
"sigs.k8s.io/controller-runtime/pkg/log"
25+
2226
feastdevv1alpha1 "github.com/feast-dev/feast/infra/feast-operator/api/v1alpha1"
2327
corev1 "k8s.io/api/core/v1"
2428
)
@@ -184,6 +188,7 @@ func (feast *FeastServices) mountTlsConfigs(podSpec *corev1.PodSpec) {
184188
feast.mountTlsConfig(OfflineFeastType, podSpec)
185189
feast.mountTlsConfig(OnlineFeastType, podSpec)
186190
feast.mountTlsConfig(UIFeastType, podSpec)
191+
feast.mountCustomCABundle(podSpec)
187192
}
188193

189194
func (feast *FeastServices) mountTlsConfig(feastType FeastServiceType, podSpec *corev1.PodSpec) {
@@ -229,6 +234,63 @@ func mountTlsRemoteRegistryConfig(podSpec *corev1.PodSpec, tls *feastdevv1alpha1
229234
}
230235
}
231236

237+
func (feast *FeastServices) mountCustomCABundle(podSpec *corev1.PodSpec) {
238+
customCaBundle := feast.GetCustomCertificatesBundle()
239+
if customCaBundle.IsDefined {
240+
podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{
241+
Name: customCaBundle.VolumeName,
242+
VolumeSource: corev1.VolumeSource{
243+
ConfigMap: &corev1.ConfigMapVolumeSource{
244+
LocalObjectReference: corev1.LocalObjectReference{Name: customCaBundle.ConfigMapName},
245+
},
246+
},
247+
})
248+
249+
for i := range podSpec.Containers {
250+
podSpec.Containers[i].VolumeMounts = append(podSpec.Containers[i].VolumeMounts, corev1.VolumeMount{
251+
Name: customCaBundle.VolumeName,
252+
MountPath: tlsPathCustomCABundle,
253+
ReadOnly: true,
254+
SubPath: "ca-bundle.crt",
255+
})
256+
}
257+
258+
log.FromContext(feast.Handler.Context).Info("Mounted custom CA bundle ConfigMap to Feast pods.")
259+
}
260+
}
261+
262+
// GetCustomCertificatesBundle retrieves the custom CA bundle ConfigMap if it exists when deployed with RHOAI or ODH
263+
func (feast *FeastServices) GetCustomCertificatesBundle() CustomCertificatesBundle {
264+
var customCertificatesBundle CustomCertificatesBundle
265+
configMapList := &corev1.ConfigMapList{}
266+
labelSelector := client.MatchingLabels{caBundleAnnotation: "true"}
267+
268+
err := feast.Handler.Client.List(
269+
feast.Handler.Context,
270+
configMapList,
271+
client.InNamespace(feast.Handler.FeatureStore.Namespace),
272+
labelSelector,
273+
)
274+
if err != nil {
275+
log.FromContext(feast.Handler.Context).Error(err, "Error listing ConfigMaps. Not using custom CA bundle.")
276+
return customCertificatesBundle
277+
}
278+
279+
// Check if caBundleName exists
280+
for _, cm := range configMapList.Items {
281+
if cm.Name == caBundleName {
282+
log.FromContext(feast.Handler.Context).Info("Found trusted CA bundle ConfigMap. Using custom CA bundle.")
283+
customCertificatesBundle.IsDefined = true
284+
customCertificatesBundle.VolumeName = caBundleName
285+
customCertificatesBundle.ConfigMapName = caBundleName
286+
return customCertificatesBundle
287+
}
288+
}
289+
290+
log.FromContext(feast.Handler.Context).Info("CA bundle ConfigMap named '" + caBundleName + "' not found. Not using custom CA bundle.")
291+
return customCertificatesBundle
292+
}
293+
232294
func getPortStr(tls *feastdevv1alpha1.TlsConfigs) string {
233295
if tls.IsTLS() {
234296
return strconv.Itoa(HttpsPort)

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ limitations under the License.
1717
package services
1818

1919
import (
20-
. "github.com/onsi/ginkgo/v2"
21-
. "github.com/onsi/gomega"
20+
"context"
2221

2322
feastdevv1alpha1 "github.com/feast-dev/feast/infra/feast-operator/api/v1alpha1"
2423
"github.com/feast-dev/feast/infra/feast-operator/internal/controller/handler"
24+
. "github.com/onsi/ginkgo/v2"
25+
. "github.com/onsi/gomega"
2526
corev1 "k8s.io/api/core/v1"
2627
"k8s.io/apimachinery/pkg/runtime"
2728
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
@@ -46,8 +47,10 @@ var _ = Describe("TLS Config", func() {
4647
// registry server w/o tls
4748
feast := FeastServices{
4849
Handler: handler.FeastHandler{
49-
FeatureStore: minimalFeatureStore(),
50+
Client: k8sClient,
5051
Scheme: scheme,
52+
Context: context.TODO(),
53+
FeatureStore: minimalFeatureStore(),
5154
},
5255
}
5356
feast.Handler.FeatureStore.Spec.Services = &feastdevv1alpha1.FeatureStoreServices{

0 commit comments

Comments
 (0)