go_study/fabric-main/orderer/common/server/main_test.go

1019 lines
31 KiB
Go

// Copyright IBM Corp. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package server
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"github.com/hyperledger/fabric-protos-go/common"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/bccsp/factory"
"github.com/hyperledger/fabric/bccsp/sw"
"github.com/hyperledger/fabric/common/channelconfig"
"github.com/hyperledger/fabric/common/crypto/tlsgen"
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/common/flogging/floggingtest"
"github.com/hyperledger/fabric/common/ledger/blockledger"
"github.com/hyperledger/fabric/common/ledger/blockledger/fileledger"
"github.com/hyperledger/fabric/common/metrics/disabled"
"github.com/hyperledger/fabric/common/metrics/prometheus"
"github.com/hyperledger/fabric/core/config/configtest"
"github.com/hyperledger/fabric/internal/configtxgen/encoder"
"github.com/hyperledger/fabric/internal/configtxgen/genesisconfig"
"github.com/hyperledger/fabric/internal/pkg/comm"
"github.com/hyperledger/fabric/internal/pkg/identity"
"github.com/hyperledger/fabric/orderer/common/cluster"
"github.com/hyperledger/fabric/orderer/common/filerepo"
"github.com/hyperledger/fabric/orderer/common/localconfig"
"github.com/hyperledger/fabric/orderer/common/multichannel"
server_mocks "github.com/hyperledger/fabric/orderer/common/server/mocks"
"github.com/hyperledger/fabric/orderer/consensus/etcdraft"
"github.com/hyperledger/fabric/protoutil"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
//go:generate counterfeiter -o mocks/signer_serializer.go --fake-name SignerSerializer . signerSerializer
type signerSerializer interface {
identity.SignerSerializer
}
// The path to cryptogen which can be used by tests to create certificates
var cryptogen string
func TestMain(m *testing.M) {
var err error
cryptogen, err = gexec.Build("github.com/hyperledger/fabric/cmd/cryptogen")
if err != nil {
fmt.Fprintf(os.Stderr, "cryptogen build failed: %v", err)
os.Exit(-1)
}
defer gexec.CleanupBuildArtifacts()
fmt.Println("TestMain built cryptogen")
os.Exit(m.Run())
}
func copyYamlFiles(src, dst string) {
for _, file := range []string{"configtx.yaml", "examplecom-config.yaml", "orderer.yaml"} {
fileBytes, err := ioutil.ReadFile(filepath.Join(src, file))
if err != nil {
os.Exit(-1)
}
err = ioutil.WriteFile(filepath.Join(dst, file), fileBytes, 0o644)
if err != nil {
os.Exit(-1)
}
}
}
func TestInitializeLogging(t *testing.T) {
t.Setenv("FABRIC_LOGGING_SPEC", "foo=debug")
initializeLogging()
require.Equal(t, "debug", flogging.LoggerLevel("foo"))
}
func TestInitializeProfilingService(t *testing.T) {
t.Setenv("FABRIC_LOGGING_SPEC", "debug")
// get a free random port
listenAddr := func() string {
l, _ := net.Listen("tcp", "localhost:0")
l.Close()
return l.Addr().String()
}()
go initializeProfilingService(
&localconfig.TopLevel{
General: localconfig.General{
Profile: localconfig.Profile{
Enabled: true,
Address: listenAddr,
},
},
},
)
time.Sleep(500 * time.Millisecond)
if _, err := http.Get("http://" + listenAddr + "/" + "/debug/"); err != nil {
t.Logf("Expected pprof to be up (will retry again in 3 seconds): %s", err)
time.Sleep(3 * time.Second)
if _, err := http.Get("http://" + listenAddr + "/" + "/debug/"); err != nil {
t.Fatalf("Expected pprof to be up: %s", err)
}
}
}
func TestInitializeServerConfig(t *testing.T) {
conf := &localconfig.TopLevel{
General: localconfig.General{
ConnectionTimeout: 7 * time.Second,
TLS: localconfig.TLS{
Enabled: true,
ClientAuthRequired: true,
Certificate: "main.go",
PrivateKey: "main.go",
RootCAs: []string{"main.go"},
ClientRootCAs: []string{"main.go"},
},
},
}
sc := initializeServerConfig(conf, nil)
expectedContent, _ := ioutil.ReadFile("main.go")
require.Equal(t, expectedContent, sc.SecOpts.Certificate)
require.Equal(t, expectedContent, sc.SecOpts.Key)
require.Equal(t, [][]byte{expectedContent}, sc.SecOpts.ServerRootCAs)
require.Equal(t, [][]byte{expectedContent}, sc.SecOpts.ClientRootCAs)
sc = initializeServerConfig(conf, nil)
defaultOpts := comm.DefaultKeepaliveOptions
require.Equal(t, defaultOpts.ServerMinInterval, sc.KaOpts.ServerMinInterval)
require.Equal(t, time.Duration(0), sc.KaOpts.ServerInterval)
require.Equal(t, time.Duration(0), sc.KaOpts.ServerTimeout)
require.Equal(t, 7*time.Second, sc.ConnectionTimeout)
testDuration := 10 * time.Second
conf.General.Keepalive = localconfig.Keepalive{
ServerMinInterval: testDuration,
ServerInterval: testDuration,
ServerTimeout: testDuration,
}
sc = initializeServerConfig(conf, nil)
require.Equal(t, testDuration, sc.KaOpts.ServerMinInterval)
require.Equal(t, testDuration, sc.KaOpts.ServerInterval)
require.Equal(t, testDuration, sc.KaOpts.ServerTimeout)
sc = initializeServerConfig(conf, nil)
require.NotNil(t, sc.Logger)
require.Equal(t, comm.NewServerStatsHandler(&disabled.Provider{}), sc.ServerStatsHandler)
require.Len(t, sc.UnaryInterceptors, 2)
require.Len(t, sc.StreamInterceptors, 2)
sc = initializeServerConfig(conf, &prometheus.Provider{})
require.NotNil(t, sc.ServerStatsHandler)
goodFile := "main.go"
badFile := "does_not_exist"
oldLogger := logger
defer func() { logger = oldLogger }()
logger, _ = floggingtest.NewTestLogger(t)
testCases := []struct {
name string
certificate string
privateKey string
rootCA string
clientRootCert string
clusterCert string
clusterKey string
clusterCA string
isCluster bool
expectedPanic string
}{
{
name: "BadCertificate",
certificate: badFile,
privateKey: goodFile,
rootCA: goodFile,
clientRootCert: goodFile,
expectedPanic: "Failed to load server Certificate file 'does_not_exist' (open does_not_exist: no such file or directory)",
},
{
name: "BadPrivateKey",
certificate: goodFile,
privateKey: badFile,
rootCA: goodFile,
clientRootCert: goodFile,
expectedPanic: "Failed to load PrivateKey file 'does_not_exist' (open does_not_exist: no such file or directory)",
},
{
name: "BadRootCA",
certificate: goodFile,
privateKey: goodFile,
rootCA: badFile,
clientRootCert: goodFile,
expectedPanic: "Failed to load ServerRootCAs file 'open does_not_exist: no such file or directory' (does_not_exist)",
},
{
name: "BadClientRootCertificate",
certificate: goodFile,
privateKey: goodFile,
rootCA: goodFile,
clientRootCert: badFile,
expectedPanic: "Failed to load ClientRootCAs file 'open does_not_exist: no such file or directory' (does_not_exist)",
},
{
name: "BadCertificate - cluster reuses server config",
certificate: badFile,
privateKey: goodFile,
rootCA: goodFile,
clientRootCert: goodFile,
clusterCert: "",
clusterKey: "",
clusterCA: "",
isCluster: true,
expectedPanic: "Failed to load client TLS certificate file 'does_not_exist' (open does_not_exist: no such file or directory)",
},
{
name: "BadPrivateKey - cluster reuses server config",
certificate: goodFile,
privateKey: badFile,
rootCA: goodFile,
clientRootCert: goodFile,
clusterCert: "",
clusterKey: "",
clusterCA: "",
isCluster: true,
expectedPanic: "Failed to load client TLS key file 'does_not_exist' (open does_not_exist: no such file or directory)",
},
{
name: "BadRootCA - cluster reuses server config",
certificate: goodFile,
privateKey: goodFile,
rootCA: badFile,
clientRootCert: goodFile,
clusterCert: "",
clusterKey: "",
clusterCA: "",
isCluster: true,
expectedPanic: "Failed to load ServerRootCAs file '' (open : no such file or directory)",
},
{
name: "ClusterBadCertificate",
certificate: goodFile,
privateKey: goodFile,
rootCA: goodFile,
clientRootCert: goodFile,
clusterCert: badFile,
clusterKey: goodFile,
clusterCA: goodFile,
isCluster: true,
expectedPanic: "Failed to load client TLS certificate file 'does_not_exist' (open does_not_exist: no such file or directory)",
},
{
name: "ClusterBadPrivateKey",
certificate: goodFile,
privateKey: goodFile,
rootCA: goodFile,
clientRootCert: goodFile,
clusterCert: goodFile,
clusterKey: badFile,
clusterCA: goodFile,
isCluster: true,
expectedPanic: "Failed to load client TLS key file 'does_not_exist' (open does_not_exist: no such file or directory)",
},
{
name: "ClusterBadRootCA",
certificate: goodFile,
privateKey: goodFile,
rootCA: goodFile,
clientRootCert: goodFile,
clusterCert: goodFile,
clusterKey: goodFile,
clusterCA: badFile,
isCluster: true,
expectedPanic: "Failed to load ServerRootCAs file 'does_not_exist' (open does_not_exist: no such file or directory)",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
conf := &localconfig.TopLevel{
General: localconfig.General{
TLS: localconfig.TLS{
Enabled: true,
ClientAuthRequired: true,
Certificate: tc.certificate,
PrivateKey: tc.privateKey,
RootCAs: []string{tc.rootCA},
ClientRootCAs: []string{tc.clientRootCert},
},
Cluster: localconfig.Cluster{
ClientCertificate: tc.clusterCert,
ClientPrivateKey: tc.clusterKey,
RootCAs: []string{tc.clusterCA},
},
},
}
require.PanicsWithValue(t, tc.expectedPanic, func() {
if !tc.isCluster {
initializeServerConfig(conf, nil)
} else {
initializeClusterClientConfig(conf)
}
},
)
})
}
}
func TestVerifyNoSystemChannelJoinBlock(t *testing.T) {
configtest.SetDevFabricConfigPath(t)
tmpDir := t.TempDir()
copyYamlFiles("testdata", tmpDir)
cryptoPath := generateCryptoMaterials(t, cryptogen, tmpDir)
t.Logf("Generated crypto material to: %s", cryptoPath)
genesisFile, _ := produceGenesisFileEtcdRaft(t, "testchannelid", tmpDir)
var (
config *localconfig.TopLevel
cryptoProvider bccsp.BCCSP
fileRepo *filerepo.Repo
genesisBytes []byte
)
setup := func() {
var err error
fileLedgerLocation := t.TempDir()
config = &localconfig.TopLevel{
General: localconfig.General{
BootstrapMethod: "none",
},
FileLedger: localconfig.FileLedger{
Location: fileLedgerLocation,
},
ChannelParticipation: localconfig.ChannelParticipation{Enabled: true},
}
cryptoProvider, err = sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
require.NoError(t, err)
fileRepo, err = multichannel.InitJoinBlockFileRepo(config)
require.NoError(t, err)
require.NotNil(t, fileRepo)
genesisBytes, err = ioutil.ReadFile(genesisFile)
require.NoError(t, err)
require.NotNil(t, genesisBytes)
}
t.Run("No join-block", func(t *testing.T) {
setup()
verifyNoSystemChannelJoinBlock(config, cryptoProvider)
})
t.Run("With genesis join-block", func(t *testing.T) {
setup()
err := fileRepo.Save("testchannelid", genesisBytes)
require.NoError(t, err)
require.Panics(t, func() { verifyNoSystemChannelJoinBlock(config, cryptoProvider) })
})
t.Run("With non-genesis join-block", func(t *testing.T) {
setup()
block := protoutil.UnmarshalBlockOrPanic(genesisBytes)
block.Header.Number = 7
configBlockBytes := protoutil.MarshalOrPanic(block)
err := fileRepo.Save("testchannelid", configBlockBytes)
require.NoError(t, err)
require.Panics(t, func() { verifyNoSystemChannelJoinBlock(config, cryptoProvider) })
})
}
func TestVerifyNoSystemChannel(t *testing.T) {
cryptoProvider, _ := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
tmpdir := t.TempDir()
rlf, err := fileledger.New(tmpdir, &disabled.Provider{})
require.NoError(t, err)
// no ledgers
verifyNoSystemChannel(rlf, cryptoProvider)
// skipping empty ledgers
_, err = rlf.GetOrCreate("emptychannelid")
require.NoError(t, err)
verifyNoSystemChannel(rlf, cryptoProvider)
// skipping app channel
conf := genesisconfig.Load(genesisconfig.SampleInsecureSoloProfile, configtest.GetDevConfigDir())
conf.Consortiums = nil
configBlock := encoder.New(conf).GenesisBlock()
rl, err := rlf.GetOrCreate("appchannelid")
require.NoError(t, err)
err = rl.Append(configBlock)
require.NoError(t, err)
verifyNoSystemChannel(rlf, cryptoProvider)
// detecting system channel genesis and panicking
conf = genesisconfig.Load(genesisconfig.SampleInsecureSoloProfile, configtest.GetDevConfigDir())
configBlock = encoder.New(conf).GenesisBlock()
rl, err = rlf.GetOrCreate("testchannelid")
require.NoError(t, err)
err = rl.Append(configBlock)
require.NoError(t, err)
require.Panics(t, func() { verifyNoSystemChannel(rlf, cryptoProvider) })
// Make and append the next config block, detecting system channel and panicking
prevHash := protoutil.BlockHeaderHash(configBlock.Header)
configBlock.Header.Number = 1
configBlock.Header.PreviousHash = prevHash
configBlock.Metadata.Metadata[common.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(&common.Metadata{
Value: protoutil.MarshalOrPanic(&common.OrdererBlockMetadata{
LastConfig: &common.LastConfig{Index: rl.Height()},
}),
})
configBlock.Metadata.Metadata[common.BlockMetadataIndex_LAST_CONFIG] = protoutil.MarshalOrPanic(&common.Metadata{
Value: protoutil.MarshalOrPanic(&common.LastConfig{Index: rl.Height()}),
})
err = rl.Append(configBlock)
require.NoError(t, err)
require.Panics(t, func() { verifyNoSystemChannel(rlf, cryptoProvider) })
}
func TestLoadLocalMSP(t *testing.T) {
t.Run("Happy", func(t *testing.T) {
localMSPDir := configtest.GetDevMspDir()
localMSP := loadLocalMSP(
&localconfig.TopLevel{
General: localconfig.General{
LocalMSPDir: localMSPDir,
LocalMSPID: "SampleOrg",
BCCSP: &factory.FactoryOpts{
Default: "SW",
SW: &factory.SwOpts{
Hash: "SHA2",
Security: 256,
},
},
},
},
)
require.NotNil(t, localMSP)
id, err := localMSP.GetIdentifier()
require.NoError(t, err)
require.Equal(t, id, "SampleOrg")
})
t.Run("Error", func(t *testing.T) {
oldLogger := logger
defer func() { logger = oldLogger }()
logger, _ = floggingtest.NewTestLogger(t)
require.Panics(t, func() {
loadLocalMSP(
&localconfig.TopLevel{
General: localconfig.General{
LocalMSPDir: "",
LocalMSPID: "",
},
},
)
})
})
}
func TestInitializeMultichannelRegistrar(t *testing.T) {
configtest.SetDevFabricConfigPath(t)
tmpDir := t.TempDir()
copyYamlFiles("testdata", tmpDir)
cryptoPath := generateCryptoMaterials(t, cryptogen, tmpDir)
t.Logf("Generated crypto material to: %s", cryptoPath)
genesisFile, _ := produceGenesisFileEtcdRaftAppChannel(t, "testchannelid", tmpDir)
cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
require.NoError(t, err)
signer := &server_mocks.SignerSerializer{}
t.Run("registrar without a system channel", func(t *testing.T) {
conf := genesisConfig(t, genesisFile)
srv, err := comm.NewGRPCServer("127.0.0.1:0", comm.ServerConfig{})
require.NoError(t, err)
lf, err := createLedgerFactory(conf, &disabled.Provider{})
require.NoError(t, err)
registrar := initializeMultichannelRegistrar(&cluster.PredicateDialer{}, comm.ServerConfig{}, srv, conf, signer, &disabled.Provider{}, lf, cryptoProvider)
require.NotNil(t, registrar)
})
}
func TestInitializeGrpcServer(t *testing.T) {
// get a free random port
listenAddr := func() string {
l, _ := net.Listen("tcp", "localhost:0")
l.Close()
return l.Addr().String()
}()
host := strings.Split(listenAddr, ":")[0]
port, _ := strconv.ParseUint(strings.Split(listenAddr, ":")[1], 10, 16)
conf := &localconfig.TopLevel{
General: localconfig.General{
ListenAddress: host,
ListenPort: uint16(port),
TLS: localconfig.TLS{
Enabled: false,
ClientAuthRequired: false,
},
},
}
require.NotPanics(t, func() {
grpcServer := initializeGrpcServer(conf, initializeServerConfig(conf, nil))
grpcServer.Listener().Close()
})
}
// generateCryptoMaterials uses cryptogen to generate the necessary
// MSP files and TLS certificates
func generateCryptoMaterials(t *testing.T, cryptogen, tmpDir string) string {
gt := NewGomegaWithT(t)
cryptoPath := filepath.Join(tmpDir, "crypto")
cmd := exec.Command(
cryptogen,
"generate",
"--config", filepath.Join(tmpDir, "examplecom-config.yaml"),
"--output", cryptoPath,
)
cryptogenProcess, err := gexec.Start(cmd, nil, nil)
gt.Expect(err).NotTo(HaveOccurred())
gt.Eventually(cryptogenProcess, time.Minute).Should(gexec.Exit(0))
return cryptoPath
}
func TestUpdateTrustedRoots(t *testing.T) {
configtest.SetDevFabricConfigPath(t)
tmpDir := t.TempDir()
copyYamlFiles("testdata", tmpDir)
cryptoPath := generateCryptoMaterials(t, cryptogen, tmpDir)
t.Logf("Generated crypto material to: %s", cryptoPath)
// get a free random port
listenAddr := func() string {
l, _ := net.Listen("tcp", "localhost:0")
l.Close()
return l.Addr().String()
}()
port, _ := strconv.ParseUint(strings.Split(listenAddr, ":")[1], 10, 16)
ledgerDir := path.Join(tmpDir, "ledger-dir")
conf := &localconfig.TopLevel{
General: localconfig.General{
BootstrapMethod: "none",
ListenAddress: "localhost",
ListenPort: uint16(port),
TLS: localconfig.TLS{
Enabled: false,
ClientAuthRequired: false,
},
},
FileLedger: localconfig.FileLedger{
Location: ledgerDir,
},
Consensus: etcdraft.Config{
WALDir: path.Join(tmpDir, "etcdraft", "wal"),
SnapDir: path.Join(tmpDir, "etcdraft", "snap"),
},
}
grpcServer := initializeGrpcServer(conf, initializeServerConfig(conf, nil))
caMgr := &caManager{
appRootCAsByChain: make(map[string][][]byte),
ordererRootCAsByChain: make(map[string][][]byte),
}
callback := func(bundle *channelconfig.Bundle) {
if grpcServer.MutualTLSRequired() {
t.Log("callback called")
caMgr.updateTrustedRoots(bundle, grpcServer)
}
}
lf, err := createLedgerFactory(conf, &disabled.Provider{})
require.NoError(t, err)
genesisFile, serverCert := produceGenesisFileEtcdRaftAppChannel(t, "testchannelid", tmpDir)
blockBytes, err := os.ReadFile(genesisFile)
require.NoError(t, err)
genesisBlock := protoutil.UnmarshalBlockOrPanic(blockBytes)
initializeAppChannel(genesisBlock, lf)
signer := &server_mocks.SignerSerializer{}
cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
require.NoError(t, err)
srvConf := comm.ServerConfig{
SecOpts: comm.SecureOptions{
Certificate: serverCert,
},
}
r := initializeMultichannelRegistrar(&cluster.PredicateDialer{}, srvConf, grpcServer, conf, signer, &disabled.Provider{}, lf, cryptoProvider, callback)
t.Logf("# app CAs: %d", len(caMgr.appRootCAsByChain["testchannelid"]))
t.Logf("# orderer CAs: %d", len(caMgr.ordererRootCAsByChain["testchannelid"]))
// mutual TLS not required so no updates should have occurred
require.Equal(t, 0, len(caMgr.appRootCAsByChain["testchannelid"]))
require.Equal(t, 0, len(caMgr.ordererRootCAsByChain["testchannelid"]))
grpcServer.Listener().Close()
cs := r.GetChain("testchannelid")
cs.Halt()
conf = &localconfig.TopLevel{
General: localconfig.General{
BootstrapMethod: "none",
ListenAddress: "localhost",
ListenPort: uint16(port),
TLS: localconfig.TLS{
Enabled: true,
ClientAuthRequired: true,
PrivateKey: filepath.Join(cryptoPath, "ordererOrganizations", "example.com", "orderers", "127.0.0.1.example.com", "tls", "server.key"),
Certificate: filepath.Join(cryptoPath, "ordererOrganizations", "example.com", "orderers", "127.0.0.1.example.com", "tls", "server.crt"),
},
},
FileLedger: localconfig.FileLedger{
Location: ledgerDir,
},
Consensus: etcdraft.Config{
WALDir: path.Join(tmpDir, "etcdraft", "wal"),
SnapDir: path.Join(tmpDir, "etcdraft", "snap"),
},
}
grpcServer = initializeGrpcServer(conf, initializeServerConfig(conf, nil))
caMgr = &caManager{
appRootCAsByChain: make(map[string][][]byte),
ordererRootCAsByChain: make(map[string][][]byte),
}
clusterConf, _ := initializeClusterClientConfig(conf)
predDialer := &cluster.PredicateDialer{
Config: clusterConf,
}
callback = func(bundle *channelconfig.Bundle) {
if grpcServer.MutualTLSRequired() {
t.Log("callback called")
caMgr.updateTrustedRoots(bundle, grpcServer)
caMgr.updateClusterDialer(predDialer, clusterConf.SecOpts.ServerRootCAs)
}
}
r = initializeMultichannelRegistrar(predDialer, srvConf, grpcServer, conf, signer, &disabled.Provider{}, lf, cryptoProvider, callback)
t.Logf("# app CAs: %d", len(caMgr.appRootCAsByChain["testchannelid"]))
t.Logf("# orderer CAs: %d", len(caMgr.ordererRootCAsByChain["testchannelid"]))
// mutual TLS is required so updates should have occurred
// we do not expect an intermediate CA, only root CA for apps and orderers
require.Equal(t, 1, len(caMgr.appRootCAsByChain["testchannelid"]))
require.Equal(t, 1, len(caMgr.ordererRootCAsByChain["testchannelid"]))
require.Len(t, predDialer.Config.SecOpts.ServerRootCAs, 1)
grpcServer.Listener().Close()
cs = r.GetChain("testchannelid")
cs.Halt()
}
func TestRootServerCertAggregation(t *testing.T) {
caMgr := &caManager{
appRootCAsByChain: make(map[string][][]byte),
ordererRootCAsByChain: make(map[string][][]byte),
}
predDialer := &cluster.PredicateDialer{
Config: comm.ClientConfig{},
}
ca1, err := tlsgen.NewCA()
require.NoError(t, err)
ca2, err := tlsgen.NewCA()
require.NoError(t, err)
caMgr.ordererRootCAsByChain["foo"] = [][]byte{ca1.CertBytes()}
caMgr.ordererRootCAsByChain["bar"] = [][]byte{ca1.CertBytes()}
caMgr.updateClusterDialer(predDialer, [][]byte{ca2.CertBytes(), ca2.CertBytes(), ca2.CertBytes()})
require.Len(t, predDialer.Config.SecOpts.ServerRootCAs, 2)
require.Contains(t, predDialer.Config.SecOpts.ServerRootCAs, ca1.CertBytes())
require.Contains(t, predDialer.Config.SecOpts.ServerRootCAs, ca2.CertBytes())
}
func TestConfigureClusterListener(t *testing.T) {
logEntries := make(chan string, 100)
allocatePort := func() uint16 {
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
_, portStr, err := net.SplitHostPort(l.Addr().String())
require.NoError(t, err)
port, err := strconv.ParseInt(portStr, 10, 64)
require.NoError(t, err)
require.NoError(t, l.Close())
t.Log("picked unused port", port)
return uint16(port)
}
unUsedPort := allocatePort()
backupLogger := logger
logger = logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
logEntries <- entry.Message
return nil
}))
defer func() {
logger = backupLogger
}()
ca, err := tlsgen.NewCA()
require.NoError(t, err)
serverKeyPair, err := ca.NewServerCertKeyPair("127.0.0.1")
require.NoError(t, err)
loadPEM := func(fileName string) ([]byte, error) {
switch fileName {
case "cert":
return serverKeyPair.Cert, nil
case "key":
return serverKeyPair.Key, nil
case "ca":
return ca.CertBytes(), nil
default:
return nil, errors.New("I/O error")
}
}
for _, testCase := range []struct {
name string
conf *localconfig.TopLevel
generalConf comm.ServerConfig
generalSrv *comm.GRPCServer
shouldBeEqual bool
expectedPanic string
expectedLogEntries []string
}{
{
name: "invalid certificate",
generalConf: comm.ServerConfig{},
conf: &localconfig.TopLevel{
General: localconfig.General{
Cluster: localconfig.Cluster{
ListenAddress: "127.0.0.1",
ListenPort: 5000,
ServerPrivateKey: "key",
ServerCertificate: "bad",
RootCAs: []string{"ca"},
},
},
},
expectedPanic: "Failed to load cluster server certificate from 'bad' (I/O error)",
generalSrv: &comm.GRPCServer{},
expectedLogEntries: []string{"Failed to load cluster server certificate from 'bad' (I/O error)"},
},
{
name: "invalid key",
generalConf: comm.ServerConfig{},
conf: &localconfig.TopLevel{
General: localconfig.General{
Cluster: localconfig.Cluster{
ListenAddress: "127.0.0.1",
ListenPort: 5000,
ServerPrivateKey: "bad",
ServerCertificate: "cert",
RootCAs: []string{"ca"},
},
},
},
expectedPanic: "Failed to load cluster server key from 'bad' (I/O error)",
generalSrv: &comm.GRPCServer{},
expectedLogEntries: []string{"Failed to load cluster server key from 'bad' (I/O error)"},
},
{
name: "invalid ca cert",
generalConf: comm.ServerConfig{},
conf: &localconfig.TopLevel{
General: localconfig.General{
Cluster: localconfig.Cluster{
ListenAddress: "127.0.0.1",
ListenPort: 5000,
ServerPrivateKey: "key",
ServerCertificate: "cert",
RootCAs: []string{"bad"},
},
},
},
expectedPanic: "Failed to load CA cert file 'bad' (I/O error)",
generalSrv: &comm.GRPCServer{},
expectedLogEntries: []string{"Failed to load CA cert file 'bad' (I/O error)"},
},
{
name: "bad listen address",
generalConf: comm.ServerConfig{},
conf: &localconfig.TopLevel{
General: localconfig.General{
Cluster: localconfig.Cluster{
ListenAddress: "99.99.99.99",
ListenPort: unUsedPort,
ServerPrivateKey: "key",
ServerCertificate: "cert",
RootCAs: []string{"ca"},
},
},
},
expectedPanic: fmt.Sprintf("Failed creating gRPC server on 99.99.99.99:%d due "+
"to listen tcp 99.99.99.99:%d:", unUsedPort, unUsedPort),
generalSrv: &comm.GRPCServer{},
},
{
name: "green path",
generalConf: comm.ServerConfig{},
conf: &localconfig.TopLevel{
General: localconfig.General{
Cluster: localconfig.Cluster{
ListenAddress: "127.0.0.1",
ListenPort: 5000,
ServerPrivateKey: "key",
ServerCertificate: "cert",
RootCAs: []string{"ca"},
},
},
},
generalSrv: &comm.GRPCServer{},
},
} {
t.Run(testCase.name, func(t *testing.T) {
if testCase.shouldBeEqual {
conf, srv := configureClusterListener(testCase.conf, testCase.generalConf, loadPEM)
require.Equal(t, conf, testCase.generalConf)
require.Equal(t, srv, testCase.generalSrv)
}
if testCase.expectedPanic != "" {
f := func() {
configureClusterListener(testCase.conf, testCase.generalConf, loadPEM)
}
require.Contains(t, panicMsg(f), testCase.expectedPanic)
} else {
configureClusterListener(testCase.conf, testCase.generalConf, loadPEM)
}
// Ensure logged messages that are expected were all logged
var loggedMessages []string
for len(logEntries) > 0 {
logEntry := <-logEntries
loggedMessages = append(loggedMessages, logEntry)
}
require.Subset(t, loggedMessages, testCase.expectedLogEntries)
})
}
}
func TestReuseListener(t *testing.T) {
t.Run("good to reuse", func(t *testing.T) {
top := &localconfig.TopLevel{General: localconfig.General{TLS: localconfig.TLS{Enabled: true}}}
require.True(t, reuseListener(top))
})
t.Run("reuse tls disabled", func(t *testing.T) {
top := &localconfig.TopLevel{}
require.PanicsWithValue(
t,
"TLS is required for running ordering nodes of cluster type.",
func() { reuseListener(top) },
)
})
t.Run("good not to reuse", func(t *testing.T) {
top := &localconfig.TopLevel{
General: localconfig.General{
Cluster: localconfig.Cluster{
ListenAddress: "127.0.0.1",
ListenPort: 5000,
ServerPrivateKey: "key",
ServerCertificate: "bad",
},
},
}
require.False(t, reuseListener(top))
})
t.Run("partial config", func(t *testing.T) {
top := &localconfig.TopLevel{
General: localconfig.General{
Cluster: localconfig.Cluster{
ListenAddress: "127.0.0.1",
ListenPort: 5000,
ServerCertificate: "bad",
},
},
}
require.PanicsWithValue(
t,
"Options: General.Cluster.ListenPort, General.Cluster.ListenAddress,"+
" General.Cluster.ServerCertificate, General.Cluster.ServerPrivateKey, should be defined altogether.",
func() { reuseListener(top) },
)
})
}
func genesisConfig(t *testing.T, genesisFile string) *localconfig.TopLevel {
t.Helper()
localMSPDir := configtest.GetDevMspDir()
ledgerDir := t.TempDir()
return &localconfig.TopLevel{
General: localconfig.General{
BootstrapMethod: "none",
LocalMSPDir: localMSPDir,
LocalMSPID: "SampleOrg",
BCCSP: &factory.FactoryOpts{
Default: "SW",
SW: &factory.SwOpts{
Hash: "SHA2",
Security: 256,
},
},
},
FileLedger: localconfig.FileLedger{
Location: ledgerDir,
},
}
}
func panicMsg(f func()) string {
var message interface{}
func() {
defer func() {
message = recover()
}()
f()
}()
return message.(string)
}
// produces a system channel genesis file to make sure the server detects it and refuses to start
func produceGenesisFileEtcdRaft(t *testing.T, channelID string, tmpDir string) (string, []byte) {
confRaft := genesisconfig.Load("SampleEtcdRaftSystemChannel", tmpDir)
serverCert, err := ioutil.ReadFile(string(confRaft.Orderer.EtcdRaft.Consenters[0].ServerTlsCert))
require.NoError(t, err)
bootstrapper, err := encoder.NewBootstrapper(confRaft)
require.NoError(t, err, "cannot create bootstrapper")
joinBlockAppRaft := bootstrapper.GenesisBlockForChannel(channelID)
f := path.Join(tmpDir, channelID+".block")
err = os.WriteFile(f, protoutil.MarshalOrPanic(joinBlockAppRaft), 0o644)
require.NoError(t, err)
return f, serverCert
}
func produceGenesisFileEtcdRaftAppChannel(t *testing.T, channelID string, tmpDir string) (string, []byte) {
confRaft := genesisconfig.Load("SampleOrgChannel", tmpDir)
serverCert, err := ioutil.ReadFile(string(confRaft.Orderer.EtcdRaft.Consenters[0].ServerTlsCert))
require.NoError(t, err)
bootstrapper, err := encoder.NewBootstrapper(confRaft)
require.NoError(t, err, "cannot create bootstrapper")
joinBlockAppRaft := bootstrapper.GenesisBlockForChannel(channelID)
f := path.Join(tmpDir, channelID+".block")
err = os.WriteFile(f, protoutil.MarshalOrPanic(joinBlockAppRaft), 0o644)
require.NoError(t, err)
return f, serverCert
}
func initializeAppChannel(genesisBlock *common.Block, lf blockledger.Factory) {
channelID, err := protoutil.GetChannelIDFromBlock(genesisBlock)
if err != nil {
logger.Fatal("Failed to parse channel ID from genesis block:", err)
}
gl, err := lf.GetOrCreate(channelID)
if err != nil {
logger.Fatal("Failed to create the system channel:", err)
}
if gl.Height() == 0 {
if err := gl.Append(genesisBlock); err != nil {
logger.Fatal("Could not write genesis block to ledger:", err)
}
}
logger.Infof("Initialized the channel '%s' from genesis block", channelID)
}