github.com/true-sqn/fabric@v2.1.1+incompatible/discovery/service.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package discovery
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"encoding/hex"
    13  	"fmt"
    14  
    15  	"github.com/hyperledger/fabric-protos-go/discovery"
    16  	"github.com/hyperledger/fabric/common/flogging"
    17  	"github.com/hyperledger/fabric/common/util"
    18  	"github.com/hyperledger/fabric/discovery/protoext"
    19  	common2 "github.com/hyperledger/fabric/gossip/common"
    20  	discovery2 "github.com/hyperledger/fabric/gossip/discovery"
    21  	"github.com/hyperledger/fabric/internal/pkg/comm"
    22  	"github.com/hyperledger/fabric/protoutil"
    23  	"github.com/pkg/errors"
    24  )
    25  
    26  var (
    27  	logger = flogging.MustGetLogger("discovery")
    28  )
    29  
    30  var accessDenied = wrapError(errors.New("access denied"))
    31  
    32  // certHashExtractor extracts the TLS certificate from a given context
    33  // and returns its hash
    34  type certHashExtractor func(ctx context.Context) []byte
    35  
    36  // dispatcher defines a function that dispatches a query
    37  type dispatcher func(q *discovery.Query) *discovery.QueryResult
    38  
    39  type service struct {
    40  	config             Config
    41  	channelDispatchers map[protoext.QueryType]dispatcher
    42  	localDispatchers   map[protoext.QueryType]dispatcher
    43  	auth               *authCache
    44  	Support
    45  }
    46  
    47  // Config defines the configuration of the discovery service
    48  type Config struct {
    49  	TLS                          bool
    50  	AuthCacheEnabled             bool
    51  	AuthCacheMaxSize             int
    52  	AuthCachePurgeRetentionRatio float64
    53  }
    54  
    55  // String returns a string representation of this Config
    56  func (c Config) String() string {
    57  	if c.AuthCacheEnabled {
    58  		return fmt.Sprintf("TLS: %t, authCacheMaxSize: %d, authCachePurgeRatio: %f", c.TLS, c.AuthCacheMaxSize, c.AuthCachePurgeRetentionRatio)
    59  	}
    60  	return fmt.Sprintf("TLS: %t, auth cache disabled", c.TLS)
    61  }
    62  
    63  // peerMapping maps PKI-IDs to Peers
    64  type peerMapping map[string]*discovery.Peer
    65  
    66  // NewService creates a new discovery service instance
    67  func NewService(config Config, sup Support) *service {
    68  	s := &service{
    69  		auth: newAuthCache(sup, authCacheConfig{
    70  			enabled:             config.AuthCacheEnabled,
    71  			maxCacheSize:        config.AuthCacheMaxSize,
    72  			purgeRetentionRatio: config.AuthCachePurgeRetentionRatio,
    73  		}),
    74  		Support: sup,
    75  	}
    76  	s.channelDispatchers = map[protoext.QueryType]dispatcher{
    77  		protoext.ConfigQueryType:         s.configQuery,
    78  		protoext.ChaincodeQueryType:      s.chaincodeQuery,
    79  		protoext.PeerMembershipQueryType: s.channelMembershipResponse,
    80  	}
    81  	s.localDispatchers = map[protoext.QueryType]dispatcher{
    82  		protoext.LocalMembershipQueryType: s.localMembershipResponse,
    83  	}
    84  	logger.Info("Created with config", config)
    85  	return s
    86  }
    87  
    88  func (s *service) Discover(ctx context.Context, request *discovery.SignedRequest) (*discovery.Response, error) {
    89  	addr := util.ExtractRemoteAddress(ctx)
    90  	req, err := validateStructure(ctx, request, s.config.TLS, comm.ExtractCertificateHashFromContext)
    91  	if err != nil {
    92  		logger.Warningf("Request from %s is malformed or invalid: %v", addr, err)
    93  		return nil, err
    94  	}
    95  	logger.Debugf("Processing request from %s: %v", addr, req)
    96  	var res []*discovery.QueryResult
    97  	for _, q := range req.Queries {
    98  		res = append(res, s.processQuery(q, request, req.Authentication.ClientIdentity, addr))
    99  	}
   100  	logger.Debugf("Returning to %s a response containing: %v", addr, res)
   101  	return &discovery.Response{
   102  		Results: res,
   103  	}, nil
   104  }
   105  
   106  func (s *service) processQuery(query *discovery.Query, request *discovery.SignedRequest, identity []byte, addr string) *discovery.QueryResult {
   107  	if query.Channel != "" && !s.ChannelExists(query.Channel) {
   108  		logger.Warning("got query for channel", query.Channel, "from", addr, "but it doesn't exist")
   109  		return accessDenied
   110  	}
   111  	if err := s.auth.EligibleForService(query.Channel, protoutil.SignedData{
   112  		Data:      request.Payload,
   113  		Signature: request.Signature,
   114  		Identity:  identity,
   115  	}); err != nil {
   116  		logger.Warning("got query for channel", query.Channel, "from", addr, "but it isn't eligible:", err)
   117  		return accessDenied
   118  	}
   119  	return s.dispatch(query)
   120  }
   121  
   122  func (s *service) dispatch(q *discovery.Query) *discovery.QueryResult {
   123  	dispatchers := s.channelDispatchers
   124  	// Ensure local queries are routed only to channel-less dispatchers
   125  	if q.Channel == "" {
   126  		dispatchers = s.localDispatchers
   127  	}
   128  	dispatchQuery, exists := dispatchers[protoext.GetQueryType(q)]
   129  	if !exists {
   130  		return wrapError(errors.New("unknown or missing request type"))
   131  	}
   132  	return dispatchQuery(q)
   133  }
   134  
   135  func (s *service) chaincodeQuery(q *discovery.Query) *discovery.QueryResult {
   136  	if err := validateCCQuery(q.GetCcQuery()); err != nil {
   137  		return wrapError(err)
   138  	}
   139  	var descriptors []*discovery.EndorsementDescriptor
   140  	for _, interest := range q.GetCcQuery().Interests {
   141  		desc, err := s.PeersForEndorsement(common2.ChannelID(q.Channel), interest)
   142  		if err != nil {
   143  			logger.Errorf("Failed constructing descriptor for chaincode %s,: %v", interest, err)
   144  			return wrapError(errors.Errorf("failed constructing descriptor for %v", interest))
   145  		}
   146  		descriptors = append(descriptors, desc)
   147  	}
   148  
   149  	return &discovery.QueryResult{
   150  		Result: &discovery.QueryResult_CcQueryRes{
   151  			CcQueryRes: &discovery.ChaincodeQueryResult{
   152  				Content: descriptors,
   153  			},
   154  		},
   155  	}
   156  }
   157  
   158  func (s *service) configQuery(q *discovery.Query) *discovery.QueryResult {
   159  	conf, err := s.Config(q.Channel)
   160  	if err != nil {
   161  		logger.Errorf("Failed fetching config for channel %s: %v", q.Channel, err)
   162  		return wrapError(errors.Errorf("failed fetching config for channel %s", q.Channel))
   163  	}
   164  	return &discovery.QueryResult{
   165  		Result: &discovery.QueryResult_ConfigResult{
   166  			ConfigResult: conf,
   167  		},
   168  	}
   169  }
   170  
   171  func wrapPeerResponse(peersByOrg map[string]*discovery.Peers) *discovery.QueryResult {
   172  	return &discovery.QueryResult{
   173  		Result: &discovery.QueryResult_Members{
   174  			Members: &discovery.PeerMembershipResult{
   175  				PeersByOrg: peersByOrg,
   176  			},
   177  		},
   178  	}
   179  }
   180  
   181  func (s *service) channelMembershipResponse(q *discovery.Query) *discovery.QueryResult {
   182  	chanPeers, err := s.PeersAuthorizedByCriteria(common2.ChannelID(q.Channel), q.GetPeerQuery().Filter)
   183  	if err != nil {
   184  		return wrapError(err)
   185  	}
   186  	membersByOrgs := make(map[string]*discovery.Peers)
   187  	chanPeerByID := discovery2.Members(chanPeers).ByID()
   188  	for org, ids2Peers := range s.computeMembership(q) {
   189  		membersByOrgs[org] = &discovery.Peers{}
   190  		for id, peer := range ids2Peers {
   191  			// Check if the peer is in the channel view
   192  			stateInfoMsg, exists := chanPeerByID[string(id)]
   193  			// If the peer isn't in the channel view, skip it and don't include it in the response
   194  			if !exists {
   195  				continue
   196  			}
   197  			peer.StateInfo = stateInfoMsg.Envelope
   198  			membersByOrgs[org].Peers = append(membersByOrgs[org].Peers, peer)
   199  		}
   200  	}
   201  	return wrapPeerResponse(membersByOrgs)
   202  }
   203  
   204  func (s *service) localMembershipResponse(q *discovery.Query) *discovery.QueryResult {
   205  	membersByOrgs := make(map[string]*discovery.Peers)
   206  	for org, ids2Peers := range s.computeMembership(q) {
   207  		membersByOrgs[org] = &discovery.Peers{}
   208  		for _, peer := range ids2Peers {
   209  			membersByOrgs[org].Peers = append(membersByOrgs[org].Peers, peer)
   210  		}
   211  	}
   212  	return wrapPeerResponse(membersByOrgs)
   213  }
   214  
   215  func (s *service) computeMembership(_ *discovery.Query) map[string]peerMapping {
   216  	peersByOrg := make(map[string]peerMapping)
   217  	peerAliveInfo := discovery2.Members(s.Peers()).ByID()
   218  	for org, peerIdentities := range s.IdentityInfo().ByOrg() {
   219  		peersForCurrentOrg := make(peerMapping)
   220  		peersByOrg[org] = peersForCurrentOrg
   221  		for _, id := range peerIdentities {
   222  			// Check peer exists in alive membership view
   223  			aliveInfo, exists := peerAliveInfo[string(id.PKIId)]
   224  			if !exists {
   225  				continue
   226  			}
   227  			peersForCurrentOrg[string(id.PKIId)] = &discovery.Peer{
   228  				Identity:       id.Identity,
   229  				MembershipInfo: aliveInfo.Envelope,
   230  			}
   231  		}
   232  	}
   233  	return peersByOrg
   234  }
   235  
   236  // validateStructure validates that the request contains all the needed fields and that they are computed correctly
   237  func validateStructure(ctx context.Context, request *discovery.SignedRequest, tlsEnabled bool, certHashFromContext certHashExtractor) (*discovery.Request, error) {
   238  	if request == nil {
   239  		return nil, errors.New("nil request")
   240  	}
   241  	req, err := protoext.SignedRequestToRequest(request)
   242  	if err != nil {
   243  		return nil, errors.Wrap(err, "failed parsing request")
   244  	}
   245  	if req.Authentication == nil {
   246  		return nil, errors.New("access denied, no authentication info in request")
   247  	}
   248  	if len(req.Authentication.ClientIdentity) == 0 {
   249  		return nil, errors.New("access denied, client identity wasn't supplied")
   250  	}
   251  	if !tlsEnabled {
   252  		return req, nil
   253  	}
   254  	computedHash := certHashFromContext(ctx)
   255  	if len(computedHash) == 0 {
   256  		return nil, errors.New("client didn't send a TLS certificate")
   257  	}
   258  	if !bytes.Equal(computedHash, req.Authentication.ClientTlsCertHash) {
   259  		claimed := hex.EncodeToString(req.Authentication.ClientTlsCertHash)
   260  		logger.Warningf("client claimed TLS hash %s doesn't match computed TLS hash from gRPC stream %s", claimed, hex.EncodeToString(computedHash))
   261  		return nil, errors.New("client claimed TLS hash doesn't match computed TLS hash from gRPC stream")
   262  	}
   263  	return req, nil
   264  }
   265  
   266  func validateCCQuery(ccQuery *discovery.ChaincodeQuery) error {
   267  	if len(ccQuery.Interests) == 0 {
   268  		return errors.New("chaincode query must have at least one chaincode interest")
   269  	}
   270  	for _, interest := range ccQuery.Interests {
   271  		if interest == nil {
   272  			return errors.New("chaincode interest is nil")
   273  		}
   274  		if len(interest.Chaincodes) == 0 {
   275  			return errors.New("chaincode interest must contain at least one chaincode")
   276  		}
   277  		for _, cc := range interest.Chaincodes {
   278  			if cc.Name == "" {
   279  				return errors.New("chaincode name in interest cannot be empty")
   280  			}
   281  		}
   282  	}
   283  	return nil
   284  }
   285  
   286  func wrapError(err error) *discovery.QueryResult {
   287  	return &discovery.QueryResult{
   288  		Result: &discovery.QueryResult_Error{
   289  			Error: &discovery.Error{
   290  				Content: err.Error(),
   291  			},
   292  		},
   293  	}
   294  }