382 lines
11 KiB
Go
382 lines
11 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package policies
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
cb "github.com/hyperledger/fabric-protos-go/common"
|
|
"github.com/hyperledger/fabric-protos-go/msp"
|
|
"github.com/hyperledger/fabric/common/crypto/tlsgen"
|
|
"github.com/hyperledger/fabric/common/flogging/floggingtest"
|
|
"github.com/hyperledger/fabric/common/policies/mocks"
|
|
mspi "github.com/hyperledger/fabric/msp"
|
|
"github.com/hyperledger/fabric/protoutil"
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
//go:generate counterfeiter -o mocks/identity_deserializer.go --fake-name IdentityDeserializer . identityDeserializer
|
|
type identityDeserializer interface {
|
|
mspi.IdentityDeserializer
|
|
}
|
|
|
|
//go:generate counterfeiter -o mocks/identity.go --fake-name Identity . identity
|
|
type identity interface {
|
|
mspi.Identity
|
|
}
|
|
|
|
type mockProvider struct{}
|
|
|
|
func (mpp mockProvider) NewPolicy(data []byte) (Policy, proto.Message, error) {
|
|
return nil, nil, nil
|
|
}
|
|
|
|
const mockType = int32(0)
|
|
|
|
func defaultProviders() map[int32]Provider {
|
|
providers := make(map[int32]Provider)
|
|
providers[mockType] = &mockProvider{}
|
|
return providers
|
|
}
|
|
|
|
func TestUnnestedManager(t *testing.T) {
|
|
config := &cb.ConfigGroup{
|
|
Policies: map[string]*cb.ConfigPolicy{
|
|
"1": {Policy: &cb.Policy{Type: mockType}},
|
|
"2": {Policy: &cb.Policy{Type: mockType}},
|
|
"3": {Policy: &cb.Policy{Type: mockType}},
|
|
},
|
|
}
|
|
|
|
m, err := NewManagerImpl("test", defaultProviders(), config)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, m)
|
|
|
|
_, ok := m.Manager([]string{"subGroup"})
|
|
require.False(t, ok, "Should not have found a subgroup manager")
|
|
|
|
r, ok := m.Manager([]string{})
|
|
require.True(t, ok, "Should have found the root manager")
|
|
require.Equal(t, m, r)
|
|
|
|
require.Len(t, m.Policies, len(config.Policies))
|
|
|
|
for policyName := range config.Policies {
|
|
_, ok := m.GetPolicy(policyName)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
}
|
|
}
|
|
|
|
func TestNestedManager(t *testing.T) {
|
|
config := &cb.ConfigGroup{
|
|
Policies: map[string]*cb.ConfigPolicy{
|
|
"n0a": {Policy: &cb.Policy{Type: mockType}},
|
|
"n0b": {Policy: &cb.Policy{Type: mockType}},
|
|
"n0c": {Policy: &cb.Policy{Type: mockType}},
|
|
},
|
|
Groups: map[string]*cb.ConfigGroup{
|
|
"nest1": {
|
|
Policies: map[string]*cb.ConfigPolicy{
|
|
"n1a": {Policy: &cb.Policy{Type: mockType}},
|
|
"n1b": {Policy: &cb.Policy{Type: mockType}},
|
|
"n1c": {Policy: &cb.Policy{Type: mockType}},
|
|
},
|
|
Groups: map[string]*cb.ConfigGroup{
|
|
"nest2a": {
|
|
Policies: map[string]*cb.ConfigPolicy{
|
|
"n2a_1": {Policy: &cb.Policy{Type: mockType}},
|
|
"n2a_2": {Policy: &cb.Policy{Type: mockType}},
|
|
"n2a_3": {Policy: &cb.Policy{Type: mockType}},
|
|
},
|
|
},
|
|
"nest2b": {
|
|
Policies: map[string]*cb.ConfigPolicy{
|
|
"n2b_1": {Policy: &cb.Policy{Type: mockType}},
|
|
"n2b_2": {Policy: &cb.Policy{Type: mockType}},
|
|
"n2b_3": {Policy: &cb.Policy{Type: mockType}},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
m, err := NewManagerImpl("nest0", defaultProviders(), config)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, m)
|
|
|
|
r, ok := m.Manager([]string{})
|
|
require.True(t, ok, "Should have found the root manager")
|
|
require.Equal(t, m, r)
|
|
|
|
n1, ok := m.Manager([]string{"nest1"})
|
|
require.True(t, ok)
|
|
n2a, ok := m.Manager([]string{"nest1", "nest2a"})
|
|
require.True(t, ok)
|
|
n2b, ok := m.Manager([]string{"nest1", "nest2b"})
|
|
require.True(t, ok)
|
|
|
|
n2as, ok := n1.Manager([]string{"nest2a"})
|
|
require.True(t, ok)
|
|
require.Equal(t, n2a, n2as)
|
|
n2bs, ok := n1.Manager([]string{"nest2b"})
|
|
require.True(t, ok)
|
|
require.Equal(t, n2b, n2bs)
|
|
|
|
absPrefix := PathSeparator + "nest0" + PathSeparator
|
|
for policyName := range config.Policies {
|
|
_, ok := m.GetPolicy(policyName)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
|
|
absName := absPrefix + policyName
|
|
_, ok = m.GetPolicy(absName)
|
|
require.True(t, ok, "Should have found absolute policy %s", absName)
|
|
}
|
|
|
|
for policyName := range config.Groups["nest1"].Policies {
|
|
_, ok := n1.GetPolicy(policyName)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
|
|
relPathFromBase := "nest1" + PathSeparator + policyName
|
|
_, ok = m.GetPolicy(relPathFromBase)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
|
|
for i, abs := range []Manager{n1, m} {
|
|
absName := absPrefix + relPathFromBase
|
|
_, ok = abs.GetPolicy(absName)
|
|
require.True(t, ok, "Should have found absolutely policy for manager %d", i)
|
|
}
|
|
}
|
|
|
|
for policyName := range config.Groups["nest1"].Groups["nest2a"].Policies {
|
|
_, ok := n2a.GetPolicy(policyName)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
|
|
relPathFromN1 := "nest2a" + PathSeparator + policyName
|
|
_, ok = n1.GetPolicy(relPathFromN1)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
|
|
relPathFromBase := "nest1" + PathSeparator + relPathFromN1
|
|
_, ok = m.GetPolicy(relPathFromBase)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
|
|
for i, abs := range []Manager{n2a, n1, m} {
|
|
absName := absPrefix + relPathFromBase
|
|
_, ok = abs.GetPolicy(absName)
|
|
require.True(t, ok, "Should have found absolutely policy for manager %d", i)
|
|
}
|
|
}
|
|
|
|
for policyName := range config.Groups["nest1"].Groups["nest2b"].Policies {
|
|
_, ok := n2b.GetPolicy(policyName)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
|
|
relPathFromN1 := "nest2b" + PathSeparator + policyName
|
|
_, ok = n1.GetPolicy(relPathFromN1)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
|
|
relPathFromBase := "nest1" + PathSeparator + relPathFromN1
|
|
_, ok = m.GetPolicy(relPathFromBase)
|
|
require.True(t, ok, "Should have found policy %s", policyName)
|
|
|
|
for i, abs := range []Manager{n2b, n1, m} {
|
|
absName := absPrefix + relPathFromBase
|
|
_, ok = abs.GetPolicy(absName)
|
|
require.True(t, ok, "Should have found absolutely policy for manager %d", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPrincipalUniqueSet(t *testing.T) {
|
|
var principalSet PrincipalSet
|
|
addPrincipal := func(i int) {
|
|
principalSet = append(principalSet, &msp.MSPPrincipal{
|
|
PrincipalClassification: msp.MSPPrincipal_Classification(i),
|
|
Principal: []byte(fmt.Sprintf("%d", i)),
|
|
})
|
|
}
|
|
|
|
addPrincipal(1)
|
|
addPrincipal(2)
|
|
addPrincipal(2)
|
|
addPrincipal(3)
|
|
addPrincipal(3)
|
|
addPrincipal(3)
|
|
|
|
for principal, plurality := range principalSet.UniqueSet() {
|
|
require.Equal(t, int(principal.PrincipalClassification), plurality)
|
|
require.Equal(t, fmt.Sprintf("%d", plurality), string(principal.Principal))
|
|
}
|
|
|
|
v := reflect.Indirect(reflect.ValueOf(msp.MSPPrincipal{}))
|
|
// Ensure msp.MSPPrincipal has only 2 fields.
|
|
// This is essential for 'UniqueSet' to work properly
|
|
// XXX This is a rather brittle check and brittle way to fix the test
|
|
// There seems to be an assumption that the number of fields in the proto
|
|
// struct matches the number of fields in the proto message
|
|
require.Equal(t, 5, v.NumField())
|
|
}
|
|
|
|
func TestPrincipalSetContainingOnly(t *testing.T) {
|
|
var principalSets PrincipalSets
|
|
var principalSet PrincipalSet
|
|
for j := 0; j < 3; j++ {
|
|
for i := 0; i < 10; i++ {
|
|
principalSet = append(principalSet, &msp.MSPPrincipal{
|
|
PrincipalClassification: msp.MSPPrincipal_IDENTITY,
|
|
Principal: []byte(fmt.Sprintf("%d", j*10+i)),
|
|
})
|
|
}
|
|
principalSets = append(principalSets, principalSet)
|
|
principalSet = nil
|
|
}
|
|
|
|
between20And30 := func(principal *msp.MSPPrincipal) bool {
|
|
n, _ := strconv.ParseInt(string(principal.Principal), 10, 32)
|
|
return n >= 20 && n <= 29
|
|
}
|
|
|
|
principalSets = principalSets.ContainingOnly(between20And30)
|
|
|
|
require.Len(t, principalSets, 1)
|
|
require.True(t, principalSets[0].ContainingOnly(between20And30))
|
|
}
|
|
|
|
func TestSignatureSetToValidIdentities(t *testing.T) {
|
|
sd := []*protoutil.SignedData{
|
|
{
|
|
Data: []byte("data1"),
|
|
Identity: []byte("identity1"),
|
|
Signature: []byte("signature1"),
|
|
},
|
|
{
|
|
Data: []byte("data1"),
|
|
Identity: []byte("identity1"),
|
|
Signature: []byte("signature1"),
|
|
},
|
|
}
|
|
|
|
fIDDs := &mocks.IdentityDeserializer{}
|
|
fID := &mocks.Identity{}
|
|
fID.VerifyReturns(nil)
|
|
fID.GetIdentifierReturns(&mspi.IdentityIdentifier{
|
|
Id: "id",
|
|
Mspid: "mspid",
|
|
})
|
|
fIDDs.DeserializeIdentityReturns(fID, nil)
|
|
|
|
ids := SignatureSetToValidIdentities(sd, fIDDs)
|
|
require.Len(t, ids, 1)
|
|
require.NotNil(t, ids[0].GetIdentifier())
|
|
require.Equal(t, "id", ids[0].GetIdentifier().Id)
|
|
require.Equal(t, "mspid", ids[0].GetIdentifier().Mspid)
|
|
data, sig := fID.VerifyArgsForCall(0)
|
|
require.Equal(t, []byte("data1"), data)
|
|
require.Equal(t, []byte("signature1"), sig)
|
|
sidBytes := fIDDs.DeserializeIdentityArgsForCall(0)
|
|
require.Equal(t, []byte("identity1"), sidBytes)
|
|
}
|
|
|
|
func TestSignatureSetToValidIdentitiesDeserializeErr(t *testing.T) {
|
|
oldLogger := logger
|
|
l, recorder := floggingtest.NewTestLogger(t, floggingtest.AtLevel(zapcore.InfoLevel))
|
|
logger = l
|
|
defer func() { logger = oldLogger }()
|
|
|
|
fakeIdentityDeserializer := &mocks.IdentityDeserializer{}
|
|
fakeIdentityDeserializer.DeserializeIdentityReturns(nil, errors.New("mango"))
|
|
|
|
// generate actual x509 certificate
|
|
ca, err := tlsgen.NewCA()
|
|
require.NoError(t, err)
|
|
client1, err := ca.NewClientCertKeyPair()
|
|
require.NoError(t, err)
|
|
id := &msp.SerializedIdentity{
|
|
Mspid: "MyMSP",
|
|
IdBytes: client1.Cert,
|
|
}
|
|
idBytes, err := proto.Marshal(id)
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
spec string
|
|
signedData []*protoutil.SignedData
|
|
expectedLogEntryContains []string
|
|
}{
|
|
{
|
|
spec: "deserialize identity error - identity is random bytes",
|
|
signedData: []*protoutil.SignedData{
|
|
{
|
|
Identity: []byte("identity1"),
|
|
},
|
|
},
|
|
expectedLogEntryContains: []string{"invalid identity", fmt.Sprintf("serialized-identity=%x", []byte("identity1")), "error=mango"},
|
|
},
|
|
{
|
|
spec: "deserialize identity error - actual certificate",
|
|
signedData: []*protoutil.SignedData{
|
|
{
|
|
Identity: idBytes,
|
|
},
|
|
},
|
|
expectedLogEntryContains: []string{"invalid identity", fmt.Sprintf("mspid=MyMSP subject=%s issuer=%s serialnumber=%d", client1.TLSCert.Subject, client1.TLSCert.Issuer, client1.TLSCert.SerialNumber), "error=mango"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.spec, func(t *testing.T) {
|
|
ids := SignatureSetToValidIdentities(tc.signedData, fakeIdentityDeserializer)
|
|
require.Len(t, ids, 0)
|
|
assertLogContains(t, recorder, tc.expectedLogEntryContains...)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSignatureSetToValidIdentitiesVerifyErr(t *testing.T) {
|
|
sd := []*protoutil.SignedData{
|
|
{
|
|
Data: []byte("data1"),
|
|
Identity: []byte("identity1"),
|
|
Signature: []byte("signature1"),
|
|
},
|
|
}
|
|
|
|
fIDDs := &mocks.IdentityDeserializer{}
|
|
fID := &mocks.Identity{}
|
|
fID.VerifyReturns(errors.New("bad signature"))
|
|
fID.GetIdentifierReturns(&mspi.IdentityIdentifier{
|
|
Id: "id",
|
|
Mspid: "mspid",
|
|
})
|
|
fIDDs.DeserializeIdentityReturns(fID, nil)
|
|
|
|
ids := SignatureSetToValidIdentities(sd, fIDDs)
|
|
require.Len(t, ids, 0)
|
|
data, sig := fID.VerifyArgsForCall(0)
|
|
require.Equal(t, []byte("data1"), data)
|
|
require.Equal(t, []byte("signature1"), sig)
|
|
sidBytes := fIDDs.DeserializeIdentityArgsForCall(0)
|
|
require.Equal(t, []byte("identity1"), sidBytes)
|
|
}
|
|
|
|
func assertLogContains(t *testing.T, r *floggingtest.Recorder, ss ...string) {
|
|
defer r.Reset()
|
|
entries := r.Entries()
|
|
for _, entry := range entries {
|
|
fmt.Println(entry)
|
|
}
|
|
for _, s := range ss {
|
|
require.NotEmpty(t, r.EntriesContaining(s))
|
|
}
|
|
}
|