Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
52 changes: 52 additions & 0 deletions pkg/common/conf/conf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// Copyright (c) 2025 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
//
// SPDX-License-Identifier: EPL-2.0
//
// Contributors:
// Red Hat, Inc. - initial API and implementation
//

package conf

import "github.com/eclipse-che/che-operator/pkg/common/utils"

var (
// since 7.98.0
// The namespace where the custom certificate ConfigMap is stored
// See https://docs.openshift.com/container-platform/latest/security/certificates/updating-ca-bundle.html
CertificatesOpenShiftConfigNamespaceName = &CheOperatorConf{
EnvName: "CHE_OPERATOR_CERTIFICATES_OPENSHIFT_CONFIG_NAMESPACE_NAME",
defaultValue: "openshift-config",
}

// since 7.98.0
// The key name containing the custom certificate in the ConfigMap
// See https://docs.openshift.com/container-platform/latest/security/certificates/updating-ca-bundle.html
CertificatesOpenShiftCustomCertificateConfigMapKeyName = &CheOperatorConf{
EnvName: "CHE_OPERATOR_CERTIFICATES_OPENSHIFT_CUSTOM_CERTIFICATE_CONFIGMAP_KEY_NAME",
defaultValue: "ca-bundle.crt",
}

// since 7.98.0
// Introduce a new behavior to sync only custom certificates
// "false" means previous behaviour when the whole OpenShift certificates bundle was synced
// by adding "config.openshift.io/inject-trusted-cabundle=true" label
// https://docs.openshift.com/container-platform/latest/networking/configuring-a-custom-pki.html#certificate-injection-using-operators_configuring-a-custom-pki
CertificatesSyncCustomOpenShiftCertificateOnly = &CheOperatorConf{
EnvName: "CHE_OPERATOR_CERTIFICATES_SYNC_CUSTOM_OPENSHIFT_CERTIFICATE_ONLY",
defaultValue: "true",
}
)

type CheOperatorConf struct {
EnvName string
defaultValue string
}

func Get(conf *CheOperatorConf) string {
return utils.GetEnvOrDefault(conf.EnvName, conf.defaultValue)
}
12 changes: 10 additions & 2 deletions pkg/common/utils/env.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2023 Red Hat, Inc.
// Copyright (c) 2019-2025 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
Expand Down Expand Up @@ -70,7 +70,7 @@ func doGetEnvsByRegExp(regExp string, isArchitectureDependentEnvNameNeeded bool)
return env
}

