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:
		 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" | ||||
| 	"github.com/hashicorp/go-secure-stdlib/strutil" | ||||
| 	"github.com/hashicorp/go-uuid" | ||||
| 	"github.com/hashicorp/vault/builtin/logical/database/schedule" | ||||
| 	"github.com/hashicorp/vault/helper/metricsutil" | ||||
| 	"github.com/hashicorp/vault/helper/syncmap" | ||||
| 	"github.com/hashicorp/vault/internalshared/configutil" | ||||
| @@ -25,7 +26,6 @@ import ( | ||||
| 	"github.com/hashicorp/vault/sdk/helper/locksutil" | ||||
| 	"github.com/hashicorp/vault/sdk/logical" | ||||
| 	"github.com/hashicorp/vault/sdk/queue" | ||||
| 	"github.com/robfig/cron/v3" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| @@ -34,7 +34,6 @@ const ( | ||||
| 	databaseRolePath        = "role/" | ||||
| 	databaseStaticRolePath  = "static-role/" | ||||
| 	minRootCredRollbackAge  = 1 * time.Minute | ||||
| 	scheduleOptionsDefault  = cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | ||||
| ) | ||||
|  | ||||
| type dbPluginInstance struct { | ||||
| @@ -129,6 +128,7 @@ func Backend(conf *logical.BackendConfig) *databaseBackend { | ||||
| 	b.connections = syncmap.NewSyncMap[string, *dbPluginInstance]() | ||||
| 	b.queueCtx, b.cancelQueueCtx = context.WithCancel(context.Background()) | ||||
| 	b.roleLocks = locksutil.CreateLocks() | ||||
| 	b.schedule = &schedule.DefaultSchedule{} | ||||
|  | ||||
| 	return &b | ||||
| } | ||||
| @@ -180,25 +180,7 @@ type databaseBackend struct { | ||||
| 	gaugeCollectionProcess     *metricsutil.GaugeCollectionProcess | ||||
| 	gaugeCollectionProcessStop sync.Once | ||||
|  | ||||
| 	// scheduleOptionsOverride is used by tests to set a custom ParseOption with seconds enabled | ||||
| 	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 | ||||
| 	schedule schedule.Scheduler | ||||
| } | ||||
|  | ||||
| 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 { | ||||
| 		parsedSchedule, err := b.ParseSchedule(rotationSchedule) | ||||
| 		parsedSchedule, err := b.schedule.Parse(rotationSchedule) | ||||
| 		if 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 { | ||||
| 			rotationWindowSeconds := rotationWindowSecondsRaw.(int) | ||||
| 			if rotationWindowSeconds < minRotationWindowSeconds { | ||||
| 				return logical.ErrorResponse(fmt.Sprintf("rotation_window must be %d seconds or more", minRotationWindowSeconds)), nil | ||||
| 			err := b.schedule.ValidateRotationWindow(rotationWindowSeconds) | ||||
| 			if err != nil { | ||||
| 				return logical.ErrorResponse("rotation_window is invalid", "error", err), nil | ||||
| 			} | ||||
| 			role.StaticAccount.RotationWindow = time.Duration(rotationWindowSeconds) * time.Second | ||||
| 		} | ||||
|   | ||||
| @@ -7,7 +7,6 @@ import ( | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| @@ -333,7 +332,7 @@ func TestBackend_StaticRole_Config(t *testing.T) { | ||||
| 				"rotation_window":   "59s", | ||||
| 			}, | ||||
| 			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": { | ||||
| 			account: map[string]interface{}{ | ||||
| @@ -1152,7 +1151,7 @@ func TestIsInsideRotationWindow(t *testing.T) { | ||||
| 			testTime := tc.now | ||||
| 			if tc.data["rotation_schedule"] != nil && tc.timeModifier != nil { | ||||
| 				rotationSchedule := tc.data["rotation_schedule"].(string) | ||||
| 				schedule, err := b.ParseSchedule(rotationSchedule) | ||||
| 				schedule, err := b.schedule.Parse(rotationSchedule) | ||||
| 				if err != nil { | ||||
| 					t.Fatalf("could not parse rotation_schedule: %s", err) | ||||
| 				} | ||||
|   | ||||
| @@ -22,9 +22,6 @@ const ( | ||||
| 	// Default interval to check the queue for items needing rotation | ||||
| 	defaultQueueTickSeconds = 5 | ||||
|  | ||||
| 	// Minimum allowed value for rotation_window | ||||
| 	minRotationWindowSeconds = 3600 | ||||
|  | ||||
| 	// Config key to set an alternate interval | ||||
| 	queueTickIntervalKey = "rotation_queue_tick_interval" | ||||
|  | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"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/testhelpers/mongodb" | ||||
| 	postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql" | ||||
| @@ -35,7 +36,8 @@ import ( | ||||
| const ( | ||||
| 	dbUser                       = "vaultstatictest" | ||||
| 	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) { | ||||
| @@ -56,7 +58,7 @@ func TestBackend_StaticRole_Rotation_basic(t *testing.T) { | ||||
| 	} | ||||
| 	defer b.Cleanup(context.Background()) | ||||
|  | ||||
| 	b.scheduleOptionsOverride = testScheduleOptionsSeconds | ||||
| 	b.schedule = &TestSchedule{} | ||||
|  | ||||
| 	cleanup, connURL := postgreshelper.PrepareTestContainer(t, "") | ||||
| 	defer cleanup() | ||||
| @@ -1297,7 +1299,7 @@ func TestStoredWALsCorrectlyProcessed(t *testing.T) { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
| 			b.credRotationQueue = queue.New() | ||||
| 			b.scheduleOptionsOverride = testScheduleOptionsSeconds | ||||
| 			b.schedule = &TestSchedule{} | ||||
| 			configureDBMount(t, config.StorageView) | ||||
| 			createRoleWithData(t, b, config.StorageView, mockDB, tc.wal.RoleName, tc.data) | ||||
| 			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 { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	b.scheduleOptionsOverride = testScheduleOptionsSeconds | ||||
| 	b.schedule = &TestSchedule{} | ||||
| 	b.credRotationQueue = queue.New() | ||||
| 	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()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| 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