From 690cc372d60118f8d66acc7720748ae246a2016c Mon Sep 17 00:00:00 2001 From: Sri Roopa Ramesh Babu Date: Tue, 23 Dec 2025 11:51:58 -0500 Subject: [PATCH] Adding key field to proxyCA config. --- api/v1alpha1/olsconfig_types.go | 20 ++++- api/v1alpha1/zz_generated.deepcopy.go | 17 +++- .../bases/ols.openshift.io_olsconfigs.yaml | 23 +++--- internal/controller/appserver/assets.go | 11 ++- internal/controller/appserver/assets_test.go | 51 +++++++++++- internal/controller/appserver/deployment.go | 32 ++++++-- internal/controller/appserver/reconciler.go | 9 ++- .../controller/appserver/reconciler_test.go | 8 +- internal/controller/lcore/config.go | 24 +++++- internal/controller/lcore/deployment.go | 79 +++++++++++++++++++ internal/controller/lcore/reconciler.go | 10 ++- internal/controller/lcore/reconciler_test.go | 9 ++- internal/controller/olsconfig_helpers_test.go | 2 +- internal/controller/utils/constants.go | 2 + internal/controller/utils/utils.go | 33 ++++++-- test/e2e/proxy_test.go | 4 +- 16 files changed, 291 insertions(+), 43 deletions(-) diff --git a/api/v1alpha1/olsconfig_types.go b/api/v1alpha1/olsconfig_types.go index bf10218c2..f91067570 100644 --- a/api/v1alpha1/olsconfig_types.go +++ b/api/v1alpha1/olsconfig_types.go @@ -505,9 +505,25 @@ type ProxyConfig struct { // +kubebuilder:validation:Pattern=`^https?://.*$` // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Proxy URL" ProxyURL string `json:"proxyURL,omitempty"` - // The configmap holding proxy CA certificate + // The configmap and key holding proxy CA certificate. + // The key is optional and defaults to "proxy-ca.crt" for backward compatibility. + // If you use a different key name in your ConfigMap, specify it in the ConfigMapKeySelector. // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Proxy CA Certificate" - ProxyCACertificateRef *corev1.LocalObjectReference `json:"proxyCACertificate,omitempty"` + ProxyCACertificateRef *ProxyCACertConfigMapRef `json:"proxyCACertificate,omitempty"` +} + +// ProxyCACertConfigMapRef references a ConfigMap containing the proxy CA certificate. +// Provides backward compatibility by making the key field optional with a default value. +type ProxyCACertConfigMapRef struct { + // Name of the ConfigMap containing the proxy CA certificate + // +kubebuilder:validation:Required + // +required + Name string `json:"name"` + // Key in the ConfigMap that contains the proxy CA certificate. + // Defaults to "proxy-ca.crt" if not specified. + // +kubebuilder:default="proxy-ca.crt" + // +optional + Key string `json:"key,omitempty"` } // MCPServer defines the settings for a single MCP server. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3e5349ef1..dee0c4c68 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -498,12 +498,27 @@ func (in *ProviderSpec) DeepCopy() *ProviderSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxyCACertConfigMapRef) DeepCopyInto(out *ProxyCACertConfigMapRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxyCACertConfigMapRef. +func (in *ProxyCACertConfigMapRef) DeepCopy() *ProxyCACertConfigMapRef { + if in == nil { + return nil + } + out := new(ProxyCACertConfigMapRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProxyConfig) DeepCopyInto(out *ProxyConfig) { *out = *in if in.ProxyCACertificateRef != nil { in, out := &in.ProxyCACertificateRef, &out.ProxyCACertificateRef - *out = new(corev1.LocalObjectReference) + *out = new(ProxyCACertConfigMapRef) **out = **in } } diff --git a/config/crd/bases/ols.openshift.io_olsconfigs.yaml b/config/crd/bases/ols.openshift.io_olsconfigs.yaml index 1c853f946..a48cfe044 100644 --- a/config/crd/bases/ols.openshift.io_olsconfigs.yaml +++ b/config/crd/bases/ols.openshift.io_olsconfigs.yaml @@ -4346,19 +4346,24 @@ spec: such as LLM providers. properties: proxyCACertificate: - description: The configmap holding proxy CA certificate + description: |- + The configmap and key holding proxy CA certificate. + The key is optional and defaults to "proxy-ca.crt" for backward compatibility. + If you use a different key name in your ConfigMap, specify it in the ConfigMapKeySelector. properties: - name: - default: "" + key: + default: proxy-ca.crt description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + Key in the ConfigMap that contains the proxy CA certificate. + Defaults to "proxy-ca.crt" if not specified. type: string + name: + description: Name of the ConfigMap containing the proxy + CA certificate + type: string + required: + - name type: object - x-kubernetes-map-type: atomic proxyURL: description: |- Proxy URL, e.g. https://proxy.example.com:8080 diff --git a/internal/controller/appserver/assets.go b/internal/controller/appserver/assets.go index a923a5439..6a3864188 100644 --- a/internal/controller/appserver/assets.go +++ b/internal/controller/appserver/assets.go @@ -181,12 +181,15 @@ func GenerateOLSConfigMap(r reconciler.Reconciler, ctx context.Context, cr *olsv ProxyURL: cr.Spec.OLSConfig.ProxyConfig.ProxyURL, ProxyCACertPath: "", } - if cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef != nil && cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef.Name != "" { - err := validateCertificateInConfigMap(r, ctx, cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef.Name, utils.ProxyCACertFileName) + proxyCACertRef := cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef + cmName := utils.GetProxyCACertConfigMapName(proxyCACertRef) + if cmName != "" { + certKey := utils.GetProxyCACertKey(proxyCACertRef) + err := validateCertificateInConfigMap(r, ctx, cmName, certKey) if err != nil { - return nil, fmt.Errorf("failed to validate proxy CA certificate %s: %w", cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef.Name, err) + return nil, fmt.Errorf("failed to validate proxy CA certificate %s: %w", cmName, err) } - proxyConfig.ProxyCACertPath = path.Join(utils.OLSAppCertsMountRoot, utils.ProxyCACertVolumeName, utils.ProxyCACertFileName) + proxyConfig.ProxyCACertPath = path.Join(utils.OLSAppCertsMountRoot, utils.ProxyCACertVolumeName, certKey) } } diff --git a/internal/controller/appserver/assets_test.go b/internal/controller/appserver/assets_test.go index 61eeba926..ce20d52dc 100644 --- a/internal/controller/appserver/assets_test.go +++ b/internal/controller/appserver/assets_test.go @@ -1648,8 +1648,9 @@ user_data_collector_config: {} cr.Spec.OLSConfig.ProxyConfig = &olsv1alpha1.ProxyConfig{ ProxyURL: "https://proxy.example.com:8080", - ProxyCACertificateRef: &corev1.LocalObjectReference{ + ProxyCACertificateRef: &olsv1alpha1.ProxyCACertConfigMapRef{ Name: caConfigMapName, + // No Key specified - tests backward compatibility }, } @@ -1668,6 +1669,12 @@ user_data_collector_config: {} Name: caConfigMapName, }, DefaultMode: &defaultVolumeMode, + Items: []corev1.KeyToPath{ + { + Key: utils.ProxyCACertFileName, + Path: utils.ProxyCACertFileName, + }, + }, }, }, })) @@ -1687,14 +1694,54 @@ user_data_collector_config: {} cr.Spec.OLSConfig.ProxyConfig = &olsv1alpha1.ProxyConfig{ ProxyURL: "https://proxy.example.com:8080", - ProxyCACertificateRef: &corev1.LocalObjectReference{ + ProxyCACertificateRef: &olsv1alpha1.ProxyCACertConfigMapRef{ Name: caConfigMapName, + // No Key specified - tests backward compatibility }, } _, err = GenerateOLSConfigMap(testReconcilerInstance, ctx, cr) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to validate proxy CA certificate")) }) + + It("should support custom ConfigMap key for proxy CA certificate", func() { + customKey := "service-ca.crt" + proxyCACm.Data = map[string]string{ + customKey: utils.TestCACert, + } + err := testReconcilerInstance.Update(ctx, proxyCACm) + Expect(err).NotTo(HaveOccurred()) + + cr.Spec.OLSConfig.ProxyConfig = &olsv1alpha1.ProxyConfig{ + ProxyURL: "https://proxy.example.com:8080", + ProxyCACertificateRef: &olsv1alpha1.ProxyCACertConfigMapRef{ + Name: caConfigMapName, + Key: customKey, + }, + } + + // Test OLS ConfigMap has correct path with custom key + olsCm, err := GenerateOLSConfigMap(testReconcilerInstance, ctx, cr) + Expect(err).NotTo(HaveOccurred()) + Expect(olsCm.Data[utils.OLSConfigFilename]).To(ContainSubstring( + fmt.Sprintf("proxy_ca_cert_path: /etc/certs/proxy-ca/%s", customKey))) + + // Test deployment has Items projection with custom key + dep, err := GenerateOLSDeployment(testReconcilerInstance, cr) + Expect(err).NotTo(HaveOccurred()) + + var proxyCAVolume *corev1.Volume + for i := range dep.Spec.Template.Spec.Volumes { + if dep.Spec.Template.Spec.Volumes[i].Name == utils.ProxyCACertVolumeName { + proxyCAVolume = &dep.Spec.Template.Spec.Volumes[i] + break + } + } + Expect(proxyCAVolume).NotTo(BeNil()) + Expect(proxyCAVolume.ConfigMap.Items).To(HaveLen(1)) + Expect(proxyCAVolume.ConfigMap.Items[0].Key).To(Equal(customKey)) + Expect(proxyCAVolume.ConfigMap.Items[0].Path).To(Equal(customKey)) + }) }) }) diff --git a/internal/controller/appserver/deployment.go b/internal/controller/appserver/deployment.go index bb3ec0156..df48c6da4 100644 --- a/internal/controller/appserver/deployment.go +++ b/internal/controller/appserver/deployment.go @@ -263,25 +263,43 @@ func GenerateOLSDeployment(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) ( // User provided CA certificates - create both volumes and volume mounts in single pass _ = utils.ForEachExternalConfigMap(cr, func(name, source string) error { var volumeName, mountPath string + var volumeSource corev1.VolumeSource + switch source { case "additional-ca": volumeName = utils.AdditionalCAVolumeName mountPath = UserCAMountPath + volumeSource = corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: name}, + DefaultMode: &volumeDefaultMode, + }, + } case "proxy-ca": volumeName = utils.ProxyCACertVolumeName mountPath = path.Join(utils.OLSAppCertsMountRoot, utils.ProxyCACertVolumeName) + // Get the key to use for the certificate + proxyCACertRef := cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef + certKey := utils.GetProxyCACertKey(proxyCACertRef) + volumeSource = corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: name}, + DefaultMode: &volumeDefaultMode, + Items: []corev1.KeyToPath{ + { + Key: certKey, + Path: certKey, // Mount with same filename as key + }, + }, + }, + } default: return nil } volumes = append(volumes, corev1.Volume{ - Name: volumeName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: name}, - DefaultMode: &volumeDefaultMode, - }, - }, + Name: volumeName, + VolumeSource: volumeSource, }) volumeMounts = append(volumeMounts, corev1.VolumeMount{ diff --git a/internal/controller/appserver/reconciler.go b/internal/controller/appserver/reconciler.go index f8645244d..60e03aaec 100644 --- a/internal/controller/appserver/reconciler.go +++ b/internal/controller/appserver/reconciler.go @@ -245,8 +245,15 @@ func reconcileProxyCAConfigMap(r reconciler.Reconciler, ctx context.Context, cr return nil } + cmName := utils.GetProxyCACertConfigMapName(cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef) + if cmName == "" { + // no proxy CA certs, skip + r.GetLogger().Info("Proxy CA not configured, reconciliation skipped") + return nil + } + cm := &corev1.ConfigMap{} - err := r.Get(ctx, client.ObjectKey{Name: cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef.Name, Namespace: r.GetNamespace()}, cm) + err := r.Get(ctx, client.ObjectKey{Name: cmName, Namespace: r.GetNamespace()}, cm) if err != nil { return fmt.Errorf("%s: %w", utils.ErrGetProxyCACM, err) } diff --git a/internal/controller/appserver/reconciler_test.go b/internal/controller/appserver/reconciler_test.go index 4e712bdbc..ef0f3882e 100644 --- a/internal/controller/appserver/reconciler_test.go +++ b/internal/controller/appserver/reconciler_test.go @@ -981,7 +981,7 @@ var _ = Describe("App server reconciliator", Ordered, func() { By("Set up a proxy CA cert") cr.Spec.OLSConfig.ProxyConfig = &olsv1alpha1.ProxyConfig{ ProxyURL: "https://proxy.example.com:8080", - ProxyCACertificateRef: &corev1.LocalObjectReference{ + ProxyCACertificateRef: &olsv1alpha1.ProxyCACertConfigMapRef{ Name: cmCACertName, }, } @@ -1008,6 +1008,12 @@ var _ = Describe("App server reconciliator", Ordered, func() { Name: cmCACertName, }, DefaultMode: &volumeDefaultMode, + Items: []corev1.KeyToPath{ + { + Key: utils.ProxyCACertFileName, + Path: utils.ProxyCACertFileName, + }, + }, }, }, }, diff --git a/internal/controller/lcore/config.go b/internal/controller/lcore/config.go index f5a6526fe..17a412467 100644 --- a/internal/controller/lcore/config.go +++ b/internal/controller/lcore/config.go @@ -635,7 +635,7 @@ func buildLCoreServiceConfig(_ reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) // color_log: enable colored logs for DEBUG, disable for production (INFO+) colorLog := logLevel == olsv1alpha1.LogLevelDebug - return map[string]interface{}{ + serviceConfig := map[string]interface{}{ "host": "0.0.0.0", "port": utils.OLSAppServerContainerPort, "auth_enabled": false, @@ -649,6 +649,28 @@ func buildLCoreServiceConfig(_ reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) "tls_key_path": "/etc/certs/lightspeed-tls/tls.key", }, } + + // Add proxy configuration if specified + if cr.Spec.OLSConfig.ProxyConfig != nil { + proxyConfigMap := map[string]interface{}{} + + if cr.Spec.OLSConfig.ProxyConfig.ProxyURL != "" { + proxyConfigMap["proxy_url"] = cr.Spec.OLSConfig.ProxyConfig.ProxyURL + } + + proxyCACertRef := cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef + cmName := utils.GetProxyCACertConfigMapName(proxyCACertRef) + if cmName != "" { + certKey := utils.GetProxyCACertKey(proxyCACertRef) + proxyConfigMap["proxy_ca_cert_path"] = "/etc/certs/" + utils.ProxyCACertVolumeName + "/" + certKey + } + + if len(proxyConfigMap) > 0 { + serviceConfig["proxy_config"] = proxyConfigMap + } + } + + return serviceConfig } func buildLCoreLlamaStackConfig(_ reconciler.Reconciler, _ *olsv1alpha1.OLSConfig) map[string]interface{} { diff --git a/internal/controller/lcore/deployment.go b/internal/controller/lcore/deployment.go index e51f5b791..d4c23c973 100644 --- a/internal/controller/lcore/deployment.go +++ b/internal/controller/lcore/deployment.go @@ -218,6 +218,16 @@ func GenerateLCoreDeployment(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) lcoreConfigMapResourceVersion, _ := utils.GetConfigMapResourceVersion(r, ctx, utils.LCoreConfigCmName) llamaStackConfigMapResourceVersion, _ := utils.GetConfigMapResourceVersion(r, ctx, utils.LlamaStackConfigCmName) + // Get Proxy CA ConfigMap ResourceVersion if proxy is configured + var proxyCACMResourceVersion string + if cr.Spec.OLSConfig.ProxyConfig != nil { + proxyCACertRef := cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef + cmName := utils.GetProxyCACertConfigMapName(proxyCACertRef) + if cmName != "" { + proxyCACMResourceVersion, _ = utils.GetConfigMapResourceVersion(r, ctx, cmName) + } + } + // Labels for the deployment labels := map[string]string{ "app": "lightspeed-stack", @@ -296,6 +306,30 @@ func GenerateLCoreDeployment(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) } } + // Proxy CA ConfigMap volume (for proxy certificate verification) + if cr.Spec.OLSConfig.ProxyConfig != nil { + proxyCACertRef := cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef + cmName := utils.GetProxyCACertConfigMapName(proxyCACertRef) + if cmName != "" { + certKey := utils.GetProxyCACertKey(proxyCACertRef) + volumes = append(volumes, corev1.Volume{ + Name: utils.ProxyCACertVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{Name: cmName}, + DefaultMode: &volumeDefaultMode, + Items: []corev1.KeyToPath{ + { + Key: certKey, + Path: certKey, + }, + }, + }, + }, + }) + } + } + // llama-stack container volume mounts llamaStackVolumeMounts := []corev1.VolumeMount{ { @@ -318,6 +352,19 @@ func GenerateLCoreDeployment(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) utils.GetPostgresCAVolumeMount("/etc/certs/postgres-ca"), } + // Proxy CA ConfigMap mount (for proxy certificate verification) + if cr.Spec.OLSConfig.ProxyConfig != nil { + proxyCACertRef := cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef + cmName := utils.GetProxyCACertConfigMapName(proxyCACertRef) + if cmName != "" { + llamaStackVolumeMounts = append(llamaStackVolumeMounts, corev1.VolumeMount{ + Name: utils.ProxyCACertVolumeName, + MountPath: path.Join(utils.OLSAppCertsMountRoot, utils.ProxyCACertVolumeName), + ReadOnly: true, + }) + } + } + // User provided CA certificates - create both volumes and volume mounts in single pass _ = utils.ForEachExternalConfigMap(cr, func(name, source string) error { var volumeName, llamaStackMountPath string @@ -526,6 +573,19 @@ func GenerateLCoreDeployment(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) } lightspeedStackContainer.Resources = *lightspeedStackResources + // Proxy CA ConfigMap mount (for proxy certificate verification) + if cr.Spec.OLSConfig.ProxyConfig != nil { + proxyCACertRef := cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef + cmName := utils.GetProxyCACertConfigMapName(proxyCACertRef) + if cmName != "" { + lightspeedStackContainer.VolumeMounts = append(lightspeedStackContainer.VolumeMounts, corev1.VolumeMount{ + Name: utils.ProxyCACertVolumeName, + MountPath: path.Join(utils.OLSAppCertsMountRoot, utils.ProxyCACertVolumeName), + ReadOnly: true, + }) + } + } + deployment := appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: "lightspeed-stack-deployment", @@ -534,6 +594,7 @@ func GenerateLCoreDeployment(r reconciler.Reconciler, cr *olsv1alpha1.OLSConfig) Annotations: map[string]string{ utils.LCoreConfigMapResourceVersionAnnotation: lcoreConfigMapResourceVersion, utils.LlamaStackConfigMapResourceVersionAnnotation: llamaStackConfigMapResourceVersion, + utils.ProxyCACertHashKey: proxyCACMResourceVersion, }, }, Spec: appsv1.DeploymentSpec{ @@ -638,6 +699,17 @@ func updateLCoreDeployment(r reconciler.Reconciler, ctx context.Context, existin } } + // Check if Proxy CA ConfigMap ResourceVersion has changed + // Get the proxy CA ConfigMap name from OLSConfig if proxy is configured + // We need to fetch the CR to get the proxy config + // For now, we'll check if the annotation exists and has a value + storedProxyCACMVersion := existingDeployment.Annotations[utils.ProxyCACertHashKey] + // Get current proxy CA ConfigMap ResourceVersion from the desired deployment annotations + currentProxyCACMVersion := desiredDeployment.Annotations[utils.ProxyCACertHashKey] + if storedProxyCACMVersion != currentProxyCACMVersion { + changed = true + } + // If nothing changed, skip update if !changed { return nil @@ -645,8 +717,15 @@ func updateLCoreDeployment(r reconciler.Reconciler, ctx context.Context, existin // Apply changes - always update spec and annotations since something changed existingDeployment.Spec = desiredDeployment.Spec + + // Initialize annotations if nil + if existingDeployment.Annotations == nil { + existingDeployment.Annotations = make(map[string]string) + } + existingDeployment.Annotations[utils.LCoreConfigMapResourceVersionAnnotation] = desiredDeployment.Annotations[utils.LCoreConfigMapResourceVersionAnnotation] existingDeployment.Annotations[utils.LlamaStackConfigMapResourceVersionAnnotation] = desiredDeployment.Annotations[utils.LlamaStackConfigMapResourceVersionAnnotation] + existingDeployment.Annotations[utils.ProxyCACertHashKey] = desiredDeployment.Annotations[utils.ProxyCACertHashKey] r.GetLogger().Info("updating LCore deployment", "name", existingDeployment.Name) diff --git a/internal/controller/lcore/reconciler.go b/internal/controller/lcore/reconciler.go index 48065d1ad..262971d5a 100644 --- a/internal/controller/lcore/reconciler.go +++ b/internal/controller/lcore/reconciler.go @@ -291,14 +291,20 @@ func reconcileOLSAdditionalCAConfigMap(r reconciler.Reconciler, ctx context.Cont } func reconcileProxyCAConfigMap(r reconciler.Reconciler, ctx context.Context, cr *olsv1alpha1.OLSConfig) error { - if cr.Spec.OLSConfig.ProxyConfig == nil || cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef == nil { + if cr.Spec.OLSConfig.ProxyConfig == nil { + // no proxy CA certs, skip + r.GetLogger().Info("Proxy CA not configured, reconciliation skipped") + return nil + } + cmName := utils.GetProxyCACertConfigMapName(cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef) + if cmName == "" { // no proxy CA certs, skip r.GetLogger().Info("Proxy CA not configured, reconciliation skipped") return nil } cm := &corev1.ConfigMap{} - err := r.Get(ctx, client.ObjectKey{Name: cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef.Name, Namespace: r.GetNamespace()}, cm) + err := r.Get(ctx, client.ObjectKey{Name: cmName, Namespace: r.GetNamespace()}, cm) if err != nil { return fmt.Errorf("%s: %w", utils.ErrGetProxyCACM, err) } diff --git a/internal/controller/lcore/reconciler_test.go b/internal/controller/lcore/reconciler_test.go index f68d3040a..ef6bbbc88 100644 --- a/internal/controller/lcore/reconciler_test.go +++ b/internal/controller/lcore/reconciler_test.go @@ -222,12 +222,12 @@ var _ = Describe("LCore reconciliator", Ordered, func() { err = k8sClient.Update(ctx, cr) Expect(err).NotTo(HaveOccurred()) - By("Create a proxy CA ConfigMap") + By("Create a proxy CA ConfigMap with default key") cm := &corev1.ConfigMap{} cm.Name = proxyCACMName cm.Namespace = utils.OLSNamespaceDefault cm.Data = map[string]string{ - "ca-bundle.crt": "test-proxy-ca-cert-content", + utils.ProxyCACertFileName: "test-proxy-ca-cert-content", } err = k8sClient.Create(ctx, cm) Expect(err).NotTo(HaveOccurred()) @@ -242,13 +242,14 @@ var _ = Describe("LCore reconciliator", Ordered, func() { }) It("should annotate proxy CA configmap with watcher annotation", func() { - By("Set up a proxy CA cert in CR") + By("Set up a proxy CA cert in CR without specifying key (uses default)") err := k8sClient.Get(ctx, crNamespacedName, cr) Expect(err).NotTo(HaveOccurred()) cr.Spec.OLSConfig.ProxyConfig = &olsv1alpha1.ProxyConfig{ ProxyURL: "https://proxy.example.com:8443", - ProxyCACertificateRef: &corev1.LocalObjectReference{ + ProxyCACertificateRef: &olsv1alpha1.ProxyCACertConfigMapRef{ Name: proxyCACMName, + // Key is omitted - will default to "proxy-ca.crt" for backward compatibility }, } // LCore requires supported Llama Stack provider types diff --git a/internal/controller/olsconfig_helpers_test.go b/internal/controller/olsconfig_helpers_test.go index 63b2bfaf1..835e06536 100644 --- a/internal/controller/olsconfig_helpers_test.go +++ b/internal/controller/olsconfig_helpers_test.go @@ -647,7 +647,7 @@ var _ = Describe("Helper Functions", func() { Name: "test-additional-ca", }, ProxyConfig: &olsv1alpha1.ProxyConfig{ - ProxyCACertificateRef: &corev1.LocalObjectReference{ + ProxyCACertificateRef: &olsv1alpha1.ProxyCACertConfigMapRef{ Name: "test-proxy-ca", }, }, diff --git a/internal/controller/utils/constants.go b/internal/controller/utils/constants.go index 48e71d470..e502a3094 100644 --- a/internal/controller/utils/constants.go +++ b/internal/controller/utils/constants.go @@ -105,6 +105,8 @@ const ( OLSConsoleTLSHashKey = "hash/olsconsoletls" // AdditionalCAHashKey is the key of the hash value of the additional CA certificates in the deployment annotations AdditionalCAHashKey = "hash/additionalca" + // ProxyCACertHashKey is the key of the hash value of the proxy CA certificate in the deployment annotations + ProxyCACertHashKey = "hash/proxycacert" // OLSAppServerContainerPort is the port number of the lightspeed-service-api container exposes OLSAppServerContainerPort = 8443 // OLSAppServerServicePort is the port number for OLS application server service. diff --git a/internal/controller/utils/utils.go b/internal/controller/utils/utils.go index 62576de64..7a4b6c571 100644 --- a/internal/controller/utils/utils.go +++ b/internal/controller/utils/utils.go @@ -787,14 +787,35 @@ func ForEachExternalConfigMap(cr *olsv1alpha1.OLSConfig, fn func(name string, so } // 2. Proxy CA certificate - if cr.Spec.OLSConfig.ProxyConfig != nil && - cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef != nil && - cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef.Name != "" { - cmName := cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef.Name - if err := fn(cmName, "proxy-ca"); err != nil { - return err + if cr.Spec.OLSConfig.ProxyConfig != nil { + cmName := GetProxyCACertConfigMapName(cr.Spec.OLSConfig.ProxyConfig.ProxyCACertificateRef) + if cmName != "" { + if err := fn(cmName, "proxy-ca"); err != nil { + return err + } } } return nil } + +// GetProxyCACertKey returns the ConfigMap key for the proxy CA certificate. +// If not specified, defaults to ProxyCACertFileName for backward compatibility. +func GetProxyCACertKey(proxyCACertRef *olsv1alpha1.ProxyCACertConfigMapRef) string { + if proxyCACertRef == nil { + return ProxyCACertFileName + } + if proxyCACertRef.Key != "" { + return proxyCACertRef.Key + } + return ProxyCACertFileName // Default for backward compatibility +} + +// GetProxyCACertConfigMapName returns the ConfigMap name for the proxy CA certificate. +// Returns empty string if the reference is nil. +func GetProxyCACertConfigMapName(proxyCACertRef *olsv1alpha1.ProxyCACertConfigMapRef) string { + if proxyCACertRef == nil || proxyCACertRef.Name == "" { + return "" + } + return proxyCACertRef.Name +} diff --git a/test/e2e/proxy_test.go b/test/e2e/proxy_test.go index 1ff24ccbf..8ec33ea24 100644 --- a/test/e2e/proxy_test.go +++ b/test/e2e/proxy_test.go @@ -52,7 +52,7 @@ var _ = Describe("Proxy test", Ordered, Label("Proxy"), FlakeAttempts(5), func() var secret *corev1.Secret // Helper function to setup proxy configuration - setupProxyConfig := func(proxyURL string, proxyCACertRef *corev1.LocalObjectReference) { + setupProxyConfig := func(proxyURL string, proxyCACertRef *olsv1alpha1.ProxyCACertConfigMapRef) { By("modifying the olsconfig to use proxy") err = client.Update(cr, func(obj ctrlclient.Object) error { cr := obj.(*olsv1alpha1.OLSConfig) @@ -478,7 +478,7 @@ var _ = Describe("Proxy test", Ordered, Label("Proxy"), FlakeAttempts(5), func() }) It("should be able to query the application server with https proxy", func() { - setupProxyConfig("https://"+squidHostname+":"+strconv.Itoa(httpsPort), &corev1.LocalObjectReference{ + setupProxyConfig("https://"+squidHostname+":"+strconv.Itoa(httpsPort), &olsv1alpha1.ProxyCACertConfigMapRef{ Name: "proxy-ca", }) waitForAppServerRollout()