You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2023/02/24 08:57:09 UTC

[dubbo-admin] branch refactor-with-go updated: Add some uts for ca

This is an automated email from the ASF dual-hosted git repository.

albumenj pushed a commit to branch refactor-with-go
in repository https://gitbox.apache.org/repos/asf/dubbo-admin.git


The following commit(s) were added to refs/heads/refactor-with-go by this push:
     new 468b884  Add some uts for ca
468b884 is described below

commit 468b884a7521dac6b028ad20a4f2b969c204f71b
Author: Albumen Kevin <jh...@gmail.com>
AuthorDate: Fri Feb 24 16:56:48 2023 +0800

    Add some uts for ca
---
 ca/main.go                      |   9 +-
 ca/pkg/cert/storage.go          |  44 ++++++-
 ca/pkg/cert/storage_test.go     | 225 ++++++++++++++++++++++++++++++++
 ca/pkg/cert/util.go             |  68 +++++++---
 ca/pkg/cert/util_test.go        | 161 +++++++++++++++++++++++
 ca/pkg/k8s/client.go            |  24 +++-
 ca/pkg/security/server.go       |  39 ++++--
 ca/pkg/v1alpha1/ca_impl.go      |  24 ++--
 ca/pkg/v1alpha1/ca_impl_test.go | 276 ++++++++++++++++++++++++++++++++++++++++
 9 files changed, 813 insertions(+), 57 deletions(-)

diff --git a/ca/main.go b/ca/main.go
index ddfe992..649df54 100644
--- a/ca/main.go
+++ b/ca/main.go
@@ -20,6 +20,8 @@ import (
 	"github.com/apache/dubbo-admin/ca/pkg/logger"
 	"github.com/apache/dubbo-admin/ca/pkg/security"
 	"os"
+	"os/signal"
+	"syscall"
 )
 
 // TODO read namespace from env
@@ -38,13 +40,14 @@ func main() {
 		CertValidity:     1 * 60 * 60 * 1000,       // 1 hour
 	}
 
-	s := &security.Server{
-		Options: options,
-	}
+	s := security.NewServer(options)
 
 	s.Init()
 	s.Start()
 
 	c := make(chan os.Signal)
+	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
+	signal.Notify(s.StopChan, syscall.SIGINT, syscall.SIGTERM)
+
 	<-c
 }
