From 4c0424d1a8d9eba9bb4b2cc44dbe54e773191a30 Mon Sep 17 00:00:00 2001 From: catpineapple Date: Tue, 24 Mar 2026 17:37:42 +0800 Subject: [PATCH] feat: support mTLS for DCR --- pkg/common/utils/resource/pod.go | 26 +++++++++++++ .../sub_controller/fe/prepare_modify.go | 11 +++++- .../sub_controller/sub_controller.go | 38 +++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/pkg/common/utils/resource/pod.go b/pkg/common/utils/resource/pod.go index 3afeb80e..9f7992a2 100644 --- a/pkg/common/utils/resource/pod.go +++ b/pkg/common/utils/resource/pod.go @@ -18,6 +18,7 @@ package resource import ( + "fmt" "strconv" "strings" @@ -543,6 +544,31 @@ func NewBaseMainContainer(dcr *v1.DorisCluster, config map[string]interface{}, c // use liveness as startup, when in debugging mode will not be killed c.StartupProbe = startupProbe(livenessPort, spec.StartTimeout, health_api_path, commands, liveProbeType) c.ReadinessProbe = readinessProbe(readnessPort, health_api_path, commands, readinessProbeType) + + // When TLS is enabled, replace HTTPGet readiness probe with Exec curl that carries client certs. + // In mTLS mode (tls_verify_mode=verify_fail_if_no_peer_cert), the HTTPS endpoint requires + // client certificates. Kubernetes HTTPGet probes cannot provide client certs, so we use + // an Exec probe with curl instead. This is compatible with both TLS and mTLS modes. + enableTLS := GetString(config, ENABLE_TLS_KEY) + if enableTLS == "true" && c.ReadinessProbe != nil && c.ReadinessProbe.HTTPGet != nil { + caCert := GetString(config, TLS_CA_CERTIFICATE_PATH_KEY) + clientCert := GetString(config, TLS_CERTIFICATE_PATH_KEY) + clientKey := GetString(config, TLS_PRIVATE_KEY_PATH_KEY) + curlCmd := fmt.Sprintf( + "curl --fail --silent --output /dev/null --cacert %s --cert %s --key %s https://localhost:%d%s", + caCert, clientCert, clientKey, readnessPort, health_api_path, + ) + c.ReadinessProbe = &corev1.Probe{ + PeriodSeconds: 5, + FailureThreshold: 3, + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"bash", "-c", curlCmd}, + }, + }, + } + } + c.Lifecycle = lifeCycle(prestopScript) return c diff --git a/pkg/controller/sub_controller/fe/prepare_modify.go b/pkg/controller/sub_controller/fe/prepare_modify.go index 5b835e84..691fa69c 100644 --- a/pkg/controller/sub_controller/fe/prepare_modify.go +++ b/pkg/controller/sub_controller/fe/prepare_modify.go @@ -25,6 +25,7 @@ import ( "github.com/apache/doris-operator/pkg/common/utils/resource" sc "github.com/apache/doris-operator/pkg/controller/sub_controller" appv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" @@ -119,7 +120,15 @@ func (fc *Controller) dropObserverBySqlClient(ctx context.Context, k8sclient cli Port: strconv.FormatInt(int64(queryPort), 10), Database: "mysql", } - masterDBClient, err := mysql.NewDorisMasterSqlDB(dbConf, nil, nil) + + // check if TLS is enabled in FE config and find the corresponding secret + tlsConfig, secretName := fc.FindSecretTLSConfig(maps, targetDCR) + var tlsSecret *corev1.Secret + if tlsConfig != nil && secretName != "" { + tlsSecret, _ = k8s.GetSecret(ctx, k8sclient, targetDCR.Namespace, secretName) + } + + masterDBClient, err := mysql.NewDorisMasterSqlDB(dbConf, tlsConfig, tlsSecret) if err != nil { klog.Errorf("NewDorisMasterSqlDB failed, get fe node connection err:%s", err.Error()) return err diff --git a/pkg/controller/sub_controller/sub_controller.go b/pkg/controller/sub_controller/sub_controller.go index 710054de..d97a6647 100644 --- a/pkg/controller/sub_controller/sub_controller.go +++ b/pkg/controller/sub_controller/sub_controller.go @@ -23,6 +23,7 @@ import ( dorisv1 "github.com/apache/doris-operator/api/doris/v1" utils "github.com/apache/doris-operator/pkg/common/utils" "github.com/apache/doris-operator/pkg/common/utils/k8s" + "github.com/apache/doris-operator/pkg/common/utils/mysql" "github.com/apache/doris-operator/pkg/common/utils/resource" "github.com/apache/doris-operator/pkg/common/utils/set" appv1 "k8s.io/api/apps/v1" @@ -32,6 +33,8 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" + "path" + "path/filepath" "sigs.k8s.io/controller-runtime/pkg/client" "strconv" "strings" @@ -280,6 +283,41 @@ func (d *SubDefaultController) CheckSecretExist(ctx context.Context, dcr *dorisv } } +// FindSecretTLSConfig reads TLS configuration from FE config map and returns +// the TLS config and secret name for establishing TLS-enabled MySQL connections. +func (d *SubDefaultController) FindSecretTLSConfig(feConfMap map[string]interface{}, dcr *dorisv1.DorisCluster) (*mysql.TLSConfig, string) { + enableTLS := resource.GetString(feConfMap, resource.ENABLE_TLS_KEY) + if enableTLS == "" { + return nil, "" + } + + caCertFile := resource.GetString(feConfMap, resource.TLS_CA_CERTIFICATE_PATH_KEY) + clientCertFile := resource.GetString(feConfMap, resource.TLS_CERTIFICATE_PATH_KEY) + clientKeyFile := resource.GetString(feConfMap, resource.TLS_PRIVATE_KEY_PATH_KEY) + caFileName := path.Base(caCertFile) + clientCertFileName := path.Base(clientCertFile) + clientKeyFileName := path.Base(clientKeyFile) + + caCertDir := filepath.Dir(caCertFile) + secretName := "" + if dcr.Spec.FeSpec != nil { + for _, sn := range dcr.Spec.FeSpec.Secrets { + if sn.MountPath == caCertDir { + secretName = sn.SecretName + break + } + } + } + + tlsConfig := &mysql.TLSConfig{ + CAFileName: caFileName, + ClientCertFileName: clientCertFileName, + ClientKeyFileName: clientKeyFileName, + } + + return tlsConfig, secretName +} + // CheckSharedPVC verifies two points: // 1. Whether the SharePVC exists // 2. Whether the AccessMode of the SharePVC is ReadWriteMany