From fa7faec367c2b7046c2bd232b27b434cecaaf6be Mon Sep 17 00:00:00 2001 From: Angelo De Caro Date: Mon, 3 Apr 2017 09:26:19 +0200 Subject: [PATCH] Access control at QSCC This change-set does the following: 1. Add access control to qscc by verifying that the caller has read access to the channel. A new test has been added to cover this access control This change-set comes in the context of: 1. https://jira.hyperledger.org/browse/FAB-2969 Change-Id: Ieca0babd3b65ecffda450b29cc82797a101f8671 Signed-off-by: Angelo De Caro --- core/chaincode/shim/mockstub.go | 21 +++++++- core/policy/mocks.go | 72 ++++++++++++------------- core/policy/policy_test.go | 16 +++--- core/scc/qscc/query.go | 23 +++++++- core/scc/qscc/query_test.go | 95 +++++++++++++++++++++++++++++---- 5 files changed, 171 insertions(+), 56 deletions(-) diff --git a/core/chaincode/shim/mockstub.go b/core/chaincode/shim/mockstub.go index 6be72655885..6b10b3d7546 100644 --- a/core/chaincode/shim/mockstub.go +++ b/core/chaincode/shim/mockstub.go @@ -61,6 +61,9 @@ type MockStub struct { TxID string TxTimestamp *timestamp.Timestamp + + // mocked signedProposal + signedProposal *pb.SignedProposal } func (stub *MockStub) GetTxID() string { @@ -96,11 +99,13 @@ func (stub *MockStub) GetFunctionAndParameters() (function string, params []stri // MockStub doesn't support concurrent transactions at present. func (stub *MockStub) MockTransactionStart(txid string) { stub.TxID = txid + stub.setSignedProposal(&pb.SignedProposal{}) stub.setTxTimestamp(util.CreateUtcTimestamp()) } // End a mocked transaction, clearing the UUID. func (stub *MockStub) MockTransactionEnd(uuid string) { + stub.signedProposal = nil stub.TxID = "" } @@ -129,6 +134,16 @@ func (stub *MockStub) MockInvoke(uuid string, args [][]byte) pb.Response { return res } +// Invoke this chaincode, also starts and ends a transaction. +func (stub *MockStub) MockInvokeWithSignedProposal(uuid string, args [][]byte, sp *pb.SignedProposal) pb.Response { + stub.args = args + stub.MockTransactionStart(uuid) + stub.signedProposal = sp + res := stub.cc.Invoke(stub) + stub.MockTransactionEnd(uuid) + return res +} + // GetState retrieves the value for a given key from the ledger func (stub *MockStub) GetState(key string) ([]byte, error) { value := stub.State[key] @@ -272,7 +287,11 @@ func (stub *MockStub) GetBinding() ([]byte, error) { // Not implemented func (stub *MockStub) GetSignedProposal() (*pb.SignedProposal, error) { - return nil, nil + return stub.signedProposal, nil +} + +func (stub *MockStub) setSignedProposal(sp *pb.SignedProposal) { + stub.signedProposal = sp } // Not implemented diff --git a/core/policy/mocks.go b/core/policy/mocks.go index af4ad0e2abf..215d44feec8 100644 --- a/core/policy/mocks.go +++ b/core/policy/mocks.go @@ -29,42 +29,42 @@ import ( mspproto "github.com/hyperledger/fabric/protos/msp" ) -type mockChannelPolicyManagerGetter struct { - managers map[string]policies.Manager +type MockChannelPolicyManagerGetter struct { + Managers map[string]policies.Manager } -func (c *mockChannelPolicyManagerGetter) Manager(channelID string) (policies.Manager, bool) { - return c.managers[channelID], true +func (c *MockChannelPolicyManagerGetter) Manager(channelID string) (policies.Manager, bool) { + return c.Managers[channelID], true } -type mockChannelPolicyManager struct { - mockPolicy policies.Policy +type MockChannelPolicyManager struct { + MockPolicy policies.Policy } -func (m *mockChannelPolicyManager) GetPolicy(id string) (policies.Policy, bool) { - return m.mockPolicy, true +func (m *MockChannelPolicyManager) GetPolicy(id string) (policies.Policy, bool) { + return m.MockPolicy, true } -func (m *mockChannelPolicyManager) Manager(path []string) (policies.Manager, bool) { +func (m *MockChannelPolicyManager) Manager(path []string) (policies.Manager, bool) { panic("Not implemented") } -func (m *mockChannelPolicyManager) BasePath() string { +func (m *MockChannelPolicyManager) BasePath() string { panic("Not implemented") } -func (m *mockChannelPolicyManager) PolicyNames() []string { +func (m *MockChannelPolicyManager) PolicyNames() []string { panic("Not implemented") } -type mockPolicy struct { - deserializer msp.IdentityDeserializer +type MockPolicy struct { + Deserializer msp.IdentityDeserializer } // Evaluate takes a set of SignedData and evaluates whether this set of signatures satisfies the policy -func (m *mockPolicy) Evaluate(signatureSet []*common.SignedData) error { +func (m *MockPolicy) Evaluate(signatureSet []*common.SignedData) error { fmt.Printf("Evaluate [%s], [% x], [% x]\n", string(signatureSet[0].Identity), string(signatureSet[0].Data), string(signatureSet[0].Signature)) - identity, err := m.deserializer.DeserializeIdentity(signatureSet[0].Identity) + identity, err := m.Deserializer.DeserializeIdentity(signatureSet[0].Identity) if err != nil { return err } @@ -72,50 +72,50 @@ func (m *mockPolicy) Evaluate(signatureSet []*common.SignedData) error { return identity.Verify(signatureSet[0].Data, signatureSet[0].Signature) } -type mockIdentityDeserializer struct { - identity []byte - msg []byte +type MockIdentityDeserializer struct { + Identity []byte + Msg []byte } -func (d *mockIdentityDeserializer) DeserializeIdentity(serializedIdentity []byte) (msp.Identity, error) { - fmt.Printf("id : [%s], [%s]\n", string(serializedIdentity), string(d.identity)) - if bytes.Equal(d.identity, serializedIdentity) { - fmt.Printf("GOT : [%s], [%s]\n", string(serializedIdentity), string(d.identity)) - return &mockIdentity{identity: d.identity, msg: d.msg}, nil +func (d *MockIdentityDeserializer) DeserializeIdentity(serializedIdentity []byte) (msp.Identity, error) { + fmt.Printf("id : [%s], [%s]\n", string(serializedIdentity), string(d.Identity)) + if bytes.Equal(d.Identity, serializedIdentity) { + fmt.Printf("GOT : [%s], [%s]\n", string(serializedIdentity), string(d.Identity)) + return &MockIdentity{identity: d.Identity, msg: d.Msg}, nil } - return nil, errors.New("Invalid identity") + return nil, errors.New("Invalid Identity") } -type mockIdentity struct { +type MockIdentity struct { identity []byte msg []byte } -func (id *mockIdentity) SatisfiesPrincipal(p *mspproto.MSPPrincipal) error { +func (id *MockIdentity) SatisfiesPrincipal(p *mspproto.MSPPrincipal) error { if !bytes.Equal(id.identity, p.Principal) { return fmt.Errorf("Different identities [% x]!=[% x]", id.identity, p.Principal) } return nil } -func (id *mockIdentity) GetIdentifier() *msp.IdentityIdentifier { +func (id *MockIdentity) GetIdentifier() *msp.IdentityIdentifier { return &msp.IdentityIdentifier{Mspid: "mock", Id: "mock"} } -func (id *mockIdentity) GetMSPIdentifier() string { +func (id *MockIdentity) GetMSPIdentifier() string { return "mock" } -func (id *mockIdentity) Validate() error { +func (id *MockIdentity) Validate() error { return nil } -func (id *mockIdentity) GetOrganizationalUnits() []string { +func (id *MockIdentity) GetOrganizationalUnits() []string { return []string{"dunno"} } -func (id *mockIdentity) Verify(msg []byte, sig []byte) error { +func (id *MockIdentity) Verify(msg []byte, sig []byte) error { fmt.Printf("VERIFY [% x], [% x], [% x]\n", string(id.msg), string(msg), string(sig)) if bytes.Equal(id.msg, msg) { if bytes.Equal(msg, sig) { @@ -126,22 +126,22 @@ func (id *mockIdentity) Verify(msg []byte, sig []byte) error { return errors.New("Invalid Signature") } -func (id *mockIdentity) VerifyOpts(msg []byte, sig []byte, opts msp.SignatureOpts) error { +func (id *MockIdentity) VerifyOpts(msg []byte, sig []byte, opts msp.SignatureOpts) error { return nil } -func (id *mockIdentity) VerifyAttributes(proof []byte, spec *msp.AttributeProofSpec) error { +func (id *MockIdentity) VerifyAttributes(proof []byte, spec *msp.AttributeProofSpec) error { return nil } -func (id *mockIdentity) Serialize() ([]byte, error) { +func (id *MockIdentity) Serialize() ([]byte, error) { return []byte("cert"), nil } -type mockMSPPrincipalGetter struct { +type MockMSPPrincipalGetter struct { Principal []byte } -func (m *mockMSPPrincipalGetter) Get(role string) (*mspproto.MSPPrincipal, error) { +func (m *MockMSPPrincipalGetter) Get(role string) (*mspproto.MSPPrincipal, error) { return &mspproto.MSPPrincipal{Principal: m.Principal}, nil } diff --git a/core/policy/policy_test.go b/core/policy/policy_test.go index 9e9ce1c7675..ba83d8cf421 100644 --- a/core/policy/policy_test.go +++ b/core/policy/policy_test.go @@ -26,18 +26,18 @@ import ( ) func TestPolicyChecker(t *testing.T) { - policyManagerGetter := &mockChannelPolicyManagerGetter{ + policyManagerGetter := &MockChannelPolicyManagerGetter{ map[string]policies.Manager{ - "A": &mockChannelPolicyManager{&mockPolicy{&mockIdentityDeserializer{[]byte("Alice"), []byte("msg1")}}}, - "B": &mockChannelPolicyManager{&mockPolicy{&mockIdentityDeserializer{[]byte("Bob"), []byte("msg2")}}}, - "C": &mockChannelPolicyManager{&mockPolicy{&mockIdentityDeserializer{[]byte("Alice"), []byte("msg3")}}}, + "A": &MockChannelPolicyManager{&MockPolicy{&MockIdentityDeserializer{[]byte("Alice"), []byte("msg1")}}}, + "B": &MockChannelPolicyManager{&MockPolicy{&MockIdentityDeserializer{[]byte("Bob"), []byte("msg2")}}}, + "C": &MockChannelPolicyManager{&MockPolicy{&MockIdentityDeserializer{[]byte("Alice"), []byte("msg3")}}}, }, } - identityDeserializer := &mockIdentityDeserializer{[]byte("Alice"), []byte("msg1")} + identityDeserializer := &MockIdentityDeserializer{[]byte("Alice"), []byte("msg1")} pc := NewPolicyChecker( policyManagerGetter, identityDeserializer, - &mockMSPPrincipalGetter{Principal: []byte("Alice")}, + &MockMSPPrincipalGetter{Principal: []byte("Alice")}, ) // Check that (non-empty channel, empty policy) fails @@ -58,7 +58,7 @@ func TestPolicyChecker(t *testing.T) { // Validate Alice signatures against channel A's readers sProp, _ := utils.MockSignedEndorserProposalOrPanic("A", &peer.ChaincodeSpec{}, []byte("Alice"), []byte("msg1")) - policyManagerGetter.managers["A"].(*mockChannelPolicyManager).mockPolicy.(*mockPolicy).deserializer.(*mockIdentityDeserializer).msg = sProp.ProposalBytes + policyManagerGetter.Managers["A"].(*MockChannelPolicyManager).MockPolicy.(*MockPolicy).Deserializer.(*MockIdentityDeserializer).Msg = sProp.ProposalBytes sProp.Signature = sProp.ProposalBytes err = pc.CheckPolicy("A", "readers", sProp) assert.NoError(t, err) @@ -72,7 +72,7 @@ func TestPolicyChecker(t *testing.T) { assert.Error(t, err) // Alice is a member of the local MSP, policy check must succeed - identityDeserializer.msg = sProp.ProposalBytes + identityDeserializer.Msg = sProp.ProposalBytes err = pc.CheckPolicyNoChannel("member", sProp) assert.NoError(t, err) diff --git a/core/scc/qscc/query.go b/core/scc/qscc/query.go index c316e99c120..b07916bb2cd 100644 --- a/core/scc/qscc/query.go +++ b/core/scc/qscc/query.go @@ -22,9 +22,12 @@ import ( "github.com/op/go-logging" + "github.com/hyperledger/fabric/common/policies" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/core/ledger" "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" "github.com/hyperledger/fabric/protos/utils" ) @@ -35,6 +38,7 @@ import ( // - GetBlockByHash returns a block // - GetTransactionByID returns a transaction type LedgerQuerier struct { + policyChecker policy.PolicyChecker } var qscclogger = logging.MustGetLogger("qscc") @@ -54,6 +58,13 @@ const ( func (e *LedgerQuerier) Init(stub shim.ChaincodeStubInterface) pb.Response { qscclogger.Info("Init QSCC") + // Init policy checker for access control + e.policyChecker = policy.NewPolicyChecker( + peer.NewChannelPolicyManagerGetter(), + mgmt.GetLocalMSP(), + mgmt.NewLocalMSPPrincipalGetter(), + ) + return shim.Success(nil) } @@ -85,7 +96,17 @@ func (e *LedgerQuerier) Invoke(stub shim.ChaincodeStubInterface) pb.Response { qscclogger.Debugf("Invoke function: %s on chain: %s", fname, cid) } - // TODO: Handle ACL + // Handle ACL: + // 1. get the signed proposal + sp, err := stub.GetSignedProposal() + if err != nil { + return shim.Error(fmt.Sprintf("Failed getting signed proposal from stub, %s: %s", cid, err)) + } + + // 2. check the channel reader policy + if err = e.policyChecker.CheckPolicy(cid, policies.ChannelApplicationReaders, sp); err != nil { + return shim.Error(fmt.Sprintf("Authorization request failed %s: %s", cid, err)) + } switch fname { case GetTransactionByID: diff --git a/core/scc/qscc/query_test.go b/core/scc/qscc/query_test.go index ffbe5c1b066..60a9849e15a 100644 --- a/core/scc/qscc/query_test.go +++ b/core/scc/qscc/query_test.go @@ -22,8 +22,15 @@ import ( "github.com/spf13/viper" + "strings" + + "github.com/hyperledger/fabric/common/policies" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/core/peer" + "github.com/hyperledger/fabric/core/policy" + peer2 "github.com/hyperledger/fabric/protos/peer" + "github.com/hyperledger/fabric/protos/utils" + "github.com/stretchr/testify/assert" ) func TestInit(t *testing.T) { @@ -50,8 +57,13 @@ func TestQueryGetChainInfo(t *testing.T) { e := new(LedgerQuerier) stub := shim.NewMockStub("LedgerQuerier", e) + if res := stub.MockInit("1", nil); res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + args := [][]byte{[]byte(GetChainInfo), []byte("mytestchainid2")} - if res := stub.MockInvoke("1", args); res.Status != shim.OK { + if res := stub.MockInvoke("2", args); res.Status != shim.OK { t.Fatalf("qscc GetChainInfo failed with err: %s", res.Message) } } @@ -65,9 +77,14 @@ func TestQueryGetTransactionByID(t *testing.T) { e := new(LedgerQuerier) stub := shim.NewMockStub("LedgerQuerier", e) + if res := stub.MockInit("1", nil); res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + args := [][]byte{[]byte(GetTransactionByID), []byte("mytestchainid3"), []byte("1")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("qscc getTransactionByID should have failed with invalid txid: 1") + if res := stub.MockInvoke("2", args); res.Status == shim.OK { + t.Fatal("qscc getTransactionByID should have failed with invalid txid: 2") } } @@ -80,10 +97,15 @@ func TestQueryWithWrongParameters(t *testing.T) { e := new(LedgerQuerier) stub := shim.NewMockStub("LedgerQuerier", e) + if res := stub.MockInit("1", nil); res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + // Test with wrong number of parameters args := [][]byte{[]byte(GetTransactionByID), []byte("mytestchainid4")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("qscc getTransactionByID should have failed with invalid txid: 1") + if res := stub.MockInvoke("2", args); res.Status == shim.OK { + t.Fatal("qscc getTransactionByID should have failed with invalid txid: 2") } } @@ -97,9 +119,14 @@ func TestQueryGetBlockByNumber(t *testing.T) { e := new(LedgerQuerier) stub := shim.NewMockStub("LedgerQuerier", e) + if res := stub.MockInit("1", nil); res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + args := [][]byte{[]byte(GetBlockByNumber), []byte("mytestchainid5"), []byte("0")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("qscc GetBlockByNumber should have failed with invalid number: 0") + if res := stub.MockInvoke("2", args); res.Status == shim.OK { + t.Fatal("qscc GetBlockByNumber should have failed with invalid number: 0") } } @@ -112,9 +139,14 @@ func TestQueryGetBlockByHash(t *testing.T) { e := new(LedgerQuerier) stub := shim.NewMockStub("LedgerQuerier", e) + if res := stub.MockInit("1", nil); res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + args := [][]byte{[]byte(GetBlockByHash), []byte("mytestchainid6"), []byte("0")} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { - t.Fatalf("qscc GetBlockByHash should have failed with invalid hash: 0") + if res := stub.MockInvoke("2", args); res.Status == shim.OK { + t.Fatal("qscc GetBlockByHash should have failed with invalid hash: 0") } } @@ -129,8 +161,51 @@ func TestQueryGetBlockByTxID(t *testing.T) { txID := "" + if res := stub.MockInit("1", nil); res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + args := [][]byte{[]byte(GetBlockByTxID), []byte("mytestchainid8"), []byte(txID)} - if res := stub.MockInvoke("1", args); res.Status == shim.OK { + if res := stub.MockInvoke("2", args); res.Status == shim.OK { t.Fatalf("qscc GetBlockByTxID should have failed with invalid txID: %s", txID) } } + +func TestFailingAccessControl(t *testing.T) { + viper.Set("peer.fileSystemPath", "/var/hyperledger/test9/") + defer os.RemoveAll("/var/hyperledger/test9/") + peer.MockInitialize() + peer.MockCreateChain("mytestchainid9") + + e := new(LedgerQuerier) + // Init the policy checker to have a failure + policyManagerGetter := &policy.MockChannelPolicyManagerGetter{ + Managers: map[string]policies.Manager{ + "mytestchainid9": &policy.MockChannelPolicyManager{MockPolicy: &policy.MockPolicy{Deserializer: &policy.MockIdentityDeserializer{[]byte("Alice"), []byte("msg1")}}}, + }, + } + + e.policyChecker = policy.NewPolicyChecker( + policyManagerGetter, + &policy.MockIdentityDeserializer{[]byte("Alice"), []byte("msg1")}, + &policy.MockMSPPrincipalGetter{Principal: []byte("Alice")}, + ) + + stub := shim.NewMockStub("LedgerQuerier", e) + + args := [][]byte{[]byte(GetChainInfo), []byte("mytestchainid9")} + sProp, _ := utils.MockSignedEndorserProposalOrPanic("mytestchainid9", &peer2.ChaincodeSpec{}, []byte("Alice"), []byte("msg1")) + policyManagerGetter.Managers["mytestchainid9"].(*policy.MockChannelPolicyManager).MockPolicy.(*policy.MockPolicy).Deserializer.(*policy.MockIdentityDeserializer).Msg = sProp.ProposalBytes + sProp.Signature = sProp.ProposalBytes + if res := stub.MockInvokeWithSignedProposal("2", args, sProp); res.Status != shim.OK { + t.Fatalf("qscc GetChainInfo failed with err: %s", res.Message) + } + + sProp, _ = utils.MockSignedEndorserProposalOrPanic("mytestchainid9", &peer2.ChaincodeSpec{}, []byte("Bob"), []byte("msg2")) + res := stub.MockInvokeWithSignedProposal("3", args, sProp) + if res.Status == shim.OK { + t.Fatalf("qscc GetChainInfo must fail: %s", res.Message) + } + assert.True(t, strings.HasPrefix(res.Message, "Authorization request failed")) +}