tidb security 源码
tidb security 代码
文件路径:/util/security.go
// Copyright 2022 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// 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 util
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"net"
"net/http"
"os"
"strings"
"sync"
"github.com/pingcap/errors"
)
// TLS saves some information about tls
type TLS struct {
inner *tls.Config
client *http.Client
url string
}
// ToTLSConfig generates tls's config.
func ToTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) {
return ToTLSConfigWithVerify(caPath, certPath, keyPath, nil)
}
func addVerifyPeerCertificate(tlsCfg *tls.Config, verifyCN []string) {
if len(verifyCN) != 0 {
checkCN := make(map[string]struct{})
for _, cn := range verifyCN {
cn = strings.TrimSpace(cn)
checkCN[cn] = struct{}{}
}
tlsCfg.ClientAuth = tls.RequireAndVerifyClientCert
tlsCfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
cns := make([]string, 0, len(verifiedChains))
for _, chains := range verifiedChains {
for _, chain := range chains {
cns = append(cns, chain.Subject.CommonName)
if _, match := checkCN[chain.Subject.CommonName]; match {
return nil
}
}
}
return errors.Errorf("client certificate authentication failed. The Common Name from the client certificate %v was not found in the configuration cluster-verify-cn with value: %s", cns, verifyCN)
}
}
}
// ToTLSConfigWithVerify constructs a `*tls.Config` from the CA, certification and key
// paths, and add verify for CN.
//
// If the CA path is empty, returns nil.
func ToTLSConfigWithVerify(caPath, certPath, keyPath string, verifyCN []string) (*tls.Config, error) {
if len(caPath) == 0 {
return nil, nil
}
// Load the client certificates from disk
var certificates []tls.Certificate
if len(certPath) != 0 && len(keyPath) != 0 {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, errors.Annotate(err, "could not load client key pair")
}
certificates = []tls.Certificate{cert}
}
// Create a certificate pool from CA
certPool := x509.NewCertPool()
//nolint: gosec
ca, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, errors.Annotate(err, "could not read ca certificate")
}
// Append the certificates from the CA
if !certPool.AppendCertsFromPEM(ca) {
return nil, errors.New("failed to append ca certs")
}
/* #nosec G402 */
tlsCfg := &tls.Config{
MinVersion: tls.VersionTLS10,
Certificates: certificates,
RootCAs: certPool,
ClientCAs: certPool,
NextProtos: []string{"h2", "http/1.1"}, // specify `h2` to let Go use HTTP/2.
}
addVerifyPeerCertificate(tlsCfg, verifyCN)
return tlsCfg, nil
}
type tlsConfigBuilder struct {
caPath, certPath, keyPath string
caContent, certContent, keyContent []byte
verifyCN []string
}
// TLSConfigOption is used to build a tls.Config in NewTLSConfig.
type TLSConfigOption func(*tlsConfigBuilder)
// WithCAPath sets the CA path to build a tls.Config, and the peer should use the certificate which can be verified by
// this CA. It has higher priority than WithCAContent.
// empty `caPath` is no-op.
func WithCAPath(caPath string) TLSConfigOption {
return func(builder *tlsConfigBuilder) {
builder.caPath = caPath
}
}
// WithCertAndKeyPath sets the client certificate and primary key path to build a tls.Config. It has higher priority
// than WithCertAndKeyContent.
// empty `certPath`/`keyPath` is no-op.
// WithCertAndKeyPath also support rotation, which means if the client certificate or primary key file is changed, the
// new content will be used.
func WithCertAndKeyPath(certPath, keyPath string) TLSConfigOption {
return func(builder *tlsConfigBuilder) {
builder.certPath = certPath
builder.keyPath = keyPath
}
}
// WithVerifyCommonName sets the Common Name the peer must provide before starting a TLS connection.
// empty `verifyCN` is no-op.
func WithVerifyCommonName(verifyCN []string) TLSConfigOption {
return func(builder *tlsConfigBuilder) {
builder.verifyCN = verifyCN
}
}
// WithCAContent sets the CA content to build a tls.Config, and the peer should use the certificate which can be
// verified by this CA. It has lower priority than WithCAPath.
// empty `caContent` is no-op.
func WithCAContent(caContent []byte) TLSConfigOption {
return func(builder *tlsConfigBuilder) {
builder.caContent = caContent
}
}
// WithCertAndKeyContent sets the client certificate and primary key content to build a tls.Config. It has lower
// priority than WithCertAndKeyPath.
// empty `certContent`/`keyContent` is no-op.
func WithCertAndKeyContent(certContent, keyContent []byte) TLSConfigOption {
return func(builder *tlsConfigBuilder) {
builder.certContent = certContent
builder.keyContent = keyContent
}
}
// NewTLSConfig creates a tls.Config from the given options. If no certificate is provided, it will return (nil, nil).
func NewTLSConfig(opts ...TLSConfigOption) (*tls.Config, error) {
builder := &tlsConfigBuilder{}
for _, opt := range opts {
opt(builder)
}
if builder.caPath == "" && len(builder.caContent) == 0 &&
builder.certPath == "" && len(builder.certContent) == 0 &&
builder.keyPath == "" && len(builder.keyContent) == 0 {
return nil, nil
}
var (
certPool *x509.CertPool
certPoolMu sync.RWMutex
verifyFuncs []func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
)
/* #nosec G402 */
tlsCfg := &tls.Config{
MinVersion: tls.VersionTLS10,
InsecureSkipVerify: true,
NextProtos: []string{"h2", "http/1.2"}, // specify `h2` to let Go use HTTP/2.
}
// 1. handle client certificates
if builder.certPath != "" && builder.keyPath != "" {
// clear the content if path is provided
builder.certContent = nil
builder.keyContent = nil
loadCert := func() (*tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(builder.certPath, builder.keyPath)
if err != nil {
return nil, errors.Annotate(err, "could not load client key pair")
}
return &cert, nil
}
tlsCfg.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return loadCert()
}
tlsCfg.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return loadCert()
}
}
if len(builder.certContent) > 0 && len(builder.keyContent) > 0 {
cert, err := tls.X509KeyPair(builder.certContent, builder.keyContent)
if err != nil {
return nil, errors.Annotate(err, "could not load client key pair")
}
tlsCfg.Certificates = []tls.Certificate{cert}
}
// 2. handle CA
// need this closure to access certPool
// thanks to https://cloud.google.com/sql/docs/mysql/samples/cloud-sql-mysql-databasesql-sslcerts
verifyCA := func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
return errors.New("no certificates available to verify")
}
cert, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return err
}
certPoolMu.RLock()
defer certPoolMu.RUnlock()
if _, err = cert.Verify(x509.VerifyOptions{Roots: certPool}); err != nil {
return errors.Wrap(err, "can't verify certificate, maybe different CA is used")
}
return nil
}
var (
caContent []byte
err error
)
if builder.caPath != "" {
caContent, err = os.ReadFile(builder.caPath)
if err != nil {
return nil, errors.Annotate(err, "could not read ca certificate")
}
} else {
caContent = builder.caContent
}
if len(caContent) > 0 {
certPool = x509.NewCertPool()
if !certPool.AppendCertsFromPEM(caContent) {
return nil, errors.New("failed to append ca certs")
}
tlsCfg.RootCAs = certPool
tlsCfg.ClientCAs = certPool
verifyFuncs = append(verifyFuncs, verifyCA)
}
// 3. handle verify Common Name
if len(builder.verifyCN) > 0 {
tlsCfg.ClientAuth = tls.RequireAndVerifyClientCert
verifyFuncs = append(verifyFuncs, verifyCommonName(builder.verifyCN))
}
tlsCfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
for _, f := range verifyFuncs {
if err := f(rawCerts, verifiedChains); err != nil {
return err
}
}
return nil
}
return tlsCfg, nil
}
func verifyCommonName(verifyCN []string) func([][]byte, [][]*x509.Certificate) error {
checkCN := make(map[string]struct{})
for _, cn := range verifyCN {
cn = strings.TrimSpace(cn)
checkCN[cn] = struct{}{}
}
return func(_ [][]byte, verifiedChains [][]*x509.Certificate) error {
cns := make([]string, 0, len(verifiedChains))
for _, chains := range verifiedChains {
for _, chain := range chains {
cns = append(cns, chain.Subject.CommonName)
if _, match := checkCN[chain.Subject.CommonName]; match {
return nil
}
}
}
return errors.Errorf("client certificate authentication failed. The Common Name from the client certificate %v was not found in the configuration cluster-verify-cn with value: %s", cns, verifyCN)
}
}
// NewTLS constructs a new HTTP client with TLS configured with the CA,
// certificate and key paths.
//
// If the CA path is empty, returns an instance where TLS is disabled.
func NewTLS(caPath, certPath, keyPath, host string, verifyCN []string) (*TLS, error) {
if len(caPath) == 0 {
return &TLS{
inner: nil,
client: &http.Client{},
url: "http://" + host,
}, nil
}
inner, err := ToTLSConfigWithVerify(caPath, certPath, keyPath, verifyCN)
if err != nil {
return nil, err
}
client := ClientWithTLS(inner)
return &TLS{
inner: inner,
client: client,
url: "https://" + host,
}, nil
}
// ClientWithTLS creates a http client wit tls
func ClientWithTLS(tlsCfg *tls.Config) *http.Client {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.TLSClientConfig = tlsCfg
return &http.Client{Transport: transport}
}
// WrapListener places a TLS layer on top of the existing listener.
func (tc *TLS) WrapListener(l net.Listener) net.Listener {
if tc.inner == nil {
return l
}
return tls.NewListener(l, tc.inner)
}
相关信息
相关文章
0
赞
热门推荐
-
2、 - 优质文章
-
3、 gate.io
-
8、 golang
-
9、 openharmony
-
10、 Vue中input框自动聚焦