-
Notifications
You must be signed in to change notification settings - Fork 8.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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 <jyellick@us.ibm.com>
- Loading branch information
Jason Yellick
committed
Jun 9, 2017
1 parent
c8e0dbb
commit 9577fb7
Showing
2 changed files
with
276 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* | ||
Copyright IBM Corp. 2017 All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package sanitycheck | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hyperledger/fabric/common/configtx" | ||
cb "github.com/hyperledger/fabric/protos/common" | ||
mspprotos "github.com/hyperledger/fabric/protos/msp" | ||
"github.com/hyperledger/fabric/protos/utils" | ||
|
||
"github.com/golang/protobuf/proto" | ||
) | ||
|
||
type Messages struct { | ||
GeneralErrors []string `json:"general_errors"` | ||
ElementWarnings []*ElementMessage `json:"element_errors"` | ||
ElementErrors []*ElementMessage `json:"element_errors"` | ||
} | ||
|
||
type ElementMessage struct { | ||
Path string `json:"path"` | ||
Message string `json:"message"` | ||
} | ||
|
||
func Check(config *cb.Config) (*Messages, error) { | ||
envConfig, err := utils.CreateSignedEnvelope(cb.HeaderType_CONFIG, "sanitycheck", nil, &cb.ConfigEnvelope{Config: config}, 0, 0) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
result := &Messages{} | ||
|
||
cm, err := configtx.NewManagerImpl(envConfig, configtx.NewInitializer(), nil) | ||
if err != nil { | ||
result.GeneralErrors = []string{err.Error()} | ||
return result, nil | ||
} | ||
|
||
// This should come from the MSP manager, but, for some reason | ||
// the MSP manager is not intialized if there are no orgs, so, | ||
// we collect this manually. | ||
mspMap := make(map[string]struct{}) | ||
|
||
if ac, ok := cm.ApplicationConfig(); ok { | ||
for _, org := range ac.Organizations() { | ||
mspMap[org.MSPID()] = struct{}{} | ||
} | ||
} | ||
|
||
if oc, ok := cm.OrdererConfig(); ok { | ||
for _, org := range oc.Organizations() { | ||
mspMap[org.MSPID()] = struct{}{} | ||
} | ||
} | ||
|
||
policyWarnings, policyErrors := checkPolicyPrincipals(config.ChannelGroup, "", mspMap) | ||
|
||
result.ElementWarnings = policyWarnings | ||
result.ElementErrors = policyErrors | ||
|
||
return result, nil | ||
} | ||
|
||
func checkPolicyPrincipals(group *cb.ConfigGroup, basePath string, mspMap map[string]struct{}) (warnings []*ElementMessage, errors []*ElementMessage) { | ||
for policyName, configPolicy := range group.Policies { | ||
appendError := func(err string) { | ||
errors = append(errors, &ElementMessage{ | ||
Path: basePath + ".policies." + policyName, | ||
Message: err, | ||
}) | ||
} | ||
|
||
appendWarning := func(err string) { | ||
warnings = append(errors, &ElementMessage{ | ||
Path: basePath + ".policies." + policyName, | ||
Message: err, | ||
}) | ||
} | ||
|
||
if configPolicy.Policy == nil { | ||
appendError(fmt.Sprintf("no policy value set for %s", policyName)) | ||
continue | ||
} | ||
|
||
if configPolicy.Policy.Type != int32(cb.Policy_SIGNATURE) { | ||
continue | ||
} | ||
spe := &cb.SignaturePolicyEnvelope{} | ||
err := proto.Unmarshal(configPolicy.Policy.Value, spe) | ||
if err != nil { | ||
appendError(fmt.Sprintf("error unmarshaling policy value to SignaturePolicyEnvelope: %s", err)) | ||
continue | ||
} | ||
|
||
for i, identity := range spe.Identities { | ||
var mspID string | ||
switch identity.PrincipalClassification { | ||
case mspprotos.MSPPrincipal_ROLE: | ||
role := &mspprotos.MSPRole{} | ||
err = proto.Unmarshal(identity.Principal, role) | ||
if err != nil { | ||
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)) | ||
continue | ||
} | ||
mspID = role.MspIdentifier | ||
case mspprotos.MSPPrincipal_ORGANIZATION_UNIT: | ||
ou := &mspprotos.OrganizationUnit{} | ||
err = proto.Unmarshal(identity.Principal, ou) | ||
if err != nil { | ||
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)) | ||
continue | ||
} | ||
mspID = ou.MspIdentifier | ||
default: | ||
continue | ||
} | ||
|
||
_, ok := mspMap[mspID] | ||
if !ok { | ||
appendWarning(fmt.Sprintf("identity principal at index %d refers to MSP ID '%s', which is not an MSP in the network", i, mspID)) | ||
} | ||
} | ||
} | ||
|
||
for subGroupName, subGroup := range group.Groups { | ||
subWarnings, subErrors := checkPolicyPrincipals(subGroup, basePath+".groups."+subGroupName, mspMap) | ||
warnings = append(warnings, subWarnings...) | ||
errors = append(errors, subErrors...) | ||
} | ||
return | ||
} |
129 changes: 129 additions & 0 deletions
129
common/tools/configtxlator/sanitycheck/sanitycheck_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/* | ||
Copyright IBM Corp. 2017 All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package sanitycheck | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/hyperledger/fabric/bccsp/factory" | ||
"github.com/hyperledger/fabric/common/cauthdsl" | ||
"github.com/hyperledger/fabric/common/config" | ||
"github.com/hyperledger/fabric/common/configtx" | ||
genesisconfig "github.com/hyperledger/fabric/common/configtx/tool/localconfig" | ||
"github.com/hyperledger/fabric/common/configtx/tool/provisional" | ||
cb "github.com/hyperledger/fabric/protos/common" | ||
mspprotos "github.com/hyperledger/fabric/protos/msp" | ||
"github.com/hyperledger/fabric/protos/utils" | ||
|
||
"github.com/golang/protobuf/proto" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var ( | ||
insecureConfig *cb.Config | ||
singleMSPConfig *cb.Config | ||
) | ||
|
||
func init() { | ||
factory.InitFactories(nil) | ||
|
||
insecureConf := genesisconfig.Load(genesisconfig.SampleInsecureProfile) | ||
insecureGB := provisional.New(insecureConf).GenesisBlockForChannel(provisional.TestChainID) | ||
insecureCtx := utils.ExtractEnvelopeOrPanic(insecureGB, 0) | ||
insecureConfig = configtx.UnmarshalConfigEnvelopeOrPanic(utils.UnmarshalPayloadOrPanic(insecureCtx.Payload).Data).Config | ||
|
||
singleMSPConf := genesisconfig.Load(genesisconfig.SampleSingleMSPSoloProfile) | ||
singleMSPGB := provisional.New(singleMSPConf).GenesisBlockForChannel(provisional.TestChainID) | ||
singleMSPCtx := utils.ExtractEnvelopeOrPanic(singleMSPGB, 0) | ||
singleMSPConfig = configtx.UnmarshalConfigEnvelopeOrPanic(utils.UnmarshalPayloadOrPanic(singleMSPCtx.Payload).Data).Config | ||
} | ||
|
||
func TestSimpleCheck(t *testing.T) { | ||
result, err := Check(insecureConfig) | ||
assert.NoError(t, err, "Simple empty config") | ||
assert.Equal(t, &Messages{}, result) | ||
} | ||
|
||
func TestOneMSPCheck(t *testing.T) { | ||
result, err := Check(singleMSPConfig) | ||
assert.NoError(t, err, "Simple single MSP config") | ||
assert.Equal(t, &Messages{}, result) | ||
} | ||
|
||
func TestEmptyConfigCheck(t *testing.T) { | ||
result, err := Check(&cb.Config{}) | ||
assert.NoError(t, err, "Simple single MSP config") | ||
assert.Empty(t, result.ElementErrors) | ||
assert.Empty(t, result.ElementWarnings) | ||
assert.NotEmpty(t, result.GeneralErrors) | ||
} | ||
|
||
func TestWrongMSPID(t *testing.T) { | ||
localConfig := proto.Clone(insecureConfig).(*cb.Config) | ||
policyName := "foo" | ||
localConfig.ChannelGroup.Groups[config.OrdererGroupKey].Policies[policyName] = &cb.ConfigPolicy{ | ||
Policy: &cb.Policy{ | ||
Type: int32(cb.Policy_SIGNATURE), | ||
Value: utils.MarshalOrPanic(cauthdsl.SignedByMspAdmin("MissingOrg")), | ||
}, | ||
} | ||
result, err := Check(localConfig) | ||
assert.NoError(t, err, "Simple empty config") | ||
assert.Empty(t, result.GeneralErrors) | ||
assert.Empty(t, result.ElementErrors) | ||
assert.Len(t, result.ElementWarnings, 1) | ||
assert.Equal(t, ".groups."+config.OrdererGroupKey+".policies."+policyName, result.ElementWarnings[0].Path) | ||
} | ||
|
||
func TestCorruptRolePrincipal(t *testing.T) { | ||
localConfig := proto.Clone(insecureConfig).(*cb.Config) | ||
policyName := "foo" | ||
sigPolicy := cauthdsl.SignedByMspAdmin("MissingOrg") | ||
sigPolicy.Identities[0].Principal = []byte("garbage which corrupts the evaluation") | ||
localConfig.ChannelGroup.Policies[policyName] = &cb.ConfigPolicy{ | ||
Policy: &cb.Policy{ | ||
Type: int32(cb.Policy_SIGNATURE), | ||
Value: utils.MarshalOrPanic(sigPolicy), | ||
}, | ||
} | ||
result, err := Check(localConfig) | ||
assert.NoError(t, err, "Simple empty config") | ||
assert.Empty(t, result.GeneralErrors) | ||
assert.Empty(t, result.ElementWarnings) | ||
assert.Len(t, result.ElementErrors, 1) | ||
assert.Equal(t, ".policies."+policyName, result.ElementErrors[0].Path) | ||
} | ||
|
||
func TestCorruptOUPrincipal(t *testing.T) { | ||
localConfig := proto.Clone(insecureConfig).(*cb.Config) | ||
policyName := "foo" | ||
sigPolicy := cauthdsl.SignedByMspAdmin("MissingOrg") | ||
sigPolicy.Identities[0].PrincipalClassification = mspprotos.MSPPrincipal_ORGANIZATION_UNIT | ||
sigPolicy.Identities[0].Principal = []byte("garbage which corrupts the evaluation") | ||
localConfig.ChannelGroup.Policies[policyName] = &cb.ConfigPolicy{ | ||
Policy: &cb.Policy{ | ||
Type: int32(cb.Policy_SIGNATURE), | ||
Value: utils.MarshalOrPanic(sigPolicy), | ||
}, | ||
} | ||
result, err := Check(localConfig) | ||
assert.NoError(t, err, "Simple empty config") | ||
assert.Empty(t, result.GeneralErrors) | ||
assert.Empty(t, result.ElementWarnings) | ||
assert.Len(t, result.ElementErrors, 1) | ||
assert.Equal(t, ".policies."+policyName, result.ElementErrors[0].Path) | ||
} |