Repository: dragonflyoss/Dragonfly2
Pull request #1616 (2022-08-31 08:15:31)

feat: add tls policy to scheduler grpc server
<!--- Provide a general summary of your changes in the Title above -->

## Description
- Add tls policy to scheduler grpc server.
<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate)

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.


codecov-commenter: # [Codecov](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#1616](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (d2922ea) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/bcfce8bcffd6daa13ecf5b1adf1196f67101aa60?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (bcfce8b) will **increase** coverage by `0.12%`.
> The diff coverage is `10.63%`.

> :exclamation: Current head d2922ea differs from pull request most recent head 63abc0d. Consider uploading reports for the commit 63abc0d to get more accurate results

[![Impacted file tree graph](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616/graphs/tree.svg?width=650&height=150&src=pr&token=PUICJYSAT7&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)

```diff
@@            Coverage Diff             @@
##             main    #1616      +/-   ##
==========================================
+ Coverage   41.74%   41.87%   +0.12%     
==========================================
  Files         125      125              
  Lines       11496    11540      +44     
==========================================
+ Hits         4799     4832      +33     
- Misses       6190     6198       +8     
- Partials      507      510       +3     
```

| Flag | Coverage Δ | |
|---|---|---|
| Object-compatibility-e2etests | `?` | |
| e2etests | `?` | |
| unittests | `41.87% <10.63%> (+0.12%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [pkg/cache/certify.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-cGtnL2NhY2hlL2NlcnRpZnkuZ28=) | `0.00% <ø> (ø)` | |
| [pkg/net/ip/ip.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-cGtnL25ldC9pcC9pcC5nbw==) | `60.37% <0.00%> (ø)` | |
| [pkg/rpc/credential.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-cGtnL3JwYy9jcmVkZW50aWFsLmdv) | `0.00% <0.00%> (ø)` | |
| [scheduler/config/config.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-c2NoZWR1bGVyL2NvbmZpZy9jb25maWcuZ28=) | `49.26% <62.50%> (+0.82%)` | :arrow_up: |
| [client/daemon/peer/piece\_manager.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BpZWNlX21hbmFnZXIuZ28=) | `52.71% <0.00%> (+0.52%)` | :arrow_up: |
| [...ent/daemon/peer/peertask\_piecetask\_synchronizer.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX3BpZWNldGFza19zeW5jaHJvbml6ZXIuZ28=) | `48.33% <0.00%> (+0.83%)` | :arrow_up: |
| [client/daemon/peer/peertask\_piecetask\_poller.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/1616/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX3BpZWNldGFza19wb2xsZXIuZ28=) | `46.89% <0.00%> (+15.86%)` | :arrow_up: |


Patch:
diff --git a/client/daemon/daemon.go b/client/daemon/daemon.go
index d78a6054..327ff627 100644
--- a/client/daemon/daemon.go
+++ b/client/daemon/daemon.go
@@ -156,7 +156,7 @@ func New(opt *config.DaemonOption, d dfpath.Dfpath) (Daemon, error) {
 				Logger:       zapadapter.New(logger.CoreLogger.Desugar()),
 				Cache: cache.NewCertifyMutliCache(
 					certify.NewMemCache(),
-					certify.DirCache(path.Join(d.CacheDir(), cache.CertifyCacheDirName))),
+					certify.DirCache(path.Join(d.CacheDir(), cache.DfdaemonCertifyCacheDirName))),
 			}
 
 			// issue a certificate to reduce first time delay
diff --git a/hack/ca/gen-manager-ca.sh b/hack/ca/gen-manager-ca.sh
index 470481eb..e22703e7 100755
--- a/hack/ca/gen-manager-ca.sh
+++ b/hack/ca/gen-manager-ca.sh
@@ -1,3 +1,5 @@
+#!/bin/bash
+
 cat << EOF > manager.conf
 [ ca ]
 # man ca
@@ -146,4 +148,4 @@ echo merge root ca cert
 cat ./manager/certs/manager.cert.pem rootca/certs/rootca.cert.pem > ./manager/certs/manager.cert-chain.pem
 
 echo review manager cert
-openssl x509 -in ./manager/certs/manager.cert.pem -noout -text
\ No newline at end of file
+openssl x509 -in ./manager/certs/manager.cert.pem -noout -text
diff --git a/hack/ca/gen-root-ca.sh b/hack/ca/gen-root-ca.sh
index 6b64c473..9a01f8a9 100755
--- a/hack/ca/gen-root-ca.sh
+++ b/hack/ca/gen-root-ca.sh
@@ -148,4 +148,4 @@ echo generate root ca cer format cert
 openssl x509 -inform PEM -in rootca/certs/rootca.cert.pem -outform DER -out rootca/certs/rootca.cert.cer
 
 echo review ca cert
-openssl x509 -in rootca/certs/rootca.cert.pem -noout -text
\ No newline at end of file
+openssl x509 -in rootca/certs/rootca.cert.pem -noout -text
diff --git a/manager/manager.go b/manager/manager.go
index f7ab7819..a886cfcc 100644
--- a/manager/manager.go
+++ b/manager/manager.go
@@ -170,7 +170,7 @@ func New(cfg *config.Config, d dfpath.Dfpath) (*Server, error) {
 
 		// Manager GRPC server's tls varify must be false. If ClientCAs are required for client verification,
 		// the client cannot call the IssueCertificate api.
-		transportCredentials, err := rpc.NewServerCredentialsByCertify(cfg.Security.TLSPolicy, false, &cert, &certify.Certify{
+		transportCredentials, err := rpc.NewServerCredentialsByCertify(cfg.Security.TLSPolicy, false, cert.Certificate, &certify.Certify{
 			CommonName:   ip.IPv4,
 			Issuer:       issuer.NewDragonflyManagerIssuer(&cert),
 			RenewBefore:  time.Hour,
@@ -179,7 +179,7 @@ func New(cfg *config.Config, d dfpath.Dfpath) (*Server, error) {
 			Logger:       zapadapter.New(logger.CoreLogger.Desugar()),
 			Cache: pkgcache.NewCertifyMutliCache(
 				certify.NewMemCache(),
-				certify.DirCache(path.Join(d.CacheDir(), pkgcache.CertifyCacheDirName))),
+				certify.DirCache(path.Join(d.CacheDir(), pkgcache.ManagerCertifyCacheDirName))),
 		})
 		if err != nil {
 			return nil, err
diff --git a/pkg/cache/certify.go b/pkg/cache/certify.go
index fcb2559b..e8a0e385 100644
--- a/pkg/cache/certify.go
+++ b/pkg/cache/certify.go
@@ -24,8 +24,14 @@ import (
 )
 
 const (
-	// CertifyCacheDirName is dir name of certify cache.
-	CertifyCacheDirName = "certs"
+	// SchedulerCertifyCacheDirName is dir name of scheduler certify cache.
+	SchedulerCertifyCacheDirName = "scheduler-certs"
+
+	// ManagerCertifyCacheDirName is dir name of manager certify cache.
+	ManagerCertifyCacheDirName = "manager-certs"
+
+	// DfdaemonCertifyCacheDirName is dir name of dfdaemon certify cache.
+	DfdaemonCertifyCacheDirName = "dfdaemon-certs"
 )
 
 type certifyCache struct {
diff --git a/pkg/issuer/dragonfly.go b/pkg/issuer/dragonfly.go
index a16e199b..c41b363e 100644
--- a/pkg/issuer/dragonfly.go
+++ b/pkg/issuer/dragonfly.go
@@ -95,7 +95,8 @@ func fromCertifyCertConfig(commonName string, conf *certify.CertConfig) ([]byte,
 
 	template := &x509.CertificateRequest{
 		Subject: pkix.Name{
-			CommonName: commonName,
+			CommonName:   commonName,
+			Organization: defaultSubjectOrganization,
 		},
 	}
 
diff --git a/pkg/issuer/dragonfly_manager.go b/pkg/issuer/dragonfly_manager.go
index 00354c6c..5b652875 100644
--- a/pkg/issuer/dragonfly_manager.go
+++ b/pkg/issuer/dragonfly_manager.go
@@ -34,15 +34,9 @@ import (
 )
 
 var (
-	// defaultSubjectCommonName is default common name of subject.
-	defaultSubjectCommonName = "manager"
-
 	// defaultSubjectOrganization is default organization of subject.
 	defaultSubjectOrganization = []string{"Dragonfly"}
 
-	// defaultSubjectOrganizationalUnit is default organizational unit of subject.
-	defaultSubjectOrganizationalUnit = []string{"Manager"}
-
 	// defaultIPAddresses is default ip addresses of certificate.
 	defaultIPAddresses = []net.IP{net.ParseIP(ip.IPv4)}
 
@@ -138,14 +132,13 @@ func (i *dragonflyManagerIssuer) Issue(ctx context.Context, commonName string, c
 	template := x509.Certificate{
 		SerialNumber: serial,
 		Subject: pkix.Name{
-			CommonName:         defaultSubjectCommonName,
-			Organization:       defaultSubjectOrganization,
-			OrganizationalUnit: defaultSubjectOrganizationalUnit,
+			CommonName:   commonName,
+			Organization: defaultSubjectOrganization,
 		},
-		DNSNames:              i.dnsNames,
+		DNSNames:              append(certConfig.SubjectAlternativeNames, i.dnsNames...),
 		EmailAddresses:        i.emailAddresses,
-		IPAddresses:           i.ipAddresses,
-		URIs:                  i.uris,
+		IPAddresses:           append(certConfig.IPSubjectAlternativeNames, i.ipAddresses...),
+		URIs:                  append(certConfig.URISubjectAlternativeNames, i.uris...),
 		NotBefore:             now.Add(-10 * time.Minute).UTC(),
 		NotAfter:              now.Add(i.validityDuration).UTC(),
 		BasicConstraintsValid: true,
diff --git a/pkg/rpc/cdnsystem/client/client.go b/pkg/rpc/cdnsystem/client/client.go
index f507523a..d8edaff8 100644
--- a/pkg/rpc/cdnsystem/client/client.go
+++ b/pkg/rpc/cdnsystem/client/client.go
@@ -29,7 +29,6 @@ import (
 	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/balancer"
-	"google.golang.org/grpc/credentials/insecure"
 
 	cdnsystemv1 "d7y.io/api/pkg/apis/cdnsystem/v1"
 	commonv1 "d7y.io/api/pkg/apis/common/v1"
@@ -59,7 +58,6 @@ func GetClientByAddr(ctx context.Context, netAddr dfnet.NetAddr, opts ...grpc.Di
 		ctx,
 		netAddr.Addr,
 		append([]grpc.DialOption{
-			grpc.WithTransportCredentials(insecure.NewCredentials()),
 			grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
 				rpc.ConvertErrorUnaryClientInterceptor,
 				otelgrpc.UnaryClientInterceptor(),
@@ -98,7 +96,6 @@ func GetClient(ctx context.Context, dynconfig config.DynconfigInterface, opts ..
 		resolver.SeedPeerVirtualTarget,
 		append([]grpc.DialOption{
 			grpc.WithDefaultServiceConfig(pkgbalancer.BalancerServiceConfig),
-			grpc.WithTransportCredentials(insecure.NewCredentials()),
 			grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
 				rpc.ConvertErrorUnaryClientInterceptor,
 				otelgrpc.UnaryClientInterceptor(),
diff --git a/pkg/rpc/credential.go b/pkg/rpc/credential.go
index 46668384..0be133de 100644
--- a/pkg/rpc/credential.go
+++ b/pkg/rpc/credential.go
@@ -24,6 +24,8 @@ import (
 
 	"github.com/johanbrandhorst/certify"
 	"google.golang.org/grpc/credentials"
+
+	"d7y.io/dragonfly/v2/pkg/net/ip"
 )
 
 const (
@@ -41,9 +43,15 @@ const (
 )
 
 // NewServerCredentialsByCertify returns server transport credentials by certify.
-func NewServerCredentialsByCertify(tlsPolicy string, tlsVerify bool, tlsCACert *tls.Certificate, certifyClient *certify.Certify) (credentials.TransportCredentials, error) {
+func NewServerCredentialsByCertify(tlsPolicy string, tlsVerify bool, clientCAs [][]byte, certifyClient *certify.Certify) (credentials.TransportCredentials, error) {
 	tlsConfig := &tls.Config{
-		GetCertificate: certifyClient.GetCertificate,
+		GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
+			if hello.ServerName == "" {
+				hello.ServerName = ip.IPv4
+			}
+
+			return certifyClient.GetCertificate(hello)
+		},
 	}
 
 	switch tlsPolicy {
@@ -56,7 +64,7 @@ func NewServerCredentialsByCertify(tlsPolicy string, tlsVerify bool, tlsCACert *
 		}
 
 		certPool := x509.NewCertPool()
-		for _, cert := range tlsCACert.Certificate {
+		for _, cert := range clientCAs {
 			if !certPool.AppendCertsFromPEM(cert) {
 				return nil, errors.New("invalid CA Cert")
 			}
@@ -69,3 +77,62 @@ func NewServerCredentialsByCertify(tlsPolicy string, tlsVerify bool, tlsCACert *
 		return nil, fmt.Errorf("invalid tlsPolicy: %s", tlsPolicy)
 	}
 }
+
+// NewClientCredentialsByCertify returns client transport credentials by certify.
+func NewClientCredentialsByCertify(tlsPolicy string, tlsVerify bool, rootCAs [][]byte, certifyClient *certify.Certify) (credentials.TransportCredentials, error) {
+	tlsConfig := &tls.Config{
+		GetClientCertificate: certifyClient.GetClientCertificate,
+	}
+
+	if !tlsVerify {
+		tlsConfig.InsecureSkipVerify = true
+	}
+
+	switch tlsPolicy {
+	case DefaultTLSPolicy, PreferTLSPolicy:
+		return NewMuxTransportCredentials(tlsConfig,
+			WithTLSPreferClientHandshake(tlsPolicy == PreferTLSPolicy)), nil
+	case ForceTLSPolicy:
+		certPool := x509.NewCertPool()
+		for _, cert := range rootCAs {
+			if !certPool.AppendCertsFromPEM(cert) {
+				return nil, errors.New("invalid CA Cert")
+			}
+		}
+
+		tlsConfig.RootCAs = certPool
+		return credentials.NewTLS(tlsConfig), nil
+	default:
+		return nil, fmt.Errorf("invalid tlsPolicy: %s", tlsPolicy)
+	}
+}
+
+// NewClientCredentials returns client transport credentials.
+func NewClientCredentials(tlsPolicy string, tlsVerify bool, certs []tls.Certificate, rootCAs [][]byte) (credentials.TransportCredentials, error) {
+	tlsConfig := &tls.Config{}
+	if !tlsVerify {
+		tlsConfig.InsecureSkipVerify = true
+	}
+
+	if len(certs) > 0 {
+		tlsConfig.Certificates = certs
+	}
+
+	switch tlsPolicy {
+	case DefaultTLSPolicy, PreferTLSPolicy:
+		return NewMuxTransportCredentials(tlsConfig,
+			WithTLSPreferClientHandshake(tlsPolicy == PreferTLSPolicy)), nil
+	case ForceTLSPolicy:
+		certPool := x509.NewCertPool()
+		for _, cert := range rootCAs {
+			if !certPool.AppendCertsFromPEM(cert) {
+				return nil, errors.New("invalid CA Cert")
+			}
+		}
+
+		tlsConfig.RootCAs = certPool
+		return credentials.NewTLS(tlsConfig), nil
+	default:
+		return nil, fmt.Errorf("invalid tlsPolicy: %s", tlsPolicy)
+	}
+}
diff --git a/scheduler/config/config.go b/scheduler/config/config.go
index 391ff254..d6cb3360 100644
--- a/scheduler/config/config.go
+++ b/scheduler/config/config.go
@@ -23,6 +23,8 @@ import (
 	"d7y.io/dragonfly/v2/cmd/dependency/base"
 	"d7y.io/dragonfly/v2/pkg/net/fqdn"
 	"d7y.io/dragonfly/v2/pkg/net/ip"
+	"d7y.io/dragonfly/v2/pkg/rpc"
+	"d7y.io/dragonfly/v2/pkg/serialize"
 	"d7y.io/dragonfly/v2/scheduler/storage"
 )
 
@@ -56,6 +58,9 @@ type Config struct {
 
 	// Metrics configuration.
 	Metrics *MetricsConfig `yaml:"metrics" mapstructure:"metrics"`
+
+	// Security configuration.
+	Security *SecurityConfig `yaml:"security" mapstructure:"security"`
 }
 
 // New default configuration.
@@ -122,6 +127,11 @@ func New() *Config {
 			Addr:           DefaultMetricsAddr,
 			EnablePeerHost: false,
 		},
+		Security: &SecurityConfig{
+			AutoIssueCert: false,
+			TLSVerify:     true,
+			TLSPolicy:     rpc.DefaultTLSPolicy,
+		},
 	}
 }
 
@@ -257,6 +267,12 @@ func (cfg *Config) Validate() error {
 		}
 	}
 
+	if cfg.Security.AutoIssueCert {
+		if cfg.Security.CACert == "" {
+			return errors.New("security requires parameter caCert")
+		}
+	}
+
 	return nil
 }
 
@@ -436,3 +452,21 @@ type MetricsConfig struct {
 	// Enable peer host metrics.
 	EnablePeerHost bool `yaml:"enablePeerHost" mapstructure:"enablePeerHost"`
 }
+
+type SecurityConfig struct {
+	// AutoIssueCert indicates to issue client certificates for all grpc call
+	// if AutoIssueCert is false, any other option in Security will be ignored.
+	AutoIssueCert bool `mapstructure:"autoIssueCert" yaml:"autoIssueCert"`
+
+	// CACert is the root CA certificate for all grpc tls handshake, it can be path or PEM format string.
+	CACert serialize.PEMContent `mapstructure:"caCert" yaml:"caCert"`
+
+	// TLSPrefer indicates to verify client certificates for grpc ServerHandshake.
+	TLSVerify bool `mapstructure:"tlsVerify" yaml:"tlsVerify"`
+
+	// TLSPolicy controls the grpc shandshake behaviors:
+	// force: both ClientHandshake and ServerHandshake are only support tls.
+	// prefer: ServerHandshake supports tls and insecure (non-tls), ClientHandshake will only support tls.
+	// default or empty: ServerHandshake supports tls and insecure (non-tls), ClientHandshake will only support insecure (non-tls).
+	TLSPolicy string `mapstructure:"tlsPolicy" yaml:"tlsPolicy"`
+}
diff --git a/scheduler/scheduler.go b/scheduler/scheduler.go
index 8a59fe19..04bfd12f 100644
--- a/scheduler/scheduler.go
+++ b/scheduler/scheduler.go
@@ -21,16 +21,23 @@ import (
 	"fmt"
 	"net"
 	"net/http"
+	"path"
 	"time"
 
+	"github.com/johanbrandhorst/certify"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
+	zapadapter "logur.dev/adapter/zap"
 
 	managerv1 "d7y.io/api/pkg/apis/manager/v1"
 
 	logger "d7y.io/dragonfly/v2/internal/dflog"
+	"d7y.io/dragonfly/v2/pkg/cache"
 	"d7y.io/dragonfly/v2/pkg/dfpath"
 	"d7y.io/dragonfly/v2/pkg/gc"
+	"d7y.io/dragonfly/v2/pkg/issuer"
+	"d7y.io/dragonfly/v2/pkg/net/ip"
+	"d7y.io/dragonfly/v2/pkg/rpc"
 	managerclient "d7y.io/dragonfly/v2/pkg/rpc/manager/client"
 	"d7y.io/dragonfly/v2/scheduler/config"
 	"d7y.io/dragonfly/v2/scheduler/job"
@@ -77,8 +84,20 @@ type Server struct {
 func New(ctx context.Context, cfg *config.Config, d dfpath.Dfpath) (*Server, error) {
 	s := &Server{config: cfg}
 
-	// Initialize manager client.
-	managerClient, err := managerclient.GetClient(ctx, cfg.Manager.Addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
+	// Initialize manager client and dial options of manager grpc client.
+	managerDialOptions := []grpc.DialOption{}
+	if cfg.Security.AutoIssueCert {
+		clientTransportCredentials, err := rpc.NewClientCredentials(cfg.Security.TLSPolicy, cfg.Security.TLSVerify, nil, [][]byte{[]byte(cfg.Security.CACert)})
+		if err != nil {
+			return nil, err
+		}
+
+		managerDialOptions = append(managerDialOptions, grpc.WithTransportCredentials(clientTransportCredentials))
+	} else {
+		managerDialOptions = append(managerDialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
+	}
+
+	managerClient, err := managerclient.GetClient(ctx, cfg.Manager.Addr, managerDialOptions...)
 	if err != nil {
 		return nil, err
 	}
@@ -107,8 +126,36 @@ func New(ctx context.Context, cfg *config.Config, d dfpath.Dfpath) (*Server, err
 	// Initialize GC.
 	s.gc = gc.New(gc.WithLogger(logger.GCLogger))
 
-	// Initialize resource.
-	resource, err := resource.New(cfg, s.gc, dynconfig)
+	// Initialize certify client.
+	var certifyClient *certify.Certify
+	if cfg.Security.AutoIssueCert {
+		certifyClient = &certify.Certify{
+			CommonName:   ip.IPv4,
+			Issuer:       issuer.NewDragonflyIssuer(managerClient),
+			RenewBefore:  time.Hour,
+			CertConfig:   nil,
+			IssueTimeout: 0,
+			Logger:       zapadapter.New(logger.CoreLogger.Desugar()),
+			Cache: cache.NewCertifyMutliCache(
+				certify.NewMemCache(),
+				certify.DirCache(path.Join(d.CacheDir(), cache.SchedulerCertifyCacheDirName))),
+		}
+	}
+
+	// Initialize resource and dial options of seed peer grpc client.
+	seedPeerDialOptions := []grpc.DialOption{}
+	if certifyClient != nil {
+		clientTransportCredentials, err := rpc.NewClientCredentialsByCertify(cfg.Security.TLSPolicy, cfg.Security.TLSVerify, [][]byte{[]byte(cfg.Security.CACert)}, certifyClient)
+		if err != nil {
+			return nil, err
+		}
+
+		seedPeerDialOptions = append(seedPeerDialOptions, grpc.WithTransportCredentials(clientTransportCredentials))
+	} else {
+		seedPeerDialOptions = append(seedPeerDialOptions, grpc.WithTransportCredentials(insecure.NewCredentials()))
+	}
+
+	resource, err := resource.New(cfg, s.gc, dynconfig, seedPeerDialOptions...)
 	if err != nil {
 		return nil, err
 	}
@@ -126,8 +173,20 @@ func New(ctx context.Context, cfg *config.Config, d dfpath.Dfpath) (*Server, err
 	// Initialize scheduler service.
 	service := service.New(cfg, resource, scheduler, dynconfig, s.storage)
 
-	// Initialize grpc service.
-	svr := rpcserver.New(service)
+	// Initialize grpc service and server options of scheduler grpc server.
+	schedulerServerOptions := []grpc.ServerOption{}
+	if certifyClient != nil {
+		serverTransportCredentials, err := rpc.NewServerCredentialsByCertify(cfg.Security.TLSPolicy, cfg.Security.TLSVerify, [][]byte{[]byte(cfg.Security.CACert)}, certifyClient)
+		if err != nil {
+			return nil, err
+		}
+
+		schedulerServerOptions = append(schedulerServerOptions, grpc.Creds(serverTransportCredentials))
+	} else {
+		schedulerServerOptions = append(schedulerServerOptions, grpc.Creds(insecure.NewCredentials()))
+	}
+
+	svr := rpcserver.New(service, schedulerServerOptions...)
 	s.grpcServer = svr
 
 	// Initialize job service.


Test patch:
diff --git a/client/daemon/rpcserver/seeder_test.go b/client/daemon/rpcserver/seeder_test.go
index b106afa8..ab03ee8f 100644
--- a/client/daemon/rpcserver/seeder_test.go
+++ b/client/daemon/rpcserver/seeder_test.go
@@ -29,6 +29,8 @@ import (
 	testifyassert "github.com/stretchr/testify/assert"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/trace"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
 
 	cdnsystemv1 "d7y.io/api/pkg/apis/cdnsystem/v1"
 	commonv1 "d7y.io/api/pkg/apis/common/v1"
@@ -382,7 +384,7 @@ func setupSeederServerAndClient(t *testing.T, srv *server, sd *seeder, assert *t
 	client, err := client.GetClientByAddr(context.Background(), dfnet.NetAddr{
 		Type: dfnet.TCP,
 		Addr: fmt.Sprintf(":%d", port),
-	})
+	}, grpc.WithTransportCredentials(insecure.NewCredentials()))
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/scheduler/config/config_test.go b/scheduler/config/config_test.go
index 051775e1..b1d54a2f 100644
--- a/scheduler/config/config_test.go
+++ b/scheduler/config/config_test.go
@@ -27,6 +27,7 @@ import (
 
 	"d7y.io/dragonfly/v2/pkg/net/fqdn"
 	"d7y.io/dragonfly/v2/pkg/net/ip"
+	"d7y.io/dragonfly/v2/pkg/rpc"
 	"d7y.io/dragonfly/v2/scheduler/storage"
 )
 
@@ -106,6 +107,12 @@ func TestConfig_Load(t *testing.T) {
 			Addr:           ":8000",
 			EnablePeerHost: false,
 		},
+		Security: &SecurityConfig{
+			AutoIssueCert: true,
+			CACert:        "foo",
+			TLSVerify:     true,
+			TLSPolicy:     "force",
+		},
 	}
 
 	schedulerConfigYAML := &Config{}
@@ -188,5 +195,11 @@ func TestConfig_New(t *testing.T) {
 			Addr:           ":8000",
 			EnablePeerHost: false,
 		},
+		Security: &SecurityConfig{
+			AutoIssueCert: false,
+			CACert:        "",
+			TLSVerify:     true,
+			TLSPolicy:     rpc.DefaultTLSPolicy,
+		},
 	})
 }
diff --git a/scheduler/config/testdata/scheduler.yaml b/scheduler/config/testdata/scheduler.yaml
index b6c2ee31..507d1a4e 100644
--- a/scheduler/config/testdata/scheduler.yaml
+++ b/scheduler/config/testdata/scheduler.yaml
@@ -65,3 +65,9 @@ metrics:
   enable: false
   addr: ":8000"
   enablePeerHost: false
+
+security:
+  autoIssueCert: true
+  caCert: foo
+  tlsVerify: true
+  tlsPolicy: force
diff --git a/scheduler/resource/resource_test.go b/scheduler/resource/resource_test.go
index c07d916d..09aedc6f 100644
--- a/scheduler/resource/resource_test.go
+++ b/scheduler/resource/resource_test.go
@@ -23,6 +23,8 @@ import (
 
 	"github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/resolver"
 
 	"d7y.io/dragonfly/v2/pkg/gc"
@@ -169,7 +171,7 @@ func TestResource_New(t *testing.T) {
 			dynconfig := configmocks.NewMockDynconfigInterface(ctl)
 			tc.mock(gc.EXPECT(), dynconfig.EXPECT())
 
-			resource, err := New(tc.config, gc, dynconfig)
+			resource, err := New(tc.config, gc, dynconfig, grpc.WithTransportCredentials(insecure.NewCredentials()))
 			tc.expect(t, resource, err)
 		})
 	}
diff --git a/scheduler/resource/seed_peer_client_test.go b/scheduler/resource/seed_peer_client_test.go
index 0f45300b..a5142e06 100644
--- a/scheduler/resource/seed_peer_client_test.go
+++ b/scheduler/resource/seed_peer_client_test.go
@@ -24,6 +24,8 @@ import (
 
 	gomock "github.com/golang/mock/gomock"
 	"github.com/stretchr/testify/assert"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/resolver"
 
 	"d7y.io/dragonfly/v2/manager/model"
@@ -94,7 +96,7 @@ func TestSeedPeerClient_newSeedPeerClient(t *testing.T) {
 			hostManager := NewMockHostManager(ctl)
 			tc.mock(dynconfig.EXPECT(), hostManager.EXPECT())
 
-			_, err := newSeedPeerClient(dynconfig, hostManager)
+			_, err := newSeedPeerClient(dynconfig, hostManager, grpc.WithTransportCredentials(insecure.NewCredentials()))
 			tc.expect(t, err)
 		})
 	}
@@ -222,7 +224,7 @@ func TestSeedPeerClient_OnNotify(t *testing.T) {
 			hostManager := NewMockHostManager(ctl)
 			tc.mock(dynconfig.EXPECT(), hostManager.EXPECT())
 
-			client, err := newSeedPeerClient(dynconfig, hostManager)
+			client, err := newSeedPeerClient(dynconfig, hostManager, grpc.WithTransportCredentials(insecure.NewCredentials()))
 			if err != nil {
 				t.Fatal(err)
 			}