Skip to content

Commit 9577fb7

Browse files
author
Jason Yellick
committed
[FAB-4468] Create configtx sanity check code
The configtx processing framework does checks on config to make sure that it is well formed, authorized, etc. However, there are certain configurations mistakes which are not considered fatal, but which still usually, do not make sense. This CR creates a library for doing configtx sanity checking. At the moment, it checks to make sure that the normal configtx processing framework considers it well formed, then checks to make sure all policies of type Signature policy only refer to MSP principals dependant on orgs defined within the system (where possible). The Checking function returns a set of messages, including General errors, and Element Errors, and Element Warnings. The Element types include a jquery type path from the channel group to the offending configuration element, along with a message describing what the error or warning is. Additional sanity checks may be defined in the future. Change-Id: I944c6dc0378523ef17f11c2b4b46adfbca38d6d8 Signed-off-by: Jason Yellick <[email protected]>
1 parent c8e0dbb commit 9577fb7

File tree

2 files changed

+276
-0
lines changed

2 files changed

+276
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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 sanitycheck
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/hyperledger/fabric/common/configtx"
23+
cb "github.com/hyperledger/fabric/protos/common"
24+
mspprotos "github.com/hyperledger/fabric/protos/msp"
25+
"github.com/hyperledger/fabric/protos/utils"
26+
27+
"github.com/golang/protobuf/proto"
28+
)
29+
30+
type Messages struct {
31+
GeneralErrors []string `json:"general_errors"`
32+
ElementWarnings []*ElementMessage `json:"element_errors"`
33+
ElementErrors []*ElementMessage `json:"element_errors"`
34+
}
35+
36+
type ElementMessage struct {
37+
Path string `json:"path"`
38+
Message string `json:"message"`
39+
}
40+
41+
func Check(config *cb.Config) (*Messages, error) {
42+
envConfig, err := utils.CreateSignedEnvelope(cb.HeaderType_CONFIG, "sanitycheck", nil, &cb.ConfigEnvelope{Config: config}, 0, 0)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
result := &Messages{}
48+
49+
cm, err := configtx.NewManagerImpl(envConfig, configtx.NewInitializer(), nil)
50+
if err != nil {
51+
result.GeneralErrors = []string{err.Error()}
52+
return result, nil
53+
}
54+
55+
// This should come from the MSP manager, but, for some reason
56+
// the MSP manager is not intialized if there are no orgs, so,
57+
// we collect this manually.
58+
mspMap := make(map[string]struct{})
59+
60+
if ac, ok := cm.ApplicationConfig(); ok {
61+
for _, org := range ac.Organizations() {
62+
mspMap[org.MSPID()] = struct{}{}
63+
}
64+
}
65+
66+
if oc, ok := cm.OrdererConfig(); ok {
67+
for _, org := range oc.Organizations() {
68+
mspMap[org.MSPID()] = struct{}{}
69+
}
70+
}
71+
72+
policyWarnings, policyErrors := checkPolicyPrincipals(config.ChannelGroup, "", mspMap)
73+
74+
result.ElementWarnings = policyWarnings
75+
result.ElementErrors = policyErrors
76+
77+
return result, nil
78+
}
79+
80+
func checkPolicyPrincipals(group *cb.ConfigGroup, basePath string, mspMap map[string]struct{}) (warnings []*ElementMessage, errors []*ElementMessage) {
81+
for policyName, configPolicy := range group.Policies {
82+
appendError := func(err string) {
83+
errors = append(errors, &ElementMessage{
84+
Path: basePath + ".policies." + policyName,
85+
Message: err,
86+
})
87+
}
88+
89+
appendWarning := func(err string) {
90+
warnings = append(errors, &ElementMessage{
91+
Path: basePath + ".policies." + policyName,
92+
Message: err,
93+
})
94+
}
95+
96+
if configPolicy.Policy == nil {
97+
appendError(fmt.Sprintf("no policy value set for %s", policyName))
98+
continue
99+
}
100+
101+
if configPolicy.Policy.Type != int32(cb.Policy_SIGNATURE) {
102+
continue
103+
}
104+
spe := &cb.SignaturePolicyEnvelope{}
105+
err := proto.Unmarshal(configPolicy.Policy.Value, spe)
106+
if err != nil {
107+
appendError(fmt.Sprintf("error unmarshaling policy value to SignaturePolicyEnvelope: %s", err))
108+
continue
109+
}
110+
111+
for i, identity := range spe.Identities {
112+
var mspID string
113+
switch identity.PrincipalClassification {
114+
case mspprotos.MSPPrincipal_ROLE:
115+
role := &mspprotos.MSPRole{}
116+
err = proto.Unmarshal(identity.Principal, role)
117+
if err != nil {
118+
appendError(fmt.Sprintf("value of identities array at index %d is of type ROLE, but could not be unmarshaled to msp.MSPRole: %s", i, err))
119+
continue
120+
}
121+
mspID = role.MspIdentifier
122+
case mspprotos.MSPPrincipal_ORGANIZATION_UNIT:
123+
ou := &mspprotos.OrganizationUnit{}
124+
err = proto.Unmarshal(identity.Principal, ou)
125+
if err != nil {
126+
appendError(fmt.Sprintf("value of identities array at index %d is of type ORGANIZATION_UNIT, but could not be unmarshaled to msp.OrganizationUnit: %s", i, err))
127+
continue
128+
}
129+
mspID = ou.MspIdentifier
130+
default:
131+
continue
132+
}
133+
134+
_, ok := mspMap[mspID]
135+
if !ok {
136+
appendWarning(fmt.Sprintf("identity principal at index %d refers to MSP ID '%s', which is not an MSP in the network", i, mspID))
137+
}
138+
}
139+
}
140+
141+
for subGroupName, subGroup := range group.Groups {
142+
subWarnings, subErrors := checkPolicyPrincipals(subGroup, basePath+".groups."+subGroupName, mspMap)
143+
warnings = append(warnings, subWarnings...)
144+
errors = append(errors, subErrors...)
145+
}
146+
return
147+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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 sanitycheck
18+
19+
import (
20+
"testing"
21+
22+
"github.com/hyperledger/fabric/bccsp/factory"
23+
"github.com/hyperledger/fabric/common/cauthdsl"
24+
"github.com/hyperledger/fabric/common/config"
25+
"github.com/hyperledger/fabric/common/configtx"
26+
genesisconfig "github.com/hyperledger/fabric/common/configtx/tool/localconfig"
27+
"github.com/hyperledger/fabric/common/configtx/tool/provisional"
28+
cb "github.com/hyperledger/fabric/protos/common"
29+
mspprotos "github.com/hyperledger/fabric/protos/msp"
30+
"github.com/hyperledger/fabric/protos/utils"
31+
32+
"github.com/golang/protobuf/proto"
33+
"github.com/stretchr/testify/assert"
34+
)
35+
36+
var (
37+
insecureConfig *cb.Config
38+
singleMSPConfig *cb.Config
39+
)
40+
41+
func init() {
42+
factory.InitFactories(nil)
43+
44+
insecureConf := genesisconfig.Load(genesisconfig.SampleInsecureProfile)
45+
insecureGB := provisional.New(insecureConf).GenesisBlockForChannel(provisional.TestChainID)
46+
insecureCtx := utils.ExtractEnvelopeOrPanic(insecureGB, 0)
47+
insecureConfig = configtx.UnmarshalConfigEnvelopeOrPanic(utils.UnmarshalPayloadOrPanic(insecureCtx.Payload).Data).Config
48+
49+
singleMSPConf := genesisconfig.Load(genesisconfig.SampleSingleMSPSoloProfile)
50+
singleMSPGB := provisional.New(singleMSPConf).GenesisBlockForChannel(provisional.TestChainID)
51+
singleMSPCtx := utils.ExtractEnvelopeOrPanic(singleMSPGB, 0)
52+
singleMSPConfig = configtx.UnmarshalConfigEnvelopeOrPanic(utils.UnmarshalPayloadOrPanic(singleMSPCtx.Payload).Data).Config
53+
}
54+
55+
func TestSimpleCheck(t *testing.T) {
56+
result, err := Check(insecureConfig)
57+
assert.NoError(t, err, "Simple empty config")
58+
assert.Equal(t, &Messages{}, result)
59+
}
60+
61+
func TestOneMSPCheck(t *testing.T) {
62+
result, err := Check(singleMSPConfig)
63+
assert.NoError(t, err, "Simple single MSP config")
64+
assert.Equal(t, &Messages{}, result)
65+
}
66+
67+
func TestEmptyConfigCheck(t *testing.T) {
68+
result, err := Check(&cb.Config{})
69+
assert.NoError(t, err, "Simple single MSP config")
70+
assert.Empty(t, result.ElementErrors)
71+
assert.Empty(t, result.ElementWarnings)
72+
assert.NotEmpty(t, result.GeneralErrors)
73+
}
74+
75+
func TestWrongMSPID(t *testing.T) {
76+
localConfig := proto.Clone(insecureConfig).(*cb.Config)
77+
policyName := "foo"
78+
localConfig.ChannelGroup.Groups[config.OrdererGroupKey].Policies[policyName] = &cb.ConfigPolicy{
79+
Policy: &cb.Policy{
80+
Type: int32(cb.Policy_SIGNATURE),
81+
Value: utils.MarshalOrPanic(cauthdsl.SignedByMspAdmin("MissingOrg")),
82+
},
83+
}
84+
result, err := Check(localConfig)
85+
assert.NoError(t, err, "Simple empty config")
86+
assert.Empty(t, result.GeneralErrors)
87+
assert.Empty(t, result.ElementErrors)
88+
assert.Len(t, result.ElementWarnings, 1)
89+
assert.Equal(t, ".groups."+config.OrdererGroupKey+".policies."+policyName, result.ElementWarnings[0].Path)
90+
}
91+
92+
func TestCorruptRolePrincipal(t *testing.T) {
93+
localConfig := proto.Clone(insecureConfig).(*cb.Config)
94+
policyName := "foo"
95+
sigPolicy := cauthdsl.SignedByMspAdmin("MissingOrg")
96+
sigPolicy.Identities[0].Principal = []byte("garbage which corrupts the evaluation")
97+
localConfig.ChannelGroup.Policies[policyName] = &cb.ConfigPolicy{
98+
Policy: &cb.Policy{
99+
Type: int32(cb.Policy_SIGNATURE),
100+
Value: utils.MarshalOrPanic(sigPolicy),
101+
},
102+
}
103+
result, err := Check(localConfig)
104+
assert.NoError(t, err, "Simple empty config")
105+
assert.Empty(t, result.GeneralErrors)
106+
assert.Empty(t, result.ElementWarnings)
107+
assert.Len(t, result.ElementErrors, 1)
108+
assert.Equal(t, ".policies."+policyName, result.ElementErrors[0].Path)
109+
}
110+
111+
func TestCorruptOUPrincipal(t *testing.T) {
112+
localConfig := proto.Clone(insecureConfig).(*cb.Config)
113+
policyName := "foo"
114+
sigPolicy := cauthdsl.SignedByMspAdmin("MissingOrg")
115+
sigPolicy.Identities[0].PrincipalClassification = mspprotos.MSPPrincipal_ORGANIZATION_UNIT
116+
sigPolicy.Identities[0].Principal = []byte("garbage which corrupts the evaluation")
117+
localConfig.ChannelGroup.Policies[policyName] = &cb.ConfigPolicy{
118+
Policy: &cb.Policy{
119+
Type: int32(cb.Policy_SIGNATURE),
120+
Value: utils.MarshalOrPanic(sigPolicy),
121+
},
122+
}
123+
result, err := Check(localConfig)
124+
assert.NoError(t, err, "Simple empty config")
125+
assert.Empty(t, result.GeneralErrors)
126+
assert.Empty(t, result.ElementWarnings)
127+
assert.Len(t, result.ElementErrors, 1)
128+
assert.Equal(t, ".policies."+policyName, result.ElementErrors[0].Path)
129+
}

0 commit comments

Comments
 (0)