Skip to content

Commit

Permalink
Update Rotation Manager OSS Stubs and SDK methods (#29401)
Browse files Browse the repository at this point in the history
  • Loading branch information
vinay-gopalan authored Jan 28, 2025
1 parent 4fcc547 commit 838a384
Show file tree
Hide file tree
Showing 12 changed files with 999 additions and 770 deletions.
24 changes: 9 additions & 15 deletions builtin/logical/aws/path_config_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,38 +217,32 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request,
if rc.ShouldRegisterRotationJob() {
cfgReq := &rotation.RotationJobConfigureRequest{
Name: rootRotationJobName,
MountPoint: req.MountPoint,
MountType: req.MountType,
ReqPath: req.Path,
RotationSchedule: rc.RotationSchedule,
RotationWindow: rc.RotationWindow,
RotationPeriod: rc.RotationPeriod,
}

rotationJob, err := rotation.ConfigureRotationJob(cfgReq)
if err != nil {
return logical.ErrorResponse("error configuring rotation job: %s", err), nil
}

b.Logger().Debug("Registering rotation job", "mount", req.MountPoint+req.Path)
rotationID, err := b.System().RegisterRotationJob(ctx, rotationJob)
_, err = b.System().RegisterRotationJob(ctx, cfgReq)
if err != nil {
return logical.ErrorResponse("error registering rotation job: %s", err), nil
}

rc.RotationID = rotationID
}

// Disable Automated Rotation and Deregister credentials if required
if rc.DisableAutomatedRotation {
// Ensure de-registering only occurs on updates and if
// a credential has actually been registered
if previousCfgExists && previousCfg.RotationID != "" {
err := b.System().DeregisterRotationJob(ctx, previousCfg.RotationID)
// a credential has actually been registered (rotation_period or rotation_schedule is set)
deregisterReq := &rotation.RotationJobDeregisterRequest{
MountType: req.MountType,
ReqPath: req.Path,
}
if previousCfgExists && previousCfg.ShouldRegisterRotationJob() {
err := b.System().DeregisterRotationJob(ctx, deregisterReq)
if err != nil {
return logical.ErrorResponse("error de-registering rotation job: %s", err), nil
}

rc.RotationID = ""
}
}

Expand Down
5 changes: 1 addition & 4 deletions builtin/logical/aws/path_config_root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ func TestBackend_PathConfigRoot(t *testing.T) {
"identity_token_ttl": int64(0),
"rotation_schedule": "",
"rotation_window": 0,
"rotation_id": "",
"disable_automated_rotation": false,
}

Expand Down Expand Up @@ -104,7 +103,6 @@ func TestBackend_PathConfigRoot_STSFallback(t *testing.T) {
"identity_token_ttl": int64(0),
"rotation_schedule": "",
"rotation_window": 0,
"rotation_id": "",
"disable_automated_rotation": false,
}

Expand Down Expand Up @@ -154,7 +152,6 @@ func TestBackend_PathConfigRoot_STSFallback(t *testing.T) {
"identity_token_ttl": int64(0),
"rotation_schedule": "",
"rotation_window": 0,
"rotation_id": "",
"disable_automated_rotation": false,
}

Expand Down Expand Up @@ -308,6 +305,6 @@ func (d testSystemView) GenerateIdentityToken(_ context.Context, _ *pluginutil.I
return nil, pluginidentityutil.ErrPluginWorkloadIdentityUnsupported
}

func (d testSystemView) RegisterRotationJob(_ context.Context, _ *rotation.RotationJob) (string, error) {
func (d testSystemView) RegisterRotationJob(_ context.Context, _ *rotation.RotationJobConfigureRequest) (string, error) {
return "", automatedrotationutil.ErrRotationManagerUnsupported
}
9 changes: 0 additions & 9 deletions sdk/helper/automatedrotationutil/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ type AutomatedRotationParams struct {
// RotationPeriod is an alternate choice for simple time-to-live based rotation timing.
RotationPeriod int `json:"rotation_period"`

// RotationID is the unique ID of the registered rotation job.
// Used by the plugin to track the rotation.
RotationID string `json:"rotation_id"`

// If set, will deregister all registered rotation jobs from the RotationManager for plugin.
DisableAutomatedRotation bool `json:"disable_automated_rotation"`
}
Expand Down Expand Up @@ -72,7 +68,6 @@ func (p *AutomatedRotationParams) PopulateAutomatedRotationData(m map[string]int
m["rotation_schedule"] = p.RotationSchedule
m["rotation_window"] = p.RotationWindow
m["rotation_period"] = p.RotationPeriod
m["rotation_id"] = p.RotationID
m["disable_automated_rotation"] = p.DisableAutomatedRotation
}

Expand All @@ -96,10 +91,6 @@ func AddAutomatedRotationFields(m map[string]*framework.FieldSchema) {
Type: framework.TypeInt,
Description: "TTL for automatic credential rotation of the given username. Mutually exclusive with rotation_schedule",
},
"rotation_id": {
Type: framework.TypeInt,
Description: "Unique ID of the registered rotation job",
},
"disable_automated_rotation": {
Type: framework.TypeBool,
Description: "If set to true, will deregister all registered rotation jobs from the RotationManager for the plugin.",
Expand Down
131 changes: 131 additions & 0 deletions sdk/helper/automatedrotationutil/fields_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package automatedrotationutil

import (
"reflect"
"strings"
"testing"

"github.com/hashicorp/vault/sdk/framework"
)

var schemaMap = map[string]*framework.FieldSchema{
"rotation_schedule": {
Type: framework.TypeString,
Description: "CRON-style string that will define the schedule on which rotations should occur. Mutually exclusive with rotation_period",
},
"rotation_window": {
Type: framework.TypeInt,
Description: "Specifies the amount of time in which the rotation is allowed to occur starting from a given rotation_schedule",
},
"rotation_period": {
Type: framework.TypeInt,
Description: "TTL for automatic credential rotation of the given username. Mutually exclusive with rotation_schedule",
},
"disable_automated_rotation": {
Type: framework.TypeBool,
Description: "If set to true, will deregister all registered rotation jobs from the RotationManager for the plugin.",
},
}

func TestParseAutomatedRotationFields(t *testing.T) {
tests := []struct {
name string
data *framework.FieldData
expectedParams *AutomatedRotationParams
expectedError string
}{
{
name: "basic",
data: &framework.FieldData{
Raw: map[string]interface{}{
"rotation_schedule": "*/15 * * * * *",
"rotation_window": 60,
},
Schema: schemaMap,
},
expectedParams: &AutomatedRotationParams{
RotationSchedule: "*/15 * * * * *",
RotationWindow: 60,
RotationPeriod: 0,
DisableAutomatedRotation: false,
},
},
{
name: "mutually-exclusive",
data: &framework.FieldData{
Raw: map[string]interface{}{
"rotation_schedule": "*/15 * * * * *",
"rotation_period": 15,
"rotation_window": 60,
},
Schema: schemaMap,
},
expectedError: ErrRotationMutuallyExclusiveFields.Error(),
},
{
name: "incompatible-fields",
data: &framework.FieldData{
Raw: map[string]interface{}{
"rotation_period": 15,
"rotation_window": 60,
},
Schema: schemaMap,
},
expectedError: "rotation_window does not apply to period",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &AutomatedRotationParams{}
err := p.ParseAutomatedRotationFields(tt.data)

if err != nil && !strings.Contains(err.Error(), tt.expectedError) {
t.Errorf("ParseAutomatedRotationFields() error = %v, expected %s", err, tt.expectedError)
}

if err == nil && !reflect.DeepEqual(tt.expectedParams, p) {
t.Errorf("ParseAutomatedRotationFields() error comparing params; got %v, expected %v", tt.expectedParams, p)
}
})
}
}

func TestPopulateAutomatedRotationData(t *testing.T) {
tests := []struct {
name string
inputParams *AutomatedRotationParams
expected map[string]interface{}
}{
{
name: "basic",
expected: map[string]interface{}{
"rotation_schedule": "*/15 * * * * *",
"rotation_window": 60,
"rotation_period": 0,
"disable_automated_rotation": false,
},
inputParams: &AutomatedRotationParams{
RotationSchedule: "*/15 * * * * *",
RotationWindow: 60,
RotationPeriod: 0,
DisableAutomatedRotation: false,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := make(map[string]interface{})

tt.inputParams.PopulateAutomatedRotationData(m)

if !reflect.DeepEqual(m, tt.expected) {
t.Errorf("PopulateAutomatedRotationData() error comparing values; got %v, expected %v", m, tt.expected)
}
})
}
}
6 changes: 3 additions & 3 deletions sdk/logical/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,9 @@ type Request struct {
// X-Vault-MFA header
MFACreds MFACreds `json:"mfa_creds" structs:"mfa_creds" mapstructure:"mfa_creds" sentinel:""`

// RotationEntryName is internally used by
// the RotationManager to rotate root credentials
RotationEntryName string
// RotationID is set by the Rotation Manager
// when making rotate requests to plugin backends
RotationID string

// Cached token entry. This avoids another lookup in request handling when
// we've already looked it up at http handling time. Note that this token
Expand Down
8 changes: 4 additions & 4 deletions sdk/logical/system_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,12 @@ type SystemView interface {
// RegisterRotationJob returns a rotation ID after registering a
// rotation job for the requesting plugin.
// NOTE: This method is intended for use only by HashiCorp Vault Enterprise plugins.
RegisterRotationJob(ctx context.Context, job *rotation.RotationJob) (rotationID string, err error)
RegisterRotationJob(ctx context.Context, req *rotation.RotationJobConfigureRequest) (rotationID string, err error)

// DeregisterRotationJob returns any errors in de-registering a
// credential from the Rotation Manager.
// NOTE: This method is intended for use only by HashiCorp Vault Enterprise plugins.
DeregisterRotationJob(ctx context.Context, rotationID string) error
DeregisterRotationJob(ctx context.Context, req *rotation.RotationJobDeregisterRequest) error
}

type PasswordPolicy interface {
Expand Down Expand Up @@ -297,10 +297,10 @@ func (d StaticSystemView) APILockShouldBlockRequest() (bool, error) {
return d.APILockShouldBlockRequestVal, nil
}

func (d StaticSystemView) RegisterRotationJob(_ context.Context, _ *rotation.RotationJob) (rotationID string, err error) {
func (d StaticSystemView) RegisterRotationJob(_ context.Context, _ *rotation.RotationJobConfigureRequest) (rotationID string, err error) {
return "", errors.New("RegisterRotationJob is not implemented in StaticSystemView")
}

func (d StaticSystemView) DeregisterRotationJob(_ context.Context, _ string) (err error) {
func (d StaticSystemView) DeregisterRotationJob(_ context.Context, _ *rotation.RotationJobDeregisterRequest) (err error) {
return errors.New("DeregisterRotationJob is not implemented in StaticSystemView")
}
78 changes: 58 additions & 20 deletions sdk/plugin/grpc_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
)

var errMissingSystemView = errors.New("missing system view implementation: this method should not be called during plugin Setup, but only during and after Initialize")
Expand Down Expand Up @@ -228,35 +227,30 @@ func (s *gRPCSystemViewClient) GenerateIdentityToken(ctx context.Context, req *p
}, nil
}

func (s *gRPCSystemViewClient) RegisterRotationJob(ctx context.Context, job *rotation.RotationJob) (id string, retErr error) {
scheduleData := map[string]interface{}{
"schedule": job.Schedule.Schedule,
"rotation_window": job.Schedule.RotationWindow,
"rotation_schedule": job.Schedule.RotationSchedule,
"next_vault_rotation": job.Schedule.NextVaultRotation,
}
m, err := structpb.NewValue(scheduleData)
if err != nil {
return "", err
}
req := &pb.RegisterRotationJobRequest{
func (s *gRPCSystemViewClient) RegisterRotationJob(ctx context.Context, req *rotation.RotationJobConfigureRequest) (id string, retErr error) {
cfgReq := &pb.RegisterRotationJobRequest{
Job: &pb.RotationJobInput{
Schedule: m.GetStructValue(),
RotationID: job.RotationID,
Path: job.Path,
Name: job.Name,
Name: req.Name,
MountType: req.MountType,
Path: req.ReqPath,
RotationSchedule: req.RotationSchedule,
RotationWindow: int64(req.RotationWindow),
RotationPeriod: int64(req.RotationPeriod),
},
}
resp, err := s.client.RegisterRotationJob(ctx, req)
resp, err := s.client.RegisterRotationJob(ctx, cfgReq)
if err != nil {
return "", err
}
return resp.RotationID, nil
}

func (s *gRPCSystemViewClient) DeregisterRotationJob(ctx context.Context, rotationID string) error {
func (s *gRPCSystemViewClient) DeregisterRotationJob(ctx context.Context, req *rotation.RotationJobDeregisterRequest) error {
_, err := s.client.DeregisterRotationJob(ctx, &pb.DeregisterRotationJobRequest{
RotationID: rotationID,
Req: &pb.DeregisterRotationRequestInput{
MountType: req.MountType,
ReqPath: req.ReqPath,
},
})
if err != nil {
return err
Expand Down Expand Up @@ -467,3 +461,47 @@ func (s *gRPCSystemViewServer) GenerateIdentityToken(ctx context.Context, req *p
TTL: int64(res.TTL.Seconds()),
}, nil
}

func (s *gRPCSystemViewServer) RegisterRotationJob(ctx context.Context, req *pb.RegisterRotationJobRequest) (*pb.RegisterRotationJobResponse, error) {
if s.impl == nil {
return nil, errMissingSystemView
}

cfgReq := &rotation.RotationJobConfigureRequest{
Name: req.Job.Name,
MountType: req.Job.MountType,
ReqPath: req.Job.Path,
RotationSchedule: req.Job.RotationSchedule,
RotationWindow: int(req.Job.RotationWindow),
RotationPeriod: int(req.Job.RotationPeriod),
}

rotationID, err := s.impl.RegisterRotationJob(ctx, cfgReq)
if err != nil {
return &pb.RegisterRotationJobResponse{}, status.Errorf(codes.Internal,
err.Error())
}

return &pb.RegisterRotationJobResponse{
RotationID: rotationID,
}, nil
}

func (s *gRPCSystemViewServer) DeregisterRotationJob(ctx context.Context, req *pb.DeregisterRotationJobRequest) (*pb.Empty, error) {
if s.impl == nil {
return &pb.Empty{}, errMissingSystemView
}

cfgReq := &rotation.RotationJobDeregisterRequest{
MountType: req.Req.MountType,
ReqPath: req.Req.ReqPath,
}

err := s.impl.DeregisterRotationJob(ctx, cfgReq)
if err != nil {
return &pb.Empty{}, status.Errorf(codes.Internal,
err.Error())
}

return &pb.Empty{}, nil
}
Loading

0 comments on commit 838a384

Please sign in to comment.