From c89ba6048c06ec9ace260733d862112da84f35bb Mon Sep 17 00:00:00 2001 From: Andrew Coleman Date: Mon, 21 Feb 2022 22:03:42 +0000 Subject: [PATCH] Gateway support for mutual TLS networks (#3235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 (cherry picked from commit 59835e66b1f92b042dfc15ac6ea5ec5186408cab) --- internal/peer/node/start.go | 1 + internal/pkg/gateway/api_test.go | 21 ++++++++-- internal/pkg/gateway/endpoint.go | 6 ++- internal/pkg/gateway/endpoint_test.go | 55 +++++++++++++++++++++++++++ internal/pkg/gateway/gateway.go | 15 ++++---- 5 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 internal/pkg/gateway/endpoint_test.go diff --git a/internal/peer/node/start.go b/internal/peer/node/start.go index 20bed3a2af1..2a1f38b93ff 100644 --- a/internal/peer/node/start.go +++ b/internal/peer/node/start.go @@ -825,6 +825,7 @@ func serve(args []string) error { serverEndorser, discoveryService, peerInstance, + &serverConfig.SecOpts, aclProvider, coreConfig.LocalMSPID, coreConfig.GatewayOptions, diff --git a/internal/pkg/gateway/api_test.go b/internal/pkg/gateway/api_test.go index 7f7a06ad181..77c4a9ad6d6 100644 --- a/internal/pkg/gateway/api_test.go +++ b/internal/pkg/gateway/api_test.go @@ -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" @@ -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() @@ -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) @@ -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 { @@ -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, } } diff --git a/internal/pkg/gateway/endpoint.go b/internal/pkg/gateway/endpoint.go index 74a92c056d1..6ccf0df2c30 100644 --- a/internal/pkg/gateway/endpoint.go +++ b/internal/pkg/gateway/endpoint.go @@ -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) { @@ -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, } diff --git a/internal/pkg/gateway/endpoint_test.go b/internal/pkg/gateway/endpoint_test.go new file mode 100644 index 00000000000..1c4bfad1f84 --- /dev/null +++ b/internal/pkg/gateway/endpoint_test.go @@ -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") +} diff --git a/internal/pkg/gateway/gateway.go b/internal/pkg/gateway/gateway.go index 484f09c243f..63ec47dbd85 100644 --- a/internal/pkg/gateway/gateway.go +++ b/internal/pkg/gateway/gateway.go @@ -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" @@ -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, } @@ -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, ) @@ -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: ®istry{ - 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{}, },