Skip to content

Commit

Permalink
[FAB-5582] create framework for ACL
Browse files Browse the repository at this point in the history
A simple interface by which peer can validate access to various resources.
It also provides an implementation which defaults to current behavior of
using READERS and WRITERS. This implementation can be used if RSCC is
disabled (or not defined).

- added SCC.FN resources
- changed "Acl" to "ACL" uniformly and add aclLogger

Change-Id: Ia34964e2714fd4693ef4829b6ea449dea5de6106
Signed-off-by: Srinivasan Muralidharan <muralisr@us.ibm.com>
  • Loading branch information
Srinivasan Muralidharan committed Aug 8, 2017
1 parent 5101b9e commit c2c8e20
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 0 deletions.
77 changes: 77 additions & 0 deletions core/aclmgmt/aclmgmt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package aclmgmt

import (
"sync"

"github.com/hyperledger/fabric/common/flogging"
)

var aclLogger = flogging.MustGetLogger("aclmgmt")

//fabric resources used for ACL checks. Note that some of the checks
//such as LSCC_INSTALL are "peer wide" (current access checks in peer are
//based on local MSP). These are not currently covered by RSCC or defaultProvider
const (
PROPOSE = "PROPOSE"

//LSCC resources
LSCC_INSTALL = "LSCC_INSTALL"
LSCC_DEPLOY = "LSCC_DEPLOY"
LSCC_UPGRADE = "LSCC_UPGRADE"
LSCC_GETCCINFO = "LSCC_GETCCINFO"
LSCC_GETDEPSPEC = "LSCC_GETDEPSPEC"
LSCC_GETCCDATA = "LSCC_GETCCDATA"
LSCC_GETCHAINCODES = "LSCC_GETCHAINCODES"
LSCC_GETINSTALLEDCHAINCODES = "LSCC_GETINSTALLEDCHAINCODES"

//QSCC resources
QSCC_GetChainInfo = "QSCC_GetChainInfo"
QSCC_GetBlockByNumber = "QSCC_GetBlockByNumber"
QSCC_GetBlockByHash = "QSCC_GetBlockByHash"
QSCC_GetTransactionByID = "QSCC_GetTransactionByID"
QSCC_GetBlockByTxID = "QSCC_GetBlockByTxID"

//CSCC resources
CSCC_JoinChain = "CSCC_JoinChain"
CSCC_GetConfigBlock = "CSCC_GetConfigBlock"
CSCC_GetChannels = "CSCC_GetChannels"

//Chaincode-to-Chaincode call
CC2CC = "CC2CC"

//Events
BLOCKEVENT = "BLOCKEVENT"
FILTEREDBLOCKEVENT = "FILTEREDBLOCKEVENT"
)

type ACLProvider interface {
//CheckACL checks the ACL for the resource for the channel using the
//idinfo. idinfo is an object such as SignedProposal from which an
//id can be extracted for testing against a policy
CheckACL(resName string, channelID string, idinfo interface{}) error
}

var aclProvider ACLProvider

var once sync.Once

//RegisterACLProvider will be called to register actual SCC if RSCC (an ACLProvider) is enabled
func RegisterACLProvider(r ACLProvider) {
once.Do(func() {
aclProvider = newACLMgmt(r)
})
}

//GetACLProvider returns ACLProvider
func GetACLProvider() ACLProvider {
if aclProvider == nil {
panic("-----RegisterACLProvider not called -----")
}
return aclProvider
}
82 changes: 82 additions & 0 deletions core/aclmgmt/aclmgmt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package aclmgmt

import (
"fmt"
"sync"
"testing"

pb "github.com/hyperledger/fabric/protos/peer"
"github.com/stretchr/testify/assert"
)

type mockACLProvider struct {
retErr error
}

func (m *mockACLProvider) CheckACL(resName string, channelID string, idinfo interface{}) error {
return m.retErr
}

//treat each test as an independent isolated one
func reinit() {
aclProvider = nil
once = sync.Once{}
}

