github.com/defanghe/fabric@v2.1.1+incompatible/orderer/consensus/etcdraft/membership.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package etcdraft 8 9 import ( 10 "crypto/x509" 11 "encoding/pem" 12 "fmt" 13 14 "github.com/golang/protobuf/proto" 15 "github.com/hyperledger/fabric-protos-go/orderer/etcdraft" 16 "github.com/hyperledger/fabric/common/channelconfig" 17 "github.com/pkg/errors" 18 "go.etcd.io/etcd/raft" 19 "go.etcd.io/etcd/raft/raftpb" 20 ) 21 22 // MembershipByCert convert consenters map into set encapsulated by map 23 // where key is client TLS certificate 24 func MembershipByCert(consenters map[uint64]*etcdraft.Consenter) map[string]uint64 { 25 set := map[string]uint64{} 26 for nodeID, c := range consenters { 27 set[string(c.ClientTlsCert)] = nodeID 28 } 29 return set 30 } 31 32 // MembershipChanges keeps information about membership 33 // changes introduced during configuration update 34 type MembershipChanges struct { 35 NewBlockMetadata *etcdraft.BlockMetadata 36 NewConsenters map[uint64]*etcdraft.Consenter 37 AddedNodes []*etcdraft.Consenter 38 RemovedNodes []*etcdraft.Consenter 39 ConfChange *raftpb.ConfChange 40 RotatedNode uint64 41 } 42 43 // ComputeMembershipChanges computes membership update based on information about new consenters, returns 44 // two slices: a slice of added consenters and a slice of consenters to be removed 45 func ComputeMembershipChanges(oldMetadata *etcdraft.BlockMetadata, oldConsenters map[uint64]*etcdraft.Consenter, newConsenters []*etcdraft.Consenter, ordererConfig channelconfig.Orderer) (mc *MembershipChanges, err error) { 46 result := &MembershipChanges{ 47 NewConsenters: map[uint64]*etcdraft.Consenter{}, 48 NewBlockMetadata: proto.Clone(oldMetadata).(*etcdraft.BlockMetadata), 49 AddedNodes: []*etcdraft.Consenter{}, 50 RemovedNodes: []*etcdraft.Consenter{}, 51 } 52 53 result.NewBlockMetadata.ConsenterIds = make([]uint64, len(newConsenters)) 54 55 var addedNodeIndex int 56 currentConsentersSet := MembershipByCert(oldConsenters) 57 for i, c := range newConsenters { 58 if nodeID, exists := currentConsentersSet[string(c.ClientTlsCert)]; exists { 59 result.NewBlockMetadata.ConsenterIds[i] = nodeID 60 result.NewConsenters[nodeID] = c 61 continue 62 } 63 err := validateConsenterTLSCerts(c, ordererConfig) 64 if err != nil { 65 return nil, err 66 } 67 addedNodeIndex = i 68 result.AddedNodes = append(result.AddedNodes, c) 69 } 70 71 var deletedNodeID uint64 72 newConsentersSet := ConsentersToMap(newConsenters) 73 for nodeID, c := range oldConsenters { 74 if _, exists := newConsentersSet[string(c.ClientTlsCert)]; !exists { 75 result.RemovedNodes = append(result.RemovedNodes, c) 76 deletedNodeID = nodeID 77 } 78 } 79 80 switch { 81 case len(result.AddedNodes) == 1 && len(result.RemovedNodes) == 1: 82 // A cert is considered being rotated, iff exact one new node is being added 83 // AND exact one existing node is being removed 84 result.RotatedNode = deletedNodeID 85 result.NewBlockMetadata.ConsenterIds[addedNodeIndex] = deletedNodeID 86 result.NewConsenters[deletedNodeID] = result.AddedNodes[0] 87 case len(result.AddedNodes) == 1 && len(result.RemovedNodes) == 0: 88 // new node 89 nodeID := result.NewBlockMetadata.NextConsenterId 90 result.NewConsenters[nodeID] = result.AddedNodes[0] 91 result.NewBlockMetadata.ConsenterIds[addedNodeIndex] = nodeID 92 result.NewBlockMetadata.NextConsenterId++ 93 result.ConfChange = &raftpb.ConfChange{ 94 NodeID: nodeID, 95 Type: raftpb.ConfChangeAddNode, 96 } 97 case len(result.AddedNodes) == 0 && len(result.RemovedNodes) == 1: 98 // removed node 99 nodeID := deletedNodeID 100 result.ConfChange = &raftpb.ConfChange{ 101 NodeID: nodeID, 102 Type: raftpb.ConfChangeRemoveNode, 103 } 104 delete(result.NewConsenters, nodeID) 105 case len(result.AddedNodes) == 0 && len(result.RemovedNodes) == 0: 106 // no change 107 default: 108 // len(result.AddedNodes) > 1 || len(result.RemovedNodes) > 1 { 109 return nil, errors.Errorf("update of more than one consenter at a time is not supported, requested changes: %s", result) 110 } 111 112 return result, nil 113 } 114 115 func validateConsenterTLSCerts(c *etcdraft.Consenter, ordererConfig channelconfig.Orderer) error { 116 clientCert, err := parseCertificateFromBytes(c.ClientTlsCert) 117 if err != nil { 118 return errors.Wrap(err, "parsing tls client cert") 119 } 120 serverCert, err := parseCertificateFromBytes(c.ServerTlsCert) 121 if err != nil { 122 return errors.Wrap(err, "parsing tls server cert") 123 } 124 125 opts, err := createX509VerifyOptions(ordererConfig.Organizations()) 126 if err != nil { 127 return errors.WithMessage(err, "creating x509 verify options") 128 } 129 _, err = clientCert.Verify(opts) 130 if err != nil { 131 return fmt.Errorf("verifying tls client cert with serial number %d: %v", clientCert.SerialNumber, err) 132 } 133 134 _, err = serverCert.Verify(opts) 135 if err != nil { 136 return fmt.Errorf("verifying tls server cert with serial number %d: %v", serverCert.SerialNumber, err) 137 } 138 139 return nil 140 } 141 142 func createX509VerifyOptions(orgs map[string]channelconfig.OrdererOrg) (x509.VerifyOptions, error) { 143 tlsRoots := x509.NewCertPool() 144 tlsIntermediates := x509.NewCertPool() 145 146 for _, org := range orgs { 147 rootCerts, err := parseCertificateListFromBytes(org.MSP().GetTLSRootCerts()) 148 if err != nil { 149 return x509.VerifyOptions{}, errors.Wrap(err, "parsing tls root certs") 150 } 151 intermediateCerts, err := parseCertificateListFromBytes(org.MSP().GetTLSIntermediateCerts()) 152 if err != nil { 153 return x509.VerifyOptions{}, errors.Wrap(err, "parsing tls intermediate certs") 154 } 155 156 for _, cert := range rootCerts { 157 tlsRoots.AddCert(cert) 158 } 159 for _, cert := range intermediateCerts { 160 tlsIntermediates.AddCert(cert) 161 } 162 } 163 164 return x509.VerifyOptions{ 165 Roots: tlsRoots, 166 Intermediates: tlsIntermediates, 167 KeyUsages: []x509.ExtKeyUsage{ 168 x509.ExtKeyUsageClientAuth, 169 x509.ExtKeyUsageServerAuth, 170 }, 171 }, nil 172 } 173 174 func parseCertificateListFromBytes(certs [][]byte) ([]*x509.Certificate, error) { 175 certificateList := []*x509.Certificate{} 176 177 for _, cert := range certs { 178 certificate, err := parseCertificateFromBytes(cert) 179 if err != nil { 180 return certificateList, err 181 } 182 183 certificateList = append(certificateList, certificate) 184 } 185 186 return certificateList, nil 187 } 188 189 func parseCertificateFromBytes(cert []byte) (*x509.Certificate, error) { 190 pemBlock, _ := pem.Decode(cert) 191 if pemBlock == nil { 192 return &x509.Certificate{}, fmt.Errorf("no PEM data found in cert[% x]", cert) 193 } 194 195 certificate, err := x509.ParseCertificate(pemBlock.Bytes) 196 if err != nil { 197 return &x509.Certificate{}, err 198 } 199 200 return certificate, nil 201 } 202 203 // Stringer implements fmt.Stringer interface 204 func (mc *MembershipChanges) String() string { 205 return fmt.Sprintf("add %d node(s), remove %d node(s)", len(mc.AddedNodes), len(mc.RemovedNodes)) 206 } 207 208 // Changed indicates whether these changes actually do anything 209 func (mc *MembershipChanges) Changed() bool { 210 return len(mc.AddedNodes) > 0 || len(mc.RemovedNodes) > 0 211 } 212 213 // Rotated indicates whether the change was a rotation 214 func (mc *MembershipChanges) Rotated() bool { 215 return len(mc.AddedNodes) == 1 && len(mc.RemovedNodes) == 1 216 } 217 218 // UnacceptableQuorumLoss returns true if membership change will result in avoidable quorum loss, 219 // given current number of active nodes in cluster. Avoidable means that more nodes can be started 220 // to prevent quorum loss. Sometimes, quorum loss is inevitable, for example expanding 1-node cluster. 221 func (mc *MembershipChanges) UnacceptableQuorumLoss(active []uint64) bool { 222 activeMap := make(map[uint64]struct{}) 223 for _, i := range active { 224 activeMap[i] = struct{}{} 225 } 226 227 isCFT := len(mc.NewConsenters) > 2 // if resulting cluster cannot tolerate any fault, quorum loss is inevitable 228 quorum := len(mc.NewConsenters)/2 + 1 229 230 switch { 231 case mc.ConfChange != nil && mc.ConfChange.Type == raftpb.ConfChangeAddNode: // Add 232 return isCFT && len(active) < quorum 233 234 case mc.RotatedNode != raft.None: // Rotate 235 delete(activeMap, mc.RotatedNode) 236 return isCFT && len(activeMap) < quorum 237 238 case mc.ConfChange != nil && mc.ConfChange.Type == raftpb.ConfChangeRemoveNode: // Remove 239 delete(activeMap, mc.ConfChange.NodeID) 240 return len(activeMap) < quorum 241 242 default: // No change 243 return false 244 } 245 }