From a425eb871f78b82c8d933894a10fd64e7937f913 Mon Sep 17 00:00:00 2001 From: Jiangyi Liu Date: Sat, 25 Apr 2026 22:48:35 +0800 Subject: [PATCH 1/5] tls: support custom certificates (bring-your-own-certificate) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add TLSMode field to GlobalOptions with IsCustomTLS() as the single branching predicate. In custom mode, each component's setTLSConfig() validates user-provided security.*-path keys instead of overwriting them, and buildCertificateTasks()/loadCertificate() are skipped entirely. Manager.TLS() accepts CustomTLSOptions for cert validation, mode transitions (managed↔custom with --force), and client cert backup+copy via swapClientCertFiles(). SwapClientCert() guards standalone cert rotation to custom-mode clusters only. CLI adds --custom, --client-ca/cert/key flags and swap-client-cert as an action in the existing tls switch. Claude was used but code has been manually reviewed and polished. This should close #2693. --- components/cluster/command/tls.go | 31 +++- pkg/cluster/manager/builder.go | 75 +++++---- pkg/cluster/manager/tls.go | 236 ++++++++++++++++++++++++++- pkg/cluster/spec/drainer.go | 42 +++-- pkg/cluster/spec/pd.go | 42 +++-- pkg/cluster/spec/pump.go | 42 +++-- pkg/cluster/spec/resource_manager.go | 22 ++- pkg/cluster/spec/router.go | 42 +++-- pkg/cluster/spec/scheduling.go | 42 +++-- pkg/cluster/spec/spec.go | 17 ++ pkg/cluster/spec/tidb.go | 42 +++-- pkg/cluster/spec/tiflash.go | 84 ++++++---- pkg/cluster/spec/tikv.go | 42 +++-- pkg/cluster/spec/tiproxy.go | 46 ++++-- pkg/cluster/spec/tso.go | 42 +++-- 15 files changed, 637 insertions(+), 210 deletions(-) diff --git a/components/cluster/command/tls.go b/components/cluster/command/tls.go index 7c4a4e1ed6..3046daa6b5 100644 --- a/components/cluster/command/tls.go +++ b/components/cluster/command/tls.go @@ -17,6 +17,7 @@ import ( "strings" perrs "github.com/pingcap/errors" + "github.com/pingcap/tiup/pkg/cluster/manager" "github.com/spf13/cobra" ) @@ -25,10 +26,14 @@ func newTLSCmd() *cobra.Command { reloadCertificate bool // reload certificate when the cluster enable encrypted communication cleanCertificate bool // cleanup certificate when the cluster disable encrypted communication enableTLS bool + customMode bool + clientCA string + clientCert string + clientKey string ) cmd := &cobra.Command{ - Use: "tls ", + Use: "tls ", Short: "Enable/Disable TLS between TiDB components", RunE: func(cmd *cobra.Command, args []string) error { if len(args) != 2 { @@ -45,8 +50,13 @@ func newTLSCmd() *cobra.Command { enableTLS = true case "disable": enableTLS = false + case "swap-client-cert": + if clientCA == "" || clientCert == "" || clientKey == "" { + return perrs.New("swap-client-cert requires --client-ca, --client-cert, and --client-key") + } + return cm.SwapClientCert(clusterName, clientCA, clientCert, clientKey) default: - return perrs.New("enable or disable must be specified at least one") + return perrs.New("action must be one of: enable, disable, swap-client-cert") } if enableTLS && cleanCertificate { @@ -57,13 +67,28 @@ func newTLSCmd() *cobra.Command { return perrs.New("reload-certificate only works when tls enable") } - return cm.TLS(clusterName, gOpt, enableTLS, cleanCertificate, reloadCertificate, skipConfirm) + if !enableTLS && customMode { + return perrs.New("custom mode only applies to enable") + } + + customOpts := manager.CustomTLSOptions{ + Enabled: customMode, + ClientCA: clientCA, + ClientCert: clientCert, + ClientKey: clientKey, + } + + return cm.TLS(clusterName, gOpt, enableTLS, cleanCertificate, reloadCertificate, skipConfirm, customOpts) }, } cmd.Flags().BoolVar(&cleanCertificate, "clean-certificate", false, "Cleanup the certificate file if it already exists when tls disable") cmd.Flags().BoolVar(&reloadCertificate, "reload-certificate", false, "Load the certificate file whether it exists or not when tls enable") cmd.Flags().BoolVar(&gOpt.Force, "force", false, "Force enable/disable tls regardless of the current state") + cmd.Flags().BoolVar(&customMode, "custom", false, "Use custom (BYOC) certificates instead of TiUP-managed self-signed certificates") + cmd.Flags().StringVar(&clientCA, "client-ca", "", "Path to the client CA certificate file (used with --custom)") + cmd.Flags().StringVar(&clientCert, "client-cert", "", "Path to the client certificate file (used with --custom)") + cmd.Flags().StringVar(&clientKey, "client-key", "", "Path to the client private key file (used with --custom)") return cmd } diff --git a/pkg/cluster/manager/builder.go b/pkg/cluster/manager/builder.go index 904df8c9ec..8f423496e3 100644 --- a/pkg/cluster/manager/builder.go +++ b/pkg/cluster/manager/builder.go @@ -711,8 +711,9 @@ func buildTLSTask( topo := metadata.GetTopology() base := metadata.GetBaseMeta() - // load certificate file - if topo.BaseTopo().GlobalOptions.TLSEnabled { + // In custom mode: skip cert generation entirely — user provides their own certs. + // In managed mode: load/generate certificates as before. + if topo.BaseTopo().GlobalOptions.TLSEnabled && !topo.BaseTopo().GlobalOptions.IsCustomTLS() { tlsDir := m.specManager.Path(name, spec.TLSCertKeyDir) m.logger.Infof("Generate certificate: %s", color.YellowString(tlsDir)) if err := m.loadCertificate(name, topo.BaseTopo().GlobalOptions, reloadCertificate); err != nil { @@ -893,43 +894,51 @@ func buildCertificateTasks( base *spec.BaseMeta, gOpt operator.Options, p *tui.SSHConnectionProps) ([]*task.StepDisplay, error) { + if !topo.BaseTopo().GlobalOptions.TLSEnabled { + return nil, nil + } + + // Custom mode: no cert generation or transfer needed. + // User's security.*-path config values point directly to their cert files. + if topo.BaseTopo().GlobalOptions.IsCustomTLS() { + return nil, nil + } + var ( iterErr error certificateTasks []*task.StepDisplay // tasks which are used to copy certificate to remote host ) - // copy TLS certificate to remote host - if topo.BaseTopo().GlobalOptions.TLSEnabled { - topo.IterInstance(func(inst spec.Instance) { - deployDir := spec.Abs(base.User, inst.DeployDir()) - tlsDir := filepath.Join(deployDir, spec.TLSCertKeyDir) - - tb := task.NewSimpleUerSSH(m.logger, inst.GetManageHost(), inst.GetSSHPort(), base.User, gOpt, p, topo.BaseTopo().GlobalOptions.SSHType). - Mkdir(base.User, inst.GetManageHost(), topo.BaseTopo().GlobalOptions.SystemdMode != spec.UserMode, deployDir, tlsDir) - ca, err := crypto.ReadCA( - name, - m.specManager.Path(name, spec.TLSCertKeyDir, spec.TLSCACert), - m.specManager.Path(name, spec.TLSCertKeyDir, spec.TLSCAKey), - ) - if err != nil { - iterErr = err - return - } - tb = tb.TLSCert( - inst.GetHost(), - inst.ComponentName(), - inst.Role(), - inst.GetMainPort(), - ca, - meta.DirPaths{ - Deploy: deployDir, - Cache: m.specManager.Path(name, spec.TempConfigPath), - }) + // Managed mode: generate and transfer TLS certificates to remote host + topo.IterInstance(func(inst spec.Instance) { + deployDir := spec.Abs(base.User, inst.DeployDir()) + tlsDir := filepath.Join(deployDir, spec.TLSCertKeyDir) - t := tb.BuildAsStep(fmt.Sprintf(" - Generate certificate %s -> %s", inst.ComponentName(), inst.ID())) - certificateTasks = append(certificateTasks, t) - }) - } + tb := task.NewSimpleUerSSH(m.logger, inst.GetManageHost(), inst.GetSSHPort(), base.User, gOpt, p, topo.BaseTopo().GlobalOptions.SSHType). + Mkdir(base.User, inst.GetManageHost(), topo.BaseTopo().GlobalOptions.SystemdMode != spec.UserMode, deployDir, tlsDir) + ca, err := crypto.ReadCA( + name, + m.specManager.Path(name, spec.TLSCertKeyDir, spec.TLSCACert), + m.specManager.Path(name, spec.TLSCertKeyDir, spec.TLSCAKey), + ) + if err != nil { + iterErr = err + return + } + tb = tb.TLSCert( + inst.GetHost(), + inst.ComponentName(), + inst.Role(), + inst.GetMainPort(), + ca, + meta.DirPaths{ + Deploy: deployDir, + Cache: m.specManager.Path(name, spec.TempConfigPath), + }) + + t := tb.BuildAsStep(fmt.Sprintf(" - Generate certificate %s -> %s", inst.ComponentName(), inst.ID())) + certificateTasks = append(certificateTasks, t) + }) return certificateTasks, iterErr } diff --git a/pkg/cluster/manager/tls.go b/pkg/cluster/manager/tls.go index 3c31e98b36..6da88773bc 100644 --- a/pkg/cluster/manager/tls.go +++ b/pkg/cluster/manager/tls.go @@ -15,9 +15,14 @@ package manager import ( "context" + "crypto/tls" + "crypto/x509" + "encoding/pem" "fmt" "os" + "path/filepath" "strings" + "time" "github.com/fatih/color" "github.com/joomcode/errorx" @@ -27,13 +32,54 @@ import ( "github.com/pingcap/tiup/pkg/cluster/executor" operator "github.com/pingcap/tiup/pkg/cluster/operation" "github.com/pingcap/tiup/pkg/cluster/spec" - + "github.com/pingcap/tiup/pkg/crypto" + "github.com/pingcap/tiup/pkg/crypto/rand" "github.com/pingcap/tiup/pkg/set" "github.com/pingcap/tiup/pkg/tui" + "github.com/pingcap/tiup/pkg/utils" + "software.sslmate.com/src/go-pkcs12" ) +// CustomTLSOptions holds the flags for enabling custom (BYOC) TLS mode. +type CustomTLSOptions struct { + Enabled bool + ClientCA string + ClientCert string + ClientKey string +} + +// validateCustomClientCerts checks that the provided client cert files exist, +// the cert+key pair is loadable, and the CA is a valid PEM-encoded certificate. +func validateCustomClientCerts(caPath, certPath, keyPath string) error { + for _, p := range []string{caPath, certPath, keyPath} { + if !utils.IsExist(p) { + return fmt.Errorf("file not found: %s", p) + } + } + + // Validate cert+key pair + if _, err := tls.LoadX509KeyPair(certPath, keyPath); err != nil { + return perrs.Annotatef(err, "invalid client cert/key pair (%s, %s)", certPath, keyPath) + } + + // Validate CA is parseable + caPEM, err := os.ReadFile(caPath) + if err != nil { + return perrs.Annotatef(err, "cannot read CA file %s", caPath) + } + caBlock, _ := pem.Decode(caPEM) + if caBlock == nil { + return fmt.Errorf("failed to decode PEM from CA file %s", caPath) + } + if _, err := x509.ParseCertificate(caBlock.Bytes); err != nil { + return perrs.Annotatef(err, "invalid CA certificate in %s", caPath) + } + + return nil +} + // TLS set cluster enable/disable encrypt communication by tls -func (m *Manager) TLS(name string, gOpt operator.Options, enable, cleanCertificate, reloadCertificate, skipConfirm bool) error { +func (m *Manager) TLS(name string, gOpt operator.Options, enable, cleanCertificate, reloadCertificate, skipConfirm bool, customOpts CustomTLSOptions) error { if err := clusterutil.ValidateClusterNameOrError(name); err != nil { return err } @@ -51,9 +97,19 @@ func (m *Manager) TLS(name string, gOpt operator.Options, enable, cleanCertifica topo := metadata.GetTopology() base := metadata.GetBaseMeta() - // set tls_enabled globalOptions := topo.BaseTopo().GlobalOptions - // if force is true, skip this check + prevMode := globalOptions.TLSMode + + // Determine target TLS mode before the early-return check so we can + // detect mode transitions (e.g. managed→custom) even when TLSEnabled + // is already true. + if enable && customOpts.Enabled { + if err := validateCustomClientCerts(customOpts.ClientCA, customOpts.ClientCert, customOpts.ClientKey); err != nil { + return err + } + } + + // If TLS state and mode are already at the target, nothing to do (unless --force). if globalOptions.TLSEnabled == enable && !gOpt.Force { if enable { m.logger.Infof("cluster `%s` TLS status is already enabled\n", name) @@ -62,12 +118,57 @@ func (m *Manager) TLS(name string, gOpt operator.Options, enable, cleanCertifica } return nil } + + // Handle mode transitions that require --force. + if enable && globalOptions.TLSEnabled { + // Cluster already has TLS enabled — this is a mode switch. + isManagedPrev := prevMode == "" || prevMode == spec.TLSModeManaged + if isManagedPrev && customOpts.Enabled { + // managed → custom + if !gOpt.Force { + return fmt.Errorf("switching from managed to custom TLS requires --force") + } + } + if prevMode == spec.TLSModeCustom && !customOpts.Enabled { + // custom → managed + if !gOpt.Force { + return fmt.Errorf("switching from custom to managed TLS requires --force") + } + if !skipConfirm { + if err := tui.PromptForConfirmOrAbortError( + "Switching from custom to managed TLS will generate a new self-signed CA.\n" + + "Existing custom certificates on remote nodes will no longer be trusted.\n" + + "Do you want to continue? [y/N]:"); err != nil { + return err + } + } + // A new CA must be generated, so force certificate reload. + reloadCertificate = true + } + } + + // Set target TLS state. globalOptions.TLSEnabled = enable + if enable && customOpts.Enabled { + globalOptions.TLSMode = spec.TLSModeCustom + } else if enable { + globalOptions.TLSMode = spec.TLSModeManaged + } + if !enable { + globalOptions.TLSMode = "" + } if err := checkTLSEnv(topo, name, base.Version, skipConfirm); err != nil { return err } + // If custom mode, copy client certs to the standard TiUP TLS dir (with backup). + if globalOptions.IsCustomTLS() { + if err := m.swapClientCertFiles(name, customOpts.ClientCA, customOpts.ClientCert, customOpts.ClientKey); err != nil { + return perrs.Annotate(err, "failed to install custom client certificates") + } + } + var sshProxyProps = &tui.SSHConnectionProps{} if gOpt.SSHType != executor.SSHTypeNone { var err error @@ -78,13 +179,13 @@ func (m *Manager) TLS(name string, gOpt operator.Options, enable, cleanCertifica } } - // delFileMap: files that need to be cleaned up, if flag -- cleanCertificate are used + // delFileMap: files that need to be cleaned up, if flag --cleanCertificate are used delFileMap, err := getTLSFileMap(m, name, topo, enable, cleanCertificate, skipConfirm) if err != nil { return err } - // Build the tls tasks + // Build the tls tasks t, err := buildTLSTask( m, name, metadata, gOpt, reloadCertificate, sshProxyProps, delFileMap) if err != nil { @@ -117,7 +218,11 @@ func (m *Manager) TLS(name string, gOpt operator.Options, enable, cleanCertifica } if enable { - m.logger.Infof("Enabled TLS between TiDB components for cluster `%s` successfully", name) + if globalOptions.IsCustomTLS() { + m.logger.Infof("Enabled custom TLS for cluster `%s` successfully", name) + } else { + m.logger.Infof("Enabled TLS between TiDB components for cluster `%s` successfully", name) + } } else { m.logger.Infof("Disabled TLS between TiDB components for cluster `%s` successfully", name) } @@ -181,3 +286,120 @@ func getTLSFileMap(m *Manager, clusterName string, topo spec.Topology, return delFileMap, nil } + +// SwapClientCert replaces the client certificate files in the standard TiUP TLS +// directory for a cluster with user-provided files, creating timestamped backups +// of the originals. No cluster restart is performed — these certs are used by +// TiUP itself, not by remote components. +// +// Only valid for clusters in custom certificate mode. +func (m *Manager) SwapClientCert(clusterName, caPath, certPath, keyPath string) error { + metadata, err := m.meta(clusterName) + if err != nil { + return err + } + if !metadata.GetTopology().BaseTopo().GlobalOptions.IsCustomTLS() { + return fmt.Errorf("cluster `%s` is not in custom certificate mode; swap-client-cert only applies to clusters with --custom TLS enabled", clusterName) + } + return m.swapClientCertFiles(clusterName, caPath, certPath, keyPath) +} + +// swapClientCertFiles performs the actual file swap without checking the cluster +// mode. Called by SwapClientCert (after the mode guard) and by TLS() (which has +// already set the mode in memory but not yet persisted it). +func (m *Manager) swapClientCertFiles(clusterName, caPath, certPath, keyPath string) error { + for _, p := range []string{caPath, certPath, keyPath} { + if !utils.IsExist(p) { + return fmt.Errorf("file not found: %s", p) + } + } + + tlsDir := m.specManager.Path(clusterName, spec.TLSCertKeyDir) + timestamp := time.Now().Format("20060102T150405") + + // Backup existing files + for _, name := range []string{spec.TLSCACert, spec.TLSClientCert, spec.TLSClientKey, spec.PFXClientCert} { + src := filepath.Join(tlsDir, name) + if utils.IsExist(src) { + if err := os.Rename(src, fmt.Sprintf("%s.bak.%s", src, timestamp)); err != nil { + return perrs.Annotatef(err, "cannot backup %s", src) + } + } + } + + // Copy new files + filesToCopy := []struct { + src, dstName string + }{ + {caPath, spec.TLSCACert}, + {certPath, spec.TLSClientCert}, + {keyPath, spec.TLSClientKey}, + } + for _, f := range filesToCopy { + data, err := os.ReadFile(f.src) + if err != nil { + return perrs.Annotatef(err, "cannot read %s", f.src) + } + if err := os.WriteFile(filepath.Join(tlsDir, f.dstName), data, 0600); err != nil { + return perrs.Annotatef(err, "cannot write %s", f.dstName) + } + } + + // Regenerate client.pfx from new cert+key+CA (best-effort). + // User-provided keys may not be RSA, so we use go-pkcs12 directly + // rather than the crypto.PrivKey interface. + if err := regenerateClientPFX(tlsDir); err != nil { + m.logger.Warnf("Failed to regenerate %s (non-fatal): %v", spec.PFXClientCert, err) + } + + return nil +} + +// regenerateClientPFX reads the client cert, key, and CA from tlsDir and +// encodes them into a PKCS#12 file. Returns an error on any failure — the +// caller decides whether to treat it as fatal. +func regenerateClientPFX(tlsDir string) error { + certPEM, err := os.ReadFile(filepath.Join(tlsDir, spec.TLSClientCert)) + if err != nil { + return err + } + keyPEM, err := os.ReadFile(filepath.Join(tlsDir, spec.TLSClientKey)) + if err != nil { + return err + } + caPEM, err := os.ReadFile(filepath.Join(tlsDir, spec.TLSCACert)) + if err != nil { + return err + } + + tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + return err + } + clientCert, err := x509.ParseCertificate(tlsCert.Certificate[0]) + if err != nil { + return err + } + + caBlock, _ := pem.Decode(caPEM) + if caBlock == nil { + return fmt.Errorf("failed to decode CA PEM") + } + caCert, err := x509.ParseCertificate(caBlock.Bytes) + if err != nil { + return err + } + + pfxData, err := pkcs12.Encode( + rand.Reader, + tlsCert.PrivateKey, + clientCert, + []*x509.Certificate{caCert}, + crypto.PKCS12Password, + ) + if err != nil { + return err + } + + return os.WriteFile(filepath.Join(tlsDir, spec.PFXClientCert), pfxData, 0600) +} diff --git a/pkg/cluster/spec/drainer.go b/pkg/cluster/spec/drainer.go index 6071d93eca..8548661281 100644 --- a/pkg/cluster/spec/drainer.go +++ b/pkg/cluster/spec/drainer.go @@ -266,22 +266,34 @@ func (i *DrainerInstance) InitConfig( // setTLSConfig set TLS Config to support enable/disable TLS func (i *DrainerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.ssl-ca", "security.ssl-cert", "security.ssl-key"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.ssl-ca"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.ssl-cert"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.ssl-key"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.ssl-ca"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.ssl-cert"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.ssl-key"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ diff --git a/pkg/cluster/spec/pd.go b/pkg/cluster/spec/pd.go index e2669268ee..6568253df6 100644 --- a/pkg/cluster/spec/pd.go +++ b/pkg/cluster/spec/pd.go @@ -284,22 +284,34 @@ func (i *PDInstance) InitConfig( func (i *PDInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.cacert-path"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.cert-path"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.key-path"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.cacert-path"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.cert-path"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.key-path"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ diff --git a/pkg/cluster/spec/pump.go b/pkg/cluster/spec/pump.go index 50d78970b2..0cf3ec1770 100644 --- a/pkg/cluster/spec/pump.go +++ b/pkg/cluster/spec/pump.go @@ -263,22 +263,34 @@ func (i *PumpInstance) InitConfig( func (i *PumpInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.ssl-ca", "security.ssl-cert", "security.ssl-key"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.ssl-ca"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.ssl-cert"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.ssl-key"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.ssl-ca"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.ssl-cert"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.ssl-key"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ diff --git a/pkg/cluster/spec/resource_manager.go b/pkg/cluster/spec/resource_manager.go index 9f7dfa7d0b..eac28b383d 100644 --- a/pkg/cluster/spec/resource_manager.go +++ b/pkg/cluster/spec/resource_manager.go @@ -266,12 +266,24 @@ func (i *ResourceManagerInstance) InitConfig( // setTLSConfig set TLS Config to support enable/disable TLS func (i *ResourceManagerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.cacert-path"] = fmt.Sprintf("%s/tls/%s", paths.Deploy, TLSCACert) + configs["security.cert-path"] = fmt.Sprintf("%s/tls/%s.crt", paths.Deploy, i.Role()) + configs["security.key-path"] = fmt.Sprintf("%s/tls/%s.pem", paths.Deploy, i.Role()) } - configs["security.cacert-path"] = fmt.Sprintf("%s/tls/%s", paths.Deploy, TLSCACert) - configs["security.cert-path"] = fmt.Sprintf("%s/tls/%s.crt", paths.Deploy, i.Role()) - configs["security.key-path"] = fmt.Sprintf("%s/tls/%s.pem", paths.Deploy, i.Role()) return configs, nil } diff --git a/pkg/cluster/spec/router.go b/pkg/cluster/spec/router.go index 85b42bd570..edc625ee80 100644 --- a/pkg/cluster/spec/router.go +++ b/pkg/cluster/spec/router.go @@ -260,22 +260,34 @@ func (i *RouterInstance) InitConfig( func (i *RouterInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.cacert-path"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.cert-path"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.key-path"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.cacert-path"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.cert-path"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.key-path"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ diff --git a/pkg/cluster/spec/scheduling.go b/pkg/cluster/spec/scheduling.go index 95592bcaff..16581aa82e 100644 --- a/pkg/cluster/spec/scheduling.go +++ b/pkg/cluster/spec/scheduling.go @@ -269,22 +269,34 @@ func (i *SchedulingInstance) InitConfig( func (i *SchedulingInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.cacert-path"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.cert-path"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.key-path"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.cacert-path"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.cert-path"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.key-path"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ diff --git a/pkg/cluster/spec/spec.go b/pkg/cluster/spec/spec.go index 2fb5ec22c3..41d3a59d6c 100644 --- a/pkg/cluster/spec/spec.go +++ b/pkg/cluster/spec/spec.go @@ -62,6 +62,17 @@ const ( UserMode SystemdMode = "user" ) +// TLSMode represents the certificate management strategy for a TLS-enabled cluster. +// Empty string is treated as "managed" for backward compatibility with existing clusters. +type TLSMode string + +const ( + // TLSModeManaged means TiUP generates and manages self-signed certificates. + TLSModeManaged TLSMode = "managed" + // TLSModeCustom means the user provides their own CA-signed certificates. + TLSModeCustom TLSMode = "custom" +) + // general role names var ( RoleMonitor = "monitor" @@ -88,6 +99,7 @@ type ( SSHPort int `yaml:"ssh_port,omitempty" default:"22" validate:"ssh_port:editable"` SSHType executor.SSHType `yaml:"ssh_type,omitempty" default:"builtin"` TLSEnabled bool `yaml:"enable_tls,omitempty"` + TLSMode TLSMode `yaml:"tls_mode,omitempty"` ListenHost string `yaml:"listen_host,omitempty" validate:"listen_host:editable"` DeployDir string `yaml:"deploy_dir,omitempty" default:"deploy"` DataDir string `yaml:"data_dir,omitempty" default:"data"` @@ -200,6 +212,11 @@ type ( } ) +// IsCustomTLS returns true if the cluster uses user-provided certificates. +func (g *GlobalOptions) IsCustomTLS() bool { + return g.TLSEnabled && g.TLSMode == TLSModeCustom +} + // BaseTopo is the base info to topology. type BaseTopo struct { GlobalOptions *GlobalOptions diff --git a/pkg/cluster/spec/tidb.go b/pkg/cluster/spec/tidb.go index 15c9e79633..8f5cc30e39 100644 --- a/pkg/cluster/spec/tidb.go +++ b/pkg/cluster/spec/tidb.go @@ -252,22 +252,34 @@ func (i *TiDBInstance) setTiProxyConfig(ctx context.Context, topo *Specification func (i *TiDBInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.cluster-ssl-ca", "security.cluster-ssl-cert", "security.cluster-ssl-key"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.cluster-ssl-ca"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.cluster-ssl-cert"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.cluster-ssl-key"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.cluster-ssl-ca"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.cluster-ssl-cert"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.cluster-ssl-key"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ diff --git a/pkg/cluster/spec/tiflash.go b/pkg/cluster/spec/tiflash.go index 3e75ef6914..200a053bdc 100644 --- a/pkg/cluster/spec/tiflash.go +++ b/pkg/cluster/spec/tiflash.go @@ -659,22 +659,34 @@ server_configs: // setTLSConfigWithTiFlashLearner set TLS Config to support enable/disable TLS func (i *TiFlashInstance) setTLSConfigWithTiFlashLearner(enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.ca-path", "security.cert-path", "security.key-path"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.ca-path"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.cert-path"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.key-path"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.ca-path"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.cert-path"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.key-path"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ @@ -696,22 +708,34 @@ func (i *TiFlashInstance) setTLSConfigWithTiFlashLearner(enableTLS bool, configs // setTLSConfig set TLS Config to support enable/disable TLS func (i *TiFlashInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.ca_path", "security.cert_path", "security.key_path"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.ca_path"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.cert_path"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.key_path"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.ca_path"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.cert_path"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.key_path"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ diff --git a/pkg/cluster/spec/tikv.go b/pkg/cluster/spec/tikv.go index a52d77ed13..c6df22e30d 100644 --- a/pkg/cluster/spec/tikv.go +++ b/pkg/cluster/spec/tikv.go @@ -305,22 +305,34 @@ func (i *TiKVInstance) InitConfig( // setTLSConfig set TLS Config to support enable/disable TLS func (i *TiKVInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.ca-path", "security.cert-path", "security.key-path"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.ca-path"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.cert-path"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.key-path"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.ca-path"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.cert-path"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.key-path"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ diff --git a/pkg/cluster/spec/tiproxy.go b/pkg/cluster/spec/tiproxy.go index 39d58b7b30..8295fda12e 100644 --- a/pkg/cluster/spec/tiproxy.go +++ b/pkg/cluster/spec/tiproxy.go @@ -241,18 +241,40 @@ func (i *TiProxyInstance) setTLSConfig(ctx context.Context, enableTLS bool, conf configs = make(map[string]any) } if enableTLS { - configs["security.cluster-tls.ca"] = fmt.Sprintf("%s/tls/%s", paths.Deploy, TLSCACert) - configs["security.cluster-tls.cert"] = fmt.Sprintf("%s/tls/%s.crt", paths.Deploy, i.Role()) - configs["security.cluster-tls.key"] = fmt.Sprintf("%s/tls/%s.pem", paths.Deploy, i.Role()) - - configs["security.server-http-tls.ca"] = fmt.Sprintf("%s/tls/%s", paths.Deploy, TLSCACert) - configs["security.server-http-tls.cert"] = fmt.Sprintf("%s/tls/%s.crt", paths.Deploy, i.Role()) - configs["security.server-http-tls.key"] = fmt.Sprintf("%s/tls/%s.pem", paths.Deploy, i.Role()) - configs["security.server-http-tls.skip-ca"] = true - - configs["security.sql-tls.ca"] = fmt.Sprintf("%s/tls/%s", paths.Deploy, TLSCACert) - configs["security.sql-tls.cert"] = fmt.Sprintf("%s/tls/%s.crt", paths.Deploy, i.Role()) - configs["security.sql-tls.key"] = fmt.Sprintf("%s/tls/%s.pem", paths.Deploy, i.Role()) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{ + "security.cluster-tls.ca", + "security.cluster-tls.cert", + "security.cluster-tls.key", + "security.server-http-tls.ca", + "security.server-http-tls.cert", + "security.server-http-tls.key", + "security.sql-tls.ca", + "security.sql-tls.cert", + "security.sql-tls.key", + } { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + configs["security.cluster-tls.ca"] = fmt.Sprintf("%s/tls/%s", paths.Deploy, TLSCACert) + configs["security.cluster-tls.cert"] = fmt.Sprintf("%s/tls/%s.crt", paths.Deploy, i.Role()) + configs["security.cluster-tls.key"] = fmt.Sprintf("%s/tls/%s.pem", paths.Deploy, i.Role()) + + configs["security.server-http-tls.ca"] = fmt.Sprintf("%s/tls/%s", paths.Deploy, TLSCACert) + configs["security.server-http-tls.cert"] = fmt.Sprintf("%s/tls/%s.crt", paths.Deploy, i.Role()) + configs["security.server-http-tls.key"] = fmt.Sprintf("%s/tls/%s.pem", paths.Deploy, i.Role()) + configs["security.server-http-tls.skip-ca"] = true + + configs["security.sql-tls.ca"] = fmt.Sprintf("%s/tls/%s", paths.Deploy, TLSCACert) + configs["security.sql-tls.cert"] = fmt.Sprintf("%s/tls/%s.crt", paths.Deploy, i.Role()) + configs["security.sql-tls.key"] = fmt.Sprintf("%s/tls/%s.pem", paths.Deploy, i.Role()) + } } else { // drainer tls config list tlsConfigs := []string{ diff --git a/pkg/cluster/spec/tso.go b/pkg/cluster/spec/tso.go index f9f7368518..9f2f9168a2 100644 --- a/pkg/cluster/spec/tso.go +++ b/pkg/cluster/spec/tso.go @@ -269,22 +269,34 @@ func (i *TSOInstance) InitConfig( func (i *TSOInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { - if configs == nil { - configs = make(map[string]any) + if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { + // Custom: validate user has set the required keys, don't overwrite. + for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { + if _, ok := configs[key]; !ok { + return nil, fmt.Errorf( + "custom TLS mode requires %q in config for %s (%s:%d)\n"+ + "Use 'tiup cluster edit-config' to set certificate paths", + key, i.ComponentName(), i.GetHost(), i.GetMainPort()) + } + } + } else { + if configs == nil { + configs = make(map[string]any) + } + configs["security.cacert-path"] = fmt.Sprintf( + "%s/tls/%s", + paths.Deploy, + TLSCACert, + ) + configs["security.cert-path"] = fmt.Sprintf( + "%s/tls/%s.crt", + paths.Deploy, + i.Role()) + configs["security.key-path"] = fmt.Sprintf( + "%s/tls/%s.pem", + paths.Deploy, + i.Role()) } - configs["security.cacert-path"] = fmt.Sprintf( - "%s/tls/%s", - paths.Deploy, - TLSCACert, - ) - configs["security.cert-path"] = fmt.Sprintf( - "%s/tls/%s.crt", - paths.Deploy, - i.Role()) - configs["security.key-path"] = fmt.Sprintf( - "%s/tls/%s.pem", - paths.Deploy, - i.Role()) } else { // drainer tls config list tlsConfigs := []string{ From 0da3fd39a8e66bae73fc52a25c4ea0ec6947bdc4 Mon Sep 17 00:00:00 2001 From: Jiangyi Liu Date: Mon, 27 Apr 2026 11:53:42 +0800 Subject: [PATCH 2/5] tls: support custom blackbox_exporter certs in BYOC mode Before this change, blackbox_exporter is hardcoded to use self-signed TLS certificates, regardless of whether TLS mode is custom. Now we skip managed cert generation for blackbox_exporter when IsCustomTLS(), and add blackbox_ca/cert/key fields to MonitoredOptions so users can specify cert paths via edit-config. --- embed/templates/config/blackbox.yml.tpl | 6 ++++++ pkg/cluster/manager/builder.go | 5 +++++ pkg/cluster/spec/spec.go | 5 +++++ pkg/cluster/task/monitored_config.go | 2 +- pkg/cluster/template/config/blackbox.go | 12 ++++++++++-- 5 files changed, 27 insertions(+), 3 deletions(-) diff --git a/embed/templates/config/blackbox.yml.tpl b/embed/templates/config/blackbox.yml.tpl index 3739ad164a..d58391660c 100644 --- a/embed/templates/config/blackbox.yml.tpl +++ b/embed/templates/config/blackbox.yml.tpl @@ -16,9 +16,15 @@ modules: tls: true tls_config: insecure_skip_verify: false +{{- if .CustomCA}} + ca_file: {{.CustomCA}} + cert_file: {{.CustomCert}} + key_file: {{.CustomKey}} +{{- else}} ca_file: {{.DeployDir}}/tls/ca.crt cert_file: {{.DeployDir}}/tls/blackbox_exporter.crt key_file: {{.DeployDir}}/tls/blackbox_exporter.pem +{{- end}} {{- end}} pop3s_banner: prober: tcp diff --git a/pkg/cluster/manager/builder.go b/pkg/cluster/manager/builder.go index 8f423496e3..2233510fd3 100644 --- a/pkg/cluster/manager/builder.go +++ b/pkg/cluster/manager/builder.go @@ -523,6 +523,11 @@ func buildMonitoredCertificateTasks( Mkdir(globalOptions.User, host, globalOptions.SystemdMode != spec.UserMode, tlsDir) if comp == spec.ComponentBlackboxExporter { + // In custom mode the user provides blackbox certs via + // monitored.blackbox_ca/cert/key — no generation needed. + if globalOptions.IsCustomTLS() { + continue + } ca, innerr := crypto.ReadCA( name, m.specManager.Path(name, spec.TLSCertKeyDir, spec.TLSCACert), diff --git a/pkg/cluster/spec/spec.go b/pkg/cluster/spec/spec.go index 41d3a59d6c..049b43dee4 100644 --- a/pkg/cluster/spec/spec.go +++ b/pkg/cluster/spec/spec.go @@ -123,6 +123,11 @@ type ( LogDir string `yaml:"log_dir,omitempty"` NumaNode string `yaml:"numa_node,omitempty" validate:"numa_node:editable"` ResourceControl meta.ResourceControl `yaml:"resource_control,omitempty" validate:"resource_control:editable"` + // Paths on the target node for blackbox_exporter TLS in custom mode. + // In managed mode these are ignored; TiUP generates and deploys certs automatically. + BlackboxCA string `yaml:"blackbox_ca,omitempty" validate:"blackbox_ca:editable"` + BlackboxCert string `yaml:"blackbox_cert,omitempty" validate:"blackbox_cert:editable"` + BlackboxKey string `yaml:"blackbox_key,omitempty" validate:"blackbox_key:editable"` } // ServerConfigs represents the server runtime configuration diff --git a/pkg/cluster/task/monitored_config.go b/pkg/cluster/task/monitored_config.go index c9daf244b5..0f0b6c1aa5 100644 --- a/pkg/cluster/task/monitored_config.go +++ b/pkg/cluster/task/monitored_config.go @@ -68,7 +68,7 @@ func (m *MonitoredConfig) Execute(ctx context.Context) error { var cfg template.ConfigGenerator switch m.component { case spec.ComponentNodeExporter: - if err := m.syncBlackboxConfig(ctx, exec, config.NewBlackboxConfig(m.paths.Deploy, m.tlsEnabled)); err != nil { + if err := m.syncBlackboxConfig(ctx, exec, config.NewBlackboxConfig(m.paths.Deploy, m.tlsEnabled, m.options.BlackboxCA, m.options.BlackboxCert, m.options.BlackboxKey)); err != nil { return err } cfg = scripts. diff --git a/pkg/cluster/template/config/blackbox.go b/pkg/cluster/template/config/blackbox.go index fb0bc56f17..f7fb5ff4ec 100644 --- a/pkg/cluster/template/config/blackbox.go +++ b/pkg/cluster/template/config/blackbox.go @@ -22,17 +22,25 @@ import ( "github.com/pingcap/tiup/pkg/utils" ) -// BlackboxConfig represent the data to generate AlertManager config +// BlackboxConfig represent the data to generate blackbox_exporter config type BlackboxConfig struct { DeployDir string TLSEnabled bool + // Custom cert paths (on the target node). When non-empty, the template + // uses these instead of the default {DeployDir}/tls/ paths. + CustomCA string + CustomCert string + CustomKey string } // NewBlackboxConfig returns a BlackboxConfig -func NewBlackboxConfig(deployDir string, tlsEnabled bool) *BlackboxConfig { +func NewBlackboxConfig(deployDir string, tlsEnabled bool, customCA, customCert, customKey string) *BlackboxConfig { return &BlackboxConfig{ DeployDir: deployDir, TLSEnabled: tlsEnabled, + CustomCA: customCA, + CustomCert: customCert, + CustomKey: customKey, } } From b64809517e1167060fd0073eaea03d8827eb630b Mon Sep 17 00:00:00 2001 From: Jiangyi Liu Date: Mon, 27 Apr 2026 11:59:47 +0800 Subject: [PATCH 3/5] cluster: show TLS mode (managed/custom) in cluster display / topology confirmation --- pkg/cluster/manager/display.go | 10 ++++++++++ pkg/cluster/manager/manager.go | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/pkg/cluster/manager/display.go b/pkg/cluster/manager/display.go index a1f184f46a..afb50d3998 100644 --- a/pkg/cluster/manager/display.go +++ b/pkg/cluster/manager/display.go @@ -177,6 +177,11 @@ func (m *Manager) Display(dopt DisplayOption, opt operator.Options) error { // display TLS info if topo.BaseTopo().GlobalOptions.TLSEnabled { fmt.Printf("TLS encryption: %s\n", cyan.Sprint("enabled")) + if topo.BaseTopo().GlobalOptions.IsCustomTLS() { + fmt.Printf("TLS mode: %s\n", cyan.Sprint("custom")) + } else { + fmt.Printf("TLS mode: %s\n", cyan.Sprint("managed")) + } fmt.Printf("CA certificate: %s\n", cyan.Sprint( m.specManager.Path(name, spec.TLSCertKeyDir, spec.TLSCACert), )) @@ -393,6 +398,11 @@ func (m *Manager) DisplayTiKVLabels(dopt DisplayOption, opt operator.Options) er // display TLS info if topo.BaseTopo().GlobalOptions.TLSEnabled { fmt.Printf("TLS encryption: %s\n", cyan.Sprint("enabled")) + if topo.BaseTopo().GlobalOptions.IsCustomTLS() { + fmt.Printf("TLS mode: %s\n", cyan.Sprint("custom")) + } else { + fmt.Printf("TLS mode: %s\n", cyan.Sprint("managed")) + } fmt.Printf("CA certificate: %s\n", cyan.Sprint( m.specManager.Path(name, spec.TLSCertKeyDir, spec.TLSCACert), )) diff --git a/pkg/cluster/manager/manager.go b/pkg/cluster/manager/manager.go index 041c9409a2..de54999937 100644 --- a/pkg/cluster/manager/manager.go +++ b/pkg/cluster/manager/manager.go @@ -89,6 +89,11 @@ func (m *Manager) confirmTopology(name, version string, topo spec.Topology, patc fmt.Printf("Cluster version: %s\n", cyan.Sprint(version)) if topo.BaseTopo().GlobalOptions.TLSEnabled { fmt.Printf("TLS encryption: %s\n", cyan.Sprint("enabled")) + if topo.BaseTopo().GlobalOptions.IsCustomTLS() { + fmt.Printf("TLS mode: %s\n", cyan.Sprint("custom")) + } else { + fmt.Printf("TLS mode: %s\n", cyan.Sprint("managed")) + } } // check if managehost is set From 629cbcb79530fdf9cb495dac32e9e177ec17b1c1 Mon Sep 17 00:00:00 2001 From: Jiangyi Liu Date: Tue, 28 Apr 2026 11:36:04 +0800 Subject: [PATCH 4/5] tls: better error message for missing custom TLS arguments --- components/cluster/command/tls.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/cluster/command/tls.go b/components/cluster/command/tls.go index 3046daa6b5..48d9517825 100644 --- a/components/cluster/command/tls.go +++ b/components/cluster/command/tls.go @@ -71,6 +71,10 @@ func newTLSCmd() *cobra.Command { return perrs.New("custom mode only applies to enable") } + if customMode && (clientCA == "" || clientCert == "" || clientKey == "") { + return perrs.New("--custom requires --client-ca, --client-cert, and --client-key") + } + customOpts := manager.CustomTLSOptions{ Enabled: customMode, ClientCA: clientCA, From 07358c7afded1fecc3c06659068eb4b0c35d4f37 Mon Sep 17 00:00:00 2001 From: Jiangyi Liu Date: Tue, 28 Apr 2026 12:38:28 +0800 Subject: [PATCH 5/5] tls: in the missing ca-cert field checks, also honor TLS configs in global config block --- components/dm/spec/logic.go | 8 ++++---- pkg/cluster/spec/alertmanager.go | 4 ++-- pkg/cluster/spec/cdc.go | 4 ++-- pkg/cluster/spec/dashboard.go | 2 +- pkg/cluster/spec/drainer.go | 6 +++--- pkg/cluster/spec/grafana.go | 4 ++-- pkg/cluster/spec/instance.go | 16 +++++++++++++--- pkg/cluster/spec/monitoring.go | 4 ++-- pkg/cluster/spec/pd.go | 6 +++--- pkg/cluster/spec/pump.go | 6 +++--- pkg/cluster/spec/resource_manager.go | 6 +++--- pkg/cluster/spec/router.go | 6 +++--- pkg/cluster/spec/scheduling.go | 6 +++--- pkg/cluster/spec/tidb.go | 6 +++--- pkg/cluster/spec/tiflash.go | 12 ++++++------ pkg/cluster/spec/tikv.go | 6 +++--- pkg/cluster/spec/tikv_cdc.go | 4 ++-- pkg/cluster/spec/tiproxy.go | 6 +++--- pkg/cluster/spec/tispark.go | 4 ++-- pkg/cluster/spec/tso.go | 6 +++--- 20 files changed, 66 insertions(+), 56 deletions(-) diff --git a/components/dm/spec/logic.go b/components/dm/spec/logic.go index 67b5c6be93..4393f51d06 100644 --- a/components/dm/spec/logic.go +++ b/components/dm/spec/logic.go @@ -177,7 +177,7 @@ func (i *MasterInstance) InitConfig( return err } - if spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths); err != nil { + if spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, nil, paths); err != nil { return err } @@ -187,7 +187,7 @@ func (i *MasterInstance) InitConfig( // setTLSConfig set TLS Config to support enable/disable TLS // MasterInstance no need to configure TLS -func (i *MasterInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *MasterInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { if configs == nil { @@ -398,7 +398,7 @@ func (i *WorkerInstance) InitConfig( return err } - if spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths); err != nil { + if spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, nil, paths); err != nil { return err } @@ -408,7 +408,7 @@ func (i *WorkerInstance) InitConfig( // setTLSConfig set TLS Config to support enable/disable TLS // workrsInstance no need to configure TLS -func (i *WorkerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *WorkerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { if configs == nil { diff --git a/pkg/cluster/spec/alertmanager.go b/pkg/cluster/spec/alertmanager.go index 1f57fce33b..90ad484f18 100644 --- a/pkg/cluster/spec/alertmanager.go +++ b/pkg/cluster/spec/alertmanager.go @@ -199,7 +199,7 @@ func (i *AlertManagerInstance) InitConfig( } // doesn't work - if _, err := i.setTLSConfig(ctx, false, nil, paths); err != nil { + if _, err := i.setTLSConfig(ctx, false, nil, nil, paths); err != nil { return err } @@ -249,6 +249,6 @@ func (i *AlertManagerInstance) ScaleConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *AlertManagerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *AlertManagerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { return nil, nil } diff --git a/pkg/cluster/spec/cdc.go b/pkg/cluster/spec/cdc.go index 5efddf4f5e..30e824fe26 100644 --- a/pkg/cluster/spec/cdc.go +++ b/pkg/cluster/spec/cdc.go @@ -238,7 +238,7 @@ func (i *CDCInstance) InitConfig( } // doesn't work - if _, err := i.setTLSConfig(ctx, false, nil, paths); err != nil { + if _, err := i.setTLSConfig(ctx, false, nil, nil, paths); err != nil { return err } @@ -260,7 +260,7 @@ func (i *CDCInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *CDCInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *CDCInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { return nil, nil } diff --git a/pkg/cluster/spec/dashboard.go b/pkg/cluster/spec/dashboard.go index e29f68c990..15a522adce 100644 --- a/pkg/cluster/spec/dashboard.go +++ b/pkg/cluster/spec/dashboard.go @@ -240,6 +240,6 @@ func (i *DashboardInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *DashboardInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *DashboardInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { return nil, nil } diff --git a/pkg/cluster/spec/drainer.go b/pkg/cluster/spec/drainer.go index 8548661281..35d99cdb4e 100644 --- a/pkg/cluster/spec/drainer.go +++ b/pkg/cluster/spec/drainer.go @@ -251,7 +251,7 @@ func (i *DrainerInstance) InitConfig( globalConfig := topo.ServerConfigs.Drainer // set TLS configs - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, globalConfig, paths) if err != nil { return err } @@ -264,12 +264,12 @@ func (i *DrainerInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *DrainerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *DrainerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.ssl-ca", "security.ssl-cert", "security.ssl-key"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/grafana.go b/pkg/cluster/spec/grafana.go index 13ddaed8ab..b74d664cca 100644 --- a/pkg/cluster/spec/grafana.go +++ b/pkg/cluster/spec/grafana.go @@ -221,7 +221,7 @@ func (i *GrafanaInstance) InitConfig( } // doesn't work - if _, err := i.setTLSConfig(ctx, false, nil, paths); err != nil { + if _, err := i.setTLSConfig(ctx, false, nil, nil, paths); err != nil { return err } @@ -334,7 +334,7 @@ func (i *GrafanaInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *GrafanaInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *GrafanaInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { return nil, nil } diff --git a/pkg/cluster/spec/instance.go b/pkg/cluster/spec/instance.go index bd8fe34ef6..67cc25c2a9 100644 --- a/pkg/cluster/spec/instance.go +++ b/pkg/cluster/spec/instance.go @@ -126,7 +126,7 @@ type Instance interface { SetPatched(bool) CalculateVersion(string) string // SetVersion(string) - setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) + setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) } // PortStarted wait until a port is being listened @@ -245,7 +245,7 @@ func (i *BaseInstance) InitConfig(ctx context.Context, e ctxt.Executor, opt Glob e.Execute(ctx, cmd, sudo) //nolint // doesn't work - if _, err := i.setTLSConfig(ctx, false, nil, paths); err != nil { + if _, err := i.setTLSConfig(ctx, false, nil, nil, paths); err != nil { return err } @@ -254,10 +254,20 @@ func (i *BaseInstance) InitConfig(ctx context.Context, e ctxt.Executor, opt Glob // setTLSConfig set TLS Config to support enable/disable TLS // baseInstance no need to configure TLS -func (i *BaseInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *BaseInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { return nil, nil } +// hasKey reports whether key exists in any of the provided maps. +func hasKey(key string, maps ...map[string]any) bool { + for _, m := range maps { + if _, ok := m[key]; ok { + return true + } + } + return false +} + // TransferLocalConfigFile scp local config file to remote // Precondition: the user on remote have permission to access & mkdir of dest files func (i *BaseInstance) TransferLocalConfigFile(ctx context.Context, e ctxt.Executor, local, remote string) error { diff --git a/pkg/cluster/spec/monitoring.go b/pkg/cluster/spec/monitoring.go index 3a752c5c4e..93ede4efb5 100644 --- a/pkg/cluster/spec/monitoring.go +++ b/pkg/cluster/spec/monitoring.go @@ -469,7 +469,7 @@ func (i *MonitorInstance) InitConfig( } // doesn't work - if _, err := i.setTLSConfig(ctx, false, nil, paths); err != nil { + if _, err := i.setTLSConfig(ctx, false, nil, nil, paths); err != nil { return err } @@ -592,7 +592,7 @@ func (i *MonitorInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *MonitorInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *MonitorInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { return nil, nil } diff --git a/pkg/cluster/spec/pd.go b/pkg/cluster/spec/pd.go index 6568253df6..5b953cefc4 100644 --- a/pkg/cluster/spec/pd.go +++ b/pkg/cluster/spec/pd.go @@ -268,7 +268,7 @@ func (i *PDInstance) InitConfig( globalConfig := topo.ServerConfigs.PD // set TLS configs - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, globalConfig, paths) if err != nil { return err } @@ -281,13 +281,13 @@ func (i *PDInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *PDInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *PDInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/pump.go b/pkg/cluster/spec/pump.go index 0cf3ec1770..1396450c7d 100644 --- a/pkg/cluster/spec/pump.go +++ b/pkg/cluster/spec/pump.go @@ -251,7 +251,7 @@ func (i *PumpInstance) InitConfig( globalConfig := topo.ServerConfigs.Pump // set TLS configs - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, globalConfig, paths) if err != nil { return err } @@ -260,13 +260,13 @@ func (i *PumpInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *PumpInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *PumpInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.ssl-ca", "security.ssl-cert", "security.ssl-key"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/resource_manager.go b/pkg/cluster/spec/resource_manager.go index eac28b383d..c12eb055d6 100644 --- a/pkg/cluster/spec/resource_manager.go +++ b/pkg/cluster/spec/resource_manager.go @@ -251,7 +251,7 @@ func (i *ResourceManagerInstance) InitConfig( globalConfig := topo.ServerConfigs.ResourceManager var err error - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, globalConfig, paths) if err != nil { return err } @@ -264,12 +264,12 @@ func (i *ResourceManagerInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *ResourceManagerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *ResourceManagerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/router.go b/pkg/cluster/spec/router.go index edc625ee80..d97c046c42 100644 --- a/pkg/cluster/spec/router.go +++ b/pkg/cluster/spec/router.go @@ -244,7 +244,7 @@ func (i *RouterInstance) InitConfig( globalConfig := topo.ServerConfigs.Router // set TLS configs - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, globalConfig, paths) if err != nil { return err } @@ -257,13 +257,13 @@ func (i *RouterInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *RouterInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *RouterInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/scheduling.go b/pkg/cluster/spec/scheduling.go index 16581aa82e..97dda7c35a 100644 --- a/pkg/cluster/spec/scheduling.go +++ b/pkg/cluster/spec/scheduling.go @@ -253,7 +253,7 @@ func (i *SchedulingInstance) InitConfig( globalConfig := topo.ServerConfigs.Scheduling // set TLS configs - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, globalConfig, paths) if err != nil { return err } @@ -266,13 +266,13 @@ func (i *SchedulingInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *SchedulingInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *SchedulingInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/tidb.go b/pkg/cluster/spec/tidb.go index 8f5cc30e39..eece84d383 100644 --- a/pkg/cluster/spec/tidb.go +++ b/pkg/cluster/spec/tidb.go @@ -218,7 +218,7 @@ func (i *TiDBInstance) InitConfig( } // set TLS configs - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, globalConfig, paths) if err != nil { return err } @@ -249,13 +249,13 @@ func (i *TiDBInstance) setTiProxyConfig(ctx context.Context, topo *Specification } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *TiDBInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *TiDBInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.cluster-ssl-ca", "security.cluster-ssl-cert", "security.cluster-ssl-key"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/tiflash.go b/pkg/cluster/spec/tiflash.go index 200a053bdc..b7a9778ba1 100644 --- a/pkg/cluster/spec/tiflash.go +++ b/pkg/cluster/spec/tiflash.go @@ -519,7 +519,7 @@ func (i *TiFlashInstance) initTiFlashConfig(ctx context.Context, version string, } // set TLS configs - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, src, paths) if err != nil { return nil, err } @@ -647,7 +647,7 @@ server_configs: enableTLS := i.topo.(*Specification).GlobalOptions.TLSEnabled // set TLS configs - spec.LearnerConfig, err = i.setTLSConfigWithTiFlashLearner(enableTLS, spec.LearnerConfig, paths) + spec.LearnerConfig, err = i.setTLSConfigWithTiFlashLearner(enableTLS, spec.LearnerConfig, src, paths) if err != nil { return nil, err } @@ -657,12 +657,12 @@ server_configs: } // setTLSConfigWithTiFlashLearner set TLS Config to support enable/disable TLS -func (i *TiFlashInstance) setTLSConfigWithTiFlashLearner(enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *TiFlashInstance) setTLSConfigWithTiFlashLearner(enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.ca-path", "security.cert-path", "security.key-path"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", @@ -706,12 +706,12 @@ func (i *TiFlashInstance) setTLSConfigWithTiFlashLearner(enableTLS bool, configs } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *TiFlashInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *TiFlashInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.ca_path", "security.cert_path", "security.key_path"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/tikv.go b/pkg/cluster/spec/tikv.go index c6df22e30d..0d0a40248b 100644 --- a/pkg/cluster/spec/tikv.go +++ b/pkg/cluster/spec/tikv.go @@ -290,7 +290,7 @@ func (i *TiKVInstance) InitConfig( globalConfig := topo.ServerConfigs.TiKV // set TLS configs - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, globalConfig, paths) if err != nil { return err } @@ -303,12 +303,12 @@ func (i *TiKVInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *TiKVInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *TiKVInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.ca-path", "security.cert-path", "security.key-path"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/tikv_cdc.go b/pkg/cluster/spec/tikv_cdc.go index 855042d0c2..13b94637e0 100644 --- a/pkg/cluster/spec/tikv_cdc.go +++ b/pkg/cluster/spec/tikv_cdc.go @@ -225,7 +225,7 @@ func (i *TiKVCDCInstance) InitConfig( } // doesn't work. - if _, err := i.setTLSConfig(ctx, false, nil, paths); err != nil { + if _, err := i.setTLSConfig(ctx, false, nil, nil, paths); err != nil { return err } @@ -247,7 +247,7 @@ func (i *TiKVCDCInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *TiKVCDCInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *TiKVCDCInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { return nil, nil } diff --git a/pkg/cluster/spec/tiproxy.go b/pkg/cluster/spec/tiproxy.go index 8295fda12e..6c7c95a755 100644 --- a/pkg/cluster/spec/tiproxy.go +++ b/pkg/cluster/spec/tiproxy.go @@ -227,7 +227,7 @@ func (i *TiProxyInstance) InitConfig( } var err error - instanceConfig, err = i.setTLSConfig(ctx, topo.GlobalOptions.TLSEnabled, instanceConfig, paths) + instanceConfig, err = i.setTLSConfig(ctx, topo.GlobalOptions.TLSEnabled, instanceConfig, globalConfig, paths) if err != nil { return err } @@ -236,7 +236,7 @@ func (i *TiProxyInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *TiProxyInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *TiProxyInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { if configs == nil { configs = make(map[string]any) } @@ -254,7 +254,7 @@ func (i *TiProxyInstance) setTLSConfig(ctx context.Context, enableTLS bool, conf "security.sql-tls.cert", "security.sql-tls.key", } { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths", diff --git a/pkg/cluster/spec/tispark.go b/pkg/cluster/spec/tispark.go index daac172fce..a2e0d8669f 100644 --- a/pkg/cluster/spec/tispark.go +++ b/pkg/cluster/spec/tispark.go @@ -460,7 +460,7 @@ func (i *TiSparkWorkerInstance) InitConfig( WithCustomFields(topo.TiSparkMasters[0].SparkConfigs) // doesn't work - if _, err := i.setTLSConfig(ctx, false, nil, paths); err != nil { + if _, err := i.setTLSConfig(ctx, false, nil, nil, paths); err != nil { return err } @@ -520,7 +520,7 @@ func (i *TiSparkWorkerInstance) InitConfig( // setTLSConfig set TLS Config to support enable/disable TLS // TiSparkWorkerInstance no need to configure TLS -func (i *TiSparkWorkerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *TiSparkWorkerInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { return nil, nil } diff --git a/pkg/cluster/spec/tso.go b/pkg/cluster/spec/tso.go index 9f2f9168a2..289f25704b 100644 --- a/pkg/cluster/spec/tso.go +++ b/pkg/cluster/spec/tso.go @@ -253,7 +253,7 @@ func (i *TSOInstance) InitConfig( globalConfig := topo.ServerConfigs.TSO // set TLS configs - spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, paths) + spec.Config, err = i.setTLSConfig(ctx, enableTLS, spec.Config, globalConfig, paths) if err != nil { return err } @@ -266,13 +266,13 @@ func (i *TSOInstance) InitConfig( } // setTLSConfig set TLS Config to support enable/disable TLS -func (i *TSOInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, paths meta.DirPaths) (map[string]any, error) { +func (i *TSOInstance) setTLSConfig(ctx context.Context, enableTLS bool, configs map[string]any, globalConfig map[string]any, paths meta.DirPaths) (map[string]any, error) { // set TLS configs if enableTLS { if i.topo.(*Specification).GlobalOptions.IsCustomTLS() { // Custom: validate user has set the required keys, don't overwrite. for _, key := range []string{"security.cacert-path", "security.cert-path", "security.key-path"} { - if _, ok := configs[key]; !ok { + if !hasKey(key, configs, globalConfig) { return nil, fmt.Errorf( "custom TLS mode requires %q in config for %s (%s:%d)\n"+ "Use 'tiup cluster edit-config' to set certificate paths",