diff --git a/ca/pkg/cert/storage.go b/ca/pkg/cert/storage.go
index 2236c74..c85bd7b 100644
--- a/ca/pkg/cert/storage.go
+++ b/ca/pkg/cert/storage.go
@@ -19,14 +19,19 @@ import (
 	"crypto/rsa"
 	"crypto/tls"
 	"crypto/x509"
+	"github.com/apache/dubbo-admin/ca/pkg/config"
 	"github.com/apache/dubbo-admin/ca/pkg/logger"
 	"math"
+	"os"
+	"reflect"
 	"sync"
 	"time"
 )
 
 type Storage struct {
-	Mutex        *sync.Mutex
+	Mutex    *sync.Mutex
+	StopChan chan os.Signal
+
 	CaValidity   int64
 	CertValidity int64
 
@@ -46,6 +51,18 @@ type Cert struct {
 	tlsCert *tls.Certificate
 }
 
+func NewStorage(options *config.Options) *Storage {
+	return &Storage{
+		Mutex:    &sync.Mutex{},
+		StopChan: make(chan os.Signal, 1),
+
+		AuthorityCert: &Cert{},
+		TrustedCert:   []*Cert{},
+		CertValidity:  options.CertValidity,
+		CaValidity:    options.CaValidity,
+	}
+}
+
 func (c *Cert) IsValid() bool {
 	if c.Cert == nil || c.CertPem == "" || c.PrivateKey == nil {
 		return false
@@ -53,9 +70,16 @@ func (c *Cert) IsValid() bool {
 	if time.Now().Before(c.Cert.NotBefore) || time.Now().After(c.Cert.NotAfter) {
 		return false
 	}
-	if c.Cert.PublicKey == c.PrivateKey.Public() {
-		return false
+
+	if c.tlsCert == nil || !reflect.DeepEqual(c.tlsCert.PrivateKey, c.PrivateKey) {
+		tlsCert, err := tls.X509KeyPair([]byte(c.CertPem), []byte(EncodePri(c.PrivateKey)))
+		if err != nil {
+			return false
+		}
+
+		c.tlsCert = &tlsCert
 	}
+
 	return true
 }
 
@@ -70,19 +94,19 @@ func (c *Cert) NeedRefresh() bool {
 	if time.Now().Add(time.Duration(math.Floor(float64(validity)*0.2)) * time.Millisecond).After(c.Cert.NotAfter) {
 		return true
 	}
-	if c.Cert.PublicKey == c.PrivateKey.Public() {
+	if !reflect.DeepEqual(c.Cert.PublicKey, c.PrivateKey.Public()) {
 		return true
 	}
 	return false
 }
 
 func (c *Cert) GetTlsCert() *tls.Certificate {
-	if c.tlsCert != nil {
+	if c.tlsCert != nil && reflect.DeepEqual(c.tlsCert.PrivateKey, c.PrivateKey) {
 		return c.tlsCert
 	}
 	tlsCert, err := tls.X509KeyPair([]byte(c.CertPem), []byte(EncodePri(c.PrivateKey)))
 	if err != nil {
-		logger.Sugar.Infof("Failed to load x509 cert. %v", err)
+		logger.Sugar.Warnf("Failed to load x509 cert. %v", err)
 	}
 	c.tlsCert = &tlsCert
 	return c.tlsCert
@@ -104,7 +128,6 @@ func (s *Storage) GetServerCert(serverName string) *tls.Certificate {
 	if !nameSigned {
 		s.ServerNames = append(s.ServerNames, serverName)
 	}
-	s.ServerNames = append(s.ServerNames, serverName)
 	s.ServerCerts = SignServerCert(s.AuthorityCert, s.ServerNames, s.CertValidity)
 	return s.ServerCerts.GetTlsCert()
 }
@@ -119,5 +142,12 @@ func (s *Storage) RefreshServerCert() {
 			s.ServerCerts = SignServerCert(s.AuthorityCert, s.ServerNames, s.CertValidity)
 		}
 		s.Mutex.Unlock()
+
+		select {
+		case <-s.StopChan:
+			return
+		default:
+			continue
+		}
 	}
 }
diff --git a/ca/pkg/cert/storage_test.go b/ca/pkg/cert/storage_test.go
new file mode 100644
index 0000000..a34fc37
--- /dev/null
+++ b/ca/pkg/cert/storage_test.go
@@ -0,0 +1,225 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cert
+
+import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"github.com/apache/dubbo-admin/ca/pkg/config"
+	"github.com/apache/dubbo-admin/ca/pkg/logger"
+	"os"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+)
+
+func TestIsValid(t *testing.T) {
+	c := &Cert{}
+	if c.IsValid() {
+		t.Errorf("cert is not valid")
+	}
+
+	c.Cert = &x509.Certificate{}
+	if c.IsValid() {
+		t.Errorf("cert is not valid")
+	}
+
+	c.CertPem = "test"
+	if c.IsValid() {
+		t.Errorf("cert is not valid")
+	}
+
+	c.PrivateKey, _ = rsa.GenerateKey(rand.Reader, 4096)
+	if c.IsValid() {
+		t.Errorf("cert is not valid")
+	}
+
+	c.Cert.NotBefore = time.Now().Add(-1 * time.Hour)
+	c.Cert.NotAfter = time.Now().Add(1 * time.Hour)
+	if c.IsValid() {
+		t.Errorf("cert is not valid")
+	}
+
+	c = GenerateAuthorityCert(nil, 2*60*60*1000)
+	if !c.IsValid() {
+		t.Errorf("cert is valid")
+	}
+}
+
+func TestNeedRefresh(t *testing.T) {
+	c := &Cert{}
+	if !c.NeedRefresh() {
+		t.Errorf("cert is need refresh")
+	}
+
+	c.Cert = &x509.Certificate{}
+	if !c.NeedRefresh() {
+		t.Errorf("cert is need refresh")
+	}
+
+	c.CertPem = "test"
+	if !c.NeedRefresh() {
+		t.Errorf("cert is need refresh")
+	}
+
+	c.PrivateKey, _ = rsa.GenerateKey(rand.Reader, 4096)
+	if !c.NeedRefresh() {
+		t.Errorf("cert is need refresh")
+	}
+
+	c.Cert.NotBefore = time.Now().Add(1 * time.Hour)
+	if !c.NeedRefresh() {
+		t.Errorf("cert is not need refresh")
+	}
+
+	c.Cert.NotBefore = time.Now().Add(-1 * time.Hour)
+	c.Cert.NotAfter = time.Now().Add(-1 * time.Hour)
+	if !c.NeedRefresh() {
+		t.Errorf("cert is not need refresh")
+	}
+
+	c.Cert.NotBefore = time.Now().Add(-1 * time.Hour).Add(2 * 60 * -0.3 * time.Minute)
+	c.Cert.NotAfter = time.Now().Add(-1 * time.Hour).Add(2 * 60 * 0.7 * time.Minute)
+	if !c.NeedRefresh() {
+		t.Errorf("cert is need refresh")
+	}
+
+	c.Cert.NotAfter = time.Now().Add(1 * time.Hour)
+	if !c.NeedRefresh() {
+		t.Errorf("cert is need refresh")
+	}
+
+	c = GenerateAuthorityCert(nil, 2*60*60*1000)
+	if c.NeedRefresh() {
+		t.Errorf("cert is valid")
+	}
+}
+
+func TestGetTlsCert(t *testing.T) {
+	cert := GenerateAuthorityCert(nil, 2*60*60*1000)
+
+	tlsCert := cert.GetTlsCert()
+	if !reflect.DeepEqual(tlsCert.PrivateKey, cert.PrivateKey) {
+		t.Errorf("cert is not equal")
+	}
+
+	if tlsCert != cert.GetTlsCert() {
+		t.Errorf("cert is not equal")
+	}
+}
+
+func TestGetServerCert(t *testing.T) {
+	cert := GenerateAuthorityCert(nil, 24*60*60*1000)
+
+	s := &Storage{
+		AuthorityCert: cert,
+		Mutex:         &sync.Mutex{},
+		CaValidity:    24 * 60 * 60 * 1000,
+		CertValidity:  2 * 60 * 60 * 1000,
+	}
+
+	c := s.GetServerCert("localhost")
+
+	pool := x509.NewCertPool()
+	pool.AddCert(cert.Cert)
+	certificate, err := x509.ParseCertificate(c.Certificate[0])
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	_, err = certificate.Verify(x509.VerifyOptions{
+		Roots:   pool,
+		DNSName: "localhost",
+	})
+
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	if c != s.GetServerCert("localhost") {
+		t.Errorf("cert is not equal")
+	}
+
+	if c != s.GetServerCert("") {
+		t.Errorf("cert is not equal")
+	}
+
+	c = s.GetServerCert("newhost")
+
+	pool = x509.NewCertPool()
+	pool.AddCert(cert.Cert)
+	certificate, err = x509.ParseCertificate(c.Certificate[0])
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	_, err = certificate.Verify(x509.VerifyOptions{
+		Roots:   pool,
+		DNSName: "localhost",
+	})
+
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+
+	_, err = certificate.Verify(x509.VerifyOptions{
+		Roots:   pool,
+		DNSName: "newhost",
+	})
+
+	if err != nil {
+		t.Errorf(err.Error())
+		return
+	}
+}
+
+func TestRefreshServerCert(t *testing.T) {
+	logger.Init()
+	s := NewStorage(&config.Options{
+		CaValidity:   24 * 60 * 60 * 1000,
+		CertValidity: 10,
+	})
+	s.AuthorityCert = GenerateAuthorityCert(nil, 24*60*60*1000)
+
+	go s.RefreshServerCert()
+
+	c := s.GetServerCert("localhost")
+	origin := s.ServerCerts
+
+	for i := 0; i < 100; i++ {
+		// at most 10s
+		time.Sleep(100 * time.Millisecond)
+		if origin != s.ServerCerts {
+			break
+		}
+	}
+
+	if c == s.GetServerCert("localhost") {
+		t.Errorf("cert is not equal")
+	}
+
+	if reflect.DeepEqual(c, s.GetServerCert("localhost")) {
+		t.Errorf("cert is not equal")
+	}
+
+	s.StopChan <- os.Kill
+}
diff --git a/ca/pkg/cert/util.go b/ca/pkg/cert/util.go
index 9c038d8..90f5fc4 100644
--- a/ca/pkg/cert/util.go
+++ b/ca/pkg/cert/util.go
@@ -30,16 +30,11 @@ import (
 
 func DecodeCert(cert string) *x509.Certificate {
 	block, _ := pem.Decode([]byte(cert))
-	p, err := x509.ParseCertificate(block.Bytes)
-	if err != nil {
-		logger.Sugar.Warnf("Failed to parse public key. " + err.Error())
+	if block == nil {
+		logger.Sugar.Warnf("Failed to parse public key.")
 		return nil
 	}
-	return p
-}
-
-func DecodePub(cert string) *rsa.PublicKey {
-	p, err := x509.ParsePKCS1PublicKey([]byte(cert))
+	p, err := x509.ParseCertificate(block.Bytes)
 	if err != nil {
 		logger.Sugar.Warnf("Failed to parse public key. " + err.Error())
 		return nil
@@ -47,9 +42,12 @@ func DecodePub(cert string) *rsa.PublicKey {
 	return p
 }
 
-func DecodePri(cert string) *rsa.PrivateKey {
+func DecodePrivateKey(cert string) *rsa.PrivateKey {
 	block, _ := pem.Decode([]byte(cert))
-
+	if block == nil {
+		logger.Sugar.Warnf("Failed to parse private key.")
+		return nil
+	}
 	p, err := x509.ParsePKCS1PrivateKey(block.Bytes)
 	if err != nil {
 		logger.Sugar.Warnf("Failed to parse private key. " + err.Error())
@@ -58,11 +56,11 @@ func DecodePri(cert string) *rsa.PrivateKey {
 	return p
 }
 
-func CreateCA(rootCert *Cert, caValidity int64) *Cert {
+func GenerateAuthorityCert(rootCert *Cert, caValidity int64) *Cert {
 	cert := &x509.Certificate{
 		SerialNumber: big.NewInt(2019),
 		Subject: pkix.Name{
-			CommonName:   "Dubbo",
+			CommonName:   "Dubbo RA",
 			Organization: []string{"Apache Dubbo"},
 		},
 		Issuer: pkix.Name{
@@ -77,12 +75,12 @@ func CreateCA(rootCert *Cert, caValidity int64) *Cert {
 		BasicConstraintsValid: true,
 	}
 
-	caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
+	privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	caBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &caPrivKey.PublicKey, caPrivKey)
+	caBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &privateKey.PublicKey, privateKey)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -98,14 +96,14 @@ func CreateCA(rootCert *Cert, caValidity int64) *Cert {
 	}
 
 	return &Cert{
-		Cert:       cert,
+		Cert:       DecodeCert(caPEM.String()),
 		CertPem:    caPEM.String(),
-		PrivateKey: caPrivKey,
+		PrivateKey: privateKey,
 	}
 }
 
 func SignServerCert(authorityCert *Cert, serverName []string, certValidity int64) *Cert {
-	privKey, err := rsa.GenerateKey(rand.Reader, 4096)
+	privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -124,7 +122,7 @@ func SignServerCert(authorityCert *Cert, serverName []string, certValidity int64
 	}
 	cert.DNSNames = serverName
 
-	c, err := x509.CreateCertificate(rand.Reader, cert, authorityCert.Cert, &privKey.PublicKey, authorityCert.PrivateKey)
+	c, err := x509.CreateCertificate(rand.Reader, cert, authorityCert.Cert, &privateKey.PublicKey, authorityCert.PrivateKey)
 
 	certPem := new(bytes.Buffer)
 	err = pem.Encode(certPem, &pem.Block{
@@ -138,8 +136,40 @@ func SignServerCert(authorityCert *Cert, serverName []string, certValidity int64
 	return &Cert{
 		Cert:       cert,
 		CertPem:    certPem.String(),
-		PrivateKey: privKey,
+		PrivateKey: privateKey,
+	}
+}
+
+func GenerateCSR() (string, *rsa.PrivateKey, error) {
+	csrTemplate := x509.CertificateRequest{
+		Subject: pkix.Name{
+			CommonName:   "Dubbo",
+			Organization: []string{"Apache Dubbo"},
+		},
+	}
+
+	privateKey, err := rsa.GenerateKey(rand.Reader, 4096)
+	if err != nil {
+		log.Fatal(err)
+		return "", nil, err
+	}
+
+	csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, privateKey)
+	if err != nil {
+		return "", nil, err
+	}
+
+	csr := new(bytes.Buffer)
+	err = pem.Encode(csr, &pem.Block{
+		Type:  "CERTIFICATE REQUEST",
+		Bytes: csrBytes,
+	})
+
+	if err != nil {
+		logger.Sugar.Warnf("Failed to encode certificate. " + err.Error())
+		return "", nil, err
 	}
+	return csr.String(), privateKey, nil
 }
 
 func LoadCSR(csrString string) (*x509.CertificateRequest, error) {
diff --git a/ca/pkg/cert/util_test.go b/ca/pkg/cert/util_test.go
new file mode 100644
index 0000000..e07069d
--- /dev/null
+++ b/ca/pkg/cert/util_test.go
@@ -0,0 +1,161 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cert
+
+import "testing"
+
+func TestCSR(t *testing.T) {
+	csr, privateKey, err := GenerateCSR()
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	request, err := LoadCSR(csr)
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	cert := GenerateAuthorityCert(nil, 365*24*60*60*1000)
+
+	target, err := SignFromCSR(request, cert, 365*24*60*60*1000)
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	certificate := DecodeCert(target)
+
+	check := &Cert{
+		Cert:       certificate,
+		PrivateKey: privateKey,
+		CertPem:    target,
+	}
+
+	if !check.IsValid() {
+		t.Fatal("Cert is not valid")
+		return
+	}
+}
+
+func TestDecodeCert(t *testing.T) {
+	if DecodeCert("") != nil {
+		t.Fatal("DecodeCert should return nil")
+		return
+	}
+
+	if DecodeCert("123") != nil {
+		t.Fatal("DecodeCert should return nil")
+		return
+	}
+
+	if DecodeCert("-----BEGIN CERTIFICATE-----\n"+
+		"123\n"+
+		"-----END CERTIFICATE-----") != nil {
+		t.Fatal("DecodeCert should return nil")
+		return
+	}
+
+	if DecodeCert("-----BEGIN CERTIFICATE-----\n"+
+		"MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV\n"+
+		"BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX\n"+
+		"aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla\n"+
+		"Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0\n"+
+		"YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT\n"+
+		"BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7\n"+
+		"+L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu\n"+
+		"g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd\n"+
+		"Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV\n"+
+		"HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau\n"+
+		"sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m\n"+
+		"oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG\n"+
+		"Dfcog5wrJytaQ6UA0wE=\n"+
+		"-----END CERTIFICATE-----\n") == nil {
+		t.Fatal("DecodeCert should not return nil")
+		return
+	}
+}
+
+func TestDecodePrivateKey(t *testing.T) {
+	if DecodePrivateKey("") != nil {
+		t.Fatal("DecodePrivateKey should return nil")
+		return
+	}
+
+	if DecodePrivateKey("123") != nil {
+		t.Fatal("DecodePrivateKey should return nil")
+		return
+	}
+
+	if DecodePrivateKey("-----BEGIN PRIVATE KEY-----\n"+
+		"123\n"+
+		"-----END PRIVATE KEY-----\n") != nil {
+		t.Fatal("DecodePrivateKey should return nil")
+		return
+	}
+
+	if DecodePrivateKey("-----BEGIN PRIVATE KEY-----\n"+
+		"MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMBA3wVeTGHZR1Ry\n"+
+		"e/i+J8a2cu5gXwFV6TnObzGM7bLFCO5i9v4mLo4iFzPsHmWDUxKS3Y8iXbu0eYBl\n"+
+		"LoNY0lSvxDx33O+DuwMmVN+DzSD+Eod9zfvwOWHsazYCZT2PhNxnVWIuJXViY4JA\n"+
+		"HUGodjx+QAi6yCAurUZGvYXGgZSBAgMBAAECgYAxRi8i9BlFlufGSBVoGmydbJOm\n"+
+		"bwLKl9dP3o33ODSP9hok5y6A0w5plWk3AJSF1hPLleK9VcSKYGYnt0clmPVHF35g\n"+
+		"bx2rVK8dOT0mn7rz9Zr70jcSz1ETA2QonHZ+Y+niLmcic9At6hRtWiewblUmyFQm\n"+
+		"GwggIzi7LOyEUHrEcQJBAOXxyQvnLvtKzXiqcsW/K6rExqVJVk+KF0fzzVyMzTJx\n"+
+		"HRBxUVgvGdEJT7j+7P2kcTyafve0BBzDSPIaDyiJ+Y0CQQDWCb7jASFSbu5M3Zcd\n"+
+		"Gkr4ZKN1XO3VLQX10b22bQYdF45hrTN2tnzRvVUR4q86VVnXmiGiTqmLkXcA2WWf\n"+
+		"pHfFAkAhv9olUBo6MeF0i3frBEMRfm41hk0PwZHnMqZ6pgPcGnQMnMU2rzsXzkkQ\n"+
+		"OwJnvAIOxhJKovZTjmofdqmw5odlAkBYVUdRWjsNUTjJwj3GRf6gyq/nFMYWz3EB\n"+
+		"RWFdM1ttkDYzu45ctO2IhfHg4sPceDMO1s6AtKQmNI9/azkUjITdAkApNa9yFRzc\n"+
+		"TBaDNPd5KVd58LVIzoPQ6i7uMHteLXJUWqSroji6S3s4gKMFJ/dO+ZXIlgQgfJJJ\n"+
+		"ZDL4cdrdkeoM\n"+
+		"-----END PRIVATE KEY-----\n") != nil {
+		t.Fatal("DecodePrivateKey should return nil")
+		return
+	}
+
+	if DecodePrivateKey("-----BEGIN RSA PRIVATE KEY-----\n"+
+		"MIIEpgIBAAKCAQEAwQl8A5KYyOmXsz+Mk05NLWS9jHDhvJC1ekWgqOApwrb0Ecio\n"+
+		"tv5dirqAtuEX+dGRVftxJdtZHWto+gKy3H6Ae866FBFt7TWgTZFkt0XW3tMmUmNG\n"+
+		"bdzHAuZGK9+RlNNTNBTZJAx338kxM7/lqqOgEZig5SmX2Xt3u+DQjJPlsWB/lKDD\n"+
+		"OKOc93lGo/8chdmMv70inE/xv6LQ9nugRvBe1XfXafuHEUVyj2rzF1v9y7yF5Tek\n"+
+		"70wK/KV+O7ukBRc4SPwJ7YAWuofMhFneNtWGNHYaLShJBhvC+E7JXD+prJfHNdSc\n"+
+		"ORnTz/LjMWsLbD1lhr/p7vrWXujDSGM6ZDR6EwIDAQABAoIBAQCjqjPwH4HUjmDl\n"+
+		"RBMe7bt3qjsfcLGjm5mSQqh1piEiCtYioduR01ZiAcCRzYTzdWBg4x/Ktg/3ZpMJ\n"+
+		"rfISCltLHTodO63U+auhOI2I6fjE0YdjQPJ8wTwmVDDYj+Qxp36a4LY93yhfn4hM\n"+
+		"1P2XUMWtRZfc1AgAB7O7ol+PYPHVEX4n9ugbRDkn7/hpi05JPAOnGNimKDi61PpS\n"+
+		"rWpkAKYCC6q2hLTOW+EKvfNqUjuK/YAzPQD14zP7KRQ9kkezAluwwVbwwaI2jJ4x\n"+
+		"n6jHwPMOH1eKTQMtUg6Xxv59jBrcPmtD38dZvzzjBZDZYu4xcWJeeY4oP8/UE7uE\n"+
+		"pTFACvBRAoGBAMlErLppeVZcgcW9gOP98qV2CbWK/+aB3oJQEQBlKB3UtQSjCnZ7\n"+
+		"lLzxgMtDD+tcPocY5FY52MzJQ2UFgScSzW04JuBQPbsHcGmuzv/cahuB/S+xwB6m\n"+
+		"I2RXbFkgPPirJ9mqTeuNMwcXgAhoVbPV3otMq45EsxHubATit7QvczabAoGBAPWH\n"+
+		"yt0uxcf/j2k7EH3Ug5TkVKI8IftCM0fRs9eUzy2zPKTVRdTbQY75J/E2zkEQat2B\n"+
+		"8hEONkkV/ScLV5Oon4oeBxCRq17h37H5znkW2yNYSMNLcqUN58ZcVxsRSPj/Eoq5\n"+
+		"Ngotll+JmITrxtd6NpFcGqrDQ/KV9uM1AoqN4EXpAoGBALAXeLRD8dhAaX4TdgCD\n"+
+		"v9dKNeZzLb+EYqRK3wUke/vVjWb4KwBM0W6aMWAlVXlLpJ1YhvZ1+Bv7/w4UydHg\n"+
+		"3oCvfzwEmG3ZbV3ZhtxPATr9+QHQl9F49EAnSPGVhiLexKfpG/F6AWo0Al3Ywxrr\n"+
+		"hKEFvJdlvfJzUmjX33gzh67/AoGBAMAnqBJ2On+NeFUozn1LxjbOg5X8bbPQWYXJ\n"+
+		"jnAXnBTuA3YVG3O8rJASWroi5ERzbs8wlZvXfZCxTtAxxjZfb4yOd4T2HCJDr+f/\n"+
+		"0yFdS99bhoahE3YtbckGF32th2inZ4F99db9WoQmkWDljVax5ObaKFygORsvVmr2\n"+
+		"36hD5NORAoGBALKQZ6j9RYCC3NiV46K6GhN7RMu70u+/ET2skEdChn3Mu0OTYzqa\n"+
+		"+qOCXvV+RWEiUoa2JX7UkSagEs+1O404afQv2+qnhdUOskxzUD+smQJBGOrXmdMq\n"+
+		"ubzSn24LsPYWYGWsgl3AJ+n8rmVMXgPaWZQD9qHkZD9Oe2wwI9W+4K74\n"+
+		"-----END RSA PRIVATE KEY-----\n") == nil {
+		t.Fatal("DecodePrivateKey should not return nil")
+		return
+	}
+}
diff --git a/ca/pkg/k8s/client.go b/ca/pkg/k8s/client.go
index 737610a..fa3636a 100644
--- a/ca/pkg/k8s/client.go
+++ b/ca/pkg/k8s/client.go
@@ -29,11 +29,23 @@ import (
 	"path/filepath"
 )
 
-type Client struct {
+type Client interface {
+	Init() bool
+	GetAuthorityCert(namespace string) (string, string)
+	UpdateAuthorityCert(cert string, pri string, namespace string)
+	UpdateAuthorityPublicKey(cert string) bool
+	VerifyServiceAccount(token string) bool
+}
+
+type ClientImpl struct {
 	kubeClient *kubernetes.Clientset
 }
 
-func (c *Client) Init() bool {
+func NewClient() Client {
+	return &ClientImpl{}
+}
+
+func (c *ClientImpl) Init() bool {
 	config, err := rest.InClusterConfig()
 	if err != nil {
 		logger.Sugar.Infof("Failed to load config from Pod. Will fall back to kube config file.")
@@ -64,7 +76,7 @@ func (c *Client) Init() bool {
 	return true
 }
 
-func (c *Client) GetAuthorityCert(namespace string) (string, string) {
+func (c *ClientImpl) GetAuthorityCert(namespace string) (string, string) {
 	s, err := c.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), "dubbo-ca-secret", metav1.GetOptions{})
 	if err != nil {
 		logger.Sugar.Warnf("Unable to get authority cert secret from kubernetes. " + err.Error())
@@ -72,7 +84,7 @@ func (c *Client) GetAuthorityCert(namespace string) (string, string) {
 	return string(s.Data["cert.pem"]), string(s.Data["pri.pem"])
 }
 
-func (c *Client) UpdateAuthorityCert(cert string, pri string, namespace string) {
+func (c *ClientImpl) UpdateAuthorityCert(cert string, pri string, namespace string) {
 	s, err := c.kubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), "dubbo-ca-secret", metav1.GetOptions{})
 	if err != nil {
 		logger.Sugar.Warnf("Unable to get ca secret from kubernetes. Will try to create. " + err.Error())
@@ -106,7 +118,7 @@ func (c *Client) UpdateAuthorityCert(cert string, pri string, namespace string)
 	}
 }
 
-func (c *Client) UpdateAuthorityPublicKey(cert string) bool {
+func (c *ClientImpl) UpdateAuthorityPublicKey(cert string) bool {
 	ns, err := c.kubeClient.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
 	if err != nil {
 		logger.Sugar.Warnf("Failed to get namespaces. " + err.Error())
@@ -149,7 +161,7 @@ func (c *Client) UpdateAuthorityPublicKey(cert string) bool {
 	return true
 }
 
-func (c *Client) VerifyServiceAccount(token string) bool {
+func (c *ClientImpl) VerifyServiceAccount(token string) bool {
 	tokenReview := &k8sauth.TokenReview{
 		Spec: k8sauth.TokenReviewSpec{
 			Token: token,
diff --git a/ca/pkg/security/server.go b/ca/pkg/security/server.go
index 744be74..1b71ee4 100644
--- a/ca/pkg/security/server.go
+++ b/ca/pkg/security/server.go
@@ -28,36 +28,42 @@ import (
 	"log"
 	"math"
 	"net"
+	"os"
 	"strconv"
-	"sync"
 	"time"
 )
 
 type Server struct {
+	StopChan chan os.Signal
+
 	Options     *config.Options
 	CertStorage *cert.Storage
 
-	KubeClient *k8s.Client
+	KubeClient k8s.Client
 
 	CertificateServer *v1alpha1.DubboCertificateServiceServerImpl
 	PlainServer       *grpc.Server
 	SecureServer      *grpc.Server
 }
 
+func NewServer(options *config.Options) *Server {
+	return &Server{
+		Options:  options,
+		StopChan: make(chan os.Signal, 1),
+	}
+}
+
 func (s *Server) Init() {
 	// TODO bypass k8s work
-	s.KubeClient = &k8s.Client{}
+	if s.KubeClient == nil {
+		s.KubeClient = k8s.NewClient()
+	}
 	if !s.KubeClient.Init() {
 		panic("Failed to create kubernetes client.")
 	}
 
-	s.CertStorage = &cert.Storage{
-		AuthorityCert: &cert.Cert{},
-		TrustedCert:   []*cert.Cert{},
-		Mutex:         &sync.Mutex{},
-		CertValidity:  s.Options.CertValidity,
-		CaValidity:    s.Options.CaValidity,
-	}
+	s.CertStorage = cert.NewStorage(s.Options)
+	s.StopChan = s.StopChan
 	go s.CertStorage.RefreshServerCert()
 
 	// TODO inject pod based on Webhook
@@ -99,7 +105,7 @@ func (s *Server) LoadAuthorityCert() {
 	if certStr != "" && priStr != "" {
 		s.CertStorage.AuthorityCert.Cert = cert.DecodeCert(certStr)
 		s.CertStorage.AuthorityCert.CertPem = certStr
-		s.CertStorage.AuthorityCert.PrivateKey = cert.DecodePri(priStr)
+		s.CertStorage.AuthorityCert.PrivateKey = cert.DecodePrivateKey(priStr)
 	}
 
 	s.RefreshAuthorityCert()
@@ -114,7 +120,7 @@ func (s *Server) ScheduleRefreshAuthorityCert() {
 			logger.Sugar.Infof("Authority cert is invalid, refresh it.")
 			// TODO lock if multi server
 			// TODO refresh signed cert
-			s.CertStorage.AuthorityCert = cert.CreateCA(s.CertStorage.RootCert, s.Options.CaValidity)
+			s.CertStorage.AuthorityCert = cert.GenerateAuthorityCert(s.CertStorage.RootCert, s.Options.CaValidity)
 			s.KubeClient.UpdateAuthorityCert(s.CertStorage.AuthorityCert.CertPem, cert.EncodePri(s.CertStorage.AuthorityCert.PrivateKey), s.Options.Namespace)
 			if s.KubeClient.UpdateAuthorityPublicKey(s.CertStorage.AuthorityCert.CertPem) {
 				logger.Sugar.Infof("Write ca to config maps success.")
@@ -122,6 +128,13 @@ func (s *Server) ScheduleRefreshAuthorityCert() {
 				logger.Sugar.Warnf("Write ca to config maps failed.")
 			}
 		}
+
+		select {
+		case <-s.StopChan:
+			return
+		default:
+			continue
+		}
 	}
 }
 
@@ -130,7 +143,7 @@ func (s *Server) RefreshAuthorityCert() {
 		logger.Sugar.Infof("Load authority cert from kubernetes secrect success.")
 	} else {
 		logger.Sugar.Warnf("Load authority cert from kubernetes secrect failed.")
-		s.CertStorage.AuthorityCert = cert.CreateCA(s.CertStorage.RootCert, s.Options.CaValidity)
+		s.CertStorage.AuthorityCert = cert.GenerateAuthorityCert(s.CertStorage.RootCert, s.Options.CaValidity)
 
 		// TODO lock if multi server
 		s.KubeClient.UpdateAuthorityCert(s.CertStorage.AuthorityCert.CertPem, cert.EncodePri(s.CertStorage.AuthorityCert.PrivateKey), s.Options.Namespace)
diff --git a/ca/pkg/v1alpha1/ca_impl.go b/ca/pkg/v1alpha1/ca_impl.go
index ac23c89..1e8e218 100644
--- a/ca/pkg/v1alpha1/ca_impl.go
+++ b/ca/pkg/v1alpha1/ca_impl.go
@@ -31,11 +31,24 @@ type DubboCertificateServiceServerImpl struct {
 	UnimplementedDubboCertificateServiceServer
 	Options     *config.Options
 	CertStorage *cert.Storage
-	KubeClient  *k8s.Client
+	KubeClient  k8s.Client
 }
 
 func (s *DubboCertificateServiceServerImpl) CreateCertificate(c context.Context, req *DubboCertificateRequest) (*DubboCertificateResponse, error) {
-	csr, _ := cert.LoadCSR(req.Csr)
+	if req.Csr == "" {
+		return &DubboCertificateResponse{
+			Success: false,
+			Message: "CSR is empty.",
+		}, nil
+	}
+
+	csr, err := cert.LoadCSR(req.Csr)
+	if csr == nil || err != nil {
+		return &DubboCertificateResponse{
+			Success: false,
+			Message: "Decode csr failed.",
+		}, nil
+	}
 	p, _ := peer.FromContext(c)
 
 	if s.Options.EnableKubernetes {
@@ -78,13 +91,6 @@ func (s *DubboCertificateServiceServerImpl) CreateCertificate(c context.Context,
 	}
 
 	// TODO check server token
-	if csr == nil {
-		logger.Sugar.Warnf("Failed to decode csr. RemoteAddr: %s", p.Addr.String())
-		return &DubboCertificateResponse{
-			Success: false,
-			Message: "Failed to read csr",
-		}, nil
-	}
 	certPem, err := cert.SignFromCSR(csr, s.CertStorage.AuthorityCert, s.Options.CertValidity)
 	if err != nil {
 		logger.Sugar.Warnf("Failed to sign certificate from csr: %v. RemoteAddr: %s", err, p.Addr.String())
diff --git a/ca/pkg/v1alpha1/ca_impl_test.go b/ca/pkg/v1alpha1/ca_impl_test.go
new file mode 100644
index 0000000..e3de6bf
--- /dev/null
+++ b/ca/pkg/v1alpha1/ca_impl_test.go
@@ -0,0 +1,276 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package v1alpha1
+
+import (
+	"github.com/apache/dubbo-admin/ca/pkg/cert"
+	"github.com/apache/dubbo-admin/ca/pkg/config"
+	"github.com/apache/dubbo-admin/ca/pkg/k8s"
+	"github.com/apache/dubbo-admin/ca/pkg/logger"
+	"golang.org/x/net/context"
+	"google.golang.org/grpc/metadata"
+	"google.golang.org/grpc/peer"
+	"net"
+	"testing"
+)
+
+type MockKubeClient struct {
+	k8s.Client
+}
+
+func (c MockKubeClient) VerifyServiceAccount(token string) bool {
+	return "expceted-token" == token
+}
+
+type fakeAddr struct {
+	net.Addr
+}
+
+func (f *fakeAddr) String() string {
+	return ""
+}
+
+func TestCSRFailed(t *testing.T) {
+	logger.Init()
+
+	md := metadata.MD{}
+	md["authorization"] = []string{"Bearer 123"}
+	c := metadata.NewIncomingContext(context.TODO(), metadata.MD{})
+	c = peer.NewContext(c, &peer.Peer{Addr: &fakeAddr{}})
+
+	options := &config.Options{
+		EnableKubernetes: false,
+		CertValidity:     24 * 60 * 60 * 1000,
+		CaValidity:       365 * 24 * 60 * 60 * 1000,
+	}
+	storage := cert.NewStorage(options)
+	storage.AuthorityCert = cert.GenerateAuthorityCert(nil, options.CaValidity)
+
+	kubeClient := &MockKubeClient{}
+	impl := &DubboCertificateServiceServerImpl{
+		Options:     options,
+		CertStorage: storage,
+		KubeClient:  kubeClient.Client,
+	}
+
+	certificate, err := impl.CreateCertificate(c, &DubboCertificateRequest{
+		Csr: "",
+	})
+
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	if certificate.Success {
+		t.Fatal("Should sign failed")
+		return
+	}
+
+	certificate, err = impl.CreateCertificate(c, &DubboCertificateRequest{
+		Csr: "123",
+	})
+
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	if certificate.Success {
+		t.Fatal("Should sign failed")
+		return
+	}
+
+	certificate, err = impl.CreateCertificate(c, &DubboCertificateRequest{
+		Csr: "-----BEGIN CERTIFICATE-----\n" +
+			"123\n" +
+			"-----END CERTIFICATE-----",
+	})
+
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	if certificate.Success {
+		t.Fatal("Should sign failed")
+		return
+	}
+}
+
+func TestTokenFailed(t *testing.T) {
+	logger.Init()
+
+	p := peer.NewContext(context.TODO(), &peer.Peer{Addr: &fakeAddr{}})
+
+	options := &config.Options{
+		EnableKubernetes: true,
+		CertValidity:     24 * 60 * 60 * 1000,
+		CaValidity:       365 * 24 * 60 * 60 * 1000,
+	}
+	storage := cert.NewStorage(options)
+	storage.AuthorityCert = cert.GenerateAuthorityCert(nil, options.CaValidity)
+
+	kubeClient := &MockKubeClient{}
+	impl := &DubboCertificateServiceServerImpl{
+		Options:     options,
+		CertStorage: storage,
+		KubeClient:  kubeClient,
+	}
+
+	csr, privateKey, err := cert.GenerateCSR()
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	certificate, err := impl.CreateCertificate(p, &DubboCertificateRequest{
+		Csr: csr,
+	})
+
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	if certificate.Success {
+		t.Fatal("Should sign failed")
+		return
+	}
+
+	md := metadata.MD{}
+	md["authorization"] = []string{"123"}
+	c := metadata.NewIncomingContext(p, md)
+
+	certificate, err = impl.CreateCertificate(c, &DubboCertificateRequest{
+		Csr: csr,
+	})
+
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	if certificate.Success {
+		t.Fatal("Should sign failed")
+		return
+	}
+
+	md = metadata.MD{}
+	md["authorization"] = []string{"Bearer 123"}
+	c = metadata.NewIncomingContext(p, md)
+
+	certificate, err = impl.CreateCertificate(c, &DubboCertificateRequest{
+		Csr: csr,
+	})
+
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	if certificate.Success {
+		t.Fatal("Should sign failed")
+		return
+	}
+
+	md = metadata.MD{}
+	md["authorization"] = []string{"Bearer expceted-token"}
+	c = metadata.NewIncomingContext(p, md)
+
+	certificate, err = impl.CreateCertificate(c, &DubboCertificateRequest{
+		Csr: csr,
+	})
+
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	if !certificate.Success {
+		t.Fatal("Sign failed")
+		return
+	}
+
+	generatedCert := cert.DecodeCert(certificate.CertPem)
+	c2 := &cert.Cert{
+		Cert:       generatedCert,
+		CertPem:    certificate.CertPem,
+		PrivateKey: privateKey,
+	}
+
+	if !c2.IsValid() {
+		t.Fatal("Cert is not valid")
+		return
+	}
+
+}
+
+func TestSuccess(t *testing.T) {
+	logger.Init()
+
+	md := metadata.MD{}
+	md["authorization"] = []string{"Bearer 123"}
+	c := metadata.NewIncomingContext(context.TODO(), metadata.MD{})
+	c = peer.NewContext(c, &peer.Peer{Addr: &fakeAddr{}})
+
+	options := &config.Options{
+		EnableKubernetes: false,
+		CertValidity:     24 * 60 * 60 * 1000,
+		CaValidity:       365 * 24 * 60 * 60 * 1000,
+	}
+	storage := cert.NewStorage(options)
+	storage.AuthorityCert = cert.GenerateAuthorityCert(nil, options.CaValidity)
+
+	kubeClient := &MockKubeClient{}
+	impl := &DubboCertificateServiceServerImpl{
+		Options:     options,
+		CertStorage: storage,
+		KubeClient:  kubeClient,
+	}
+
+	csr, privateKey, err := cert.GenerateCSR()
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	certificate, err := impl.CreateCertificate(c, &DubboCertificateRequest{
+		Csr: csr,
+	})
+
+	if err != nil {
+		t.Fatal(err)
+		return
+	}
+
+	if !certificate.Success {
+		t.Fatal("Sign failed")
+		return
+	}
+
+	generatedCert := cert.DecodeCert(certificate.CertPem)
+	c2 := &cert.Cert{
+		Cert:       generatedCert,
+		CertPem:    certificate.CertPem,
+		PrivateKey: privateKey,
+	}
+
+	if !c2.IsValid() {
+		t.Fatal("Cert is not valid")
+		return
+	}
+}