From fe40f7365695e29c4b55fe304bba63cc98053f30 Mon Sep 17 00:00:00 2001 From: Austin Gebauer Date: Mon, 10 Jul 2023 16:32:42 -0700 Subject: [PATCH 1/3] Adds replication state helper to framework.Backend --- builtin/logical/aws/backend.go | 9 ++------- builtin/logical/database/backend.go | 2 +- builtin/logical/database/rotation.go | 7 ++----- sdk/framework/backend.go | 20 ++++++++++++++++++++ 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/builtin/logical/aws/backend.go b/builtin/logical/aws/backend.go index d93c394f98fd..7718029198af 100644 --- a/builtin/logical/aws/backend.go +++ b/builtin/logical/aws/backend.go @@ -12,7 +12,6 @@ import ( "github.com/aws/aws-sdk-go/service/iam/iamiface" "github.com/aws/aws-sdk-go/service/sts/stsiface" "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/queue" ) @@ -32,7 +31,7 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, return b, nil } -func Backend(conf *logical.BackendConfig) *backend { +func Backend(_ *logical.BackendConfig) *backend { var b backend b.credRotationQueue = queue.New() b.Backend = &framework.Backend{ @@ -67,11 +66,7 @@ func Backend(conf *logical.BackendConfig) *backend { WALRollback: b.walRollback, WALRollbackMinAge: minAwsUserRollbackAge, PeriodicFunc: func(ctx context.Context, req *logical.Request) error { - repState := conf.System.ReplicationState() - if (conf.System.LocalMount() || - !repState.HasState(consts.ReplicationPerformanceSecondary)) && - !repState.HasState(consts.ReplicationDRSecondary) && - !repState.HasState(consts.ReplicationPerformanceStandby) { + if b.WriteSafeReplicationState() { return b.rotateExpiredStaticCreds(ctx, req) } return nil diff --git a/builtin/logical/database/backend.go b/builtin/logical/database/backend.go index f4e5ef31bdaf..9b9cd763d6b5 100644 --- a/builtin/logical/database/backend.go +++ b/builtin/logical/database/backend.go @@ -68,7 +68,7 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, b.credRotationQueue = queue.New() // Load queue and kickoff new periodic ticker - go b.initQueue(b.queueCtx, conf, conf.System.ReplicationState()) + go b.initQueue(b.queueCtx, conf) // collect metrics on number of plugin instances var err error diff --git a/builtin/logical/database/rotation.go b/builtin/logical/database/rotation.go index 1ef54aecac32..0d1067e8686e 100644 --- a/builtin/logical/database/rotation.go +++ b/builtin/logical/database/rotation.go @@ -13,7 +13,6 @@ import ( "github.com/hashicorp/go-secure-stdlib/strutil" v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5" "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/locksutil" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/queue" @@ -517,14 +516,12 @@ func (b *databaseBackend) setStaticAccount(ctx context.Context, s logical.Storag // not wait for success or failure of it's tasks before continuing. This is to // avoid blocking the mount process while loading and evaluating existing roles, // etc. -func (b *databaseBackend) initQueue(ctx context.Context, conf *logical.BackendConfig, replicationState consts.ReplicationState) { +func (b *databaseBackend) initQueue(ctx context.Context, conf *logical.BackendConfig) { // Verify this mount is on the primary server, or is a local mount. If not, do // not create a queue or launch a ticker. Both processing the WAL list and // populating the queue are done sequentially and before launching a // go-routine to run the periodic ticker. - if (conf.System.LocalMount() || !replicationState.HasState(consts.ReplicationPerformanceSecondary)) && - !replicationState.HasState(consts.ReplicationDRSecondary) && - !replicationState.HasState(consts.ReplicationPerformanceStandby) { + if b.WriteSafeReplicationState() { b.Logger().Info("initializing database rotation queue") // Poll for a PutWAL call that does not return a "read-only storage" error. diff --git a/sdk/framework/backend.go b/sdk/framework/backend.go index c0527addf999..d1337c659ab0 100644 --- a/sdk/framework/backend.go +++ b/sdk/framework/backend.go @@ -60,6 +60,11 @@ type Backend struct { // InitializeFunc is the callback, which if set, will be invoked via // Initialize() just after a plugin has been mounted. + // + // Note that storage writes should only occur on the active instance within a + // primary cluster or local mount on a performance secondary. If your InitializeFunc + // writes to storage, you can use the backend's WriteSafeReplicationState() method + // to prevent it from attempting to write on a Vault instance with read-only storage. InitializeFunc InitializeFunc // PeriodicFunc is the callback, which if set, will be invoked when the @@ -70,6 +75,11 @@ type Backend struct { // entries in backend's storage, while the backend is still being used. // (Note the difference between this action and `Clean`, which is // invoked just before the backend is unmounted). + // + // Note that storage writes should only occur on the active instance within a + // primary cluster or local mount on a performance secondary. If your PeriodicFunc + // writes to storage, you can use the backend's WriteSafeReplicationState() method + // to prevent it from attempting to write on a Vault instance with read-only storage. PeriodicFunc periodicFunc // WALRollback is called when a WAL entry (see wal.go) has to be rolled @@ -466,6 +476,16 @@ func (b *Backend) Secret(k string) *Secret { return nil } +// WriteSafeReplicationState returns true if this backend instance is capable of writing +// to storage without receiving an ErrReadOnly error. The active instance in a primary +// cluster or a local mount on a performance secondary is capable of writing to storage. +func (b *Backend) WriteSafeReplicationState() bool { + replicationState := b.System().ReplicationState() + return (b.System().LocalMount() || !replicationState.HasState(consts.ReplicationPerformanceSecondary)) && + !replicationState.HasState(consts.ReplicationDRSecondary) && + !replicationState.HasState(consts.ReplicationPerformanceStandby) +} + func (b *Backend) init() { b.pathsRe = make([]*regexp.Regexp, len(b.Paths)) for i, p := range b.Paths { From 725db907ffc3465a5402992e832135a9c3c27bdf Mon Sep 17 00:00:00 2001 From: Austin Gebauer Date: Mon, 10 Jul 2023 17:16:30 -0700 Subject: [PATCH 2/3] Fix test --- builtin/logical/database/rotation_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/builtin/logical/database/rotation_test.go b/builtin/logical/database/rotation_test.go index 5dfa09659305..b36ad011652c 100644 --- a/builtin/logical/database/rotation_test.go +++ b/builtin/logical/database/rotation_test.go @@ -20,7 +20,6 @@ import ( postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql" v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5" "github.com/hashicorp/vault/sdk/framework" - "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/dbtxn" "github.com/hashicorp/vault/sdk/helper/pluginutil" "github.com/hashicorp/vault/sdk/logical" @@ -1224,7 +1223,7 @@ func TestStoredWALsCorrectlyProcessed(t *testing.T) { b.credRotationQueue = queue.New() // Now finish the startup process by populating the queue, which should discard the WAL - b.initQueue(ctx, config, consts.ReplicationUnknown) + b.initQueue(ctx, config) if tc.shouldRotate { requireWALs(t, storage, 1) From 41a2c8d31f3eddb778940f382c06cbfd6c2df5f5 Mon Sep 17 00:00:00 2001 From: Austin Gebauer Date: Tue, 11 Jul 2023 14:21:41 -0700 Subject: [PATCH 3/3] adds changelog --- changelog/21743.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/21743.txt diff --git a/changelog/21743.txt b/changelog/21743.txt new file mode 100644 index 000000000000..1bb8279543ba --- /dev/null +++ b/changelog/21743.txt @@ -0,0 +1,3 @@ +```release-note:improvement +sdk/framework: Adds replication state helper for backends to check for read-only storage +```