Repository: kubeclipper/kubeclipper
Pull request #409 (2022-11-17 01:44:50)

feat: kcctl deploy support https
Signed-off-by: lyp256 <lyp256@qq.com>

<!-- Thanks for sending a pull request! Here are some tips for you:

1. If you want **faster** PR reviews, read how: https://github.com/kubeclipper/community/blob/master/developer-guide/development/the-pr-author-guide-to-getting-through-code-review.md
2. In case you want to know how your PR got reviewed, read: https://github.com/kubeclipper/community/blob/master/developer-guide/development/code-review-guide.md
3. Here are some coding convetions followed by Kubeclipper community: https://github.com/kubeclipper/community/blob/master/developer-guide/development/coding-conventions.md
-->

### What type of PR is this?
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->


### What this PR does / why we need it:

### Which issue(s) this PR fixes:
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

### Special notes for reviewers:
```
```

### Does this PR introduced a user-facing change?
<!--
If no, just write "None" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md
-->
```release-note
Use https by default when kcctl deploy
```

### Additional documentation, usage docs, etc.:
<!--
This section can be blank if this pull request does not require a release note.
Please use the following format for linking documentation or pass the
section below:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```

kubeclipper-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubeclipper/kubeclipper/pull/409#" title="Author self-approved">lyp256</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **x893675** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @x893675` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubeclipper%2Fkubeclipper).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubeclipper/kubeclipper/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["x893675"]} -->
x893675: /cc @lixd 
x893675: /lgtm
/approve
kubeclipper-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubeclipper/kubeclipper/pull/409#" title="Author self-approved">lyp256</a>*, *<a href="https://github.com/kubeclipper/kubeclipper/pull/409#issuecomment-1323404708" title="Approved">x893675</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubeclipper%2Fkubeclipper).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubeclipper/kubeclipper/blob/master/OWNERS)~~ [x893675]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
kubeclipper-bot: LGTM label has been added.  <details>Git tree hash: d91963ac71ae2b9e967add8f22084a0eec7a9015</details>

