From 11ec003bdbab5f0ef5f39e76252ad4f5485d87ea Mon Sep 17 00:00:00 2001 From: pingcap-github-bot Date: Fri, 6 Mar 2020 10:31:16 +0800 Subject: [PATCH] server: support check the "CommanName" of tls-cert for status-port(http/grpc) (#15137) (#15164) --- config/config.go | 15 ++++---- server/http_handler_test.go | 16 +++++++++ server/http_status.go | 43 ++++++++++++++++++++--- server/tidb_test.go | 51 ++++++++++++++++++++++++++++ tests/cncheckcert/ca-key.pem | 9 +++++ tests/cncheckcert/ca-tidb-test-1.crt | 10 ++++++ tests/cncheckcert/client-cert-1.pem | 10 ++++++ tests/cncheckcert/client-cert-2.pem | 10 ++++++ tests/cncheckcert/client-key-1.pem | 9 +++++ tests/cncheckcert/client-key-2.pem | 9 +++++ tests/cncheckcert/server-cert.pem | 10 ++++++ tests/cncheckcert/server-key.pem | 9 +++++ 12 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 tests/cncheckcert/ca-key.pem create mode 100644 tests/cncheckcert/ca-tidb-test-1.crt create mode 100644 tests/cncheckcert/client-cert-1.pem create mode 100644 tests/cncheckcert/client-cert-2.pem create mode 100644 tests/cncheckcert/client-key-1.pem create mode 100644 tests/cncheckcert/client-key-2.pem create mode 100644 tests/cncheckcert/server-cert.pem create mode 100644 tests/cncheckcert/server-key.pem diff --git a/config/config.go b/config/config.go index 69fd37b08a299..afeb14dafd0b5 100644 --- a/config/config.go +++ b/config/config.go @@ -124,13 +124,14 @@ type Log struct { // Security is the security section of the config. type Security struct { - SkipGrantTable bool `toml:"skip-grant-table" json:"skip-grant-table"` - SSLCA string `toml:"ssl-ca" json:"ssl-ca"` - SSLCert string `toml:"ssl-cert" json:"ssl-cert"` - SSLKey string `toml:"ssl-key" json:"ssl-key"` - ClusterSSLCA string `toml:"cluster-ssl-ca" json:"cluster-ssl-ca"` - ClusterSSLCert string `toml:"cluster-ssl-cert" json:"cluster-ssl-cert"` - ClusterSSLKey string `toml:"cluster-ssl-key" json:"cluster-ssl-key"` + SkipGrantTable bool `toml:"skip-grant-table" json:"skip-grant-table"` + SSLCA string `toml:"ssl-ca" json:"ssl-ca"` + SSLCert string `toml:"ssl-cert" json:"ssl-cert"` + SSLKey string `toml:"ssl-key" json:"ssl-key"` + ClusterSSLCA string `toml:"cluster-ssl-ca" json:"cluster-ssl-ca"` + ClusterSSLCert string `toml:"cluster-ssl-cert" json:"cluster-ssl-cert"` + ClusterSSLKey string `toml:"cluster-ssl-key" json:"cluster-ssl-key"` + ClusterVerifyCN []string `toml:"cluster-verify-cn" json:"cluster-verify-cn"` } // The ErrConfigValidationFailed error is used so that external callers can do a type assertion diff --git a/server/http_handler_test.go b/server/http_handler_test.go index 2420e354e1ba2..1bcae5671192b 100644 --- a/server/http_handler_test.go +++ b/server/http_handler_test.go @@ -15,6 +15,9 @@ package server import ( "bytes" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" "database/sql" "encoding/base64" "encoding/hex" @@ -879,3 +882,16 @@ func (ts *HTTPHandlerTestSuite) TestAllServerInfo(c *C) { c.Assert(serverInfo.GitHash, Equals, printer.TiDBGitHash) c.Assert(serverInfo.ID, Equals, ddl.GetID()) } + +func (ts *HTTPHandlerTestSuite) TestCheckCN(c *C) { + s := &Server{cfg: &config.Config{Security: config.Security{ClusterVerifyCN: []string{"a ", "b", "c"}}}} + tlsConfig := &tls.Config{} + s.setCNChecker(tlsConfig) + c.Assert(tlsConfig.VerifyPeerCertificate, NotNil) + err := tlsConfig.VerifyPeerCertificate(nil, [][]*x509.Certificate{{{Subject: pkix.Name{CommonName: "a"}}}}) + c.Assert(err, IsNil) + err = tlsConfig.VerifyPeerCertificate(nil, [][]*x509.Certificate{{{Subject: pkix.Name{CommonName: "b"}}}}) + c.Assert(err, IsNil) + err = tlsConfig.VerifyPeerCertificate(nil, [][]*x509.Certificate{{{Subject: pkix.Name{CommonName: "d"}}}}) + c.Assert(err, NotNil) +} diff --git a/server/http_status.go b/server/http_status.go index 822df86feea28..b70354d452217 100644 --- a/server/http_status.go +++ b/server/http_status.go @@ -17,6 +17,8 @@ import ( "archive/zip" "bytes" "context" + "crypto/tls" + "crypto/x509" "encoding/json" "fmt" "net" @@ -253,15 +255,48 @@ func (s *Server) startHTTPServer() { logutil.Logger(context.Background()).Info("for status and metrics report", zap.String("listening on addr", addr)) s.statusServer = &http.Server{Addr: addr, Handler: CorsHandler{handler: serverMux, cfg: s.cfg}} + ln, err := net.Listen("tcp", addr) + if err != nil { + logutil.Logger(context.Background()).Info("listen failed", zap.Error(err)) + return + } + if len(s.cfg.Security.ClusterSSLCA) != 0 { - err = s.statusServer.ListenAndServeTLS(s.cfg.Security.ClusterSSLCert, s.cfg.Security.ClusterSSLKey) - } else { - err = s.statusServer.ListenAndServe() + tlsConfig, err := s.cfg.Security.ToTLSConfig() + if err != nil { + logutil.Logger(context.Background()).Error("invalid TLS config", zap.Error(err)) + return + } + tlsConfig = s.setCNChecker(tlsConfig) + ln = tls.NewListener(ln, tlsConfig) } + err = s.statusServer.Serve(ln) if err != nil { - logutil.Logger(context.Background()).Info("listen failed", zap.Error(err)) + logutil.Logger(context.Background()).Info("serve status port failed", zap.Error(err)) + } +} + +func (s *Server) setCNChecker(tlsConfig *tls.Config) *tls.Config { + if tlsConfig != nil && len(s.cfg.Security.ClusterVerifyCN) != 0 { + checkCN := make(map[string]struct{}) + for _, cn := range s.cfg.Security.ClusterVerifyCN { + cn = strings.TrimSpace(cn) + checkCN[cn] = struct{}{} + } + tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + for _, chain := range verifiedChains { + if len(chain) != 0 { + if _, match := checkCN[chain[0].Subject.CommonName]; match { + return nil + } + } + } + return errors.Errorf("client certificate authentication failed. The Common Name from the client certificate was not found in the configuration cluster-verify-cn with value: %s", s.cfg.Security.ClusterVerifyCN) + } + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert } + return tlsConfig } // status of TiDB. diff --git a/server/tidb_test.go b/server/tidb_test.go index f75900698c750..3f0f673f561ac 100644 --- a/server/tidb_test.go +++ b/server/tidb_test.go @@ -22,9 +22,12 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" "io/ioutil" "math/big" + "net/http" "os" + "path/filepath" "time" "github.com/go-sql-driver/mysql" @@ -157,6 +160,54 @@ func (ts *TidbTestSuite) TestStatusAPI(c *C) { runTestStatusAPI(c) } +func (ts *TidbTestSuite) TestStatusAPIWithTLSCNCheck(c *C) { + c.Skip("need add ca-tidb-test-1.crt to OS") + root := filepath.Join(os.Getenv("GOPATH"), "/src/github.com/pingcap/tidb") + ca := filepath.Join(root, "/tests/cncheckcert/ca-tidb-test-1.crt") + statusURL := fmt.Sprintf("%s://localhost:%d%s", "https", 4100, "/status") + + cfg := config.NewConfig() + cfg.Status.StatusPort = 4100 + cfg.Security.ClusterSSLCA = ca + cfg.Security.ClusterSSLCert = filepath.Join(root, "/tests/cncheckcert/server-cert.pem") + cfg.Security.ClusterSSLKey = filepath.Join(root, "/tests/cncheckcert/server-key.pem") + cfg.Security.ClusterVerifyCN = []string{"tidb-client-2"} + server, err := NewServer(cfg, ts.tidbdrv) + c.Assert(err, IsNil) + go server.Run() + time.Sleep(time.Millisecond * 100) + + hc := newTLSHttpClient(c, ca, + filepath.Join(root, "/tests/cncheckcert/client-cert-1.pem"), + filepath.Join(root, "/tests/cncheckcert/client-key-1.pem"), + ) + _, err = hc.Get(statusURL) + c.Assert(err, NotNil) + + hc = newTLSHttpClient(c, ca, + filepath.Join(root, "/tests/cncheckcert/client-cert-2.pem"), + filepath.Join(root, "/tests/cncheckcert/client-key-2.pem"), + ) + _, err = hc.Get(statusURL) + c.Assert(err, IsNil) +} + +func newTLSHttpClient(c *C, caFile, certFile, keyFile string) *http.Client { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + c.Assert(err, IsNil) + caCert, err := ioutil.ReadFile(caFile) + c.Assert(err, IsNil) + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + InsecureSkipVerify: true, + } + tlsConfig.BuildNameToCertificate() + return &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}} +} + func (ts *TidbTestSuite) TestMultiStatements(c *C) { c.Parallel() runTestMultiStatements(c) diff --git a/tests/cncheckcert/ca-key.pem b/tests/cncheckcert/ca-key.pem new file mode 100644 index 0000000000000..2845bda86b81f --- /dev/null +++ b/tests/cncheckcert/ca-key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBQwIBAAJDAL6zaGJBgNTBNhtGTfSXnbzoZa1LlfCpFlIwtXc1YBBtl9IIzRI5 +j2Cyd/DTYjO0vniHGIHv7H64336szhSGOcHV5QIDAQABAkIpDH9MnyL3KPvXlSOU +oco/bprsWZfV7N+0I238Ug3ym1QyCK3ue8m/bJveL9AXCwJWMLdvHQoiyCFnaQ6f +Ay2hGYUCIgD4YcAP4aIJL1H9vo0vmXQzFZJzklyOUcmRm00Ulvie5dsCIgDEjMGk +QZoiR9ammPSc1IKF/c6THtd1sA0rW9Vh0sCBXz8CIgCMkq4jjtyo/BoYVRcM4HmO +S+A1/pjZh1pgSRfH1mXhcE8CITRT5Rn9/TMzPQqNnlJCoZ1avSyeAW7ruBXbFSw+ +F9JZsQIhMvQjbm0ygrSwOlvRhWOzAtUKSxcs7JKfxgt9/5/XIV4C +-----END RSA PRIVATE KEY----- diff --git a/tests/cncheckcert/ca-tidb-test-1.crt b/tests/cncheckcert/ca-tidb-test-1.crt new file mode 100644 index 0000000000000..cd5e961116f5a --- /dev/null +++ b/tests/cncheckcert/ca-tidb-test-1.crt @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBXzCCAQegAwIBAgIBADANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlUaURC +IENBIDUwHhcNMjAwMzA0MTI0MzIxWhcNMjAwMzA0MTM0MzIxWjAUMRIwEAYDVQQD +EwlUaURCIENBIDUwXjANBgkqhkiG9w0BAQEFAANNADBKAkMAvrNoYkGA1ME2G0ZN +9JedvOhlrUuV8KkWUjC1dzVgEG2X0gjNEjmPYLJ38NNiM7S+eIcYge/sfrjffqzO +FIY5wdXlAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwICpDAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAND +AKfu05xLZQ6PVMyYPjpo/RI/WchaLFRbBZKpdu+/QMaxreH7/xAqDfnslTVblWLo +uGxIrIOBL/1jR8CoAhBB1VAoJw== +-----END CERTIFICATE----- diff --git a/tests/cncheckcert/client-cert-1.pem b/tests/cncheckcert/client-cert-1.pem new file mode 100644 index 0000000000000..e6e442a649be6 --- /dev/null +++ b/tests/cncheckcert/client-cert-1.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBYDCCAQigAwIBAgIBATANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlUaURC +IENBIDUwHhcNMjAwMzA0MTI0MzIxWhcNMjAwMzA0MTM0MzIxWjAYMRYwFAYDVQQD +Ew10aWRiLWNsaWVudC0xMF4wDQYJKoZIhvcNAQEBBQADTQAwSgJDAMMfWMiDqdQE +xy0kE/W+V+OmYZGit7iGk+BzMe6G0w9fFmJt8ajwmHp5dMT5AmJpNF/i089Wej3K +SdEkWhWa96BBOwIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQAD +QwAnXNy43kWtlyelP1M5s0h4KNkmYBe6McaGV5nvhD/dwFfviY1dMRQ4ChgBgc5y +vv2r0fnRd2XF+81EmNGQk79Xg9A= +-----END CERTIFICATE----- diff --git a/tests/cncheckcert/client-cert-2.pem b/tests/cncheckcert/client-cert-2.pem new file mode 100644 index 0000000000000..820f3c9b17f0a --- /dev/null +++ b/tests/cncheckcert/client-cert-2.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBYDCCAQigAwIBAgIBATANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlUaURC +IENBIDUwHhcNMjAwMzA0MTI0MzIxWhcNMjAwMzA0MTM0MzIxWjAYMRYwFAYDVQQD +Ew10aWRiLWNsaWVudC0yMF4wDQYJKoZIhvcNAQEBBQADTQAwSgJDANuIi1RlJKHF +KJg5D/fblj8FfRaay9CLJOhDZkabJTuMHerlpY0vf8wsY15khaJU/dRiJeAT+lvg +V4CluJLSSLuIEQIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQAD +QwABsC2+tggurK8iUDmuaEKOqGnQdHrMMohDnSjmuS+X9xYcLlix7Haf53fBhs8B +kvUiPFk8QEiOi7xttaMHKeDLnRE= +-----END CERTIFICATE----- diff --git a/tests/cncheckcert/client-key-1.pem b/tests/cncheckcert/client-key-1.pem new file mode 100644 index 0000000000000..79a8de46f434d --- /dev/null +++ b/tests/cncheckcert/client-key-1.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBRQIBAAJDAMMfWMiDqdQExy0kE/W+V+OmYZGit7iGk+BzMe6G0w9fFmJt8ajw +mHp5dMT5AmJpNF/i089Wej3KSdEkWhWa96BBOwIDAQABAkJ4ps1zT1aX70xpsUFW +Vxhpf9wc/Yy04SJXS2O4pk2j15seJ9chhaolp26mVm1w/jyV/k6TwuKo0pM+F4pI +ePJNQQECIgDLWTCV6E1KGmprVnm/WRqUTewcNzfsXFHUlPSuIhTxUZsCIgD1pOfU +DpzwOoZb7XUogqkLKleaWWm22ffHuvuT3+ozGOECIgDDDS9Ea8pPTV1MzmsDtyV+ +oevb+L9ksf0wKx00Nq7d9wcCIgCX/++4B0bTW9OSBLCvXZKOpyfICbXhgKTTQX+0 +9CRuc+ECIgCqTejjhRnhz3rYD0wWqRRsXT/144RmiIpBslRp+HJ+5dg= +-----END RSA PRIVATE KEY----- diff --git a/tests/cncheckcert/client-key-2.pem b/tests/cncheckcert/client-key-2.pem new file mode 100644 index 0000000000000..f1555c589f1d5 --- /dev/null +++ b/tests/cncheckcert/client-key-2.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBQgIBAAJDANuIi1RlJKHFKJg5D/fblj8FfRaay9CLJOhDZkabJTuMHerlpY0v +f8wsY15khaJU/dRiJeAT+lvgV4CluJLSSLuIEQIDAQABAkJw/adQqbof9Pz+1CfO +11tOVoHaV5PdYzB8xuvmHUYdjvCG4WJcfFFkVipqE8VhJsS2Onzb7BFJ7gOqxCV6 +9+FoYgECIgD1IP2jXxqOJgI3p78YaCJnBlRUMIu/+g+MmGPcLqg8taECIgDlRPYh +m0kOwtmzTtTI5sOLDgDPew16p8jyBvBPxsk53HECIW6W/rc5DeL5tOBlFqqtOHAg +g+Ujrbjj2SYGDm9kwVP6YQIhPd/xqTo2alRt2nWA+cNFrMaXs2cbSSn1ElSLEIyu +i/4RAiFcI5yrNMt2XwyKo/A+hzFvJeuQ6xTs9I1KMYbsngXJ58k= +-----END RSA PRIVATE KEY----- diff --git a/tests/cncheckcert/server-cert.pem b/tests/cncheckcert/server-cert.pem new file mode 100644 index 0000000000000..2b66f1c10b920 --- /dev/null +++ b/tests/cncheckcert/server-cert.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBYDCCAQigAwIBAgIBATANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlUaURC +IENBIDUwHhcNMjAwMzA0MTI0MzIxWhcNMjAwMzA0MTM0MzIxWjAYMRYwFAYDVQQD +Ew10aWRiLXNlcnZlci0zMF4wDQYJKoZIhvcNAQEBBQADTQAwSgJDAM+ez7yZLKp9 +spGiUqqInrH8iTZ8JPa3C8K8bYw/Fc/W028sVvf6+VLC1ZP0cJX4Lx3iyyyIfzjG +KV4+MVcBOlfn4wIDAQABoz8wPTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQAD +QwCUAO+eDYvVXBIqpNWr/h513TTSzZa+ELNOrYNGr2MgQ6j8TW0+J+J4JGgj0Wm7 +1haBMbGShAHlXYK81ZShtnbPpFI= +-----END CERTIFICATE----- diff --git a/tests/cncheckcert/server-key.pem b/tests/cncheckcert/server-key.pem new file mode 100644 index 0000000000000..d225652b7061a --- /dev/null +++ b/tests/cncheckcert/server-key.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIBRAIBAAJDAM+ez7yZLKp9spGiUqqInrH8iTZ8JPa3C8K8bYw/Fc/W028sVvf6 ++VLC1ZP0cJX4Lx3iyyyIfzjGKV4+MVcBOlfn4wIDAQABAkJ5hiNh6OZUBK74v2JT +nxQEaiSGV7PrFMk1esVESciilsKsRJLcyzxbpANooQQefOswHzRR/YyMmy2HiW++ +H08ENMECIgDR6EfM7CKKJGj2jz3b+2bUoBpdpuPtD9kPo4u6ubVnNz8CIgD9Nfhq +Viiwl2IentOXOWvIasGQdQXz1fWHGLP5nA2Xql0CIgCXcSGUXG2TAy/ja3cy5k/L +afN7y/O3zm5JlTIzttaFMFsCIgC7Io8Eb8bEtCzk+nbgVaStyxBhJcuPaPp7rKse +d9Gn3FUCITGo0wLxVqHmjk2Pe2n9IJK1ooupjGY1unWQ2rM8RDkqfA== +-----END RSA PRIVATE KEY-----