func TestPanicOnUnregistered(t *testing.T) {
reinit()
assert.Panics(t, func() {
GetACLProvider()
}, "Should have paniced on unregistered call")
}

func TestRegisterNilProvider(t *testing.T) {
reinit()
RegisterACLProvider(nil)
assert.NotNil(t, GetACLProvider(), "Expected non-nil retval")
}

func TestBadID(t *testing.T) {
reinit()
RegisterACLProvider(nil)
err := GetACLProvider().CheckACL(PROPOSE, "somechain", "badidtype")
assert.Error(t, err, "Expected error")
}

func TestBadResource(t *testing.T) {
reinit()
RegisterACLProvider(nil)
err := GetACLProvider().CheckACL("unknownresource", "somechain", &pb.SignedProposal{})
assert.Error(t, err, "Expected error")
}

func TestOverride(t *testing.T) {
reinit()
RegisterACLProvider(nil)
GetACLProvider().(*aclMgmtImpl).aclOverrides[PROPOSE] = func(res, c string, idinfo interface{}) error {
return nil
}
err := GetACLProvider().CheckACL(PROPOSE, "somechain", &pb.SignedProposal{})
assert.NoError(t, err)
delete(GetACLProvider().(*aclMgmtImpl).aclOverrides, PROPOSE)
}

func TestWithProvider(t *testing.T) {
reinit()
RegisterACLProvider(&mockACLProvider{})
err := GetACLProvider().CheckACL(PROPOSE, "somechain", &pb.SignedProposal{})
assert.NoError(t, err)
}

func TestBadACL(t *testing.T) {
reinit()
RegisterACLProvider(&mockACLProvider{retErr: fmt.Errorf("badacl")})
err := GetACLProvider().CheckACL(PROPOSE, "somechain", &pb.SignedProposal{})
assert.Error(t, err, "Expected error")
}
50 changes: 50 additions & 0 deletions core/aclmgmt/aclmgmtimpl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package aclmgmt

import (
"github.com/hyperledger/fabric/common/flogging"
)

var aclMgmtLogger = flogging.MustGetLogger("aclmgmt")

type aclMethod func(resName string, channelID string, idinfo interface{}) error

//implementation of aclMgmt
type aclMgmtImpl struct {
//by default resources are managed by RSCC. However users may set aclMethod for
//a resource and bypass RSCC if necessary
aclOverrides map[string]aclMethod
}

var rscc ACLProvider

//CheckACL checks the ACL for the resource for the channel using the
//idinfo. idinfo is an object such as SignedProposal from which an
//id can be extracted for testing against a policy
func (am *aclMgmtImpl) CheckACL(resName string, channelID string, idinfo interface{}) error {
aclMeth := am.aclOverrides[resName]
if aclMeth != nil {
return aclMeth(resName, channelID, idinfo)
}

if rscc == nil {
panic("-----RegisterACLProvider not called ----")
}

return rscc.CheckACL(resName, channelID, idinfo)
}

func newACLMgmt(r ACLProvider) ACLProvider {
rscc = r
if rscc == nil {
rscc = newDefaultACLProvider()
}

//by default overrides are not set and all acl checks are referred to rscc
return &aclMgmtImpl{aclOverrides: make(map[string]aclMethod)}
}
121 changes: 121 additions & 0 deletions core/aclmgmt/defaultaclprovider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package aclmgmt

import (
"fmt"

"github.com/hyperledger/fabric/common/policies"
"github.com/hyperledger/fabric/core/peer"
"github.com/hyperledger/fabric/core/policy"
"github.com/hyperledger/fabric/msp/mgmt"
pb "github.com/hyperledger/fabric/protos/peer"
)

const (
CHANNELREADERS = policies.ChannelApplicationReaders
CHANNELWRITERS = policies.ChannelApplicationWriters
)