Patch:
diff --git a/cmd/kcctl/app/options/options.go b/cmd/kcctl/app/options/options.go
index 1a5776a..eb4a32c 100644
--- a/cmd/kcctl/app/options/options.go
+++ b/cmd/kcctl/app/options/options.go
@@ -31,9 +31,8 @@ import (
 
 	"github.com/kubeclipper/kubeclipper/pkg/auditing/option"
 	"github.com/kubeclipper/kubeclipper/pkg/authentication/options"
-	"github.com/kubeclipper/kubeclipper/pkg/utils/sliceutil"
-
 	"github.com/kubeclipper/kubeclipper/pkg/utils/autodetection"
+	"github.com/kubeclipper/kubeclipper/pkg/utils/sliceutil"
 
 	"gopkg.in/yaml.v2"
 	"k8s.io/client-go/util/homedir"
@@ -65,14 +64,16 @@ const (
 )
 
 const (
-	DefaultPath               = ".kc"
-	DefaultDeployConfig       = "deploy-config.yaml"
-	DefaultConfig             = "config"
-	DefaultCaPath             = "pki"
-	DefaultEtcdPKIPath        = "pki/etcd"
-	DefaultNatsPKIPath        = "pki/nats"
-	DefaultKcServerConfigPath = "/etc/kubeclipper-server"
-	DefaultKcAgentConfigPath  = "/etc/kubeclipper-agent"
+	DefaultPath                = ".kc"
+	DefaultDeployConfig        = "deploy-config.yaml"
+	DefaultConfig              = "config"
+	DefaultCaPath              = "pki"
+	DefaultEtcdPKIPath         = "pki/etcd"
+	DefaultNatsPKIPath         = "pki/nats"
+	DefaultKCPKIPath           = "pki/kc"
+	DefaultKcServerConfigPath  = "/etc/kubeclipper-server"
+	DefaultKcAgentConfigPath   = "/etc/kubeclipper-agent"
+	DefaultKcConsoleConfigPath = "/etc/kc-console"
 
 	DefaultRegion = "default"
 
@@ -84,8 +85,9 @@ const (
 	EtcdHealthCheck  = "kube-etcd-healthcheck-client" // healthcheck-client
 	NatsIOClient     = "kc-server-nats-client"
 	NatsIOServer     = "kc-server-nats-server"
+	KCServer         = "kc-server"
 	NatsAltNameProxy = "proxy.kubeclipper.io" // add nats server SAN for agent proxy
-
+	KCServerAltName  = "server.kubeclipper.io"
 )
 
 var AssumeYes bool
@@ -241,6 +243,7 @@ type DeployConfig struct {
 	Debug              bool                           `json:"debug" yaml:"debug,omitempty"`
 	DefaultRegion      string                         `json:"defaultRegion" yaml:"defaultRegion,omitempty"`
 	ServerPort         int                            `json:"serverPort" yaml:"serverPort,omitempty"`
+	TLS                bool                           `json:"tls" yaml:"tls,omitempty"`
 	StaticServerPort   int                            `json:"staticServerPort" yaml:"staticServerPort,omitempty"`
 	StaticServerPath   string                         `json:"staticServerPath" yaml:"staticServerPath,omitempty"`
 	Pkg                string                         `json:"pkg" yaml:"pkg,omitempty"`
@@ -304,6 +307,7 @@ func NewDeployOptions() *DeployConfig {
 		Debug:            false,
 		DefaultRegion:    "default",
 		ServerPort:       8080,
+		TLS:              true,
 		StaticServerPort: 8081,
 		StaticServerPath: "/opt/kubeclipper-server/resource",
 		AuditOpts:        option.NewAuditOptions(),
@@ -419,6 +423,7 @@ func (c *DeployConfig) AddFlags(flags *pflag.FlagSet) {
 	flags.StringVar(&c.IPDetect, "ip-detect", c.IPDetect, "Kc ip detect method.")
 	flags.BoolVar(&c.Debug, "debug", c.Debug, "Deploy kc use debug mode")
 	flags.StringVarP(&c.DefaultRegion, "region", "r", c.DefaultRegion, "Kc agent default region")
+	flags.BoolVar(&c.TLS, "tls", c.TLS, "Kc api server  use tls mode")
 	flags.IntVar(&c.ServerPort, "server-port", c.ServerPort, "Kc server port")
 	flags.IntVar(&c.StaticServerPort, "static-server-port", c.StaticServerPort, "Kc static server port")
 	flags.StringVar(&c.StaticServerPath, "static-server-path", c.StaticServerPath, "Kc static server path(absolute path")
@@ -467,6 +472,12 @@ func (c *DeployConfig) GetKcServerConfigTemplateContent(ip string) (string, erro
 	var data = make(map[string]interface{})
 	data["ServerAddress"] = ip
 	data["ServerPort"] = c.ServerPort
+	if c.TLS {
+		data["TLS"] = true
+		data["TLSCertFile"] = filepath.Join(DefaultKcServerConfigPath, DefaultKCPKIPath, fmt.Sprintf("%s.crt", KCServer))
+		data["TLSPrivateKey"] = filepath.Join(DefaultKcServerConfigPath, DefaultKCPKIPath, fmt.Sprintf("%s.key", KCServer))
+		data["CACertFile"] = filepath.Join(DefaultKcServerConfigPath, DefaultCaPath, "ca.crt")
+	}
 	// TODO: make auto generate
 	data["JwtSecret"] = c.JWTSecret
 	data["RetentionPeriod"] = c.AuditOpts.RetentionPeriod
diff --git a/cmd/kubeclipper-server/app/options/options.go b/cmd/kubeclipper-server/app/options/options.go
index c03cb4c..c4ceeb1 100644
--- a/cmd/kubeclipper-server/app/options/options.go
+++ b/cmd/kubeclipper-server/app/options/options.go
@@ -84,6 +84,10 @@ func (s *ServerOptions) NewAPIServer(stopCh <-chan struct{}) (*server.APIServer,
 		if err != nil {
 			return nil, err
 		}
+
+		if httpSrv.TLSConfig == nil {
+			httpSrv.TLSConfig = new(tls.Config)
+		}
 		httpSrv.TLSConfig.Certificates = []tls.Certificate{certificate}
 		httpSrv.Addr = fmt.Sprintf("%s:%d", s.GenericServerRunOptions.BindAddress, s.GenericServerRunOptions.SecurePort)
 	}
diff --git a/pkg/apis/core/v1/handler.go b/pkg/apis/core/v1/handler.go
index 8d10a63..4748865 100644
--- a/pkg/apis/core/v1/handler.go
+++ b/pkg/apis/core/v1/handler.go
@@ -31,10 +31,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/kubeclipper/kubeclipper/pkg/clusteroperation"
-
-	"github.com/kubeclipper/kubeclipper/pkg/controller/cronbackupcontroller"
-
 	"github.com/emicklei/go-restful"
 	"github.com/google/uuid"
 	"github.com/gorilla/websocket"
@@ -48,17 +44,19 @@ import (
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/tools/remotecommand"
 
-	"github.com/kubeclipper/kubeclipper/pkg/models"
-
+	"github.com/kubeclipper/kubeclipper/cmd/kcctl/app/options"
 	tenantapiv1 "github.com/kubeclipper/kubeclipper/pkg/apis/tenant/v1"
 	"github.com/kubeclipper/kubeclipper/pkg/authentication/auth"
 	"github.com/kubeclipper/kubeclipper/pkg/client/clientrest"
 	"github.com/kubeclipper/kubeclipper/pkg/clustermanage"
+	"github.com/kubeclipper/kubeclipper/pkg/clusteroperation"
 	"github.com/kubeclipper/kubeclipper/pkg/component"
 	"github.com/kubeclipper/kubeclipper/pkg/controller"
 	"github.com/kubeclipper/kubeclipper/pkg/controller-runtime/client"
 	"github.com/kubeclipper/kubeclipper/pkg/controller/cloudprovidercontroller"
+	"github.com/kubeclipper/kubeclipper/pkg/controller/cronbackupcontroller"
 	"github.com/kubeclipper/kubeclipper/pkg/logger"
+	"github.com/kubeclipper/kubeclipper/pkg/models"
 	"github.com/kubeclipper/kubeclipper/pkg/models/cluster"
 	"github.com/kubeclipper/kubeclipper/pkg/models/core"
 	"github.com/kubeclipper/kubeclipper/pkg/models/lease"
@@ -77,6 +75,7 @@ import (
 	"github.com/kubeclipper/kubeclipper/pkg/server/restplus"
 	"github.com/kubeclipper/kubeclipper/pkg/service"
 	bs "github.com/kubeclipper/kubeclipper/pkg/simple/backupstore"
+	"github.com/kubeclipper/kubeclipper/pkg/simple/generic"
 	"github.com/kubeclipper/kubeclipper/pkg/utils/certs"
 	"github.com/kubeclipper/kubeclipper/pkg/utils/netutil"
 	"github.com/kubeclipper/kubeclipper/pkg/utils/sshutils"
@@ -89,6 +88,7 @@ var (
 )
 
 type handler struct {
+	genericConfig    *generic.ServerRunOptions
 	clusterOperator  cluster.Operator
 	leaseOperator    lease.Operator
 	opOperator       operation.Operator
@@ -111,10 +111,11 @@ var (
 	ErrNodesRegionDifferent = errors.New("nodes belongs to different region")
 )
 
-func newHandler(clusterOperator cluster.Operator, op operation.Operator, leaseOperator lease.Operator,
+func newHandler(conf *generic.ServerRunOptions, clusterOperator cluster.Operator, op operation.Operator, leaseOperator lease.Operator,
 	platform platform.Operator, coreOperator core.Operator, delivery service.IDelivery,
 	tokenOperator auth.TokenManagementInterface, tenantOperator tenant.Operator) *handler {
 	return &handler{
+		genericConfig:    conf,
 		clusterOperator:  clusterOperator,
 		delivery:         delivery,
 		opOperator:       op,
@@ -776,7 +777,7 @@ func (h *handler) GetKubeConfig(request *restful.Request, response *restful.Resp
 		kubeConfigData []byte
 	)
 	if proxyMode {
-		kubeConfigData, err = h.getProxyKubeConfig(ctx, name, request.Request.Host)
+		kubeConfigData, err = h.getProxyKubeConfig(ctx, name)
 		if err != nil {
 			restplus.HandleInternalError(response, request, err)
 			return
@@ -803,11 +804,20 @@ func (h *handler) GetKubeConfig(request *restful.Request, response *restful.Resp
 	_, _ = response.Write(kubeConfigData)
 }
 
-func (h *handler) getProxyKubeConfig(ctx context.Context, clusterName string, kcHost string) ([]byte, error) {
-	// FIXME: KC support secure port deploy
-	var serverCA []byte
+func (h *handler) getProxyKubeConfig(ctx context.Context, clusterName string) ([]byte, error) {
 	// TODO: get CA from kc server config
-	serverURL := fmt.Sprintf("https://%s/cluster/%s", kcHost, clusterName)
+	var serverCA []byte
+
+	scheme := "http"
+	port := h.genericConfig.InsecurePort
+	if h.genericConfig.SecurePort != 0 {
+		scheme = "https"
+		port = h.genericConfig.SecurePort
+	}
+	// FIXME floatIP or domain?
+	host := fmt.Sprintf("%s:%d", h.genericConfig.BindAddress, port)
+
+	serverURL := fmt.Sprintf("%s://%s/cluster/%s", scheme, host, clusterName)
 	user, ok := apirequest.UserFrom(ctx)
 	if !ok {
 		return nil, fmt.Errorf("not get user info")
@@ -823,6 +833,7 @@ func (h *handler) getProxyKubeConfig(ctx context.Context, clusterName string, kc
 	for _, c := range kubecfg.Clusters {
 		if strings.HasPrefix(strings.ToLower(c.Server), "https") {
 			c.InsecureSkipTLSVerify = true
+			c.TLSServerName = options.KCServerAltName
 		}
 	}
 
diff --git a/pkg/apis/core/v1/registry.go b/pkg/apis/core/v1/registry.go
index aa489e7..455fb23 100644
--- a/pkg/apis/core/v1/registry.go
+++ b/pkg/apis/core/v1/registry.go
@@ -26,6 +26,7 @@ import (
 	"github.com/kubeclipper/kubeclipper/pkg/authentication/auth"
 	"github.com/kubeclipper/kubeclipper/pkg/models/core"
 	"github.com/kubeclipper/kubeclipper/pkg/models/tenant"
+	"github.com/kubeclipper/kubeclipper/pkg/simple/generic"
 
 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
 
@@ -1619,10 +1620,12 @@ func SetupWebService(h *handler) *restful.WebService {
 	return webservice
 }
 
-func AddToContainer(c *restful.Container, clusterOperator cluster.Operator, op operation.Operator, platform platform.Operator, leaseOperator lease.Operator, coreOperator core.Operator,
-	delivery service.IDelivery, tokenOperator auth.TokenManagementInterface,
-	tenantOperator tenant.Operator) error {
-	h := newHandler(clusterOperator, op, leaseOperator, platform, coreOperator, delivery, tokenOperator, tenantOperator)
+func AddToContainer(c *restful.Container, clusterOperator cluster.Operator,
+	op operation.Operator, platform platform.Operator, leaseOperator lease.Operator,
+	coreOperator core.Operator, delivery service.IDelivery,
+	tokenOperator auth.TokenManagementInterface, tenantOperator tenant.Operator,
+	conf *generic.ServerRunOptions) error {
+	h := newHandler(conf, clusterOperator, op, leaseOperator, platform, coreOperator, delivery, tokenOperator, tenantOperator)
 	webservice := SetupWebService(h)
 	c.Add(webservice)
 	return nil
diff --git a/pkg/cli/config/template.go b/pkg/cli/config/template.go
index 8f0ebc9..78820a5 100644
--- a/pkg/cli/config/template.go
+++ b/pkg/cli/config/template.go
@@ -69,7 +69,14 @@ const KcCaddyTmpl = `{
 			health_status 2xx
 			#health_body
 			#health_headers
+{{- if .TLS }}
+			transport http {
+				tls_trusted_ca_certs {{.CACert}}
+				tls_server_name {{.TLSServerName}}
+				#tls_insecure_skip_verify
+            }
 		}
+{{- end}}
 		try_files {path} {path}/ /index.html
 		file_server
 	}
@@ -167,10 +174,19 @@ WantedBy=multi-user.target`
 
 const KcServerConfigTmpl = `generic:
   bindAddress: {{.ServerAddress}}
+{{- if .TLS }}
+  insecurePort: 0
+  securePort: {{.ServerPort}}
+  tlsCertFile: "{{.TLSCertFile}}"
+  tlsPrivateKey: "{{.TLSPrivateKey}}"
+  caCertFile: "{{.CACertFile}}"
+{{- else }}
   insecurePort: {{.ServerPort}}
   securePort: 0
   tlsCertFile: ""
   tlsPrivateKey: ""
+  caCertFile: ""
+{{- end}}
 authentication:
   authenticateRateLimiterMaxTries: {{.AuthenticateRateLimiterMaxTries}}
   authenticateRateLimiterDuration: {{.AuthenticateRateLimiterDuration}}
diff --git a/pkg/cli/deploy/deploy.go b/pkg/cli/deploy/deploy.go
index 9b3cdd5..dcba9db 100644
--- a/pkg/cli/deploy/deploy.go
+++ b/pkg/cli/deploy/deploy.go
@@ -458,7 +458,7 @@ func (d *DeployOptions) sendPackage() {
 	}
 }
 
-func (d DeployOptions) generateAndSendCerts() error {
+func (d *DeployOptions) generateAndSendCerts() error {
 	var altNames []string
 	for _, name := range d.servers {
 		altNames = append(altNames, name)
@@ -483,6 +483,16 @@ func (d DeployOptions) generateAndSendCerts() error {
 		natsCert = certList(options.DefaultNatsPKIPath, options.Ca, append(append(altNames, d.deployConfig.ServerIPs...), options.NatsAltNameProxy), natsCommonNameUsages)
 		certs = append(certs, natsCert...)
 	}
+	var kcCerts []certutils.Config
+	if d.deployConfig.TLS {
+		nameUsages := map[string][]x509.ExtKeyUsage{
+			options.KCServer: {x509.ExtKeyUsageServerAuth},
+		}
+		names := append(altNames, d.deployConfig.ServerIPs...)
+		names = append(names, options.KCServerAltName)
+		kcCerts = certList(options.DefaultKCPKIPath, options.Ca, names, nameUsages)
+		certs = append(certs, kcCerts...)
+	}
 
 	CACerts := map[string]*x509.Certificate{}
 	CAKeys := map[string]crypto.Signer{}
@@ -569,7 +579,16 @@ func (d DeployOptions) generateAndSendCerts() error {
 			}
 		}
 	}
-
+	if d.deployConfig.TLS {
+		err := d.sendConsoleCert(cas, options.DefaultCaPath)
+		if err != nil {
+			return err
+		}
+		err = d.sendCertAndKey(kcCerts, options.DefaultKCPKIPath)
+		if err != nil {
+			return err
+		}
+	}
 	return nil
 }
 
@@ -632,10 +651,18 @@ func (d *DeployOptions) getKcConsoleTemplateContent() string {
 		logger.Fatalf("template parse failed: %s", err.Error())
 	}
 	var serverUpstream string
+	scheme := "http"
+	if d.deployConfig.TLS {
+		scheme = "https"
+	}
 	for k := range d.servers {
-		serverUpstream = serverUpstream + fmt.Sprintf(" http://%s:%d", k, d.deployConfig.ServerPort)
+		serverUpstream = serverUpstream + fmt.Sprintf(" %s://%s:%d",
+			scheme, k, d.deployConfig.ServerPort)
 	}
 	var data = make(map[string]interface{})
+	data["TLS"] = d.deployConfig.TLS
+	data["TLSServerName"] = options.KCServerAltName
+	data["CACert"] = filepath.Join(options.DefaultKcConsoleConfigPath, options.DefaultCaPath, "ca.crt")
 	data["ConsolePort"] = d.deployConfig.ConsolePort
 	data["ServerUpstream"] = serverUpstream
 	var buffer bytes.Buffer
@@ -781,11 +808,28 @@ func (d *DeployOptions) sendAgentCertAndKey(contents []certutils.Config, pki str
 	return nil
 }
 
+func (d *DeployOptions) sendConsoleCert(contents []certutils.Config, pki string) error {
+	for _, content := range contents {
+		err := utils.SendPackageV2(d.deployConfig.SSHConfig,
+			path.Join(content.Path, content.BaseName+".crt"),
+			d.deployConfig.Agents.ListIP(),
+			filepath.Join(options.DefaultKcConsoleConfigPath, pki), nil, nil)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 func (d *DeployOptions) uploadConfig() {
-	host := fmt.Sprintf("http://%s:%d", d.deployConfig.ServerIPs[0], d.deployConfig.ServerPort)
-	c, err := kc.NewClientWithOpts(
-		kc.WithHost(host),
-	)
+	scheme := "http"
+	if d.deployConfig.TLS {
+		scheme = "https"
+	}
+	host := fmt.Sprintf("%s://%s:%d", scheme, d.deployConfig.ServerIPs[0],
+		d.deployConfig.ServerPort)
+	// TODO use WithCAData insteadof WithInsecureSkipTLSVerify
+	c, err := kc.NewClientWithOpts(kc.WithEndpoint(host), kc.WithInsecureSkipTLSVerify())
 	if err != nil {
 		logger.Fatal(err)
 	}
@@ -800,6 +844,8 @@ func (d *DeployOptions) uploadConfig() {
 		Servers: map[string]*config.Server{
 			"default": {
 				Server: host,
+				// TODO get ca insteadof InsecureSkipTLSVerify
+				InsecureSkipTLSVerify: true,
 			},
 		},
 		AuthInfos: map[string]*config.AuthInfo{
diff --git a/pkg/cli/login/login.go b/pkg/cli/login/login.go
index 9a3a499..e142cdc 100644
--- a/pkg/cli/login/login.go
+++ b/pkg/cli/login/login.go
@@ -125,8 +125,8 @@ func (l *LoginOptions) RunLogin() error {
 			_, _ = fmt.Fprintf(l.IOStreams.Out, "\n")
 		}
 	}
-
-	c, err := kc.NewClientWithOpts(kc.WithHost(l.Host))
+	// TODO use WithCAData insteadof WithInsecureSkipTLSVerify
+	c, err := kc.NewClientWithOpts(kc.WithEndpoint(l.Host), kc.WithInsecureSkipTLSVerify())
 	if err != nil {
 		return err
 	}
@@ -143,6 +143,8 @@ func (l *LoginOptions) RunLogin() error {
 		Servers: map[string]*config.Server{
 			"default": {
 				Server: l.Host,
+				// TODO get ca form kc server
+				InsecureSkipTLSVerify: true,
 			},
 		},
 		AuthInfos: map[string]*config.AuthInfo{
diff --git a/pkg/client/clientrest/transport.go b/pkg/client/clientrest/transport.go
index 0906fa5..662cbed 100644
--- a/pkg/client/clientrest/transport.go
+++ b/pkg/client/clientrest/transport.go
@@ -20,7 +20,9 @@ package clientrest
 
 import (
 	"fmt"
+	"net"
 	"net/http"
+	"time"
 
 	"go.uber.org/zap"
 
@@ -53,15 +55,54 @@ func NewTransport(config *transport.Config) (http.RoundTripper, error) {
 	}
 
 	var rt http.RoundTripper
-
+	var err error
 	if config.Transport != nil {
 		rt = config.Transport
 	} else {
-		rt = http.DefaultTransport
+		rt, err = initTransport(config)
+		if err != nil {
+			return nil, err
+		}
 	}
 	return HTTPWrappersForConfig(config, rt)
 }
 
+func initTransport(config *transport.Config) (http.RoundTripper, error) {
+	// Get the TLS options for this client config
+	tlsConfig, err := transport.TLSConfigFor(config)
+	if err != nil {
+		return nil, err
+	}
+	// The options didn't require a custom TLS config
+	if tlsConfig == nil && config.Dial == nil && config.Proxy == nil {
+		return http.DefaultTransport, nil
+	}
+
+	dial := config.Dial
+	if dial == nil {
+		dial = (&net.Dialer{
+			Timeout:   30 * time.Second,
+			KeepAlive: 30 * time.Second,
+		}).DialContext
+	}
+
+	proxy := http.ProxyFromEnvironment
+	if config.Proxy != nil {
+		proxy = config.Proxy
+	}
+
+	rt := utilnet.SetTransportDefaults(&http.Transport{
+		Proxy:               proxy,
+		TLSHandshakeTimeout: 10 * time.Second,
+		TLSClientConfig:     tlsConfig,
+		MaxIdleConnsPerHost: 25,
+		DialContext:         dial,
+		DisableCompression:  config.DisableCompression,
+	})
+
+	return rt, nil
+}
+
 // HTTPWrappersForConfig wraps a round tripper with any relevant layered
 // behavior from the config. Exposed to allow more clients that need HTTP-like
 // behavior but then must hijack the underlying connection (like WebSocket or
diff --git a/pkg/controller-runtime/manager/manager.go b/pkg/controller-runtime/manager/manager.go
index ce0df59..5478d9d 100644
--- a/pkg/controller-runtime/manager/manager.go
+++ b/pkg/controller-runtime/manager/manager.go
@@ -23,10 +23,11 @@ import (
 	"sync"
 	"time"
 
+	"k8s.io/client-go/rest"
+
 	"github.com/kubeclipper/kubeclipper/pkg/controller-runtime/client"
 	"github.com/kubeclipper/kubeclipper/pkg/server/registry"
 
-	"github.com/kubeclipper/kubeclipper/pkg/client/clientrest"
 	"github.com/kubeclipper/kubeclipper/pkg/client/clientset"
 	"github.com/kubeclipper/kubeclipper/pkg/client/informers"
 
@@ -48,8 +49,8 @@ var _ service.Interface = (*ControllerManager)(nil)
 var _ Manager = (*ControllerManager)(nil)
 
 type Manager interface {
-	//ClusterClientWriter() cluster.OperatorWriter
-	//OperationClient() operation.Writer
+	// ClusterClientWriter() cluster.OperatorWriter
+	// OperationClient() operation.Writer
 	AddRunnable(run Runnable)
 	AddWorkerLoop(fn LoopFunc, period time.Duration)
 	GetLogger() logger.Logging
@@ -92,10 +93,7 @@ type ControllerManager struct {
 	cmdDelivery    service.CmdDelivery
 	storageFactory registry.SharedStorageFactory
 
-	cs                      clientset.Interface
-	internalInformerAddress string
-	internalInformerUser    string
-	InternalInformerToken   string
+	cs clientset.Interface
 
 	setupFunc SetupFunc
 
@@ -155,7 +153,7 @@ func (s *ControllerManager) GetCmdDelivery() service.CmdDelivery {
 	return s.cmdDelivery
 }
 
-func NewControllerManager(addr, user, pass string, storageFactory registry.SharedStorageFactory, cmdDelivery service.CmdDelivery, setupFunc SetupFunc) (*ControllerManager, error) {
+func NewControllerManager(rc *rest.Config, storageFactory registry.SharedStorageFactory, cmdDelivery service.CmdDelivery, setupFunc SetupFunc) (*ControllerManager, error) {
 	s := &ControllerManager{
 		leaderStopChan:          make(chan struct{}, 1),
 		defaultWorkerLoopPeriod: time.Second,
@@ -164,14 +162,11 @@ func NewControllerManager(addr, user, pass string, storageFactory registry.Share
 		storageFactory:          storageFactory,
 		setupFunc:               setupFunc,
 	}
-	s.internalInformerAddress = addr
-	s.internalInformerUser = user
-	s.InternalInformerToken = pass
 	s.lock = leaderelect.NewLock(resourceLockNS, resourceLockName, lease.NewLeaseOperator(storageFactory.Leases()), resourcelock.ResourceLockConfig{
 		Identity:      uuid.New().String(),
 		EventRecorder: nil,
 	})
-	cs, err := clientset.NewForConfig(clientrest.InternalRestConfig(s.internalInformerAddress, s.internalInformerUser, s.InternalInformerToken))
+	cs, err := clientset.NewForConfig(rc)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/server/server.go b/pkg/server/server.go
index d7df5b2..67c4e4a 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -34,6 +34,7 @@ import (
 	etcdRESTOptions "k8s.io/apiserver/pkg/server/options"
 	"k8s.io/component-base/version"
 
+	"github.com/kubeclipper/kubeclipper/cmd/kcctl/app/options"
 	auditingv1 "github.com/kubeclipper/kubeclipper/pkg/apis/auditing/v1"
 	configv1 "github.com/kubeclipper/kubeclipper/pkg/apis/config/v1"
 	corev1 "github.com/kubeclipper/kubeclipper/pkg/apis/core/v1"
@@ -51,6 +52,7 @@ import (
 	"github.com/kubeclipper/kubeclipper/pkg/authentication/request/wstoken"
 	"github.com/kubeclipper/kubeclipper/pkg/authorization/authorizer"
 	"github.com/kubeclipper/kubeclipper/pkg/authorization/rbac"
+	"github.com/kubeclipper/kubeclipper/pkg/client/clientrest"
 	"github.com/kubeclipper/kubeclipper/pkg/client/informers"
 	"github.com/kubeclipper/kubeclipper/pkg/controller"
 	"github.com/kubeclipper/kubeclipper/pkg/controller-runtime/manager"
@@ -293,19 +295,37 @@ func (s *APIServer) installAPIs(stopCh <-chan struct{}) error {
 		s.Config.AuthenticationOptions, s.cache); err != nil {
 		return err
 	}
-	addr := ""
+	scheme := "http"
+	host := s.Config.GenericServerRunOptions.BindAddress
+	if host == "0.0.0.0" {
+		host = "127.0.0.1"
+	}
+	port := s.Config.GenericServerRunOptions.InsecurePort
 	if s.Config.GenericServerRunOptions.SecurePort != 0 {
-		addr = fmt.Sprintf("https://%s:%d", s.Config.GenericServerRunOptions.BindAddress, s.Config.GenericServerRunOptions.SecurePort)
-	} else {
-		addr = fmt.Sprintf("http://%s:%d", s.Config.GenericServerRunOptions.BindAddress, s.Config.GenericServerRunOptions.InsecurePort)
+		scheme = "https"
+		port = s.Config.GenericServerRunOptions.SecurePort
+	}
+	addr := fmt.Sprintf("%s://%s:%d", scheme, host, port)
+
+	rc := clientrest.InternalRestConfig(addr, s.internalInformerUser, s.InternalInformerToken)
+	if scheme == "https" {
+		rc.TLSClientConfig.ServerName = options.KCServerAltName
+		rc.TLSClientConfig.CAFile = s.Config.GenericServerRunOptions.CACertFile
+		if rc.TLSClientConfig.CAFile == "" {
+			// self-signed certificate
+			rc.TLSClientConfig.CAFile = s.Config.GenericServerRunOptions.TLSCertFile
+		}
 	}
-	ctrl, err := manager.NewControllerManager(addr, s.internalInformerUser, s.InternalInformerToken, s.storageFactory, deliverySvc, s.SetupController)
+
+	ctrl, err := manager.NewControllerManager(rc, s.storageFactory, deliverySvc, s.SetupController)
 	if err != nil {
 		return err
 	}
 	s.Services = append(s.Services, ctrl)
 
-	if err = corev1.AddToContainer(s.container, clusterOperator, opOperator, platformOperator, leaseOperator, coreOperator, deliverySvc, tokenOperator, tenantOperator); err != nil {
+	if err = corev1.AddToContainer(s.container, clusterOperator, opOperator, platformOperator,
+		leaseOperator, coreOperator, deliverySvc, tokenOperator, tenantOperator,
+		s.Config.GenericServerRunOptions); err != nil {
 		return err
 	}
 	if err = proxy.AddToContainer(s.container, clusterOperator); err != nil {
diff --git a/pkg/simple/client/kc/client.go b/pkg/simple/client/kc/client.go
index 5645488..1aa83f0 100644
--- a/pkg/simple/client/kc/client.go
+++ b/pkg/simple/client/kc/client.go
@@ -20,10 +20,16 @@ package kc
 
 import (
 	"context"
+	"crypto/tls"
+	"crypto/x509"
 	"errors"
+	"fmt"
+	"net"
 	"net/http"
 	"net/url"
+	"os"
 	"strings"
+	"time"
 
 	"github.com/kubeclipper/kubeclipper/pkg/cli/config"
 	"github.com/kubeclipper/kubeclipper/pkg/query"
@@ -34,18 +40,41 @@ const (
 )
 
 type Client struct {
-	client      *http.Client
-	host        string
-	bearerToken string
-	scheme      string
+	client                *http.Client
+	host                  string
+	bearerToken           string
+	scheme                string
+	insecureSkipTLSVerify bool
+	tlsServerName         string
+	caPool                *x509.CertPool
 }
 
 func FromConfig(c config.Config) (*Client, error) {
 	ctx := c.Contexts[c.CurrentContext]
+	currentServer := c.Servers[ctx.Server]
 
-	cli, err := NewClientWithOpts(WithHost(c.Servers[ctx.Server].Server),
-		WithScheme("http"),
+	var opts []Opt
+	opts = append(opts,
+		WithEndpoint(c.Servers[ctx.Server].Server),
 		WithBearerAuth(c.AuthInfos[ctx.AuthInfo].Token))
+	if currentServer.TLSServerName != "" {
+		opts = append(opts, WithServerName(currentServer.TLSServerName))
+	}
+	if currentServer.InsecureSkipTLSVerify {
+		opts = append(opts, WithInsecureSkipTLSVerify())
+	}
+	switch {
+	case len(currentServer.CertificateAuthorityData) != 0:
+		opts = append(opts, WithCAData(currentServer.CertificateAuthorityData))
+	case currentServer.CertificateAuthority != "":
+		caData, err := os.ReadFile(currentServer.CertificateAuthority)
+		if err != nil {
+			return nil, err
+		}
+		opts = append(opts, WithCAData(caData))
+	}
+
+	cli, err := NewClientWithOpts(opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -63,7 +92,6 @@ func FromConfig(c config.Config) (*Client, error) {
 
 func NewClientWithOpts(opts ...Opt) (*Client, error) {
 	c := &Client{
-		client: http.DefaultClient,
 		scheme: defaultHTTPScheme,
 	}
 
@@ -72,7 +100,28 @@ func NewClientWithOpts(opts ...Opt) (*Client, error) {
 			return nil, err
 		}
 	}
-
+	// init tlsConfig
+	if c.insecureSkipTLSVerify || c.tlsServerName != "" || c.caPool != nil {
+		tr, err := c.httpTransport()
+		if err != nil {
+			return nil, err
+		}
+		if tr.TLSClientConfig == nil {
+			tr.TLSClientConfig = new(tls.Config)
+		}
+		if c.insecureSkipTLSVerify {
+			tr.TLSClientConfig.InsecureSkipVerify = true
+		}
+		if c.tlsServerName != "" {
+			tr.TLSClientConfig.ServerName = c.tlsServerName
+		}
+		if c.caPool != nil {
+			tr.TLSClientConfig.RootCAs = c.caPool
+		}
+	}
+	if c.client == nil {
+		c.client = http.DefaultClient
+	}
 	return c, nil
 }
 
@@ -103,3 +152,35 @@ func (cli *Client) Validate() error {
 	// TODO
 	return nil
 }
+
+func (cli *Client) httpTransport() (*http.Transport, error) {
+	if cli.client == nil {
+		cli.client = &http.Client{Transport: defaultHTTPTransport()}
+	}
+	if cli.client.Transport == nil {
+		cli.client.Transport = defaultHTTPTransport()
+	}
+	tr, ok := cli.client.Transport.(*http.Transport)
+	if !ok {
+		return nil, fmt.Errorf("http client Transport not is http.Transport")
+	}
+	return tr, nil
+
+}
+
+func defaultHTTPTransport() *http.Transport {
+	dialer := &net.Dialer{
+		Timeout:   30 * time.Second,
+		KeepAlive: 30 * time.Second,
+	}
+	return &http.Transport{
+		Proxy:                 http.ProxyFromEnvironment,
+		DialContext:           dialer.DialContext,
+		ForceAttemptHTTP2:     true,
+		TLSClientConfig:       new(tls.Config),
+		MaxIdleConns:          100,
+		IdleConnTimeout:       90 * time.Second,
+		TLSHandshakeTimeout:   10 * time.Second,
+		ExpectContinueTimeout: 1 * time.Second,
+	}
+}
diff --git a/pkg/simple/client/kc/options.go b/pkg/simple/client/kc/options.go
index aeb2d0b..b5556ab 100644
--- a/pkg/simple/client/kc/options.go
+++ b/pkg/simple/client/kc/options.go
@@ -19,10 +19,9 @@
 package kc
 
 import (
-	"fmt"
+	"crypto/x509"
 	"net/http"
 	"net/url"
-	"strings"
 )
 
 var (
@@ -33,36 +32,24 @@ var (
 
 type Opt func(*Client) error
 
-func WithHost(host string) Opt {
+func WithEndpoint(endpoint string) Opt {
 	return func(c *Client) error {
-		host = strings.TrimSuffix(host, "/")
-		//c.host = host
-
-		hostURL, err := ParseHostURL(host)
+		endpointURL, err := url.Parse(endpoint)
 		if err != nil {
 			return err
 		}
-		c.host = hostURL.Host
-		c.scheme = hostURL.Scheme
-
-		//c.basePath = host
+		c.host = endpointURL.Host
+		c.scheme = endpointURL.Scheme
+		// c.basePath = endpointURL.Path
 		return nil
 	}
 }
 
-// ParseHostURL parses a url string, validates the string is a host url, and
-// returns the parsed URL
-func ParseHostURL(host string) (*url.URL, error) {
-	protoAddrParts := strings.SplitN(host, "://", 2)
-	if len(protoAddrParts) == 1 {
-		return nil, fmt.Errorf("unable to parse host `%s`", host)
+func WithHost(h string) Opt {
+	return func(c *Client) error {
+		c.host = h
+		return nil
 	}
-
-	proto, addr := protoAddrParts[0], protoAddrParts[1]
-	return &url.URL{
-		Scheme: proto,
-		Host:   addr,
-	}, nil
 }
 
 // WithHTTPClient overrides the client http client with the specified one
@@ -89,3 +76,26 @@ func WithBearerAuth(token string) Opt {
 		return nil
 	}
 }
+
+func WithCAData(ca []byte) Opt {
+	return func(client *Client) error {
+		caPool := x509.NewCertPool()
+		caPool.AppendCertsFromPEM(ca)
+		client.caPool = caPool
+		return nil
+	}
+}
+
+func WithInsecureSkipTLSVerify() Opt {
+	return func(client *Client) error {
+		client.insecureSkipTLSVerify = true
+		return nil
+	}
+}
+
+func WithServerName(name string) Opt {
+	return func(client *Client) error {
+		client.tlsServerName = name
+		return nil
+	}
+}
diff --git a/pkg/simple/client/kc/request.go b/pkg/simple/client/kc/request.go
index d26e12c..9d184f5 100644
--- a/pkg/simple/client/kc/request.go
+++ b/pkg/simple/client/kc/request.go
@@ -209,7 +209,7 @@ func (cli *Client) doRequest(ctx context.Context, req *http.Request) (serverResp
 	serverResp := serverResponse{statusCode: -1, reqURL: req.URL}
 
 	req = req.WithContext(ctx)
-	resp, err := cli.client.Do(req)
+	resp, err := cli.HTTPClient().Do(req)
 	if err != nil {
 		if resp != nil && resp.Body != nil {
 			defer resp.Body.Close()
diff --git a/pkg/simple/generic/options.go b/pkg/simple/generic/options.go
index a7e1b77..31316b0 100644
--- a/pkg/simple/generic/options.go
+++ b/pkg/simple/generic/options.go
@@ -31,6 +31,7 @@ type ServerRunOptions struct {
 	BindAddress   string `json:"bindAddress" yaml:"bindAddress"`
 	InsecurePort  int    `json:"insecurePort" yaml:"insecurePort"`
 	SecurePort    int    `json:"securePort" yaml:"securePort"`
+	CACertFile    string `json:"caCertFile" yaml:"caCertFile"`
 	TLSCertFile   string `json:"tlsCertFile" yaml:"tlsCertFile"`
 	TLSPrivateKey string `json:"tlsPrivateKey" yaml:"tlsPrivateKey"`
 }
diff --git a/tools/doc-gen/main.go b/tools/doc-gen/main.go
index a62c265..858921d 100644
--- a/tools/doc-gen/main.go
+++ b/tools/doc-gen/main.go
@@ -100,7 +100,7 @@ func validateSpec(apiSpec []byte) error {
 func generateSwaggerJSON() []byte {
 
 	container := restful.NewContainer()
-	urlruntime.Must(corev1.AddToContainer(container, nil, nil, nil, nil, nil, nil, nil, nil))
+	urlruntime.Must(corev1.AddToContainer(container, nil, nil, nil, nil, nil, nil, nil, nil, nil))
 	urlruntime.Must(iamv1.AddToContainer(container, nil, nil, nil, nil))
 	urlruntime.Must(tenantv1.AddToContainer(container, nil, nil, nil, nil))
 	urlruntime.Must(configv1.AddToContainer(container, nil, nil))


Test patch:
diff --git a/pkg/apis/core/v1/utils_test.go b/pkg/apis/core/v1/utils_test.go
index 6c24c5f..478336c 100644
--- a/pkg/apis/core/v1/utils_test.go
+++ b/pkg/apis/core/v1/utils_test.go
@@ -145,7 +145,7 @@ var (
 )
 
 func Test_parseOperationFromCluster(t *testing.T) {
-	h := newHandler(nil, nil, nil, nil, nil, nil, nil, nil)
+	h := newHandler(nil, nil, nil, nil, nil, nil, nil, nil, nil)
 	type args struct {
 		c      *v1.Cluster
 		meta   *component.ExtraMetadata
@@ -193,7 +193,7 @@ func Test_parseOperationFromComponent(t *testing.T) {
 		cluster    *v1.Cluster
 		components []v1.Addon
 	}
-	h := newHandler(nil, nil, nil, nil, nil, nil, nil, nil)
+	h := newHandler(nil, nil, nil, nil, nil, nil, nil, nil, nil)
 	nfs := nfsprovisioner.NFSProvisioner{
 		ManifestsDir:     "/tmp/.nfs",
 		Namespace:        "kube-system",