mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-31 18:48:08 +00:00
adv ttl mgmt: define schedule interface (#22590)
This commit is contained in:
committed by
GitHub
parent
e941d444a9
commit
aa05ba6105
@@ -15,6 +15,7 @@ import (
|
|||||||
log "github.com/hashicorp/go-hclog"
|
log "github.com/hashicorp/go-hclog"
|
||||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
|
"github.com/hashicorp/vault/builtin/logical/database/schedule"
|
||||||
"github.com/hashicorp/vault/helper/metricsutil"
|
"github.com/hashicorp/vault/helper/metricsutil"
|
||||||
"github.com/hashicorp/vault/helper/syncmap"
|
"github.com/hashicorp/vault/helper/syncmap"
|
||||||
"github.com/hashicorp/vault/internalshared/configutil"
|
"github.com/hashicorp/vault/internalshared/configutil"
|
||||||
@@ -25,7 +26,6 @@ import (
|
|||||||
"github.com/hashicorp/vault/sdk/helper/locksutil"
|
"github.com/hashicorp/vault/sdk/helper/locksutil"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
"github.com/hashicorp/vault/sdk/queue"
|
"github.com/hashicorp/vault/sdk/queue"
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -34,7 +34,6 @@ const (
|
|||||||
databaseRolePath = "role/"
|
databaseRolePath = "role/"
|
||||||
databaseStaticRolePath = "static-role/"
|
databaseStaticRolePath = "static-role/"
|
||||||
minRootCredRollbackAge = 1 * time.Minute
|
minRootCredRollbackAge = 1 * time.Minute
|
||||||
scheduleOptionsDefault = cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type dbPluginInstance struct {
|
type dbPluginInstance struct {
|
||||||
@@ -129,6 +128,7 @@ func Backend(conf *logical.BackendConfig) *databaseBackend {
|
|||||||
b.connections = syncmap.NewSyncMap[string, *dbPluginInstance]()
|
b.connections = syncmap.NewSyncMap[string, *dbPluginInstance]()
|
||||||
b.queueCtx, b.cancelQueueCtx = context.WithCancel(context.Background())
|
b.queueCtx, b.cancelQueueCtx = context.WithCancel(context.Background())
|
||||||
b.roleLocks = locksutil.CreateLocks()
|
b.roleLocks = locksutil.CreateLocks()
|
||||||
|
b.schedule = &schedule.DefaultSchedule{}
|
||||||
|
|
||||||
return &b
|
return &b
|
||||||
}
|
}
|
||||||
@@ -180,25 +180,7 @@ type databaseBackend struct {
|
|||||||
gaugeCollectionProcess *metricsutil.GaugeCollectionProcess
|
gaugeCollectionProcess *metricsutil.GaugeCollectionProcess
|
||||||
gaugeCollectionProcessStop sync.Once
|
gaugeCollectionProcessStop sync.Once
|
||||||
|
|
||||||
// scheduleOptionsOverride is used by tests to set a custom ParseOption with seconds enabled
|
schedule schedule.Scheduler
|
||||||
scheduleOptionsOverride cron.ParseOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *databaseBackend) ParseSchedule(rotationSchedule string) (*cron.SpecSchedule, error) {
|
|
||||||
scheduleOptions := scheduleOptionsDefault
|
|
||||||
if b.scheduleOptionsOverride != 0 {
|
|
||||||
scheduleOptions = b.scheduleOptionsOverride
|
|
||||||
}
|
|
||||||
parser := cron.NewParser(scheduleOptions)
|
|
||||||
schedule, err := parser.Parse(rotationSchedule)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sched, ok := schedule.(*cron.SpecSchedule)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("invalid rotation schedule")
|
|
||||||
}
|
|
||||||
return sched, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *databaseBackend) DatabaseConfig(ctx context.Context, s logical.Storage, name string) (*DatabaseConfig, error) {
|
func (b *databaseBackend) DatabaseConfig(ctx context.Context, s logical.Storage, name string) (*DatabaseConfig, error) {
|
||||||
|
|||||||
@@ -591,7 +591,7 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
|
|||||||
}
|
}
|
||||||
|
|
||||||
if rotationScheduleOk {
|
if rotationScheduleOk {
|
||||||
parsedSchedule, err := b.ParseSchedule(rotationSchedule)
|
parsedSchedule, err := b.schedule.Parse(rotationSchedule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logical.ErrorResponse("could not parse rotation_schedule", "error", err), nil
|
return logical.ErrorResponse("could not parse rotation_schedule", "error", err), nil
|
||||||
}
|
}
|
||||||
@@ -600,8 +600,9 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
|
|||||||
|
|
||||||
if rotationWindowOk {
|
if rotationWindowOk {
|
||||||
rotationWindowSeconds := rotationWindowSecondsRaw.(int)
|
rotationWindowSeconds := rotationWindowSecondsRaw.(int)
|
||||||
if rotationWindowSeconds < minRotationWindowSeconds {
|
err := b.schedule.ValidateRotationWindow(rotationWindowSeconds)
|
||||||
return logical.ErrorResponse(fmt.Sprintf("rotation_window must be %d seconds or more", minRotationWindowSeconds)), nil
|
if err != nil {
|
||||||
|
return logical.ErrorResponse("rotation_window is invalid", "error", err), nil
|
||||||
}
|
}
|
||||||
role.StaticAccount.RotationWindow = time.Duration(rotationWindowSeconds) * time.Second
|
role.StaticAccount.RotationWindow = time.Duration(rotationWindowSeconds) * time.Second
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -332,8 +331,8 @@ func TestBackend_StaticRole_Config(t *testing.T) {
|
|||||||
"rotation_schedule": "* * * * *",
|
"rotation_schedule": "* * * * *",
|
||||||
"rotation_window": "59s",
|
"rotation_window": "59s",
|
||||||
},
|
},
|
||||||
path: "plugin-role-test",
|
path: "plugin-role-test",
|
||||||
err: errors.New(fmt.Sprintf("rotation_window must be %d seconds or more", minRotationWindowSeconds)),
|
errContains: "rotation_window is invalid",
|
||||||
},
|
},
|
||||||
"disallowed role config": {
|
"disallowed role config": {
|
||||||
account: map[string]interface{}{
|
account: map[string]interface{}{
|
||||||
@@ -1152,7 +1151,7 @@ func TestIsInsideRotationWindow(t *testing.T) {
|
|||||||
testTime := tc.now
|
testTime := tc.now
|
||||||
if tc.data["rotation_schedule"] != nil && tc.timeModifier != nil {
|
if tc.data["rotation_schedule"] != nil && tc.timeModifier != nil {
|
||||||
rotationSchedule := tc.data["rotation_schedule"].(string)
|
rotationSchedule := tc.data["rotation_schedule"].(string)
|
||||||
schedule, err := b.ParseSchedule(rotationSchedule)
|
schedule, err := b.schedule.Parse(rotationSchedule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not parse rotation_schedule: %s", err)
|
t.Fatalf("could not parse rotation_schedule: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ const (
|
|||||||
// Default interval to check the queue for items needing rotation
|
// Default interval to check the queue for items needing rotation
|
||||||
defaultQueueTickSeconds = 5
|
defaultQueueTickSeconds = 5
|
||||||
|
|
||||||
// Minimum allowed value for rotation_window
|
|
||||||
minRotationWindowSeconds = 3600
|
|
||||||
|
|
||||||
// Config key to set an alternate interval
|
// Config key to set an alternate interval
|
||||||
queueTickIntervalKey = "rotation_queue_tick_interval"
|
queueTickIntervalKey = "rotation_queue_tick_interval"
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sectorbob/mlab-ns2/gae/ns/digest"
|
"github.com/Sectorbob/mlab-ns2/gae/ns/digest"
|
||||||
|
"github.com/hashicorp/vault/builtin/logical/database/schedule"
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/helper/testhelpers/mongodb"
|
"github.com/hashicorp/vault/helper/testhelpers/mongodb"
|
||||||
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
||||||
@@ -33,9 +34,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dbUser = "vaultstatictest"
|
dbUser = "vaultstatictest"
|
||||||
dbUserDefaultPassword = "password"
|
dbUserDefaultPassword = "password"
|
||||||
testScheduleOptionsSeconds = cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow
|
testMinRotationWindowSeconds = 10
|
||||||
|
testScheduleParseOptions = cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBackend_StaticRole_Rotation_basic(t *testing.T) {
|
func TestBackend_StaticRole_Rotation_basic(t *testing.T) {
|
||||||
@@ -56,7 +58,7 @@ func TestBackend_StaticRole_Rotation_basic(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer b.Cleanup(context.Background())
|
defer b.Cleanup(context.Background())
|
||||||
|
|
||||||
b.scheduleOptionsOverride = testScheduleOptionsSeconds
|
b.schedule = &TestSchedule{}
|
||||||
|
|
||||||
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
cleanup, connURL := postgreshelper.PrepareTestContainer(t, "")
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
@@ -1297,7 +1299,7 @@ func TestStoredWALsCorrectlyProcessed(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
b.credRotationQueue = queue.New()
|
b.credRotationQueue = queue.New()
|
||||||
b.scheduleOptionsOverride = testScheduleOptionsSeconds
|
b.schedule = &TestSchedule{}
|
||||||
configureDBMount(t, config.StorageView)
|
configureDBMount(t, config.StorageView)
|
||||||
createRoleWithData(t, b, config.StorageView, mockDB, tc.wal.RoleName, tc.data)
|
createRoleWithData(t, b, config.StorageView, mockDB, tc.wal.RoleName, tc.data)
|
||||||
role, err := b.StaticRole(ctx, config.StorageView, "hashicorp")
|
role, err := b.StaticRole(ctx, config.StorageView, "hashicorp")
|
||||||
@@ -1458,7 +1460,7 @@ func getBackend(t *testing.T) (*databaseBackend, logical.Storage, *mockNewDataba
|
|||||||
if err := b.Setup(context.Background(), config); err != nil {
|
if err := b.Setup(context.Background(), config); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
b.scheduleOptionsOverride = testScheduleOptionsSeconds
|
b.schedule = &TestSchedule{}
|
||||||
b.credRotationQueue = queue.New()
|
b.credRotationQueue = queue.New()
|
||||||
b.populateQueue(context.Background(), config.StorageView)
|
b.populateQueue(context.Background(), config.StorageView)
|
||||||
|
|
||||||
@@ -1548,3 +1550,27 @@ func assertPriorityUnchanged(t *testing.T, priority int64, nextRotationTime time
|
|||||||
t.Fatalf("expected next rotation at %s, but got %s", nextRotationTime, time.Unix(priority, 0).String())
|
t.Fatalf("expected next rotation at %s, but got %s", nextRotationTime, time.Unix(priority, 0).String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ schedule.Scheduler = &TestSchedule{}
|
||||||
|
|
||||||
|
type TestSchedule struct{}
|
||||||
|
|
||||||
|
func (d *TestSchedule) Parse(rotationSchedule string) (*cron.SpecSchedule, error) {
|
||||||
|
parser := cron.NewParser(testScheduleParseOptions)
|
||||||
|
schedule, err := parser.Parse(rotationSchedule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sched, ok := schedule.(*cron.SpecSchedule)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid rotation schedule")
|
||||||
|
}
|
||||||
|
return sched, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *TestSchedule) ValidateRotationWindow(s int) error {
|
||||||
|
if s < testMinRotationWindowSeconds {
|
||||||
|
return fmt.Errorf("rotation_window must be %d seconds or more", testMinRotationWindowSeconds)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
42
builtin/logical/database/schedule/schedule.go
Normal file
42
builtin/logical/database/schedule/schedule.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package schedule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/robfig/cron/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Minimum allowed value for rotation_window
|
||||||
|
minRotationWindowSeconds = 3600
|
||||||
|
parseOptions = cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow
|
||||||
|
)
|
||||||
|
|
||||||
|
type Scheduler interface {
|
||||||
|
Parse(string) (*cron.SpecSchedule, error)
|
||||||
|
ValidateRotationWindow(int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Scheduler = &DefaultSchedule{}
|
||||||
|
|
||||||
|
type DefaultSchedule struct{}
|
||||||
|
|
||||||
|
func (d *DefaultSchedule) Parse(rotationSchedule string) (*cron.SpecSchedule, error) {
|
||||||
|
parser := cron.NewParser(parseOptions)
|
||||||
|
schedule, err := parser.Parse(rotationSchedule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sched, ok := schedule.(*cron.SpecSchedule)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid rotation schedule")
|
||||||
|
}
|
||||||
|
return sched, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DefaultSchedule) ValidateRotationWindow(s int) error {
|
||||||
|
if s < minRotationWindowSeconds {
|
||||||
|
return fmt.Errorf("rotation_window must be %d seconds or more", minRotationWindowSeconds)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user