// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package rotation import ( "fmt" "time" "github.com/robfig/cron/v3" ) const ( PerformedRegistration = "registration" PerformedDeregistration = "deregistration" ) // RotationOptions is an embeddable struct to capture common rotation // settings between a Secret and Auth type RotationOptions struct { // Schedule holds the info for the framework.Schedule Schedule *RotationSchedule } // RotationJob represents the secret part of a response. type RotationJob struct { RotationOptions // RotationID is the ID returned to the user to manage this secret. // This is generated by Vault core. Any set value will be ignored. // For requests, this will always be blank. RotationID string `sentinel:""` Path string MountPoint string Name string } type RotationJobConfigureRequest struct { Name string MountPoint string ReqPath string RotationSchedule string RotationWindow time.Duration RotationPeriod time.Duration } type RotationJobDeregisterRequest struct { MountPoint string ReqPath string } // RotationInfoRequest is the request struct used by SystemView.GetRotationInformation. type RotationInfoRequest struct { // ReqPath is the plugin-local path to the credential, and needs to match the ReqPath value that // was supplied in schedule creation with RegisterRotationJob ReqPath string } // RotationInfoResponse is the response struct returned by SystemView.GetRotationInformation. type RotationInfoResponse struct { // NextVaultRotation is the scheduled time of the next rotation. NextVaultRotation time.Time // LastVaultRotation is the time of the prior rotation. LastVaultRotation time.Time // TTL is integer seconds until next rotation, conventionally clamped to 0 (i.e., will not be negative) TTL int64 } func (s *RotationJob) Validate() error { if s.MountPoint == "" { return fmt.Errorf("MountPoint is required") } if s.Path == "" { return fmt.Errorf("ReqPath is required") } if s.Schedule.RotationSchedule == "" && s.Schedule.RotationPeriod.Seconds() == 0 { return fmt.Errorf("RotationSchedule or RotationPeriod is required to set up rotation job") } return nil } func newRotationJob(configRequest *RotationJobConfigureRequest) (*RotationJob, error) { var cronSc *cron.SpecSchedule if configRequest.RotationSchedule != "" { var err error cronSc, err = DefaultScheduler.Parse(configRequest.RotationSchedule) if err != nil { return nil, err } } rs := &RotationSchedule{ Schedule: cronSc, RotationSchedule: configRequest.RotationSchedule, RotationWindow: configRequest.RotationWindow, RotationPeriod: configRequest.RotationPeriod, NextVaultRotation: time.Time{}, LastVaultRotation: time.Time{}, } return &RotationJob{ RotationOptions: RotationOptions{ Schedule: rs, }, MountPoint: configRequest.MountPoint, Path: configRequest.ReqPath, Name: configRequest.Name, }, nil } // ConfigureRotationJob builds and returns a configured RotationJob for the mount and request with the given schedule. func ConfigureRotationJob(configRequest *RotationJobConfigureRequest) (*RotationJob, error) { rotationJob, err := newRotationJob(configRequest) if err != nil { return nil, err } if err := rotationJob.Validate(); err != nil { return nil, fmt.Errorf("error validating rotation job: %s", err) } // Expect rotation job to exist here if rotationJob == nil { return nil, fmt.Errorf("rotation credential was nil; expected non-nil value") } return rotationJob, nil }