前面我們的證書生成的篇章中,只是提了下tls相關(guān)的內(nèi)容,但他們是怎么用的并沒有涉及,這一篇我們來探討下Fabric中tls是怎么運(yùn)轉(zhuǎn)的。
首先我們來看下SecureOptions,這里包含了不管是server還是client所有涉及的配置項(xiàng),搞懂這些選項(xiàng)的來龍去脈,基本上你也就搞懂了tls。
SecureOptions
type SecureOptions struct {
// VerifyCertificate, if not nil, is called after normal
// certificate verification by either a TLS client or server.
// If it returns a non-nil error, the handshake is aborted and that error results.
VerifyCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
// PEM-encoded X509 public key to be used for TLS communication
Certificate []byte
// PEM-encoded private key to be used for TLS communication
Key []byte
// Set of PEM-encoded X509 certificate authorities used by clients to
// verify server certificates
ServerRootCAs [][]byte
// Set of PEM-encoded X509 certificate authorities used by servers to
// verify client certificates
ClientRootCAs [][]byte
// Whether or not to use TLS for communication
UseTLS bool
// Whether or not TLS client must present certificates for authentication
RequireClientCert bool
// CipherSuites is a list of supported cipher suites for TLS
CipherSuites []uint16
}
- VerifyCertificate -- 回調(diào)方法,用來在tls握手期間,正常的證書校驗(yàn)完畢后,回調(diào)這部分自定義校驗(yàn)邏輯。
- Certificate -- 這里是存放tls所要使用的證書,不論是server還是client
- Key -- 私鑰
- ServerRootCAs -- 客戶端用來校驗(yàn)服務(wù)端證書
- ClientRootCAs -- 服務(wù)端用來校驗(yàn)客戶端證書
- UseTLS -- 是否開啟tls
- RequireClientCert -- 是否需要客戶端證書
- CipherSuites -- 加密套件
VerifyCertificate
v := c.verifyHandshake(endpoint, expectedServerCert)
conn, err := c.dialer.Dial(endpoint, v)
func (c *ConnectionStore) verifyHandshake(endpoint string, certificate []byte) RemoteVerifier {
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if bytes.Equal(certificate, rawCerts[0]) {
return nil
}
return errors.Errorf("certificate presented by %s doesn't match any authorized certificate", endpoint)
}
}
- 可以看到,這里的意思是,連接的過程中,連接方希望被連接方的證書跟我所期待的完全一致。可以說這是非常嚴(yán)格的證書校驗(yàn)邏輯。整個(gè)fabric有用到這個(gè)的地方就是etcd的部分,因?yàn)閑tcd的網(wǎng)絡(luò)層是自己實(shí)現(xiàn)的,在一般流程外,還需要嚴(yán)格的證書校驗(yàn)邏輯。普通的tls只是判斷是否是ca簽發(fā),因?yàn)閑tcd節(jié)點(diǎn)保存了所有成員的服務(wù)端證書,它需要證書要嚴(yán)格一致才表示握手通過。
- 這部分不再展開,可以參考etcdraft篇。
Certificate
handshake的過程需要發(fā)給對方證書,讓對方拿去做校驗(yàn)的。而這里就是放證書的地方。只要發(fā)起tls連接,這個(gè)證書就會(huì)發(fā)送。
- peer.tls.clientCert.file(CORE_PEER_TLS_CLIENTCERT_FILE),orderer.tls.clientCert.file負(fù)責(zé)客戶端證書配置
- CORE_PEER_TLS_CERT_FILE, ORDERER_GENERAL_TLS_CERTIFICATE負(fù)責(zé)服務(wù)端證書配置
Key
在tls中,雙方需要進(jìn)行密鑰協(xié)商,協(xié)商的結(jié)果是雙方發(fā)送的內(nèi)容會(huì)用這個(gè)密鑰進(jìn)行加密解密,一般說來這個(gè)密鑰是對稱加密。當(dāng)然了,你也可以用不對稱,但是用在tls的場景效率太低了,一般不這么用。
怎么協(xié)商呢?客戶端在發(fā)起連接時(shí),會(huì)隨機(jī)一個(gè)預(yù)備密鑰,用服務(wù)端的公鑰進(jìn)行加密,發(fā)給對方,而服務(wù)端用自己的私鑰進(jìn)行解密,進(jìn)而協(xié)商出一個(gè)主密鑰。而這里的key就是做協(xié)商加密用的。
- peer.tls.clientKey.file(CORE_PEER_TLS_CLIENTKEY_FILE),orderer.tls.clientKey.file負(fù)責(zé)客戶端私鑰配置
- CORE_PEER_TLS_KEY_FILE,ORDERER_GENERAL_TLS_PRIVATEKEY負(fù)責(zé)服務(wù)端私鑰
ServerRootCAs
這是客戶端用來校驗(yàn)服務(wù)端證書的。這里的校驗(yàn)是指收到的服務(wù)端正式是否是該證書簽發(fā)的。我舉兩個(gè)場景的例子,你就明白了。
ordererclient
什么叫ordererclient,其實(shí)就是peer,peer會(huì)去broadcast推送給orderer事件,也會(huì)deliver拉取block。按照上面的邏輯,這個(gè)client創(chuàng)建的時(shí)候會(huì)將orderer的ca證書作為tls的ServerRootCAs。
if secOpts.UseTLS {
caPEM, res := ioutil.ReadFile(config.GetPath(prefix + ".tls.rootcert.file"))
if res != nil {
err = errors.WithMessage(res,
fmt.Sprintf("unable to load %s.tls.rootcert.file", prefix))
return
}
secOpts.ServerRootCAs = [][]byte{caPEM}
}
這里prefix是orderer,相當(dāng)于去獲取了orderer.tls.rootcert.file,也可以當(dāng)成是ORDERER_GENERAL_TLS_ROOTCAS。注意這兩個(gè)配置理論上并沒有什么關(guān)系。在first-network中orderer.tls.rootcert.file指定的是tlscacerts目錄,而ORDERER_GENERAL_TLS_ROOTCAS是指定的tls目錄的ca.crt, 有可能是向下兼容的關(guān)系,比較混亂,但是指定的證書都是同一個(gè),只是名字不同,最好兩個(gè)還是指保持一致。
peerclient
我們知道peer相互會(huì)互聯(lián),其中最知名的就是gossip協(xié)議,我們看下peer作為client的時(shí)候
if secOpts.UseTLS {
caPEM, res := ioutil.ReadFile(config.GetPath(prefix + ".tls.rootcert.file"))
if res != nil {
err = errors.WithMessage(res,
fmt.Sprintf("unable to load %s.tls.rootcert.file", prefix))
return
}
secOpts.ServerRootCAs = [][]byte{caPEM}
}
同樣的代碼,只不過是prefix不同,這里是peer,相當(dāng)于去獲取了peer.tls.rootcert.file,也就是CORE_PEER_TLS_ROOTCERT_FILE
ClientRootCAs
在tls的世界里,一般來說服務(wù)端發(fā)來證書,客戶端校驗(yàn)就完了。有時(shí),服務(wù)端也可以要求客戶端把證書發(fā)來,進(jìn)行雙向校驗(yàn)。跟上面同理,客戶端連接時(shí),也需要明確的告訴服務(wù)端哪些客戶端證書是有效的,被承認(rèn)的。
orderer
if secureOpts.RequireClientCert {
for _, clientRoot := range conf.General.TLS.ClientRootCAs {
root, err := ioutil.ReadFile(clientRoot)
if err != nil {
logger.Fatalf("Failed to load ClientRootCAs file '%s' (%s)",
err, clientRoot)
}
clientRootCAs = append(clientRootCAs, root)
}
msg = "mutual TLS"
}
這里的意思是,如果需要對客戶端證書進(jìn)行校驗(yàn)的話,從conf.General.TLS.ClientRootCAs加載客戶端的ca證書。也就是ORDERER_GENERAL_TLS_CLIENTROOTCAS配置。
peer
if secureOptions.RequireClientCert {
var clientRoots [][]byte
for _, file := range viper.GetStringSlice("peer.tls.clientRootCAs.files") {
clientRoot, err := ioutil.ReadFile(
config.TranslatePath(filepath.Dir(viper.ConfigFileUsed()), file))
if err != nil {
return serverConfig,
fmt.Errorf("error loading client root CAs (%s)", err)
}
clientRoots = append(clientRoots, clientRoot)
}
secureOptions.ClientRootCAs = clientRoots
}
這里跟上面類似,如果需要對客戶端證書進(jìn)行校驗(yàn)的話,從peer.tls.clientRootCAs.files加載客戶端的ca證書。也就是CORE_PEER_TLS_CLIENTROOTCAS_FILES配置。
UseTLS
CORE_PEER_TLS_ENABLED,ORDERER_GENERAL_TLS_ENABLED分別開啟peer和orderer的tls
RequireClientCert
CORE_PEER_TLS_CLIENTAUTHREQUIRED,ORDERER_GENERAL_TLS_CLIENTAUTHREQUIRED分別指示peer和orderer需要客戶端證書進(jìn)行校驗(yàn)
CipherSuites
什么叫加密套件,在tls中協(xié)商的過程中是需要有前提的,雙方都支持的加密算法。就好比買東西,客戶端提出自己的需求,然后服務(wù)端按照對方的情況對他進(jìn)行量身打造,雙方共同協(xié)商一致。
DefaultTLSCipherSuites = []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
}
1554817013176.png
這個(gè)圖只是解釋下這一串的意義,沒有深入的意思。大家就當(dāng)聽個(gè)響。
Add Org
想象一個(gè)場景,動(dòng)態(tài)加入Org的場景,現(xiàn)在orderer已經(jīng)更新最新的配置bundle。假如orderer的tls開啟了客戶端證書校驗(yàn),會(huì)有什么問題?
orderer現(xiàn)在還沒有將新Org的tlsca證書加到ClientRootCAs里面,所有所有新Org的節(jié)點(diǎn)發(fā)來的請求服務(wù)端根本就不認(rèn)識(shí)。
沒關(guān)系,下面我們一起來看下,至少得出一個(gè)結(jié)論,服務(wù)端會(huì)觸發(fā)的時(shí)機(jī)就是更新bundle。
func (bs *BundleSource) Update(newBundle *Bundle) {
bs.bundle.Store(newBundle)
for _, callback := range bs.callbacks {
callback(newBundle)
}
}
可以看到更新bundle沒那么簡單,有一堆callback等著去處理
callback
tlsCallback := func(bundle *channelconfig.Bundle) {
// only need to do this if mutual TLS is required or if the orderer node is part of a cluster
if grpcServer.MutualTLSRequired() || clusterType {
logger.Debug("Executing callback to update root CAs")
updateTrustedRoots(caSupport, bundle, servers...)
if clusterType {
updateClusterDialer(caSupport, clusterDialer, clusterClientConfig.SecOpts.ServerRootCAs)
}
}
}
在orderer啟動(dòng)的時(shí)候,會(huì)設(shè)置相關(guān)的callback,也就是tlscallback
func updateTrustedRoots(rootCASupport *comm.CASupport, cm channelconfig.Resources, servers ...*comm.GRPCServer) {
rootCASupport.Lock()
defer rootCASupport.Unlock()
appRootCAs := [][]byte{}
ordererRootCAs := [][]byte{}
appOrgMSPs := make(map[string]struct{})
ordOrgMSPs := make(map[string]struct{})
if ac, ok := cm.ApplicationConfig(); ok {
// loop through app orgs and build map of MSPIDs
for _, appOrg := range ac.Organizations() {
appOrgMSPs[appOrg.MSPID()] = struct{}{}
}
}
if ac, ok := cm.OrdererConfig(); ok {
// loop through orderer orgs and build map of MSPIDs
for _, ordOrg := range ac.Organizations() {
ordOrgMSPs[ordOrg.MSPID()] = struct{}{}
}
}
if cc, ok := cm.ConsortiumsConfig(); ok {
for _, consortium := range cc.Consortiums() {
// loop through consortium orgs and build map of MSPIDs
for _, consortiumOrg := range consortium.Organizations() {
appOrgMSPs[consortiumOrg.MSPID()] = struct{}{}
}
}
}
cid := cm.ConfigtxValidator().ChainID()
logger.Debugf("updating root CAs for channel [%s]", cid)
msps, err := cm.MSPManager().GetMSPs()
if err != nil {
logger.Errorf("Error getting root CAs for channel %s (%s)", cid, err)
return
}
for k, v := range msps {
// check to see if this is a FABRIC MSP
if v.GetType() == msp.FABRIC {
for _, root := range v.GetTLSRootCerts() {
// check to see of this is an app org MSP
if _, ok := appOrgMSPs[k]; ok {
logger.Debugf("adding app root CAs for MSP [%s]", k)
appRootCAs = append(appRootCAs, root)
}
// check to see of this is an orderer org MSP
if _, ok := ordOrgMSPs[k]; ok {
logger.Debugf("adding orderer root CAs for MSP [%s]", k)
ordererRootCAs = append(ordererRootCAs, root)
}
}
for _, intermediate := range v.GetTLSIntermediateCerts() {
// check to see of this is an app org MSP
if _, ok := appOrgMSPs[k]; ok {
logger.Debugf("adding app root CAs for MSP [%s]", k)
appRootCAs = append(appRootCAs, intermediate)
}
// check to see of this is an orderer org MSP
if _, ok := ordOrgMSPs[k]; ok {
logger.Debugf("adding orderer root CAs for MSP [%s]", k)
ordererRootCAs = append(ordererRootCAs, intermediate)
}
}
}
}
rootCASupport.AppRootCAsByChain[cid] = appRootCAs
rootCASupport.OrdererRootCAsByChain[cid] = ordererRootCAs
// now iterate over all roots for all app and orderer chains
trustedRoots := [][]byte{}
for _, roots := range rootCASupport.AppRootCAsByChain {
trustedRoots = append(trustedRoots, roots...)
}
for _, roots := range rootCASupport.OrdererRootCAsByChain {
trustedRoots = append(trustedRoots, roots...)
}
// also need to append statically configured root certs
if len(rootCASupport.ClientRootCAs) > 0 {
trustedRoots = append(trustedRoots, rootCASupport.ClientRootCAs...)
}
// now update the client roots for the gRPC server
for _, srv := range servers {
err = srv.SetClientRootCAs(trustedRoots)
if err != nil {
msg := "Failed to update trusted roots for orderer from latest config " +
"block. This orderer may not be able to communicate " +
"with members of channel %s (%s)"
logger.Warningf(msg, cm.ConfigtxValidator().ChainID(), err)
}
}
}
- 前面的部分就是收集最新配置中的orderer和application下所有org的tlsca證書和中間證書
- 最重要的時(shí)候的調(diào)用grpcserver的SetClientRootCAs
SetClientRootCAs
func (gServer *GRPCServer) SetClientRootCAs(clientRoots [][]byte) error {
gServer.lock.Lock()
defer gServer.lock.Unlock()
errMsg := "Failed to set client root certificate(s): %s"
//create a new map and CertPool
clientRootCAs := make(map[string]*x509.Certificate)
for _, clientRoot := range clientRoots {
certs, subjects, err := pemToX509Certs(clientRoot)
if err != nil {
return fmt.Errorf(errMsg, err.Error())
}
if len(certs) >= 1 {
for i, cert := range certs {
//add it to our clientRootCAs map using subject as key
clientRootCAs[subjects[i]] = cert
}
}
}
//create a new CertPool and populate with the new clientRootCAs
certPool := x509.NewCertPool()
for _, clientRoot := range clientRootCAs {
certPool.AddCert(clientRoot)
}
//replace the internal map
gServer.clientRootCAs = clientRootCAs
//replace the current ClientCAs pool
gServer.tlsConfig.ClientCAs = certPool
return nil
}
好了,終于到了我們想要找的邏輯。
- 首先根據(jù)前面收集的證書集合來組裝tlsca的certpool,因?yàn)镸SP那邊過來的證書PEM格式的,還需要轉(zhuǎn)換成x509的標(biāo)準(zhǔn)
- 之后就是更新gServer.tlsConfig.ClientCAs了,這就是前面所講的ClientRootCAs
小結(jié)
好了,至此,fabric的tls配置的部分就是這點(diǎn)東西,看完,你起碼知道那幾個(gè)配置項(xiàng)的意義,出問題有的放矢了。
下面我們通過一個(gè)實(shí)際的場景來串聯(lián)下上面涉及的知識(shí)。
peer channel update -o orderer.example.com:7050 -c $CHANNEL_NAME -f ./channel-artifacts/${CORE_PEER_LOCALMSPID}anchors.tx --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA
這是一個(gè)通道更新的腳本,這條命令告訴我們幾件事情
- 通道更新是需要發(fā)給orderer去處理的
- 連接需要開啟tls
- 指定了cafile
那問題來了,為什么要指定cafile?而且還是orderer的ca。
首先我們先要確認(rèn)的是cafile是對應(yīng)的前面提到的哪種配置。
- viper.Set("orderer.tls.rootcert.file", caFile)
回顧下ServerRootCAs的部分,原來這里的目的是為了校驗(yàn)服務(wù)端證書用。
