From fb4edc129e599a57be8bfff8c9a3e5d1c7a93a2f Mon Sep 17 00:00:00 2001 From: Jim Kalafut Date: Tue, 7 Jan 2020 14:04:08 -0800 Subject: [PATCH] Add path attributes to indicate when operations should forward (#7175) --- sdk/framework/backend.go | 16 +++++- sdk/framework/backend_test.go | 94 +++++++++++++++++++++++++++++++++++ sdk/framework/path.go | 38 +++++++++----- 3 files changed, 134 insertions(+), 14 deletions(-) diff --git a/sdk/framework/backend.go b/sdk/framework/backend.go index a4e9f2de3..bfcbe4e9d 100644 --- a/sdk/framework/backend.go +++ b/sdk/framework/backend.go @@ -15,7 +15,8 @@ import ( "github.com/hashicorp/errwrap" log "github.com/hashicorp/go-hclog" - multierror "github.com/hashicorp/go-multierror" + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/entropy" "github.com/hashicorp/vault/sdk/helper/errutil" "github.com/hashicorp/vault/sdk/helper/license" @@ -225,6 +226,19 @@ func (b *Backend) HandleRequest(ctx context.Context, req *logical.Request) (*log if path.Operations != nil { if op, ok := path.Operations[req.Operation]; ok { + + // Check whether this operation should be forwarded + replState := b.System().ReplicationState() + props := op.Properties() + + if props.ForwardPerformanceStandby && replState.HasState(consts.ReplicationPerformanceStandby) { + return nil, logical.ErrReadOnly + } + + if props.ForwardPerformanceSecondary && !b.System().LocalMount() && replState.HasState(consts.ReplicationPerformanceSecondary) { + return nil, logical.ErrReadOnly + } + callback = op.Handler() } } else { diff --git a/sdk/framework/backend_test.go b/sdk/framework/backend_test.go index 567557aef..9d194c59b 100644 --- a/sdk/framework/backend_test.go +++ b/sdk/framework/backend_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/logical" ) @@ -93,6 +94,7 @@ func TestBackendHandleRequest(t *testing.T) { }, }, }, + system: &logical.StaticSystemView{}, } for _, path := range []string{"foo/bar", "foo/baz/handler", "foo/both/handler"} { @@ -114,6 +116,98 @@ func TestBackendHandleRequest(t *testing.T) { } } +func TestBackendHandleRequest_Forwarding(t *testing.T) { + tests := map[string]struct { + fwdStandby bool + fwdSecondary bool + isLocal bool + isStandby bool + isSecondary bool + expectFwd bool + }{ + "no forward": { + expectFwd: false, + }, + "no forward, local restricted": { + isSecondary: true, + fwdSecondary: true, + isLocal: true, + expectFwd: false, + }, + "no forward, forwarding not requested": { + isSecondary: true, + isStandby: true, + expectFwd: false, + }, + "forward, secondary": { + fwdSecondary: true, + isSecondary: true, + expectFwd: true, + }, + "forward, standby": { + fwdStandby: true, + isStandby: true, + expectFwd: true, + }, + "no forward, only secondary": { + fwdSecondary: true, + isStandby: true, + expectFwd: false, + }, + "no forward, only standby": { + fwdStandby: true, + isSecondary: true, + expectFwd: false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + var replState consts.ReplicationState + if test.isStandby { + replState.AddState(consts.ReplicationPerformanceStandby) + } + if test.isSecondary { + replState.AddState(consts.ReplicationPerformanceSecondary) + } + + b := &Backend{ + Paths: []*Path{ + { + Pattern: "foo", + Operations: map[logical.Operation]OperationHandler{ + logical.ReadOperation: &PathOperation{ + Callback: func(ctx context.Context, req *logical.Request, data *FieldData) (*logical.Response, error) { + return nil, nil + }, + ForwardPerformanceSecondary: test.fwdSecondary, + ForwardPerformanceStandby: test.fwdStandby, + }, + }, + }, + }, + + system: &logical.StaticSystemView{ + LocalMountVal: test.isLocal, + ReplicationStateVal: replState, + }, + } + + _, err := b.HandleRequest(context.Background(), &logical.Request{ + Operation: logical.ReadOperation, + Path: "foo", + }) + + if !test.expectFwd && err != nil { + t.Fatalf("unexpected err: %v", err) + } + if test.expectFwd && err != logical.ErrReadOnly { + t.Fatalf("expected ErrReadOnly, got: %v", err) + } + }) + } +} + func TestBackendHandleRequest_badwrite(t *testing.T) { callback := func(ctx context.Context, req *logical.Request, data *FieldData) (*logical.Response, error) { return &logical.Response{ diff --git a/sdk/framework/path.go b/sdk/framework/path.go index d6fc6e935..8f5dd5bee 100644 --- a/sdk/framework/path.go +++ b/sdk/framework/path.go @@ -153,6 +153,14 @@ type OperationProperties struct { // Deprecated indicates that this operation should be avoided. Deprecated bool + // ForwardPerformanceStandby indicates that this path should not be processed + // on a performance standby node, and should be forwarded to the active node instead. + ForwardPerformanceStandby bool + + // ForwardPerformanceSecondary indicates that this path should not be processed + // on a performance secondary node, and should be forwarded to the active node instead. + ForwardPerformanceSecondary bool + // DisplayAttrs provides hints for UI and documentation generators. They // will be included in OpenAPI output if set. DisplayAttrs *DisplayAttributes @@ -206,13 +214,15 @@ type Response struct { // PathOperation is a concrete implementation of OperationHandler. type PathOperation struct { - Callback OperationFunc - Summary string - Description string - Examples []RequestExample - Responses map[int][]Response - Unpublished bool - Deprecated bool + Callback OperationFunc + Summary string + Description string + Examples []RequestExample + Responses map[int][]Response + Unpublished bool + Deprecated bool + ForwardPerformanceSecondary bool + ForwardPerformanceStandby bool } func (p *PathOperation) Handler() OperationFunc { @@ -221,12 +231,14 @@ func (p *PathOperation) Handler() OperationFunc { func (p *PathOperation) Properties() OperationProperties { return OperationProperties{ - Summary: strings.TrimSpace(p.Summary), - Description: strings.TrimSpace(p.Description), - Responses: p.Responses, - Examples: p.Examples, - Unpublished: p.Unpublished, - Deprecated: p.Deprecated, + Summary: strings.TrimSpace(p.Summary), + Description: strings.TrimSpace(p.Description), + Responses: p.Responses, + Examples: p.Examples, + Unpublished: p.Unpublished, + Deprecated: p.Deprecated, + ForwardPerformanceSecondary: p.ForwardPerformanceSecondary, + ForwardPerformanceStandby: p.ForwardPerformanceStandby, } }