297 lines
10 KiB
Go
297 lines
10 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package peer_test
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
|
|
cb "github.com/hyperledger/fabric-protos-go/common"
|
|
mspproto "github.com/hyperledger/fabric-protos-go/msp"
|
|
pb "github.com/hyperledger/fabric-protos-go/peer"
|
|
configtxtest "github.com/hyperledger/fabric/common/configtx/test"
|
|
"github.com/hyperledger/fabric/common/crypto/tlsgen"
|
|
"github.com/hyperledger/fabric/core/ledger/mock"
|
|
"github.com/hyperledger/fabric/core/peer"
|
|
"github.com/hyperledger/fabric/internal/pkg/comm"
|
|
"github.com/hyperledger/fabric/internal/pkg/comm/testpb"
|
|
"github.com/hyperledger/fabric/internal/pkg/txflags"
|
|
"github.com/hyperledger/fabric/msp"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials"
|
|
)
|
|
|
|
// test server to be registered with the GRPCServer
|
|
type testServiceServer struct{}
|
|
|
|
func (tss *testServiceServer) EmptyCall(context.Context, *testpb.Empty) (*testpb.Empty, error) {
|
|
return new(testpb.Empty), nil
|
|
}
|
|
|
|
// createCertPool creates an x509.CertPool from an array of PEM-encoded certificates
|
|
func createCertPool(rootCAs [][]byte) (*x509.CertPool, error) {
|
|
certPool := x509.NewCertPool()
|
|
for _, rootCA := range rootCAs {
|
|
if !certPool.AppendCertsFromPEM(rootCA) {
|
|
return nil, errors.New("Failed to load root certificates")
|
|
}
|
|
}
|
|
return certPool, nil
|
|
}
|
|
|
|
// helper function to invoke the EmptyCall againt the test service
|
|
func invokeEmptyCall(address string, dialOptions []grpc.DialOption) (*testpb.Empty, error) {
|
|
// add DialOptions
|
|
dialOptions = append(dialOptions, grpc.WithBlock())
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
|
|
// create GRPC client conn
|
|
clientConn, err := grpc.DialContext(ctx, address, dialOptions...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer clientConn.Close()
|
|
|
|
// create GRPC client
|
|
client := testpb.NewTestServiceClient(clientConn)
|
|
|
|
// invoke service
|
|
empty, err := client.EmptyCall(context.Background(), new(testpb.Empty))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return empty, nil
|
|
}
|
|
|
|
// helper function to build an MSPConfig given root certs
|
|
func createMSPConfig(mspID string, rootCerts, tlsRootCerts, tlsIntermediateCerts [][]byte) (*mspproto.MSPConfig, error) {
|
|
fmspconf := &mspproto.FabricMSPConfig{
|
|
RootCerts: rootCerts,
|
|
TlsRootCerts: tlsRootCerts,
|
|
TlsIntermediateCerts: tlsIntermediateCerts,
|
|
Name: mspID,
|
|
FabricNodeOus: &mspproto.FabricNodeOUs{
|
|
Enable: true,
|
|
ClientOuIdentifier: &mspproto.FabricOUIdentifier{
|
|
OrganizationalUnitIdentifier: "client",
|
|
},
|
|
PeerOuIdentifier: &mspproto.FabricOUIdentifier{
|
|
OrganizationalUnitIdentifier: "peer",
|
|
},
|
|
AdminOuIdentifier: &mspproto.FabricOUIdentifier{
|
|
OrganizationalUnitIdentifier: "admin",
|
|
},
|
|
},
|
|
}
|
|
|
|
return &mspproto.MSPConfig{
|
|
Config: protoutil.MarshalOrPanic(fmspconf),
|
|
Type: int32(msp.FABRIC),
|
|
}, nil
|
|
}
|
|
|
|
func createConfigBlock(channelID string, appMSPConf, ordererMSPConf *mspproto.MSPConfig,
|
|
appOrgID, ordererOrgID string) (*cb.Block, error) {
|
|
block, err := configtxtest.MakeGenesisBlockFromMSPs(channelID, appMSPConf, ordererMSPConf, appOrgID, ordererOrgID)
|
|
if block == nil || err != nil {
|
|
return block, err
|
|
}
|
|
|
|
txsFilter := txflags.NewWithValues(len(block.Data.Data), pb.TxValidationCode_VALID)
|
|
block.Metadata.Metadata[cb.BlockMetadataIndex_TRANSACTIONS_FILTER] = txsFilter
|
|
|
|
return block, nil
|
|
}
|
|
|
|
func TestUpdateRootsFromConfigBlock(t *testing.T) {
|
|
org1CA, err := tlsgen.NewCA()
|
|
require.NoError(t, err)
|
|
org1Server1KeyPair, err := org1CA.NewServerCertKeyPair("localhost", "127.0.0.1", "::1")
|
|
require.NoError(t, err)
|
|
|
|
org2CA, err := tlsgen.NewCA()
|
|
require.NoError(t, err)
|
|
org2Server1KeyPair, err := org2CA.NewServerCertKeyPair("localhost", "127.0.0.1", "::1")
|
|
require.NoError(t, err)
|
|
|
|
org2IntermediateCA, err := org2CA.NewIntermediateCA()
|
|
require.NoError(t, err)
|
|
org2IntermediateServer1KeyPair, err := org2IntermediateCA.NewServerCertKeyPair("localhost", "127.0.0.1", "::1")
|
|
require.NoError(t, err)
|
|
|
|
ordererOrgCA, err := tlsgen.NewCA()
|
|
require.NoError(t, err)
|
|
ordererOrgServer1KeyPair, err := ordererOrgCA.NewServerCertKeyPair("localhost", "127.0.0.1", "::1")
|
|
require.NoError(t, err)
|
|
|
|
// create test MSPConfigs
|
|
org1MSPConf, err := createMSPConfig("Org1MSP", [][]byte{org2CA.CertBytes()}, [][]byte{org1CA.CertBytes()}, [][]byte{})
|
|
require.NoError(t, err)
|
|
org2MSPConf, err := createMSPConfig("Org2MSP", [][]byte{org1CA.CertBytes()}, [][]byte{org2CA.CertBytes()}, [][]byte{})
|
|
require.NoError(t, err)
|
|
org2IntermediateMSPConf, err := createMSPConfig("Org2IntermediateMSP", [][]byte{org1CA.CertBytes()}, [][]byte{org2CA.CertBytes()}, [][]byte{org2IntermediateCA.CertBytes()})
|
|
require.NoError(t, err)
|
|
ordererOrgMSPConf, err := createMSPConfig("OrdererOrgMSP", [][]byte{org1CA.CertBytes()}, [][]byte{ordererOrgCA.CertBytes()}, [][]byte{})
|
|
require.NoError(t, err)
|
|
|
|
// create test channel create blocks
|
|
channel1Block, err := createConfigBlock("channel1", org1MSPConf, ordererOrgMSPConf, "Org1MSP", "OrdererOrgMSP")
|
|
require.NoError(t, err)
|
|
channel2Block, err := createConfigBlock("channel2", org2MSPConf, ordererOrgMSPConf, "Org2MSP", "OrdererOrgMSP")
|
|
require.NoError(t, err)
|
|
channel3Block, err := createConfigBlock("channel3", org2IntermediateMSPConf, ordererOrgMSPConf, "Org2IntermediateMSP", "OrdererOrgMSP")
|
|
require.NoError(t, err)
|
|
|
|
serverConfig := comm.ServerConfig{
|
|
SecOpts: comm.SecureOptions{
|
|
UseTLS: true,
|
|
Certificate: org1Server1KeyPair.Cert,
|
|
Key: org1Server1KeyPair.Key,
|
|
ServerRootCAs: [][]byte{org1CA.CertBytes()},
|
|
RequireClientCert: true,
|
|
},
|
|
}
|
|
|
|
peerInstance, cleanup := peer.NewTestPeer(t)
|
|
defer cleanup()
|
|
peerInstance.CredentialSupport = comm.NewCredentialSupport()
|
|
|
|
createChannel := func(t *testing.T, cid string, block *cb.Block) {
|
|
err = peerInstance.CreateChannel(cid, block, &mock.DeployedChaincodeInfoProvider{}, nil, nil)
|
|
require.NoError(t, err, "failed to create channel from block")
|
|
t.Logf("Channel %s MSPIDs: (%s)", cid, peerInstance.GetMSPIDs(cid))
|
|
}
|
|
|
|
org1CertPool, err := createCertPool([][]byte{org1CA.CertBytes()})
|
|
require.NoError(t, err)
|
|
|
|
// use server cert as client cert
|
|
org1ClientCert, err := tls.X509KeyPair(org1Server1KeyPair.Cert, org1Server1KeyPair.Key)
|
|
require.NoError(t, err)
|
|
|
|
org1Creds := credentials.NewTLS(&tls.Config{
|
|
Certificates: []tls.Certificate{org1ClientCert},
|
|
RootCAs: org1CertPool,
|
|
})
|
|
|
|
org2ClientCert, err := tls.X509KeyPair(org2Server1KeyPair.Cert, org2Server1KeyPair.Key)
|
|
require.NoError(t, err)
|
|
org2Creds := credentials.NewTLS(&tls.Config{
|
|
Certificates: []tls.Certificate{org2ClientCert},
|
|
RootCAs: org1CertPool,
|
|
})
|
|
|
|
org2IntermediateClientCert, err := tls.X509KeyPair(org2IntermediateServer1KeyPair.Cert, org2IntermediateServer1KeyPair.Key)
|
|
require.NoError(t, err)
|
|
org2IntermediateCreds := credentials.NewTLS(&tls.Config{
|
|
Certificates: []tls.Certificate{org2IntermediateClientCert},
|
|
RootCAs: org1CertPool,
|
|
})
|
|
|
|
ordererOrgClientCert, err := tls.X509KeyPair(ordererOrgServer1KeyPair.Cert, ordererOrgServer1KeyPair.Key)
|
|
require.NoError(t, err)
|
|
|
|
ordererOrgCreds := credentials.NewTLS(&tls.Config{
|
|
Certificates: []tls.Certificate{ordererOrgClientCert},
|
|
RootCAs: org1CertPool,
|
|
})
|
|
|
|
// basic function tests
|
|
tests := []struct {
|
|
name string
|
|
serverConfig comm.ServerConfig
|
|
createChannel func(*testing.T)
|
|
goodOptions []grpc.DialOption
|
|
badOptions []grpc.DialOption
|
|
numAppCAs int
|
|
numOrdererCAs int
|
|
}{
|
|
{
|
|
name: "MutualTLSOrg1Org1",
|
|
serverConfig: serverConfig,
|
|
createChannel: func(t *testing.T) { createChannel(t, "channel1", channel1Block) },
|
|
goodOptions: []grpc.DialOption{grpc.WithTransportCredentials(org1Creds)},
|
|
badOptions: []grpc.DialOption{grpc.WithTransportCredentials(ordererOrgCreds)},
|
|
numAppCAs: 3, // each channel also has a DEFAULT MSP
|
|
numOrdererCAs: 1,
|
|
},
|
|
{
|
|
name: "MutualTLSOrg1Org2",
|
|
serverConfig: serverConfig,
|
|
createChannel: func(t *testing.T) { createChannel(t, "channel2", channel2Block) },
|
|
goodOptions: []grpc.DialOption{
|
|
grpc.WithTransportCredentials(org2Creds),
|
|
},
|
|
badOptions: []grpc.DialOption{
|
|
grpc.WithTransportCredentials(ordererOrgCreds),
|
|
},
|
|
numAppCAs: 6,
|
|
numOrdererCAs: 2,
|
|
},
|
|
{
|
|
name: "MutualTLSOrg1Org2Intermediate",
|
|
serverConfig: serverConfig,
|
|
createChannel: func(t *testing.T) { createChannel(t, "channel3", channel3Block) },
|
|
goodOptions: []grpc.DialOption{
|
|
grpc.WithTransportCredentials(org2IntermediateCreds),
|
|
},
|
|
badOptions: []grpc.DialOption{
|
|
grpc.WithTransportCredentials(ordererOrgCreds),
|
|
},
|
|
numAppCAs: 10,
|
|
numOrdererCAs: 3,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
t.Run(test.name, func(t *testing.T) {
|
|
server, err := comm.NewGRPCServer("localhost:0", test.serverConfig)
|
|
require.NoError(t, err, "failed to create gRPC server")
|
|
require.NotNil(t, server)
|
|
|
|
peerInstance.SetServer(server)
|
|
peerInstance.ServerConfig = test.serverConfig
|
|
|
|
// register a GRPC test service
|
|
testpb.RegisterTestServiceServer(server.Server(), &testServiceServer{})
|
|
go server.Start()
|
|
defer server.Stop()
|
|
|
|
// extract dynamic listen port
|
|
_, port, err := net.SplitHostPort(server.Listener().Addr().String())
|
|
require.NoError(t, err, "unable to extract listener port")
|
|
testAddress := "localhost:" + port
|
|
|
|
// invoke the EmptyCall service with good options but should fail
|
|
// until channel is created and root CAs are updated
|
|
_, err = invokeEmptyCall(testAddress, test.goodOptions)
|
|
require.Error(t, err, "Expected error invoking the EmptyCall service ")
|
|
|
|
// creating channel should update the trusted client roots
|
|
test.createChannel(t)
|
|
|
|
// invoke the EmptyCall service with good options
|
|
_, err = invokeEmptyCall(testAddress, test.goodOptions)
|
|
require.NoError(t, err, "Failed to invoke the EmptyCall service")
|
|
|
|
// invoke the EmptyCall service with bad options
|
|
_, err = invokeEmptyCall(testAddress, test.badOptions)
|
|
require.Error(t, err, "Expected error using bad dial options")
|
|
})
|
|
}
|
|
}
|