mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	adv ttl mgmt: define schedule interface (#22590)
This commit is contained in:
		 John-Michael Faircloth
					John-Michael Faircloth
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						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