Skip to content

Commit

Permalink
[FAB-2773] Restrict the total count of channels
Browse files Browse the repository at this point in the history
https://jira.hyperledger.org/browse/FAB-2773

There is a risk that a malicious user with channel creation rights could
simply create an unlimited number of channels until exhausting the
resources of the ordering network, causing it to fail.

This CR introduces a new genesis block parameter ChannelRestrictions
which for now, contains only max_count.  This parameter is checked
before accepting a new channel creation request.

Note that this must be a genesis parameter so that it is synchronized
across the network and all orderers agree to accept or reject channel
creation requests deterministically.

Change-Id: I582963925e05d50e6bad6d1aa7d2a490c0e45cc1
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick committed Mar 14, 2017
1 parent 6e9229b commit 844fe2d
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 20 deletions.
3 changes: 3 additions & 0 deletions common/config/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ type Orderer interface {
// This field is only set for the system ordering chain
ChainCreationPolicyNames() []string

// MaxChannelsCount returns the maximum count of channels to allow for an ordering network
MaxChannelsCount() uint64

// KafkaBrokers returns the addresses (IP:port notation) of a set of "bootstrap"
// Kafka brokers, i.e. this is not necessarily the entire set of Kafka brokers
// used for ordering
Expand Down
9 changes: 9 additions & 0 deletions common/config/orderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ const (
// ChainCreationPolicyNamesKey is the cb.ConfigItem type key name for the ChainCreationPolicyNames message
ChainCreationPolicyNamesKey = "ChainCreationPolicyNames"

// ChannelRestrictions is the key name for the ChannelRestrictions message
ChannelRestrictionsKey = "ChannelRestrictions"

// KafkaBrokersKey is the cb.ConfigItem type key name for the KafkaBrokers message
KafkaBrokersKey = "KafkaBrokers"
)
Expand All @@ -57,6 +60,7 @@ type OrdererProtos struct {
ChainCreationPolicyNames *ab.ChainCreationPolicyNames
KafkaBrokers *ab.KafkaBrokers
CreationPolicy *ab.CreationPolicy
ChannelRestrictions *ab.ChannelRestrictions
}

// Config is stores the orderer component configuration
Expand Down Expand Up @@ -142,6 +146,11 @@ func (oc *OrdererConfig) KafkaBrokers() []string {
return oc.protos.KafkaBrokers.Brokers
}

// MaxChannelsCount returns the maximum count of channels this orderer supports
func (oc *OrdererConfig) MaxChannelsCount() uint64 {
return oc.protos.ChannelRestrictions.MaxCount
}

func (oc *OrdererConfig) Validate(tx interface{}, groups map[string]ValueProposer) error {
for _, validator := range []func() error{
oc.validateConsensusType,
Expand Down
5 changes: 5 additions & 0 deletions common/config/orderer_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ func TemplateChainCreationPolicyNames(names []string) *cb.ConfigGroup {
return ordererConfigGroup(ChainCreationPolicyNamesKey, utils.MarshalOrPanic(&ab.ChainCreationPolicyNames{Names: names}))
}

// TemplateChannelRestrictions creates a config group with ChannelRestrictions specified
func TemplateChannelRestrictions(maxChannels uint64) *cb.ConfigGroup {
return ordererConfigGroup(ChannelRestrictionsKey, utils.MarshalOrPanic(&ab.ChannelRestrictions{MaxCount: maxChannels}))
}

// TemplateKafkaBrokers creates a headerless config item representing the kafka brokers
func TemplateKafkaBrokers(brokers []string) *cb.ConfigGroup {
return ordererConfigGroup(KafkaBrokersKey, utils.MarshalOrPanic(&ab.KafkaBrokers{Brokers: brokers}))
Expand Down
4 changes: 4 additions & 0 deletions common/configtx/tool/configtx.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ Orderer: &OrdererDefaults
# bytes.
PreferredMaxBytes: 512 KB

# Max Channels is the maximum number of channels to allow on the ordering network
# When set to 0, this implies no maximum number of channels
MaxChannels: 0

Kafka:
# Brokers: A list of Kafka brokers to which the orderer connects.
# NOTE: Use IP:port notation
Expand Down
1 change: 1 addition & 0 deletions common/configtx/tool/localconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type Orderer struct {
BatchSize BatchSize `yaml:"BatchSize"`
Kafka Kafka `yaml:"Kafka"`
Organizations []*Organization `yaml:"Organizations"`
MaxChannels uint64 `yaml:"MaxChannels"`
}

// BatchSize contains configuration affecting the size of batches
Expand Down
1 change: 1 addition & 0 deletions common/configtx/tool/provisional/provisional.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ func New(conf *genesisconfig.Profile) Generator {
PreferredMaxBytes: conf.Orderer.BatchSize.PreferredMaxBytes,
}),
config.TemplateBatchTimeout(conf.Orderer.BatchTimeout.String()),
config.TemplateChannelRestrictions(conf.Orderer.MaxChannels),

// Initialize the default Reader/Writer/Admins orderer policies, as well as block validation policy
policies.TemplateImplicitMetaPolicyWithSubPolicy([]string{config.OrdererGroupKey}, BlockValidationPolicyKey, configvaluesmsp.WritersPolicyKey, cb.ImplicitMetaPolicy_ANY),
Expand Down
7 changes: 7 additions & 0 deletions common/mocks/configvalues/channel/orderer/sharedconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ type SharedConfig struct {
IngressPolicyNamesVal []string
// EgressPolicyNamesVal is returned as the result of EgressPolicyNames()
EgressPolicyNamesVal []string
// MaxChannelsCountVal is returns as the result of MaxChannelsCount()
MaxChannelsCountVal uint64
}

// ConsensusType returns the ConsensusTypeVal
Expand Down Expand Up @@ -62,6 +64,11 @@ func (scm *SharedConfig) KafkaBrokers() []string {
return scm.KafkaBrokersVal
}

// MaxChannelsCount returns the MaxChannelsCountVal
func (scm *SharedConfig) MaxChannelsCount() uint64 {
return scm.MaxChannelsCountVal
}

// IngressPolicyNames returns the IngressPolicyNamesVal
func (scm *SharedConfig) IngressPolicyNames() []string {
return scm.IngressPolicyNamesVal
Expand Down
4 changes: 4 additions & 0 deletions orderer/multichain/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,7 @@ func (ml *multiLedger) newChain(configtx *cb.Envelope) {

ml.chains = newChains
}

func (ml *multiLedger) channelsCount() int {
return len(ml.chains)
}
10 changes: 10 additions & 0 deletions orderer/multichain/systemchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
// Define some internal interfaces for easier mocking
type chainCreator interface {
newChain(configTx *cb.Envelope)
channelsCount() int
}

type limitedSupport interface {
Expand Down Expand Up @@ -87,6 +88,15 @@ func (scf *systemChainFilter) Apply(env *cb.Envelope) (filter.Action, filter.Com
return filter.Forward, nil
}

maxChannels := scf.support.SharedConfig().MaxChannelsCount()
if maxChannels > 0 {
// We check for strictly greater than to accomodate the system channel
if uint64(scf.cc.channelsCount()) > maxChannels {
logger.Warningf("Rejecting channel creation because the orderer has reached the maximum number of channels, %d", maxChannels)
return filter.Reject, nil
}
}

configTx := &cb.Envelope{}
err = proto.Unmarshal(msgData.Data, configTx)
if err != nil {
Expand Down
26 changes: 26 additions & 0 deletions orderer/multichain/systemchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func (mcc *mockChainCreator) newChain(configTx *cb.Envelope) {
mcc.newChains = append(mcc.newChains, configTx)
}

func (mcc *mockChainCreator) channelsCount() int {
return len(mcc.newChains)
}

func TestGoodProposal(t *testing.T) {
newChainID := "NewChainID"

Expand Down Expand Up @@ -131,3 +135,25 @@ func TestProposalWithMissingPolicy(t *testing.T) {

assert.EqualValues(t, filter.Reject, action, "Transaction had missing policy")
}

func TestNumChainsExceeded(t *testing.T) {
newChainID := "NewChainID"

mcc := newMockChainCreator()
mcc.ms.msc.ChainCreationPolicyNamesVal = []string{provisional.AcceptAllPolicyKey}
mcc.ms.mpm.Policy = &mockpolicies.Policy{}
mcc.ms.msc.MaxChannelsCountVal = 1
mcc.newChains = make([]*cb.Envelope, 2)

configEnv, err := configtx.NewChainCreationTemplate(provisional.AcceptAllPolicyKey, configtx.NewCompositeTemplate()).Envelope(newChainID)
if err != nil {
t.Fatalf("Error constructing configtx")
}
ingressTx := makeConfigTxFromConfigUpdateEnvelope(newChainID, configEnv)
wrapped := wrapConfigTx(ingressTx)

sysFilter := newSystemChainFilter(mcc.ms, mcc)
action, _ := sysFilter.Apply(wrapped)

assert.EqualValues(t, filter.Reject, action, "Transaction had created too many channels")
}
1 change: 1 addition & 0 deletions protos/orderer/ab.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 33 additions & 20 deletions protos/orderer/configuration.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions protos/orderer/configuration.proto
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,8 @@ message KafkaBrokers {
// e.g. 127.0.0.1:7050, or localhost:7050 are valid entries
repeated string brokers = 1;
}

// ChannelRestrictions is the mssage which conveys restrictions on channel creation for an orderer
message ChannelRestrictions {
uint64 max_count = 1; // The max count of channels to allow to be created, a value of 0 indicates no limit
}

0 comments on commit 844fe2d

Please sign in to comment.