diff --git a/core/aclmgmt/aclmgmt.go b/core/aclmgmt/aclmgmt.go index 4affd97eefa..054d3a4da52 100644 --- a/core/aclmgmt/aclmgmt.go +++ b/core/aclmgmt/aclmgmt.go @@ -75,3 +75,9 @@ func GetACLProvider() ACLProvider { } return aclProvider } + +//NewDefaultACLProvider constructs a new default provider for other systems +//such as RSCC to use +func NewDefaultACLProvider() ACLProvider { + return newDefaultACLProvider() +} diff --git a/core/scc/importsysccs.go b/core/scc/importsysccs.go index 1df323e83b2..822ba9915a0 100644 --- a/core/scc/importsysccs.go +++ b/core/scc/importsysccs.go @@ -17,11 +17,14 @@ limitations under the License. package scc import ( + "github.com/hyperledger/fabric/core/aclmgmt" + //import system chain codes here "github.com/hyperledger/fabric/core/scc/cscc" "github.com/hyperledger/fabric/core/scc/escc" "github.com/hyperledger/fabric/core/scc/lscc" "github.com/hyperledger/fabric/core/scc/qscc" + "github.com/hyperledger/fabric/core/scc/rscc" "github.com/hyperledger/fabric/core/scc/vscc" ) @@ -67,14 +70,33 @@ var systemChaincodes = []*SystemChaincode{ InvokableExternal: true, // qscc can be invoked to retrieve blocks InvokableCC2CC: true, // qscc can be invoked to retrieve blocks also by a cc }, + { + Enabled: true, + Name: "rscc", + Path: "github.com/hyperledger/fabric/core/chaincode/rscc", + InitArgs: [][]byte{[]byte("")}, + Chaincode: rscc.NewRscc(), + InvokableExternal: true, // rscc can be invoked to update policies + InvokableCC2CC: false, // rscc cannot be invoked from a cc + }, } //RegisterSysCCs is the hook for system chaincodes where system chaincodes are registered with the fabric //note the chaincode must still be deployed and launched like a user chaincode will be func RegisterSysCCs() { + var aclProvider aclmgmt.ACLProvider for _, sysCC := range systemChaincodes { - RegisterSysCC(sysCC) + if reg, _ := registerSysCC(sysCC); reg { + //rscc is registered, lets make it the aclProvider + if sysCC.Name == "rscc" { + aclProvider = sysCC.Chaincode.(aclmgmt.ACLProvider) + } + } } + //a nil aclProvider will initialize defaultACLProvider + //which will provide 1.0 ACL defaults + aclmgmt.RegisterACLProvider(aclProvider) + } //DeploySysCCs is the hook for system chaincodes where system chaincodes are registered with the fabric diff --git a/core/scc/rscc/rscc.go b/core/scc/rscc/rscc.go new file mode 100644 index 00000000000..cd2e7ac27e2 --- /dev/null +++ b/core/scc/rscc/rscc.go @@ -0,0 +1,175 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package rscc + +import ( + "fmt" + "sync" + + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/common/flogging" + "github.com/hyperledger/fabric/core/aclmgmt" + "github.com/hyperledger/fabric/core/chaincode/shim" + "github.com/hyperledger/fabric/protos/common" + pb "github.com/hyperledger/fabric/protos/peer" +) + +const ( + //CHANNEL name + CHANNEL = "channel" + + //POLICY for the channel + POLICY = "policy" + + //Init Errors (for UT) + + //NOCHANNEL channel not found + NOCHANNEL = "nochannel" + + //NOPOLICY policy not found for channel + NOPOLICY = "nopolicy" + + //BADPOLICY bad policy + BADPOLICY = "badpolicy" +) + +//the basic policyProvider for the channel, consists of a +//default and rscc policy providers +type policyProvider struct { + //maybe we should use a separate default policy provider + //than the only from aclmgmt (which is 1.0 defaults) + defaultProvider aclmgmt.ACLProvider + + rsccProvider rsccPolicyProvider +} + +//Rscc SCC implementing resouce->Policy mapping for the fabric +type Rscc struct { + sync.RWMutex + + //the cache of RSCC policies for all the channels + policyCache map[string]*policyProvider +} + +var rsccLogger = flogging.MustGetLogger("rscc") + +//NewRscc get an initialzed new Rscc +func NewRscc() *Rscc { + return &Rscc{policyCache: make(map[string]*policyProvider)} +} + +//--------- errors --------- + +//NoPolicyProviderInCache in cache for channel +type NoPolicyProviderInCache string + +func (e NoPolicyProviderInCache) Error() string { + return fmt.Sprintf("cannot find policy provider in cache for channel %s", string(e)) +} + +//PolicyProviderNotFound for channel +type PolicyProviderNotFound string + +func (e PolicyProviderNotFound) Error() string { + return fmt.Sprintf("cannot find policy provider for channel %s", string(e)) +} + +//-------- ACLProvider interface ------ + +//CheckACL rscc implements AClProvider's CheckACL interface. This is the key interface +// . CheckACL works off the cache +// . CheckACL uses two providers - the RSCC provider from channel config and default provider +// that implements 1.0 functions +// . If a resource in RSCC Provider it'll use the policy defined there. Otherwise it'll defer +// to default provider +func (rscc *Rscc) CheckACL(resName string, channelID string, idinfo interface{}) error { + rsccLogger.Debugf("acl check(%s, %s)", resName, channelID) + rscc.RLock() + defer rscc.RUnlock() + pp := rscc.policyCache[channelID] + if pp == nil { + return NoPolicyProviderInCache(channelID) + } + + //found policyProvider + if pp.rsccProvider != nil { + //get the policy mapping if any + if policyName := pp.rsccProvider.GetPolicyName(resName); policyName != "" { + return pp.rsccProvider.CheckACL(policyName, idinfo) + } + } + + //try default provider + if pp.defaultProvider != nil { + return pp.defaultProvider.CheckACL(resName, channelID, idinfo) + } + + return PolicyProviderNotFound(channelID) +} + +//---------- misc functions --------- +func (rscc *Rscc) putPolicyProvider(channel string, pp *policyProvider) { + rscc.Lock() + defer rscc.Unlock() + rscc.policyCache[channel] = pp +} + +func (rscc *Rscc) setPolicyProvider(channel string, defProv aclmgmt.ACLProvider, rsccProv rsccPolicyProvider) { + pp := &policyProvider{defProv, rsccProv} + rscc.putPolicyProvider(channel, pp) +} + +//-------- CC interface ------------ + +// Init RSCC - Init is just used to initialize policy assuming it is found in the +// channel ledger. Don't return error and get out. For example, we might still serve +// Invokes to set policy +func (rscc *Rscc) Init(stub shim.ChaincodeStubInterface) pb.Response { + rsccLogger.Info("Init RSCC") + + b, err := stub.GetState(CHANNEL) + if err != nil || len(b) == 0 { + rsccLogger.Errorf("cannot find channel name") + return shim.Success([]byte(NOCHANNEL)) + } + + channel := string(b) + + defProv := aclmgmt.NewDefaultACLProvider() + + b, err = stub.GetState(POLICY) + if err != nil || len(b) == 0 { + //we do not have policy, create just defaults + rscc.setPolicyProvider(channel, defProv, nil) + rsccLogger.Errorf("cannot find policy for channel %s", channel) + return shim.Success([]byte(NOPOLICY)) + } + + //TODO this is a place holder.. will change based on what is stored in + //ledger + cg := &common.ConfigGroup{} + err = proto.Unmarshal(b, cg) + if err != nil { + rscc.setPolicyProvider(channel, defProv, nil) + rsccLogger.Errorf("cannot unmarshal policy for channel %s", channel) + return shim.Success([]byte(BADPOLICY)) + } + + rsccpp, err := newRsccPolicyProvider(cg) + if err != nil { + rsccLogger.Errorf("cannot create policy provider for channel %s", channel) + } + + rscc.setPolicyProvider(channel, defProv, rsccpp) + + return shim.Success(nil) +} + +//Invoke - update policies +func (rscc *Rscc) Invoke(stub shim.ChaincodeStubInterface) pb.Response { + return shim.Error("--TBD---") +} diff --git a/core/scc/rscc/rscc_test.go b/core/scc/rscc/rscc_test.go new file mode 100644 index 00000000000..5dd924e3f45 --- /dev/null +++ b/core/scc/rscc/rscc_test.go @@ -0,0 +1,187 @@ +/* +Copyright IBM Corp. 2016 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 rscc + +import ( + "fmt" + "testing" + + "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/core/chaincode/shim" + "github.com/hyperledger/fabric/protos/common" + "github.com/stretchr/testify/assert" +) + +// ------- mocks --------- + +//mock rscc provider +type mockRsccProvider struct { + //resource to policy + resMap map[string]string + throwPanic string +} + +//GetPolicyName returns the policy name given the resource string +func (mp *mockRsccProvider) GetPolicyName(resName string) string { + if mp.resMap == nil { + return "" + } + + return mp.resMap[resName] +} + +func (mp *mockRsccProvider) CheckACL(polName string, idinfo interface{}) error { + //if it got here, it must have worked, all allowed..throw panic to signify + //it was called from here + if mp.throwPanic != "" { + panic(mp.throwPanic) + } + return nil +} + +//mock default provider +type mockDefaulACLProvider struct { + //resource to policy + resMap map[string]string + throwPanic string +} + +//CheckACL rscc implements AClProvider's CheckACL interface so it can be registered +//as a provider with aclmgmt +func (mp *mockDefaulACLProvider) CheckACL(resName string, channelID string, idinfo interface{}) error { + if mp.resMap[resName] == "" { + return fmt.Errorf("[defaultprovider]no resource %s", resName) + } + + //if it got here, it must have worked, all allowed..throw panic to signify + //it was called from here + if mp.throwPanic != "" { + panic(mp.throwPanic) + } + + return nil +} + +//-------- misc funcs ----------- +func setupGood(t *testing.T) (*Rscc, *shim.MockStub) { + rscc := NewRscc() + stub := shim.NewMockStub("rscc", rscc) + stub.State[CHANNEL] = []byte("myc") + b, _ := proto.Marshal(&common.ConfigGroup{Version: 1}) + stub.State[POLICY] = b + + res := stub.MockInit("1", [][]byte{}) + if res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + + if len(res.Payload) != 0 { + fmt.Println("Init failed (expected nil payload)", string(res.Payload)) + t.FailNow() + } + + assert.NotNil(t, rscc.policyCache["myc"], "Expected non-nil cache") + assert.NotNil(t, rscc.policyCache["myc"].defaultProvider, "Expected non-nil default provider") + assert.NotNil(t, rscc.policyCache["myc"].rsccProvider, "Expected non-nil rscc provider") + + return rscc, stub +} + +//-------- Tests ----------- +func TestInit(t *testing.T) { + setupGood(t) +} + +func TestBadState(t *testing.T) { + rscc := NewRscc() + stub := shim.NewMockStub("rscc", rscc) + + res := stub.MockInit("1", [][]byte{}) + if string(res.Payload) != NOCHANNEL { + fmt.Printf("Init failed (expected %s found %s)", NOCHANNEL, res.Payload) + t.FailNow() + } + + stub.State[CHANNEL] = []byte("myc") + res = stub.MockInit("1", [][]byte{}) + if string(res.Payload) != NOPOLICY { + fmt.Printf("Init failed (expected %s found %s)", NOPOLICY, res.Payload) + t.FailNow() + } + + b := []byte("badbadbadpolicy") + stub.State[POLICY] = b + + res = stub.MockInit("1", [][]byte{}) + if string(res.Payload) != BADPOLICY { + fmt.Printf("Init failed (expected %s found %s)", BADPOLICY, res.Payload) + t.FailNow() + } +} + +func testProviderSource(t *testing.T, rscc *Rscc, res, channel, id, panicMessage string) { + defer func() { + r := recover() + assert.NotNil(t, r, "should have panicked") + //assert.Equal(t, fmt.Sprintf("%s", r), panicMessage) + assert.Equal(t, r, panicMessage) + }() + rscc.CheckACL(res, channel, id) +} + +//tests if the right ACL providers get called +func TestACLPaths(t *testing.T) { + //start with a nice setup + rscc, _ := setupGood(t) + + //setup two resources, "res" in rsccProvider and "defres" in defaultProvider for channel "myc" + rscc.policyCache["myc"] = &policyProvider{&mockDefaulACLProvider{resMap: map[string]string{"defres": "defresPol"}, throwPanic: "defprovider"}, &mockRsccProvider{resMap: map[string]string{"res": "resPol"}, throwPanic: "rsccprovider"}} + + //bad channel + err := rscc.CheckACL("res", "badchannel", "id") + assert.Error(t, err, "should have received error") + assert.Equal(t, err.Error(), NoPolicyProviderInCache("badchannel").Error()) + + //good channel no resource + err = rscc.CheckACL("nores", "myc", "id") + assert.Error(t, err, "should have received error") + assert.Equal(t, err.Error(), "[defaultprovider]no resource nores") + + //resource from rscc provider + testProviderSource(t, rscc, "res", "myc", "id", "rsccprovider") + + //resource from default provider + testProviderSource(t, rscc, "defres", "myc", "id", "defprovider") + + //remove rsccProvider and test for res - should go to default provider and receive no resource + rscc.policyCache["myc"].rsccProvider = nil + err = rscc.CheckACL("res", "myc", "id") + assert.Error(t, err, "should have received error") + assert.Equal(t, err.Error(), "[defaultprovider]no resource res") + + //remove "defres" from default provider - should give defres (was there before) + delete(rscc.policyCache["myc"].defaultProvider.(*mockDefaulACLProvider).resMap, "defres") + err = rscc.CheckACL("defres", "myc", "id") + assert.Error(t, err, "should have received error") + assert.Equal(t, err.Error(), "[defaultprovider]no resource defres") + + //remove defaultProvider and test for anyres + rscc.policyCache["myc"].defaultProvider = nil + err = rscc.CheckACL("anyres", "myc", "id") + assert.Error(t, err, "should have received error") + assert.Equal(t, err.Error(), PolicyProviderNotFound("myc").Error()) +} diff --git a/core/scc/rscc/rsccpolicy.go b/core/scc/rscc/rsccpolicy.go new file mode 100644 index 00000000000..4d15f1ae0e5 --- /dev/null +++ b/core/scc/rscc/rsccpolicy.go @@ -0,0 +1,39 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package rscc + +import ( + "github.com/hyperledger/fabric/protos/common" +) + +//TODO - PLACE HOLDER FOR RESOURCE POLICY INTEGRATION. + +//rsccPolicyProvider is the basic policy provider for RSCC. It is an ACLProvider +type rsccPolicyProvider interface { + GetPolicyName(resName string) string + CheckACL(resName string, idinfo interface{}) error +} + +//rsccPolicyProviderImpl holds the bytes from state of the ledger +type rsccPolicyProviderImpl struct { +} + +//GetPolicyName returns the policy name given the resource string +func (rp *rsccPolicyProviderImpl) GetPolicyName(resName string) string { + return "" +} + +func newRsccPolicyProvider(cg *common.ConfigGroup) (*rsccPolicyProviderImpl, error) { + return &rsccPolicyProviderImpl{}, nil +} + +//CheckACL rscc implements AClProvider's CheckACL interface so it can be registered +//as a provider with aclmgmt +func (rp *rsccPolicyProviderImpl) CheckACL(polName string, idinfo interface{}) error { + rsccLogger.Debugf("acl check(%s)", polName) + return nil +} diff --git a/core/scc/scc_test.go b/core/scc/scc_test.go index 5b5f7bb69ff..95f16d359cd 100644 --- a/core/scc/scc_test.go +++ b/core/scc/scc_test.go @@ -101,13 +101,13 @@ func TestMockRegisterAndResetSysCCs(t *testing.T) { } func TestRegisterSysCC(t *testing.T) { - err := RegisterSysCC(&SystemChaincode{ + _, err := registerSysCC(&SystemChaincode{ Name: "lscc", Path: "path", Enabled: true, }) assert.NoError(t, err) - err = RegisterSysCC(&SystemChaincode{ + _, err = registerSysCC(&SystemChaincode{ Name: "lscc", Path: "path", Enabled: true, diff --git a/core/scc/sysccapi.go b/core/scc/sysccapi.go index 01cc2bdb8c9..364e7101d29 100644 --- a/core/scc/sysccapi.go +++ b/core/scc/sysccapi.go @@ -67,11 +67,11 @@ type SystemChaincode struct { Enabled bool } -// RegisterSysCC registers the given system chaincode with the peer -func RegisterSysCC(syscc *SystemChaincode) error { +// registerSysCC registers the given system chaincode with the peer +func registerSysCC(syscc *SystemChaincode) (bool, error) { if !syscc.Enabled || !isWhitelisted(syscc) { sysccLogger.Info(fmt.Sprintf("system chaincode (%s,%s,%t) disabled", syscc.Name, syscc.Path, syscc.Enabled)) - return nil + return false, nil } err := inproccontroller.Register(syscc.Path, syscc.Chaincode) @@ -80,12 +80,12 @@ func RegisterSysCC(syscc *SystemChaincode) error { if _, ok := err.(inproccontroller.SysCCRegisteredErr); !ok { errStr := fmt.Sprintf("could not register (%s,%v): %s", syscc.Path, syscc, err) sysccLogger.Error(errStr) - return fmt.Errorf(errStr) + return false, fmt.Errorf(errStr) } } sysccLogger.Infof("system chaincode %s(%s) registered", syscc.Name, syscc.Path) - return err + return true, err } // deploySysCC deploys the given system chaincode on a chain diff --git a/sampleconfig/core.yaml b/sampleconfig/core.yaml index b1eddbb3145..315fe205866 100644 --- a/sampleconfig/core.yaml +++ b/sampleconfig/core.yaml @@ -380,6 +380,7 @@ chaincode: escc: enable vscc: enable qscc: enable + rscc: disable # Logging section for the chaincode container logging: