From 24b11a75995c24b0b23d1a344b34e19e50af6da4 Mon Sep 17 00:00:00 2001 From: liangchuan Date: Sat, 1 Feb 2025 15:38:15 +0800 Subject: [PATCH] Add HTTPS backend support Signed-off-by: liangchuan --- bfe_balance/backend/health_check.go | 266 ++++++++++++- bfe_balance/backend/health_check_test.go | 370 +++++++++++++++++- .../cluster_conf/cluster_conf_load.go | 257 ++++++++++-- .../cluster_conf/cluster_conf_load_test.go | 52 +++ bfe_http/transport.go | 80 +++- bfe_route/bfe_cluster/bfe_cluster.go | 15 +- bfe_server/bfe_server.go | 13 +- bfe_server/reverseproxy.go | 81 +++- bfe_tls/bfe_testkeys.go | 300 ++++++++++++++ bfe_tls/bfe_testkeys_test.go | 46 +++ bfe_tls/bfe_verify_hooks.go | 275 +++++++++++++ bfe_tls/bfe_verify_hooks_test.go | 113 ++++++ bfe_tls/common.go | 24 +- bfe_tls/handshake_client.go | 7 + bfe_tls/handshake_server.go | 11 +- conf/cluster_conf/cluster_table.data | 10 + conf/cluster_conf/gslb.data | 4 + conf/server_data_conf/cluster_conf.data | 45 +++ conf/server_data_conf/host_rule.data | 1 + conf/server_data_conf/route_rule.data | 4 + conf/tls_conf/backend_rs/bfe_i_ca.crt | 23 ++ conf/tls_conf/backend_rs/bfe_r_ca.crt | 22 ++ conf/tls_conf/backend_rs/r_bfe_dev.crt | 85 ++++ conf/tls_conf/backend_rs/r_bfe_dev_prv.pem | 27 ++ .../tls_conf/backend_rs/r_san_example.org.crt | 85 ++++ .../backend_rs/r_san_example.org_prv.pem | 27 ++ 26 files changed, 2145 insertions(+), 98 deletions(-) create mode 100644 bfe_tls/bfe_testkeys.go create mode 100644 bfe_tls/bfe_testkeys_test.go create mode 100644 bfe_tls/bfe_verify_hooks.go create mode 100644 bfe_tls/bfe_verify_hooks_test.go create mode 100644 conf/tls_conf/backend_rs/bfe_i_ca.crt create mode 100644 conf/tls_conf/backend_rs/bfe_r_ca.crt create mode 100644 conf/tls_conf/backend_rs/r_bfe_dev.crt create mode 100644 conf/tls_conf/backend_rs/r_bfe_dev_prv.pem create mode 100644 conf/tls_conf/backend_rs/r_san_example.org.crt create mode 100644 conf/tls_conf/backend_rs/r_san_example.org_prv.pem diff --git a/bfe_balance/backend/health_check.go b/bfe_balance/backend/health_check.go index c13b88863..9a7c17150 100644 --- a/bfe_balance/backend/health_check.go +++ b/bfe_balance/backend/health_check.go @@ -17,25 +17,36 @@ package backend import ( + "crypto/x509" + "encoding/json" "fmt" "net" "net/http" + "net/url" + "regexp" + "strconv" "strings" "time" -) -import ( "github.com/baidu/go-lib/log" -) -import ( "github.com/bfenetworks/bfe/bfe_config/bfe_cluster_conf/cluster_conf" "github.com/bfenetworks/bfe/bfe_debug" + "github.com/bfenetworks/bfe/bfe_tls" ) +type checkRtn struct { + ok bool + err error +} + func UpdateStatus(backend *BfeBackend, cluster string) bool { + var ( + checkConf *cluster_conf.BackendCheck + httpsConf *cluster_conf.BackendHTTPS + ) // get conf of health check, which is separately stored for each cluster - checkConf := getCheckConf(cluster) + checkConf, httpsConf = getCheckConf(cluster) if checkConf == nil { // just ignore if not found health check conf return false @@ -45,14 +56,15 @@ func UpdateStatus(backend *BfeBackend, cluster string) bool { // if backend's status become fail, start healthcheck. // at most start 1 check goroutine for each backend. if backend.UpdateStatus(*checkConf.FailNum) { - go check(backend, cluster) + go check(backend, cluster, httpsConf) return true } return false } -func check(backend *BfeBackend, cluster string) { +func check(backend *BfeBackend, cluster string, httpsConf *cluster_conf.BackendHTTPS) { + log.Logger.Info("start healthcheck for %s", backend.Name) // backend close chan @@ -67,7 +79,7 @@ loop: } // get the latest conf to do health check - checkConf := getCheckConf(cluster) + checkConf, _ := getCheckConf(cluster) if checkConf == nil { // never come here time.Sleep(time.Second) @@ -76,7 +88,7 @@ loop: checkInterval := time.Duration(*checkConf.CheckInterval) * time.Millisecond // health check - if ok, err := CheckConnect(backend, checkConf); !ok { + if ok, err := CheckConnect(backend, checkConf, httpsConf); !ok { backend.ResetSuccNum() if bfe_debug.DebugHealthCheck { log.Logger.Debug("backend %s still not avail (check failure: %s)", backend.Name, err) @@ -150,6 +162,232 @@ func doHTTPHealthCheck(request *http.Request, timeout time.Duration) (int, error return response.StatusCode, nil } +// extractIP extract ip address +func extractIP(rsAddr string) string { + if strings.HasPrefix(rsAddr, "[") { + // IPv6 + endIndex := strings.LastIndex(rsAddr, "]") + if endIndex == -1 { + return "" + } + ip := rsAddr[:endIndex+1] + if net.ParseIP(ip[1:endIndex]) == nil { + return "" + } + return ip + } else { + // IPv4 + ip := strings.Split(rsAddr, ":")[0] + if net.ParseIP(ip) == nil { + return "" + } + return ip + } +} + +func getHostByType(host, rsAddr, hostType *string, def string) string { + if hostType == nil { + ht := cluster_conf.HostType_HOST + hostType = &ht + } + switch *hostType { + case cluster_conf.HostType_Instance_IP: + if rsAddr != nil { + return extractIP(*rsAddr) + } + default: + if host != nil { + return *host + } + } + return def +} + +// add by liangc +func checkHTTPSConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck, httpsConf *cluster_conf.BackendHTTPS) (bool, error) { + var ( + err error + conn net.Conn + addrInfo = getHealthCheckAddrInfo(backend, checkConf) + checkTimeout = 30 * time.Second + statusCode = 0 + host string + rootCAs *x509.CertPool = nil + certs []bfe_tls.Certificate = nil + cert bfe_tls.Certificate + insecure = false + uri = "/" + checkRtnCh = make(chan checkRtn, 1) + rtn checkRtn + ) + + var ( + getStatusCodeFn = func(statusLine string) (int, error) { + // "HTTP/1.1 200 OK" + re, err := regexp.Compile(`\s(\d{3})\s`) + if err != nil { + return 0, err + } + matches := re.FindStringSubmatch(statusLine) + if len(matches) == 2 { + statusCode := matches[1] + log.Logger.Debug("StatusCode = %s, raw = %s", statusCode, statusLine) + return strconv.Atoi(statusCode) + } else { + return 0, fmt.Errorf("Status code not found: %s", statusLine) + } + } + + doCheckFn = func(conn net.Conn) checkRtn { + // TLS Check >>>>>>> + if err = conn.(*bfe_tls.Conn).Handshake(); err != nil { + log.Logger.Debug("debug_https err=%s", err.Error()) + return checkRtn{false, err} + } + if *checkConf.Schem == "tls" { // https or tls + return checkRtn{true, nil} + } + // TLS Check <<<<<<< + + // HTTPS Check vvvvvvvvvvvvv + if checkConf.Uri != nil && *checkConf.Uri != "" { + uri = *checkConf.Uri + } + request := fmt.Sprintf("GET %s HTTP/1.1\r\n"+ + "Host: %s\r\n"+ + "User-Agent: BFE-Health-Check\r\n"+ + "\r\n", uri, host) + _, err = conn.Write([]byte(request)) + if err != nil { + log.Logger.Debug("debug_https err=%s", err.Error()) + return checkRtn{false, err} + } + var ( + response = "" + ok bool + err error + data = make([]byte, 0) + bufSz = 128 + buf = make([]byte, bufSz) + total = 0 + ) + //TODO: if timeout , how to handle ? + for { + total, err = conn.Read(buf) + if err != nil { + break + } + data = append(data, buf[:total]...) + if total < bufSz { + break + } + } + //data, err = ioutil.ReadAll(conn) + if err != nil { + log.Logger.Debug("debug_https err=%s", err.Error()) + return checkRtn{false, err} + } + response = string(data) + log.Logger.Debug("<- Request:\n%s", request) + log.Logger.Debug("-> Response:\n%s", response) + if checkConf.StatusCode != nil { // check status code + var ( + s string + arr = strings.Split(response, "\n") + ) + if len(arr) > 0 { + s = strings.ToUpper(arr[0]) + statusCode, err = getStatusCodeFn(s) + if err != nil { + return checkRtn{false, err} + } + if checkConf.StatusCodeRange != nil && *checkConf.StatusCodeRange != "" { + log.Logger.Debug("statusCode=%d, statusCodeRange=%s", statusCode, *checkConf.StatusCodeRange) + ok, err := cluster_conf.MatchStatusCodeRange(fmt.Sprintf("%d", statusCode), *checkConf.StatusCodeRange) + return checkRtn{ok, err} + } + } + ok, err = cluster_conf.MatchStatusCode(statusCode, *checkConf.StatusCode) + } + return checkRtn{ok, err} + } + + toStringFn = func(o interface{}) string { + b, err := json.Marshal(o) + if err != nil { + return err.Error() + } + return string(b) + } + ) + + if checkConf.CheckTimeout != nil { + checkTimeout = time.Duration(*checkConf.CheckTimeout) * time.Millisecond + } + conn, err = net.DialTimeout("tcp", addrInfo, checkTimeout) + + if err != nil { + log.Logger.Debug("debug_https err=%v", err) + return false, err + } + + defer func() { + if r := recover(); r != nil { + log.Logger.Debug("recover_panic = %v", r) + } + _ = conn.Close() + }() + + _, err = url.Parse(fmt.Sprintf("%s://%s%s", "https", addrInfo, *checkConf.Uri)) + if err != nil { + log.Logger.Debug("debug_https err=%v", err) + return false, err + } + + serverName := "" + if httpsConf.RSHost != nil { + serverName = *httpsConf.RSHost + } else if checkConf.Host != nil { + serverName = *checkConf.Host + } + host = getHostByType(checkConf.Host, &addrInfo, checkConf.HostType, serverName) + + rootCAs, err = httpsConf.GetRSCAList() + + if cert, err = httpsConf.GetBFECert(); err == nil { + certs = []bfe_tls.Certificate{cert} + } + + if httpsConf.RSInsecureSkipVerify != nil { + insecure = *httpsConf.RSInsecureSkipVerify + } + + conn = bfe_tls.Client(conn, &bfe_tls.Config{ + Certificates: certs, + InsecureSkipVerify: true, + ServerName: host, + RootCAs: rootCAs, + VerifyPeerCertificate: bfe_tls.NewVerifyPeerCertHooks(insecure, host, rootCAs).Ready(), + }) + + log.Logger.Debug("httpsCheck conf=%s", toStringFn(checkConf)) + go func(conn net.Conn, rtnCh chan checkRtn) { + rtnCh <- doCheckFn(conn) + }(conn, checkRtnCh) + + if checkTimeout > 0 { + select { + case rtn = <-checkRtnCh: + return rtn.ok, rtn.err + case <-time.Tick(checkTimeout): + return false, fmt.Errorf("https checkTimeout %dms", checkTimeout/time.Millisecond) + } + } else { + rtn = <-checkRtnCh + } + return rtn.ok, rtn.err +} + func checkHTTPConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck) (bool, error) { // prepare health check request addrInfo := getHealthCheckAddrInfo(backend, checkConf) @@ -182,12 +420,14 @@ func checkHTTPConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck) } // CheckConnect checks whether backend server become available. -func CheckConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck) (bool, error) { +func CheckConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck, httpsConf *cluster_conf.BackendHTTPS) (bool, error) { switch *checkConf.Schem { case "http": return checkHTTPConnect(backend, checkConf) case "tcp": return checkTCPConnect(backend, checkConf) + case "https", "tls": + return checkHTTPSConnect(backend, checkConf, httpsConf) default: // never come here return checkHTTPConnect(backend, checkConf) @@ -195,13 +435,13 @@ func CheckConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck) (bo } // CheckConfFetcher returns current health check conf for cluster. -type CheckConfFetcher func(cluster string) *cluster_conf.BackendCheck +type CheckConfFetcher func(name string) (*cluster_conf.BackendCheck, *cluster_conf.BackendHTTPS) var checkConfFetcher CheckConfFetcher -func getCheckConf(cluster string) *cluster_conf.BackendCheck { +func getCheckConf(cluster string) (*cluster_conf.BackendCheck, *cluster_conf.BackendHTTPS) { if checkConfFetcher == nil { - return nil + return nil, nil } return checkConfFetcher(cluster) } diff --git a/bfe_balance/backend/health_check_test.go b/bfe_balance/backend/health_check_test.go index 05a832634..174d84762 100644 --- a/bfe_balance/backend/health_check_test.go +++ b/bfe_balance/backend/health_check_test.go @@ -15,15 +15,25 @@ package backend import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/hex" "fmt" + "math/big" + "math/rand" + "net" "net/http" "net/http/httptest" "strings" + "sync" "testing" -) + "time" + + "github.com/baidu/go-lib/log" -import ( "github.com/bfenetworks/bfe/bfe_config/bfe_cluster_conf/cluster_conf" + "github.com/bfenetworks/bfe/bfe_tls" ) // test CheckConnect, AnyStatusCode case @@ -50,7 +60,7 @@ func TestCheckConnect_1(t *testing.T) { } // CheckConnect - isHealthy, err := CheckConnect(&backend, &checkConf) + isHealthy, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -85,7 +95,7 @@ func TestCheckConnect_2(t *testing.T) { } // CheckConnect - isHealthy, err := CheckConnect(&backend, &checkConf) + isHealthy, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -120,7 +130,7 @@ func TestCheckConnect_3(t *testing.T) { } // CheckConnect - _, err := CheckConnect(&backend, &checkConf) + _, err := CheckConnect(&backend, &checkConf, nil) if err == nil { t.Errorf("should have err") } @@ -146,7 +156,7 @@ func TestCheckConnect_4(t *testing.T) { } // CheckConnect - _, err := CheckConnect(&backend, &checkConf) + _, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -176,7 +186,7 @@ func TestCheckConnect_5(t *testing.T) { } // CheckConnect - _, err := CheckConnect(&backend, &checkConf) + _, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -213,7 +223,7 @@ func TestCheckConnect_6(t *testing.T) { } // CheckConnect - isHealthy, err := CheckConnect(&backend, &checkConf) + isHealthy, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -251,7 +261,7 @@ func TestCheckConnect_7(t *testing.T) { } // CheckConnect - isHealthy, err := CheckConnect(&backend, &checkConf) + isHealthy, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -285,7 +295,7 @@ func TestCheckConnect_8(t *testing.T) { } // CheckConnect - _, err := CheckConnect(&backend, &checkConf) + _, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -315,7 +325,7 @@ func TestCheckConnect_9(t *testing.T) { } // CheckConnect - isHealthy, err := CheckConnect(&backend, &checkConf) + isHealthy, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -350,7 +360,7 @@ func TestCheckConnect_10(t *testing.T) { } // CheckConnect - isHealthy, err := CheckConnect(&backend, &checkConf) + isHealthy, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -387,7 +397,7 @@ func TestCheckConnect_11(t *testing.T) { } // CheckConnect - isHealthy, err := CheckConnect(&backend, &checkConf) + isHealthy, err := CheckConnect(&backend, &checkConf, nil) if err != nil { t.Errorf("should have no err: %v", err) } @@ -424,15 +434,343 @@ func TestCheck_1(t *testing.T) { CheckInterval: &checkInterval, } - mockCheckConfFetcher := func(cluster string) *cluster_conf.BackendCheck { - return &checkConf + mockCheckConfFetcher := func(cluster string) (*cluster_conf.BackendCheck, *cluster_conf.BackendHTTPS) { + return &checkConf, nil } checkConfFetcher = mockCheckConfFetcher // check func - check(&backend, "") + check(&backend, "", nil) if backend.SuccNum() != 0 { t.Errorf("recover num should be 0") } } + +// test CheckConnect->checkHTTPSConnect >>>>>>>>>>>>>>>>>>>> +// test CheckConnect->checkHTTPSConnect >>>>>>>>>>>>>>>>>>>> +func TestCheckConnect_checkHTTPSConnect(t *testing.T) { + _ = log.Init(fmt.Sprintf("test_%s", time.Now().String()), "DEBUG", "/tmp", true, "M", 5) + type confArg struct { + Schem string // protocol for health check (HTTP/TCP) + Uri string // uri used in health check + Host string // if check request use special host header + StatusCode int // default value is 200 + FailNum int // unhealthy threshold (consecutive failures of check request) + SuccNum int // healthy threshold (consecutive successes of normal request) + CheckTimeout int // timeout for health check, in ms + CheckInterval int // interval of health check, in ms + StatusCodeRange string // #issue-14 + + //----------------------- + addrInfo string + requireAndVerifyClientCert bool + clientInsecure bool + assertOk, assertNoErr bool + + __id string // uuid for mock cluster name + } + type mockConf struct { + check *cluster_conf.BackendCheck + https *cluster_conf.BackendHTTPS + } + var ( + testCase = map[string]confArg{ + "https_xxx_connect_fail": { + assertOk: false, + assertNoErr: false, + Schem: "https", + StatusCodeRange: "1xx|20x|301|302|4xx", + Uri: "/foo/bar", + SuccNum: 1, + clientInsecure: false, + requireAndVerifyClientCert: false, + addrInfo: "1.1.1.1:1111", + }, + + "https_xxx": { + assertOk: true, + assertNoErr: true, + Schem: "https", + StatusCodeRange: "1xx|20x|301|302|4xx", + Uri: "/foo/bar", + SuccNum: 1, + clientInsecure: false, + requireAndVerifyClientCert: false, + }, + "https_xxx_two_way_authc": { + assertOk: false, + assertNoErr: false, + Schem: "https", + StatusCodeRange: "1xx|301|4xx", + Uri: "/", + SuccNum: 1, + clientInsecure: false, + requireAndVerifyClientCert: true, + }, + "https_xxx_wrong_host": { + assertOk: false, + assertNoErr: false, + Schem: "https", + StatusCodeRange: "1xx|20x|301|302|4xx", + Uri: "/foo/bar", + SuccNum: 1, + clientInsecure: false, + requireAndVerifyClientCert: false, + Host: "www.foobar.org", + }, + "https_404": { + assertOk: true, + assertNoErr: true, + Schem: "https", + StatusCode: 404, + Uri: "/foobar", + SuccNum: 1, + clientInsecure: false, + requireAndVerifyClientCert: false, + }, + + "https_404_two_way_authc": { + assertOk: true, + assertNoErr: true, + Schem: "https", + StatusCode: 404, + Uri: "/foobar", + SuccNum: 1, + clientInsecure: false, + requireAndVerifyClientCert: true, + }, + + "https_200": { + assertOk: true, + assertNoErr: true, + Schem: "https", + StatusCode: 200, + Uri: "/", + SuccNum: 1, + clientInsecure: false, + requireAndVerifyClientCert: false, + }, + "httos_200_two_way_authc": { + assertOk: true, + assertNoErr: true, + Schem: "https", + StatusCode: 200, + Uri: "/", + SuccNum: 1, + clientInsecure: false, + requireAndVerifyClientCert: true, + }, + + "tls_connect_fail": { + assertOk: false, + assertNoErr: false, + Schem: "tls", + clientInsecure: false, + requireAndVerifyClientCert: true, + addrInfo: "1.1.1.1:1111", + }, + + "tls": { + assertOk: true, + assertNoErr: true, + Schem: "tls", + clientInsecure: true, + requireAndVerifyClientCert: false, + }, + + "tls_two_way_authc": { + assertOk: true, + assertNoErr: true, + Schem: "tls", + clientInsecure: false, + requireAndVerifyClientCert: true, + }, + } + ) + + var ( + confMap = make(map[string]mockConf) + confMapLock = new(sync.RWMutex) + serverHost = "foobar.org" + //serverTimeout = 3 * time.Second + cert, _ = tls.X509KeyPair(bfe_tls.I_CN_FOOBAR_ORG_CRT.Bytes(), bfe_tls.I_CN_FOOBAR_ORG_PRV.Bytes()) + handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.String() != "/" { + w.WriteHeader(404) + } else { + w.WriteHeader(200) + w.Header().Set("Content-Type", "text/plain") + w.Header().Set("Host", serverHost) + _, _ = w.Write([]byte("Hello BFE https")) + } + }) + + clusterNameFn = func() string { + rand.Seed(time.Now().UnixNano()) + return hex.EncodeToString(big.NewInt(time.Now().UnixNano() + rand.Int63()).Bytes()) + } + casFn = func() *x509.CertPool { + cas := x509.NewCertPool() + cas.AppendCertsFromPEM(bfe_tls.BFE_R_CA_CRT.Bytes()) + cas.AppendCertsFromPEM(bfe_tls.BFE_I_CA_CRT.Bytes()) + return cas + } + cliCertFn = func() *bfe_tls.Certificate { + ccert, _ := bfe_tls.X509KeyPair(bfe_tls.I_BFE_DEV_CRT.Bytes(), bfe_tls.I_BFE_DEV_PRV.Bytes()) + return &ccert + } + checkConfFn = func(c confArg) (*cluster_conf.BackendCheck, *cluster_conf.BackendHTTPS) { + var ( + cas = casFn() + httpsConf = new(cluster_conf.BackendHTTPS) + checkConf = &cluster_conf.BackendCheck{ + Schem: &c.Schem, + StatusCode: &c.StatusCode, + Uri: &c.Uri, + SuccNum: &c.SuccNum, + StatusCodeRange: &c.StatusCodeRange, + CheckTimeout: &c.CheckTimeout, + Host: &c.Host, + } + // for httpsConf >>>> + rscalist = []string{} + insecure = c.clientInsecure + ) + if c.requireAndVerifyClientCert { + // client >>>> + httpsConf.SetBFECert(cliCertFn()) + insecure = false + // client <<<< + } + httpsConf.SetRSCAList(cas) + httpsConf.RSHost = &c.Host + httpsConf.RSCAList = &rscalist + httpsConf.RSInsecureSkipVerify = &insecure + confMapLock.Lock() + confMap[c.__id] = mockConf{checkConf, httpsConf} + confMapLock.Unlock() + return checkConf, httpsConf + } + + buildConfAndStartServerFn = func(ctx context.Context, c confArg) *BfeBackend { + var ( + cas = casFn() + backend = &BfeBackend{} + // 创建TLS配置 + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + ClientAuth: tls.NoClientCert, // default + } + ) + // Two-way authc + if c.requireAndVerifyClientCert { + // server + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = cas + tlsConfig.RootCAs = cas + } + + listener, err := net.Listen("tcp", ":0") + if err != nil { + t.Errorf("failed to listen: %v", err) + } + + server := &http.Server{ + TLSConfig: tlsConfig, + Handler: handler, + } + go func() { + if err := server.ServeTLS(listener, "", ""); err != nil && err != http.ErrServerClosed { + t.Errorf("HTTPS test server fail : %v", err) + } + }() + time.Sleep(100 * time.Millisecond) + port := listener.Addr().(*net.TCPAddr).Port + backend.AddrInfo = fmt.Sprintf("127.0.0.1:%d", port) + if c.addrInfo != "" { + backend.AddrInfo = c.addrInfo + } + // 等待ctx取消信号 + go func() { + <-ctx.Done() + if err := server.Close(); err != nil { + t.Errorf("HTTPS test server Close failed: %v", err) + } + }() + time.Sleep(100 * time.Millisecond) + return backend + } + + startlinkAndWaitRtnFn = func(args confArg) func(t *testing.T) { + args.__id = clusterNameFn() + if args.Host == "" { + args.Host = serverHost + } + if args.CheckTimeout <= 0 { + args.CheckTimeout = 2000 // ms + } + log.Logger.Debug("cid=%s, test-host=%s", args.__id, args.Host) + return func(t *testing.T) { + defer func() { + }() + var rtnCh = make(chan checkRtn, 1) + conf, httpsConf := checkConfFn(args) + ctx, cancelFn := context.WithCancel(context.Background()) + go func(ctx context.Context, conf *cluster_conf.BackendCheck, httpsConf *cluster_conf.BackendHTTPS) { + backend := buildConfAndStartServerFn(ctx, args) + + isHealthy, err := CheckConnect(backend, conf, httpsConf) + rtnCh <- checkRtn{ok: isHealthy, err: err} + }(ctx, conf, httpsConf) + select { + case rtn := <-rtnCh: + defer cancelFn() + if rtn.err != nil { + if args.assertNoErr { + t.Errorf("assertNoErr=%v, err=%v", args.assertNoErr, rtn.err) + } else { + t.Logf("assertNoErr=%v, err=%v", args.assertNoErr, rtn.err) + } + } + if !rtn.ok { + if args.assertOk { + t.Errorf("backend assertOk=%v, rtnOk=%v", args.assertOk, rtn.ok) + } else { + t.Logf("backend assertOk=%v, rtnOk=%v", args.assertOk, rtn.ok) + } + } + } + } + } + ) + + SetCheckConfFetcher(func(name string) (*cluster_conf.BackendCheck, *cluster_conf.BackendHTTPS) { + confMapLock.Lock() + defer confMapLock.Unlock() + cnf := confMap[name] + return cnf.check, cnf.https + }) + for k, v := range testCase { + t.Run(k, startlinkAndWaitRtnFn(v)) + } + time.Sleep(1 * time.Second) +} + +// test CheckConnect->checkHTTPSConnect <<<<<<<<<<<<<<<<<<<< +// test CheckConnect->checkHTTPSConnect <<<<<<<<<<<<<<<<<<<< + +func TestExtractIP(t *testing.T) { + rsAddr1 := "192.168.1.1:8888" + rsAddr2 := "[fe80::d450:2dc5:d]:8888" + + ip1 := extractIP(rsAddr1) + if ip1 != "192.168.1.1" { + t.Error("expect: 192.168.1.1, got :", ip1) + } + + ip2 := extractIP(rsAddr2) + if ip2 != "[fe80::d450:2dc5:d]" { + t.Error("expect: [fe80::d450:2dc5:d], got :", ip2) + } +} diff --git a/bfe_config/bfe_cluster_conf/cluster_conf/cluster_conf_load.go b/bfe_config/bfe_cluster_conf/cluster_conf/cluster_conf_load.go index f7c727cc6..11ae55194 100644 --- a/bfe_config/bfe_cluster_conf/cluster_conf/cluster_conf_load.go +++ b/bfe_config/bfe_cluster_conf/cluster_conf/cluster_conf_load.go @@ -17,13 +17,17 @@ package cluster_conf import ( + "crypto/x509" + "encoding/pem" "errors" "fmt" "os" + "regexp" "strings" -) -import ( + "github.com/baidu/go-lib/log" + + "github.com/bfenetworks/bfe/bfe_tls" "github.com/bfenetworks/bfe/bfe_util/json" ) @@ -42,8 +46,8 @@ const ( // HashStrategy for subcluster-level load balance (GSLB). // Note: -// - CLIENTID is a special request header which represents a unique client, -// eg. baidu id, passport id, device id etc. +// - CLIENTID is a special request header which represents a unique client, +// eg. baidu id, passport id, device id etc. const ( ClientIdOnly = iota // use CLIENTID to hash ClientIpOnly // use CLIENTIP to hash @@ -63,16 +67,27 @@ const ( AnyStatusCode = 0 ) +const ( + HostType_HOST = "HOST" + HostType_Instance_IP = "Instance_IP" +) + // BackendCheck is conf of backend check type BackendCheck struct { - Schem *string // protocol for health check (HTTP/TCP) - Uri *string // uri used in health check - Host *string // if check request use special host header - StatusCode *int // default value is 200 - FailNum *int // unhealthy threshold (consecutive failures of check request) - SuccNum *int // healthy threshold (consecutive successes of normal request) - CheckTimeout *int // timeout for health check, in ms - CheckInterval *int // interval of health check, in ms + Schem *string // protocol for health check (HTTP/HTTPS/TCP) + Uri *string // uri used in health check + Host *string // if check request use special host header + HostType *string // extending the type of Host. + StatusCode *int // default value is 200 + // StatusCodeRange Legal configuration items: + // (1) One of "3xx", "4xx", "5xx" + // (2) Specific HTTP status codes + // (3) Combinations of the above (1) or (2) connected by the "|" symbol, for example: "3xx", "4xx", "5xx", "503" | "4xx", "501" | "409" + StatusCodeRange *string + FailNum *int // unhealthy threshold (consecutive failures of check request) + SuccNum *int // healthy threshold (consecutive successes of normal request) + CheckTimeout *int // timeout for health check, in ms + CheckInterval *int // interval of health check, in ms } // FCGIConf are FastCGI related configurations @@ -95,6 +110,75 @@ type BackendBasic struct { FCGIConf *FCGIConf } +// BackendHTTPS is conf of backend https +type BackendHTTPS struct { + // confs + RSHost *string // real server hostname + RSInsecureSkipVerify *bool // whether to skip verify cert of real server + RSCAList *[]string // real server CA Cert list. nil/empty - use system ca list; not empty - completely use RSCAList and do not use system list + BFECertFile *string // BFE Cert file + BFEKeyFile *string // Privatekey file of BFECert + + // caches + rsCAList *x509.CertPool // cache RSCAList + bfeCert *bfe_tls.Certificate // cache BFECertFile & BFEKeyFile + protocol string // protocol of backend https +} + +func (conf *BackendHTTPS) GetProtocol() string { + return conf.protocol +} + +// GetRSCAList : cache the RSCAList in memory, +func (conf *BackendHTTPS) GetRSCAList() (*x509.CertPool, error) { + return conf.rsCAList, nil +} + +// SetRSCAList : just for unit test +func (conf *BackendHTTPS) SetRSCAList(cal *x509.CertPool) { + conf.rsCAList = cal +} + +// GetBFECert : cache the cert in memory +func (conf *BackendHTTPS) GetBFECert() (bfe_tls.Certificate, error) { + if conf.bfeCert == nil { + return bfe_tls.Certificate{}, errors.New("BFECert not found.") + } + return *conf.bfeCert, nil +} + +// SetBFECert : just for unit test +func (conf *BackendHTTPS) SetBFECert(cert *bfe_tls.Certificate) { + conf.bfeCert = cert +} + +// CheckBFECertAndKey : check the BFECertFile and BFEKeyFile +func (conf *BackendHTTPS) CheckBFECertAndKey() error { + var ( + certPem, keyPem []byte + cert bfe_tls.Certificate + err error = nil + ) + conf.bfeCert = nil + if conf.BFECertFile != nil && *conf.BFECertFile != "" { + if certPem, err = os.ReadFile(*conf.BFECertFile); err != nil { + return err + } + } + if conf.BFEKeyFile != nil && *conf.BFEKeyFile != "" { + if keyPem, err = os.ReadFile(*conf.BFEKeyFile); err != nil { + return err + } + } + if certPem == nil || keyPem == nil { + return nil + } else if cert, err = bfe_tls.X509KeyPair(certPem, keyPem); err != nil { + return err + } + conf.bfeCert = &cert + return nil +} + type HashConf struct { // HashStrategy is hash strategy for subcluster-level load balance. // ClientIdOnly, ClientIpOnly, ClientIdPreferred, RequestURI. @@ -137,6 +221,7 @@ type ClusterConf struct { CheckConf *BackendCheck // how to check backend GslbBasic *GslbBasicConf // gslb basic conf for cluster ClusterBasic *ClusterBasicConf // basic conf for cluster + HTTPSConf *BackendHTTPS // backend's https conf } type ClusterToConf map[string]ClusterConf @@ -147,6 +232,47 @@ type BfeClusterConf struct { Config *ClusterToConf } +// BackendHTTPS is https conf of backend. +func BackendHTTPSCheck(protocol *string, conf *BackendHTTPS) error { + if protocol == nil || *protocol != "https" { + return nil + } + conf.protocol = *protocol + if conf.RSInsecureSkipVerify == nil || !*conf.RSInsecureSkipVerify { + if conf.RSCAList != nil && len(*conf.RSCAList) > 0 { + rootCAs := x509.NewCertPool() + for _, caFp := range *conf.RSCAList { + chain, err := os.ReadFile(caFp) + log.Logger.Debug("load: ca_fp=%s, err=%v", caFp, err) + if err != nil { + return err + } + var certs []*x509.Certificate + block, rest := pem.Decode(chain) + for block != nil { + if block.Type == "CERTIFICATE" { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return err + } + certs = append(certs, cert) + } + if len(rest) > 0 { + block, rest = pem.Decode(rest) + } else { + break + } + } + for _, crt := range certs { + rootCAs.AddCert(crt) + } + } + conf.rsCAList = rootCAs + } + } + return conf.CheckBFECertAndKey() +} + // BackendBasicCheck check BackendBasic config. func BackendBasicCheck(conf *BackendBasic) error { if conf.Protocol == nil { @@ -155,9 +281,9 @@ func BackendBasicCheck(conf *BackendBasic) error { } *conf.Protocol = strings.ToLower(*conf.Protocol) switch *conf.Protocol { - case "http", "tcp", "ws", "fcgi", "h2c": + case "http", "tcp", "ws", "fcgi", "h2c", "https": default: - return fmt.Errorf("protocol only support http/tcp/ws/fcgi/h2c, but is:%s", *conf.Protocol) + return fmt.Errorf("protocol only support http/tcp/ws/fcgi/h2c/https, but is:%s", *conf.Protocol) } if conf.TimeoutConnSrv == nil { @@ -256,6 +382,51 @@ func convertStatusCode(statusCode int) string { return fmt.Sprintf("INVALID %d", statusCode) } +func checkStatusCodeRange(sp *string) error { + if sp == nil || *sp == "" { + return nil + } + s := *sp + s = strings.ReplaceAll(s, " ", "") + validPattern := "^[0-9xX|]+$" + matched, err := regexp.MatchString(validPattern, s) + if err != nil { + return err + } + rtnErr := fmt.Errorf("StatusCodeRange format error : %s", s) + if !matched { + return rtnErr + } + parts := strings.Split(s, "|") + for _, part := range parts { + if len(part) != 3 { // xxx|xxx|xxx + return rtnErr + } + } + return nil + +} + +func MatchStatusCodeRange(statusCode string, statusCodeRange string) (bool, error) { + if statusCodeRange == "" { + return true, nil + } + statusCodeRange = strings.ReplaceAll(statusCodeRange, " ", "") + ranges := strings.Split(statusCodeRange, "|") + for _, rangeStr := range ranges { + pattern := strings.ReplaceAll(rangeStr, "x", `\d`) + pattern = fmt.Sprintf("^%s$", pattern) + match, err := regexp.MatchString(pattern, statusCode) + if err != nil { + return false, err + } + if match { + return true, nil + } + } + return false, fmt.Errorf("not match: statusCode=%s, statusCodeRange=%s", statusCode, statusCodeRange) +} + func MatchStatusCode(statusCodeGet int, statusCodeExpect int) (bool, error) { // for normal status code if statusCodeExpect >= 100 && statusCodeExpect <= 599 { @@ -287,6 +458,8 @@ func BackendCheckCheck(conf *BackendCheck) error { // set default schem to http schem := "http" conf.Schem = &schem + } else if *conf.Schem != "http" && *conf.Schem != "https" && *conf.Schem != "tls" && *conf.Schem != "tcp" { + return errors.New("Schem for BackendCheck should be http/https/tls/tcp") } if conf.Uri == nil { @@ -299,6 +472,11 @@ func BackendCheckCheck(conf *BackendCheck) error { conf.Host = &host } + if conf.HostType == nil { + hostType := HostType_HOST + conf.HostType = &hostType + } + if conf.StatusCode == nil { statusCode := 0 conf.StatusCode = &statusCode @@ -319,11 +497,7 @@ func BackendCheckCheck(conf *BackendCheck) error { conf.SuccNum = &succNum } - if *conf.Schem != "http" && *conf.Schem != "tcp" { - return errors.New("schem for BackendCheck should be http/tcp") - } - - if *conf.Schem == "http" { + if *conf.Schem == "http" || *conf.Schem == "https" { if !strings.HasPrefix(*conf.Uri, "/") { return errors.New("Uri should be start with '/'") } @@ -331,6 +505,10 @@ func BackendCheckCheck(conf *BackendCheck) error { if err := checkStatusCode(*conf.StatusCode); err != nil { return err } + + if err := checkStatusCodeRange(conf.StatusCodeRange); err != nil { + return err + } } if *conf.SuccNum < 1 { @@ -503,8 +681,7 @@ func ClusterToConfCheck(conf ClusterToConf) error { return nil } -// BfeClusterConfCheck check integrity of config -func BfeClusterConfCheck(conf *BfeClusterConf) error { +func prevCheckBfeClusterConf(conf *BfeClusterConf, fn func() error) error { if conf == nil { return errors.New("nil BfeClusterConf") } @@ -515,13 +692,35 @@ func BfeClusterConfCheck(conf *BfeClusterConf) error { if conf.Config == nil { return errors.New("no Config") } + return fn() +} - err := ClusterToConfCheck(*conf.Config) - if err != nil { - return fmt.Errorf("BfeClusterConf.Config:%s", err.Error()) - } +// ClusterToConfBackendHTTPSCheck check ClusterToConf.HTTPSConf +func ClusterToConfBackendHTTPSCheck(conf *BfeClusterConf) error { + return prevCheckBfeClusterConf(conf, func() error { + for clusterName, clusterConf := range *conf.Config { + // "HTTPSConf" does not have strict required fields, so it should be allowed to be empty. + if clusterConf.HTTPSConf == nil { + clusterConf.HTTPSConf = new(BackendHTTPS) + } + err := BackendHTTPSCheck(clusterConf.BackendConf.Protocol, clusterConf.HTTPSConf) + if err != nil { + return fmt.Errorf("BackendHTTPS: clusterName=%s, err=%s", clusterName, err.Error()) + } + } + return nil + }) +} - return nil +// BfeClusterConfCheck check integrity of config +func BfeClusterConfCheck(conf *BfeClusterConf) error { + return prevCheckBfeClusterConf(conf, func() error { + err := ClusterToConfCheck(*conf.Config) + if err != nil { + return fmt.Errorf("BfeClusterConf.Config:%s", err.Error()) + } + return nil + }) } func GetCookieKey(header string) (string, bool) { @@ -548,11 +747,15 @@ func (conf *BfeClusterConf) LoadAndCheck(filename string) (string, error) { return "", err } - /* check conf */ + /* check conf */ if err := BfeClusterConfCheck(conf); err != nil { return "", err } + if err := ClusterToConfBackendHTTPSCheck(conf); err != nil { + return "", fmt.Errorf("BfeClusterConf.Config.HTTPSConf:%s", err.Error()) + } + return *(conf.Version), nil } diff --git a/bfe_config/bfe_cluster_conf/cluster_conf/cluster_conf_load_test.go b/bfe_config/bfe_cluster_conf/cluster_conf/cluster_conf_load_test.go index ec610472a..8ebc54dc8 100644 --- a/bfe_config/bfe_cluster_conf/cluster_conf/cluster_conf_load_test.go +++ b/bfe_config/bfe_cluster_conf/cluster_conf/cluster_conf_load_test.go @@ -65,3 +65,55 @@ func TestClusterConfLoad_6(t *testing.T) { return } } + +func TestStatusCodeRange(t *testing.T) { + var ( + statusCode = "400" + statusCodeRanges = map[string]bool{ + "200": false, + "2xx": false, + "4x0": true, + "43x": false, + "400": true, + "40x": true, + "4xx": true, + "x00": true, + "x0x": true, + "404|30x|2xx": false, + "200|40x|3xx": true, + "2xx|4xx|3xx": true, + } + worngRange = []string{ + "4000", + "x4x&5xx|6xx|111", + "[200-300]", + "|400", + } + ) + t.Run("checkStatusCodeRange", func(t *testing.T) { + var err error + for statusCodeRange, _ := range statusCodeRanges { + if err = checkStatusCodeRange(&statusCodeRange); err != nil { + t.Error(err) + } + } + for _, w := range worngRange { + if err = checkStatusCodeRange(&w); err == nil { + t.Errorf("assertOk=false, ok=true, statusCodeRange=%s", w) + } else { + t.Log(err) + } + } + }) + t.Run("MatchStatusCodeRange", func(t *testing.T) { + for statusCodeRange, assert := range statusCodeRanges { + ok, err := MatchStatusCodeRange(statusCode, statusCodeRange) + if ok != assert { + t.Errorf("statusCode=%s, statusCodeRange=%s, assertOk=%v, ok=%v", statusCode, statusCodeRange, assert, ok) + } + if err != nil { + t.Logf("assertOk=%v, ok=%v, err_msg=%s", assert, ok, err.Error()) + } + } + }) +} diff --git a/bfe_http/transport.go b/bfe_http/transport.go index 90bd9b197..a25f2d4ac 100644 --- a/bfe_http/transport.go +++ b/bfe_http/transport.go @@ -25,6 +25,7 @@ package bfe_http import ( "compress/gzip" + "crypto/x509" "errors" "fmt" "io" @@ -34,15 +35,11 @@ import ( "strings" "sync" "time" -) -import ( "github.com/baidu/go-lib/gotrack" "github.com/baidu/go-lib/log" -) - -import ( "github.com/bfenetworks/bfe/bfe_bufio" + "github.com/bfenetworks/bfe/bfe_config/bfe_cluster_conf/cluster_conf" "github.com/bfenetworks/bfe/bfe_tls" ) @@ -128,10 +125,16 @@ type Transport struct { // If zero, no periodic flushing is done. ReqFlushInterval time.Duration + HttpsConf *cluster_conf.BackendHTTPS + // TODO: tunable on global max cached connections // TODO: tunable on timeout on cached connections } +func (t *Transport) SetHttpsConf(c *cluster_conf.BackendHTTPS) { + t.HttpsConf = c +} + // ProxyFromEnvironment returns the URL of the proxy to use for a // given request, as indicated by the environment variables // $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy). @@ -188,6 +191,9 @@ func (tr *transportRequest) extraHeaders() Header { // For higher-level HTTP client support (such as handling of cookies // and redirects), see Get, Post, and the Client type. func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) { + if t.HttpsConf != nil && t.HttpsConf.GetProtocol() == "https" { + req.URL.Scheme = "https" + } state.HttpBackendReqAll.Inc(1) if req.URL == nil { return nil, errors.New("http: nil Request.URL") @@ -306,6 +312,7 @@ func (t *Transport) connectMethodForRequest(treq *transportRequest) (*connectMet cm := &connectMethod{ targetScheme: treq.URL.Scheme, targetAddr: canonicalAddr(treq.URL), + targetHost: treq.Host, } if t.Proxy != nil { var err error @@ -591,13 +598,70 @@ func (t *Transport) dialConn(cm *connectMethod) (*persistConn, error) { } if cm.targetScheme == "https" { + if t.HttpsConf == nil { + return nil, errors.New("https: error, httpsConf is nil") + } // Initiate TLS and check remote host name against certificate. cfg := t.TLSClientConfig if cfg == nil || cfg.ServerName == "" { - host := cm.tlsHost() + //host := cm.tlsHost() + var ( + host string + rootCAs *x509.CertPool = nil + certs []bfe_tls.Certificate = nil + cert bfe_tls.Certificate + httpsConf = t.HttpsConf + ) + if httpsConf.RSInsecureSkipVerify == nil || !*httpsConf.RSInsecureSkipVerify { + + // if the host has port, only use the hostname + host = cm.targetHost + if hasPort(host) { + host = host[:strings.LastIndex(host, ":")] + } + + if httpsConf.RSHost != nil && *httpsConf.RSHost != "" { + host = *httpsConf.RSHost + } + rootCAs, err = httpsConf.GetRSCAList() + if err != nil { + log.Logger.Debug("debug_https get_cas err=%s", err.Error()) + return nil, err + } + } + if cert, err = httpsConf.GetBFECert(); err == nil { + certs = []bfe_tls.Certificate{cert} + } if cfg == nil { - cfg = &bfe_tls.Config{ServerName: host} + if httpsConf.RSInsecureSkipVerify != nil && *httpsConf.RSInsecureSkipVerify { + // should skip Insecure Verify + cfg = &bfe_tls.Config{ + Certificates: certs, + InsecureSkipVerify: true, + ServerName: host, + } + } else { + // do Insecure Verify + if rootCAs == nil { + // use system cas + cfg = &bfe_tls.Config{ + Certificates: certs, + InsecureSkipVerify: false, + ServerName: host, + } + } else { + // use custom cas only, keep unchanged. + cfg = &bfe_tls.Config{ + Certificates: certs, + InsecureSkipVerify: true, + ServerName: host, + RootCAs: rootCAs, + VerifyPeerCertificate: bfe_tls.NewVerifyPeerCertHooks(*httpsConf.RSInsecureSkipVerify, host, rootCAs).Ready(), + } + } + } } else { + // should not arrive here, since t.TLSClientConfig is never set clone := cfg.Clone() // shallow clone clone.ServerName = host cfg = clone @@ -698,11 +762,11 @@ func useProxy(addr string) bool { // http://proxy.com|http http to proxy, http to anywhere after that // // Note: no support to https to the proxy yet. -// type connectMethod struct { proxyURL *url.URL // nil for no proxy, else full proxy URL targetScheme string // "http" or "https" targetAddr string // Not used if proxy + http targetScheme (4th example in table) + targetHost string // used in https protocol for default servername } func (ck *connectMethod) key() string { diff --git a/bfe_route/bfe_cluster/bfe_cluster.go b/bfe_route/bfe_cluster/bfe_cluster.go index c0db12c8d..5be369b3d 100644 --- a/bfe_route/bfe_cluster/bfe_cluster.go +++ b/bfe_route/bfe_cluster/bfe_cluster.go @@ -19,13 +19,8 @@ package bfe_cluster import ( "sync" "time" -) -import ( "github.com/baidu/go-lib/log" -) - -import ( "github.com/bfenetworks/bfe/bfe_config/bfe_cluster_conf/cluster_conf" ) @@ -36,6 +31,7 @@ type BfeCluster struct { backendConf *cluster_conf.BackendBasic // backend's basic conf CheckConf *cluster_conf.BackendCheck // how to check backend GslbBasic *cluster_conf.GslbBasicConf // gslb basic + httpsConf *cluster_conf.BackendHTTPS // https basic timeoutReadClient time.Duration // timeout for read client body timeoutReadClientAgain time.Duration // timeout for read client again @@ -57,6 +53,7 @@ func (cluster *BfeCluster) BasicInit(clusterConf cluster_conf.ClusterConf) { // set backendConf and checkConf cluster.backendConf = clusterConf.BackendConf cluster.CheckConf = clusterConf.CheckConf + cluster.httpsConf = clusterConf.HTTPSConf // set gslb retry conf cluster.GslbBasic = clusterConf.GslbBasic @@ -102,6 +99,14 @@ func (cluster *BfeCluster) BackendConf() *cluster_conf.BackendBasic { return res } +func (cluster *BfeCluster) BackendHTTPSConf() *cluster_conf.BackendHTTPS { + cluster.RLock() + res := cluster.httpsConf + cluster.RUnlock() + + return res +} + func (cluster *BfeCluster) RetryLevel() int { cluster.RLock() retryLevel := cluster.backendConf.RetryLevel diff --git a/bfe_server/bfe_server.go b/bfe_server/bfe_server.go index cfd5fa3e6..a11b45ef1 100644 --- a/bfe_server/bfe_server.go +++ b/bfe_server/bfe_server.go @@ -24,13 +24,8 @@ import ( "sync" "syscall" "time" -) -import ( "github.com/baidu/go-lib/log" -) - -import ( "github.com/bfenetworks/bfe/bfe_balance" "github.com/bfenetworks/bfe/bfe_config/bfe_cluster_conf/cluster_conf" "github.com/bfenetworks/bfe/bfe_config/bfe_conf" @@ -396,13 +391,13 @@ func (srv *BfeServer) GetServerConf() *bfe_route.ServerDataConf { // GetCheckConf implements CheckConfFetcher and return current // health check configuration. -func (srv *BfeServer) GetCheckConf(clusterName string) *cluster_conf.BackendCheck { - sf := srv.GetServerConf() +func (s *BfeServer) GetCheckConf(clusterName string) (*cluster_conf.BackendCheck, *cluster_conf.BackendHTTPS) { + sf := s.GetServerConf() cluster, err := sf.ClusterTable.Lookup(clusterName) if err != nil { - return nil + return nil, nil } - return cluster.BackendCheckConf() + return cluster.BackendCheckConf(), cluster.BackendHTTPSConf() } func (srv *BfeServer) InitListeners(config bfe_conf.BfeConfig) error { diff --git a/bfe_server/reverseproxy.go b/bfe_server/reverseproxy.go index a14e69424..12685ed3a 100644 --- a/bfe_server/reverseproxy.go +++ b/bfe_server/reverseproxy.go @@ -29,15 +29,11 @@ import ( "strings" "sync" "time" -) -import ( "golang.org/x/net/http2" "github.com/baidu/go-lib/log" -) -import ( bfe_cluster_backend "github.com/bfenetworks/bfe/bfe_balance/backend" "github.com/bfenetworks/bfe/bfe_balance/bal_gslb" "github.com/bfenetworks/bfe/bfe_basic" @@ -63,8 +59,9 @@ import ( // prior to the headers being written. If the set of trailers is fixed // or known before the header is written, the normal Go trailers mechanism // is preferred: -// https://golang.org/pkg/net/http/#ResponseWriter -// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers +// +// https://golang.org/pkg/net/http/#ResponseWriter +// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers const TrailerPrefix = "Trailer:" // RoundTripperMap holds mappings from cluster-name to RoundTripper. @@ -140,6 +137,53 @@ func setBackendAddr(req *bfe_http.Request, backend *bfe_cluster_backend.BfeBacke req.URL.Host = backend.GetAddrInfo() } +// compareHttpsConf compares two BackendHTTPS configurations and determines whether they are identical. +// Return: +// - bool: Returns true if all fields in src and dst are identical, otherwise false. +func compareHttpsConf(src, dst *cluster_conf.BackendHTTPS) bool { + // Check if either src or dst is nil + if src == nil || dst == nil { + return src == dst // Both must be nil to be equal + } + + // Compare RSHost + if (src.RSHost == nil) != (dst.RSHost == nil) || (src.RSHost != nil && *src.RSHost != *dst.RSHost) { + return false + } + + // Compare RSInsecureSkipVerify + if (src.RSInsecureSkipVerify == nil) != (dst.RSInsecureSkipVerify == nil) || (src.RSInsecureSkipVerify != nil && *src.RSInsecureSkipVerify != *dst.RSInsecureSkipVerify) { + return false + } + + // Compare RSCAList + if (src.RSCAList == nil) != (dst.RSCAList == nil) { + return false + } + if src.RSCAList != nil && dst.RSCAList != nil { + if len(*src.RSCAList) != len(*dst.RSCAList) { + return false + } + for i := range *src.RSCAList { + if (*src.RSCAList)[i] != (*dst.RSCAList)[i] { + return false + } + } + } + + // Compare BFECertFile + if (src.BFECertFile == nil) != (dst.BFECertFile == nil) || (src.BFECertFile != nil && *src.BFECertFile != *dst.BFECertFile) { + return false + } + + // Compare BFEKeyFile + if (src.BFEKeyFile == nil) != (dst.BFEKeyFile == nil) || (src.BFEKeyFile != nil && *src.BFEKeyFile != *dst.BFEKeyFile) { + return false + } + + return true +} + func (p *ReverseProxy) setTransports(clusterMap bfe_route.ClusterMap) { p.tsMu.Lock() defer p.tsMu.Unlock() @@ -157,7 +201,15 @@ func (p *ReverseProxy) setTransports(clusterMap bfe_route.ClusterMap) { case *bfe_http.Transport: // get transport, check if transport needs update backendConf := conf.BackendConf() - if (t.MaxIdleConnsPerHost != *backendConf.MaxIdleConnsPerHost) || + + proto := "http" + if t.HttpsConf != nil { + proto = "https" + } + + if (proto != *backendConf.Protocol) || + !compareHttpsConf(t.HttpsConf, conf.BackendHTTPSConf()) || + (t.MaxIdleConnsPerHost != *backendConf.MaxIdleConnsPerHost) || (t.MaxConnsPerHost != *backendConf.MaxConnsPerHost) || (t.ResponseHeaderTimeout != time.Millisecond*time.Duration(*backendConf.TimeoutResponseHeader)) || (t.ReqWriteBufferSize != conf.ReqWriteBufferSize()) || @@ -168,7 +220,6 @@ func (p *ReverseProxy) setTransports(clusterMap bfe_route.ClusterMap) { newTransports[cluster] = transport continue } - newTransports[cluster] = transport default: transport = createTransport(conf) @@ -202,7 +253,7 @@ func createTransport(cluster *bfe_cluster.BfeCluster) bfe_http.RoundTripper { log.Logger.Debug("create a new transport for %s, timeout %d", cluster.Name, *backendConf.TimeoutResponseHeader) switch protocol { - case "http": + case "http", "https": // cluster has its own Connect Server Timeout. // so each cluster has a different transport // once cluster's timeout updated, dailer use new value @@ -211,7 +262,7 @@ func createTransport(cluster *bfe_cluster.BfeCluster) bfe_http.RoundTripper { return net.DialTimeout(network, add, timeout) } - return &bfe_http.Transport{ + transport := &bfe_http.Transport{ Dial: dailer, DisableKeepAlives: (*backendConf.MaxIdleConnsPerHost) == 0, MaxIdleConnsPerHost: *backendConf.MaxIdleConnsPerHost, @@ -221,6 +272,10 @@ func createTransport(cluster *bfe_cluster.BfeCluster) bfe_http.RoundTripper { DisableCompression: true, MaxConnsPerHost: *backendConf.MaxConnsPerHost, } + if protocol == "https" { + transport.SetHttpsConf(cluster.BackendHTTPSConf()) + } + return transport case "fcgi": return &bfe_fcgi.Transport{ Root: backendConf.FCGIConf.Root, @@ -561,11 +616,11 @@ func (p *ReverseProxy) setReadClientAgainTimeout(cluster *bfe_cluster.BfeCluster // ServeHTTP processes http request and send http response. // // Params: -// - rw : context for sending response -// - request: context for request +// - rw : context for sending response +// - request: context for request // // Return: -// - action: action to do after ServeHTTP +// - action: action to do after ServeHTTP func (p *ReverseProxy) ServeHTTP(rw bfe_http.ResponseWriter, basicReq *bfe_basic.Request) (action int) { var err error var res *bfe_http.Response diff --git a/bfe_tls/bfe_testkeys.go b/bfe_tls/bfe_testkeys.go new file mode 100644 index 000000000..837447d6d --- /dev/null +++ b/bfe_tls/bfe_testkeys.go @@ -0,0 +1,300 @@ +// Copyright (c) 2025 The BFE Authors. +// +// 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, +// 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 bfe_tls + +type TestPemNameEnmu string + +/* +* +Prefix Explanation + - I: Intermediate certificate issued + - R: Root certificate issued + +Suffix Explanation + - CRT: x509 certificate + - PRV: Private key +*/ +const ( + BFE_R_CA_CRT TestPemNameEnmu = "bfe_r_ca.crt" + BFE_I_CA_CRT TestPemNameEnmu = "bfe_i_ca.crt" + I_BFE_DEV_PRV TestPemNameEnmu = "i_bfe_dev_prv.pem" + I_BFE_DEV_CRT TestPemNameEnmu = "i_bfe_dev.crt" + I_CN_FOOBAR_ORG_PRV TestPemNameEnmu = "i_cn_foobar.org_prv.pem" + I_CN_FOOBAR_ORG_CRT TestPemNameEnmu = "i_cn_foobar.org.crt" + R_BFE_DEV_PRV TestPemNameEnmu = "r_bfe_dev_prv.pem" + R_BFE_DEV_CRT TestPemNameEnmu = "r_bfe_dev.crt" + R_SAN_EXAMPLE_ORG_PRV TestPemNameEnmu = "r_san_example.org_prv.pem" + R_SAN_EXAMPLE_ORG_CRT TestPemNameEnmu = "r_san_example.org.crt" +) + +var testkeys = map[TestPemNameEnmu]string{ + BFE_R_CA_CRT: `-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIBCjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJjbjEQ +MA4GA1UECAwHYmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsM +C3lpbmdmZWktZGV2MRcwFQYDVQQDDA55aW5nZmVpLWRldi1jYTAeFw0yMzExMDMx +MzQ5NTVaFw0zNzA3MTIxMzQ5NTVaMGQxCzAJBgNVBAYTAmNuMRAwDgYDVQQIDAdi +ZWlqaW5nMRQwEgYDVQQKDAt5aW5nZmVpLWRldjEUMBIGA1UECwwLeWluZ2ZlaS1k +ZXYxFzAVBgNVBAMMDnlpbmdmZWktZGV2LWNhMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAvNA3HrsMjBcXrMIIhGVWsurIA1F9jxKeA7dh06H00Vt4inVV +SUvNFrTqgPRhLkAhGRMPxrjVRgJ5bbFqqXIuPIpUFBhUsWXIDH+oVXQl9jsxAXaG +gZ0lTO/uYR9qyrS1rj9nyNPwRf59Al/VlsQL71cNQ/T/agJ4PfvPfULTPLOsqclJ +hj0IgXmDj464dqcdG3ZdfXpfhNF6ab+8YjpwafTRmY+LoV8qjUwsYeJMcW4N8pxJ +8F2ktZj9J6uWepNGj+87ZeXg9XquzC62ASIFzPjoE1WN//Q518EqizxhuLGBDpK3 +6sEYUK4kHYUL4gZKFPlKTPXIl0ZsJIM5PsBMKwIDAQABo1AwTjAdBgNVHQ4EFgQU +DZloT8VhVbysD819oG/oqHdW1D0wHwYDVR0jBBgwFoAUDZloT8VhVbysD819oG/o +qHdW1D0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEALgB+IcN3UD4T ++4g8jaOyOiSUpUVdYqW+3go5DppqnvlGNq99N2cXKqssVPa/T9TikEcBEicFa8zU +bwlx6TEte+MkWfWdQxFR1EuI1FgKr3ps6ZBRr7MPpnYmI7K9aK372K9n7WrQhbmP +s7ult8bWB1/t6o3R7B9ChNkWT+7DPD4+FvB1GMJSGPno7cdnvDkevBOuC2DnQl3M ++ADFAge1Lo8wKBy6gYkNFd2BfarHGvRC5Qmmrme+RIpWZnvux1+lfXIInnfSTJRM +uAo/ePkoNsM3qQll6uEdhDxOMx8Pq94bCkM3DtI3ObuxWXjKCgUm/n9yetUvPj4U +iYhRUqHpUg== +-----END CERTIFICATE----- +`, + BFE_I_CA_CRT: `-----BEGIN CERTIFICATE----- +MIIDwDCCAqigAwIBAgIBCzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJjbjEQ +MA4GA1UECAwHYmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsM +C3lpbmdmZWktZGV2MRcwFQYDVQQDDA55aW5nZmVpLWRldi1jYTAeFw0yMzExMDMx +NDAxNDZaFw0zNzA3MTIxNDAxNDZaMIGQMQswCQYDVQQGEwJjbjEQMA4GA1UECAwH +YmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsMC3lpbmdmZWkt +ZGV2MRgwFgYDVQQDDA95aW5nZmVpLWRldi1pY2ExKTAnBgkqhkiG9w0BCQEWGmxp +YW5nY2h1YW5AeWYtbmV0d29ya3MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA6eFuWgoknixrRO9NCX4jAKyLtcAOWVJqVN2yX7CxjZjLyPurjvTZ +W73NYUbrPAN4AB5gY6UAPzuiEOSopVVIZ0OschK0cJldu9vZ0mZObBOsovuFcLQq +dgNSJ5slJSk7tCgD2EnCB3GYPG4D+uIKYd0c49wzTWWv4bjDwpgnf0LQbFpy7GhN +7D59zFH4qgOK/IQ5vaTMGyvIvtWR5/1Gvc9MLpGopTgi0DiNLed4UwDYrod5kysl +q3UcB5puONHQISOVoD3uRxo7wdsmVsHUfW7YfAWkhi6ec8mx9fy8IyE6f7GlXnuV +ysNccwqyEotL5bOXPJCqwUCL1v+iKTMPWwIDAQABo1AwTjAdBgNVHQ4EFgQUCjvC +vDVr8QXh9/hMI5xBgNvoSFgwHwYDVR0jBBgwFoAUDZloT8VhVbysD819oG/oqHdW +1D0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAhIggK/6JK4N7+HZW +VoTemwRpilugZZyKrcVAHbiiwUfXVQVuI64vc+yHWMSFRD1mykHkKBFzxEoDabMl +ASBtUJNt4b4zEL9V7k295vmAOp2IdLUQxlgeKWqABm4DGX96tGR9nKQTcn1ZeAxx +NyQFV1aj+dnNcF1iFFNF6t0bRrOEZ/aRSiu1bWp3Dj1JYTjXbyyplh3Ktb8lv4lt +EmvCXjo/l4TgQC9233kcTkXcq1swppzkkXfhB0NVuf9DE3C2xAWX0b2FiIEqnAhl +Bx0Cn3RrX7PZ6qrdRL6oBKvGJV6DP8BlF4LXiuD1VN/waQFJha57KQ78ZYYIXxQg +WMjWgA== +-----END CERTIFICATE-----`, + I_BFE_DEV_PRV: `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxTz6v6i/s/egWxWvXrjwmGp5c4QZD4ybcCezJHOQvmGpjwaf +yVqgvEXprxOZ9DUeE3frCFb2j6wo0KeofvZcerUaiE/Lde0/olhUC2N5KZKcZI6T +z6IKXqO/IYUH5QLZoOt/p+R61vfDmm/RPedScuPe7vi7IjHZqjG+zevRpngWt9vc +CMMe15NBmkyUkfDcD2L+TKibpEFa+NMMUuM4ZdwrWY4ctbgvPGNWDF97wa4riKGG +sM3V6iayHqgKsdstpk9tYY/+RKqGQSaMYgbRZA+4jUysuE3oOsKz0QOhn5XAAe98 +rhJbCa5Fi5avnTVQOypDk5aAxsdHybip8lvP/wIDAQABAoIBAEJNA0UV5osKfF7h +5TeEF2xErlrEVuBBNab30WI5brhwf9zSLzgyPMHNBoaRojjS+i81Kk59XRhimL26 +/grfqaqd4jNcD1quy6s44deKMppk7ClpPAqZv76ccI1F+Kdk098iCqFXTmugkaIC +YGXcsnxoWPIfrlgKRc7ONgNmd+zq7SAuVxC7gFrDSkjNX0rtMF32y2ZaSjPnwwu+ +Eego/wk35IhMYM7xx2t39CQPwXxvTeGmc2Ri8Q4fWtiVnPZAIURDghouWEwoU/T+ +70cByaZpoxPJLfgSC5XHAGOm1C+XzKw8H0SKZsPVLnQrhx8ouNhMGJ16e8MhHWmN +r3V2OkECgYEA/U2KB5S+rWfnKwkI9hucTA1XBFprEVVSAjRLKpwrR5E6SZbotiAu +mMi5BPQA0VD94M66JQE9p0q6VmfJYZGWq8ALvpA+bh+u914L66HU5eRCtfXA8rCb +GyOv27GHyam9guzisJJUCNpgvM+IBHhAHT3tX0+E9sj+oduYyZbkto8CgYEAx1ae +Eboy3baPvsMBAlNS38f0NUpfvp8j4jiPcGk48BWafn7jTU5GC4aRSmzCWhIKvgmA +If7hiEBFC+HQWXUVrq4svV7CIxd460E+OuLPz9cOUse+ppAToJXqlr0FRQyBPovH +eZ5E2SSK+ubv7cqrD2soMlBxdq67F6QK5rZFh5ECgYEAixtFHUqzuJliG4E3uaiK +Gj4NNqfNOtSnV+yOBxWCsyfvYbCNlk9wJ5m7+htiQ5F+CzKciWv4BuKEGKWgs4N3 +wlSSXpHqpyGhPdoZI8tZFvNXK4SN9PnGBI6Bql4Bm18rYzZie+OwYLhE/gvev82m +MCjtLjWGaN0S8aKecr76VcMCgYAM+C3AqYS1uDMSDk36gMFbnf7dmMEx/div1049 +2hrCRCWRJWBUn0sfZNn/JaRfh9z7EFMt4w95dfUIGOEdcOjAPMTcbVXkQpqzc8NA +wZETzMI50JUu8SDVyetBc3rsSyv9jcqktw9zsVT5jhz+M7l9f1NWMrWvKx8xIpMy +/5j2gQKBgGQ5mRLkmTl8cruj+pa9VBZUNBGW0JIEY6U44iX4l1TrPd34UjANb9Cu +pcoy47AvYKGcZUvS2Z1KKscqClzue0glgtw06zVIK9z1non/3suiGENSREu534ey +ilHWKsWtn+943EndCZMVdECe60AbUMxNWECos3KdEOKo262hG+kU +-----END RSA PRIVATE KEY----- +`, + I_BFE_DEV_CRT: `-----BEGIN CERTIFICATE----- +MIID4zCCAsugAwIBAgIBCzANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMCY24x +EDAOBgNVBAgMB2JlaWppbmcxFDASBgNVBAoMC3lpbmdmZWktZGV2MRQwEgYDVQQL +DAt5aW5nZmVpLWRldjEYMBYGA1UEAwwPeWluZ2ZlaS1kZXYtaWNhMSkwJwYJKoZI +hvcNAQkBFhpsaWFuZ2NodWFuQHlmLW5ldHdvcmtzLmNvbTAeFw0yMzExMDMxNTUz +NDhaFw0zMzEwMzExNTUzNDhaMFwxCzAJBgNVBAYTAmNuMRAwDgYDVQQIDAdiZWlq +aW5nMRQwEgYDVQQKDAt5aW5nZmVpLWRldjEUMBIGA1UECwwLeWluZ2ZlaS1kZXYx +DzANBgNVBAMMBmktdGVzdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AMU8+r+ov7P3oFsVr1648JhqeXOEGQ+Mm3AnsyRzkL5hqY8Gn8laoLxF6a8TmfQ1 +HhN36whW9o+sKNCnqH72XHq1GohPy3XtP6JYVAtjeSmSnGSOk8+iCl6jvyGFB+UC +2aDrf6fketb3w5pv0T3nUnLj3u74uyIx2aoxvs3r0aZ4Frfb3AjDHteTQZpMlJHw +3A9i/kyom6RBWvjTDFLjOGXcK1mOHLW4LzxjVgxfe8GuK4ihhrDN1eomsh6oCrHb +LaZPbWGP/kSqhkEmjGIG0WQPuI1MrLhN6DrCs9EDoZ+VwAHvfK4SWwmuRYuWr501 +UDsqQ5OWgMbHR8m4qfJbz/8CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhC +AQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFL/f +4zPE61A0bZ6854Yg0q4zbZ9GMB8GA1UdIwQYMBaAFAo7wrw1a/EF4ff4TCOcQYDb +6EhYMA0GCSqGSIb3DQEBCwUAA4IBAQAIUv8sRqJ1NfrbURoQdS2N17P66q0t717e +dpHSBfCpqeq4aVgDwKT1k6XRr/LdYgSHuatctProtcHT3BOSqrT2iqK4YvEdlht8 +obOoL7aQFn5hFh64n5ziHHZt1XrZjNobT7jilxHgekdsJlpfT7tIwlMS2vTOy3qJ +7yj2uHr0VCxDLFaW0fdsiAyLY+K7GertqU2xs0eJNnbrx5lA/7QqpQvhRQh97Rn6 +pldqXdsQ/wGOueVyzyRg08HAmLR/BkB4tg6ZpeY7pR2YWr2p6C6v+pg5p3RqXp8f +2T0fpCROTtl47Rzl8Az3D0NItImVLuyp1FMxqBXLIUObk/aM3z+d +-----END CERTIFICATE----- +`, + I_CN_FOOBAR_ORG_PRV: `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAreq0i1hG2iGhDNzsA7jYRPqK9WlU8ZJGcrfKgtx5PUEkvkPi +Kqg7epkHzSSftOA2Vis1L612SXzi75zGFP9XyBmmu5P1Cjt7AMy+pfsetlwEyHdz +CV9OoFj0UmeFhPgK44LqejlOq7dnvYTDTMLW4w/e4Cv6LdNNc9UYHOWtzwdgWtP+ +KODxpAJMTFUNqy/+al7GXodIdZLwK9hZ0pahjOck9h5Z6FyZ5dyBWWFCOuzydWvb +k6KSmYUbnW2EGTIxu83X/gUlRK5iQiDs90eOORoz1BtdF8lJp2fnCM7NExNvzc9h +6GRjY4P0HXaeO6q3VD2JG//TdmM1JjXTDJRbmQIDAQABAoIBAFDBXeoiKGwbF2wg +nRqxVwLYj3Oa4E13jWyxOMCA3W7687PBU6BZE50+t6ei7OGRBsoGMCEeW4GOEtYO +JtATLNCGUKxi7HI+4kOhzpjFvSISIkpvZKQidCDyjShCV3ZBstSnsnbka0pC1FHk +9saK5jry4JuY8AcdSaaSeSrZ31X0o+igelUe1NFCGQYdNrctrK8WmpXIE6B17xw9 +toorD/bvBLpKJ7857s6AdWI0/jH3MjUq43DwTg/4MD4ppmgRRDmtdghdC4wyKse+ +BNsbRIfASApGefQZloP3VjztzJ+MzF2ATjdWGPipcKjHDOteLDGW1YWiOIjFxndA +KKKidl0CgYEA1f7Vt6XCCVk8BTuBAf9uVAkDvgCglumtJ16DmgtT3DVNyDddboqg +aoJK0rONMbVRUhiPOeJdhZyQOh6qTx6XVxUq0IdpZ9dgPy7t6mYaBv+Je8pIbUMq +qVC89IEg9oF5YfeDyd8Pt+glU3NKjpOGpC2aIwcx8+BeljHw1fa9mPsCgYEA0A3w +Z1MAAB+2Nz+d0fmRkG4H4My4tSAr0Dy7A1TfEKrh+SOLX6pXfvV5CN2vHRLbxSOW +wsng27BO84/sxKhvQhl+twZTj8mKv8Q5ggGxks8F3UaVOrr5Tr/tBpD3Uyvf5Sbf +a5aDwHtBvq/g0pH/nmvjuDd7igGG4K49Id/yoXsCgYASdIvR7sWxMLTwbpVNqs3F +CZH9DOjMmxKH1qra2ic9UouGvN+d7O6wwNPbIAkJRG3i+qM/hroyD7KQkJx3flfm +9rhei48XmYd/a3ixQmT0PY2J04QziBthxsjE/W3uVHQ3crU277nXTnoJnGPNsANo +nVYxtykjszH1GhF/ImxviQKBgGjuLsodvUlStRbGOfV1limupMCG371R/WbnyjYS +7vG8DX4WpCtagQhiC2oiTDgwk7Cok1eoc4S5Ngh3FSXWAU7oBtWOFkTVk+nFsG9W +JVXynXWNLKRTOnuyJnwCTwqefSxYX6QmZEqGn5DpqUzqH26p4U6+hMsEnB7jIafd +B8TTAoGBALJyVJ7SULTr2Q03s/GDaug9KaIVexfieIRqbY2fsZPhlUQXt0dERznd +CS/qnU2OH69ddQagJ63T8SK54PSLhQ7XsjxdZ92GxMdid+KzTH277f2lklnneE8q +I5glmikP62ljseHxls2rr0nMod7yMwnn3gbQX7TMFdd2oSCiVEMs +-----END RSA PRIVATE KEY----- +`, + I_CN_FOOBAR_ORG_CRT: `-----BEGIN CERTIFICATE----- +MIIEDTCCAvWgAwIBAgIBDDANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMCY24x +EDAOBgNVBAgMB2JlaWppbmcxFDASBgNVBAoMC3lpbmdmZWktZGV2MRQwEgYDVQQL +DAt5aW5nZmVpLWRldjEYMBYGA1UEAwwPeWluZ2ZlaS1kZXYtaWNhMSkwJwYJKoZI +hvcNAQkBFhpsaWFuZ2NodWFuQHlmLW5ldHdvcmtzLmNvbTAgFw0yNDExMDYwMjUw +NDdaGA8yMTI0MTAxMzAyNTA0N1owgYMxCzAJBgNVBAYTAmNuMRAwDgYDVQQIDAdi +ZWlqaW5nMRQwEgYDVQQKDAt5aW5nZmVpLWRldjEUMBIGA1UECwwLeWluZ2ZlaS1k +ZXYxEzARBgNVBAMMCmZvb2Jhci5vcmcxITAfBgkqhkiG9w0BCQEWEmNjMTQ1MTRA +aWNsb3VkLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3qtItY +RtohoQzc7AO42ET6ivVpVPGSRnK3yoLceT1BJL5D4iqoO3qZB80kn7TgNlYrNS+t +dkl84u+cxhT/V8gZpruT9Qo7ewDMvqX7HrZcBMh3cwlfTqBY9FJnhYT4CuOC6no5 +Tqu3Z72Ew0zC1uMP3uAr+i3TTXPVGBzlrc8HYFrT/ijg8aQCTExVDasv/mpexl6H +SHWS8CvYWdKWoYznJPYeWehcmeXcgVlhQjrs8nVr25OikpmFG51thBkyMbvN1/4F +JUSuYkIg7PdHjjkaM9QbXRfJSadn5wjOzRMTb83PYehkY2OD9B12njuqt1Q9iRv/ +03ZjNSY10wyUW5kCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYd +T3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFK0/W1YLE7CK +2QpgpSR40IgEbVp3MB8GA1UdIwQYMBaAFAo7wrw1a/EF4ff4TCOcQYDb6EhYMA0G +CSqGSIb3DQEBCwUAA4IBAQBoyqiqQFHmH3DqWX9BJywWe2b6CdmJo+8eRhLbrHpv +JxPZ7ThhwIZS8Wku8D/z20g6McpurNy/fihqLCfkSXmoXCM0pl6ZGorVJCb4/2H4 +VwmZXdSN4YREjF2Mz1tud2t0/BXKToKRT8tw/yamO0Zv53uf6R/SOBgLwFyMHNLa +oU0Mq8adjPdkHVs8nkI09WKPd300xExgksEtwM8afXF8zSuWMPyxnTULwEPpj/mP +BFXhHx0Xy6XnMXgHfeJS/kTvNOQ+7TGAhcTYEdHEioOnMVu0whvy7ewAf8mfR7IZ +8uaFZ81R6wW0nJRqoHDHj5h4fyErZ+PaCKlZxESfmNuC +-----END CERTIFICATE----- +`, + R_BFE_DEV_PRV: `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsn/BDM1JQ9mZeBVMWlKpp7/V65JxQ+E35SkbaPRvTOr6pIp8 +KQEq+nyBW8jroUCUtyuC4gAINoTwt9JaHlaXqjb/TQdJLf4lOvnp8a1ObiGXqfmi +payCIwrS4JfNLxSx8Iqo5sWXRZT4jj+WZlsLjHwHYRhEkvcjWsJLiFhZXcpcDW7d +/xhZZd+VmeM6NkgfPzrmzoXzCwReku1vjnSS5DdG2l8XYpyCQAb7KfhV8rojdcpk +wEUDEr31FxV+R9W9MPKZymvjB7CuRIkeECbqdd9vB7ZHdlRHT2z2aP6ozyIgc+gZ +VYr+9XjoUYhSgB951MWuj9Ir9kEBQgHPmMLJJQIDAQABAoIBAFAKHzupVb/58+o3 +yqv5wx94Uuk2GlnwxIqaezL94GaiO0/K1U/huS7m426P0rDU75qPBTpn/0bLJ9GV +nllaRNnLnYEh0juwaWtfovp+1ttlbseGK9uUVip2cQbKqvQAmKWe14vbcDCAU1Ad +zUgKbUxKVVjBdAZekVjiJNJ3o2L9WPhf/uQo7A1XAJh2DajlTbgvrDM73W+47QuU +X4OHU0FMio6bxupu3OWl1bMrnKhuC4qczZWf2nOpcVQa89rtopuP4ENLJuWkbeGk +YQpNilEclnAa/Noumt/j/6GKC1EEHFsH2CNRRIazcZrsFhkSKc4pn1Y/WI3vj8kZ ++RYnJsUCgYEA6gr0x8suwRTAmVpoPk4XyP1x+eInG7onV5RjgsUtgruYd9naxRHg +2p8PHcv32pDs51Fa+4RldyMd/jec1SscRF5/+VOP9qeoRnaDqUu+uASgEO3OBxbP +JcWovyxRHIQxbYCQtqIr9bdzXw55MBLZou/sBUVTAkrIyPVyjWqZlV8CgYEAwz7L +YyYN615TsrzZKURxMjj94Nmob/NldSLRXaR3Ax7/ABtEOA685cwQxq7ONdkJTMIA +uR8u2GHZSzGiWnehuF6Zp7Xs71a57eFbs3ueZvvEba4Dff7hl7Y4tTlwrKndKjvP +J/5a2Ol8siQcRWAXHOdzggEHMSZ/sB4hWswly/sCgYEAhLRBpyemEwTZUBrbELjm +86gBgFajJi2fMSGKaxOygnYsNYjpauSAQnX99D87Aks6iM6wb/zaK3tV/lc6LgSL +uph6p7yh3JGj8JAyh0PTmDPHLtIoCAz+18QDsqJGO40ZGaXUaDn8Aw9J85QZUxDd +Jm4zvalZL+uHfarukRDolLECgYBUiupS4nWAh3XCnZeDEQna72avaFBROZmjIRJ7 +c+28wj009JmTlH4jGzvgbG0KUBKA1Div8Fq+g5AtyS498jNqvDvYrSQNdwZHhR/K +Fis++KHTxFfqxOU2Zkcj4d1yRpNn6EIJVVBNQL0n/g7n03XupCIWFw/gLoV343QZ +9vAe5QKBgA8ml59z1w3eUooc0yGfLhihXqCmM3IU006bFbODA30fBU4QKrHO8+Yx +Xbz9bi/1QLagLG6FzYQAkOjBlEBt5XLayYvwSb8xvWsm5A3vzTAbMFDOsDPEmRoH +dWtQccJcygOuK+PZtoZnNoJciNO5c9dZWD3xtIiURmX/kVtWrf6N +-----END RSA PRIVATE KEY----- +`, + R_BFE_DEV_CRT: `-----BEGIN CERTIFICATE----- +MIIDtzCCAp+gAwIBAgIBDTANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJjbjEQ +MA4GA1UECAwHYmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsM +C3lpbmdmZWktZGV2MRcwFQYDVQQDDA55aW5nZmVpLWRldi1jYTAeFw0yMzExMDYx +MzExMjlaFw0zMzExMDMxMzExMjlaMF0xCzAJBgNVBAYTAmNuMRAwDgYDVQQIDAdi +ZWlqaW5nMRQwEgYDVQQKDAt5aW5nZmVpLWRldjEUMBIGA1UECwwLeWluZ2ZlaS1k +ZXYxEDAOBgNVBAMMB2JmZV9kZXYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCyf8EMzUlD2Zl4FUxaUqmnv9XrknFD4TflKRto9G9M6vqkinwpASr6fIFb +yOuhQJS3K4LiAAg2hPC30loeVpeqNv9NB0kt/iU6+enxrU5uIZep+aKlrIIjCtLg +l80vFLHwiqjmxZdFlPiOP5ZmWwuMfAdhGESS9yNawkuIWFldylwNbt3/GFll35WZ +4zo2SB8/OubOhfMLBF6S7W+OdJLkN0baXxdinIJABvsp+FXyuiN1ymTARQMSvfUX +FX5H1b0w8pnKa+MHsK5EiR4QJup1328Htkd2VEdPbPZo/qjPIiBz6BlViv71eOhR +iFKAH3nUxa6P0iv2QQFCAc+YwsklAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZI +AYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQW +BBTu6GhC37Hw729HUb3UlGAfBYWhAzAfBgNVHSMEGDAWgBQNmWhPxWFVvKwPzX2g +b+iod1bUPTANBgkqhkiG9w0BAQsFAAOCAQEANZjZV/+uNUpr/FRUI9go6I3nJ95g +ElJxzz7QIgg4/UuZoD9bEA9d1qwIs5lxLqMGAHFwyqh5U8/6lHsFG9iZsbpWU+B0 +Dm5lyyNIediedSviri8mArQYZEckGrERTMFZ08hj3SrGyS5q1Z/IjJf5Tc9edh2X +D6pnaVcgf83M36yjHJybXopbKgJbHJTupSBp6DTUuSNe3Me7gf1ZrMdC7LhOs8ty +wuLVkRitAfb1SPhHVeivY75V+85fX5UsDJbFHGnUrTHSqqoCspqTvl+TdxH5T5gO +cyRHBgurceRZqAdCanjjgKG4yUFpWeOSDBb4EAbfC2Wvi3oqqyV0QN9wMQ== +-----END CERTIFICATE----- +`, + R_SAN_EXAMPLE_ORG_PRV: `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA1kewF2mRTNBmySUuOPKEJ+J8OMwEuQyOPczvS8U1K8GC2EH8 +Jcgk8uAlqvl2POKyUPst7E4W1MHaHuNRn53JcvnLzBScwYLJdh+Y3Q65iiDWq/Km ++S8jgfOv5EcMVZWU3u32pSTuMudreeFCb6QHspUd9amOYEJwQr3iMBhodFIymKmB +2tjGb14dznm28+xP7X0iV9IU0PvyUNSAO4ntd/1FbOZSawpScaxZyNUl9UAD+1Gw +EacAednYTwBDlmhEKUHc0syRyGGVQU0OZlq1FWc+im8p3xyKb+6el5yeaXHTNFJ1 +6ernUXcjmEbKR6LT05cDQUvjMxFyLa+/Kz6zUQIDAQABAoIBAC4sYGuLGf49Ygix +9FXdHFEj4rSyccoWRIhYoq/nHOAC4NkMzvKtQBj95+ABxVK1XstIdMrYwN6zrva8 +8Re9/mzCGwIs5uJj9ll30Y7A34Y+MUP4E7baS4JzKlG8ZZIDm4K2MFHBtXpOl8A5 +pAE+jVIUA9Kt6LohVuNq21SVzdxSfNYC/+SLqSftkWa/ZsqdkiHM5Hl+fVedh516 +IaLNW5hSthGh5n8dHY5h/AKPjfoq77aYp5/CUtJTC9mYdZu1j/W/pBVTRfOnwLQd +SQ1Xmr7f6q9Vmz+HnajIbFg9hQ54blvtUJ7DnugWxfUcoxf7ue79fnYjOIUOkRWw +8Iid/mECgYEA+5s0p0j+gkNZN5QtVNStoT04+1DqA1O381gczeaiz6Njt/MT0y5W +OpCsILQ70CpEjWAV+f6PDJiesDMxdGV+v2TCqK8ml8GahEczBLnHoAkPWCVf2XOX +oNj/CkZ2kmWufHFR+kcQbeDt1vFFcYUa61hKDFyNjinMW79Qy+PQID0CgYEA2gWd +7thE05sqU7/1MntmVRONKoAgnJmHcfSpWwLyh6E4YX3iKDSgI/9RnAMF39KUUY/O +XFWyIwAM9soeXknVsV/SmCaPeaEDiLHqz98aUEfvdLYMnuR883GgoXc4JrLsLw4z +oSi9lbAZFn0ekJ5L+rSFrY4rz9QZgYZxsLjUXKUCgYAbkaUSU2g3w8Np2J2i9u7T +hQ7SUsphdPHqAxSc5xGd6MxLYqIgeKpQHnwN1VHcfFUonIer7d2kxrBUpDdeBqT9 +ub+ulgqHhFo29ko70VNzUKrSwL2g6Q6LPFutt4zUe7nDvvL5loHRWF0XOTafurL5 +aKIsepO0KRZQU0U6IgszDQKBgFs6ZHaP2mTtFY4L0a75Ab3xu20gRgUhHRLq/H6P +wipMpMnuodaPBr9pU53Dig65D8T9Nq1eUnbgy4vs0T5FCPz6iqWN5RVQ8aieQhIP +WfRj1Wfx0WAfXcWEM2G9ACr5TWj3OVVjNclP8X9+hW6gPky+gv03c0+4gZ+4QRRg +ksPdAoGBAKXVv8L5Qt8rmi+QpF/6DVSeMB6lEevf319UEtyZPiYSBn1JJ9H+3Ddc +TMriXgZrw+PoXNJTAguDeGgzmtye1vraP0cjt7aG6sQ4YtMTnnuja880vK8z4i3Q +HJAi5fI8NvlW92dQBAccgrzFIKpRx+6CHwtCkNrxL2vJmGGzp8BR +-----END RSA PRIVATE KEY----- +`, + R_SAN_EXAMPLE_ORG_CRT: `-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIBDjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJjbjEQ +MA4GA1UECAwHYmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsM +C3lpbmdmZWktZGV2MRcwFQYDVQQDDA55aW5nZmVpLWRldi1jYTAeFw0yMzExMDYx +MzEyMThaFw0zMzExMDMxMzEyMThaMGExCzAJBgNVBAYTAmNuMRAwDgYDVQQIDAdi +ZWlqaW5nMRQwEgYDVQQKDAt5aW5nZmVpLWRldjEUMBIGA1UECwwLeWluZ2ZlaS1k +ZXYxFDASBgNVBAMMC2V4YW1wbGUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA1kewF2mRTNBmySUuOPKEJ+J8OMwEuQyOPczvS8U1K8GC2EH8Jcgk +8uAlqvl2POKyUPst7E4W1MHaHuNRn53JcvnLzBScwYLJdh+Y3Q65iiDWq/Km+S8j +gfOv5EcMVZWU3u32pSTuMudreeFCb6QHspUd9amOYEJwQr3iMBhodFIymKmB2tjG +b14dznm28+xP7X0iV9IU0PvyUNSAO4ntd/1FbOZSawpScaxZyNUl9UAD+1GwEacA +ednYTwBDlmhEKUHc0syRyGGVQU0OZlq1FWc+im8p3xyKb+6el5yeaXHTNFJ16ern +UXcjmEbKR6LT05cDQUvjMxFyLa+/Kz6zUQIDAQABo4GlMIGiMAkGA1UdEwQCMAAw +LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G +A1UdDgQWBBRaJzKd5zYko8HcL5WAxc8MhejmrzAfBgNVHSMEGDAWgBQNmWhPxWFV +vKwPzX2gb+iod1bUPTAnBgNVHREEIDAeggtleGFtcGxlLm9yZ4IPd3d3LmV4YW1w +bGUub3JnMA0GCSqGSIb3DQEBCwUAA4IBAQB3woFp8Bc64AkU1vo5SJ98y7zc7Nyg +xBD+g8r4DrCqWArRsCtLcvAreVSNXhhSygnqydT1qpnZCjq0GOtFWi5pb2cXwGLc +0/7jRXVM2bxg/cRQo5V+DsA+IWcmgxojoVT7wkNN5qUXjIABboD+8bv9a/hqavOq +ijd5dBEqTriaF33IJbF7gk35Va//j/glBMI6uPS+Ft5XPu8liQ3heJ/ulC4HXFaz +Elf9q6C2KE8Mx5YqIO6gAapF52Derkvj6ksJl6P6ToJODiYm//+xW+67f68a8nhQ +NX+/kQz6aJXT7/9MqDD7K/MqTKbVKsfbjQxAbSCet0jMiO1AFyBHoI8Y +-----END CERTIFICATE----- +`, +} + +func (t TestPemNameEnmu) Bytes() []byte { + return []byte(testkeys[t]) +} diff --git a/bfe_tls/bfe_testkeys_test.go b/bfe_tls/bfe_testkeys_test.go new file mode 100644 index 000000000..23a7f097c --- /dev/null +++ b/bfe_tls/bfe_testkeys_test.go @@ -0,0 +1,46 @@ +// Copyright (c) 2025 The BFE Authors. +// +// 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, +// 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 bfe_tls + +import ( + "crypto/x509" + "encoding/pem" + "fmt" + "testing" +) + +func TestKeysDecode(t *testing.T) { + chain := append(BFE_R_CA_CRT.Bytes(), BFE_I_CA_CRT.Bytes()...) + + // 解码 PEM 数据 + var certs []*x509.Certificate + block, rest := pem.Decode(chain) + for block != nil { + if block.Type == "CERTIFICATE" { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + fmt.Println("cannot parse certificate", err) + return + } + certs = append(certs, cert) + t.Log(cert.Subject) + } + if len(rest) > 0 { + block, rest = pem.Decode(rest) + } else { + break + } + } + +} diff --git a/bfe_tls/bfe_verify_hooks.go b/bfe_tls/bfe_verify_hooks.go new file mode 100644 index 000000000..909c9c8d5 --- /dev/null +++ b/bfe_tls/bfe_verify_hooks.go @@ -0,0 +1,275 @@ +// Copyright (c) 2025 The BFE Authors. +// +// 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, +// 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 bfe_tls + +import ( + "crypto/x509" + "fmt" + "net" + "strings" + "unicode/utf8" + + "github.com/baidu/go-lib/log" +) + +type VerifyCertHook func(insecureSkipVerify bool, host string, certificates []*x509.Certificate, cas *x509.CertPool) error + +type VerifyPeerCertHooks struct { + InsecureSkipVerify bool + Host string + certificates []*x509.Certificate + CAs *x509.CertPool + verifyFn []VerifyCertHook + disableDef bool +} + +func toLowerCaseASCII(in string) string { + // If the string is already lower-case then there's nothing to do. + isAlreadyLowerCase := true + for _, c := range in { + if c == utf8.RuneError { + // If we get a UTF-8 error then there might be + // upper-case ASCII bytes in the invalid sequence. + isAlreadyLowerCase = false + break + } + if 'A' <= c && c <= 'Z' { + isAlreadyLowerCase = false + break + } + } + + if isAlreadyLowerCase { + return in + } + + out := []byte(in) + for i, c := range out { + if 'A' <= c && c <= 'Z' { + out[i] += 'a' - 'A' + } + } + return string(out) +} +func matchExactly(hostA, hostB string) bool { + if hostA == "" || hostA == "." || hostB == "" || hostB == "." { + return false + } + return toLowerCaseASCII(hostA) == toLowerCaseASCII(hostB) +} + +func matchHostnames(pattern, host string) bool { + pattern = toLowerCaseASCII(pattern) + host = toLowerCaseASCII(strings.TrimSuffix(host, ".")) + + if len(pattern) == 0 || len(host) == 0 { + return false + } + + patternParts := strings.Split(pattern, ".") + hostParts := strings.Split(host, ".") + + if len(patternParts) != len(hostParts) { + return false + } + + for i, patternPart := range patternParts { + if i == 0 && patternPart == "*" { + continue + } + if patternPart != hostParts[i] { + return false + } + } + return true +} + +func validHostname(host string, isPattern bool) bool { + if !isPattern { + host = strings.TrimSuffix(host, ".") + } + if len(host) == 0 { + return false + } + for i, part := range strings.Split(host, ".") { + if part == "" { + // Empty label. + return false + } + if isPattern && i == 0 && part == "*" { + // Only allow full left-most wildcards, as those are the only ones + // we match, and matching literal '*' characters is probably never + // the expected behavior. + continue + } + for j, c := range part { + if 'a' <= c && c <= 'z' { + continue + } + if '0' <= c && c <= '9' { + continue + } + if 'A' <= c && c <= 'Z' { + continue + } + if c == '-' && j != 0 { + continue + } + if c == '_' { + // Not a valid character in hostnames, but commonly + // found in deployments outside the WebPKI. + continue + } + return false + } + } + return true +} +func verifyIpFn(c *x509.Certificate, h string) error { + candidateIP := h + if len(h) >= 3 && h[0] == '[' && h[len(h)-1] == ']' { + candidateIP = h[1 : len(h)-1] + } + if ip := net.ParseIP(candidateIP); ip != nil { + // We only match IP addresses against IP SANs. + // See RFC 6125, Appendix B.2. + for _, candidate := range c.IPAddresses { + if ip.Equal(candidate) { + return nil + } + } + return x509.HostnameError{Certificate: c, Host: candidateIP} + } + return nil +} + +// Only allow full left-most wildcards +func verifyHost(c *x509.Certificate, h string) error { + // Verify : SAN + names := c.DNSNames + if validHostname(c.Subject.CommonName, true) { + // Verify : CN + names = append(names, c.Subject.CommonName) + } + candidateName := toLowerCaseASCII(h) // Save allocations inside the loop. + validCandidateName := validHostname(candidateName, false) + for _, match := range names { + if validCandidateName && validHostname(match, true) { + if matchHostnames(match, candidateName) { + return nil + } + } else { + if matchExactly(match, candidateName) { + return nil + } + } + } + return x509.HostnameError{Certificate: c, Host: h} +} + +// NewVerifyPeerCertHooks : build hooks for bef_tls.Config.VerifyPeerCertificate and wait callback +func NewVerifyPeerCertHooks(insecureSkipVerify bool, host string, cas *x509.CertPool) *VerifyPeerCertHooks { + return &VerifyPeerCertHooks{ + InsecureSkipVerify: insecureSkipVerify, + Host: host, + CAs: cas, + verifyFn: make([]VerifyCertHook, 0), + } +} + +func (p *VerifyPeerCertHooks) DisableDefaultHooks() *VerifyPeerCertHooks { + p.disableDef = true + return p +} + +// Ready : ready to wait tls callback +func (p *VerifyPeerCertHooks) Ready() func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + if !p.disableDef { + p.verifyFn = append([]VerifyCertHook{defVerifyCA, defVerifyHost}, p.verifyFn...) + } + return p.verifyPeerCertificate +} + +func (p *VerifyPeerCertHooks) verifyPeerCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error { + + var certificates []*x509.Certificate + for _, rawCert := range rawCerts { + cert, err := x509.ParseCertificate(rawCert) + if err != nil { + return err + } + certificates = append(certificates, cert) + } + p.certificates = certificates + return p.apply() +} + +func (p *VerifyPeerCertHooks) AppendHook(fn VerifyCertHook) *VerifyPeerCertHooks { + p.verifyFn = append(p.verifyFn, fn) + return p +} + +func (p *VerifyPeerCertHooks) apply() error { + if !p.InsecureSkipVerify { + for _, fn := range p.verifyFn { + if err := fn(p.InsecureSkipVerify, p.Host, p.certificates, p.CAs); err != nil { + return err + } + } + } + return nil +} + +// defVerifyCA : verify cert by cas +func defVerifyCA(insecureSkipVerify bool, _ string, certificates []*x509.Certificate, cas *x509.CertPool) (err error) { + if !insecureSkipVerify { + for _, cert := range certificates { + if _, err = cert.Verify(x509.VerifyOptions{ + Roots: cas, + }); err != nil { + log.Logger.Debug("err=%s", err.Error()) + return err + } + log.Logger.Debug("HTTPS-Verify-CA-Success: %s", cert.Subject.String()) + } + } + return nil +} + +// defVerifyHost : verify host over CN and SAN +func defVerifyHost(insecureSkipVerify bool, host string, certificates []*x509.Certificate, _ *x509.CertPool) (err error) { + if !insecureSkipVerify { + for _, cert := range certificates { + var ( + err error + candidateIP = host + ) + if len(host) >= 3 && host[0] == '[' && host[len(host)-1] == ']' { + candidateIP = host[1 : len(host)-1] + } + if ip := net.ParseIP(candidateIP); ip != nil { + err = verifyIpFn(cert, host) + } else { + err = verifyHost(cert, host) + } + if err == nil { + return nil + } + log.Logger.Debug("debug_https not_match host=%s, sn=%s, cn=%s, san=%v, err=%v", host, cert.SerialNumber.String(), cert.Subject.CommonName, cert.DNSNames, err) + } + err = fmt.Errorf("host=%s not match CN/SAN", host) + return err + } + return nil +} diff --git a/bfe_tls/bfe_verify_hooks_test.go b/bfe_tls/bfe_verify_hooks_test.go new file mode 100644 index 000000000..9ea7a9066 --- /dev/null +++ b/bfe_tls/bfe_verify_hooks_test.go @@ -0,0 +1,113 @@ +// Copyright (c) 2025 The BFE Authors. +// +// 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, +// 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 bfe_tls + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "math/big" + "net" + "testing" + "time" +) + +// 创建 CN 证书 +func createCert(sn int64, cn string) *x509.Certificate { + template := &x509.Certificate{ + SerialNumber: big.NewInt(sn), + Subject: pkix.Name{CommonName: cn}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + BasicConstraintsValid: true, + } + priv, _ := rsa.GenerateKey(rand.Reader, 2048) + pub := &priv.PublicKey + certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, pub, priv) + cert, _ := x509.ParseCertificate(certBytes) + return cert +} + +// 创建带 SAN 的证书 +func createCertWithSAN(sn int64, dnsNames []string, ips []net.IP) *x509.Certificate { + template := &x509.Certificate{ + SerialNumber: big.NewInt(sn), + Subject: pkix.Name{}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour), + BasicConstraintsValid: true, + DNSNames: dnsNames, + IPAddresses: ips, + } + priv, _ := rsa.GenerateKey(rand.Reader, 2048) + pub := &priv.PublicKey + certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, pub, priv) + cert, _ := x509.ParseCertificate(certBytes) + return cert +} + +func TestDefVerifyHost(t *testing.T) { + // _ = log.Init(fmt.Sprintf("test_%s", time.Now().String()), "DEBUG", "/tmp", true, "M", 5) + type testcase struct { + host string + cert *x509.Certificate + assertOk bool + } + + var ( + cnCert0 = createCert(1, "example.com") + cnCert1 = createCert(2, "*.example.com") + sanCert = createCertWithSAN(3, + []string{"www.example.com", "*.example.org"}, + []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("192.168.0.1")}, + ) + _testcase = map[string]testcase{ + "cn0_assert_ok": {"example.com", cnCert0, true}, + "cn0_assert_1_fail": {"login.example.com", cnCert0, false}, + "cn0_assert_2_fail": {"www.example.com", cnCert0, false}, + "cn1_assert_wildcard1_ok": {"login.example.com", cnCert1, true}, + "cn1_assert_wildcard2_ok": {"www.example.com", cnCert1, true}, + "cn1_assert_wildcard4_fail": {"example.com", cnCert1, false}, + "cn1_assert_wildcard5_fail": {"example.org", cnCert1, false}, + "cn1_assert_wildcard6_fail": {"www.login.example.org", cnCert1, false}, + + "san_assert_wildcard1_fail": {"example.com", sanCert, false}, + "san_assert_wildcard2_fail": {"login.example.com", sanCert, false}, + "san_assert_wildcard3_ok": {"www.example.com", sanCert, true}, + "san_assert_wildcard4_ok": {"login.example.org", sanCert, true}, + "san_assert_wildcard5_ok": {"www.example.org", sanCert, true}, + "san_assert_wildcard6_fail": {"example.org", sanCert, false}, + + "san_assert_wildcard7_ok": {"127.0.0.1", sanCert, true}, + "san_assert_wildcard8_ok": {"192.168.0.1", sanCert, true}, + "san_assert_wildcard9_fail": {"192.168.0.2", sanCert, false}, + } + fn = func(tc testcase) func(t *testing.T) { + return func(t *testing.T) { + err := defVerifyHost(false, tc.host, []*x509.Certificate{tc.cert}, nil) + if tc.assertOk && err != nil { + t.Error(err) + } else if !tc.assertOk && err == nil { + t.Error("assertOk=false, err==nil") + } + } + } + ) + // 测试验证 + for name, val := range _testcase { + t.Run(name, fn(val)) + } + time.Sleep(time.Second) +} diff --git a/bfe_tls/common.go b/bfe_tls/common.go index 46e84aa0a..69c9c87fe 100644 --- a/bfe_tls/common.go +++ b/bfe_tls/common.go @@ -29,9 +29,7 @@ import ( "strings" "sync" "time" -) -import ( "golang.org/x/crypto/ocsp" ) @@ -60,7 +58,9 @@ const ( // Grade A+: no ssl3, tls1.0, tls1.1 && no RC4 ciphers // Grade A: no ssl3 && no RC4 ciphers // Grade B: ssl3 is ok only with RC4 cipher, or -// modern version(>=tls10) with no RC4 cipher +// +// modern version(>=tls10) with no RC4 cipher +// // Grade C: ssl3 is ok only with RC4 cipher const ( GradeAPlus = "A+" @@ -79,8 +79,8 @@ const ( * http://chimera.labs.oreilly.com/books/1230000000545/ch04.html#TLS_RECORD_SIZE */ var ( - initPlaintext int = minPlaintext // initial length of plaintext payload - bytesThreshold int = 1024 * 1024 // 1 MB + initPlaintext int = minPlaintext // initial length of plaintext payload + bytesThreshold int = 1024 * 1024 // 1 MB inactiveSeconds time.Duration = time.Duration(1 * time.Second) // 1 second ) @@ -349,6 +349,19 @@ type Config struct { // for all connections. NameToCertificate map[string]*Certificate + // VerifyPeerCertificate, if not nil, is called after normal + // certificate verification by either a TLS client or server. It + // receives the raw ASN.1 certificates provided by the peer and also + // any verified chains that normal processing found. If it returns a + // non-nil error, the handshake is aborted and that error results. + // + // If normal verification fails then the handshake will abort before + // considering this callback. If normal verification is disabled by + // setting InsecureSkipVerify, or (for a server) when ClientAuth is + // RequestClientCert or RequireAnyClientCert, then this callback will + // be considered but the verifiedChains argument will always be nil. + VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error + // default multiply certificates policy for tls server MultiCert MultiCertificate @@ -466,6 +479,7 @@ func (c *Config) Clone() *Config { Time: c.Time, Certificates: c.Certificates, NameToCertificate: c.NameToCertificate, + VerifyPeerCertificate: c.VerifyPeerCertificate, MultiCert: c.MultiCert, RootCAs: c.RootCAs, NextProtos: c.NextProtos, diff --git a/bfe_tls/handshake_client.go b/bfe_tls/handshake_client.go index 383cb2846..dfe6eaee9 100644 --- a/bfe_tls/handshake_client.go +++ b/bfe_tls/handshake_client.go @@ -268,6 +268,13 @@ func (hs *clientHandshakeState) doFullHandshake() error { } } + if c.config.VerifyPeerCertificate != nil { + if err := c.config.VerifyPeerCertificate(certMsg.certificates, c.verifiedChains); err != nil { + c.sendAlert(alertBadCertificate) + return err + } + } + switch certs[0].PublicKey.(type) { case *rsa.PublicKey, *ecdsa.PublicKey: break diff --git a/bfe_tls/handshake_server.go b/bfe_tls/handshake_server.go index c9eb3cd74..5d9fed2f3 100644 --- a/bfe_tls/handshake_server.go +++ b/bfe_tls/handshake_server.go @@ -932,8 +932,8 @@ func (hs *serverHandshakeState) processCertsFromClient(certificates [][]byte) (c c.verifiedChains = chains } + var pub crypto.PublicKey if len(certs) > 0 { - var pub crypto.PublicKey switch key := certs[0].PublicKey.(type) { case *ecdsa.PublicKey, *rsa.PublicKey: pub = key @@ -945,7 +945,14 @@ func (hs *serverHandshakeState) processCertsFromClient(certificates [][]byte) (c return pub, nil } - return nil, nil + if c.config.VerifyPeerCertificate != nil { + if err := c.config.VerifyPeerCertificate(certificates, c.verifiedChains); err != nil { + c.sendAlert(alertBadCertificate) + return nil, err + } + } + + return pub, nil } // tryCipherSuite returns a cipherSuite with the given id if that cipher suite diff --git a/conf/cluster_conf/cluster_table.data b/conf/cluster_conf/cluster_table.data index 4006823dc..f162dca2a 100644 --- a/conf/cluster_conf/cluster_table.data +++ b/conf/cluster_conf/cluster_table.data @@ -10,6 +10,16 @@ } ] }, + "https_cluster_example": { + "example.bfe.bj": [ + { + "Addr": "127.0.0.1", + "Name": "https_example_hostname", + "Port": 9443, + "Weight": 10 + } + ] + }, "fcgi_cluster_example": { "fcgi.example.bfe.bj": [ { diff --git a/conf/cluster_conf/gslb.data b/conf/cluster_conf/gslb.data index 67ba6f419..8b4f7c281 100644 --- a/conf/cluster_conf/gslb.data +++ b/conf/cluster_conf/gslb.data @@ -4,6 +4,10 @@ "GSLB_BLACKHOLE": 0, "example.bfe.bj": 100 }, + "https_cluster_example": { + "GSLB_BLACKHOLE": 0, + "example.bfe.bj": 100 + }, "fcgi_cluster_example": { "GSLB_BLACKHOLE": 0, "fcgi.example.bfe.bj": 100 diff --git a/conf/server_data_conf/cluster_conf.data b/conf/server_data_conf/cluster_conf.data index a23dfe602..0c03f3c67 100644 --- a/conf/server_data_conf/cluster_conf.data +++ b/conf/server_data_conf/cluster_conf.data @@ -35,6 +35,51 @@ "CancelOnClientClose": false } }, + "https_cluster_example": { + "BackendConf": { + "Protocol": "https", + "TimeoutConnSrv": 2000, + "TimeoutResponseHeader": 50000, + "MaxIdleConnsPerHost": 0, + "RetryLevel": 0 + }, + "CheckConf": { + "Schem": "https", + "Uri": "/", + "Host": "example.org", + "StatusCode": 200, + "FailNum": 10, + "CheckInterval": 1000 + }, + "GslbBasic": { + "CrossRetry": 0, + "RetryMax": 2, + "HashConf": { + "HashStrategy": 0, + "HashHeader": "Cookie:UID", + "SessionSticky": false + } + }, + "ClusterBasic": { + "TimeoutReadClient": 30000, + "TimeoutWriteClient": 60000, + "TimeoutReadClientAgain": 30000, + "ReqWriteBufferSize": 512, + "ReqFlushInterval": 0, + "ResFlushInterval": -1, + "CancelOnClientClose": false + }, + "HTTPSConf":{ + "RSHost": "www.example.org", + "BFEKeyFile": "../conf/tls_conf/backend_rs/r_bfe_dev_prv.pem", + "BFECertFile": "../conf/tls_conf/backend_rs/r_bfe_dev.crt", + "RSCAList": [ + "../conf/tls_conf/backend_rs/bfe_r_ca.crt", + "../conf/tls_conf/backend_rs/bfe_i_ca.crt" + ], + "RSInsecureSkipVerify": false + } + }, "h2c_cluster_example": { "BackendConf": { "Protocol": "h2c", diff --git a/conf/server_data_conf/host_rule.data b/conf/server_data_conf/host_rule.data index 734d675f7..28c485f8d 100644 --- a/conf/server_data_conf/host_rule.data +++ b/conf/server_data_conf/host_rule.data @@ -4,6 +4,7 @@ "Hosts": { "exampleTag":[ "example.org", + "www.example.org", "fcgi.example.org", "h2c.example.org" ], diff --git a/conf/server_data_conf/route_rule.data b/conf/server_data_conf/route_rule.data index 40e9710a2..987124747 100644 --- a/conf/server_data_conf/route_rule.data +++ b/conf/server_data_conf/route_rule.data @@ -6,6 +6,10 @@ "Cond": "req_host_in(\"example.org\")", "ClusterName": "cluster_example" }, + { + "Cond": "req_host_in(\"www.example.org\")", + "ClusterName": "https_cluster_example" + }, { "Cond": "req_host_in(\"fcgi.example.org\")", "ClusterName": "fcgi_cluster_example" diff --git a/conf/tls_conf/backend_rs/bfe_i_ca.crt b/conf/tls_conf/backend_rs/bfe_i_ca.crt new file mode 100644 index 000000000..8b5eeee95 --- /dev/null +++ b/conf/tls_conf/backend_rs/bfe_i_ca.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDwDCCAqigAwIBAgIBCzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJjbjEQ +MA4GA1UECAwHYmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsM +C3lpbmdmZWktZGV2MRcwFQYDVQQDDA55aW5nZmVpLWRldi1jYTAeFw0yMzExMDMx +NDAxNDZaFw0zNzA3MTIxNDAxNDZaMIGQMQswCQYDVQQGEwJjbjEQMA4GA1UECAwH +YmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsMC3lpbmdmZWkt +ZGV2MRgwFgYDVQQDDA95aW5nZmVpLWRldi1pY2ExKTAnBgkqhkiG9w0BCQEWGmxp +YW5nY2h1YW5AeWYtbmV0d29ya3MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA6eFuWgoknixrRO9NCX4jAKyLtcAOWVJqVN2yX7CxjZjLyPurjvTZ +W73NYUbrPAN4AB5gY6UAPzuiEOSopVVIZ0OschK0cJldu9vZ0mZObBOsovuFcLQq +dgNSJ5slJSk7tCgD2EnCB3GYPG4D+uIKYd0c49wzTWWv4bjDwpgnf0LQbFpy7GhN +7D59zFH4qgOK/IQ5vaTMGyvIvtWR5/1Gvc9MLpGopTgi0DiNLed4UwDYrod5kysl +q3UcB5puONHQISOVoD3uRxo7wdsmVsHUfW7YfAWkhi6ec8mx9fy8IyE6f7GlXnuV +ysNccwqyEotL5bOXPJCqwUCL1v+iKTMPWwIDAQABo1AwTjAdBgNVHQ4EFgQUCjvC +vDVr8QXh9/hMI5xBgNvoSFgwHwYDVR0jBBgwFoAUDZloT8VhVbysD819oG/oqHdW +1D0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAhIggK/6JK4N7+HZW +VoTemwRpilugZZyKrcVAHbiiwUfXVQVuI64vc+yHWMSFRD1mykHkKBFzxEoDabMl +ASBtUJNt4b4zEL9V7k295vmAOp2IdLUQxlgeKWqABm4DGX96tGR9nKQTcn1ZeAxx +NyQFV1aj+dnNcF1iFFNF6t0bRrOEZ/aRSiu1bWp3Dj1JYTjXbyyplh3Ktb8lv4lt +EmvCXjo/l4TgQC9233kcTkXcq1swppzkkXfhB0NVuf9DE3C2xAWX0b2FiIEqnAhl +Bx0Cn3RrX7PZ6qrdRL6oBKvGJV6DP8BlF4LXiuD1VN/waQFJha57KQ78ZYYIXxQg +WMjWgA== +-----END CERTIFICATE----- diff --git a/conf/tls_conf/backend_rs/bfe_r_ca.crt b/conf/tls_conf/backend_rs/bfe_r_ca.crt new file mode 100644 index 000000000..62065c47b --- /dev/null +++ b/conf/tls_conf/backend_rs/bfe_r_ca.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkzCCAnugAwIBAgIBCjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJjbjEQ +MA4GA1UECAwHYmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsM +C3lpbmdmZWktZGV2MRcwFQYDVQQDDA55aW5nZmVpLWRldi1jYTAeFw0yMzExMDMx +MzQ5NTVaFw0zNzA3MTIxMzQ5NTVaMGQxCzAJBgNVBAYTAmNuMRAwDgYDVQQIDAdi +ZWlqaW5nMRQwEgYDVQQKDAt5aW5nZmVpLWRldjEUMBIGA1UECwwLeWluZ2ZlaS1k +ZXYxFzAVBgNVBAMMDnlpbmdmZWktZGV2LWNhMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAvNA3HrsMjBcXrMIIhGVWsurIA1F9jxKeA7dh06H00Vt4inVV +SUvNFrTqgPRhLkAhGRMPxrjVRgJ5bbFqqXIuPIpUFBhUsWXIDH+oVXQl9jsxAXaG +gZ0lTO/uYR9qyrS1rj9nyNPwRf59Al/VlsQL71cNQ/T/agJ4PfvPfULTPLOsqclJ +hj0IgXmDj464dqcdG3ZdfXpfhNF6ab+8YjpwafTRmY+LoV8qjUwsYeJMcW4N8pxJ +8F2ktZj9J6uWepNGj+87ZeXg9XquzC62ASIFzPjoE1WN//Q518EqizxhuLGBDpK3 +6sEYUK4kHYUL4gZKFPlKTPXIl0ZsJIM5PsBMKwIDAQABo1AwTjAdBgNVHQ4EFgQU +DZloT8VhVbysD819oG/oqHdW1D0wHwYDVR0jBBgwFoAUDZloT8VhVbysD819oG/o +qHdW1D0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEALgB+IcN3UD4T ++4g8jaOyOiSUpUVdYqW+3go5DppqnvlGNq99N2cXKqssVPa/T9TikEcBEicFa8zU +bwlx6TEte+MkWfWdQxFR1EuI1FgKr3ps6ZBRr7MPpnYmI7K9aK372K9n7WrQhbmP +s7ult8bWB1/t6o3R7B9ChNkWT+7DPD4+FvB1GMJSGPno7cdnvDkevBOuC2DnQl3M ++ADFAge1Lo8wKBy6gYkNFd2BfarHGvRC5Qmmrme+RIpWZnvux1+lfXIInnfSTJRM +uAo/ePkoNsM3qQll6uEdhDxOMx8Pq94bCkM3DtI3ObuxWXjKCgUm/n9yetUvPj4U +iYhRUqHpUg== +-----END CERTIFICATE----- diff --git a/conf/tls_conf/backend_rs/r_bfe_dev.crt b/conf/tls_conf/backend_rs/r_bfe_dev.crt new file mode 100644 index 000000000..dbbb7e51a --- /dev/null +++ b/conf/tls_conf/backend_rs/r_bfe_dev.crt @@ -0,0 +1,85 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 18 (0x12) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=cn, ST=beijing, O=yingfei-dev, OU=yingfei-dev, CN=yingfei-dev-ca + Validity + Not Before: Jan 31 02:55:32 2024 GMT + Not After : Jan 28 02:55:32 2034 GMT + Subject: C=cn, ST=beijing, O=yingfei-dev, OU=yingfei-dev, CN=bfe-dev/emailAddress=dev@example.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b2:7f:c1:0c:cd:49:43:d9:99:78:15:4c:5a:52: + a9:a7:bf:d5:eb:92:71:43:e1:37:e5:29:1b:68:f4: + 6f:4c:ea:fa:a4:8a:7c:29:01:2a:fa:7c:81:5b:c8: + eb:a1:40:94:b7:2b:82:e2:00:08:36:84:f0:b7:d2: + 5a:1e:56:97:aa:36:ff:4d:07:49:2d:fe:25:3a:f9: + e9:f1:ad:4e:6e:21:97:a9:f9:a2:a5:ac:82:23:0a: + d2:e0:97:cd:2f:14:b1:f0:8a:a8:e6:c5:97:45:94: + f8:8e:3f:96:66:5b:0b:8c:7c:07:61:18:44:92:f7: + 23:5a:c2:4b:88:58:59:5d:ca:5c:0d:6e:dd:ff:18: + 59:65:df:95:99:e3:3a:36:48:1f:3f:3a:e6:ce:85: + f3:0b:04:5e:92:ed:6f:8e:74:92:e4:37:46:da:5f: + 17:62:9c:82:40:06:fb:29:f8:55:f2:ba:23:75:ca: + 64:c0:45:03:12:bd:f5:17:15:7e:47:d5:bd:30:f2: + 99:ca:6b:e3:07:b0:ae:44:89:1e:10:26:ea:75:df: + 6f:07:b6:47:76:54:47:4f:6c:f6:68:fe:a8:cf:22: + 20:73:e8:19:55:8a:fe:f5:78:e8:51:88:52:80:1f: + 79:d4:c5:ae:8f:d2:2b:f6:41:01:42:01:cf:98:c2: + c9:25 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + EE:E8:68:42:DF:B1:F0:EF:6F:47:51:BD:D4:94:60:1F:05:85:A1:03 + X509v3 Authority Key Identifier: + keyid:0D:99:68:4F:C5:61:55:BC:AC:0F:CD:7D:A0:6F:E8:A8:77:56:D4:3D + + X509v3 Extended Key Usage: + TLS Web Client Authentication + Signature Algorithm: sha256WithRSAEncryption + a9:a6:26:8e:42:61:15:22:ee:fc:b5:e1:e4:6b:dd:ac:f5:15: + 11:39:10:9a:ca:6f:85:fd:cb:90:1c:b2:4f:fe:29:de:b0:e7: + 73:e2:f7:5e:63:8c:7f:c1:7e:75:2e:c9:9d:e9:c2:45:75:f3: + 27:ba:82:94:de:7f:6c:87:0c:5c:71:af:0f:14:00:68:35:f7: + 5a:4a:ff:f5:ef:35:dd:50:72:76:f0:6f:b6:7b:42:33:07:b4: + 24:44:0a:fd:9d:61:9e:44:e8:88:0f:02:76:c6:90:3f:9d:1b: + d8:3b:64:25:2a:a3:39:78:38:bd:20:89:4a:9c:bd:68:38:18: + 4c:cb:20:3a:9b:5b:5f:58:52:86:73:de:85:fe:d6:a1:c6:a7: + 86:b0:96:4b:fa:28:04:ad:5d:85:e8:a1:fc:ca:0f:3c:be:5c: + 90:7e:3e:84:ae:67:ee:9a:72:71:3c:b2:80:45:82:fc:7e:58: + 74:99:42:c5:c3:8a:4a:eb:e1:8b:d5:84:ce:25:aa:a1:75:79: + 94:66:ae:ee:df:30:15:0b:b5:c5:b1:2c:d5:0a:54:78:b6:2e: + 67:29:81:41:f6:16:49:31:96:e7:41:e1:99:6b:27:57:bb:7d: + 76:eb:e4:d5:59:aa:a2:5c:bd:1c:18:2a:fa:9d:28:1a:0b:b6: + bf:7d:58:1a +-----BEGIN CERTIFICATE----- +MIID7jCCAtagAwIBAgIBEjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJjbjEQ +MA4GA1UECAwHYmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsM +C3lpbmdmZWktZGV2MRcwFQYDVQQDDA55aW5nZmVpLWRldi1jYTAeFw0yNDAxMzEw +MjU1MzJaFw0zNDAxMjgwMjU1MzJaMH0xCzAJBgNVBAYTAmNuMRAwDgYDVQQIDAdi +ZWlqaW5nMRQwEgYDVQQKDAt5aW5nZmVpLWRldjEUMBIGA1UECwwLeWluZ2ZlaS1k +ZXYxEDAOBgNVBAMMB2JmZS1kZXYxHjAcBgkqhkiG9w0BCQEWD2RldkBleGFtcGxl +Lm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ/wQzNSUPZmXgV +TFpSqae/1euScUPhN+UpG2j0b0zq+qSKfCkBKvp8gVvI66FAlLcrguIACDaE8LfS +Wh5Wl6o2/00HSS3+JTr56fGtTm4hl6n5oqWsgiMK0uCXzS8UsfCKqObFl0WU+I4/ +lmZbC4x8B2EYRJL3I1rCS4hYWV3KXA1u3f8YWWXflZnjOjZIHz865s6F8wsEXpLt +b450kuQ3RtpfF2KcgkAG+yn4VfK6I3XKZMBFAxK99RcVfkfVvTDymcpr4wewrkSJ +HhAm6nXfbwe2R3ZUR09s9mj+qM8iIHPoGVWK/vV46FGIUoAfedTFro/SK/ZBAUIB +z5jCySUCAwEAAaOBkTCBjjAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVu +U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU7uhoQt+x8O9vR1G9 +1JRgHwWFoQMwHwYDVR0jBBgwFoAUDZloT8VhVbysD819oG/oqHdW1D0wEwYDVR0l +BAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBAKmmJo5CYRUi7vy14eRr +3az1FRE5EJrKb4X9y5Acsk/+Kd6w53Pi915jjH/BfnUuyZ3pwkV18ye6gpTef2yH +DFxxrw8UAGg191pK//XvNd1Qcnbwb7Z7QjMHtCRECv2dYZ5E6IgPAnbGkD+dG9g7 +ZCUqozl4OL0giUqcvWg4GEzLIDqbW19YUoZz3oX+1qHGp4awlkv6KAStXYXoofzK +Dzy+XJB+PoSuZ+6acnE8soBFgvx+WHSZQsXDikrr4YvVhM4lqqF1eZRmru7fMBUL +tcWxLNUKVHi2LmcpgUH2FkkxludB4ZlrJ1e7fXbr5NVZqqJcvRwYKvqdKBoLtr99 +WBo= +-----END CERTIFICATE----- diff --git a/conf/tls_conf/backend_rs/r_bfe_dev_prv.pem b/conf/tls_conf/backend_rs/r_bfe_dev_prv.pem new file mode 100644 index 000000000..c576080b6 --- /dev/null +++ b/conf/tls_conf/backend_rs/r_bfe_dev_prv.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsn/BDM1JQ9mZeBVMWlKpp7/V65JxQ+E35SkbaPRvTOr6pIp8 +KQEq+nyBW8jroUCUtyuC4gAINoTwt9JaHlaXqjb/TQdJLf4lOvnp8a1ObiGXqfmi +payCIwrS4JfNLxSx8Iqo5sWXRZT4jj+WZlsLjHwHYRhEkvcjWsJLiFhZXcpcDW7d +/xhZZd+VmeM6NkgfPzrmzoXzCwReku1vjnSS5DdG2l8XYpyCQAb7KfhV8rojdcpk +wEUDEr31FxV+R9W9MPKZymvjB7CuRIkeECbqdd9vB7ZHdlRHT2z2aP6ozyIgc+gZ +VYr+9XjoUYhSgB951MWuj9Ir9kEBQgHPmMLJJQIDAQABAoIBAFAKHzupVb/58+o3 +yqv5wx94Uuk2GlnwxIqaezL94GaiO0/K1U/huS7m426P0rDU75qPBTpn/0bLJ9GV +nllaRNnLnYEh0juwaWtfovp+1ttlbseGK9uUVip2cQbKqvQAmKWe14vbcDCAU1Ad +zUgKbUxKVVjBdAZekVjiJNJ3o2L9WPhf/uQo7A1XAJh2DajlTbgvrDM73W+47QuU +X4OHU0FMio6bxupu3OWl1bMrnKhuC4qczZWf2nOpcVQa89rtopuP4ENLJuWkbeGk +YQpNilEclnAa/Noumt/j/6GKC1EEHFsH2CNRRIazcZrsFhkSKc4pn1Y/WI3vj8kZ ++RYnJsUCgYEA6gr0x8suwRTAmVpoPk4XyP1x+eInG7onV5RjgsUtgruYd9naxRHg +2p8PHcv32pDs51Fa+4RldyMd/jec1SscRF5/+VOP9qeoRnaDqUu+uASgEO3OBxbP +JcWovyxRHIQxbYCQtqIr9bdzXw55MBLZou/sBUVTAkrIyPVyjWqZlV8CgYEAwz7L +YyYN615TsrzZKURxMjj94Nmob/NldSLRXaR3Ax7/ABtEOA685cwQxq7ONdkJTMIA +uR8u2GHZSzGiWnehuF6Zp7Xs71a57eFbs3ueZvvEba4Dff7hl7Y4tTlwrKndKjvP +J/5a2Ol8siQcRWAXHOdzggEHMSZ/sB4hWswly/sCgYEAhLRBpyemEwTZUBrbELjm +86gBgFajJi2fMSGKaxOygnYsNYjpauSAQnX99D87Aks6iM6wb/zaK3tV/lc6LgSL +uph6p7yh3JGj8JAyh0PTmDPHLtIoCAz+18QDsqJGO40ZGaXUaDn8Aw9J85QZUxDd +Jm4zvalZL+uHfarukRDolLECgYBUiupS4nWAh3XCnZeDEQna72avaFBROZmjIRJ7 +c+28wj009JmTlH4jGzvgbG0KUBKA1Div8Fq+g5AtyS498jNqvDvYrSQNdwZHhR/K +Fis++KHTxFfqxOU2Zkcj4d1yRpNn6EIJVVBNQL0n/g7n03XupCIWFw/gLoV343QZ +9vAe5QKBgA8ml59z1w3eUooc0yGfLhihXqCmM3IU006bFbODA30fBU4QKrHO8+Yx +Xbz9bi/1QLagLG6FzYQAkOjBlEBt5XLayYvwSb8xvWsm5A3vzTAbMFDOsDPEmRoH +dWtQccJcygOuK+PZtoZnNoJciNO5c9dZWD3xtIiURmX/kVtWrf6N +-----END RSA PRIVATE KEY----- diff --git a/conf/tls_conf/backend_rs/r_san_example.org.crt b/conf/tls_conf/backend_rs/r_san_example.org.crt new file mode 100644 index 000000000..cb1ba8d94 --- /dev/null +++ b/conf/tls_conf/backend_rs/r_san_example.org.crt @@ -0,0 +1,85 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 15 (0xf) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=cn, ST=beijing, O=yingfei-dev, OU=yingfei-dev, CN=yingfei-dev-ca + Validity + Not Before: Dec 7 09:57:42 2023 GMT + Not After : Dec 4 09:57:42 2033 GMT + Subject: C=cn, ST=beijing, O=yingfei-dev, OU=yingfei, CN=dev-test + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:d6:47:b0:17:69:91:4c:d0:66:c9:25:2e:38:f2: + 84:27:e2:7c:38:cc:04:b9:0c:8e:3d:cc:ef:4b:c5: + 35:2b:c1:82:d8:41:fc:25:c8:24:f2:e0:25:aa:f9: + 76:3c:e2:b2:50:fb:2d:ec:4e:16:d4:c1:da:1e:e3: + 51:9f:9d:c9:72:f9:cb:cc:14:9c:c1:82:c9:76:1f: + 98:dd:0e:b9:8a:20:d6:ab:f2:a6:f9:2f:23:81:f3: + af:e4:47:0c:55:95:94:de:ed:f6:a5:24:ee:32:e7: + 6b:79:e1:42:6f:a4:07:b2:95:1d:f5:a9:8e:60:42: + 70:42:bd:e2:30:18:68:74:52:32:98:a9:81:da:d8: + c6:6f:5e:1d:ce:79:b6:f3:ec:4f:ed:7d:22:57:d2: + 14:d0:fb:f2:50:d4:80:3b:89:ed:77:fd:45:6c:e6: + 52:6b:0a:52:71:ac:59:c8:d5:25:f5:40:03:fb:51: + b0:11:a7:00:79:d9:d8:4f:00:43:96:68:44:29:41: + dc:d2:cc:91:c8:61:95:41:4d:0e:66:5a:b5:15:67: + 3e:8a:6f:29:df:1c:8a:6f:ee:9e:97:9c:9e:69:71: + d3:34:52:75:e9:ea:e7:51:77:23:98:46:ca:47:a2: + d3:d3:97:03:41:4b:e3:33:11:72:2d:af:bf:2b:3e: + b3:51 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 5A:27:32:9D:E7:36:24:A3:C1:DC:2F:95:80:C5:CF:0C:85:E8:E6:AF + X509v3 Authority Key Identifier: + keyid:0D:99:68:4F:C5:61:55:BC:AC:0F:CD:7D:A0:6F:E8:A8:77:56:D4:3D + + X509v3 Subject Alternative Name: + DNS:example.org, DNS:www.example.org, DNS:example.com, DNS:*.example.com, IP Address:127.0.0.1, IP Address:192.168.0.100 + Signature Algorithm: sha256WithRSAEncryption + 1e:e8:e8:8a:ad:a8:0e:fc:c9:82:00:a1:ab:30:3c:a5:b9:dc: + d6:fb:86:ad:30:52:7f:61:be:90:a6:b8:56:bb:f1:0b:e6:39: + 38:65:09:6b:da:83:f7:65:ff:c4:21:de:b4:9e:8b:bd:1e:1c: + d1:d5:94:b8:18:79:f2:d0:06:51:39:67:13:40:3b:73:5b:cb: + ea:de:c1:19:76:f8:7b:0f:15:51:61:49:fb:98:f7:ea:4f:fc: + c2:fb:a7:f4:3c:48:64:14:79:b5:78:5b:20:10:b5:7a:2d:4c: + 04:51:60:ec:20:10:19:26:5f:e2:fd:32:59:67:e9:3f:48:8d: + f5:52:12:01:81:2c:c0:e5:72:cd:7d:0a:eb:7a:05:df:a0:77: + b9:ba:9a:7d:d1:4b:6a:44:e4:2d:98:af:bd:77:2b:f5:ef:26: + 4b:75:b3:97:d0:3a:bc:07:21:ef:71:92:30:fe:a2:79:e5:56: + d7:7e:c2:f3:57:ab:d7:de:fc:97:ed:20:0c:9a:cb:c5:5d:00: + 3b:61:29:e8:00:d4:39:e0:f2:4e:a4:03:c2:12:52:ff:e7:78: + f9:f7:c0:12:dc:36:a4:05:a2:f0:6b:47:e2:21:3d:a2:e1:a1: + 91:c7:ac:8f:b8:ae:58:65:e0:2b:57:80:eb:77:2d:48:ef:e6: + fb:b9:e1:20 +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgIBDzANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJjbjEQ +MA4GA1UECAwHYmVpamluZzEUMBIGA1UECgwLeWluZ2ZlaS1kZXYxFDASBgNVBAsM +C3lpbmdmZWktZGV2MRcwFQYDVQQDDA55aW5nZmVpLWRldi1jYTAeFw0yMzEyMDcw +OTU3NDJaFw0zMzEyMDQwOTU3NDJaMFoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIDAdi +ZWlqaW5nMRQwEgYDVQQKDAt5aW5nZmVpLWRldjEQMA4GA1UECwwHeWluZ2ZlaTER +MA8GA1UEAwwIZGV2LXRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDWR7AXaZFM0GbJJS448oQn4nw4zAS5DI49zO9LxTUrwYLYQfwlyCTy4CWq+XY8 +4rJQ+y3sThbUwdoe41Gfncly+cvMFJzBgsl2H5jdDrmKINar8qb5LyOB86/kRwxV +lZTe7falJO4y52t54UJvpAeylR31qY5gQnBCveIwGGh0UjKYqYHa2MZvXh3Oebbz +7E/tfSJX0hTQ+/JQ1IA7ie13/UVs5lJrClJxrFnI1SX1QAP7UbARpwB52dhPAEOW +aEQpQdzSzJHIYZVBTQ5mWrUVZz6KbynfHIpv7p6XnJ5pcdM0UnXp6udRdyOYRspH +otPTlwNBS+MzEXItr78rPrNRAgMBAAGjgc0wgcowCQYDVR0TBAIwADAsBglghkgB +hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE +FFonMp3nNiSjwdwvlYDFzwyF6OavMB8GA1UdIwQYMBaAFA2ZaE/FYVW8rA/NfaBv +6Kh3VtQ9ME8GA1UdEQRIMEaCC2V4YW1wbGUub3Jngg93d3cuZXhhbXBsZS5vcmeC +C2V4YW1wbGUuY29tgg0qLmV4YW1wbGUuY29thwR/AAABhwTAqABkMA0GCSqGSIb3 +DQEBCwUAA4IBAQAe6OiKragO/MmCAKGrMDyludzW+4atMFJ/Yb6QprhWu/EL5jk4 +ZQlr2oP3Zf/EId60nou9HhzR1ZS4GHny0AZROWcTQDtzW8vq3sEZdvh7DxVRYUn7 +mPfqT/zC+6f0PEhkFHm1eFsgELV6LUwEUWDsIBAZJl/i/TJZZ+k/SI31UhIBgSzA +5XLNfQrregXfoHe5upp90UtqROQtmK+9dyv17yZLdbOX0Dq8ByHvcZIw/qJ55VbX +fsLzV6vX3vyX7SAMmsvFXQA7YSnoANQ54PJOpAPCElL/53j598AS3DakBaLwa0fi +IT2i4aGRx6yPuK5YZeArV4Drdy1I7+b7ueEg +-----END CERTIFICATE----- diff --git a/conf/tls_conf/backend_rs/r_san_example.org_prv.pem b/conf/tls_conf/backend_rs/r_san_example.org_prv.pem new file mode 100644 index 000000000..b19d3d4c8 --- /dev/null +++ b/conf/tls_conf/backend_rs/r_san_example.org_prv.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA1kewF2mRTNBmySUuOPKEJ+J8OMwEuQyOPczvS8U1K8GC2EH8 +Jcgk8uAlqvl2POKyUPst7E4W1MHaHuNRn53JcvnLzBScwYLJdh+Y3Q65iiDWq/Km ++S8jgfOv5EcMVZWU3u32pSTuMudreeFCb6QHspUd9amOYEJwQr3iMBhodFIymKmB +2tjGb14dznm28+xP7X0iV9IU0PvyUNSAO4ntd/1FbOZSawpScaxZyNUl9UAD+1Gw +EacAednYTwBDlmhEKUHc0syRyGGVQU0OZlq1FWc+im8p3xyKb+6el5yeaXHTNFJ1 +6ernUXcjmEbKR6LT05cDQUvjMxFyLa+/Kz6zUQIDAQABAoIBAC4sYGuLGf49Ygix +9FXdHFEj4rSyccoWRIhYoq/nHOAC4NkMzvKtQBj95+ABxVK1XstIdMrYwN6zrva8 +8Re9/mzCGwIs5uJj9ll30Y7A34Y+MUP4E7baS4JzKlG8ZZIDm4K2MFHBtXpOl8A5 +pAE+jVIUA9Kt6LohVuNq21SVzdxSfNYC/+SLqSftkWa/ZsqdkiHM5Hl+fVedh516 +IaLNW5hSthGh5n8dHY5h/AKPjfoq77aYp5/CUtJTC9mYdZu1j/W/pBVTRfOnwLQd +SQ1Xmr7f6q9Vmz+HnajIbFg9hQ54blvtUJ7DnugWxfUcoxf7ue79fnYjOIUOkRWw +8Iid/mECgYEA+5s0p0j+gkNZN5QtVNStoT04+1DqA1O381gczeaiz6Njt/MT0y5W +OpCsILQ70CpEjWAV+f6PDJiesDMxdGV+v2TCqK8ml8GahEczBLnHoAkPWCVf2XOX +oNj/CkZ2kmWufHFR+kcQbeDt1vFFcYUa61hKDFyNjinMW79Qy+PQID0CgYEA2gWd +7thE05sqU7/1MntmVRONKoAgnJmHcfSpWwLyh6E4YX3iKDSgI/9RnAMF39KUUY/O +XFWyIwAM9soeXknVsV/SmCaPeaEDiLHqz98aUEfvdLYMnuR883GgoXc4JrLsLw4z +oSi9lbAZFn0ekJ5L+rSFrY4rz9QZgYZxsLjUXKUCgYAbkaUSU2g3w8Np2J2i9u7T +hQ7SUsphdPHqAxSc5xGd6MxLYqIgeKpQHnwN1VHcfFUonIer7d2kxrBUpDdeBqT9 +ub+ulgqHhFo29ko70VNzUKrSwL2g6Q6LPFutt4zUe7nDvvL5loHRWF0XOTafurL5 +aKIsepO0KRZQU0U6IgszDQKBgFs6ZHaP2mTtFY4L0a75Ab3xu20gRgUhHRLq/H6P +wipMpMnuodaPBr9pU53Dig65D8T9Nq1eUnbgy4vs0T5FCPz6iqWN5RVQ8aieQhIP +WfRj1Wfx0WAfXcWEM2G9ACr5TWj3OVVjNclP8X9+hW6gPky+gv03c0+4gZ+4QRRg +ksPdAoGBAKXVv8L5Qt8rmi+QpF/6DVSeMB6lEevf319UEtyZPiYSBn1JJ9H+3Ddc +TMriXgZrw+PoXNJTAguDeGgzmtye1vraP0cjt7aG6sQ4YtMTnnuja880vK8z4i3Q +HJAi5fI8NvlW92dQBAccgrzFIKpRx+6CHwtCkNrxL2vJmGGzp8BR +-----END RSA PRIVATE KEY-----