From bb071c54156f19a6593f96ab03a144676c882406 Mon Sep 17 00:00:00 2001 From: Matthias Neugschwandtner Date: Tue, 18 Apr 2017 14:24:55 +0200 Subject: [PATCH] [FAB-2931] CC instantiation tx validation This change adds checks that evaluate the chaincode instantiation policy from the chaincode package against the instantiation/upgrade proposal submitter at instantiation/upgrade endorsement time. For instantiate, the instantiation policy is fetched from the cc package on disk. In addition, the instantiation policy is added to the ChaincodeData struct, which is written to the LSCC state. This is necessary to have the policy available for a subsequent chaincode upgrade. On chaincode upgrade, the submitter is evaluated against the instantiation policy of the currently instantiated chaincode. Next, the submitter is evaluated against the instantiation policy of the new chaincode. If both checks pass, the instantiation policy of the new chaincode replaces the previous instantiation policy. Unit tests for instantiate and upgrade are implemented as part of the LSCC tests. To test manually on a runnning system, use the CLI to package, sign, install, instantiate and upgrade chaincode with instantiation policies. A simple example of an instantiation policy for the sample config is: "OR('DEFAULT.member', 'DEFAULT.admin')" Change-Id: I975de7dfa8cc28816b2ea270900b08abb51bb9ef Signed-off-by: Matthias Neugschwandtner --- core/common/ccprovider/ccprovider.go | 3 + core/common/ccprovider/sigcdspackage.go | 8 + core/scc/lscc/lscc.go | 98 +++++++++ core/scc/lscc/lscc_test.go | 280 +++++++++++++++++++++++- 4 files changed, 387 insertions(+), 2 deletions(-) diff --git a/core/common/ccprovider/ccprovider.go b/core/common/ccprovider/ccprovider.go index 6a46b9d7890..472485f327f 100644 --- a/core/common/ccprovider/ccprovider.go +++ b/core/common/ccprovider/ccprovider.go @@ -289,6 +289,9 @@ type ChaincodeData struct { //This is not currently used anywhere but serves as a good //eyecatcher Id []byte `protobuf:"bytes,7,opt,name=id,proto3"` + + //InstantiationPolicy for the chaincode + InstantiationPolicy []byte `protobuf:"bytes,8,opt,name=instantiation_policy,proto3"` } //implement functions needed from proto.Message for proto's mar/unmarshal functions diff --git a/core/common/ccprovider/sigcdspackage.go b/core/common/ccprovider/sigcdspackage.go index 1a385299b3f..9b3473b4810 100644 --- a/core/common/ccprovider/sigcdspackage.go +++ b/core/common/ccprovider/sigcdspackage.go @@ -100,6 +100,14 @@ func (ccpack *SignedCDSPackage) GetDepSpec() *pb.ChaincodeDeploymentSpec { return ccpack.depSpec } +// GetInstantiationPolicy gets the instantiation policy from the package +func (ccpack *SignedCDSPackage) GetInstantiationPolicy() []byte { + if ccpack.sDepSpec == nil { + panic("GetInstantiationPolicy called on uninitialized package") + } + return ccpack.sDepSpec.InstantiationPolicy +} + // GetDepSpecBytes gets the serialized ChaincodeDeploymentSpec from the package func (ccpack *SignedCDSPackage) GetDepSpecBytes() []byte { //this has to be after creating a package and initializing it diff --git a/core/scc/lscc/lscc.go b/core/scc/lscc/lscc.go index 13767afc932..a8656d64f38 100644 --- a/core/scc/lscc/lscc.go +++ b/core/scc/lscc/lscc.go @@ -31,6 +31,8 @@ import ( "github.com/hyperledger/fabric/core/policy" "github.com/hyperledger/fabric/core/policyprovider" "github.com/hyperledger/fabric/msp/mgmt" + mspmgmt "github.com/hyperledger/fabric/msp/mgmt" + "github.com/hyperledger/fabric/protos/common" pb "github.com/hyperledger/fabric/protos/peer" "github.com/hyperledger/fabric/protos/utils" ) @@ -220,6 +222,13 @@ func (f InvalidCCOnFSError) Error() string { return fmt.Sprintf("chaincode fingerprint mismatch %s", string(f)) } +//InstantiationPolicyViolatedErr when chaincode instantiation policy has been violated on instantiate or upgrade +type InstantiationPolicyViolatedErr string + +func (f InstantiationPolicyViolatedErr) Error() string { + return "chaincode instantiation policy violated" +} + //-------------- helper functions ------------------ //create the chaincode on the given chain func (lscc *LifeCycleSysCC) createChaincode(stub shim.ChaincodeStubInterface, cd *ccprovider.ChaincodeData) error { @@ -462,6 +471,63 @@ func (lscc *LifeCycleSysCC) executeInstall(stub shim.ChaincodeStubInterface, ccb return err } +// getInstantiationPolicy retrieves the instantiation policy from a SignedCDSPackage +func (lscc *LifeCycleSysCC) getInstantiationPolicy(stub shim.ChaincodeStubInterface, ccpack ccprovider.CCPackage) ([]byte, error) { + //if ccpack is a SignedCDSPackage, evaluate submitter against instantiation policy + sccpack, isSccpack := ccpack.(*ccprovider.SignedCDSPackage) + if isSccpack { + ip := sccpack.GetInstantiationPolicy() + if ip == nil { + return nil, fmt.Errorf("Instantiation policy cannot be null for a SignedCCDeploymentSpec") + } + return ip, nil + } + return nil, nil +} + +// checkInstantiationPolicy evaluates an instantiation policy against a signed proposal +func (lscc *LifeCycleSysCC) checkInstantiationPolicy(stub shim.ChaincodeStubInterface, chainName string, instantiationPolicy []byte) error { + // create a policy object from the policy bytes + mgr := mspmgmt.GetManagerForChain(chainName) + if mgr == nil { + return fmt.Errorf("Error checking chaincode instantiation policy: MSP manager for chain %s not found", chainName) + } + npp := cauthdsl.NewPolicyProvider(mgr) + instPol, _, err := npp.NewPolicy(instantiationPolicy) + if err != nil { + return err + } + // get the signed instantiation proposal + signedProp, err := stub.GetSignedProposal() + if err != nil { + return err + } + proposal, err := utils.GetProposal(signedProp.ProposalBytes) + if err != nil { + return err + } + // get the signature header of the proposal + header, err := utils.GetHeader(proposal.Header) + if err != nil { + return err + } + shdr, err := utils.GetSignatureHeader(header.SignatureHeader) + if err != nil { + return err + } + // construct signed data we can evaluate the instantiation policy against + sd := []*common.SignedData{&common.SignedData{ + Data: signedProp.ProposalBytes, + Identity: shdr.Creator, + Signature: signedProp.Signature, + }} + err = instPol.Evaluate(sd) + if err != nil { + return InstantiationPolicyViolatedErr("") + } + return nil +} + // executeDeploy implements the "instantiate" Invoke transaction func (lscc *LifeCycleSysCC) executeDeploy(stub shim.ChaincodeStubInterface, chainname string, depSpec []byte, policy []byte, escc []byte, vscc []byte) (*ccprovider.ChaincodeData, error) { cds, err := utils.GetChaincodeDeploymentSpec(depSpec) @@ -502,6 +568,18 @@ func (lscc *LifeCycleSysCC) executeDeploy(stub shim.ChaincodeStubInterface, chai cd.Vscc = string(vscc) cd.Policy = policy + // retrieve and evaluate instantiation policy + cd.InstantiationPolicy, err = lscc.getInstantiationPolicy(stub, ccpack) + if err != nil { + return nil, err + } + if cd.InstantiationPolicy != nil { + err = lscc.checkInstantiationPolicy(stub, chainname, cd.InstantiationPolicy) + if err != nil { + return nil, err + } + } + err = lscc.createChaincode(stub, cd) return cd, err @@ -546,6 +624,14 @@ func (lscc *LifeCycleSysCC) executeUpgrade(stub shim.ChaincodeStubInterface, cha return nil, IdenticalVersionErr(cds.ChaincodeSpec.ChaincodeId.Name) } + //do not upgrade if instantiation policy is violated + if cd.InstantiationPolicy != nil { + err = lscc.checkInstantiationPolicy(stub, chainName, cd.InstantiationPolicy) + if err != nil { + return nil, err + } + } + ccpack, err := ccprovider.GetChaincodeFromFS(chaincodeName, cds.ChaincodeSpec.ChaincodeId.Version) if err != nil { return nil, fmt.Errorf("cannot get package for the chaincode to be upgraded (%s:%s)-%s", chaincodeName, cds.ChaincodeSpec.ChaincodeId.Version, err) @@ -559,6 +645,18 @@ func (lscc *LifeCycleSysCC) executeUpgrade(stub shim.ChaincodeStubInterface, cha cd.Vscc = string(vscc) cd.Policy = policy + // retrieve and evaluate new instantiation policy + cd.InstantiationPolicy, err = lscc.getInstantiationPolicy(stub, ccpack) + if err != nil { + return nil, err + } + if cd.InstantiationPolicy != nil { + err = lscc.checkInstantiationPolicy(stub, chainName, cd.InstantiationPolicy) + if err != nil { + return nil, err + } + } + err = lscc.upgradeChaincode(stub, cd) if err != nil { return nil, err diff --git a/core/scc/lscc/lscc_test.go b/core/scc/lscc/lscc_test.go index 24091756269..0f5e6ea7341 100644 --- a/core/scc/lscc/lscc_test.go +++ b/core/scc/lscc/lscc_test.go @@ -23,7 +23,10 @@ import ( "testing" "github.com/golang/protobuf/proto" + "github.com/hyperledger/fabric/common/cauthdsl" + "github.com/hyperledger/fabric/common/util" "github.com/hyperledger/fabric/core/chaincode/shim" + "github.com/hyperledger/fabric/core/common/ccpackage" "github.com/hyperledger/fabric/core/common/ccprovider" "github.com/hyperledger/fabric/core/common/sysccprovider" //"github.com/hyperledger/fabric/core/container" @@ -32,8 +35,14 @@ import ( "compress/gzip" "github.com/hyperledger/fabric/common/policies" - "github.com/hyperledger/fabric/core/container/util" "github.com/hyperledger/fabric/core/policy" + "github.com/hyperledger/fabric/msp" + mspmgmt "github.com/hyperledger/fabric/msp/mgmt" + "github.com/hyperledger/fabric/msp/mgmt/testtools" + "github.com/hyperledger/fabric/protos/common" + putils "github.com/hyperledger/fabric/protos/utils" + + cutil "github.com/hyperledger/fabric/core/container/util" pb "github.com/hyperledger/fabric/protos/peer" "github.com/hyperledger/fabric/protos/utils" ) @@ -73,7 +82,7 @@ func constructDeploymentSpec(name string, path string, version string, initArgs gz := gzip.NewWriter(codePackageBytes) tw := tar.NewWriter(gz) - err := util.WriteBytesToPackage("src/garbage.go", []byte(name+path+version), tw) + err := cutil.WriteBytesToPackage("src/garbage.go", []byte(name+path+version), tw) if err != nil { return nil, err } @@ -627,6 +636,101 @@ func TestTamperChaincode(t *testing.T) { } } +//TestIPolDeploy tests chaincode deploy with an instantiation policy +func TestIPolDeploy(t *testing.T) { + // default policy, this should succeed + testIPolDeploy(t, "", true) + // policy involving an unknown ORG, this should fail + testIPolDeploy(t, "AND('ORG.admin')", false) +} + +func testIPolDeploy(t *testing.T, iPol string, successExpected bool) { + scc := new(LifeCycleSysCC) + stub := shim.NewMockStub("lscc", scc) + + if res := stub.MockInit("1", nil); res.Status != shim.OK { + t.Fatalf("Init failed [%s]", string(res.Message)) + } + + // Init the policy checker + identityDeserializer := &policy.MockIdentityDeserializer{[]byte("Alice"), []byte("msg1")} + policyManagerGetter := &policy.MockChannelPolicyManagerGetter{ + Managers: map[string]policies.Manager{ + chainid: &policy.MockChannelPolicyManager{MockPolicy: &policy.MockPolicy{Deserializer: identityDeserializer}}, + }, + } + scc.policyChecker = policy.NewPolicyChecker( + policyManagerGetter, + identityDeserializer, + &policy.MockMSPPrincipalGetter{Principal: []byte("Alice")}, + ) + sProp, _ := utils.MockSignedEndorserProposalOrPanic("", &pb.ChaincodeSpec{}, []byte("Alice"), []byte("msg1")) + identityDeserializer.Msg = sProp.ProposalBytes + sProp.Signature = sProp.ProposalBytes + + // create deployment spec, don't write to disk, just marshal it to be used in a signed dep spec + cds, err := constructDeploymentSpec("example02", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", "0", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false) + if err != nil { + t.Fatalf("Error creating deployment spec: [%s]", err) + } + // create an instantiation policy + var ip *common.SignaturePolicyEnvelope + ip = cauthdsl.SignedByMspAdmin(mspid) + if iPol != "" { + ip, err = cauthdsl.FromString(iPol) + if err != nil { + t.Fatalf("Error creating instantiation policy %s: [%s]", iPol, err) + } + } + // create signed dep spec + cdsbytes, err := proto.Marshal(cds) + if err != nil { + t.Fatalf("Marshalling CDS failed: [%s]", err) + } + objToWrite, err := ccpackage.OwnerCreateSignedCCDepSpec(cds, ip, nil) + if err != nil { + t.Fatalf("Failed to create Signed CDS envelope %s", err) + } + // write it to disk + bytesToWrite, err := proto.Marshal(objToWrite) + if err != nil { + t.Fatalf("Failed to marshal Signed CDS envelope %s", err) + } + fileToWrite := lscctestpath + "/example02.0" + err = ioutil.WriteFile(fileToWrite, bytesToWrite, 0700) + if err != nil { + t.Fatalf("Failed to write Signed CDS envelope to disk: %s", err) + } + defer os.Remove(lscctestpath + "/example02.0") + + // invoke deploy with a signed proposal that will be evaluated based on the policy + prop, _, err := putils.CreateChaincodeProposal( + common.HeaderType_ENDORSER_TRANSACTION, + chainid, + &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{}}, + sid) + if err != nil { + t.Fatalf("Error creating signed CC proposal [%s]", err) + } + sProp2, err := putils.GetSignedProposal(prop, id) + if err != nil { + t.Fatalf("Error getting signed proposal [%s]", err) + } + args := [][]byte{[]byte(DEPLOY), []byte(chainid), cdsbytes} + if res := stub.MockInvokeWithSignedProposal("1", args, sProp2); res.Status != shim.OK { + if successExpected { + t.Fatalf("Deploy failed %s", res) + } + } + + args = [][]byte{[]byte(GETCCINFO), []byte(chainid), []byte(cds.ChaincodeSpec.ChaincodeId.Name)} + if res := stub.MockInvokeWithSignedProposal("1", args, sProp); res.Status != shim.OK { + if successExpected { + t.Fatalf("GetCCInfo failed %s", res) + } + } +} + // TestUpgrade tests the upgrade function with various inputs for basic use cases func TestUpgrade(t *testing.T) { path := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" @@ -707,6 +811,134 @@ func testUpgrade(t *testing.T, ccname string, version string, newccname string, } } +//TestIPolUpgrade tests chaincode deploy with an instantiation policy +func TestIPolUpgrade(t *testing.T) { + // default policy, this should succeed + testIPolUpgrade(t, "", true) + // policy involving an unknown ORG, this should fail + testIPolUpgrade(t, "AND('ORG.admin')", false) +} + +func testIPolUpgrade(t *testing.T, iPol string, successExpected bool) { + // deploy version 0 with a default instantiation policy, this should succeed in any case + scc := new(LifeCycleSysCC) + stub := shim.NewMockStub("lscc", scc) + if res := stub.MockInit("1", nil); res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + // Init the policy checker + identityDeserializer := &policy.MockIdentityDeserializer{[]byte("Alice"), []byte("msg1")} + policyManagerGetter := &policy.MockChannelPolicyManagerGetter{ + Managers: map[string]policies.Manager{ + chainid: &policy.MockChannelPolicyManager{MockPolicy: &policy.MockPolicy{Deserializer: identityDeserializer}}, + }, + } + scc.policyChecker = policy.NewPolicyChecker( + policyManagerGetter, + identityDeserializer, + &policy.MockMSPPrincipalGetter{Principal: []byte("Alice")}, + ) + sProp, _ := utils.MockSignedEndorserProposalOrPanic("", &pb.ChaincodeSpec{}, []byte("Alice"), []byte("msg1")) + identityDeserializer.Msg = sProp.ProposalBytes + sProp.Signature = sProp.ProposalBytes + // create deployment spec, don't write to disk, just marshal it to be used in a signed dep spec + cds, err := constructDeploymentSpec("example02", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", "0", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false) + if err != nil { + t.Fatalf("Error creating deployment spec: [%s]", err) + } + // create an instantiation policy + ip := cauthdsl.SignedByMspAdmin(mspid) + // create signed dep spec + cdsbytes, err := proto.Marshal(cds) + if err != nil { + t.Fatalf("Marshalling CDS failed: [%s]", err) + } + objToWrite, err := ccpackage.OwnerCreateSignedCCDepSpec(cds, ip, nil) + if err != nil { + t.Fatalf("Failed to create Signed CDS envelope %s", err) + } + // write it to disk + bytesToWrite, err := proto.Marshal(objToWrite) + if err != nil { + t.Fatalf("Failed to marshal Signed CDS envelope %s", err) + } + fileToWrite := lscctestpath + "/example02.0" + err = ioutil.WriteFile(fileToWrite, bytesToWrite, 0700) + if err != nil { + t.Fatalf("Failed to write CC to disk: %s", err) + } + defer os.Remove(lscctestpath + "/example02.0") + // invoke deploy with a signed proposal that will be evaluated based on the policy + prop, _, err := putils.CreateChaincodeProposal( + common.HeaderType_ENDORSER_TRANSACTION, + chainid, + &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{}}, + sid) + if err != nil { + t.Fatalf("Error creating signed CC proposal [%s]", err) + } + sProp2, err := putils.GetSignedProposal(prop, id) + if err != nil { + t.Fatalf("Error getting signed proposal [%s]", err) + } + args := [][]byte{[]byte(DEPLOY), []byte(chainid), cdsbytes} + if res := stub.MockInvokeWithSignedProposal("1", args, sProp2); res.Status != shim.OK { + t.Fatalf("Deploy failed %s", res) + } + args = [][]byte{[]byte(GETCCINFO), []byte(chainid), []byte(cds.ChaincodeSpec.ChaincodeId.Name)} + if res := stub.MockInvokeWithSignedProposal("1", args, sProp); res.Status != shim.OK { + t.Fatalf("GetCCInfo after deploy failed %s", res) + } + + // here starts the interesting part for upgrade + // create deployment spec + cds, err = constructDeploymentSpec("example02", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", "1", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, false) + if err != nil { + t.Fatalf("Error creating deployment spec: [%s]", err) + } + cdsbytes, err = proto.Marshal(cds) + if err != nil { + t.Fatalf("Marshalling CDS failed: [%s]", err) + } + // create the instantiation policy + if iPol != "" { + ip, err = cauthdsl.FromString(iPol) + if err != nil { + t.Fatalf("Error creating instantiation policy %s: [%s]", iPol, err) + } + } + // create the signed ccpackage of the new version + objToWrite, err = ccpackage.OwnerCreateSignedCCDepSpec(cds, ip, nil) + if err != nil { + t.Fatalf("Failed to create Signed CDS envelope %s", err) + } + bytesToWrite, err = proto.Marshal(objToWrite) + if err != nil { + t.Fatalf("Failed to marshal Signed CDS envelope %s", err) + } + fileToWrite = lscctestpath + "/example02.1" + err = ioutil.WriteFile(fileToWrite, bytesToWrite, 0700) + if err != nil { + t.Fatalf("Failed to write CC to disk: %s", err) + } + defer os.Remove(lscctestpath + "/example02.1") + + // invoke upgrade with a signed proposal that will be evaluated based on the policy + args = [][]byte{[]byte(UPGRADE), []byte(chainid), cdsbytes} + if res := stub.MockInvokeWithSignedProposal("1", args, sProp2); res.Status != shim.OK { + if successExpected { + t.Fatalf("Upgrade failed %s", res) + } + } + args = [][]byte{[]byte(GETCCINFO), []byte(chainid), []byte(cds.ChaincodeSpec.ChaincodeId.Name)} + if res := stub.MockInvokeWithSignedProposal("1", args, sProp); res.Status != shim.OK { + if successExpected { + t.Fatalf("GetCCInfo failed") + } + } +} + //TestGetAPIsWithoutInstall get functions should return the right responses when chaicode is on //ledger but not on FS func TestGetAPIsWithoutInstall(t *testing.T) { @@ -1000,8 +1232,52 @@ func TestGetCCAccessRights(t *testing.T) { } } +var id msp.SigningIdentity +var sid []byte +var mspid string +var chainid string = util.GetTestChainID() + func TestMain(m *testing.M) { ccprovider.SetChaincodesPath(lscctestpath) sysccprovider.RegisterSystemChaincodeProviderFactory(&mocksccProviderFactory{}) + var err error + + // setup the MSP manager so that we can sign/verify + msptesttools.LoadMSPSetupForTesting() + + id, err = mspmgmt.GetLocalMSP().GetDefaultSigningIdentity() + if err != nil { + fmt.Printf("GetSigningIdentity failed with err %s", err) + os.Exit(-1) + } + + sid, err = id.Serialize() + if err != nil { + fmt.Printf("Serialize failed with err %s", err) + os.Exit(-1) + } + + // determine the MSP identifier for the first MSP in the default chain + var msp msp.MSP + mspMgr := mspmgmt.GetManagerForChain(chainid) + msps, err := mspMgr.GetMSPs() + if err != nil { + fmt.Printf("Could not retrieve the MSPs for the chain manager, err %s", err) + os.Exit(-1) + } + if len(msps) == 0 { + fmt.Printf("At least one MSP was expected") + os.Exit(-1) + } + for _, m := range msps { + msp = m + break + } + mspid, err = msp.GetIdentifier() + if err != nil { + fmt.Printf("Failure getting the msp identifier, err %s", err) + os.Exit(-1) + } + os.Exit(m.Run()) }