Skip to content

Commit

Permalink
http_api (ticdc): check --cert-allowed-cn before add server common na…
Browse files Browse the repository at this point in the history
…me (#3628) (#3882)
  • Loading branch information
ti-chi-bot authored Dec 20, 2021
1 parent e23ef53 commit 626029b
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 14 deletions.
6 changes: 6 additions & 0 deletions cdc/capture/capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ func NewCapture(pdClient pd.Client, kvStorage tidbkv.Storage, etcdClient *etcd.C
}
}

func NewCapture4Test() *Capture {
return &Capture{
info: &model.CaptureInfo{ID: "capture-for-test", AdvertiseAddr: "127.0.0.1", Version: "test"},
}
}

func (c *Capture) reset(ctx context.Context) error {
c.captureMu.Lock()
defer c.captureMu.Unlock()
Expand Down
10 changes: 0 additions & 10 deletions cdc/capture/http_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -672,11 +672,6 @@ func (h *HTTPHandler) ListCapture(c *gin.Context) {
// @Failure 500,400 {object} model.HTTPError
// @Router /api/v1/status [get]
func (h *HTTPHandler) ServerStatus(c *gin.Context) {
if !h.capture.IsOwner() {
h.forwardToOwner(c)
return
}

status := model.ServerStatus{
Version: version.ReleaseVersion,
GitHash: version.GitHash,
Expand All @@ -697,11 +692,6 @@ func (h *HTTPHandler) ServerStatus(c *gin.Context) {
// @Failure 500 {object} model.HTTPError
// @Router /api/v1/health [get]
func (h *HTTPHandler) Health(c *gin.Context) {
if !h.capture.IsOwner() {
h.forwardToOwner(c)
return
}

ctx := c.Request.Context()

if _, err := h.capture.GetOwner(ctx); err != nil {
Expand Down
14 changes: 10 additions & 4 deletions cdc/http_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,17 @@ func (s *Server) startStatusHTTP() error {
prometheus.DefaultGatherer = registry
router.Any("/metrics", gin.WrapH(promhttp.Handler()))

err := conf.Security.AddSelfCommonName()
if err != nil {
log.Error("status server set tls config failed", zap.Error(err))
return errors.Trace(err)
// if CertAllowedCN was specified, we should add server's common name
// otherwise, https requests sent to non-owner capture can't be forward
// to owner
if len(conf.Security.CertAllowedCN) != 0 {
err := conf.Security.AddSelfCommonName()
if err != nil {
log.Error("status server set tls config failed", zap.Error(err))
return errors.Trace(err)
}
}

tlsConfig, err := conf.Security.ToTLSConfigWithVerify()
if err != nil {
log.Error("status server get tls config failed", zap.Error(err))
Expand Down
146 changes: 146 additions & 0 deletions cdc/http_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,27 @@ package cdc

import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"

"github.com/pingcap/check"
"github.com/pingcap/failpoint"
"github.com/pingcap/tidb/br/pkg/httputil"
"github.com/pingcap/tiflow/cdc/capture"
"github.com/pingcap/tiflow/cdc/model"
"github.com/pingcap/tiflow/pkg/config"
cerrors "github.com/pingcap/tiflow/pkg/errors"
"github.com/pingcap/tiflow/pkg/retry"
security2 "github.com/pingcap/tiflow/pkg/security"
"github.com/pingcap/tiflow/pkg/util/testleak"
"github.com/tikv/pd/pkg/tempurl"
"go.etcd.io/etcd/clientv3/concurrency"
)

Expand Down Expand Up @@ -159,3 +170,138 @@ func testHandleFailpoint(c *check.C) {
})
c.Assert(failpointHit, check.IsFalse)
}

func (s *httpStatusSuite) TestServerTLSWithoutCommonName(c *check.C) {
defer testleak.AfterTest(c)
addr := tempurl.Alloc()[len("http://"):]
// Do not specify common name
security, err := security2.NewCredential4Test("")
c.Assert(err, check.IsNil)
conf := config.GetDefaultServerConfig()
conf.Addr = addr
conf.AdvertiseAddr = addr
conf.Security = &security
config.StoreGlobalServerConfig(conf)

server, err := NewServer([]string{"https://127.0.0.1:2379"})
server.capture = capture.NewCapture4Test()
c.Assert(err, check.IsNil)
err = server.startStatusHTTP()
c.Assert(err, check.IsNil)
defer func() {
c.Assert(server.statusServer.Close(), check.IsNil)
}()

statusURL := fmt.Sprintf("https://%s/api/v1/status", addr)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// test cli sends request without a cert will success
err = retry.Do(ctx, func() error {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
cli := &http.Client{Transport: tr}
resp, err := cli.Get(statusURL)
if err != nil {
return err
}
decoder := json.NewDecoder(resp.Body)
captureInfo := &model.CaptureInfo{}
err = decoder.Decode(captureInfo)
c.Assert(err, check.IsNil)
c.Assert(captureInfo.ID, check.Equals, server.capture.Info().ID)
resp.Body.Close()
return nil
}, retry.WithMaxTries(retryTime), retry.WithBackoffBaseDelay(50), retry.WithIsRetryableErr(cerrors.IsRetryableError))
c.Assert(err, check.IsNil)

// test cli sends request with a cert will success
err = retry.Do(ctx, func() error {
tlsConfig, err := security.ToTLSConfigWithVerify()
if err != nil {
c.Assert(err, check.IsNil)
}
cli := httputil.NewClient(tlsConfig)
resp, err := cli.Get(statusURL)
if err != nil {
return err
}
decoder := json.NewDecoder(resp.Body)
captureInfo := &model.CaptureInfo{}
err = decoder.Decode(captureInfo)
c.Assert(err, check.IsNil)
c.Assert(captureInfo.ID, check.Equals, server.capture.Info().ID)
resp.Body.Close()
return nil
}, retry.WithMaxTries(retryTime), retry.WithBackoffBaseDelay(50), retry.WithIsRetryableErr(cerrors.IsRetryableError))
c.Assert(err, check.IsNil)
}

//
func (s *httpStatusSuite) TestServerTLSWithCommonName(c *check.C) {
defer testleak.AfterTest(c)
addr := tempurl.Alloc()[len("http://"):]
// specify a common name
security, err := security2.NewCredential4Test("test")
c.Assert(err, check.IsNil)
conf := config.GetDefaultServerConfig()
conf.Addr = addr
conf.AdvertiseAddr = addr
conf.Security = &security
config.StoreGlobalServerConfig(conf)

server, err := NewServer([]string{"https://127.0.0.1:2379"})
server.capture = capture.NewCapture4Test()
c.Assert(err, check.IsNil)
err = server.startStatusHTTP()
c.Assert(err, check.IsNil)
defer func() {
c.Assert(server.statusServer.Close(), check.IsNil)
}()

statusURL := fmt.Sprintf("https://%s/api/v1/status", addr)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// test cli sends request without a cert will fail
err = retry.Do(ctx, func() error {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
cli := &http.Client{Transport: tr}
resp, err := cli.Get(statusURL)
if err != nil {
return err
}
decoder := json.NewDecoder(resp.Body)
captureInfo := &model.CaptureInfo{}
err = decoder.Decode(captureInfo)
c.Assert(err, check.IsNil)
c.Assert(captureInfo.ID, check.Equals, server.capture.Info().ID)
resp.Body.Close()
return nil
}, retry.WithMaxTries(retryTime), retry.WithBackoffBaseDelay(50), retry.WithIsRetryableErr(cerrors.IsRetryableError))
c.Assert(strings.Contains(err.Error(), "remote error: tls: bad certificate"), check.IsTrue)

// test cli sends request with a cert will success
err = retry.Do(ctx, func() error {
tlsConfig, err := security.ToTLSConfigWithVerify()
if err != nil {
c.Assert(err, check.IsNil)
}
cli := httputil.NewClient(tlsConfig)
resp, err := cli.Get(statusURL)
if err != nil {
return err
}
decoder := json.NewDecoder(resp.Body)
captureInfo := &model.CaptureInfo{}
err = decoder.Decode(captureInfo)
c.Assert(err, check.IsNil)
c.Assert(captureInfo.ID, check.Equals, server.capture.Info().ID)
resp.Body.Close()
return nil
}, retry.WithMaxTries(retryTime), retry.WithBackoffBaseDelay(50), retry.WithIsRetryableErr(cerrors.IsRetryableError))
c.Assert(err, check.IsNil)
}
132 changes: 132 additions & 0 deletions pkg/security/test_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2021 PingCAP, Inc.
//
// Licensed 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package security

import "os"

// All these certificates or keys are use for testing only.
var certPem = `-----BEGIN CERTIFICATE-----
MIIDjzCCAnegAwIBAgIUWBTDQm4xOYDxZBTkpCQouREtT8QwDQYJKoZIhvcNAQEL
BQAwVzELMAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0Jl
aWppbmcxEDAOBgNVBAoTB1BpbmdDQVAxEjAQBgNVBAMTCU15IG93biBDQTAgFw0y
MDAyMTgwOTExMDBaGA8yMTIwMDEyNTA5MTEwMFowFjEUMBIGA1UEAxMLdGlkYi1z
ZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr2ZxAb+dItEQz
avuza0IoIT/UolC9XTGaQiCUUPZUMN9hb4KYEwTks1ZthHovTIJUdTwHtpWfDUWx
uIXhOlRjfD+viY4aXtBsaK8xi9F7o2HbFQ5O9y3AXK/YW+u0FfWtnn/xAtvPUgUc
61NXtBTMvNard9ICIXW+FWxLcFSaHpC9ZTyr13KWmZRDbai1JFeaKvATMW30r7Dd
Ur1npppzt7ZdG6tU/FuqBBSrZtuVSGKLwVx0JQDw16eVan4friY3ZPNVoZvkKyvt
I1LsBYMQ8p+ijtbcftvMqWZFVw95F1+3C2JIjWN9ujGmvJr+dtPIE8T/J8tT9Jif
9vz16nOLAgMBAAGjgZEwgY4wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG
AQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRVB/Bvdzvh
6WQRWpc9SzcbXLz77zAfBgNVHSMEGDAWgBSdAhKsS8BKSOidoGCUYNeaFma4/zAP
BgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQAAqg5pgGQqORKRSdlY
wzVvzKaulpvjZfVMM6YiOUtmlU0CGWq7E3gLFzkvebpU0KsFlbyZ92h/2Fw5Ay2b
kxkCy18mJ4lGkvF0cU4UD3XheFMvD2QWWRX4WPpAhStofrWOXeyq3Div2+fQjMJd
kyeWUzPU7T467IWUHOWNsFAjfVHNsmG45qLGt+XQckHTvASX5IvN+5tkRUCW30vO
b3BdDQUFglGTUFU2epaZGTti0SYiRiY+9R3zFWX4uBcEBYhk9e/0BU8FqdWW5GjI
pFpH9t64CjKIdRQXpIn4cogK/GwyuRuDPV/RkMjrIqOi7pGejXwyDe9avHFVR6re
oowA
-----END CERTIFICATE-----`

var caPem = `-----BEGIN CERTIFICATE-----
MIIDgDCCAmigAwIBAgIUHWvlRJydvYTR0ot3b8f6IlSHcGUwDQYJKoZIhvcNAQEL
BQAwVzELMAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0Jl
aWppbmcxEDAOBgNVBAoTB1BpbmdDQVAxEjAQBgNVBAMTCU15IG93biBDQTAgFw0y
MDAyMTgwNzQxMDBaGA8yMTIwMDEyNTA3NDEwMFowVzELMAkGA1UEBhMCQ04xEDAO
BgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxEDAOBgNVBAoTB1BpbmdD
QVAxEjAQBgNVBAMTCU15IG93biBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAOAdNtjanFhPaKJHQjr7+h/Cpps5bLc6S1vmgi/EIi9PKv3eyDgtlW1r
As2sjXRMHjcuZp2hHJ9r9FrMQD1rQQq5vJzQqM+eyWLc2tyZWXNWkZVvpjU4Hy5k
jZFLXoyHgAvps/LGu81F5Lk5CvLHswWTyGQUCFi1l/cYcQg6AExh2pO/WJu4hQhe
1mBBIKsJhZ5b5tWruLeI+YIjD1oo1ADMHYLK1BHON2fUmUHRGbrYKu4yCuyip3wn
rbVlpabn7l1JBMatCUJLHR6VWQ2MNjrOXAEUYm4xGEN+tUYyUOGl5mHFguLl3OIn
wj+1dT3WOr/NraPYlwVOnAd9GNbPJj0CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ0CEqxLwEpI6J2gYJRg15oWZrj/
MA0GCSqGSIb3DQEBCwUAA4IBAQCf8xRf7q1xAaGrc9HCPvN4OFkxDwz1CifrvrLR
ZgIWGUdCHDW2D1IiWKZQWeJKC1otA5x0hrS5kEGfkLFhADEU4txwp70DQaBArPti
pSgheIEbaT0H3BUTYSgS3VL2HjxN5OVMN6jNG3rWyxnJpNOCsJhhJXPK50CRZ7fk
Dcodj6FfEM2bfp2bGkxyVtUch7eepfUVbslXa7jE7Y8M3cr9NoLUcSP6D1RJWkNd
dBQoUsb6Ckq27ozEKOgwuBVv4BrrbFN//+7WHP8Vy6sSMyd+dJLBi6wehJjQhIz6
vqLWE81rSJuxZqjLpCkFdeEF+9SRjWegU0ZDM4V+YeX53BPC
-----END CERTIFICATE-----
`

var keyPem = `-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAq9mcQG/nSLREM2r7s2tCKCE/1KJQvV0xmkIglFD2VDDfYW+C
mBME5LNWbYR6L0yCVHU8B7aVnw1FsbiF4TpUY3w/r4mOGl7QbGivMYvRe6Nh2xUO
TvctwFyv2FvrtBX1rZ5/8QLbz1IFHOtTV7QUzLzWq3fSAiF1vhVsS3BUmh6QvWU8
q9dylpmUQ22otSRXmirwEzFt9K+w3VK9Z6aac7e2XRurVPxbqgQUq2bblUhii8Fc
dCUA8NenlWp+H64mN2TzVaGb5Csr7SNS7AWDEPKfoo7W3H7bzKlmRVcPeRdftwti
SI1jfboxprya/nbTyBPE/yfLU/SYn/b89epziwIDAQABAoIBACPlI08OULgN90Tq
LsLuP3ZUY5nNgaHcKnU3JMj2FE3Hm5ElkpijOF1w3Dep+T+R8pMjnbNavuvnAMy7
ZzOBVIknNcI7sDPv5AcQ4q8trkbt/I2fW0rBNIw+j/hYUuZdw+BNABpeZ31pe2nr
+Y+TLNkLBKfyMiqBxK88mE81mmZKblyvXCawW0A/iDDJ7fPNqoGF+y9ylTYaNRPk
aJGnaEZobJ4Lm5tSqW4gRX2ft6Hm67RkvVaopPFnlkvfusXUTFUqEVQCURRUqXbf
1ah2chUHxj22UdY9540H5yVNgEP3oR+uS/hbZqxKcJUTznUW5th3CyQPIKMlGlcB
p+zWlTECgYEAxlY4zGJw4QQwGYMKLyWCSHUgKYrKu2Ub2JKJFMTdsoj9H7DI+WHf
lQaO9NCOo2lt0ofYM1MzEpI5Cl/aMrPw+mwquBbxWdMHXK2eSsUQOVo9HtUjgK2t
J2AYFCfsYndo+hCj3ApMHgiY3sghTCXeycvT52bm11VeNVcs3pKxIYMCgYEA3dAJ
PwIfAB8t+6JCP2yYH4ExNjoMNYMdXqhz4vt3UGwgskRqTW6qdd9JvrRQ/JPvGpDy
T375h/+lLw0E4ljsnOPGSzbXNf4bYRHTwPOL+LqVM4Bg90hjclqphElHChxep1di
WcdArB0oae/l4M96z3GjfnXIUVOp8K6BUQCab1kCgYAFFAQUR5j4SfEpVg+WsXEq
hcUzCxixv5785pOX8opynctNWmtq5zSgTjCu2AAu8u4a69t/ROwT16aaO2YM0kqj
Ps3BNOUtFZgkqVVaOL13mnXiKjbkfo3majFzoqoMw13uuSpY4fKc+j9fxOQFXRrd
M9jTHfFfJhJpbzf44uyiHQKBgFIPwzvyVvG+l05/Ky83x9fv/frn4thxV45LmAQj
sHKqbjZFpWZcSOgu4aOSJlwrhsw3T84lVcAAzmXn1STAbVll01jEQz6QciSpacP6
1pAAx240UqtptpD6BbkROxz8ffA/Hf3E/6Itb2QyAsP3PqI8kpYYkTG1WCvZA7Kq
HHiRAoGAXbUZ25LcrmyuxKWpbty8fck1tjKPvclQB35rOx6vgnfW6pcKMeebYvgq
nJka/QunEReOH/kGxAd/+ymvUBuFQCfFg3Aus+DtAuh9AkBr+cIyPjJqynnIT87J
MbkOw4uEhDJAtGUR9o1j83N1f05bnEwssXiXR0LZPylb9Qzc4tg=
-----END RSA PRIVATE KEY-----
`

// NewCredential4Test return a Credential for testing
func NewCredential4Test(cn string) (Credential, error) {
res := Credential{}
if cn != "" {
res.CertAllowedCN = append(res.CertAllowedCN, cn)
}
cert, err := os.CreateTemp("", "ticdc-test-cert")
if err != nil {
return res, err
}
_, err = cert.Write([]byte(certPem))
if err != nil {
return res, err
}

ca, err := os.CreateTemp("", "ticdc-test-ca")
if err != nil {
return res, err
}
_, err = ca.Write([]byte(caPem))
if err != nil {
return res, err
}

key, err := os.CreateTemp("", "ticdc-test-key")
if err != nil {
return res, err
}
_, err = key.Write([]byte(keyPem))
if err != nil {
return res, err
}

res.CertPath = cert.Name()
res.CAPath = ca.Name()
res.KeyPath = key.Name()

return res, nil
}

0 comments on commit 626029b

Please sign in to comment.