Skip to content

Commit 05a0edf

Browse files
committed
[FAB-1349] Enforce restrictions on chain IDs
https://jira.hyperledger.org/browse/FAB-1349 This changeset introduces a function which rejects chain IDs (i.e. channel names) that do NOT comply with the following restrictions: 1. Contain only ASCII alphanumerics, dots '.', dashes '-'. 2. Are shorter than 250 characters. 3. Are not "." or "..". Our hand here is forced by Kafka, since a channel in Fabric maps to a Kafka topic, and there are restrictions for the allowed topic names [1]. Note that underscores are allowed in Kafka, but topics with a period or underscore could collide. (Thanks to Luis Sanchez for bringing this to my attention.) We therefore remove support for underscores altoghether to keep the checks simpler. UPDATE: Switched from a filter to a function because filters are not evaluated during the system chain creation. [1] https://github.com/apache/kafka/blob/trunk/core/src/main/scala/kafka/common/Topic.scala#L29 Change-Id: I14b95477e485fea27868338e2f33772588b8a770 Signed-off-by: Kostas Christidis <[email protected]>
1 parent 7833d4d commit 05a0edf

File tree

4 files changed

+125
-5
lines changed

4 files changed

+125
-5
lines changed

common/configtx/manager.go

+48-3
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,29 @@ package configtx
1919
import (
2020
"fmt"
2121
"reflect"
22+
"regexp"
2223

2324
"github.com/hyperledger/fabric/common/policies"
2425
cb "github.com/hyperledger/fabric/protos/common"
2526

2627
"errors"
28+
2729
"github.com/golang/protobuf/proto"
2830
logging "github.com/op/go-logging"
2931
)
3032

3133
var logger = logging.MustGetLogger("common/configtx")
3234

