Skip to content

Commit 4579ed1

Browse files
committed
[FAB-2007] Gossip: External and internal endpoints I
Intro: An organization might want to publish external endpoints for other organizations, but use internal endpoints (intranet) for communication between peers inside the organization. At the same time, an organization might not want to leak information about its internal addresses to other organizations. A peer has 2 endpoints when it is configured: 1) Internal endpoint (exists anyway) 2) External endpoint (might be configured) Only peers that have an external endpoint configured are supposed to be visible to peers outside the organization. What's in this commit? This commit addresses this deal in the discovery layer: When a membership request message reaches a peer, it grabs all alive messages it posseses and sends them to the remote peer in a membership response message. Both messages are point-to-point (not "gossiped"/broadcasted). And need to be created in such a way to: 1) Not tell about peers that have no external endpoint 2) Not leak internal endpoints to peers outside the org This commit adds a policy to the discovery layer that enables: 1) Filter (Sieve): Only to include peers that hold some criteria in the membership response message. 2) Message mutator (Disjoiner): removes fields of the messages sent to remote peers that shouldn't be exposed to the remote peer. How is it tested? I wrote a test that simulates 2 organizations, and a disclosure policy that fits what is going to be done in the next commit in the gossip layer (the layer above). The test checks conditions (1) and (2). Signed-off-by: Yacov Manevich <[email protected]> Change-Id: Iade3d32b0d2a58400734b76c30189474c001718b
1 parent 565a758 commit 4579ed1

File tree

4 files changed

+281
-58
lines changed

4 files changed

+281
-58
lines changed

gossip/discovery/discovery.go

+20
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ type CryptoService interface {
3030
SignMessage(m *proto.GossipMessage, internalEndpoint string) *proto.Envelope
3131
}
3232

