前面我们的证书生成的篇章中,只是提了下tls相关的内容,但他们是怎么用的并没有涉及,这一篇我们来探讨下Fabric中tls是怎么运转的。
首先我们来看下SecureOptions,这里包含了不管是server还是client所有涉及的配置项,搞懂这些选项的来龙去脉,基本上你也就搞懂了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 -- 回调方法,用来在tls握手期间,正常的证书校验完毕后,回调这部分自定义校验逻辑。
- Certificate -- 这里是存放tls所要使用的证书,不论是server还是client
- Key -- 私钥
- ServerRootCAs -- 客户端用来校验服务端证书
- ClientRootCAs -- 服务端用来校验客户端证书
- 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)
}
}
- 可以看到,这里的意思是,连接的过程中,连接方希望被连接方的证书跟我所期待的完全一致。可以说这是非常严格的证书校验逻辑。整个fabric有用到这个的地方就是etcd的部分,因为etcd的网络层是自己实现的,在一般流程外,还需要严格的证书校验逻辑。普通的tls只是判断是否是ca签发,因为etcd节点保存了所有成员的服务端证书,它需要证书要严格一致才表示握手通过。
- 这部分不再展开,可以参考etcdraft篇。
Certificate
handshake的过程需要发给对方证书,让对方拿去做校验的。而这里就是放证书的地方。只要发起tls连接,这个证书就会发送。
- peer.tls.clientCert.file(CORE_PEER_TLS_CLIENTCERT_FILE),orderer.tls.clientCert.file负责客户端证书配置
- CORE_PEER_TLS_CERT_FILE, ORDERER_GENERAL_TLS_CERTIFICATE负责服务端证书配置
Key
在tls中,双方需要进行密钥协商,协商的结果是双方发送的内容会用这个密钥进行加密解密,一般说来这个密钥是对称加密。当然了,你也可以用不对称,但是用在tls的场景效率太低了,一般不这么用。
怎么协商呢?客户端在发起连接时,会随机一个预备密钥,用服务端的公钥进行加密,发给对方,而服务端用自己的私钥进行解密,进而协商出一个主密钥。而这里的key就是做协商加密用的。
- peer.tls.clientKey.file(CORE_PEER_TLS_CLIENTKEY_FILE),orderer.tls.clientKey.file负责客户端私钥配置
- CORE_PEER_TLS_KEY_FILE,ORDERER_GENERAL_TLS_PRIVATEKEY负责服务端私钥
ServerRootCAs
这是客户端用来校验服务端证书的。这里的校验是指收到的服务端正式是否是该证书签发的。我举两个场景的例子,你就明白了。
ordererclient
什么叫ordererclient,其实就是peer,peer会去broadcast推送给orderer事件,也会deliver拉取block。按照上面的逻辑,这个client创建的时候会将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,相当于去获取了orderer.tls.rootcert.file,也可以当成是ORDERER_GENERAL_TLS_ROOTCAS。注意这两个配置理论上并没有什么关系。在first-network中orderer.tls.rootcert.file指定的是tlscacerts目录,而ORDERER_GENERAL_TLS_ROOTCAS是指定的tls目录的ca.crt, 有可能是向下兼容的关系,比较混乱,但是指定的证书都是同一个,只是名字不同,最好两个还是指保持一致。
peerclient
我们知道peer相互会互联,其中最知名的就是gossip协议,我们看下peer作为client的时候
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,相当于去获取了peer.tls.rootcert.file,也就是CORE_PEER_TLS_ROOTCERT_FILE
ClientRootCAs
在tls的世界里,一般来说服务端发来证书,客户端校验就完了。有时,服务端也可以要求客户端把证书发来,进行双向校验。跟上面同理,客户端连接时,也需要明确的告诉服务端哪些客户端证书是有效的,被承认的。
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"
}
这里的意思是,如果需要对客户端证书进行校验的话,从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
}
这里跟上面类似,如果需要对客户端证书进行校验的话,从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需要客户端证书进行校验
CipherSuites
什么叫加密套件,在tls中协商的过程中是需要有前提的,双方都支持的加密算法。就好比买东西,客户端提出自己的需求,然后服务端按照对方的情况对他进行量身打造,双方共同协商一致。
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,
}
这个图只是解释下这一串的意义,没有深入的意思。大家就当听个响。
Add Org
想象一个场景,动态加入Org的场景,现在orderer已经更新最新的配置bundle。假如orderer的tls开启了客户端证书校验,会有什么问题?
orderer现在还没有将新Org的tlsca证书加到ClientRootCAs里面,所有所有新Org的节点发来的请求服务端根本就不认识。
没关系,下面我们一起来看下,至少得出一个结论,服务端会触发的时机就是更新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启动的时候,会设置相关的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证书和中间证书
- 最重要的时候的调用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
}
好了,终于到了我们想要找的逻辑。
- 首先根据前面收集的证书集合来组装tlsca的certpool,因为MSP那边过来的证书PEM格式的,还需要转换成x509的标准
- 之后就是更新gServer.tlsConfig.ClientCAs了,这就是前面所讲的ClientRootCAs
小结
好了,至此,fabric的tls配置的部分就是这点东西,看完,你起码知道那几个配置项的意义,出问题有的放矢了。
下面我们通过一个实际的场景来串联下上面涉及的知识。
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
这是一个通道更新的脚本,这条命令告诉我们几件事情
- 通道更新是需要发给orderer去处理的
- 连接需要开启tls
- 指定了cafile
那问题来了,为什么要指定cafile?而且还是orderer的ca。
首先我们先要确认的是cafile是对应的前面提到的哪种配置。
- viper.Set("orderer.tls.rootcert.file", caFile)
回顾下ServerRootCAs的部分,原来这里的目的是为了校验服务端证书用。