Skip to content

Commit

Permalink
Gateway support for mutual TLS networks (#3235)
Browse files Browse the repository at this point in the history
To support client authentication (mTLS), the gateway needs to pass the host peer’s client certificate when making connections to the other nodes in the network.
If client authentication is not enabled, then these certs will be ignored.

Signed-off-by: andrew-coleman <andrew_coleman@uk.ibm.com>
(cherry picked from commit 59835e6)
  • Loading branch information
andrew-coleman authored and denyeart committed Feb 23, 2022
1 parent 602f4c6 commit c89ba60
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 11 deletions.
1 change: 1 addition & 0 deletions internal/peer/node/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ func serve(args []string) error {
serverEndorser,
discoveryService,
peerInstance,
&serverConfig.SecOpts,
aclProvider,
coreConfig.LocalMSPID,
coreConfig.GatewayOptions,
Expand Down
21 changes: 18 additions & 3 deletions internal/pkg/gateway/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/hyperledger/fabric/gossip/api"
"github.com/hyperledger/fabric/gossip/common"
gdiscovery "github.com/hyperledger/fabric/gossip/discovery"
"github.com/hyperledger/fabric/internal/pkg/comm"
"github.com/hyperledger/fabric/internal/pkg/gateway/commit"
"github.com/hyperledger/fabric/internal/pkg/gateway/config"
"github.com/hyperledger/fabric/internal/pkg/gateway/mocks"
Expand Down Expand Up @@ -1764,9 +1765,12 @@ func TestNilArgs(t *testing.T) {
&mocks.CommitFinder{},
&mocks.ACLChecker{},
&mocks.LedgerProvider{},
common.PKIidType("id1"),
"localhost:7051",
gdiscovery.NetworkMember{
PKIid: common.PKIidType("id1"),
Endpoint: "localhost:7051",
},
"msp1",
&comm.SecureOptions{},
config.GetOptions(viper.New()),
)
ctx := context.Background()
Expand Down Expand Up @@ -1905,7 +1909,12 @@ func prepareTest(t *testing.T, tt *testDef) *preparedTest {
EndorsementTimeout: endorsementTimeout,
}

server := newServer(localEndorser, disc, mockFinder, mockPolicy, mockLedgerProvider, common.PKIidType("id1"), "localhost:7051", "msp1", options)
member := gdiscovery.NetworkMember{
PKIid: common.PKIidType("id1"),
Endpoint: "localhost:7051",
}

server := newServer(localEndorser, disc, mockFinder, mockPolicy, mockLedgerProvider, member, "msp1", &comm.SecureOptions{}, options)

dialer := &mocks.Dialer{}
dialer.Returns(nil, nil)
Expand Down Expand Up @@ -2100,6 +2109,10 @@ func createMockPeer(t *testing.T, endorser *endorserState) *dp.Peer {

func createEndpointFactory(t *testing.T, definition *endpointDef, dialer dialer) *endpointFactory {
var endpoint string
ca, err := tlsgen.NewCA()
require.NoError(t, err, "failed to create CA")
pair, err := ca.NewClientCertKeyPair()
require.NoError(t, err, "failed to create client key pair")
return &endpointFactory{
timeout: 5 * time.Second,
connectEndorser: func(conn *grpc.ClientConn) peer.EndorserClient {
Expand Down Expand Up @@ -2127,6 +2140,8 @@ func createEndpointFactory(t *testing.T, definition *endpointDef, dialer dialer)
endpoint = target
return dialer(ctx, target, opts...)
},
clientKey: pair.Key,
clientCert: pair.Cert,
}
}

Expand Down
6 changes: 5 additions & 1 deletion internal/pkg/gateway/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type endpointFactory struct {
connectEndorser endorserConnector
connectOrderer ordererConnector
dialer dialer
clientCert []byte
clientKey []byte
}

func (ef *endpointFactory) newEndorser(pkiid common.PKIidType, address, mspid string, tlsRootCerts [][]byte) (*endorser, error) {
Expand Down Expand Up @@ -97,7 +99,9 @@ func (ef *endpointFactory) newConnection(address string, tlsRootCerts [][]byte)
SecOpts: comm.SecureOptions{
UseTLS: len(tlsRootCerts) > 0,
ServerRootCAs: tlsRootCerts,
RequireClientCert: false,
RequireClientCert: true,
Certificate: ef.clientCert,
Key: ef.clientKey,
},
DialTimeout: ef.timeout,
}
Expand Down
55 changes: 55 additions & 0 deletions internal/pkg/gateway/endpoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package gateway

import (
"testing"
"time"

"github.com/hyperledger/fabric/common/crypto/tlsgen"
"github.com/hyperledger/fabric/gossip/common"
"github.com/hyperledger/fabric/internal/pkg/comm"
"github.com/stretchr/testify/require"
)

func TestMutualTLS(t *testing.T) {
ca, err := tlsgen.NewCA()
require.NoError(t, err, "failed to create CA")

serverPair, err := ca.NewServerCertKeyPair("127.0.0.1")
require.NoError(t, err, "failed to create server key pair")

clientPair, err := ca.NewClientCertKeyPair()
require.NoError(t, err, "failed to create client key pair")

rootTLSCert := ca.CertBytes()

server, err := comm.NewGRPCServer("127.0.0.1:0", comm.ServerConfig{
SecOpts: comm.SecureOptions{
UseTLS: true,
RequireClientCert: true,
Certificate: serverPair.Cert,
Key: serverPair.Key,
ClientRootCAs: [][]byte{rootTLSCert},
},
})
require.NoError(t, err)

go server.Start()
defer server.Stop()

factory := &endpointFactory{
timeout: 10 * time.Second,
clientCert: clientPair.Cert,
clientKey: clientPair.Key,
}

endorser, err := factory.newEndorser(common.PKIidType{}, server.Address(), "msp1", [][]byte{rootTLSCert})
require.NoError(t, err, "failed to make mTLS connection to server")

err = endorser.closeConnection()
require.NoError(t, err, "failed to close connection")
}
15 changes: 8 additions & 7 deletions internal/pkg/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import (
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/common/ledger"
"github.com/hyperledger/fabric/core/peer"
"github.com/hyperledger/fabric/gossip/common"
gdiscovery "github.com/hyperledger/fabric/gossip/discovery"
"github.com/hyperledger/fabric/internal/pkg/comm"
"github.com/hyperledger/fabric/internal/pkg/gateway/commit"
"github.com/hyperledger/fabric/internal/pkg/gateway/config"
"google.golang.org/grpc"
Expand Down Expand Up @@ -51,7 +52,7 @@ type LedgerProvider interface {
}

// CreateServer creates an embedded instance of the Gateway.
func CreateServer(localEndorser peerproto.EndorserServer, discovery Discovery, peerInstance *peer.Peer, policy ACLChecker, localMSPID string, options config.Options) *Server {
func CreateServer(localEndorser peerproto.EndorserServer, discovery Discovery, peerInstance *peer.Peer, secureOptions *comm.SecureOptions, policy ACLChecker, localMSPID string, options config.Options) *Server {
adapter := &peerAdapter{
Peer: peerInstance,
}
Expand All @@ -65,9 +66,9 @@ func CreateServer(localEndorser peerproto.EndorserServer, discovery Discovery, p
commit.NewFinder(adapter, notifier),
policy,
adapter,
peerInstance.GossipService.SelfMembershipInfo().PKIid,
peerInstance.GossipService.SelfMembershipInfo().Endpoint,
peerInstance.GossipService.SelfMembershipInfo(),
localMSPID,
secureOptions,
options,
)

Expand All @@ -76,13 +77,13 @@ func CreateServer(localEndorser peerproto.EndorserServer, discovery Discovery, p
return server
}

func newServer(localEndorser peerproto.EndorserClient, discovery Discovery, finder CommitFinder, policy ACLChecker, ledgerProvider LedgerProvider, localPKIID common.PKIidType, localEndpoint, localMSPID string, options config.Options) *Server {
func newServer(localEndorser peerproto.EndorserClient, discovery Discovery, finder CommitFinder, policy ACLChecker, ledgerProvider LedgerProvider, localInfo gdiscovery.NetworkMember, localMSPID string, secureOptions *comm.SecureOptions, options config.Options) *Server {
return &Server{
registry: &registry{
localEndorser: &endorser{client: localEndorser, endpointConfig: &endpointConfig{pkiid: localPKIID, address: localEndpoint, mspid: localMSPID}},
localEndorser: &endorser{client: localEndorser, endpointConfig: &endpointConfig{pkiid: localInfo.PKIid, address: localInfo.Endpoint, mspid: localMSPID}},
discovery: discovery,
logger: logger,
endpointFactory: &endpointFactory{timeout: options.DialTimeout},
endpointFactory: &endpointFactory{timeout: options.DialTimeout, clientCert: secureOptions.Certificate, clientKey: secureOptions.Key},
remoteEndorsers: map[string]*endorser{},
channelInitialized: map[string]bool{},
},
Expand Down

0 comments on commit c89ba60

Please sign in to comment.