Skip to content

Commit 95837c9

Browse files
committed
[FAB-10970] Make connection refusal more lenient
If the gossip communication layer is called to send a message to a peer with a given PKI-ID, but after the handshake it discovers the remote peer has a different PKI-ID than what was expected, it aborts the connection. This is prolematic for cases where a peer has renewed its certificate, as the PKI-ID which is a hash on the certificate, won't be the same - and as a result, the reincarnated peer would be isolated. This change set makes the connection be aborted only if the peer is from a different organization. Change-Id: I8e13dbce90a9df86eb40912f6e8105e8f19ef776 Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent 8726745 commit 95837c9

File tree

5 files changed

+138
-23
lines changed

5 files changed

+138
-23
lines changed

gossip/api/channel.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ func init() {
1818
}
1919
}
2020

21+
//go:generate mockery -dir . -name SecurityAdvisor -case underscore -output ../mocks/
22+
2123
// SecurityAdvisor defines an external auxiliary object
2224
// that provides security and identity related capabilities
2325
type SecurityAdvisor interface {

gossip/comm/comm_impl.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ const (
3838
defSendBuffSize = 20
3939
)
4040

41+
// SecurityAdvisor defines an external auxiliary object
42+
// that provides security and identity related capabilities
43+
type SecurityAdvisor interface {
44+
// OrgByPeerIdentity returns the organization identity of the given PeerIdentityType
45+
OrgByPeerIdentity(api.PeerIdentityType) api.OrgIdentityType
46+
}
47+
4148
// SetDialTimeout sets the dial timeout
4249
func SetDialTimeout(timeout time.Duration) {
4350
viper.Set("peer.gossip.dialTimeout", timeout)
@@ -53,7 +60,7 @@ func (c *commImpl) SetDialOpts(opts ...grpc.DialOption) {
5360

5461
// NewCommInstanceWithServer creates a comm instance that creates an underlying gRPC server
5562
func NewCommInstanceWithServer(port int, idMapper identity.Mapper, peerIdentity api.PeerIdentityType,
56-
secureDialOpts api.PeerSecureDialOpts, dialOpts ...grpc.DialOption) (Comm, error) {
63+
secureDialOpts api.PeerSecureDialOpts, sa api.SecurityAdvisor, dialOpts ...grpc.DialOption) (Comm, error) {
5764

5865
var ll net.Listener
5966
var s *grpc.Server
@@ -64,6 +71,7 @@ func NewCommInstanceWithServer(port int, idMapper identity.Mapper, peerIdentity
6471
}
6572

6673
commInst := &commImpl{
74+
sa: sa,
6775
pubSub: util.NewPubSub(),
6876
PKIID: idMapper.GetPKIidOfCert(peerIdentity),
6977
idMapper: idMapper,
@@ -99,10 +107,10 @@ func NewCommInstanceWithServer(port int, idMapper identity.Mapper, peerIdentity
99107

100108
// NewCommInstance creates a new comm instance that binds itself to the given gRPC server
101109
func NewCommInstance(s *grpc.Server, certs *common.TLSCertificates, idStore identity.Mapper,
102-
peerIdentity api.PeerIdentityType, secureDialOpts api.PeerSecureDialOpts,
110+
peerIdentity api.PeerIdentityType, secureDialOpts api.PeerSecureDialOpts, sa api.SecurityAdvisor,
103111
dialOpts ...grpc.DialOption) (Comm, error) {
104112

105-
commInst, err := NewCommInstanceWithServer(-1, idStore, peerIdentity, secureDialOpts, dialOpts...)
113+
commInst, err := NewCommInstanceWithServer(-1, idStore, peerIdentity, secureDialOpts, sa, dialOpts...)
106114
if err != nil {
107115
return nil, errors.WithStack(err)
108116
}
@@ -115,6 +123,7 @@ func NewCommInstance(s *grpc.Server, certs *common.TLSCertificates, idStore iden
115123
}
116124

117125
type commImpl struct {
126+
sa api.SecurityAdvisor
118127
tlsCerts *common.TLSCertificates
119128
pubSub *util.PubSub
120129
peerIdentity api.PeerIdentityType
@@ -175,11 +184,18 @@ func (c *commImpl) createConnection(endpoint string, expectedPKIID common.PKIidT
175184
connInfo, err = c.authenticateRemotePeer(stream, true)
176185
if err == nil {
177186
pkiID = connInfo.ID
187+
// PKIID is nil when we don't know the remote PKI id's
178188
if expectedPKIID != nil && !bytes.Equal(pkiID, expectedPKIID) {
179-
// PKIID is nil when we don't know the remote PKI id's
180-
c.logger.Warning("Remote endpoint claims to be a different peer, expected", expectedPKIID, "but got", pkiID)
181-
cc.Close()
182-
return nil, errors.New("Authentication failure")
189+
actualOrg := c.sa.OrgByPeerIdentity(connInfo.Identity)
190+
// If the identity isn't present, it's nil - therefore OrgByPeerIdentity would
191+
// return nil too and thus would be different than the actual organization
192+
identity, _ := c.idMapper.Get(expectedPKIID)
193+
oldOrg := c.sa.OrgByPeerIdentity(identity)
194+
if !bytes.Equal(actualOrg, oldOrg) {
195+
c.logger.Warning("Remote endpoint claims to be a different peer, expected", expectedPKIID, "but got", pkiID)
196+
cc.Close()
197+
return nil, errors.New("authentication failure")
198+
}
183199
}
184200
conn := newConnection(cl, cc, stream, nil)
185201
conn.pkiID = pkiID

gossip/comm/comm_test.go

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ import (
2525
"github.com/hyperledger/fabric/gossip/api"
2626
"github.com/hyperledger/fabric/gossip/common"
2727
"github.com/hyperledger/fabric/gossip/identity"
28+
"github.com/hyperledger/fabric/gossip/mocks"
2829
"github.com/hyperledger/fabric/gossip/util"
2930
proto "github.com/hyperledger/fabric/protos/gossip"
3031
"github.com/spf13/viper"
3132
"github.com/stretchr/testify/assert"
33+
"github.com/stretchr/testify/mock"
3234
"golang.org/x/net/context"
3335
"google.golang.org/grpc"
3436
"google.golang.org/grpc/credentials"
@@ -38,6 +40,7 @@ func init() {
3840
util.SetupTestLogging()
3941
rand.Seed(time.Now().UnixNano())
4042
factory.InitFactories(nil)
43+
naiveSec.On("OrgByPeerIdentity", mock.Anything).Return(api.OrgIdentityType{})
4144
}
4245

4346
func acceptAll(msg interface{}) bool {
@@ -54,10 +57,11 @@ var (
5457
)
5558

5659
type naiveSecProvider struct {
60+
mocks.SecurityAdvisor
5761
}
5862

59-
func (*naiveSecProvider) OrgByPeerIdentity(api.PeerIdentityType) api.OrgIdentityType {
60-
return nil
63+
func (nsp *naiveSecProvider) OrgByPeerIdentity(identity api.PeerIdentityType) api.OrgIdentityType {
64+
return nsp.SecurityAdvisor.Called(identity).Get(0).(api.OrgIdentityType)
6165
}
6266

6367
func (*naiveSecProvider) Expiration(peerIdentity api.PeerIdentityType) (time.Time, error) {
@@ -109,7 +113,7 @@ func (*naiveSecProvider) VerifyByChannel(_ common.ChainID, _ api.PeerIdentityTyp
109113
func newCommInstance(port int, sec *naiveSecProvider) (Comm, error) {
110114
endpoint := fmt.Sprintf("localhost:%d", port)
111115
id := []byte(endpoint)
112-
inst, err := NewCommInstanceWithServer(port, identity.NewIdentityMapper(sec, id, noopPurgeIdentity, sec), id, nil)
116+
inst, err := NewCommInstanceWithServer(port, identity.NewIdentityMapper(sec, id, noopPurgeIdentity, sec), id, nil, sec)
113117
return inst, err
114118
}
115119

@@ -273,7 +277,7 @@ func TestHandshake(t *testing.T) {
273277
idMapper := identity.NewIdentityMapper(naiveSec, id, noopPurgeIdentity, naiveSec)
274278
inst, err := NewCommInstance(s, nil, idMapper, api.PeerIdentityType("localhost:9611"), func() []grpc.DialOption {
275279
return []grpc.DialOption{grpc.WithInsecure()}
276-
})
280+
}, naiveSec)
277281
go s.Serve(ll)
278282
assert.NoError(t, err)
279283
var msg proto.ReceivedMessage
@@ -396,20 +400,87 @@ func TestBasic(t *testing.T) {
396400
waitForMessages(t, out, 2, "Didn't receive 2 messages")
397401
}
398402

403+
func TestConnectUnexpectedPeer(t *testing.T) {
404+
t.Parallel()
405+
// Scenarios: In both scenarios, comm1 connects to comm2 or comm3.
406+
// and expects to see a PKI-ID which is equal to comm4's PKI-ID.
407+
// The connection attempt would succeed or fail based on whether comm2 or comm3
408+
// are in the same org as comm4
409+
comm1Port := 1548
410+
comm2Port := 1549
411+
comm3Port := 1550
412+
comm4Port := 1551
413+
414+
identityByPort := func(port int) api.PeerIdentityType {
415+
return api.PeerIdentityType(fmt.Sprintf("localhost:%d", port))
416+
}
417+
418+
customNaiveSec := &naiveSecProvider{}
419+
customNaiveSec.On("OrgByPeerIdentity", identityByPort(comm1Port)).Return(api.OrgIdentityType("O"))
420+
customNaiveSec.On("OrgByPeerIdentity", identityByPort(comm2Port)).Return(api.OrgIdentityType("A"))
421+
customNaiveSec.On("OrgByPeerIdentity", identityByPort(comm3Port)).Return(api.OrgIdentityType("B"))
422+
customNaiveSec.On("OrgByPeerIdentity", identityByPort(comm4Port)).Return(api.OrgIdentityType("A"))
423+
424+
comm1, _ := newCommInstance(comm1Port, customNaiveSec)
425+
comm2, _ := newCommInstance(comm2Port, naiveSec)
426+
comm3, _ := newCommInstance(comm3Port, naiveSec)
427+
comm4, _ := newCommInstance(comm4Port, naiveSec)
428+
429+
defer comm1.Stop()
430+
defer comm2.Stop()
431+
defer comm3.Stop()
432+
defer comm4.Stop()
433+
434+
messagesForComm1 := comm1.Accept(acceptAll)
435+
messagesForComm2 := comm2.Accept(acceptAll)
436+
messagesForComm3 := comm3.Accept(acceptAll)
437+
438+
// Have comm4 send a message to comm1
439+
// in order for comm1 to know comm4
440+
comm4.Send(createGossipMsg(), remotePeer(comm1Port))
441+
<-messagesForComm1
442+
// Close the connection with comm4
443+
comm1.CloseConn(remotePeer(comm4Port))
444+
// At this point, comm1 knows comm4's identity and organization
445+
446+
t.Run("Same organization", func(t *testing.T) {
447+
unexpectedRemotePeer := remotePeer(comm2Port)
448+
unexpectedRemotePeer.PKIID = remotePeer(comm4Port).PKIID
449+
comm1.Send(createGossipMsg(), unexpectedRemotePeer)
450+
select {
451+
case <-messagesForComm2:
452+
case <-time.After(time.Second * 5):
453+
assert.Fail(t, "Didn't receive a message within a timely manner")
454+
util.PrintStackTrace()
455+
}
456+
})
457+
458+
t.Run("Unexpected organization", func(t *testing.T) {
459+
unexpectedRemotePeer := remotePeer(comm3Port)
460+
unexpectedRemotePeer.PKIID = remotePeer(comm4Port).PKIID
461+
comm1.Send(createGossipMsg(), unexpectedRemotePeer)
462+
select {
463+
case <-messagesForComm3:
464+
assert.Fail(t, "Message shouldn't have been received")
465+
case <-time.After(time.Second * 5):
466+
}
467+
})
468+
}
469+
399470
func TestProdConstructor(t *testing.T) {
400471
t.Parallel()
401472
srv, lsnr, dialOpts, certs := createGRPCLayer(20000)
402473
defer srv.Stop()
403474
defer lsnr.Close()
404475
id := []byte("localhost:20000")
405-
comm1, _ := NewCommInstance(srv, certs, identity.NewIdentityMapper(naiveSec, id, noopPurgeIdentity, naiveSec), id, dialOpts)
476+
comm1, _ := NewCommInstance(srv, certs, identity.NewIdentityMapper(naiveSec, id, noopPurgeIdentity, naiveSec), id, dialOpts, naiveSec)
406477
go srv.Serve(lsnr)
407478

408479
srv, lsnr, dialOpts, certs = createGRPCLayer(30000)
409480
defer srv.Stop()
410481
defer lsnr.Close()
411482
id = []byte("localhost:30000")
412-
comm2, _ := NewCommInstance(srv, certs, identity.NewIdentityMapper(naiveSec, id, noopPurgeIdentity, naiveSec), id, dialOpts)
483+
comm2, _ := NewCommInstance(srv, certs, identity.NewIdentityMapper(naiveSec, id, noopPurgeIdentity, naiveSec), id, dialOpts, naiveSec)
413484
go srv.Serve(lsnr)
414485
defer comm1.Stop()
415486
defer comm2.Stop()

gossip/gossip/gossip_impl.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,16 @@ type gossipServiceImpl struct {
6464
}
6565

6666
// NewGossipService creates a gossip instance attached to a gRPC server
67-
func NewGossipService(conf *Config, s *grpc.Server, secAdvisor api.SecurityAdvisor,
67+
func NewGossipService(conf *Config, s *grpc.Server, sa api.SecurityAdvisor,
6868
mcs api.MessageCryptoService, selfIdentity api.PeerIdentityType,
6969
secureDialOpts api.PeerSecureDialOpts) Gossip {
7070
var err error
7171

7272
lgr := util.GetLogger(util.LoggingGossipModule, conf.ID)
7373

7474
g := &gossipServiceImpl{
75-
selfOrg: secAdvisor.OrgByPeerIdentity(selfIdentity),
76-
secAdvisor: secAdvisor,
75+
selfOrg: sa.OrgByPeerIdentity(selfIdentity),
76+
secAdvisor: sa,
7777
selfIdentity: selfIdentity,
7878
presumedDead: make(chan common.PKIidType, presumedDeadChanSize),
7979
disc: nil,
@@ -91,12 +91,12 @@ func NewGossipService(conf *Config, s *grpc.Server, secAdvisor api.SecurityAdvis
9191
g.idMapper = identity.NewIdentityMapper(mcs, selfIdentity, func(pkiID common.PKIidType, identity api.PeerIdentityType) {
9292
g.comm.CloseConn(&comm.RemotePeer{PKIID: pkiID})
9393
g.certPuller.Remove(string(pkiID))
94-
}, secAdvisor)
94+
}, sa)
9595

9696
if s == nil {
97-
g.comm, err = createCommWithServer(conf.BindPort, g.idMapper, selfIdentity, secureDialOpts)
97+
g.comm, err = createCommWithServer(conf.BindPort, g.idMapper, selfIdentity, secureDialOpts, sa)
9898
} else {
99-
g.comm, err = createCommWithoutServer(s, conf.TLSCerts, g.idMapper, selfIdentity, secureDialOpts)
99+
g.comm, err = createCommWithoutServer(s, conf.TLSCerts, g.idMapper, selfIdentity, secureDialOpts, sa)
100100
}
101101

102102
if err != nil {
@@ -159,8 +159,8 @@ func newChannelState(g *gossipServiceImpl) *channelState {
159159
}
160160

161161
func createCommWithoutServer(s *grpc.Server, certs *common.TLSCertificates, idStore identity.Mapper,
162-
identity api.PeerIdentityType, secureDialOpts api.PeerSecureDialOpts) (comm.Comm, error) {
163-
return comm.NewCommInstance(s, certs, idStore, identity, secureDialOpts)
162+
identity api.PeerIdentityType, secureDialOpts api.PeerSecureDialOpts, sa api.SecurityAdvisor) (comm.Comm, error) {
163+
return comm.NewCommInstance(s, certs, idStore, identity, secureDialOpts, sa)
164164
}
165165

166166
// NewGossipServiceWithServer creates a new gossip instance with a gRPC server
@@ -170,8 +170,8 @@ func NewGossipServiceWithServer(conf *Config, secAdvisor api.SecurityAdvisor, mc
170170
}
171171

172172
func createCommWithServer(port int, idStore identity.Mapper, identity api.PeerIdentityType,
173-
secureDialOpts api.PeerSecureDialOpts) (comm.Comm, error) {
174-
return comm.NewCommInstanceWithServer(port, idStore, identity, secureDialOpts)
173+
secureDialOpts api.PeerSecureDialOpts, sa api.SecurityAdvisor) (comm.Comm, error) {
174+
return comm.NewCommInstanceWithServer(port, idStore, identity, secureDialOpts, sa)
175175
}
176176

177177
func (g *gossipServiceImpl) toDie() bool {

gossip/mocks/security_advisor.go

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)