Skip to content

Commit

Permalink
[FAB-2007] Gossip: External and internal endpoints I
Browse files Browse the repository at this point in the history
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 <yacovm@il.ibm.com>
Change-Id: Iade3d32b0d2a58400734b76c30189474c001718b
  • Loading branch information
yacovm committed Mar 2, 2017
1 parent 565a758 commit 4579ed1
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 58 deletions.
20 changes: 20 additions & 0 deletions gossip/discovery/discovery.go
Expand Up @@ -30,6 +30,26 @@ type CryptoService interface {
SignMessage(m *proto.GossipMessage, internalEndpoint string) *proto.Envelope
}

// EnvelopeFilter may or may not remove part of the Envelope
// that the given SignedGossipMessage originates from.
type EnvelopeFilter func(message *proto.SignedGossipMessage) *proto.Envelope

// Sieve defines the messages that are allowed to be sent to some remote peer,
// based on some criteria.
// Returns whether the sieve permits sending a given message.
type Sieve func(message *proto.SignedGossipMessage) bool

// DisclosurePolicy defines which messages a given remote peer
// is eligible of knowing about, and also what is it eligible
// to know about out of a given SignedGossipMessage.
// Returns:
// 1) A Sieve for a given remote peer.
// The Sieve is applied for each peer in question and outputs
// whether the message should be disclosed to the remote peer.
// 2) A EnvelopeFilter for a given SignedGossipMessage, which may remove
// part of the Envelope the SignedGossipMessage originates from
type DisclosurePolicy func(remotePeer *NetworkMember) (Sieve, EnvelopeFilter)

// CommService is an interface that the discovery expects to be implemented and passed on creation
type CommService interface {
// Gossip gossips a message
Expand Down
104 changes: 59 additions & 45 deletions gossip/discovery/discovery_impl.go
Expand Up @@ -84,28 +84,30 @@ type gossipDiscoveryImpl struct {
crypt CryptoService
lock *sync.RWMutex

toDieChan chan struct{}
toDieFlag int32
logger *logging.Logger
toDieChan chan struct{}
toDieFlag int32
logger *logging.Logger
disclosurePolicy DisclosurePolicy
}

// NewDiscoveryService returns a new discovery service with the comm module passed and the crypto service passed
func NewDiscoveryService(bootstrapPeers []string, self NetworkMember, comm CommService, crypt CryptoService) Discovery {
func NewDiscoveryService(bootstrapPeers []string, self NetworkMember, comm CommService, crypt CryptoService, disPol DisclosurePolicy) Discovery {
d := &gossipDiscoveryImpl{
self: self,
incTime: uint64(time.Now().UnixNano()),
seqNum: uint64(0),
deadLastTS: make(map[string]*timestamp),
aliveLastTS: make(map[string]*timestamp),
id2Member: make(map[string]*NetworkMember),
aliveMembership: util.NewMembershipStore(),
deadMembership: util.NewMembershipStore(),
crypt: crypt,
comm: comm,
lock: &sync.RWMutex{},
toDieChan: make(chan struct{}, 1),
toDieFlag: int32(0),
logger: util.GetLogger(util.LoggingDiscoveryModule, self.InternalEndpoint),
self: self,
incTime: uint64(time.Now().UnixNano()),
seqNum: uint64(0),
deadLastTS: make(map[string]*timestamp),
aliveLastTS: make(map[string]*timestamp),
id2Member: make(map[string]*NetworkMember),
aliveMembership: util.NewMembershipStore(),
deadMembership: util.NewMembershipStore(),
crypt: crypt,
comm: comm,
lock: &sync.RWMutex{},
toDieChan: make(chan struct{}, 1),
toDieFlag: int32(0),
logger: util.GetLogger(util.LoggingDiscoveryModule, self.InternalEndpoint),
disclosurePolicy: disPol,
}

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

Expand Down Expand Up @@ -352,19 +354,27 @@ func (d *gossipDiscoveryImpl) handleMsgFromComm(m *proto.SignedGossipMessage) {
}
}

func (d *gossipDiscoveryImpl) sendMemResponse(member *proto.Member, known [][]byte, internalEndpoint string) {
d.logger.Debug("Entering", member)
func (d *gossipDiscoveryImpl) sendMemResponse(targetMember *proto.Member, internalEndpoint string) {
d.logger.Debug("Entering", targetMember)

memResp := d.createMembershipResponse(known)
targetPeer := &NetworkMember{
Endpoint: targetMember.Endpoint,
Metadata: targetMember.Metadata,
PKIid: targetMember.PkiID,
InternalEndpoint: internalEndpoint,
}

memResp := d.createMembershipResponse(targetPeer)
if memResp == nil {
errMsg := `Got a membership request from a peer that shouldn't have sent one: %v, closing connection to the peer as a result.`
d.logger.Warningf(errMsg, targetMember)
d.comm.CloseConn(targetPeer)
return
}

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

d.comm.SendToPeer(&NetworkMember{
Endpoint: member.Endpoint,
Metadata: member.Metadata,
PKIid: member.PkiID,
InternalEndpoint: internalEndpoint,
}, (&proto.GossipMessage{
d.comm.SendToPeer(targetPeer, (&proto.GossipMessage{
Tag: proto.GossipMessage_EMPTY,
Nonce: uint64(0),
Content: &proto.GossipMessage_MemRes{
Expand All @@ -373,36 +383,37 @@ func (d *gossipDiscoveryImpl) sendMemResponse(member *proto.Member, known [][]by
}).NoopSign())
}

func (d *gossipDiscoveryImpl) createMembershipResponse(known [][]byte) *proto.MembershipResponse {
func (d *gossipDiscoveryImpl) createMembershipResponse(targetMember *NetworkMember) *proto.MembershipResponse {
shouldBeDisclosed, omitConcealedFields := d.disclosurePolicy(targetMember)
aliveMsg := d.createAliveMessage()

if !shouldBeDisclosed(aliveMsg) {
return nil
}

d.lock.RLock()
defer d.lock.RUnlock()

deadPeers := []*proto.Envelope{}

for _, dm := range d.deadMembership.ToSlice() {
isKnown := false
for _, knownPeer := range known {
if equalPKIid(knownPeer, dm.GetAliveMsg().Membership.PkiID) {
isKnown = true
break
}
}
if !isKnown {
deadPeers = append(deadPeers, dm.Envelope)
break

if !shouldBeDisclosed(dm) {
continue
}
deadPeers = append(deadPeers, omitConcealedFields(dm))
}

aliveMembersAsSlice := d.aliveMembership.ToSlice()
aliveSnapshot := make([]*proto.Envelope, len(aliveMembersAsSlice))
for i, msg := range aliveMembersAsSlice {
aliveSnapshot[i] = msg.Envelope
var aliveSnapshot []*proto.Envelope
for _, am := range d.aliveMembership.ToSlice() {
if !shouldBeDisclosed(am) {
continue
}
aliveSnapshot = append(aliveSnapshot, omitConcealedFields(am))
}

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

0 comments on commit 4579ed1

Please sign in to comment.