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