github.com/true-sqn/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  }