Skip to content

Commit

Permalink
[FAB-5637] Add resources config code
Browse files Browse the repository at this point in the history
The rscc_seed_data is to be populated with a config structure,
representing the resource ACLs for the system.  It will look something
like this:

{
    Values: {
        "QSCC_GetChainInfo": {
            "policy_ref": "Foo"
        },
        "QSCC_GetBlockByHash": {
            "policy_ref": "Foo"
        }
    }
    Policies: {
        "Foo": NoOutOf ...
    }
    Groups: {
        // Optional, may be used for implicit policies
    }
}

This CR adds the config code necessary to handle this, and provides an
interface for retrieving the policy reference for a resource.

Change-Id: I06eddbc0be3a144b97c476eb066168c6d815cdc9
Signed-off-by: Jason Yellick <[email protected]>
  • Loading branch information
Jason Yellick committed Aug 12, 2017
1 parent 9a4172e commit fccd54d
Show file tree
Hide file tree
Showing 4 changed files with 384 additions and 0 deletions.
115 changes: 115 additions & 0 deletions common/config/resources/initializer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package resources

import (
"fmt"

"github.com/hyperledger/fabric/common/cauthdsl"
"github.com/hyperledger/fabric/common/config"
"github.com/hyperledger/fabric/common/configtx"
configtxapi "github.com/hyperledger/fabric/common/configtx/api"
"github.com/hyperledger/fabric/common/policies"
"github.com/hyperledger/fabric/msp"
cb "github.com/hyperledger/fabric/protos/common"

"github.com/golang/protobuf/proto"
)

const RootGroupKey = "Resources"

type policyProposerRoot struct {
policyManager policies.Proposer
}

// BeginPolicyProposals is used to start a new config proposal
func (p *policyProposerRoot) BeginPolicyProposals(tx interface{}, groups []string) ([]policies.Proposer, error) {
if len(groups) != 1 {
logger.Panicf("Initializer only supports having one root group")
}
return []policies.Proposer{p.policyManager}, nil
}

func (i *policyProposerRoot) ProposePolicy(tx interface{}, key string, policy *cb.ConfigPolicy) (proto.Message, error) {
return nil, fmt.Errorf("Programming error, this should never be invoked")
}

// PreCommit is a no-op and returns nil
func (i *policyProposerRoot) PreCommit(tx interface{}) error {
return nil
}

// RollbackConfig is used to abandon a new config proposal
func (i *policyProposerRoot) RollbackProposals(tx interface{}) {}

// CommitConfig is used to commit a new config proposal
func (i *policyProposerRoot) CommitProposals(tx interface{}) {}

type Bundle struct {
ppr *policyProposerRoot
vpr *valueProposerRoot
cm configtxapi.Manager
pm policies.Manager
}

// New creates a new resources config bundle
func New(envConfig *cb.Envelope, mspManager msp.MSPManager) (*Bundle, error) {
policyProviderMap := make(map[int32]policies.Provider)
for pType := range cb.Policy_PolicyType_name {
rtype := cb.Policy_PolicyType(pType)
switch rtype {
case cb.Policy_UNKNOWN:
// Do not register a handler
case cb.Policy_SIGNATURE:
policyProviderMap[pType] = cauthdsl.NewPolicyProvider(mspManager)
case cb.Policy_MSP:
// Add hook for MSP Handler here
}
}

pm := policies.NewManagerImpl(RootGroupKey, policyProviderMap)

b := &Bundle{
vpr: newValueProposerRoot(),
ppr: &policyProposerRoot{
policyManager: pm,
},
pm: pm,
}
var err error
b.cm, err = configtx.NewManagerImpl(envConfig, b, nil)
if err != nil {
return nil, err
}
return b, err
}

func (b *Bundle) RootGroupKey() string {
return RootGroupKey
}

func (b *Bundle) PolicyProposer() policies.Proposer {
logger.Debug("Boo", b)
logger.Debug("boo", b.ppr)
return b.ppr
}

func (b *Bundle) ValueProposer() config.ValueProposer {
return b.vpr
}

func (b *Bundle) ConfigtxManager() configtxapi.Manager {
return b.cm
}

func (b *Bundle) PolicyManager() policies.Manager {
return b.pm
}

func (b *Bundle) ResourcePolicyMapper() PolicyMapper {
return b.vpr
}
105 changes: 105 additions & 0 deletions common/config/resources/initializer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package resources

