Commit 7d06d382 authored by Jay Guo's avatar Jay Guo
Browse files

FAB-14821 validate Raft config metadata



While creating new channel, the Raft config metadata supplied
should be validated before being proposed and consented, to
avoid crashing the orderer due to malformed configs.

Change-Id: Ie7cbd9ff609c1cb04e891dd05d49fa8a80195c98
Signed-off-by: default avatarJay Guo <guojiannan1101@gmail.com>
parent 65061e71
......@@ -404,6 +404,7 @@ func (c *Chain) Order(env *common.Envelope, configSeq uint64) error {
func (c *Chain) Configure(env *common.Envelope, configSeq uint64) error {
c.Metrics.ConfigProposalsReceived.Add(1)
if err := c.checkConfigUpdateValidity(env); err != nil {
c.logger.Warnf("Rejected config: %s", err)
c.Metrics.ProposalFailures.Add(1)
return err
}
......@@ -424,7 +425,36 @@ func (c *Chain) checkConfigUpdateValidity(ctx *common.Envelope) error {
switch chdr.Type {
case int32(common.HeaderType_ORDERER_TRANSACTION):
return nil
newChannelConfig, err := utils.UnmarshalEnvelope(payload.Data)
if err != nil {
return err
}
payload, err = utils.UnmarshalPayload(newChannelConfig.Payload)
if err != nil {
return err
}
configUpdate, err := configtx.UnmarshalConfigUpdateFromPayload(payload)
if err != nil {
return err
}
metadata, err := MetadataFromConfigUpdate(configUpdate)
if err != nil {
return err
}
if metadata == nil {
return nil // ConsensusType is not updated
}
c.raftMetadataLock.RLock()
set := MembershipByCert(c.opts.Consenters)
c.raftMetadataLock.RUnlock()
return CheckConfigMetadata(metadata, set)
case int32(common.HeaderType_CONFIG):
configUpdate, err := configtx.UnmarshalConfigUpdateFromPayload(payload)
......
......@@ -1579,8 +1579,25 @@ var _ = Describe("Chain", func() {
defaultTimeout = 5 * time.Second
)
var (
options = &raftprotos.Options{
TickInterval: "500ms",
ElectionTick: 10,
HeartbeatTick: 1,
MaxInflightBlocks: 5,
SnapshotIntervalSize: 200,
}
updateRaftConfigValue = func(metadata *raftprotos.ConfigMetadata) map[string]*common.ConfigValue {
return map[string]*common.ConfigValue{
"ConsensusType": {
Version: 1,
Value: marshalOrPanic(&orderer.ConsensusType{
Metadata: marshalOrPanic(metadata),
}),
},
}
}
addConsenterConfigValue = func() map[string]*common.ConfigValue {
metadata := &raftprotos.ConfigMetadata{}
metadata := &raftprotos.ConfigMetadata{Options: options}
for _, consenter := range consenters {
metadata.Consenters = append(metadata.Consenters, consenter)
}
......@@ -1592,32 +1609,34 @@ var _ = Describe("Chain", func() {
ClientTlsCert: clientTLSCert(tlsCA),
}
metadata.Consenters = append(metadata.Consenters, newConsenter)
return map[string]*common.ConfigValue{
"ConsensusType": {
Version: 1,
Value: marshalOrPanic(&orderer.ConsensusType{
Metadata: marshalOrPanic(metadata),
}),
},
}
return updateRaftConfigValue(metadata)
}
removeConsenterConfigValue = func(id uint64) map[string]*common.ConfigValue {
metadata := &raftprotos.ConfigMetadata{}
metadata := &raftprotos.ConfigMetadata{Options: options}
for nodeID, consenter := range consenters {
if nodeID == id {
continue
}
metadata.Consenters = append(metadata.Consenters, consenter)
}
return updateRaftConfigValue(metadata)
}
createChannelEnv = func(metadata *raftprotos.ConfigMetadata) *common.Envelope {
configEnv := newConfigEnv("another-channel",
common.HeaderType_CONFIG,
newConfigUpdateEnv(channelID, nil, updateRaftConfigValue(metadata)))
return map[string]*common.ConfigValue{
"ConsensusType": {
Version: 1,
Value: marshalOrPanic(&orderer.ConsensusType{
Metadata: marshalOrPanic(metadata),
}),
},
// Wrap config env in Orderer transaction
return &common.Envelope{
Payload: marshalOrPanic(&common.Payload{
Header: &common.Header{
ChannelHeader: marshalOrPanic(&common.ChannelHeader{
Type: int32(common.HeaderType_ORDERER_TRANSACTION),
ChannelId: channelID,
}),
},
Data: marshalOrPanic(configEnv),
}),
}
}
)
......@@ -1649,6 +1668,111 @@ var _ = Describe("Chain", func() {
network.stop()
})
Context("channel creation", func() {
It("succeeds with valid config metadata", func() {
metadata := &raftprotos.ConfigMetadata{Options: options}
for _, consenter := range consenters {
metadata.Consenters = append(metadata.Consenters, consenter)
}
Expect(c1.Configure(createChannelEnv(metadata), 0)).To(Succeed())
network.exec(func(c *chain) {
Eventually(c.support.WriteConfigBlockCallCount, LongEventualTimeout).Should(Equal(1))
})
})
It("fails with invalid timeout", func() {
metadata := &raftprotos.ConfigMetadata{Options: proto.Clone(options).(*raftprotos.Options)}
metadata.Options.ElectionTick = metadata.Options.HeartbeatTick
for _, consenter := range consenters {
metadata.Consenters = append(metadata.Consenters, consenter)
}
Expect(c1.Configure(createChannelEnv(metadata), 0)).To(MatchError(
"ElectionTick (1) must be greater than HeartbeatTick (1)"))
})
It("fails with zero ElectionTick", func() {
metadata := &raftprotos.ConfigMetadata{Options: proto.Clone(options).(*raftprotos.Options)}
metadata.Options.ElectionTick = 0
for _, consenter := range consenters {
metadata.Consenters = append(metadata.Consenters, consenter)
}
Expect(c1.Configure(createChannelEnv(metadata), 0)).To(MatchError(
"none of the fields in Raft config option can be zero"))
})
It("fails with zero max inflgith blocks", func() {
metadata := &raftprotos.ConfigMetadata{Options: proto.Clone(options).(*raftprotos.Options)}
metadata.Options.MaxInflightBlocks = 0
for _, consenter := range consenters {
metadata.Consenters = append(metadata.Consenters, consenter)
}
Expect(c1.Configure(createChannelEnv(metadata), 0)).To(MatchError(
"none of the fields in Raft config option can be zero"))
})
It("fails with invalid tick interval", func() {
metadata := &raftprotos.ConfigMetadata{Options: proto.Clone(options).(*raftprotos.Options)}
metadata.Options.TickInterval = "invalid"
for _, consenter := range consenters {
metadata.Consenters = append(metadata.Consenters, consenter)
}
Expect(c1.Configure(createChannelEnv(metadata), 0)).To(MatchError(ContainSubstring(
"failed to parse TickInterval (invalid) to time duration")))
})
It("fails with zero tick interval", func() {
metadata := &raftprotos.ConfigMetadata{Options: proto.Clone(options).(*raftprotos.Options)}
metadata.Options.TickInterval = "0"
for _, consenter := range consenters {
metadata.Consenters = append(metadata.Consenters, consenter)
}
Expect(c1.Configure(createChannelEnv(metadata), 0)).To(MatchError(ContainSubstring(
"TickInterval cannot be zero")))
})
It("fails with empty consenter set", func() {
metadata := &raftprotos.ConfigMetadata{Options: options}
Expect(c1.Configure(createChannelEnv(metadata), 0)).To(MatchError(
"cannot create channel with empty consenter set"))
})
It("fails with invalid certificate", func() {
metadata := &raftprotos.ConfigMetadata{Options: options}
for _, consenter := range consenters {
metadata.Consenters = append(metadata.Consenters, consenter)
}
metadata.Consenters[0].ClientTlsCert = []byte("Hello")
Expect(c1.Configure(createChannelEnv(metadata), 0)).To(MatchError(
"invalid client TLS cert: Hello"))
})
It("fails with extra consenter", func() {
metadata := &raftprotos.ConfigMetadata{Options: options}
for _, consenter := range consenters {
metadata.Consenters = append(metadata.Consenters, consenter)
}
metadata.Consenters = append(
metadata.Consenters,
&raftprotos.Consenter{
Host: "localhost",
Port: 7050,
ServerTlsCert: serverTLSCert(tlsCA),
ClientTlsCert: clientTLSCert(tlsCA),
})
Expect(c1.Configure(createChannelEnv(metadata), 0)).To(MatchError(
"new channel has consenter that is not part of system consenter set"))
})
})
Context("reconfiguration", func() {
It("cannot change consenter set by more than 1 node", func() {
metadata := &raftprotos.ConfigMetadata{}
......
......@@ -382,6 +382,60 @@ func ConsensusMetadataFromConfigBlock(block *common.Block) (*etcdraft.ConfigMeta
return MetadataFromConfigUpdate(configUpdate)
}
// CheckConfigMetadata validates Raft config metadata
func CheckConfigMetadata(metadata *etcdraft.ConfigMetadata, consenters map[string]uint64) error {
if metadata == nil {
// defensive check. this should not happen as CheckConfigMetadata
// should always be called with non-nil config metadata
return errors.Errorf("nil Raft config metadata")
}
if metadata.Options.HeartbeatTick == 0 ||
metadata.Options.ElectionTick == 0 ||
metadata.Options.MaxInflightBlocks == 0 {
return errors.Errorf("none of the fields in Raft config option can be zero")
}
// check Raft options
if metadata.Options.ElectionTick <= metadata.Options.HeartbeatTick {
return errors.Errorf("ElectionTick (%d) must be greater than HeartbeatTick (%d)",
metadata.Options.HeartbeatTick, metadata.Options.HeartbeatTick)
}
if d, err := time.ParseDuration(metadata.Options.TickInterval); err != nil {
return errors.Errorf("failed to parse TickInterval (%s) to time duration: %s", metadata.Options.TickInterval, err)
} else if d == 0 {
return errors.Errorf("TickInterval cannot be zero")
}
if len(metadata.Consenters) == 0 {
return errors.Errorf("cannot create channel with empty consenter set")
}
// sanity check of certificates
for _, consenter := range metadata.Consenters {
if bl, _ := pem.Decode(consenter.ServerTlsCert); bl == nil {
return errors.Errorf("invalid server TLS cert: %s", string(consenter.ServerTlsCert))
}
if bl, _ := pem.Decode(consenter.ClientTlsCert); bl == nil {
return errors.Errorf("invalid client TLS cert: %s", string(consenter.ClientTlsCert))
}
}
if err := MetadataHasDuplication(metadata); err != nil {
return err
}
for _, c := range metadata.Consenters {
if _, exits := consenters[string(c.ClientTlsCert)]; !exits {
return errors.Errorf("new channel has consenter that is not part of system consenter set")
}
}
return nil
}
// ConsenterCertificate denotes a TLS certificate of a consenter
type ConsenterCertificate []byte
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment