Skip to content

Commit

Permalink
Add a Policy Manager
Browse files Browse the repository at this point in the history
In order to enforce policy for configuration there must be a policy
manager which tracks the policies and evaluates them.

This policy manager is designed to be updated by the config manager,
which itself depends on the policy manager.  This loop is broken because
the policy manager stands on its own, and validates configuration
changes first, and then is atomically updated to the new policy
configuration if the new config (and policy) is admitted by the existing
policy.

Note that the mechanism for updating policy seems somewhat strange,
namely Begin, Propose, ..., Propose, Commit/Rollback, rather than simply
supplying a new set of policy.  This is done in order to accomodate the
design of the configuration manager (which comes next in this patch
series).

This resolves:

https://jira.hyperledger.org/browse/FAB-705

Change-Id: Ie4b85aed2622d34e9b29d10c46f39b266ac9a936
Signed-off-by: Jason Yellick <jyellick@us.ibm.com>
  • Loading branch information
Jason Yellick committed Oct 25, 2016
1 parent 4bead68 commit d5d01e4
Show file tree
Hide file tree
Showing 3 changed files with 299 additions and 0 deletions.
30 changes: 30 additions & 0 deletions orderer/common/cauthdsl/cauthdsl_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,38 @@ package cauthdsl

import (
ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"

"github.com/golang/protobuf/proto"
)

// AcceptAllPolicy always evaluates to true
var AcceptAllPolicy *ab.SignaturePolicyEnvelope

// MarshaledAcceptAllPolicy is the Marshaled version of AcceptAllPolicy
var MarshaledAcceptAllPolicy []byte

// RejectAllPolicy always evaluates to false
var RejectAllPolicy *ab.SignaturePolicyEnvelope

// MarshaledRejectAllPolicy is the Marshaled version of RejectAllPolicy
var MarshaledRejectAllPolicy []byte

func init() {
var err error

AcceptAllPolicy = Envelope(NOutOf(0, []*ab.SignaturePolicy{}), [][]byte{})
MarshaledAcceptAllPolicy, err = proto.Marshal(AcceptAllPolicy)
if err != nil {
panic("Error marshaling trueEnvelope")
}

RejectAllPolicy = Envelope(NOutOf(1, []*ab.SignaturePolicy{}), [][]byte{})
MarshaledRejectAllPolicy, err = proto.Marshal(RejectAllPolicy)
if err != nil {
panic("Error marshaling falseEnvelope")
}
}

// Envelope builds an envelope message embedding a SignaturePolicy
func Envelope(policy *ab.SignaturePolicy, identities [][]byte) *ab.SignaturePolicyEnvelope {
return &ab.SignaturePolicyEnvelope{
Expand Down
158 changes: 158 additions & 0 deletions orderer/common/policies/policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
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 policies

import (
"fmt"

ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
"github.com/hyperledger/fabric/orderer/common/cauthdsl"

"github.com/golang/protobuf/proto"
)

// Policy is used to determine if a signature is valid
type Policy interface {
// Evaluate returns nil if a msg is properly signed by sigs, or an error indicating why it failed
Evaluate(msg []byte, sigs []*ab.SignedData) error
}

// Manager is intended to be the primary accessor of ManagerImpl
// It is intended to discourage use of the other exported ManagerImpl methods
// which are used for updating policy by the ConfigManager
type Manager interface {
// GetPolicy returns a policy and true if it was the policy requested, or false if it is the default policy
GetPolicy(id string) (Policy, bool)
}

type policy struct {
source *ab.Policy
evaluator *cauthdsl.SignaturePolicyEvaluator
}

func newPolicy(policySource *ab.Policy, ch cauthdsl.CryptoHelper) (*policy, error) {
envelopeWrapper, ok := policySource.Type.(*ab.Policy_SignaturePolicy)

if !ok {
return nil, fmt.Errorf("Unknown policy type: %T", policySource.Type)
}

if envelopeWrapper.SignaturePolicy == nil {
return nil, fmt.Errorf("Nil signature policy received")
}

sigPolicy := envelopeWrapper.SignaturePolicy

evaluator, err := cauthdsl.NewSignaturePolicyEvaluator(sigPolicy, ch)
if err != nil {
return nil, err
}

return &policy{
evaluator: evaluator,
source: policySource,
}, nil
}

// Evaluate returns nil if a msg is properly signed by sigs, or an error indicating why it failed
func (p *policy) Evaluate(msg []byte, sigs []*ab.SignedData) error {
if p == nil {
return fmt.Errorf("Evaluated default policy, results in reject")
}

identities := make([][]byte, len(sigs))
signatures := make([][]byte, len(sigs))
for i, sigpair := range sigs {
envelope := &ab.PayloadEnvelope{}
if err := proto.Unmarshal(sigpair.PayloadEnvelope, envelope); err != nil {
return fmt.Errorf("Failed to unmarshal the payload envelope to extract the signatures")
}
identities[i] = envelope.Signer
signatures[i] = sigpair.Signature
}
// XXX This is wrong, as the signatures are over the payload envelope, not the message, fix either here, or in cauthdsl once transaction is finalized
if !p.evaluator.Authenticate(msg, identities, signatures) {
return fmt.Errorf("Failed to authenticate policy")
}
return nil
}

// ManagerImpl is an implementation of Manager and configtx.ConfigHandler
// In general, it should only be referenced as an Impl for the configtx.ConfigManager
type ManagerImpl struct {
policies map[string]*policy
pendingPolicies map[string]*policy
ch cauthdsl.CryptoHelper
}

// NewManagerImpl creates a new ManagerImpl with the given CryptoHelper
func NewManagerImpl(ch cauthdsl.CryptoHelper) *ManagerImpl {
return &ManagerImpl{
ch: ch,
policies: make(map[string]*policy),
}
}

// GetPolicy returns a policy and true if it was the policy requested, or false if it is the default policy
func (pm *ManagerImpl) GetPolicy(id string) (Policy, bool) {
policy, ok := pm.policies[id]
// Note the nil policy evaluates fine
return policy, ok
}

// BeginConfig is used to start a new configuration proposal
func (pm *ManagerImpl) BeginConfig() {
if pm.pendingPolicies != nil {
panic("Programming error, cannot call begin in the middle of a proposal")
}
pm.pendingPolicies = make(map[string]*policy)
}

// RollbackConfig is used to abandon a new configuration proposal
func (pm *ManagerImpl) RollbackConfig() {
pm.pendingPolicies = nil
}

// CommitConfig is used to commit a new configuration proposal
func (pm *ManagerImpl) CommitConfig() {
if pm.pendingPolicies == nil {
panic("Programming error, cannot call commit without an existing proposal")
}
pm.policies = pm.pendingPolicies
pm.pendingPolicies = nil
}

// ProposeConfig is used to add new configuration to the configuration proposal
func (pm *ManagerImpl) ProposeConfig(configItem *ab.Configuration) error {
if configItem.Type != ab.Configuration_Policy {
return fmt.Errorf("Expected type of Configuration_Policy, got %v", configItem.Type)
}

policy := &ab.Policy{}
err := proto.Unmarshal(configItem.Data, policy)
if err != nil {
return err
}

cPolicy, err := newPolicy(policy, pm.ch)
if err != nil {
return err
}

pm.pendingPolicies[configItem.ID] = cPolicy
return nil
}
111 changes: 111 additions & 0 deletions orderer/common/policies/policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
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 policies

import (
"testing"

ab "github.com/hyperledger/fabric/orderer/atomicbroadcast"
"github.com/hyperledger/fabric/orderer/common/cauthdsl"

"github.com/golang/protobuf/proto"
)

type mockCryptoHelper struct{}

func (mch *mockCryptoHelper) VerifySignature(msg []byte, identity []byte, signature []byte) bool {
return true
}

var acceptAllPolicy []byte
var rejectAllPolicy []byte

func init() {
acceptAllPolicy = makePolicySource(true)
rejectAllPolicy = makePolicySource(false)
}

func makePolicySource(policyResult bool) []byte {
var policyData *ab.SignaturePolicyEnvelope
if policyResult {
policyData = cauthdsl.AcceptAllPolicy
} else {
policyData = cauthdsl.RejectAllPolicy
}
marshaledPolicy, err := proto.Marshal(&ab.Policy{
Type: &ab.Policy_SignaturePolicy{
SignaturePolicy: policyData,
},
})
if err != nil {
panic("Error marshaling policy")
}
return marshaledPolicy
}

func addPolicy(manager *ManagerImpl, id string, policy []byte) {
manager.BeginConfig()
err := manager.ProposeConfig(&ab.Configuration{
ID: id,
Type: ab.Configuration_Policy,
Data: policy,
})
if err != nil {
panic(err)
}
manager.CommitConfig()
}

func TestAccept(t *testing.T) {
policyID := "policyID"
m := NewManagerImpl(&mockCryptoHelper{})
addPolicy(m, policyID, acceptAllPolicy)
policy, ok := m.GetPolicy(policyID)
if !ok {
t.Errorf("Should have found policy which was just added, but did not")
}
err := policy.Evaluate(nil, nil)
if err != nil {
t.Fatalf("Should not have errored evaluating an acceptAll policy: %s", err)
}
}

func TestReject(t *testing.T) {
policyID := "policyID"
m := NewManagerImpl(&mockCryptoHelper{})
addPolicy(m, policyID, rejectAllPolicy)
policy, ok := m.GetPolicy(policyID)
if !ok {
t.Errorf("Should have found policy which was just added, but did not")
}
err := policy.Evaluate(nil, nil)
if err == nil {
t.Fatalf("Should have errored evaluating the rejectAll policy")
}
}

func TestRejectOnUnknown(t *testing.T) {
m := NewManagerImpl(&mockCryptoHelper{})
policy, ok := m.GetPolicy("FakePolicyID")
if ok {
t.Errorf("Should not have found policy which was never added, but did")
}
err := policy.Evaluate(nil, nil)
if err == nil {
t.Fatalf("Should have errored evaluating the default policy")
}
}

0 comments on commit d5d01e4

Please sign in to comment.