838 lines
24 KiB
Go
838 lines
24 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
cb "github.com/hyperledger/fabric-protos-go/common"
|
|
"github.com/hyperledger/fabric/bccsp"
|
|
"github.com/hyperledger/fabric/cmd/osnadmin/mocks"
|
|
"github.com/hyperledger/fabric/common/crypto/tlsgen"
|
|
"github.com/hyperledger/fabric/orderer/common/channelparticipation"
|
|
"github.com/hyperledger/fabric/orderer/common/localconfig"
|
|
"github.com/hyperledger/fabric/orderer/common/types"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
var _ = Describe("osnadmin", func() {
|
|
var (
|
|
tempDir string
|
|
ordererCACert string
|
|
clientCert string
|
|
clientKey string
|
|
mockChannelManagement *mocks.ChannelManagement
|
|
testServer *httptest.Server
|
|
tlsConfig *tls.Config
|
|
ordererURL string
|
|
channelID string
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
var err error
|
|
tempDir, err = os.MkdirTemp("", "osnadmin")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
generateCertificates(tempDir)
|
|
|
|
ordererCACert = filepath.Join(tempDir, "server-ca.pem")
|
|
clientCert = filepath.Join(tempDir, "client-cert.pem")
|
|
clientKey = filepath.Join(tempDir, "client-key.pem")
|
|
|
|
channelID = "testing123"
|
|
|
|
config := localconfig.ChannelParticipation{
|
|
Enabled: true,
|
|
MaxRequestBodySize: 1024 * 1024,
|
|
}
|
|
mockChannelManagement = &mocks.ChannelManagement{}
|
|
|
|
h := channelparticipation.NewHTTPHandler(config, mockChannelManagement)
|
|
Expect(h).NotTo(BeNil())
|
|
testServer = httptest.NewUnstartedServer(h)
|
|
|
|
cert, err := tls.LoadX509KeyPair(
|
|
filepath.Join(tempDir, "server-cert.pem"),
|
|
filepath.Join(tempDir, "server-key.pem"),
|
|
)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
caCertPool := x509.NewCertPool()
|
|
clientCAPem, err := ioutil.ReadFile(filepath.Join(tempDir, "client-ca.pem"))
|
|
Expect(err).NotTo(HaveOccurred())
|
|
caCertPool.AppendCertsFromPEM(clientCAPem)
|
|
|
|
tlsConfig = &tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
ClientCAs: caCertPool,
|
|
ClientAuth: tls.RequireAndVerifyClientCert,
|
|
}
|
|
})
|
|
|
|
JustBeforeEach(func() {
|
|
if tlsConfig != nil {
|
|
testServer.TLS = tlsConfig
|
|
testServer.StartTLS()
|
|
} else {
|
|
testServer.Start()
|
|
}
|
|
|
|
u, err := url.Parse(testServer.URL)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
ordererURL = u.Host
|
|
})
|
|
|
|
AfterEach(func() {
|
|
os.RemoveAll(tempDir)
|
|
testServer.Close()
|
|
})
|
|
|
|
Describe("List", func() {
|
|
BeforeEach(func() {
|
|
mockChannelManagement.ChannelListReturns(types.ChannelList{
|
|
Channels: []types.ChannelInfoShort{
|
|
{
|
|
Name: "participation-trophy",
|
|
},
|
|
{
|
|
Name: "another-participation-trophy",
|
|
},
|
|
},
|
|
SystemChannel: &types.ChannelInfoShort{
|
|
Name: "fight-the-system",
|
|
},
|
|
})
|
|
|
|
mockChannelManagement.ChannelInfoReturns(types.ChannelInfo{
|
|
Name: "asparagus",
|
|
ConsensusRelation: "broccoli",
|
|
Status: "carrot",
|
|
Height: 987,
|
|
}, nil)
|
|
})
|
|
|
|
It("uses the channel participation API to list all application channels and the system channel (when it exists)", func() {
|
|
args := []string{
|
|
"channel",
|
|
"list",
|
|
"--orderer-address", ordererURL,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ChannelList{
|
|
Channels: []types.ChannelInfoShort{
|
|
{
|
|
Name: "participation-trophy",
|
|
URL: "/participation/v1/channels/participation-trophy",
|
|
},
|
|
{
|
|
Name: "another-participation-trophy",
|
|
URL: "/participation/v1/channels/another-participation-trophy",
|
|
},
|
|
},
|
|
SystemChannel: &types.ChannelInfoShort{
|
|
Name: "fight-the-system",
|
|
URL: "/participation/v1/channels/fight-the-system",
|
|
},
|
|
}
|
|
checkStatusOutput(output, exit, err, 200, expectedOutput)
|
|
})
|
|
|
|
It("uses the channel participation API to list the details of a single channel", func() {
|
|
args := []string{
|
|
"channel",
|
|
"list",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", "tell-me-your-secrets",
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ChannelInfo{
|
|
Name: "asparagus",
|
|
URL: "/participation/v1/channels/asparagus",
|
|
ConsensusRelation: "broccoli",
|
|
Status: "carrot",
|
|
Height: 987,
|
|
}
|
|
checkStatusOutput(output, exit, err, 200, expectedOutput)
|
|
})
|
|
|
|
Context("when the channel does not exist", func() {
|
|
BeforeEach(func() {
|
|
mockChannelManagement.ChannelInfoReturns(types.ChannelInfo{}, errors.New("eat-your-peas"))
|
|
})
|
|
|
|
It("returns 404 not found", func() {
|
|
args := []string{
|
|
"channel",
|
|
"list",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", "tell-me-your-secrets",
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ErrorResponse{
|
|
Error: "eat-your-peas",
|
|
}
|
|
checkStatusOutput(output, exit, err, 404, expectedOutput)
|
|
})
|
|
})
|
|
|
|
Context("when TLS is disabled", func() {
|
|
BeforeEach(func() {
|
|
tlsConfig = nil
|
|
})
|
|
|
|
It("uses the channel participation API to list all channels", func() {
|
|
args := []string{
|
|
"channel",
|
|
"list",
|
|
"--orderer-address", ordererURL,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(exit).To(Equal(0))
|
|
|
|
expectedOutput := types.ChannelList{
|
|
Channels: []types.ChannelInfoShort{
|
|
{
|
|
Name: "participation-trophy",
|
|
URL: "/participation/v1/channels/participation-trophy",
|
|
},
|
|
{
|
|
Name: "another-participation-trophy",
|
|
URL: "/participation/v1/channels/another-participation-trophy",
|
|
},
|
|
},
|
|
SystemChannel: &types.ChannelInfoShort{
|
|
Name: "fight-the-system",
|
|
URL: "/participation/v1/channels/fight-the-system",
|
|
},
|
|
}
|
|
checkStatusOutput(output, exit, err, 200, expectedOutput)
|
|
})
|
|
|
|
It("uses the channel participation API to list the details of a single channel", func() {
|
|
args := []string{
|
|
"channel",
|
|
"list",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", "tell-me-your-secrets",
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(exit).To(Equal(0))
|
|
|
|
expectedOutput := types.ChannelInfo{
|
|
Name: "asparagus",
|
|
URL: "/participation/v1/channels/asparagus",
|
|
ConsensusRelation: "broccoli",
|
|
Status: "carrot",
|
|
Height: 987,
|
|
}
|
|
checkStatusOutput(output, exit, err, 200, expectedOutput)
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("Remove", func() {
|
|
It("uses the channel participation API to remove a channel", func() {
|
|
args := []string{
|
|
"channel",
|
|
"remove",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(exit).To(Equal(0))
|
|
Expect(output).To(Equal("Status: 204\n"))
|
|
})
|
|
|
|
It("uses the channel participation API to remove a channel (without status)", func() {
|
|
args := []string{
|
|
"channel",
|
|
"remove",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
"--no-status",
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(exit).To(Equal(0))
|
|
Expect(output).To(BeEmpty())
|
|
})
|
|
|
|
Context("when the channel does not exist", func() {
|
|
BeforeEach(func() {
|
|
mockChannelManagement.RemoveChannelReturns(types.ErrChannelNotExist)
|
|
})
|
|
|
|
It("returns 404 not found", func() {
|
|
args := []string{
|
|
"channel",
|
|
"remove",
|
|
"--ca-file", ordererCACert,
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ErrorResponse{
|
|
Error: "cannot remove: channel does not exist",
|
|
}
|
|
checkStatusOutput(output, exit, err, 404, expectedOutput)
|
|
})
|
|
})
|
|
|
|
Context("when TLS is disabled", func() {
|
|
BeforeEach(func() {
|
|
tlsConfig = nil
|
|
})
|
|
|
|
It("uses the channel participation API to remove a channel", func() {
|
|
args := []string{
|
|
"channel",
|
|
"remove",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(exit).To(Equal(0))
|
|
Expect(output).To(Equal("Status: 204\n"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("Join", func() {
|
|
var blockPath string
|
|
|
|
BeforeEach(func() {
|
|
configBlock := blockWithGroups(
|
|
map[string]*cb.ConfigGroup{
|
|
"Application": {},
|
|
},
|
|
"testing123",
|
|
)
|
|
blockPath = createBlockFile(tempDir, configBlock)
|
|
|
|
mockChannelManagement.JoinChannelReturns(types.ChannelInfo{
|
|
Name: "apple",
|
|
ConsensusRelation: "banana",
|
|
Status: "orange",
|
|
Height: 123,
|
|
}, nil)
|
|
})
|
|
|
|
It("uses the channel participation API to join a channel", func() {
|
|
args := []string{
|
|
"channel",
|
|
"join",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--config-block", blockPath,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ChannelInfo{
|
|
Name: "apple",
|
|
URL: "/participation/v1/channels/apple",
|
|
ConsensusRelation: "banana",
|
|
Status: "orange",
|
|
Height: 123,
|
|
}
|
|
checkStatusOutput(output, exit, err, 201, expectedOutput)
|
|
})
|
|
|
|
Context("when the block is empty", func() {
|
|
BeforeEach(func() {
|
|
blockPath = createBlockFile(tempDir, &cb.Block{})
|
|
})
|
|
|
|
It("returns with exit code 1 and prints the error", func() {
|
|
args := []string{
|
|
"channel",
|
|
"join",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--config-block", blockPath,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
|
|
checkFlagError(output, exit, err, "failed to retrieve channel id - block is empty")
|
|
})
|
|
})
|
|
|
|
Context("when the --channelID does not match the channel ID in the block", func() {
|
|
BeforeEach(func() {
|
|
channelID = "not-the-channel-youre-looking-for"
|
|
})
|
|
|
|
It("returns with exit code 1 and prints the error", func() {
|
|
args := []string{
|
|
"channel",
|
|
"join",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--config-block", blockPath,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
|
|
checkFlagError(output, exit, err, "specified --channelID not-the-channel-youre-looking-for does not match channel ID testing123 in config block")
|
|
})
|
|
})
|
|
|
|
Context("when the block isn't a valid config block", func() {
|
|
BeforeEach(func() {
|
|
block := &cb.Block{
|
|
Data: &cb.BlockData{
|
|
Data: [][]byte{
|
|
protoutil.MarshalOrPanic(&cb.Envelope{
|
|
Payload: protoutil.MarshalOrPanic(&cb.Payload{
|
|
Header: &cb.Header{
|
|
ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
|
|
Type: int32(cb.HeaderType_ENDORSER_TRANSACTION),
|
|
ChannelId: channelID,
|
|
}),
|
|
},
|
|
}),
|
|
}),
|
|
},
|
|
},
|
|
}
|
|
blockPath = createBlockFile(tempDir, block)
|
|
})
|
|
|
|
It("returns 405 bad request", func() {
|
|
args := []string{
|
|
"channel",
|
|
"join",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--config-block", blockPath,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(exit).To(Equal(0))
|
|
|
|
expectedOutput := types.ErrorResponse{
|
|
Error: "invalid join block: block is not a config block",
|
|
}
|
|
checkStatusOutput(output, exit, err, 400, expectedOutput)
|
|
})
|
|
})
|
|
|
|
Context("when joining the channel fails", func() {
|
|
BeforeEach(func() {
|
|
mockChannelManagement.JoinChannelReturns(types.ChannelInfo{}, types.ErrChannelAlreadyExists)
|
|
})
|
|
|
|
It("returns 405 not allowed", func() {
|
|
args := []string{
|
|
"channel",
|
|
"join",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--config-block", blockPath,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ErrorResponse{
|
|
Error: "cannot join: channel already exists",
|
|
}
|
|
checkStatusOutput(output, exit, err, 405, expectedOutput)
|
|
})
|
|
|
|
It("returns 405 not allowed (without status)", func() {
|
|
args := []string{
|
|
"channel",
|
|
"join",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--config-block", blockPath,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
"--no-status",
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ErrorResponse{
|
|
Error: "cannot join: channel already exists",
|
|
}
|
|
checkOutput(output, exit, err, expectedOutput)
|
|
})
|
|
})
|
|
|
|
Context("when TLS is disabled", func() {
|
|
BeforeEach(func() {
|
|
tlsConfig = nil
|
|
})
|
|
|
|
It("uses the channel participation API to join a channel", func() {
|
|
args := []string{
|
|
"channel",
|
|
"join",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--config-block", blockPath,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ChannelInfo{
|
|
Name: "apple",
|
|
URL: "/participation/v1/channels/apple",
|
|
ConsensusRelation: "banana",
|
|
Status: "orange",
|
|
Height: 123,
|
|
}
|
|
checkStatusOutput(output, exit, err, 201, expectedOutput)
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("Flags", func() {
|
|
It("accepts short versions of the --orderer-address, --channelID, and --config-block flags", func() {
|
|
configBlock := blockWithGroups(
|
|
map[string]*cb.ConfigGroup{
|
|
"Application": {},
|
|
},
|
|
"testing123",
|
|
)
|
|
blockPath := createBlockFile(tempDir, configBlock)
|
|
mockChannelManagement.JoinChannelReturns(types.ChannelInfo{
|
|
Name: "apple",
|
|
ConsensusRelation: "banana",
|
|
Status: "orange",
|
|
Height: 123,
|
|
}, nil)
|
|
|
|
args := []string{
|
|
"channel",
|
|
"join",
|
|
"-o", ordererURL,
|
|
"-c", channelID,
|
|
"-b", blockPath,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ChannelInfo{
|
|
Name: "apple",
|
|
URL: "/participation/v1/channels/apple",
|
|
ConsensusRelation: "banana",
|
|
Status: "orange",
|
|
Height: 123,
|
|
}
|
|
checkStatusOutput(output, exit, err, 201, expectedOutput)
|
|
})
|
|
|
|
Context("when an unknown flag is used", func() {
|
|
It("returns an error for long flags", func() {
|
|
_, _, err := executeForArgs([]string{"channel", "list", "--bad-flag"})
|
|
Expect(err).To(MatchError("unknown long flag '--bad-flag'"))
|
|
})
|
|
|
|
It("returns an error for short flags", func() {
|
|
_, _, err := executeForArgs([]string{"channel", "list", "-z"})
|
|
Expect(err).To(MatchError("unknown short flag '-z'"))
|
|
})
|
|
})
|
|
|
|
Context("when the ca cert cannot be read", func() {
|
|
BeforeEach(func() {
|
|
ordererCACert = "not-the-ca-cert-youre-looking-for"
|
|
})
|
|
|
|
It("returns with exit code 1 and prints the error", func() {
|
|
args := []string{
|
|
"channel",
|
|
"list",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
checkFlagError(output, exit, err, "reading orderer CA certificate: open not-the-ca-cert-youre-looking-for: no such file or directory")
|
|
})
|
|
})
|
|
|
|
Context("when the ca-file contains a private key instead of certificate(s)", func() {
|
|
BeforeEach(func() {
|
|
ordererCACert = clientKey
|
|
})
|
|
|
|
It("returns with exit code 1 and prints the error", func() {
|
|
args := []string{
|
|
"channel",
|
|
"remove",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
checkFlagError(output, exit, err, "failed to add ca-file PEM to cert pool")
|
|
})
|
|
})
|
|
|
|
Context("when the client cert/key pair fail to load", func() {
|
|
BeforeEach(func() {
|
|
clientKey = "brussel-sprouts"
|
|
})
|
|
|
|
It("returns with exit code 1 and prints the error", func() {
|
|
args := []string{
|
|
"channel",
|
|
"list",
|
|
"--orderer-address", ordererURL,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
checkFlagError(output, exit, err, "loading client cert/key pair: open brussel-sprouts: no such file or directory")
|
|
})
|
|
})
|
|
|
|
Context("when the config block cannot be read", func() {
|
|
var configBlockPath string
|
|
|
|
BeforeEach(func() {
|
|
configBlockPath = "not-the-config-block-youre-looking-for"
|
|
})
|
|
|
|
It("returns with exit code 1 and prints the error", func() {
|
|
args := []string{
|
|
"channel",
|
|
"join",
|
|
"--orderer-address", ordererURL,
|
|
"--channelID", channelID,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
"--config-block", configBlockPath,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
checkFlagError(output, exit, err, "reading config block: open not-the-config-block-youre-looking-for: no such file or directory")
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("Server using intermediate CA", func() {
|
|
BeforeEach(func() {
|
|
cert, err := tls.LoadX509KeyPair(
|
|
filepath.Join(tempDir, "server-intermediate-cert.pem"),
|
|
filepath.Join(tempDir, "server-intermediate-key.pem"),
|
|
)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
|
|
|
ordererCACert = filepath.Join(tempDir, "server-ca+intermediate-ca.pem")
|
|
})
|
|
|
|
It("uses the channel participation API to list all application and the system channel (when it exists)", func() {
|
|
args := []string{
|
|
"channel",
|
|
"list",
|
|
"--orderer-address", ordererURL,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
expectedOutput := types.ChannelList{
|
|
Channels: nil,
|
|
SystemChannel: nil,
|
|
}
|
|
checkStatusOutput(output, exit, err, 200, expectedOutput)
|
|
})
|
|
|
|
Context("when the ca-file does not include the intermediate CA", func() {
|
|
BeforeEach(func() {
|
|
ordererCACert = filepath.Join(tempDir, "server-ca.pem")
|
|
})
|
|
It("returns with exit code 1 and prints the error", func() {
|
|
args := []string{
|
|
"channel",
|
|
"list",
|
|
"--orderer-address", ordererURL,
|
|
"--ca-file", ordererCACert,
|
|
"--client-cert", clientCert,
|
|
"--client-key", clientKey,
|
|
}
|
|
output, exit, err := executeForArgs(args)
|
|
checkCLIError(output, exit, err, fmt.Sprintf("Get \"%s/participation/v1/channels\": tls: failed to verify certificate: x509: certificate signed by unknown authority", testServer.URL))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
func checkStatusOutput(output string, exit int, err error, expectedStatus int, expectedOutput interface{}) {
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(exit).To(Equal(0))
|
|
json, err := json.MarshalIndent(expectedOutput, "", "\t")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal(fmt.Sprintf("Status: %d\n%s\n", expectedStatus, string(json))))
|
|
}
|
|
|
|
func checkOutput(output string, exit int, err error, expectedOutput interface{}) {
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(exit).To(Equal(0))
|
|
json, err := json.MarshalIndent(expectedOutput, "", "\t")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(output).To(Equal(string(json) + "\n"))
|
|
}
|
|
|
|
func checkFlagError(output string, exit int, err error, expectedError string) {
|
|
Expect(err).To(MatchError(ContainSubstring(expectedError)))
|
|
Expect(exit).To(Equal(1))
|
|
Expect(output).To(BeEmpty())
|
|
}
|
|
|
|
func checkCLIError(output string, exit int, err error, expectedError string) {
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(exit).To(Equal(1))
|
|
Expect(output).To(Equal(fmt.Sprintf("Error: %s\n", expectedError)))
|
|
}
|
|
|
|
func generateCertificates(tempDir string) {
|
|
serverCA, err := tlsgen.NewCA()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "server-ca.pem"), serverCA.CertBytes(), 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
serverKeyPair, err := serverCA.NewServerCertKeyPair("127.0.0.1")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "server-cert.pem"), serverKeyPair.Cert, 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "server-key.pem"), serverKeyPair.Key, 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
serverIntermediateCA, err := serverCA.NewIntermediateCA()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "server-intermediate-ca.pem"), serverIntermediateCA.CertBytes(), 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
serverIntermediateKeyPair, err := serverIntermediateCA.NewServerCertKeyPair("127.0.0.1")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "server-intermediate-cert.pem"), serverIntermediateKeyPair.Cert, 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "server-intermediate-key.pem"), serverIntermediateKeyPair.Key, 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
serverAndIntermediateCABytes := append(serverCA.CertBytes(), serverIntermediateCA.CertBytes()...)
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "server-ca+intermediate-ca.pem"), serverAndIntermediateCABytes, 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
clientCA, err := tlsgen.NewCA()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "client-ca.pem"), clientCA.CertBytes(), 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
clientKeyPair, err := clientCA.NewClientCertKeyPair()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "client-cert.pem"), clientKeyPair.Cert, 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
err = ioutil.WriteFile(filepath.Join(tempDir, "client-key.pem"), clientKeyPair.Key, 0o640)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
}
|
|
|
|
func blockWithGroups(groups map[string]*cb.ConfigGroup, channelID string) *cb.Block {
|
|
block := protoutil.NewBlock(0, []byte{})
|
|
block.Data = &cb.BlockData{
|
|
Data: [][]byte{
|
|
protoutil.MarshalOrPanic(&cb.Envelope{
|
|
Payload: protoutil.MarshalOrPanic(&cb.Payload{
|
|
Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{
|
|
Config: &cb.Config{
|
|
ChannelGroup: &cb.ConfigGroup{
|
|
Groups: groups,
|
|
Values: map[string]*cb.ConfigValue{
|
|
"HashingAlgorithm": {
|
|
Value: protoutil.MarshalOrPanic(&cb.HashingAlgorithm{
|
|
Name: bccsp.SHA256,
|
|
}),
|
|
},
|
|
"BlockDataHashingStructure": {
|
|
Value: protoutil.MarshalOrPanic(&cb.BlockDataHashingStructure{
|
|
Width: math.MaxUint32,
|
|
}),
|
|
},
|
|
"OrdererAddresses": {
|
|
Value: protoutil.MarshalOrPanic(&cb.OrdererAddresses{
|
|
Addresses: []string{"localhost"},
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
Header: &cb.Header{
|
|
ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
|
|
Type: int32(cb.HeaderType_CONFIG),
|
|
ChannelId: channelID,
|
|
}),
|
|
},
|
|
}),
|
|
}),
|
|
},
|
|
}
|
|
block.Header.DataHash = protoutil.BlockDataHash(block.Data)
|
|
protoutil.InitBlockMetadata(block)
|
|
|
|
return block
|
|
}
|
|
|
|
func createBlockFile(tempDir string, configBlock *cb.Block) string {
|
|
blockBytes, err := proto.Marshal(configBlock)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
blockPath := filepath.Join(tempDir, "block.pb")
|
|
err = ioutil.WriteFile(blockPath, blockBytes, 0o644)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
return blockPath
|
|
}
|