// GetArchitectureDependentEnvName returns environment variable name dependending on architecture
// GetArchitectureDependentEnvName returns environment variable name depending on architecture
// by adding "_<ARCHITECTURE>" suffix. If variable is not set then the default will be return.
func GetArchitectureDependentEnvName(name string) string {
archName := name + "_" + runtime.GOARCH
Expand All @@ -80,3 +80,11 @@ func GetArchitectureDependentEnvName(name string) string {

return name
}

// GetEnvOrDefault returns environment variable value or default value if variable is not set.
func GetEnvOrDefault(name string, defaultValue string) string {
if value, ok := os.LookupEnv(name); ok {
return value
}
return defaultValue
}
26 changes: 25 additions & 1 deletion pkg/common/utils/utils_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2023 Red Hat, Inc.
// Copyright (c) 2019-2025 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
Expand All @@ -13,6 +13,7 @@
package utils

import (
"os"
"reflect"
"testing"

Expand Down Expand Up @@ -105,3 +106,26 @@ func TestMergeMaps(t *testing.T) {
actual := MergeMaps([]map[string]string{nil, map1, nil, map2, make(map[string]string)})
assert.Equal(t, expected, actual)
}

func TestGetEnvOrDefault(t *testing.T) {
var tests = []struct {
envName string
envValue string
defaultValue string
expectedValue string
}{
{"env1", "", "default1", "default1"},
{"env2", "value2", "default2", "value2"},
}

_ = os.Setenv("env2", "value2")

defer func() {
_ = os.Unsetenv("env1")
_ = os.Unsetenv("env2")
}()

for _, test := range tests {
assert.Equal(t, test.expectedValue, GetEnvOrDefault(test.envName, test.defaultValue))
}
}
125 changes: 79 additions & 46 deletions pkg/deploy/tls/certificates.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2023 Red Hat, Inc.
// Copyright (c) 2019-2025 Red Hat, Inc.
// This program and the accompanying materials are made
// available under the terms of the Eclipse Public License 2.0
// which is available at https://www.eclipse.org/legal/epl-2.0/
Expand All @@ -15,8 +15,10 @@ package tls
import (
"errors"
"fmt"
"github.com/eclipse-che/che-operator/pkg/common/conf"
"os"
"reflect"
"strconv"
"strings"

"github.com/devfile/devworkspace-operator/pkg/infrastructure"
Expand Down Expand Up @@ -64,7 +66,8 @@ func NewCertificatesReconciler() *CertificatesReconciler {

func (c *CertificatesReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.Result, bool, error) {
if infrastructure.IsOpenShift() {
if done, err := c.syncOpenShiftCABundleCertificates(ctx); !done {
syncCustomOpenShiftCertificateOnly := conf.Get(conf.CertificatesSyncCustomOpenShiftCertificateOnly) == "true"
if done, err := c.syncOpenShiftCertificates(ctx, syncCustomOpenShiftCertificateOnly); !done {
return reconcile.Result{}, false, err
}
} else {
Expand Down Expand Up @@ -98,50 +101,6 @@ func (c *CertificatesReconciler) Finalize(ctx *chetypes.DeployContext) bool {
return true
}

func (c *CertificatesReconciler) syncOpenShiftCABundleCertificates(ctx *chetypes.DeployContext) (bool, error) {
openShiftCaBundleCMKey := types.NamespacedName{
Namespace: ctx.CheCluster.Namespace,
Name: constants.DefaultCaBundleCertsCMName,
}

openShiftCaBundleCM := &corev1.ConfigMap{}
exists, err := deploy.Get(ctx, openShiftCaBundleCMKey, openShiftCaBundleCM)
if err != nil {
return false, err
}

if !exists {
openShiftCaBundleCM = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.DefaultCaBundleCertsCMName,
Namespace: ctx.CheCluster.Namespace,
Labels: deploy.GetLabels(constants.CheCABundle),
},
}
}

openShiftCaBundleCM.ObjectMeta.Labels[injectTrustedCaBundle] = "true"
openShiftCaBundleCM.ObjectMeta.Labels[constants.KubernetesPartOfLabelKey] = constants.CheEclipseOrg
openShiftCaBundleCM.ObjectMeta.Labels[constants.KubernetesComponentLabelKey] = constants.CheCABundle
openShiftCaBundleCM.TypeMeta = metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
}

return deploy.Sync(
ctx,
openShiftCaBundleCM,
cmp.Options{
cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"),
cmpopts.IgnoreFields(corev1.ConfigMap{}, "Data"),
cmp.Comparer(func(x, y metav1.ObjectMeta) bool {
return x.Labels[injectTrustedCaBundle] == y.Labels[injectTrustedCaBundle] &&
x.Labels[constants.KubernetesComponentLabelKey] == y.Labels[constants.KubernetesComponentLabelKey]
}),
},
)
}

func (c *CertificatesReconciler) syncKubernetesCABundleCertificates(ctx *chetypes.DeployContext) (bool, error) {
data, err := c.readKubernetesCaBundle()
if err != nil {
Expand Down Expand Up @@ -358,6 +317,80 @@ func (c *CertificatesReconciler) syncCheCABundleCerts(ctx *chetypes.DeployContex
return deploy.Sync(ctx, mergedCABundlesCM, deploy.ConfigMapDiffOpts)
}

func (c *CertificatesReconciler) syncOpenShiftCertificates(
ctx *chetypes.DeployContext,
syncCustomOpenShiftCertificateOnly bool) (bool, error) {

openShiftCertsCMKey := types.NamespacedName{
Name: constants.DefaultCaBundleCertsCMName,
Namespace: ctx.CheCluster.Namespace,
}

openShiftCertsCM := &corev1.ConfigMap{}
exists, err := deploy.Get(ctx, openShiftCertsCMKey, openShiftCertsCM)
if err != nil {
return false, err
}

if !exists {
openShiftCertsCM = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: constants.DefaultCaBundleCertsCMName,
Namespace: ctx.CheCluster.Namespace,
},
Data: map[string]string{},
}
}

if openShiftCertsCM.ObjectMeta.Labels == nil {
openShiftCertsCM.ObjectMeta.Labels = map[string]string{}
}

openShiftCertsCM.ObjectMeta.Labels[injectTrustedCaBundle] = strconv.FormatBool(!syncCustomOpenShiftCertificateOnly)
openShiftCertsCM.ObjectMeta.Labels[constants.KubernetesPartOfLabelKey] = constants.CheEclipseOrg
openShiftCertsCM.ObjectMeta.Labels[constants.KubernetesComponentLabelKey] = constants.CheCABundle
openShiftCertsCM.TypeMeta = metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
}

if syncCustomOpenShiftCertificateOnly {
customCerCMKeyName := conf.Get(conf.CertificatesOpenShiftCustomCertificateConfigMapKeyName)
delete(openShiftCertsCM.Data, customCerCMKeyName)

if ctx.Proxy.TrustedCAMapName != "" {
trustedCACMKey := types.NamespacedName{
Namespace: conf.Get(conf.CertificatesOpenShiftConfigNamespaceName),
Name: ctx.Proxy.TrustedCAMapName,
}

trustedCACM := &corev1.ConfigMap{}
if exists, err = deploy.Get(ctx, trustedCACMKey, trustedCACM); exists {
openShiftCertsCM.Data[customCerCMKeyName] = trustedCACM.Data[customCerCMKeyName]
} else if err != nil {
return false, err
}
}
}

diffOpts := cmp.Options{
cmpopts.IgnoreFields(corev1.ConfigMap{}, "TypeMeta"),
cmp.Comparer(func(x, y metav1.ObjectMeta) bool {
return x.Labels[injectTrustedCaBundle] == y.Labels[injectTrustedCaBundle] &&
x.Labels[constants.KubernetesComponentLabelKey] == y.Labels[constants.KubernetesComponentLabelKey] &&
x.Labels[constants.KubernetesPartOfLabelKey] == y.Labels[constants.KubernetesPartOfLabelKey]
}),
}

if !syncCustomOpenShiftCertificateOnly {
// Cluster Network operator add OpenShift CA bundle to the ConfigMap itself
// So, ignore Data field to avoid endless sync loop
diffOpts = append(diffOpts, cmpopts.IgnoreFields(corev1.ConfigMap{}, "Data"))
}

return deploy.Sync(ctx, openShiftCertsCM, diffOpts)
}

func readKubernetesCaBundle() ([]byte, error) {
data, err := os.ReadFile(kubernetesCABundleCertsDir + string(os.PathSeparator) + kubernetesCABundleCertsFile)
if err != nil {
Expand Down
Loading
Loading