33+
// EnvelopeFilter may or may not remove part of the Envelope
34+
// that the given SignedGossipMessage originates from.
35+
type EnvelopeFilter func(message *proto.SignedGossipMessage) *proto.Envelope
36+
37+
// Sieve defines the messages that are allowed to be sent to some remote peer,
38+
// based on some criteria.
39+
// Returns whether the sieve permits sending a given message.
40+
type Sieve func(message *proto.SignedGossipMessage) bool
41+
42+
// DisclosurePolicy defines which messages a given remote peer
43+
// is eligible of knowing about, and also what is it eligible
44+
// to know about out of a given SignedGossipMessage.
45+
// Returns:
46+
// 1) A Sieve for a given remote peer.
47+
// The Sieve is applied for each peer in question and outputs
48+
// whether the message should be disclosed to the remote peer.
49+
// 2) A EnvelopeFilter for a given SignedGossipMessage, which may remove
50+
// part of the Envelope the SignedGossipMessage originates from
51+
type DisclosurePolicy func(remotePeer *NetworkMember) (Sieve, EnvelopeFilter)
52+
3353
// CommService is an interface that the discovery expects to be implemented and passed on creation
3454
type CommService interface {
3555
// Gossip gossips a message

gossip/discovery/discovery_impl.go

+59-45
Original file line numberDiff line numberDiff line change
@@ -84,28 +84,30 @@ type gossipDiscoveryImpl struct {
8484
crypt CryptoService
8585
lock *sync.RWMutex
8686

87-
toDieChan chan struct{}
88-
toDieFlag int32
89-
logger *logging.Logger
87+
toDieChan chan struct{}
88+
toDieFlag int32
89+
logger *logging.Logger
90+
disclosurePolicy DisclosurePolicy
9091
}
9192

9293
// NewDiscoveryService returns a new discovery service with the comm module passed and the crypto service passed
93-
func NewDiscoveryService(bootstrapPeers []string, self NetworkMember, comm CommService, crypt CryptoService) Discovery {
94+
func NewDiscoveryService(bootstrapPeers []string, self NetworkMember, comm CommService, crypt CryptoService, disPol DisclosurePolicy) Discovery {
9495
d := &gossipDiscoveryImpl{
95-
self: self,
96-
incTime: uint64(time.Now().UnixNano()),
97-
seqNum: uint64(0),
98-
deadLastTS: make(map[string]*timestamp),
99-
aliveLastTS: make(map[string]*timestamp),
100-
id2Member: make(map[string]*NetworkMember),
101-
aliveMembership: util.NewMembershipStore(),
102-
deadMembership: util.NewMembershipStore(),
103-
crypt: crypt,
104-
comm: comm,
105-
lock: &sync.RWMutex{},
106-
toDieChan: make(chan struct{}, 1),
107-
toDieFlag: int32(0),
108-
logger: util.GetLogger(util.LoggingDiscoveryModule, self.InternalEndpoint),
96+
self: self,
97+
incTime: uint64(time.Now().UnixNano()),
98+
seqNum: uint64(0),
99+
deadLastTS: make(map[string]*timestamp),
100+
aliveLastTS: make(map[string]*timestamp),
101+
id2Member: make(map[string]*NetworkMember),
102+
aliveMembership: util.NewMembershipStore(),
103+
deadMembership: util.NewMembershipStore(),
104+
crypt: crypt,
105+
comm: comm,
106+
lock: &sync.RWMutex{},
107+
toDieChan: make(chan struct{}, 1),
108+
toDieFlag: int32(0),
109+
logger: util.GetLogger(util.LoggingDiscoveryModule, self.InternalEndpoint),
110+
disclosurePolicy: disPol,
109111
}
110112

111113
go d.periodicalSendAlive()
@@ -307,7 +309,7 @@ func (d *gossipDiscoveryImpl) handleMsgFromComm(m *proto.SignedGossipMessage) {
307309
// Sending a membership response to a peer may block this routine
308310
// in case the sending is deliberately slow (i.e attack).
309311
// will keep this async until I'll write a timeout detector in the comm layer
310-
go d.sendMemResponse(selfInfoGossipMsg.GetAliveMsg().Membership, memReq.Known, internalEndpoint)
312+
go d.sendMemResponse(selfInfoGossipMsg.GetAliveMsg().Membership, internalEndpoint)
311313
return
312314
}
313315

@@ -352,19 +354,27 @@ func (d *gossipDiscoveryImpl) handleMsgFromComm(m *proto.SignedGossipMessage) {
352354
}
353355
}
354356

355-
func (d *gossipDiscoveryImpl) sendMemResponse(member *proto.Member, known [][]byte, internalEndpoint string) {
356-
d.logger.Debug("Entering", member)
357+
func (d *gossipDiscoveryImpl) sendMemResponse(targetMember *proto.Member, internalEndpoint string) {
358+
d.logger.Debug("Entering", targetMember)
357359

358-
memResp := d.createMembershipResponse(known)
360+
targetPeer := &NetworkMember{
361+
Endpoint: targetMember.Endpoint,
362+
Metadata: targetMember.Metadata,
363+
PKIid: targetMember.PkiID,
364+
InternalEndpoint: internalEndpoint,
365+
}
366+
367+
memResp := d.createMembershipResponse(targetPeer)
368+
if memResp == nil {
369+
errMsg := `Got a membership request from a peer that shouldn't have sent one: %v, closing connection to the peer as a result.`
370+
d.logger.Warningf(errMsg, targetMember)
371+
d.comm.CloseConn(targetPeer)
372+
return
373+
}
359374

360375
defer d.logger.Debug("Exiting, replying with", memResp)
361376

362-
d.comm.SendToPeer(&NetworkMember{
363-
Endpoint: member.Endpoint,
364-
Metadata: member.Metadata,
365-
PKIid: member.PkiID,
366-
InternalEndpoint: internalEndpoint,
367-
}, (&proto.GossipMessage{
377+
d.comm.SendToPeer(targetPeer, (&proto.GossipMessage{
368378
Tag: proto.GossipMessage_EMPTY,
369379
Nonce: uint64(0),
370380
Content: &proto.GossipMessage_MemRes{
@@ -373,36 +383,37 @@ func (d *gossipDiscoveryImpl) sendMemResponse(member *proto.Member, known [][]by
373383
}).NoopSign())
374384
}
375385

376-
func (d *gossipDiscoveryImpl) createMembershipResponse(known [][]byte) *proto.MembershipResponse {
386+
func (d *gossipDiscoveryImpl) createMembershipResponse(targetMember *NetworkMember) *proto.MembershipResponse {
387+
shouldBeDisclosed, omitConcealedFields := d.disclosurePolicy(targetMember)
377388
aliveMsg := d.createAliveMessage()
378389

390+
if !shouldBeDisclosed(aliveMsg) {
391+
return nil
392+
}
393+
379394
d.lock.RLock()
380395
defer d.lock.RUnlock()
381396

382397
deadPeers := []*proto.Envelope{}
383398

384399
for _, dm := range d.deadMembership.ToSlice() {
385-
isKnown := false
386-
for _, knownPeer := range known {
387-
if equalPKIid(knownPeer, dm.GetAliveMsg().Membership.PkiID) {
388-
isKnown = true
389-
break
390-
}
391-
}
392-
if !isKnown {
393-
deadPeers = append(deadPeers, dm.Envelope)
394-
break
400+
401+
if !shouldBeDisclosed(dm) {
402+
continue
395403
}
404+
deadPeers = append(deadPeers, omitConcealedFields(dm))
396405
}
397406

398-
aliveMembersAsSlice := d.aliveMembership.ToSlice()
399-
aliveSnapshot := make([]*proto.Envelope, len(aliveMembersAsSlice))
400-
for i, msg := range aliveMembersAsSlice {
401-
aliveSnapshot[i] = msg.Envelope
407+
var aliveSnapshot []*proto.Envelope
408+
for _, am := range d.aliveMembership.ToSlice() {
409+
if !shouldBeDisclosed(am) {
410+
continue
411+
}
412+
aliveSnapshot = append(aliveSnapshot, omitConcealedFields(am))
402413
}
403414

404415
return &proto.MembershipResponse{
405-
Alive: append(aliveSnapshot, aliveMsg.Envelope),
416+
Alive: append(aliveSnapshot, omitConcealedFields(aliveMsg)),
406417
Dead: deadPeers,
407418
}
408419
}
@@ -536,7 +547,10 @@ func (d *gossipDiscoveryImpl) sendMembershipRequest(member *NetworkMember) {
536547
func (d *gossipDiscoveryImpl) createMembershipRequest() *proto.SignedGossipMessage {
537548
req := &proto.MembershipRequest{
538549
SelfInformation: d.createAliveMessage().Envelope,
539-
Known: d.getKnownPeers(),
550+
// TODO: sending the known peers is not secure because the remote peer might shouldn't know
551+
// TODO: about the known peers. I'm deprecating this until a secure mechanism will be implemented.
552+
// TODO: See FAB-2570 for tracking this issue.
553+
Known: [][]byte{},
540554
}
541555
return (&proto.GossipMessage{
542556
Tag: proto.GossipMessage_EMPTY,

0 commit comments

Comments
 (0)