35+
// Constraints for valid chain IDs
36+
var (
37+
allowedChars = "[a-zA-Z0-9.-]+"
38+
maxLength = 249
39+
illegalNames = map[string]struct{}{
40+
".": struct{}{},
41+
"..": struct{}{},
42+
}
43+
)
44+
3345
// Handler provides a hook which allows other pieces of code to participate in config proposals
3446
type Handler interface {
3547
// BeginConfig called when a config proposal is begun
@@ -91,8 +103,7 @@ func computeChainIDAndSequence(configtx *cb.ConfigurationEnvelope) (string, uint
91103

92104
for _, signedItem := range configtx.Items {
93105
item := &cb.ConfigurationItem{}
94-
err := proto.Unmarshal(signedItem.ConfigurationItem, item)
95-
if err != nil {
106+
if err := proto.Unmarshal(signedItem.ConfigurationItem, item); err != nil {
96107
return "", 0, fmt.Errorf("Error unmarshaling signedItem.ConfigurationItem: %s", err)
97108
}
98109

@@ -115,11 +126,45 @@ func computeChainIDAndSequence(configtx *cb.ConfigurationEnvelope) (string, uint
115126
return "", 0, fmt.Errorf("Mismatched chainIDs in envelope %s != %s", chainID, item.Header.ChainID)
116127
}
117128
}
129+
130+
if err := validateChainID(chainID); err != nil {
131+
return "", 0, err
132+
}
118133
}
119134

120135
return chainID, m, nil
121136
}
122137

138+
// validateChainID makes sure that proposed chain IDs (i.e. channel names)
139+
// comply with the following restrictions:
140+
// 1. Contain only ASCII alphanumerics, dots '.', dashes '-'
141+
// 2. Are shorter than 250 characters.
142+
// 3. Are not the strings "." or "..".
143+
//
144+
// Our hand here is forced by:
145+
// https://github.com/apache/kafka/blob/trunk/core/src/main/scala/kafka/common/Topic.scala#L29
146+
func validateChainID(chainID string) error {
147+
re, _ := regexp.Compile(allowedChars)
148+
// Length
149+
if len(chainID) <= 0 {
150+
return fmt.Errorf("chain ID illegal, cannot be empty")
151+
}
152+
if len(chainID) > maxLength {
153+
return fmt.Errorf("chain ID illegal, cannot be longer than %d", maxLength)
154+
}
155+
// Illegal name
156+
if _, ok := illegalNames[chainID]; ok {
157+
return fmt.Errorf("name '%s' for chain ID is not allowed", chainID)
158+
}
159+
// Illegal characters
160+
matched := re.FindString(chainID)
161+
if len(matched) != len(chainID) {
162+
return fmt.Errorf("Chain ID '%s' contains illegal characters", chainID)
163+
}
164+
165+
return nil
166+
}
167+
123168
// NewManagerImpl creates a new Manager unless an error is encountered, each element of the callOnUpdate slice
124169
// is invoked when a new configuration is committed
125170
func NewManagerImpl(configtx *cb.ConfigurationEnvelope, initializer Initializer, callOnUpdate []func(Manager)) (Manager, error) {
@@ -218,7 +263,7 @@ func (cm *configurationManager) processConfig(configtx *cb.ConfigurationEnvelope
218263
}
219264

220265
// Ensure the config sequence numbers are correct to prevent replay attacks
221-
isModified := false
266+
var isModified bool
222267

223268
if val, ok := cm.configuration[config.Type][config.Key]; ok {
224269
// Config was modified if any of the contents changed

common/configtx/manager_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
cb "github.com/hyperledger/fabric/protos/common"
2727

2828
"errors"
29+
2930
"github.com/golang/protobuf/proto"
3031
)
3132

@@ -118,8 +119,8 @@ func TestCallback(t *testing.T) {
118119
}
119120
}
120121

121-
// TestWrongChainID tests that a configuration update for a different chain ID fails
122-
func TestWrongChainID(t *testing.T) {
122+
// TestDifferentChainID tests that a configuration update for a different chain ID fails
123+
func TestDifferentChainID(t *testing.T) {
123124
cm, err := NewManagerImpl(&cb.ConfigurationEnvelope{
124125
Items: []*cb.SignedConfigurationItem{makeSignedConfigurationItem("foo", "foo", 0, []byte("foo"), defaultChain)},
125126
}, &mockconfigtx.Initializer{PolicyManagerVal: &mockPolicyManager{&mockPolicy{}}, HandlersVal: defaultHandlers()}, nil)

common/configtx/template.go

+3
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ func MakeChainCreationTransaction(creationPolicy string, chainID string, signer
163163
}
164164

165165
manager, err := NewManagerImpl(&cb.ConfigurationEnvelope{Items: items}, NewInitializer(), nil)
166+
if err != nil {
167+
return nil, err
168+
}
166169

167170
newChainTemplate := NewChainCreationTemplate(creationPolicy, manager.ChainConfig().HashingAlgorithm(), composite)
168171
signedConfigItems, err := newChainTemplate.Items(chainID)

common/configtx/util_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package configtx
18+
19+
import (
20+
"math/rand"
21+
"testing"
22+
)
23+
24+
// TestValidchainID checks that the constraints on chain IDs are enforced properly
25+
func TestValidChainID(t *testing.T) {
26+
acceptMsg := "Should have accepted valid chain ID"
27+
rejectMsg := "Should have rejected invalid chain ID"
28+
29+
t.Run("ZeroLength", func(t *testing.T) {
30+
if err := validateChainID(""); err == nil {
31+
t.Fatal(rejectMsg)
32+
}
33+
})
34+
35+
t.Run("LongerThanMaxAllowed", func(t *testing.T) {
36+
if err := validateChainID(randomAlphaString(maxLength + 1)); err == nil {
37+
t.Fatal(rejectMsg)
38+
}
39+
})
40+
41+
t.Run("HasIllegalName", func(t *testing.T) {
42+
for illegalName := range illegalNames {
43+
if err := validateChainID(illegalName); err == nil {
44+
t.Fatal(rejectMsg)
45+
}
46+
}
47+
})
48+
49+
t.Run("ContainsIllegalCharacter", func(t *testing.T) {
50+
if err := validateChainID("foo_bar"); err == nil {
51+
t.Fatal(rejectMsg)
52+
}
53+
})
54+
55+
t.Run("ValidName", func(t *testing.T) {
56+
if err := validateChainID("foo.bar"); err != nil {
57+
t.Fatal(acceptMsg)
58+
}
59+
})
60+
}
61+
62+
// Helper functions
63+
64+
func randomAlphaString(size int) string {
65+
letters := []rune("abcdefghijklmnopqrstuvwxyz")
66+
output := make([]rune, size)
67+
for i := range output {
68+
output[i] = letters[rand.Intn(len(letters))]
69+
}
70+
return string(output)
71+
}

0 commit comments

Comments
 (0)