import (
"testing"

"github.com/hyperledger/fabric/common/cauthdsl"
cb "github.com/hyperledger/fabric/protos/common"
pb "github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/protos/utils"

logging "github.com/op/go-logging"
"github.com/stretchr/testify/assert"
)

func init() {
logging.SetLevel(logging.DEBUG, "")
}

var dummyPolicy = &cb.ConfigPolicy{
Policy: &cb.Policy{
Type: int32(cb.Policy_SIGNATURE),
Value: utils.MarshalOrPanic(cauthdsl.AcceptAllPolicy),
},
}

func TestBundleGreenPath(t *testing.T) {
env, err := utils.CreateSignedEnvelope(cb.HeaderType_CONFIG, "foo", nil, &cb.ConfigEnvelope{
Config: &cb.Config{
ChannelGroup: &cb.ConfigGroup{
Values: map[string]*cb.ConfigValue{
"Foo": &cb.ConfigValue{
Value: utils.MarshalOrPanic(&pb.Resource{
PolicyRef: "foo",
}),
},
"Bar": &cb.ConfigValue{
Value: utils.MarshalOrPanic(&pb.Resource{
PolicyRef: "/Channel/foo",
}),
},
},
Policies: map[string]*cb.ConfigPolicy{
"foo": dummyPolicy,
"bar": dummyPolicy,
},
Groups: map[string]*cb.ConfigGroup{
"subGroup": &cb.ConfigGroup{
Policies: map[string]*cb.ConfigPolicy{
"other": dummyPolicy,
},
},
},
},
},
}, 0, 0)
assert.NoError(t, err)

b, err := New(env, nil)
assert.NotNil(t, b)
assert.NoError(t, err)
assert.Equal(t, "/Resources/foo", b.ResourcePolicyMapper().PolicyRefForResource("Foo"))
assert.Equal(t, "/Channel/foo", b.ResourcePolicyMapper().PolicyRefForResource("Bar"))

t.Run("Code coverage nits", func(t *testing.T) {
assert.Equal(t, b.RootGroupKey(), RootGroupKey)
assert.NotNil(t, b.PolicyProposer())
assert.NotNil(t, b.ValueProposer())
assert.NotNil(t, b.ConfigtxManager())
assert.NotNil(t, b.PolicyManager())
})
}

func TestBundleBadSubGroup(t *testing.T) {
env, err := utils.CreateSignedEnvelope(cb.HeaderType_CONFIG, "foo", nil, &cb.ConfigEnvelope{
Config: &cb.Config{
ChannelGroup: &cb.ConfigGroup{
Groups: map[string]*cb.ConfigGroup{
"subGroup": &cb.ConfigGroup{
Values: map[string]*cb.ConfigValue{
"Foo": &cb.ConfigValue{
Value: utils.MarshalOrPanic(&pb.Resource{
PolicyRef: "foo",
}),
},
},
Policies: map[string]*cb.ConfigPolicy{
"other": dummyPolicy,
},
},
},
},
},
}, 0, 0)
assert.NoError(t, err)

_, err = New(env, nil)
assert.Error(t, err)
assert.Regexp(t, "sub-groups not allowed to have values", err.Error())
}
89 changes: 89 additions & 0 deletions common/config/resources/resources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
Copyright IBM Corp. 2017 All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package resources

import (
"github.com/hyperledger/fabric/common/config"
pb "github.com/hyperledger/fabric/protos/peer"

"github.com/golang/protobuf/proto"
)

// ResourceGroup represents the ConfigGroup at the base of the resource configuration
type resourceGroup struct {
pendingResourceToPolicyRef map[string]string
vpr *valueProposerRoot
}

func newResourceGroup(vpr *valueProposerRoot) *resourceGroup {
return &resourceGroup{
vpr: vpr,
pendingResourceToPolicyRef: make(map[string]string),
}
}

// BeginValueProposals is invoked for each sub-group. These sub-groups are only for defining policies, not other config
// so, an emptyGroup is returned to handle them
func (rg *resourceGroup) BeginValueProposals(tx interface{}, groups []string) (config.ValueDeserializer, []config.ValueProposer, error) {
subGroups := make([]config.ValueProposer, len(groups))
for i := range subGroups {
subGroups[i] = emptyGroup{}
}
return &resourceGroupDeserializer{rg: rg}, subGroups, nil
}

// RollbackConfig a no-op
func (rg *resourceGroup) RollbackProposals(tx interface{}) {}

