From 83534ec583118256c04e4e770fd6e44f9f4a9633 Mon Sep 17 00:00:00 2001 From: kpcraig <3031348+kpcraig@users.noreply.github.com> Date: Tue, 6 May 2025 13:36:15 -0400 Subject: [PATCH] Add RotationManager stubs to the mock plugin (#30532) --- sdk/plugin/mock/backend.go | 14 +++- sdk/plugin/mock/path_config.go | 131 +++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 sdk/plugin/mock/path_config.go diff --git a/sdk/plugin/mock/backend.go b/sdk/plugin/mock/backend.go index b34191b938..26dd6052e6 100644 --- a/sdk/plugin/mock/backend.go +++ b/sdk/plugin/mock/backend.go @@ -56,6 +56,7 @@ func Backend() *backend { errorPaths(&b), kvPaths(&b), []*framework.Path{ + pathConfig(&b), pathInternal(&b), pathSpecial(&b), pathRaw(&b), @@ -67,9 +68,10 @@ func Backend() *backend { "special", }, }, - Secrets: []*framework.Secret{}, - Invalidate: b.invalidate, - BackendType: logical.TypeLogical, + Secrets: []*framework.Secret{}, + Invalidate: b.invalidate, + BackendType: logical.TypeLogical, + RotateCredential: b.rotateRootCredential, } b.internal = MockPluginDefaultInternalValue b.RunningVersion = "v0.0.0+mock" @@ -128,3 +130,9 @@ func expectInternalValue(t *testing.T, client *api.Client, mountPath, expected s t.Fatalf("expected %q but got %q", expected, resp.Data["value"].(string)) } } + +func (b *backend) rotateRootCredential(ctx context.Context, req *logical.Request) error { + b.Logger().Debug("mock rotateRootCredential") + b.internal = "rotated" + return nil +} diff --git a/sdk/plugin/mock/path_config.go b/sdk/plugin/mock/path_config.go new file mode 100644 index 0000000000..714638d055 --- /dev/null +++ b/sdk/plugin/mock/path_config.go @@ -0,0 +1,131 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mock + +import ( + "context" + + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/helper/automatedrotationutil" + "github.com/hashicorp/vault/sdk/logical" + "github.com/hashicorp/vault/sdk/rotation" +) + +// pathConfig is used to test auto rotation. +func pathConfig(b *backend) *framework.Path { + p := &framework.Path{ + Pattern: "config", + Fields: map[string]*framework.FieldSchema{}, + Callbacks: map[logical.Operation]framework.OperationFunc{ + logical.CreateOperation: b.pathConfigUpdate, + logical.UpdateOperation: b.pathConfigUpdate, + logical.ReadOperation: b.pathConfigRead, + }, + ExistenceCheck: b.pathConfigExistenceCheck, + } + automatedrotationutil.AddAutomatedRotationFields(p.Fields) + return p +} + +func (b *backend) pathConfigUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + conf, err := b.configEntry(ctx, req.Storage) + if err != nil { + return nil, err + } + if conf == nil { + conf = &config{} + } + + if err := conf.ParseAutomatedRotationFields(data); err != nil { + return logical.ErrorResponse(err.Error()), nil + } + + if conf.ShouldDeregisterRotationJob() { + deregisterReq := &rotation.RotationJobDeregisterRequest{ + MountPoint: req.MountPoint, + ReqPath: req.Path, + } + + b.Logger().Debug("Deregistering rotation job", "mount", req.MountPoint+req.Path) + if err := b.System().DeregisterRotationJob(ctx, deregisterReq); err != nil { + return logical.ErrorResponse("error deregistering rotation job: %s", err), nil + } + } else if conf.ShouldRegisterRotationJob() { + cfgReq := &rotation.RotationJobConfigureRequest{ + MountPoint: req.MountPoint, + ReqPath: req.Path, + RotationSchedule: conf.RotationSchedule, + RotationWindow: conf.RotationWindow, + RotationPeriod: conf.RotationPeriod, + } + + b.Logger().Debug("Registering rotation job", "mount", req.MountPoint+req.Path) + if _, err = b.System().RegisterRotationJob(ctx, cfgReq); err != nil { + return logical.ErrorResponse("error registering rotation job: %s", err), nil + } + } + + entry, err := logical.StorageEntryJSON("config", conf) + if err != nil { + return nil, err + } + if err := req.Storage.Put(ctx, entry); err != nil { + return nil, err + } + return nil, nil +} + +func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + conf, err := b.configEntry(ctx, req.Storage) + if err != nil { + return nil, err + } + + if conf == nil { + return nil, nil + } + + configData := map[string]interface{}{} + conf.PopulateAutomatedRotationData(configData) + + return &logical.Response{ + Data: configData, + }, nil +} + +func (b *backend) pathConfigDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + if err := req.Storage.Delete(ctx, "config"); err != nil { + return nil, err + } + return nil, nil +} + +func (b *backend) pathConfigExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) { + entry, err := b.configEntry(ctx, req.Storage) + if err != nil { + return false, err + } + return entry != nil, nil +} + +// Fetch the client configuration required to access the AWS API. +func (b *backend) configEntry(ctx context.Context, s logical.Storage) (*config, error) { + entry, err := s.Get(ctx, "config") + if err != nil { + return nil, err + } + if entry == nil { + return nil, nil + } + + var result config + if err := entry.DecodeJSON(&result); err != nil { + return nil, err + } + return &result, nil +} + +type config struct { + automatedrotationutil.AutomatedRotationParams +}