//defaultACLProvider if RSCC not provided use the default pre 1.0 implementation
type defaultACLProvider struct {
policyChecker policy.PolicyChecker

//peer wide policy (currently not used)
pResourcePolicyMap map[string]string

//channel specific policy
cResourcePolicyMap map[string]string
}

func newDefaultACLProvider() ACLProvider {
d := &defaultACLProvider{}
d.initialize()

return d
}

func (d *defaultACLProvider) initialize() {
d.policyChecker = policy.NewPolicyChecker(
peer.NewChannelPolicyManagerGetter(),
mgmt.GetLocalMSP(),
mgmt.NewLocalMSPPrincipalGetter(),
)

d.pResourcePolicyMap = make(map[string]string)
d.cResourcePolicyMap = make(map[string]string)

//-------------- LSCC --------------
//p resources (implemented by the chaincode currently)
d.pResourcePolicyMap[LSCC_INSTALL] = ""
d.pResourcePolicyMap[LSCC_GETCHAINCODES] = ""
d.pResourcePolicyMap[LSCC_GETINSTALLEDCHAINCODES] = ""

//c resources
d.cResourcePolicyMap[LSCC_DEPLOY] = "" //ACL check covered by PROPOSAL
d.cResourcePolicyMap[LSCC_UPGRADE] = "" //ACL check covered by PROPOSAL
d.cResourcePolicyMap[LSCC_GETCCINFO] = CHANNELREADERS
d.cResourcePolicyMap[LSCC_GETDEPSPEC] = CHANNELREADERS
d.cResourcePolicyMap[LSCC_GETCCDATA] = CHANNELREADERS

//-------------- QSCC --------------
//p resources (none)

//c resources
d.cResourcePolicyMap[QSCC_GetChainInfo] = CHANNELREADERS
d.cResourcePolicyMap[QSCC_GetBlockByNumber] = CHANNELREADERS
d.cResourcePolicyMap[QSCC_GetBlockByHash] = CHANNELREADERS
d.cResourcePolicyMap[QSCC_GetTransactionByID] = CHANNELREADERS
d.cResourcePolicyMap[QSCC_GetBlockByTxID] = CHANNELREADERS

//--------------- CSCC resources -----------
//p resources (implemented by the chaincode currently)
d.pResourcePolicyMap[CSCC_JoinChain] = ""
d.cResourcePolicyMap[CSCC_GetChannels] = ""

//c resources
d.pResourcePolicyMap[CSCC_GetConfigBlock] = CHANNELREADERS

//---------------- non-scc resources ------------
//Propose
d.cResourcePolicyMap[PROPOSE] = CHANNELWRITERS

//Chaincode-to-Chaincode
d.cResourcePolicyMap[CC2CC] = CHANNELWRITERS

//Events (not used currently - for future)
d.cResourcePolicyMap[BLOCKEVENT] = CHANNELREADERS
d.cResourcePolicyMap[FILTEREDBLOCKEVENT] = CHANNELREADERS
}

//this should cover an exhaustive list of everything called from the peer
func (d *defaultACLProvider) defaultPolicy(resName string, cprovider bool) string {
var pol string
if cprovider {
pol = d.cResourcePolicyMap[resName]
} else {
pol = d.pResourcePolicyMap[resName]
}
return pol
}

//CheckACL provides default (v 1.0) behavior by mapping resources to their ACL for a channel
func (d *defaultACLProvider) CheckACL(resName string, channelID string, idinfo interface{}) error {
policy := d.defaultPolicy(resName, true)
if policy == "" {
aclLogger.Errorf("Unmapped policy for %s", resName)
return fmt.Errorf("Unmapped policy for %s", resName)
}

switch idinfo.(type) {
case *pb.SignedProposal:
return d.policyChecker.CheckPolicy(channelID, policy, idinfo.(*pb.SignedProposal))
default:
aclLogger.Errorf("Unmapped id on checkACL %s", resName)
return fmt.Errorf("Unknown id on checkACL %s", resName)
}
}

0 comments on commit c2c8e20

Please sign in to comment.