From 231460cda38bee12bd99edb73acd72c06d4ee33c Mon Sep 17 00:00:00 2001 From: Jason Yellick Date: Mon, 24 Jul 2017 12:22:22 -0400 Subject: [PATCH] [FAB-5441] Add multisig support to peer CLI Without utilizing the SDKs, it is currently not possible to submit a configtx with signatures from multiple parties. This makes it very difficult if not impossible to modify things like channel membership. This CR adds a new command to the peer channel CLI: signconfigtx This command operates like the update command, but instead of signing and submitting it to ordering, it simply signs the transaction, then writes it back to disk. This write is done in place, replacing the input file. Change-Id: I59bd922ec6ecaca11d8e23ef604228e51c229b46 Signed-off-by: Jason Yellick --- peer/channel/channel.go | 1 + peer/channel/create.go | 6 ++ peer/channel/signconfigtx.go | 65 ++++++++++++++++++++ peer/channel/signconfigtx_test.go | 98 +++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 peer/channel/signconfigtx.go create mode 100644 peer/channel/signconfigtx_test.go diff --git a/peer/channel/channel.go b/peer/channel/channel.go index 003e8474654..277bb34a000 100644 --- a/peer/channel/channel.go +++ b/peer/channel/channel.go @@ -73,6 +73,7 @@ func Cmd(cf *ChannelCmdFactory) *cobra.Command { channelCmd.AddCommand(joinCmd(cf)) channelCmd.AddCommand(listCmd(cf)) channelCmd.AddCommand(updateCmd(cf)) + channelCmd.AddCommand(signconfigtxCmd(cf)) return channelCmd } diff --git a/peer/channel/create.go b/peer/channel/create.go index d6157cb5d36..2b37f1d510f 100644 --- a/peer/channel/create.go +++ b/peer/channel/create.go @@ -116,6 +116,12 @@ func sanityCheckAndSignConfigTx(envConfigUpdate *cb.Envelope) (*cb.Envelope, err return nil, InvalidCreateTx("empty channel id") } + // Specifying the chainID on the CLI is usually redundant, as a hack, set it + // here if it has not been set explicitly + if chainID == "" { + chainID = ch.ChannelId + } + if ch.ChannelId != chainID { return nil, InvalidCreateTx(fmt.Sprintf("mismatched channel ID %s != %s", ch.ChannelId, chainID)) } diff --git a/peer/channel/signconfigtx.go b/peer/channel/signconfigtx.go new file mode 100644 index 00000000000..e9ded84247b --- /dev/null +++ b/peer/channel/signconfigtx.go @@ -0,0 +1,65 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package channel + +import ( + "io/ioutil" + + "github.com/hyperledger/fabric/protos/utils" + + "github.com/spf13/cobra" +) + +func signconfigtxCmd(cf *ChannelCmdFactory) *cobra.Command { + signconfigtxCmd := &cobra.Command{ + Use: "signconfigtx", + Short: "Signs a configtx update.", + Long: "Signs the supplied configtx update file in place on the filesystem. Requires '-f'.", + RunE: func(cmd *cobra.Command, args []string) error { + return sign(cmd, args, cf) + }, + } + flagList := []string{ + "file", + } + attachFlags(signconfigtxCmd, flagList) + + return signconfigtxCmd +} + +func sign(cmd *cobra.Command, args []string, cf *ChannelCmdFactory) error { + if channelTxFile == "" { + return InvalidCreateTx("No configtx file name supplied") + } + + var err error + if cf == nil { + cf, err = InitCmdFactory(EndorserNotRequired, OrdererNotRequired) + if err != nil { + return err + } + } + + fileData, err := ioutil.ReadFile(channelTxFile) + if err != nil { + return ConfigTxFileNotFound(err.Error()) + } + + ctxEnv, err := utils.UnmarshalEnvelope(fileData) + if err != nil { + return err + } + + sCtxEnv, err := sanityCheckAndSignConfigTx(ctxEnv) + if err != nil { + return err + } + + sCtxEnvData := utils.MarshalOrPanic(sCtxEnv) + + return ioutil.WriteFile(channelTxFile, sCtxEnvData, 0660) +} diff --git a/peer/channel/signconfigtx_test.go b/peer/channel/signconfigtx_test.go new file mode 100644 index 00000000000..fb044497464 --- /dev/null +++ b/peer/channel/signconfigtx_test.go @@ -0,0 +1,98 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package channel + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/hyperledger/fabric/peer/common" + cb "github.com/hyperledger/fabric/protos/common" + + "github.com/stretchr/testify/assert" +) + +func TestSignConfigtx(t *testing.T) { + InitMSP() + resetFlags() + + dir, err := ioutil.TempDir("/tmp", "signconfigtxtest-") + if err != nil { + t.Fatalf("couldn't create temp dir") + } + defer os.RemoveAll(dir) // clean up + + configtxFile := filepath.Join(dir, mockChannel) + if _, err = createTxFile(configtxFile, cb.HeaderType_CONFIG_UPDATE, mockChannel); err != nil { + t.Fatalf("couldn't create tx file") + } + + signer, err := common.GetDefaultSigner() + if err != nil { + t.Fatalf("Get default signer error: %v", err) + } + + mockCF := &ChannelCmdFactory{ + Signer: signer, + } + + cmd := signconfigtxCmd(mockCF) + + AddFlags(cmd) + + args := []string{"-f", configtxFile} + cmd.SetArgs(args) + + assert.NoError(t, cmd.Execute()) +} + +func TestSignConfigtxMissingConfigTxFlag(t *testing.T) { + InitMSP() + resetFlags() + + signer, err := common.GetDefaultSigner() + if err != nil { + t.Fatalf("Get default signer error: %v", err) + } + + mockCF := &ChannelCmdFactory{ + Signer: signer, + } + + cmd := signconfigtxCmd(mockCF) + + AddFlags(cmd) + + cmd.SetArgs([]string{}) + + assert.Error(t, cmd.Execute()) +} + +func TestSignConfigtxChannelMissingConfigTxFile(t *testing.T) { + InitMSP() + resetFlags() + + signer, err := common.GetDefaultSigner() + if err != nil { + t.Fatalf("Get default signer error: %v", err) + } + + mockCF := &ChannelCmdFactory{ + Signer: signer, + } + + cmd := signconfigtxCmd(mockCF) + + AddFlags(cmd) + + args := []string{"-f", "Non-existant"} + cmd.SetArgs(args) + + assert.Error(t, cmd.Execute()) +}