Skip to content

Commit

Permalink
[FAB-4468] Create configtx sanity check code
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 2 changed files with 276 additions and 0 deletions.
147 changes: 147 additions & 0 deletions common/tools/configtxlator/sanitycheck/sanitycheck.go
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 common/tools/configtxlator/sanitycheck/sanitycheck_test.go
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)
}

0 comments on commit 9577fb7

Please sign in to comment.