// PreCommit is a no-op
func (rg *resourceGroup) PreCommit(tx interface{}) error { return nil }

// CommitProposals writes the pendingResourceToPolicyRef map to the resource config root
func (rg *resourceGroup) CommitProposals(tx interface{}) {
rg.vpr.updatePolicyRefForResources(rg.pendingResourceToPolicyRef)
}

type resourceGroupDeserializer struct {
rg *resourceGroup
}

// Deserialize unmarshals bytes to a pb.Resource
func (rgd *resourceGroupDeserializer) Deserialize(key string, value []byte) (proto.Message, error) {
resource := &pb.Resource{}
if err := proto.Unmarshal(value, resource); err != nil {
return nil, err
}

// If the policy is fully qualified, ie to /Channel/Application/Readers leave it alone
// otherwise, make it fully qualified referring to /Resources/policyName
if '/' != resource.PolicyRef[0] {
rgd.rg.pendingResourceToPolicyRef[key] = "/" + RootGroupKey + "/" + resource.PolicyRef
} else {
rgd.rg.pendingResourceToPolicyRef[key] = resource.PolicyRef
}

return resource, nil
}

type emptyGroup struct{}

func (eg emptyGroup) BeginValueProposals(tx interface{}, groups []string) (config.ValueDeserializer, []config.ValueProposer, error) {
subGroups := make([]config.ValueProposer, len(groups))
for i := range subGroups {
subGroups[i] = emptyGroup{}
}
return failDeserializer("sub-groups not allowed to have values"), subGroups, nil
}

// RollbackConfig a no-op
func (eg emptyGroup) RollbackProposals(tx interface{}) {}

// PreCommit is a no-op
func (eg emptyGroup) PreCommit(tx interface{}) error { return nil }

// CommitConfig is a no-op
func (eg emptyGroup) CommitProposals(tx interface{}) {}
75 changes: 75 additions & 0 deletions common/config/resources/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package resources

import (
"fmt"
"sync/atomic"

"github.com/hyperledger/fabric/common/config"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/common/flogging"
)

var logger = flogging.MustGetLogger("common/config/resource")

// PolicyMapper is an interface for
type PolicyMapper interface {
// PolicyRefForResource takes the name of a resource, and returns the policy name for a resource
// or the empty string is the resource is not found
PolicyRefForResource(resourceName string) string
}

type valueProposerRoot struct {
resourceToPolicyRefMap atomic.Value
}

// newValueProposerRoot creates a new instance of the resources config root
func newValueProposerRoot() *valueProposerRoot {
vpr := &valueProposerRoot{}
vpr.resourceToPolicyRefMap.Store(map[string]string{})
return vpr
}

type failDeserializer string

func (fd failDeserializer) Deserialize(key string, value []byte) (proto.Message, error) {
return nil, fmt.Errorf(string(fd))
}

// BeginValueProposals is used to start a new config proposal
func (vpr *valueProposerRoot) BeginValueProposals(tx interface{}, groups []string) (config.ValueDeserializer, []config.ValueProposer, error) {
if len(groups) != 1 {
return nil, nil, fmt.Errorf("Root config only supports having one base group")
}
if groups[0] != RootGroupKey {
return nil, nil, fmt.Errorf("Root group must be %s", RootGroupKey)
}
return failDeserializer("Programming error, this should never be invoked"), []config.ValueProposer{newResourceGroup(vpr)}, nil
}

// RollbackConfig a no-op
func (vpr *valueProposerRoot) RollbackProposals(tx interface{}) {}

// PreCommit is a no-op
func (vpr *valueProposerRoot) PreCommit(tx interface{}) error { return nil }

// CommitConfig a no-op
func (vpr *valueProposerRoot) CommitProposals(tx interface{}) {}

// PolicyRefForResources implements the PolicyMapper interface
func (vpr *valueProposerRoot) PolicyRefForResource(resourceName string) string {
return vpr.resourceToPolicyRefMap.Load().(map[string]string)[resourceName]
}

// updatePolicyRefForResources should be called to update the policyRefForResources map
// it wraps the operation in the atomic package for thread safety.
func (vpr *valueProposerRoot) updatePolicyRefForResources(newMap map[string]string) {
logger.Debugf("Updating policy ref map for %p", vpr)
vpr.resourceToPolicyRefMap.Store(newMap)
}

0 comments on commit fccd54d

Please sign in to comment.