github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/cmd/osnadmin/main_test.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package main
     8  
     9  import (
    10  	"crypto/tls"
    11  	"crypto/x509"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"io/ioutil"
    16  	"math"
    17  	"net/http/httptest"
    18  	"net/url"
    19  	"os"
    20  	"path/filepath"
    21  
    22  	"github.com/golang/protobuf/proto"
    23  	"github.com/hechain20/hechain/bccsp"
    24  	"github.com/hechain20/hechain/cmd/osnadmin/mocks"
    25  	"github.com/hechain20/hechain/common/crypto/tlsgen"
    26  	"github.com/hechain20/hechain/orderer/common/channelparticipation"
    27  	"github.com/hechain20/hechain/orderer/common/localconfig"
    28  	"github.com/hechain20/hechain/orderer/common/types"
    29  	"github.com/hechain20/hechain/protoutil"
    30  	cb "github.com/hyperledger/fabric-protos-go/common"
    31  	. "github.com/onsi/ginkgo"
    32  	. "github.com/onsi/gomega"
    33  )
    34  
    35  var _ = Describe("osnadmin", func() {
    36  	var (
    37  		tempDir               string
    38  		ordererCACert         string
    39  		clientCert            string
    40  		clientKey             string
    41  		mockChannelManagement *mocks.ChannelManagement
    42  		testServer            *httptest.Server
    43  		tlsConfig             *tls.Config
    44  		ordererURL            string
    45  		channelID             string
    46  	)
    47  
    48  	BeforeEach(func() {
    49  		var err error
    50  		tempDir, err = ioutil.TempDir("", "osnadmin")
    51  		Expect(err).NotTo(HaveOccurred())
    52  
    53  		generateCertificates(tempDir)
    54  
    55  		ordererCACert = filepath.Join(tempDir, "server-ca.pem")
    56  		clientCert = filepath.Join(tempDir, "client-cert.pem")
    57  		clientKey = filepath.Join(tempDir, "client-key.pem")
    58  
    59  		channelID = "testing123"
    60  
    61  		config := localconfig.ChannelParticipation{
    62  			Enabled:            true,
    63  			MaxRequestBodySize: 1024 * 1024,
    64  		}
    65  		mockChannelManagement = &mocks.ChannelManagement{}
    66  
    67  		h := channelparticipation.NewHTTPHandler(config, mockChannelManagement)
    68  		Expect(h).NotTo(BeNil())
    69  		testServer = httptest.NewUnstartedServer(h)
    70  
    71  		cert, err := tls.LoadX509KeyPair(
    72  			filepath.Join(tempDir, "server-cert.pem"),
    73  			filepath.Join(tempDir, "server-key.pem"),
    74  		)
    75  		Expect(err).NotTo(HaveOccurred())
    76  
    77  		caCertPool := x509.NewCertPool()
    78  		clientCAPem, err := ioutil.ReadFile(filepath.Join(tempDir, "client-ca.pem"))
    79  		Expect(err).NotTo(HaveOccurred())
    80  		caCertPool.AppendCertsFromPEM(clientCAPem)
    81  
    82  		tlsConfig = &tls.Config{
    83  			Certificates: []tls.Certificate{cert},
    84  			ClientCAs:    caCertPool,
    85  			ClientAuth:   tls.RequireAndVerifyClientCert,
    86  		}
    87  	})
    88  
    89  	JustBeforeEach(func() {
    90  		if tlsConfig != nil {
    91  			testServer.TLS = tlsConfig
    92  			testServer.StartTLS()
    93  		} else {
    94  			testServer.Start()
    95  		}
    96  
    97  		u, err := url.Parse(testServer.URL)
    98  		Expect(err).NotTo(HaveOccurred())
    99  		ordererURL = u.Host
   100  	})
   101  
   102  	AfterEach(func() {
   103  		os.RemoveAll(tempDir)
   104  		testServer.Close()
   105  	})
   106  
   107  	Describe("List", func() {
   108  		BeforeEach(func() {
   109  			mockChannelManagement.ChannelListReturns(types.ChannelList{
   110  				Channels: []types.ChannelInfoShort{
   111  					{
   112  						Name: "participation-trophy",
   113  					},
   114  					{
   115  						Name: "another-participation-trophy",
   116  					},
   117  				},
   118  				SystemChannel: &types.ChannelInfoShort{
   119  					Name: "fight-the-system",
   120  				},
   121  			})
   122  
   123  			mockChannelManagement.ChannelInfoReturns(types.ChannelInfo{
   124  				Name:              "asparagus",
   125  				ConsensusRelation: "broccoli",
   126  				Status:            "carrot",
   127  				Height:            987,
   128  			}, nil)
   129  		})
   130  
   131  		It("uses the channel participation API to list all application channels and the system channel (when it exists)", func() {
   132  			args := []string{
   133  				"channel",
   134  				"list",
   135  				"--orderer-address", ordererURL,
   136  				"--ca-file", ordererCACert,
   137  				"--client-cert", clientCert,
   138  				"--client-key", clientKey,
   139  			}
   140  			output, exit, err := executeForArgs(args)
   141  			expectedOutput := types.ChannelList{
   142  				Channels: []types.ChannelInfoShort{
   143  					{
   144  						Name: "participation-trophy",
   145  						URL:  "/participation/v1/channels/participation-trophy",
   146  					},
   147  					{
   148  						Name: "another-participation-trophy",
   149  						URL:  "/participation/v1/channels/another-participation-trophy",
   150  					},
   151  				},
   152  				SystemChannel: &types.ChannelInfoShort{
   153  					Name: "fight-the-system",
   154  					URL:  "/participation/v1/channels/fight-the-system",
   155  				},
   156  			}
   157  			checkStatusOutput(output, exit, err, 200, expectedOutput)
   158  		})
   159  
   160  		It("uses the channel participation API to list the details of a single channel", func() {
   161  			args := []string{
   162  				"channel",
   163  				"list",
   164  				"--orderer-address", ordererURL,
   165  				"--channelID", "tell-me-your-secrets",
   166  				"--ca-file", ordererCACert,
   167  				"--client-cert", clientCert,
   168  				"--client-key", clientKey,
   169  			}
   170  			output, exit, err := executeForArgs(args)
   171  			expectedOutput := types.ChannelInfo{
   172  				Name:              "asparagus",
   173  				URL:               "/participation/v1/channels/asparagus",
   174  				ConsensusRelation: "broccoli",
   175  				Status:            "carrot",
   176  				Height:            987,
   177  			}
   178  			checkStatusOutput(output, exit, err, 200, expectedOutput)
   179  		})
   180  
   181  		Context("when the channel does not exist", func() {
   182  			BeforeEach(func() {
   183  				mockChannelManagement.ChannelInfoReturns(types.ChannelInfo{}, errors.New("eat-your-peas"))
   184  			})
   185  
   186  			It("returns 404 not found", func() {
   187  				args := []string{
   188  					"channel",
   189  					"list",
   190  					"--orderer-address", ordererURL,
   191  					"--channelID", "tell-me-your-secrets",
   192  					"--ca-file", ordererCACert,
   193  					"--client-cert", clientCert,
   194  					"--client-key", clientKey,
   195  				}
   196  				output, exit, err := executeForArgs(args)
   197  				expectedOutput := types.ErrorResponse{
   198  					Error: "eat-your-peas",
   199  				}
   200  				checkStatusOutput(output, exit, err, 404, expectedOutput)
   201  			})
   202  		})
   203  
   204  		Context("when TLS is disabled", func() {
   205  			BeforeEach(func() {
   206  				tlsConfig = nil
   207  			})
   208  
   209  			It("uses the channel participation API to list all channels", func() {
   210  				args := []string{
   211  					"channel",
   212  					"list",
   213  					"--orderer-address", ordererURL,
   214  				}
   215  				output, exit, err := executeForArgs(args)
   216  				Expect(err).NotTo(HaveOccurred())
   217  				Expect(exit).To(Equal(0))
   218  
   219  				expectedOutput := types.ChannelList{
   220  					Channels: []types.ChannelInfoShort{
   221  						{
   222  							Name: "participation-trophy",
   223  							URL:  "/participation/v1/channels/participation-trophy",
   224  						},
   225  						{
   226  							Name: "another-participation-trophy",
   227  							URL:  "/participation/v1/channels/another-participation-trophy",
   228  						},
   229  					},
   230  					SystemChannel: &types.ChannelInfoShort{
   231  						Name: "fight-the-system",
   232  						URL:  "/participation/v1/channels/fight-the-system",
   233  					},
   234  				}
   235  				checkStatusOutput(output, exit, err, 200, expectedOutput)
   236  			})
   237  
   238  			It("uses the channel participation API to list the details of a single channel", func() {
   239  				args := []string{
   240  					"channel",
   241  					"list",
   242  					"--orderer-address", ordererURL,
   243  					"--channelID", "tell-me-your-secrets",
   244  				}
   245  				output, exit, err := executeForArgs(args)
   246  				Expect(err).NotTo(HaveOccurred())
   247  				Expect(exit).To(Equal(0))
   248  
   249  				expectedOutput := types.ChannelInfo{
   250  					Name:              "asparagus",
   251  					URL:               "/participation/v1/channels/asparagus",
   252  					ConsensusRelation: "broccoli",
   253  					Status:            "carrot",
   254  					Height:            987,
   255  				}
   256  				checkStatusOutput(output, exit, err, 200, expectedOutput)
   257  			})
   258  		})
   259  	})
   260  
   261  	Describe("Remove", func() {
   262  		It("uses the channel participation API to remove a channel", func() {
   263  			args := []string{
   264  				"channel",
   265  				"remove",
   266  				"--orderer-address", ordererURL,
   267  				"--channelID", channelID,
   268  				"--ca-file", ordererCACert,
   269  				"--client-cert", clientCert,
   270  				"--client-key", clientKey,
   271  			}
   272  			output, exit, err := executeForArgs(args)
   273  			Expect(err).NotTo(HaveOccurred())
   274  			Expect(exit).To(Equal(0))
   275  			Expect(output).To(Equal("Status: 204\n"))
   276  		})
   277  
   278  		It("uses the channel participation API to remove a channel (without status)", func() {
   279  			args := []string{
   280  				"channel",
   281  				"remove",
   282  				"--orderer-address", ordererURL,
   283  				"--channelID", channelID,
   284  				"--ca-file", ordererCACert,
   285  				"--client-cert", clientCert,
   286  				"--client-key", clientKey,
   287  				"--no-status",
   288  			}
   289  			output, exit, err := executeForArgs(args)
   290  			Expect(err).NotTo(HaveOccurred())
   291  			Expect(exit).To(Equal(0))
   292  			Expect(output).To(BeEmpty())
   293  		})
   294  
   295  		Context("when the channel does not exist", func() {
   296  			BeforeEach(func() {
   297  				mockChannelManagement.RemoveChannelReturns(types.ErrChannelNotExist)
   298  			})
   299  
   300  			It("returns 404 not found", func() {
   301  				args := []string{
   302  					"channel",
   303  					"remove",
   304  					"--ca-file", ordererCACert,
   305  					"--orderer-address", ordererURL,
   306  					"--channelID", channelID,
   307  					"--client-cert", clientCert,
   308  					"--client-key", clientKey,
   309  				}
   310  				output, exit, err := executeForArgs(args)
   311  				expectedOutput := types.ErrorResponse{
   312  					Error: "cannot remove: channel does not exist",
   313  				}
   314  				checkStatusOutput(output, exit, err, 404, expectedOutput)
   315  			})
   316  		})
   317  
   318  		Context("when TLS is disabled", func() {
   319  			BeforeEach(func() {
   320  				tlsConfig = nil
   321  			})
   322  
   323  			It("uses the channel participation API to remove a channel", func() {
   324  				args := []string{
   325  					"channel",
   326  					"remove",
   327  					"--orderer-address", ordererURL,
   328  					"--channelID", channelID,
   329  				}
   330  				output, exit, err := executeForArgs(args)
   331  				Expect(err).NotTo(HaveOccurred())
   332  				Expect(exit).To(Equal(0))
   333  				Expect(output).To(Equal("Status: 204\n"))
   334  			})
   335  		})
   336  	})
   337  
   338  	Describe("Join", func() {
   339  		var blockPath string
   340  
   341  		BeforeEach(func() {
   342  			configBlock := blockWithGroups(
   343  				map[string]*cb.ConfigGroup{
   344  					"Application": {},
   345  				},
   346  				"testing123",
   347  			)
   348  			blockPath = createBlockFile(tempDir, configBlock)
   349  
   350  			mockChannelManagement.JoinChannelReturns(types.ChannelInfo{
   351  				Name:              "apple",
   352  				ConsensusRelation: "banana",
   353  				Status:            "orange",
   354  				Height:            123,
   355  			}, nil)
   356  		})
   357  
   358  		It("uses the channel participation API to join a channel", func() {
   359  			args := []string{
   360  				"channel",
   361  				"join",
   362  				"--orderer-address", ordererURL,
   363  				"--channelID", channelID,
   364  				"--config-block", blockPath,
   365  				"--ca-file", ordererCACert,
   366  				"--client-cert", clientCert,
   367  				"--client-key", clientKey,
   368  			}
   369  			output, exit, err := executeForArgs(args)
   370  			expectedOutput := types.ChannelInfo{
   371  				Name:              "apple",
   372  				URL:               "/participation/v1/channels/apple",
   373  				ConsensusRelation: "banana",
   374  				Status:            "orange",
   375  				Height:            123,
   376  			}
   377  			checkStatusOutput(output, exit, err, 201, expectedOutput)
   378  		})
   379  
   380  		Context("when the block is empty", func() {
   381  			BeforeEach(func() {
   382  				blockPath = createBlockFile(tempDir, &cb.Block{})
   383  			})
   384  
   385  			It("returns with exit code 1 and prints the error", func() {
   386  				args := []string{
   387  					"channel",
   388  					"join",
   389  					"--orderer-address", ordererURL,
   390  					"--channelID", channelID,
   391  					"--config-block", blockPath,
   392  					"--ca-file", ordererCACert,
   393  					"--client-cert", clientCert,
   394  					"--client-key", clientKey,
   395  				}
   396  				output, exit, err := executeForArgs(args)
   397  
   398  				checkFlagError(output, exit, err, "failed to retrieve channel id - block is empty")
   399  			})
   400  		})
   401  
   402  		Context("when the --channelID does not match the channel ID in the block", func() {
   403  			BeforeEach(func() {
   404  				channelID = "not-the-channel-youre-looking-for"
   405  			})
   406  
   407  			It("returns with exit code 1 and prints the error", func() {
   408  				args := []string{
   409  					"channel",
   410  					"join",
   411  					"--orderer-address", ordererURL,
   412  					"--channelID", channelID,
   413  					"--config-block", blockPath,
   414  					"--ca-file", ordererCACert,
   415  					"--client-cert", clientCert,
   416  					"--client-key", clientKey,
   417  				}
   418  				output, exit, err := executeForArgs(args)
   419  
   420  				checkFlagError(output, exit, err, "specified --channelID not-the-channel-youre-looking-for does not match channel ID testing123 in config block")
   421  			})
   422  		})
   423  
   424  		Context("when the block isn't a valid config block", func() {
   425  			BeforeEach(func() {
   426  				block := &cb.Block{
   427  					Data: &cb.BlockData{
   428  						Data: [][]byte{
   429  							protoutil.MarshalOrPanic(&cb.Envelope{
   430  								Payload: protoutil.MarshalOrPanic(&cb.Payload{
   431  									Header: &cb.Header{
   432  										ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
   433  											Type:      int32(cb.HeaderType_ENDORSER_TRANSACTION),
   434  											ChannelId: channelID,
   435  										}),
   436  									},
   437  								}),
   438  							}),
   439  						},
   440  					},
   441  				}
   442  				blockPath = createBlockFile(tempDir, block)
   443  			})
   444  
   445  			It("returns 405 bad request", func() {
   446  				args := []string{
   447  					"channel",
   448  					"join",
   449  					"--orderer-address", ordererURL,
   450  					"--channelID", channelID,
   451  					"--config-block", blockPath,
   452  					"--ca-file", ordererCACert,
   453  					"--client-cert", clientCert,
   454  					"--client-key", clientKey,
   455  				}
   456  				output, exit, err := executeForArgs(args)
   457  				Expect(err).NotTo(HaveOccurred())
   458  				Expect(exit).To(Equal(0))
   459  
   460  				expectedOutput := types.ErrorResponse{
   461  					Error: "invalid join block: block is not a config block",
   462  				}
   463  				checkStatusOutput(output, exit, err, 400, expectedOutput)
   464  			})
   465  		})
   466  
   467  		Context("when joining the channel fails", func() {
   468  			BeforeEach(func() {
   469  				mockChannelManagement.JoinChannelReturns(types.ChannelInfo{}, types.ErrChannelAlreadyExists)
   470  			})
   471  
   472  			It("returns 405 not allowed", func() {
   473  				args := []string{
   474  					"channel",
   475  					"join",
   476  					"--orderer-address", ordererURL,
   477  					"--channelID", channelID,
   478  					"--config-block", blockPath,
   479  					"--ca-file", ordererCACert,
   480  					"--client-cert", clientCert,
   481  					"--client-key", clientKey,
   482  				}
   483  				output, exit, err := executeForArgs(args)
   484  				expectedOutput := types.ErrorResponse{
   485  					Error: "cannot join: channel already exists",
   486  				}
   487  				checkStatusOutput(output, exit, err, 405, expectedOutput)
   488  			})
   489  
   490  			It("returns 405 not allowed (without status)", func() {
   491  				args := []string{
   492  					"channel",
   493  					"join",
   494  					"--orderer-address", ordererURL,
   495  					"--channelID", channelID,
   496  					"--config-block", blockPath,
   497  					"--ca-file", ordererCACert,
   498  					"--client-cert", clientCert,
   499  					"--client-key", clientKey,
   500  					"--no-status",
   501  				}
   502  				output, exit, err := executeForArgs(args)
   503  				expectedOutput := types.ErrorResponse{
   504  					Error: "cannot join: channel already exists",
   505  				}
   506  				checkOutput(output, exit, err, expectedOutput)
   507  			})
   508  		})
   509  
   510  		Context("when TLS is disabled", func() {
   511  			BeforeEach(func() {
   512  				tlsConfig = nil
   513  			})
   514  
   515  			It("uses the channel participation API to join a channel", func() {
   516  				args := []string{
   517  					"channel",
   518  					"join",
   519  					"--orderer-address", ordererURL,
   520  					"--channelID", channelID,
   521  					"--config-block", blockPath,
   522  				}
   523  				output, exit, err := executeForArgs(args)
   524  				expectedOutput := types.ChannelInfo{
   525  					Name:              "apple",
   526  					URL:               "/participation/v1/channels/apple",
   527  					ConsensusRelation: "banana",
   528  					Status:            "orange",
   529  					Height:            123,
   530  				}
   531  				checkStatusOutput(output, exit, err, 201, expectedOutput)
   532  			})
   533  		})
   534  	})
   535  
   536  	Describe("Flags", func() {
   537  		It("accepts short versions of the --orderer-address, --channelID, and --config-block flags", func() {
   538  			configBlock := blockWithGroups(
   539  				map[string]*cb.ConfigGroup{
   540  					"Application": {},
   541  				},
   542  				"testing123",
   543  			)
   544  			blockPath := createBlockFile(tempDir, configBlock)
   545  			mockChannelManagement.JoinChannelReturns(types.ChannelInfo{
   546  				Name:              "apple",
   547  				ConsensusRelation: "banana",
   548  				Status:            "orange",
   549  				Height:            123,
   550  			}, nil)
   551  
   552  			args := []string{
   553  				"channel",
   554  				"join",
   555  				"-o", ordererURL,
   556  				"-c", channelID,
   557  				"-b", blockPath,
   558  				"--ca-file", ordererCACert,
   559  				"--client-cert", clientCert,
   560  				"--client-key", clientKey,
   561  			}
   562  			output, exit, err := executeForArgs(args)
   563  			expectedOutput := types.ChannelInfo{
   564  				Name:              "apple",
   565  				URL:               "/participation/v1/channels/apple",
   566  				ConsensusRelation: "banana",
   567  				Status:            "orange",
   568  				Height:            123,
   569  			}
   570  			checkStatusOutput(output, exit, err, 201, expectedOutput)
   571  		})
   572  
   573  		Context("when an unknown flag is used", func() {
   574  			It("returns an error for long flags", func() {
   575  				_, _, err := executeForArgs([]string{"channel", "list", "--bad-flag"})
   576  				Expect(err).To(MatchError("unknown long flag '--bad-flag'"))
   577  			})
   578  
   579  			It("returns an error for short flags", func() {
   580  				_, _, err := executeForArgs([]string{"channel", "list", "-z"})
   581  				Expect(err).To(MatchError("unknown short flag '-z'"))
   582  			})
   583  		})
   584  
   585  		Context("when the ca cert cannot be read", func() {
   586  			BeforeEach(func() {
   587  				ordererCACert = "not-the-ca-cert-youre-looking-for"
   588  			})
   589  
   590  			It("returns with exit code 1 and prints the error", func() {
   591  				args := []string{
   592  					"channel",
   593  					"list",
   594  					"--orderer-address", ordererURL,
   595  					"--channelID", channelID,
   596  					"--ca-file", ordererCACert,
   597  					"--client-cert", clientCert,
   598  					"--client-key", clientKey,
   599  				}
   600  				output, exit, err := executeForArgs(args)
   601  				checkFlagError(output, exit, err, "reading orderer CA certificate: open not-the-ca-cert-youre-looking-for: no such file or directory")
   602  			})
   603  		})
   604  
   605  		Context("when the ca-file contains a private key instead of certificate(s)", func() {
   606  			BeforeEach(func() {
   607  				ordererCACert = clientKey
   608  			})
   609  
   610  			It("returns with exit code 1 and prints the error", func() {
   611  				args := []string{
   612  					"channel",
   613  					"remove",
   614  					"--orderer-address", ordererURL,
   615  					"--channelID", channelID,
   616  					"--ca-file", ordererCACert,
   617  					"--client-cert", clientCert,
   618  					"--client-key", clientKey,
   619  				}
   620  				output, exit, err := executeForArgs(args)
   621  				checkFlagError(output, exit, err, "failed to add ca-file PEM to cert pool")
   622  			})
   623  		})
   624  
   625  		Context("when the client cert/key pair fail to load", func() {
   626  			BeforeEach(func() {
   627  				clientKey = "brussel-sprouts"
   628  			})
   629  
   630  			It("returns with exit code 1 and prints the error", func() {
   631  				args := []string{
   632  					"channel",
   633  					"list",
   634  					"--orderer-address", ordererURL,
   635  					"--ca-file", ordererCACert,
   636  					"--client-cert", clientCert,
   637  					"--client-key", clientKey,
   638  				}
   639  				output, exit, err := executeForArgs(args)
   640  				checkFlagError(output, exit, err, "loading client cert/key pair: open brussel-sprouts: no such file or directory")
   641  			})
   642  		})
   643  
   644  		Context("when the config block cannot be read", func() {
   645  			var configBlockPath string
   646  
   647  			BeforeEach(func() {
   648  				configBlockPath = "not-the-config-block-youre-looking-for"
   649  			})
   650  
   651  			It("returns with exit code 1 and prints the error", func() {
   652  				args := []string{
   653  					"channel",
   654  					"join",
   655  					"--orderer-address", ordererURL,
   656  					"--channelID", channelID,
   657  					"--ca-file", ordererCACert,
   658  					"--client-cert", clientCert,
   659  					"--client-key", clientKey,
   660  					"--config-block", configBlockPath,
   661  				}
   662  				output, exit, err := executeForArgs(args)
   663  				checkFlagError(output, exit, err, "reading config block: open not-the-config-block-youre-looking-for: no such file or directory")
   664  			})
   665  		})
   666  	})
   667  
   668  	Describe("Server using intermediate CA", func() {
   669  		BeforeEach(func() {
   670  			cert, err := tls.LoadX509KeyPair(
   671  				filepath.Join(tempDir, "server-intermediate-cert.pem"),
   672  				filepath.Join(tempDir, "server-intermediate-key.pem"),
   673  			)
   674  			Expect(err).NotTo(HaveOccurred())
   675  			tlsConfig.Certificates = []tls.Certificate{cert}
   676  
   677  			ordererCACert = filepath.Join(tempDir, "server-ca+intermediate-ca.pem")
   678  		})
   679  
   680  		It("uses the channel participation API to list all application and and the system channel (when it exists)", func() {
   681  			args := []string{
   682  				"channel",
   683  				"list",
   684  				"--orderer-address", ordererURL,
   685  				"--ca-file", ordererCACert,
   686  				"--client-cert", clientCert,
   687  				"--client-key", clientKey,
   688  			}
   689  			output, exit, err := executeForArgs(args)
   690  			expectedOutput := types.ChannelList{
   691  				Channels:      nil,
   692  				SystemChannel: nil,
   693  			}
   694  			checkStatusOutput(output, exit, err, 200, expectedOutput)
   695  		})
   696  
   697  		Context("when the ca-file does not include the intermediate CA", func() {
   698  			BeforeEach(func() {
   699  				ordererCACert = filepath.Join(tempDir, "server-ca.pem")
   700  			})
   701  			It("returns with exit code 1 and prints the error", func() {
   702  				args := []string{
   703  					"channel",
   704  					"list",
   705  					"--orderer-address", ordererURL,
   706  					"--ca-file", ordererCACert,
   707  					"--client-cert", clientCert,
   708  					"--client-key", clientKey,
   709  				}
   710  				output, exit, err := executeForArgs(args)
   711  				checkCLIError(output, exit, err, fmt.Sprintf("Get \"%s/participation/v1/channels\": x509: certificate signed by unknown authority", testServer.URL))
   712  			})
   713  		})
   714  	})
   715  })
   716  
   717  func checkStatusOutput(output string, exit int, err error, expectedStatus int, expectedOutput interface{}) {
   718  	Expect(err).NotTo(HaveOccurred())
   719  	Expect(exit).To(Equal(0))
   720  	json, err := json.MarshalIndent(expectedOutput, "", "\t")
   721  	Expect(err).NotTo(HaveOccurred())
   722  	Expect(output).To(Equal(fmt.Sprintf("Status: %d\n%s\n", expectedStatus, string(json))))
   723  }
   724  
   725  func checkOutput(output string, exit int, err error, expectedOutput interface{}) {
   726  	Expect(err).NotTo(HaveOccurred())
   727  	Expect(exit).To(Equal(0))
   728  	json, err := json.MarshalIndent(expectedOutput, "", "\t")
   729  	Expect(err).NotTo(HaveOccurred())
   730  	Expect(output).To(Equal(string(json) + "\n"))
   731  }
   732  
   733  func checkFlagError(output string, exit int, err error, expectedError string) {
   734  	Expect(err).To(MatchError(ContainSubstring(expectedError)))
   735  	Expect(exit).To(Equal(1))
   736  	Expect(output).To(BeEmpty())
   737  }
   738  
   739  func checkCLIError(output string, exit int, err error, expectedError string) {
   740  	Expect(err).NotTo(HaveOccurred())
   741  	Expect(exit).To(Equal(1))
   742  	Expect(output).To(Equal(fmt.Sprintf("Error: %s\n", expectedError)))
   743  }
   744  
   745  func generateCertificates(tempDir string) {
   746  	serverCA, err := tlsgen.NewCA()
   747  	Expect(err).NotTo(HaveOccurred())
   748  	err = ioutil.WriteFile(filepath.Join(tempDir, "server-ca.pem"), serverCA.CertBytes(), 0o640)
   749  	Expect(err).NotTo(HaveOccurred())
   750  	serverKeyPair, err := serverCA.NewServerCertKeyPair("127.0.0.1")
   751  	Expect(err).NotTo(HaveOccurred())
   752  	err = ioutil.WriteFile(filepath.Join(tempDir, "server-cert.pem"), serverKeyPair.Cert, 0o640)
   753  	Expect(err).NotTo(HaveOccurred())
   754  	err = ioutil.WriteFile(filepath.Join(tempDir, "server-key.pem"), serverKeyPair.Key, 0o640)
   755  	Expect(err).NotTo(HaveOccurred())
   756  
   757  	serverIntermediateCA, err := serverCA.NewIntermediateCA()
   758  	Expect(err).NotTo(HaveOccurred())
   759  	err = ioutil.WriteFile(filepath.Join(tempDir, "server-intermediate-ca.pem"), serverIntermediateCA.CertBytes(), 0o640)
   760  	Expect(err).NotTo(HaveOccurred())
   761  	serverIntermediateKeyPair, err := serverIntermediateCA.NewServerCertKeyPair("127.0.0.1")
   762  	Expect(err).NotTo(HaveOccurred())
   763  	err = ioutil.WriteFile(filepath.Join(tempDir, "server-intermediate-cert.pem"), serverIntermediateKeyPair.Cert, 0o640)
   764  	Expect(err).NotTo(HaveOccurred())
   765  	err = ioutil.WriteFile(filepath.Join(tempDir, "server-intermediate-key.pem"), serverIntermediateKeyPair.Key, 0o640)
   766  	Expect(err).NotTo(HaveOccurred())
   767  
   768  	serverAndIntermediateCABytes := append(serverCA.CertBytes(), serverIntermediateCA.CertBytes()...)
   769  	err = ioutil.WriteFile(filepath.Join(tempDir, "server-ca+intermediate-ca.pem"), serverAndIntermediateCABytes, 0o640)
   770  	Expect(err).NotTo(HaveOccurred())
   771  
   772  	clientCA, err := tlsgen.NewCA()
   773  	Expect(err).NotTo(HaveOccurred())
   774  	err = ioutil.WriteFile(filepath.Join(tempDir, "client-ca.pem"), clientCA.CertBytes(), 0o640)
   775  	Expect(err).NotTo(HaveOccurred())
   776  	clientKeyPair, err := clientCA.NewClientCertKeyPair()
   777  	Expect(err).NotTo(HaveOccurred())
   778  	err = ioutil.WriteFile(filepath.Join(tempDir, "client-cert.pem"), clientKeyPair.Cert, 0o640)
   779  	Expect(err).NotTo(HaveOccurred())
   780  	err = ioutil.WriteFile(filepath.Join(tempDir, "client-key.pem"), clientKeyPair.Key, 0o640)
   781  	Expect(err).NotTo(HaveOccurred())
   782  }
   783  
   784  func blockWithGroups(groups map[string]*cb.ConfigGroup, channelID string) *cb.Block {
   785  	return &cb.Block{
   786  		Data: &cb.BlockData{
   787  			Data: [][]byte{
   788  				protoutil.MarshalOrPanic(&cb.Envelope{
   789  					Payload: protoutil.MarshalOrPanic(&cb.Payload{
   790  						Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
   791  							Config: &cb.Config{
   792  								ChannelGroup: &cb.ConfigGroup{
   793  									Groups: groups,
   794  									Values: map[string]*cb.ConfigValue{
   795  										"HashingAlgorithm": {
   796  											Value: protoutil.MarshalOrPanic(&cb.HashingAlgorithm{
   797  												Name: bccsp.SHA256,
   798  											}),
   799  										},
   800  										"BlockDataHashingStructure": {
   801  											Value: protoutil.MarshalOrPanic(&cb.BlockDataHashingStructure{
   802  												Width: math.MaxUint32,
   803  											}),
   804  										},
   805  										"OrdererAddresses": {
   806  											Value: protoutil.MarshalOrPanic(&cb.OrdererAddresses{
   807  												Addresses: []string{"localhost"},
   808  											}),
   809  										},
   810  									},
   811  								},
   812  							},
   813  						}),
   814  						Header: &cb.Header{
   815  							ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
   816  								Type:      int32(cb.HeaderType_CONFIG),
   817  								ChannelId: channelID,
   818  							}),
   819  						},
   820  					}),
   821  				}),
   822  			},
   823  		},
   824  	}
   825  }
   826  
   827  func createBlockFile(tempDir string, configBlock *cb.Block) string {
   828  	blockBytes, err := proto.Marshal(configBlock)
   829  	Expect(err).NotTo(HaveOccurred())
   830  	blockPath := filepath.Join(tempDir, "block.pb")
   831  	err = ioutil.WriteFile(blockPath, blockBytes, 0o644)
   832  	Expect(err).NotTo(HaveOccurred())
   833  	return blockPath
   834  }