Documentation
¶
Overview ¶
Package rotation provides interfaces and types for secret value rotation in dsops.
This package implements the core rotation engine that orchestrates the process of rotating actual secret values (passwords, API keys, certificates) within services, as opposed to encryption keys used for file encryption or storage-level versioning.
Architecture Overview ¶
The rotation system follows a multi-layered architecture that separates concerns and enables flexible, extensible secret rotation across diverse service types:
┌─────────────────────────────────────────────────────────────┐
│ CLI Commands │
│ (cmd/dsops/commands/) │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ Rotation Engine │
│ (pkg/rotation/) ◄───────────┤
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ Rotation Strategies │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ PostgreSQL │ │ Stripe │ │ Generic │ ... │
│ │ Rotator │ │ Rotator │ │ Rotator │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ Protocol Adapters │
│ (pkg/protocol/) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ SQL │ │ HTTP API │ │ Certificate │ ... │
│ │ Adapter │ │ Adapter │ │ Adapter │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ dsops-data Repository │
│ Community Service Definitions │
└─────────────────────────────────────────────────────────────┘
Core Concepts ¶
## Secret Value Rotation vs Storage Rotation
This package handles **secret value rotation** - updating the actual credential values used by services. This is distinct from:
- Storage-level versioning (handled by secret store providers)
- Encryption key rotation for file encryption
- Database encryption key rotation
Examples of secret value rotation:
- Changing a PostgreSQL user's password
- Generating new API keys in Stripe
- Issuing new TLS certificates
- Refreshing OAuth tokens
## Data-Driven Architecture
The rotation system uses a data-driven approach built on three key components:
- **Secret Store Providers** (pkg/provider) - Where secrets are stored
- **Service Integrations** (this package) - What uses secrets and supports rotation
- **Protocol Adapters** (pkg/protocol) - How to communicate with services
Service integrations are defined using community-maintained data from the dsops-data repository rather than hardcoded implementations. This enables support for hundreds of services without requiring code changes.
## Rotation Strategies
The package supports multiple rotation strategies to handle different security and availability requirements:
- **Immediate**: Replace secret instantly (brief downtime acceptable)
- **Two-Key**: Maintain two valid secrets for zero-downtime rotation
- **Overlap**: Gradual transition with configurable overlap period
- **Gradual**: Percentage-based rollout for large deployments
- **Custom**: User-defined scripts for special cases
Each strategy is implemented as a separate rotator that can be registered with the rotation engine and selected based on service requirements.
Rotation Engine ¶
The RotationEngine serves as the central coordination point for all rotation operations. It provides:
- Strategy registration and discovery
- Request routing based on secret type and strategy
- Batch operations for rotating multiple secrets
- Rotation history tracking and retrieval
- Scheduling future rotations
- Error aggregation and reporting
Rotation Lifecycle ¶
A complete rotation operation follows this lifecycle:
- **Planning**: Validate request, check constraints, select strategy
- **Generation**: Create new secret value according to requirements
- **Service Update**: Update target service with new credentials
- **Verification**: Test that new credentials work correctly
- **Storage Update**: Store new secret in secret management system
- **Cleanup**: Remove or deprecate old credentials
- **Audit**: Record all actions taken for compliance
Each step can fail and trigger rollback procedures to restore service functionality.
Core Interfaces ¶
## SecretValueRotator
The primary interface that all rotation strategies must implement. Defines methods for:
- Capability checking (SupportsSecret)
- Primary rotation operation (Rotate)
- Verification of new credentials (Verify)
- Rollback to previous values (Rollback)
- Status monitoring (GetStatus)
## TwoSecretRotator
Extended interface for zero-downtime rotation strategies. Enables:
- Creating secondary secrets alongside primary
- Promoting secondary to primary status
- Deprecating old primary after verification
## SchemaAwareRotator
Interface for rotators that leverage dsops-data community definitions. Enables data-driven rotation without hardcoded service logic.
Data Structures ¶
The package defines comprehensive data structures for rotation operations:
## Core Request/Response Types
- **RotationRequest**: Complete specification of what to rotate and how
- **RotationResult**: Detailed outcome including timing and audit trail
- **SecretInfo**: Comprehensive secret metadata and constraints
- **VerificationRequest**: Specification of how to test new credentials
## Supporting Types
- **SecretReference**: Points to specific versions of secrets
- **NewSecretValue**: Specifies how to generate new values
- **RotationConstraints**: Defines limits and requirements
- **AuditEntry**: Records actions taken during rotation
Rotation Strategies Implementation ¶
## Immediate Strategy
Replaces credentials instantly:
- Generate new secret value
- Update service configuration
- Verify new credentials work
- Update secret storage
- Clean up old credentials
Suitable for: Development environments, non-critical services, maintenance windows
## Two-Key Strategy
Maintains two valid credentials during transition:
- Create secondary secret alongside primary
- Deploy secondary to all systems
- Verify secondary works everywhere
- Promote secondary to primary
- Deprecate old primary after grace period
Suitable for: High-availability services, distributed systems, critical databases
## Overlap Strategy
Gradual transition with configurable overlap:
- Create new credentials
- Begin directing percentage of traffic to new credentials
- Gradually increase percentage over time
- Monitor for issues and rollback if needed
- Complete transition after validation period
Suitable for: Large-scale services, gradual rollouts, risk-averse environments
Integration with dsops-data ¶
The rotation system leverages the dsops-data community repository for service definitions, enabling:
- **Service Type Definitions**: Standard schemas for common services
- **Instance Configurations**: Pre-configured deployment patterns
- **Rotation Policies**: Best practices and constraints
- **Protocol Specifications**: Communication patterns and APIs
This data-driven approach means new services can be supported by contributing definitions to dsops-data without modifying dsops code.
Security Architecture ¶
The rotation system implements multiple security layers:
## Secret Handling
- Secrets never logged (use logging.Secret wrapper)
- In-memory only during rotation operations
- Secure cleanup of memory after operations
- Context-based cancellation for timeout handling
## Verification and Rollback
- Comprehensive verification before completing rotation
- Automatic rollback on verification failures
- Manual rollback capabilities for emergency scenarios
- Audit trails for all operations
## Access Controls
- Service-level permission validation
- Rotation constraints and policies
- Rate limiting and abuse prevention
- Integration with enterprise IAM systems
Error Handling and Observability ¶
The package provides comprehensive error handling:
- Structured error types for different failure modes
- Detailed error messages with actionable guidance
- Error aggregation for batch operations
- Integration with monitoring and alerting systems
Observability features include:
- Detailed audit trails for compliance
- Timing metrics for performance monitoring
- Success/failure rate tracking
- Integration with OpenTelemetry
Extension Points ¶
The architecture provides multiple extension points:
## Custom Rotators
Implement SecretValueRotator for service-specific rotation logic:
type CustomRotator struct {
client CustomServiceClient
}
func (r *CustomRotator) Rotate(ctx context.Context, req RotationRequest) (*RotationResult, error) {
// Custom rotation logic
}
## Protocol Adapters
Create adapters for new communication protocols in pkg/protocol.
## Verification Tests
Define custom verification procedures for ensuring new credentials work.
## Notification Integrations
Add support for new notification channels (Slack, email, webhooks, etc.).
Future Evolution ¶
The rotation architecture is designed for evolution:
- **Plugin Architecture**: Support for external rotation strategies
- **Advanced Scheduling**: Cron-like scheduling with dependencies
- **Multi-Region Coordination**: Coordinate rotations across regions
- **ML-Driven Optimization**: Optimize rotation timing and strategies
- **Compliance Automation**: Automated compliance reporting and validation
This package represents the core of dsops's secret rotation capabilities, providing a secure, scalable, and extensible foundation for automated secret lifecycle management across diverse service ecosystems.
Package rotation provides interfaces and types for secret value rotation in dsops.
This package implements the core rotation engine that orchestrates the process of rotating actual secret values (passwords, API keys, certificates) as opposed to encryption keys used for file encryption.
Rotation Architecture ¶
dsops uses a data-driven approach to rotation built on three key components:
- **Secret Store Providers** (pkg/provider) - Where secrets are stored (Vault, AWS, etc.)
- **Service Integrations** (this package) - What uses secrets (PostgreSQL, Stripe, etc.)
- **Protocol Adapters** (pkg/protocol) - How to communicate with services
Service integrations are defined using community-maintained data from the dsops-data repository rather than hardcoded implementations. This enables support for hundreds of services without requiring code changes.
Rotation Strategies ¶
Different rotation strategies handle various security and availability requirements:
- **Immediate**: Replace secret instantly (brief downtime acceptable)
- **Two-Key**: Maintain two valid secrets for zero-downtime rotation
- **Overlap**: Gradual transition with configurable overlap period
- **Gradual**: Percentage-based rollout for large deployments
- **Custom**: User-defined scripts for special cases
Usage Example ¶
// Create rotation engine
engine := NewRotationEngine()
// Register strategies
engine.RegisterStrategy(&PostgresRotator{})
engine.RegisterStrategy(&StripeRotator{})
// Perform rotation
request := RotationRequest{
Secret: SecretInfo{
Key: "DATABASE_PASSWORD",
Provider: "aws.secretsmanager",
SecretType: SecretTypePassword,
},
Strategy: "postgres",
TwoSecret: true,
}
result, err := engine.Rotate(ctx, request)
if err != nil {
return fmt.Errorf("rotation failed: %w", err)
}
fmt.Printf("Rotation completed: %s\n", result.Status)
Implementing Custom Rotators ¶
To implement a custom rotation strategy:
- Implement SecretValueRotator interface
- Optionally implement TwoSecretRotator for zero-downtime support
- Implement SchemaAwareRotator to use dsops-data definitions
- Register your rotator with the rotation engine
Example:
type MyServiceRotator struct {
client MyServiceClient
}
func (r *MyServiceRotator) Name() string {
return "my-service"
}
func (r *MyServiceRotator) SupportsSecret(ctx context.Context, secret SecretInfo) bool {
return secret.SecretType == SecretTypeAPIKey
}
func (r *MyServiceRotator) Rotate(ctx context.Context, req RotationRequest) (*RotationResult, error) {
// Implementation specific to your service
newAPIKey, err := r.client.CreateNewAPIKey(ctx)
if err != nil {
return nil, err
}
// Store new key in secret provider
// Update service to use new key
// Verify new key works
// Deprecate old key
return &RotationResult{
Status: StatusCompleted,
RotatedAt: &now,
}, nil
}
Security Considerations ¶
Rotation operations must:
- Never log secret values (use logging.Secret wrapper)
- Verify new secrets work before removing old ones
- Support rollback to previous values on failure
- Generate audit trails for all operations
- Handle concurrent rotation attempts gracefully
- Respect service-specific constraints and policies
Data-Driven Integration ¶
Rotators can leverage dsops-data repository for service definitions:
type DataDrivenRotator struct {
repository *dsopsdata.Repository
}
func (r *DataDrivenRotator) SetRepository(repo *dsopsdata.Repository) {
r.repository = repo
}
func (r *DataDrivenRotator) Rotate(ctx context.Context, req RotationRequest) (*RotationResult, error) {
// Use repository to get service-specific configuration
serviceDef, err := r.repository.GetServiceType(req.Secret.Provider)
if err != nil {
return nil, err
}
// Use service definition to determine rotation approach
// ...
}
Index ¶
- Variables
- type AuditEntry
- type CredentialConstraints
- type DefaultRotationEngine
- func (e *DefaultRotationEngine) AutoSelectStrategy(ctx context.Context, secret SecretInfo) (string, error)
- func (e *DefaultRotationEngine) BatchRotate(ctx context.Context, requests []RotationRequest) ([]RotationResult, error)
- func (e *DefaultRotationEngine) Close() error
- func (e *DefaultRotationEngine) GetRotationHistory(ctx context.Context, secret SecretInfo, limit int) ([]RotationResult, error)
- func (e *DefaultRotationEngine) GetRotationStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
- func (e *DefaultRotationEngine) GetServiceInstanceMetadata(serviceType, instanceID string) map[string]interface{}
- func (e *DefaultRotationEngine) GetStrategy(name string) (SecretValueRotator, error)
- func (e *DefaultRotationEngine) ListSecrets(ctx context.Context) ([]SecretInfo, error)
- func (e *DefaultRotationEngine) ListStrategies() []string
- func (e *DefaultRotationEngine) RegisterStrategy(strategy SecretValueRotator) error
- func (e *DefaultRotationEngine) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
- func (e *DefaultRotationEngine) ScheduleRotation(ctx context.Context, request RotationRequest, when time.Time) error
- func (e *DefaultRotationEngine) SetNotifier(notifier *notifications.Manager)
- func (e *DefaultRotationEngine) SetRepository(repository *dsopsdata.Repository)
- type DefaultStrategySelector
- type DeprecateRequest
- type FileRotationStorage
- func (f *FileRotationStorage) Close() error
- func (f *FileRotationStorage) GetRotationHistory(ctx context.Context, secret SecretInfo, limit int) ([]RotationResult, error)
- func (f *FileRotationStorage) GetRotationStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
- func (f *FileRotationStorage) ListSecrets(ctx context.Context) ([]SecretInfo, error)
- func (f *FileRotationStorage) StoreRotationResult(ctx context.Context, result RotationResult) error
- func (f *FileRotationStorage) UpdateRotationStatus(ctx context.Context, secret SecretInfo, status RotationStatusInfo) error
- type ImmediateRotationStrategy
- func (s *ImmediateRotationStrategy) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
- func (s *ImmediateRotationStrategy) Name() string
- func (s *ImmediateRotationStrategy) Rollback(ctx context.Context, request RollbackRequest) error
- func (s *ImmediateRotationStrategy) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
- func (s *ImmediateRotationStrategy) SupportsSecret(ctx context.Context, secret SecretInfo) bool
- func (s *ImmediateRotationStrategy) Verify(ctx context.Context, request VerificationRequest) error
- type MemoryRotationStorage
- func (m *MemoryRotationStorage) Close() error
- func (m *MemoryRotationStorage) GetRotationHistory(ctx context.Context, secret SecretInfo, limit int) ([]RotationResult, error)
- func (m *MemoryRotationStorage) GetRotationStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
- func (m *MemoryRotationStorage) ListSecrets(ctx context.Context) ([]SecretInfo, error)
- func (m *MemoryRotationStorage) StoreRotationResult(ctx context.Context, result RotationResult) error
- func (m *MemoryRotationStorage) UpdateRotationStatus(ctx context.Context, secret SecretInfo, status RotationStatusInfo) error
- type NewSecretValue
- type OverlapRotationStrategy
- func (s *OverlapRotationStrategy) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
- func (s *OverlapRotationStrategy) Name() string
- func (s *OverlapRotationStrategy) Rollback(ctx context.Context, request RollbackRequest) error
- func (s *OverlapRotationStrategy) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
- func (s *OverlapRotationStrategy) SupportsSecret(ctx context.Context, secret SecretInfo) bool
- func (s *OverlapRotationStrategy) Verify(ctx context.Context, request VerificationRequest) error
- type PromoteRequest
- type ProviderCapabilities
- type RandomRotator
- func (r *RandomRotator) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
- func (r *RandomRotator) Name() string
- func (r *RandomRotator) Rollback(ctx context.Context, request RollbackRequest) error
- func (r *RandomRotator) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
- func (r *RandomRotator) SupportsSecret(ctx context.Context, secret SecretInfo) bool
- func (r *RandomRotator) Verify(ctx context.Context, request VerificationRequest) error
- type RollbackRequest
- type RotationConstraints
- type RotationEngine
- type RotationRequest
- type RotationResult
- type RotationStatus
- type RotationStatusInfo
- type RotationStorage
- type RotationStrategy
- type SchemaAwareRotator
- type SchemaMetadata
- type ScriptInput
- type ScriptOutput
- type ScriptRotator
- func (s *ScriptRotator) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
- func (s *ScriptRotator) Name() string
- func (s *ScriptRotator) Rollback(ctx context.Context, request RollbackRequest) error
- func (s *ScriptRotator) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
- func (s *ScriptRotator) SetRepository(repository *dsopsdata.Repository)
- func (s *ScriptRotator) SupportsSecret(ctx context.Context, secret SecretInfo) bool
- func (s *ScriptRotator) Verify(ctx context.Context, request VerificationRequest) error
- type SecondarySecretRequest
- type SecretInfo
- type SecretReference
- type SecretType
- type SecretValueRotator
- type ServiceInstanceMeta
- type StoredRotationResult
- type StoredRotationStatus
- type StrategyRegistry
- func (r *StrategyRegistry) CreateStrategy(name string) (SecretValueRotator, error)
- func (r *StrategyRegistry) HasStrategy(name string) bool
- func (r *StrategyRegistry) ListStrategies() []string
- func (r *StrategyRegistry) RegisterCustomStrategy(name string, factory func(*logging.Logger) SecretValueRotator) error
- type StrategySelector
- type TestStatus
- type TestType
- type TwoSecretMetadata
- type TwoSecretRotator
- type TwoSecretStrategy
- func (s *TwoSecretStrategy) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
- func (s *TwoSecretStrategy) GetTwoSecretStatus(ctx context.Context, secret SecretInfo) (*TwoSecretMetadata, error)
- func (s *TwoSecretStrategy) Name() string
- func (s *TwoSecretStrategy) Rollback(ctx context.Context, request RollbackRequest) error
- func (s *TwoSecretStrategy) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
- func (s *TwoSecretStrategy) SupportsSecret(ctx context.Context, secret SecretInfo) bool
- func (s *TwoSecretStrategy) Verify(ctx context.Context, request VerificationRequest) error
- type ValueType
- type VerificationRequest
- type VerificationResult
- type VerificationTest
- type WebhookRequest
- type WebhookResponse
- type WebhookRotator
- func (w *WebhookRotator) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
- func (w *WebhookRotator) Name() string
- func (w *WebhookRotator) Rollback(ctx context.Context, request RollbackRequest) error
- func (w *WebhookRotator) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
- func (w *WebhookRotator) SupportsSecret(ctx context.Context, secret SecretInfo) bool
- func (w *WebhookRotator) Verify(ctx context.Context, request VerificationRequest) error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // AWSIAMCapabilities for AWS IAM access keys AWSIAMCapabilities = ProviderCapabilities{ MaxActiveKeys: 2, SupportsVersioning: false, SupportsExpiration: false, SupportsRevocation: true, SupportsMetadata: true, MinRotationInterval: 0, RecommendedStrategies: []RotationStrategy{ StrategyTwoKey, StrategyEmergency, }, } // AWSSecretsManagerCapabilities for AWS Secrets Manager AWSSecretsManagerCapabilities = ProviderCapabilities{ MaxActiveKeys: -1, SupportsVersioning: true, SupportsExpiration: true, SupportsRevocation: false, SupportsMetadata: true, MinRotationInterval: 1 * time.Hour, RecommendedStrategies: []RotationStrategy{ StrategyVersioned, StrategyOverlap, }, } // AzureServicePrincipalCapabilities for Azure AD AzureServicePrincipalCapabilities = ProviderCapabilities{ MaxActiveKeys: -1, SupportsVersioning: false, SupportsExpiration: true, SupportsRevocation: true, SupportsMetadata: true, MinRotationInterval: 0, RecommendedStrategies: []RotationStrategy{ StrategyTwoKey, StrategyOverlap, StrategyEmergency, }, } // GitHubPATCapabilities for GitHub Personal Access Tokens GitHubPATCapabilities = ProviderCapabilities{ MaxActiveKeys: -1, SupportsVersioning: false, SupportsExpiration: true, SupportsRevocation: true, SupportsMetadata: true, MinRotationInterval: 0, RecommendedStrategies: []RotationStrategy{ StrategyImmediate, StrategyOverlap, StrategyEmergency, }, } // StripeAPIKeyCapabilities for Stripe StripeAPIKeyCapabilities = ProviderCapabilities{ MaxActiveKeys: -1, SupportsVersioning: false, SupportsExpiration: false, SupportsRevocation: true, SupportsMetadata: true, MinRotationInterval: 0, RecommendedStrategies: []RotationStrategy{ StrategyTwoKey, StrategyEmergency, }, } // DatadogAPIKeyCapabilities for Datadog DatadogAPIKeyCapabilities = ProviderCapabilities{ MaxActiveKeys: 50, SupportsVersioning: false, SupportsExpiration: false, SupportsRevocation: true, SupportsMetadata: true, MinRotationInterval: 0, RecommendedStrategies: []RotationStrategy{ StrategyTwoKey, StrategyEmergency, }, } // OktaTokenCapabilities for Okta (static tokens) OktaTokenCapabilities = ProviderCapabilities{ MaxActiveKeys: -1, SupportsVersioning: false, SupportsExpiration: true, SupportsRevocation: true, SupportsMetadata: false, MinRotationInterval: 0, RecommendedStrategies: []RotationStrategy{ StrategyImmediate, StrategyEmergency, }, } )
Common provider capabilities
Functions ¶
This section is empty.
Types ¶
type AuditEntry ¶
type AuditEntry struct {
Timestamp time.Time `json:"timestamp"`
Action string `json:"action"`
Component string `json:"component"`
Status string `json:"status"`
Message string `json:"message,omitempty"`
Details map[string]interface{} `json:"details,omitempty"`
Error string `json:"error,omitempty"`
}
AuditEntry records an action taken during rotation
type CredentialConstraints ¶
type CredentialConstraints struct {
MaxActive interface{} `json:"maxActive,omitempty"` // Can be int or "unlimited"
TTL string `json:"ttl,omitempty"`
Format string `json:"format,omitempty"`
RotationRequired bool `json:"rotationRequired,omitempty"`
MaxKeys int `json:"maxKeys,omitempty"`
Renewable bool `json:"renewable,omitempty"`
Managed bool `json:"managed,omitempty"`
RequiresMFA bool `json:"requiresMFA,omitempty"`
}
CredentialConstraints mirrors the dsops-data constraints structure
type DefaultRotationEngine ¶
type DefaultRotationEngine struct {
// contains filtered or unexported fields
}
DefaultRotationEngine implements the RotationEngine interface
func NewRotationEngine ¶
func NewRotationEngine(logger *logging.Logger) *DefaultRotationEngine
NewRotationEngine creates a new rotation engine with in-memory storage
func NewRotationEngineWithStorage ¶
func NewRotationEngineWithStorage(storage RotationStorage, logger *logging.Logger) *DefaultRotationEngine
NewRotationEngineWithStorage creates a new rotation engine with custom storage
func (*DefaultRotationEngine) AutoSelectStrategy ¶
func (e *DefaultRotationEngine) AutoSelectStrategy(ctx context.Context, secret SecretInfo) (string, error)
AutoSelectStrategy automatically selects the best rotation strategy for a secret
func (*DefaultRotationEngine) BatchRotate ¶
func (e *DefaultRotationEngine) BatchRotate(ctx context.Context, requests []RotationRequest) ([]RotationResult, error)
BatchRotate rotates multiple secrets
func (*DefaultRotationEngine) Close ¶
func (e *DefaultRotationEngine) Close() error
Close closes the rotation engine and any associated resources
func (*DefaultRotationEngine) GetRotationHistory ¶
func (e *DefaultRotationEngine) GetRotationHistory(ctx context.Context, secret SecretInfo, limit int) ([]RotationResult, error)
GetRotationHistory returns rotation history for a secret
func (*DefaultRotationEngine) GetRotationStatus ¶
func (e *DefaultRotationEngine) GetRotationStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
GetRotationStatus returns the current rotation status for a secret
func (*DefaultRotationEngine) GetServiceInstanceMetadata ¶
func (e *DefaultRotationEngine) GetServiceInstanceMetadata(serviceType, instanceID string) map[string]interface{}
GetServiceInstanceMetadata retrieves metadata from dsops-data for enhanced rotation
func (*DefaultRotationEngine) GetStrategy ¶
func (e *DefaultRotationEngine) GetStrategy(name string) (SecretValueRotator, error)
GetStrategy returns a strategy by name
func (*DefaultRotationEngine) ListSecrets ¶
func (e *DefaultRotationEngine) ListSecrets(ctx context.Context) ([]SecretInfo, error)
ListSecrets returns all secrets that have rotation metadata
func (*DefaultRotationEngine) ListStrategies ¶
func (e *DefaultRotationEngine) ListStrategies() []string
ListStrategies returns all available strategies
func (*DefaultRotationEngine) RegisterStrategy ¶
func (e *DefaultRotationEngine) RegisterStrategy(strategy SecretValueRotator) error
RegisterStrategy adds a rotation strategy
func (*DefaultRotationEngine) Rotate ¶
func (e *DefaultRotationEngine) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
Rotate performs rotation using the appropriate strategy
func (*DefaultRotationEngine) ScheduleRotation ¶
func (e *DefaultRotationEngine) ScheduleRotation(ctx context.Context, request RotationRequest, when time.Time) error
ScheduleRotation schedules a rotation for future execution
func (*DefaultRotationEngine) SetNotifier ¶
func (e *DefaultRotationEngine) SetNotifier(notifier *notifications.Manager)
SetNotifier sets the notification manager for rotation events. The notification manager must be started before it will send notifications.
func (*DefaultRotationEngine) SetRepository ¶
func (e *DefaultRotationEngine) SetRepository(repository *dsopsdata.Repository)
SetRepository sets the dsops-data repository for schema-aware rotation
type DefaultStrategySelector ¶
type DefaultStrategySelector struct{}
DefaultStrategySelector implements intelligent strategy selection
func (*DefaultStrategySelector) SelectStrategy ¶
func (s *DefaultStrategySelector) SelectStrategy(ctx context.Context, secret SecretInfo, capabilities ProviderCapabilities) (RotationStrategy, error)
SelectStrategy chooses the best rotation strategy
func (*DefaultStrategySelector) ValidateStrategy ¶
func (s *DefaultStrategySelector) ValidateStrategy(strategy RotationStrategy, capabilities ProviderCapabilities) error
ValidateStrategy ensures strategy is compatible with provider
type DeprecateRequest ¶
type DeprecateRequest struct {
Secret SecretInfo `json:"secret"`
OldRef SecretReference `json:"old_ref"`
GracePeriod time.Duration `json:"grace_period"`
HardDelete bool `json:"hard_delete"`
}
DeprecateRequest for deprecating old primary
type FileRotationStorage ¶
type FileRotationStorage struct {
// contains filtered or unexported fields
}
FileRotationStorage implements RotationStorage using local file system
func NewFileRotationStorage ¶
func NewFileRotationStorage(dataDir string, logger *logging.Logger) (*FileRotationStorage, error)
NewFileRotationStorage creates a new file-based rotation storage
func (*FileRotationStorage) Close ¶
func (f *FileRotationStorage) Close() error
Close closes any resources used by the storage
func (*FileRotationStorage) GetRotationHistory ¶
func (f *FileRotationStorage) GetRotationHistory(ctx context.Context, secret SecretInfo, limit int) ([]RotationResult, error)
GetRotationHistory retrieves rotation history for a secret
func (*FileRotationStorage) GetRotationStatus ¶
func (f *FileRotationStorage) GetRotationStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
GetRotationStatus gets the current rotation status for a secret
func (*FileRotationStorage) ListSecrets ¶
func (f *FileRotationStorage) ListSecrets(ctx context.Context) ([]SecretInfo, error)
ListSecrets returns all secrets that have rotation metadata
func (*FileRotationStorage) StoreRotationResult ¶
func (f *FileRotationStorage) StoreRotationResult(ctx context.Context, result RotationResult) error
StoreRotationResult saves a rotation result to persistent storage
func (*FileRotationStorage) UpdateRotationStatus ¶
func (f *FileRotationStorage) UpdateRotationStatus(ctx context.Context, secret SecretInfo, status RotationStatusInfo) error
UpdateRotationStatus updates the current rotation status for a secret
type ImmediateRotationStrategy ¶
type ImmediateRotationStrategy struct {
// contains filtered or unexported fields
}
ImmediateRotationStrategy replaces secrets immediately without overlap This may cause brief downtime but is simpler and works with all providers
func NewImmediateRotationStrategy ¶
func NewImmediateRotationStrategy(baseRotator SecretValueRotator, logger *logging.Logger) *ImmediateRotationStrategy
NewImmediateRotationStrategy creates an immediate replacement strategy
func (*ImmediateRotationStrategy) GetStatus ¶
func (s *ImmediateRotationStrategy) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
GetStatus delegates to base rotator
func (*ImmediateRotationStrategy) Name ¶
func (s *ImmediateRotationStrategy) Name() string
Name returns the strategy name
func (*ImmediateRotationStrategy) Rollback ¶
func (s *ImmediateRotationStrategy) Rollback(ctx context.Context, request RollbackRequest) error
Rollback attempts to restore the previous value
func (*ImmediateRotationStrategy) Rotate ¶
func (s *ImmediateRotationStrategy) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
Rotate performs immediate secret replacement
func (*ImmediateRotationStrategy) SupportsSecret ¶
func (s *ImmediateRotationStrategy) SupportsSecret(ctx context.Context, secret SecretInfo) bool
SupportsSecret delegates to base rotator
func (*ImmediateRotationStrategy) Verify ¶
func (s *ImmediateRotationStrategy) Verify(ctx context.Context, request VerificationRequest) error
Verify delegates to base rotator
type MemoryRotationStorage ¶
type MemoryRotationStorage struct {
// contains filtered or unexported fields
}
MemoryRotationStorage implements RotationStorage using in-memory storage (for testing)
func NewMemoryRotationStorage ¶
func NewMemoryRotationStorage() *MemoryRotationStorage
NewMemoryRotationStorage creates a new in-memory rotation storage
func (*MemoryRotationStorage) Close ¶
func (m *MemoryRotationStorage) Close() error
Close closes any resources used by the storage
func (*MemoryRotationStorage) GetRotationHistory ¶
func (m *MemoryRotationStorage) GetRotationHistory(ctx context.Context, secret SecretInfo, limit int) ([]RotationResult, error)
GetRotationHistory retrieves rotation history for a secret
func (*MemoryRotationStorage) GetRotationStatus ¶
func (m *MemoryRotationStorage) GetRotationStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
GetRotationStatus gets the current rotation status for a secret
func (*MemoryRotationStorage) ListSecrets ¶
func (m *MemoryRotationStorage) ListSecrets(ctx context.Context) ([]SecretInfo, error)
ListSecrets returns all secrets that have rotation metadata
func (*MemoryRotationStorage) StoreRotationResult ¶
func (m *MemoryRotationStorage) StoreRotationResult(ctx context.Context, result RotationResult) error
StoreRotationResult saves a rotation result to memory
func (*MemoryRotationStorage) UpdateRotationStatus ¶
func (m *MemoryRotationStorage) UpdateRotationStatus(ctx context.Context, secret SecretInfo, status RotationStatusInfo) error
UpdateRotationStatus updates the current rotation status for a secret
type NewSecretValue ¶
type NewSecretValue struct {
Type ValueType `json:"type"`
Config map[string]interface{} `json:"config,omitempty"`
Value string `json:"value,omitempty"` // For literal values
}
NewSecretValue specifies how to generate the new secret value
type OverlapRotationStrategy ¶
type OverlapRotationStrategy struct {
// contains filtered or unexported fields
}
OverlapRotationStrategy creates new secrets with validity overlap Used for providers that support expiration dates (certificates, some API keys)
func NewOverlapRotationStrategy ¶
func NewOverlapRotationStrategy(baseRotator SecretValueRotator, logger *logging.Logger, overlapPeriod, totalValidity time.Duration) *OverlapRotationStrategy
NewOverlapRotationStrategy creates an overlap-based rotation strategy
func (*OverlapRotationStrategy) GetStatus ¶
func (s *OverlapRotationStrategy) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
GetStatus returns rotation status including validity periods
func (*OverlapRotationStrategy) Name ¶
func (s *OverlapRotationStrategy) Name() string
Name returns the strategy name
func (*OverlapRotationStrategy) Rollback ¶
func (s *OverlapRotationStrategy) Rollback(ctx context.Context, request RollbackRequest) error
Rollback during overlap means revoking the new secret
func (*OverlapRotationStrategy) Rotate ¶
func (s *OverlapRotationStrategy) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
Rotate performs rotation with overlap period
func (*OverlapRotationStrategy) SupportsSecret ¶
func (s *OverlapRotationStrategy) SupportsSecret(ctx context.Context, secret SecretInfo) bool
SupportsSecret checks if the secret and provider support overlap rotation
func (*OverlapRotationStrategy) Verify ¶
func (s *OverlapRotationStrategy) Verify(ctx context.Context, request VerificationRequest) error
Verify checks both old and new secrets during overlap
type PromoteRequest ¶
type PromoteRequest struct {
Secret SecretInfo `json:"secret"`
SecondaryRef SecretReference `json:"secondary_ref"`
GracePeriod time.Duration `json:"grace_period"`
VerifyFirst bool `json:"verify_first"`
}
PromoteRequest for promoting secondary to primary
type ProviderCapabilities ¶
type ProviderCapabilities struct {
// MaxActiveKeys is the maximum number of active keys (-1 for unlimited)
MaxActiveKeys int
// SupportsVersioning indicates if provider maintains version history
SupportsVersioning bool
// SupportsExpiration indicates if secrets can have expiration dates
SupportsExpiration bool
// SupportsRevocation indicates if secrets can be revoked immediately
SupportsRevocation bool
// SupportsMetadata indicates if secrets can have associated metadata
SupportsMetadata bool
// MinRotationInterval is the minimum time between rotations
MinRotationInterval time.Duration
// RecommendedStrategies lists strategies that work well with this provider
RecommendedStrategies []RotationStrategy
}
ProviderCapabilities describes what rotation features a provider supports
type RandomRotator ¶
type RandomRotator struct {
// contains filtered or unexported fields
}
RandomRotator implements a simple random value rotation for testing and generic use
func NewRandomRotator ¶
func NewRandomRotator(logger *logging.Logger) *RandomRotator
NewRandomRotator creates a new random rotation strategy
func (*RandomRotator) GetStatus ¶
func (r *RandomRotator) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
GetStatus returns the current rotation status for random values
func (*RandomRotator) Rollback ¶
func (r *RandomRotator) Rollback(ctx context.Context, request RollbackRequest) error
Rollback simulates rollback for random values
func (*RandomRotator) Rotate ¶
func (r *RandomRotator) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
Rotate generates a new random value and simulates rotation
func (*RandomRotator) SupportsSecret ¶
func (r *RandomRotator) SupportsSecret(ctx context.Context, secret SecretInfo) bool
SupportsSecret checks if this strategy can rotate the given secret
func (*RandomRotator) Verify ¶
func (r *RandomRotator) Verify(ctx context.Context, request VerificationRequest) error
Verify simulates verification of the random value
type RollbackRequest ¶
type RollbackRequest struct {
Secret SecretInfo `json:"secret"`
OldSecretRef SecretReference `json:"old_secret_ref"`
Reason string `json:"reason"`
}
RollbackRequest contains information for rolling back a rotation
type RotationConstraints ¶
type RotationConstraints struct {
MinRotationInterval time.Duration `json:"min_rotation_interval,omitempty"`
MaxValueLength int `json:"max_value_length,omitempty"`
MinValueLength int `json:"min_value_length,omitempty"`
AllowedCharsets []string `json:"allowed_charsets,omitempty"`
RequiredTests []VerificationTest `json:"required_tests,omitempty"`
GracePeriod time.Duration `json:"grace_period,omitempty"`
NotificationRequired bool `json:"notification_required,omitempty"`
}
RotationConstraints define limits and requirements for rotation
type RotationEngine ¶
type RotationEngine interface {
// RegisterStrategy adds a rotation strategy to the engine's registry.
//
// This method registers a new rotation strategy that can handle specific
// types of secrets or services. The strategy's Name() method must return
// a unique identifier that will be used to route rotation requests.
//
// Multiple strategies can be registered, and the engine will route requests
// based on the strategy name specified in RotationRequest.
//
// Returns error if:
// - A strategy with the same name is already registered
// - The strategy is nil or invalid
//
// Example:
//
// postgresRotator := &PostgreSQLRotator{}
// if err := engine.RegisterStrategy(postgresRotator); err != nil {
// return fmt.Errorf("failed to register postgres rotator: %w", err)
// }
RegisterStrategy(strategy SecretValueRotator) error
// GetStrategy returns a registered rotation strategy by name.
//
// This method looks up a previously registered strategy by its name.
// Used internally for request routing and can be used externally for
// direct strategy access or introspection.
//
// Returns error if no strategy with the given name is registered.
//
// Example:
//
// strategy, err := engine.GetStrategy("postgresql")
// if err != nil {
// return fmt.Errorf("postgresql strategy not available: %w", err)
// }
// if strategy.SupportsSecret(ctx, secretInfo) {
// // Use strategy for custom rotation logic
// }
GetStrategy(name string) (SecretValueRotator, error)
// ListStrategies returns names of all registered rotation strategies.
//
// This method provides discovery of available strategies for UI display,
// configuration validation, and operational introspection.
//
// Returns empty slice if no strategies are registered.
//
// Example:
//
// strategies := engine.ListStrategies()
// fmt.Printf("Available rotation strategies: %v\n", strategies)
// for _, name := range strategies {
// strategy, _ := engine.GetStrategy(name)
// caps := strategy.Capabilities() // If strategy implements capability interface
// }
ListStrategies() []string
// Rotate performs rotation using the appropriate strategy.
//
// This is the primary method for executing rotation operations. It:
// - Routes the request to the specified strategy
// - Validates the request and strategy compatibility
// - Executes the rotation with proper error handling
// - Records results in rotation history
// - Handles notifications if configured
//
// Returns RotationResult with detailed outcome information, including
// success/failure status, timing, verification results, and audit trail.
//
// Example:
//
// request := RotationRequest{
// Secret: SecretInfo{
// Key: "database_password",
// Provider: "postgresql",
// SecretType: SecretTypePassword,
// },
// Strategy: "postgresql",
// TwoSecret: true, // Zero-downtime rotation
// }
//
// result, err := engine.Rotate(ctx, request)
// if err != nil {
// return fmt.Errorf("rotation failed: %w", err)
// }
// if result.Status != StatusCompleted {
// return fmt.Errorf("rotation incomplete: %s", result.Error)
// }
Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
// BatchRotate rotates multiple secrets efficiently.
//
// This method processes multiple rotation requests, potentially in parallel,
// with proper error handling and coordination. It's more efficient than
// calling Rotate() multiple times for bulk operations.
//
// The method handles:
// - Parallel execution where safe
// - Dependency ordering (rotate A before B)
// - Error isolation (failure of one doesn't stop others)
// - Progress reporting for long-running operations
//
// Returns slice of RotationResult corresponding to input requests.
// Check individual results for per-secret success/failure status.
//
// Example:
//
// requests := []RotationRequest{
// {Secret: dbSecret, Strategy: "postgresql"},
// {Secret: apiSecret, Strategy: "stripe"},
// {Secret: certSecret, Strategy: "certificate"},
// }
//
// results, err := engine.BatchRotate(ctx, requests)
// if err != nil {
// return fmt.Errorf("batch rotation failed: %w", err)
// }
//
// for i, result := range results {
// if result.Status != StatusCompleted {
// log.Errorf("Request %d failed: %s", i, result.Error)
// }
// }
BatchRotate(ctx context.Context, requests []RotationRequest) ([]RotationResult, error)
// GetRotationHistory returns rotation history for a specific secret.
//
// This method retrieves historical rotation records for audit, monitoring,
// and troubleshooting purposes. History includes successful rotations,
// failures, and any rollback operations.
//
// The limit parameter controls how many historical records to return.
// Use 0 for no limit, positive number for most recent N records.
//
// Returns records in reverse chronological order (newest first).
//
// Example:
//
// // Get last 10 rotation attempts for this secret
// history, err := engine.GetRotationHistory(ctx, secretInfo, 10)
// if err != nil {
// return fmt.Errorf("failed to get history: %w", err)
// }
//
// for _, record := range history {
// fmt.Printf("%s: %s (status: %s)\n",
// record.RotatedAt.Format(time.RFC3339),
// record.Secret.Key,
// record.Status)
// }
GetRotationHistory(ctx context.Context, secret SecretInfo, limit int) ([]RotationResult, error)
// ScheduleRotation schedules a rotation for future execution.
//
// This method adds a rotation request to the scheduling system for execution
// at a specified time. Useful for planned maintenance windows, regular
// rotation cycles, and coordinated multi-secret rotations.
//
// The scheduling system should handle:
// - Persistent storage of scheduled operations
// - Reliable execution at the specified time
// - Retry logic for transient failures
// - Notification of schedule completion
//
// Example:
//
// // Schedule rotation for next maintenance window
// maintenanceWindow := time.Date(2024, 1, 15, 2, 0, 0, 0, time.UTC)
// request := RotationRequest{
// Secret: criticalSecret,
// Strategy: "postgresql",
// TwoSecret: true, // Zero downtime
// }
//
// if err := engine.ScheduleRotation(ctx, request, maintenanceWindow); err != nil {
// return fmt.Errorf("failed to schedule rotation: %w", err)
// }
ScheduleRotation(ctx context.Context, request RotationRequest, when time.Time) error
}
RotationEngine orchestrates rotation across multiple strategies.
The RotationEngine serves as the central coordination point for all secret rotation operations in dsops. It manages a registry of rotation strategies, routes rotation requests to appropriate strategies, and provides higher-level rotation operations like batch processing and scheduling.
Key responsibilities:
- Strategy registration and discovery
- Request routing based on secret type and strategy name
- Batch operations for rotating multiple secrets
- Rotation history tracking and retrieval
- Scheduling future rotations
- Error aggregation and reporting
Example usage:
// Create and configure engine
engine := NewRotationEngine()
// Register rotation strategies
engine.RegisterStrategy(&PostgreSQLRotator{})
engine.RegisterStrategy(&StripeRotator{})
engine.RegisterStrategy(&GenericAPIKeyRotator{})
// Perform single rotation
request := RotationRequest{
Secret: SecretInfo{Key: "db_password", Provider: "postgresql"},
Strategy: "postgresql",
}
result, err := engine.Rotate(ctx, request)
// Batch rotate multiple secrets
requests := []RotationRequest{dbRequest, apiRequest, certRequest}
results, err := engine.BatchRotate(ctx, requests)
// Schedule future rotation
nextWeek := time.Now().Add(7 * 24 * time.Hour)
err = engine.ScheduleRotation(ctx, request, nextWeek)
Example ¶
Example demonstrates rotation engine orchestrating multiple strategies
package main
import (
"context"
"fmt"
"log"
"sort"
"time"
"github.com/systmms/dsops/pkg/rotation"
)
func main() {
// Create rotation engine
engine := &MockRotationEngine{
strategies: make(map[string]rotation.SecretValueRotator),
}
// Register multiple rotation strategies
_ = engine.RegisterStrategy(&MockDatabaseRotator{name: "postgresql"})
_ = engine.RegisterStrategy(&MockAPIKeyRotator{name: "stripe"})
_ = engine.RegisterStrategy(&MockCertificateRotator{name: "tls-cert"})
// List available strategies
strategies := engine.ListStrategies()
fmt.Printf("Available strategies: %v\n", strategies)
// Create rotation requests for different secret types
dbRequest := rotation.RotationRequest{
Secret: rotation.SecretInfo{
Key: "db_password",
Provider: "postgresql",
SecretType: rotation.SecretTypePassword,
},
Strategy: "postgresql",
}
apiRequest := rotation.RotationRequest{
Secret: rotation.SecretInfo{
Key: "api_key",
Provider: "stripe",
SecretType: rotation.SecretTypeAPIKey,
},
Strategy: "stripe",
}
ctx := context.Background()
// Rotate database password
dbResult, err := engine.Rotate(ctx, dbRequest)
if err != nil {
log.Printf("Database rotation failed: %v", err)
} else {
fmt.Printf("Database rotation: %s\n", dbResult.Status)
}
// Rotate API key
apiResult, err := engine.Rotate(ctx, apiRequest)
if err != nil {
log.Printf("API rotation failed: %v", err)
} else {
fmt.Printf("API rotation: %s\n", apiResult.Status)
}
// Batch rotate multiple secrets
requests := []rotation.RotationRequest{dbRequest, apiRequest}
results, err := engine.BatchRotate(ctx, requests)
if err != nil {
log.Printf("Batch rotation failed: %v", err)
} else {
fmt.Printf("Batch rotation completed: %d results\n", len(results))
for i, result := range results {
fmt.Printf(" Request %d: %s\n", i+1, result.Status)
}
}
}
type MockDatabaseRotator struct {
name string
db *MockDatabase
}
func (r *MockDatabaseRotator) Name() string {
return r.name
}
func (r *MockDatabaseRotator) SupportsSecret(ctx context.Context, secret rotation.SecretInfo) bool {
return secret.SecretType == rotation.SecretTypePassword
}
func (r *MockDatabaseRotator) Rotate(ctx context.Context, request rotation.RotationRequest) (*rotation.RotationResult, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
verificationResults := []rotation.VerificationResult{
{
Test: rotation.VerificationTest{
Name: "connection_test",
Type: rotation.TestTypeConnection,
},
Status: rotation.TestStatusPassed,
Duration: 100 * time.Millisecond,
Message: "Database connection successful",
},
}
if request.Secret.Constraints != nil && len(request.Secret.Constraints.RequiredTests) > 1 {
verificationResults = append(verificationResults, rotation.VerificationResult{
Test: rotation.VerificationTest{
Name: "permission_test",
Type: rotation.TestTypeQuery,
},
Status: rotation.TestStatusPassed,
Duration: 50 * time.Millisecond,
Message: "Permission check successful",
})
}
result := &rotation.RotationResult{
Secret: request.Secret,
Status: rotation.StatusCompleted,
RotatedAt: &fixedTime,
VerificationResults: verificationResults,
AuditTrail: []rotation.AuditEntry{
{Timestamp: fixedTime, Action: "rotation_started", Status: "success"},
{Timestamp: fixedTime, Action: "password_generated", Status: "success"},
{Timestamp: fixedTime, Action: "database_updated", Status: "success"},
{Timestamp: fixedTime, Action: "verification_completed", Status: "success"},
},
}
return result, nil
}
func (r *MockDatabaseRotator) Verify(ctx context.Context, request rotation.VerificationRequest) error {
if request.NewSecretRef.Key == "new_password" {
return fmt.Errorf("connection test failed: mock connection error")
}
return nil
}
func (r *MockDatabaseRotator) Rollback(ctx context.Context, request rotation.RollbackRequest) error {
return nil
}
func (r *MockDatabaseRotator) GetStatus(ctx context.Context, secret rotation.SecretInfo) (*rotation.RotationStatusInfo, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationStatusInfo{
Status: rotation.StatusCompleted,
LastRotated: &fixedTime,
CanRotate: true,
}, nil
}
type MockAPIKeyRotator struct {
name string
}
func (r *MockAPIKeyRotator) Name() string {
return r.name
}
func (r *MockAPIKeyRotator) SupportsSecret(ctx context.Context, secret rotation.SecretInfo) bool {
return secret.SecretType == rotation.SecretTypeAPIKey
}
func (r *MockAPIKeyRotator) Rotate(ctx context.Context, request rotation.RotationRequest) (*rotation.RotationResult, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationResult{
Secret: request.Secret,
Status: rotation.StatusCompleted,
RotatedAt: &fixedTime,
}, nil
}
func (r *MockAPIKeyRotator) Verify(ctx context.Context, request rotation.VerificationRequest) error {
return nil
}
func (r *MockAPIKeyRotator) Rollback(ctx context.Context, request rotation.RollbackRequest) error {
return nil
}
func (r *MockAPIKeyRotator) GetStatus(ctx context.Context, secret rotation.SecretInfo) (*rotation.RotationStatusInfo, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationStatusInfo{
Status: rotation.StatusCompleted,
CanRotate: true,
LastRotated: &fixedTime,
}, nil
}
type MockCertificateRotator struct {
name string
}
func (r *MockCertificateRotator) Name() string {
return r.name
}
func (r *MockCertificateRotator) SupportsSecret(ctx context.Context, secret rotation.SecretInfo) bool {
return secret.SecretType == rotation.SecretTypeCertificate
}
func (r *MockCertificateRotator) Rotate(ctx context.Context, request rotation.RotationRequest) (*rotation.RotationResult, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationResult{
Secret: request.Secret,
Status: rotation.StatusCompleted,
RotatedAt: &fixedTime,
}, nil
}
func (r *MockCertificateRotator) Verify(ctx context.Context, request rotation.VerificationRequest) error {
return nil
}
func (r *MockCertificateRotator) Rollback(ctx context.Context, request rotation.RollbackRequest) error {
return nil
}
func (r *MockCertificateRotator) GetStatus(ctx context.Context, secret rotation.SecretInfo) (*rotation.RotationStatusInfo, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationStatusInfo{
Status: rotation.StatusCompleted,
CanRotate: true,
LastRotated: &fixedTime,
}, nil
}
type MockRotationEngine struct {
strategies map[string]rotation.SecretValueRotator
}
func (e *MockRotationEngine) RegisterStrategy(strategy rotation.SecretValueRotator) error {
e.strategies[strategy.Name()] = strategy
return nil
}
func (e *MockRotationEngine) GetStrategy(name string) (rotation.SecretValueRotator, error) {
strategy, exists := e.strategies[name]
if !exists {
return nil, fmt.Errorf("strategy not found: %s", name)
}
return strategy, nil
}
func (e *MockRotationEngine) ListStrategies() []string {
var names []string
for name := range e.strategies {
names = append(names, name)
}
sort.Strings(names)
return names
}
func (e *MockRotationEngine) Rotate(ctx context.Context, request rotation.RotationRequest) (*rotation.RotationResult, error) {
strategy, err := e.GetStrategy(request.Strategy)
if err != nil {
return nil, err
}
return strategy.Rotate(ctx, request)
}
func (e *MockRotationEngine) BatchRotate(ctx context.Context, requests []rotation.RotationRequest) ([]rotation.RotationResult, error) {
results := make([]rotation.RotationResult, len(requests))
for i, request := range requests {
result, err := e.Rotate(ctx, request)
if err != nil {
result = &rotation.RotationResult{
Secret: request.Secret,
Status: rotation.StatusFailed,
Error: err.Error(),
}
}
results[i] = *result
}
return results, nil
}
func (e *MockRotationEngine) GetRotationHistory(ctx context.Context, secret rotation.SecretInfo, limit int) ([]rotation.RotationResult, error) {
return []rotation.RotationResult{}, nil
}
func (e *MockRotationEngine) ScheduleRotation(ctx context.Context, request rotation.RotationRequest, when time.Time) error {
return nil
}
type MockDatabase struct {
_ [0]func()
}
Output: Available strategies: [postgresql stripe tls-cert] Database rotation: completed API rotation: completed Batch rotation completed: 2 results Request 1: completed Request 2: completed
type RotationRequest ¶
type RotationRequest struct {
// Secret contains detailed information about what secret to rotate.
Secret SecretInfo `json:"secret"`
// Strategy specifies which rotation strategy/rotator to use.
// Must match a registered rotator's Name() method.
Strategy string `json:"strategy"`
// NewValue specifies how to generate the new secret value.
// If nil, the rotator will use its default value generation.
NewValue *NewSecretValue `json:"new_value,omitempty"`
// DryRun indicates this is a planning/validation run that should not
// make any actual changes. Used for testing and verification.
DryRun bool `json:"dry_run"`
// Force bypasses safety checks and constraints (use with caution).
// This can skip minimum rotation intervals, verification failures, etc.
Force bool `json:"force"`
// TwoSecret enables two-key rotation strategy if the rotator supports it.
// This creates overlap periods for zero-downtime rotation.
TwoSecret bool `json:"two_secret"`
// Config provides additional configuration specific to the rotation
// strategy. Contents depend on the specific rotator being used.
Config map[string]interface{} `json:"config,omitempty"`
// Notification specifies who should be notified about rotation events.
// Format depends on notification system configuration.
Notification []string `json:"notification,omitempty"`
}
RotationRequest contains all information needed to perform a rotation.
This structure encapsulates a complete rotation operation including the secret to rotate, the strategy to use, and various options controlling the rotation process. It serves as the input to rotation operations.
Example:
request := RotationRequest{
Secret: SecretInfo{
Key: "api_key",
Provider: "stripe",
SecretType: SecretTypeAPIKey,
},
Strategy: "stripe",
DryRun: false,
Force: false,
TwoSecret: true, // Use two-key rotation for zero downtime
Config: map[string]interface{}{
"environment": "production",
"scopes": []string{"read", "write"},
},
Notification: []string{"ops-team", "on-call"},
}
type RotationResult ¶
type RotationResult struct {
// Secret identifies which secret was rotated.
Secret SecretInfo `json:"secret"`
// Status indicates the final state of the rotation operation.
Status RotationStatus `json:"status"`
// NewSecretRef points to the newly created/rotated secret version.
// Present for successful rotations.
NewSecretRef *SecretReference `json:"new_secret_ref,omitempty"`
// OldSecretRef points to the previous secret version that was replaced.
// Used for rollback operations if needed.
OldSecretRef *SecretReference `json:"old_secret_ref,omitempty"`
// RotatedAt indicates when the rotation was successfully completed.
// Nil for failed or incomplete rotations.
RotatedAt *time.Time `json:"rotated_at,omitempty"`
// ExpiresAt indicates when the rotated secret expires or should be rotated again.
// Used for scheduling future rotations.
ExpiresAt *time.Time `json:"expires_at,omitempty"`
// VerificationResults contains outcomes of all verification tests performed
// during the rotation to ensure the new secret works correctly.
VerificationResults []VerificationResult `json:"verification_results,omitempty"`
// Error contains error message if the rotation failed.
// Empty string indicates successful rotation.
Error string `json:"error,omitempty"`
// Warnings contains non-fatal issues encountered during rotation.
// These don't prevent success but should be reviewed.
Warnings []string `json:"warnings,omitempty"`
// AuditTrail provides detailed record of all actions taken during rotation.
// Used for compliance, debugging, and operational monitoring.
AuditTrail []AuditEntry `json:"audit_trail,omitempty"`
}
RotationResult contains the complete outcome of a rotation attempt.
This structure provides comprehensive information about what happened during rotation, including success/failure status, timing information, verification results, and detailed audit trails. It serves as the output from rotation operations and can be used for monitoring, alerting, and compliance.
Example:
result := &RotationResult{
Secret: secretInfo,
Status: StatusCompleted,
NewSecretRef: &SecretReference{
Provider: "aws.secretsmanager",
Key: "prod/db/password",
Version: "v2",
Identifier: "db_user_prod",
},
RotatedAt: &rotationTime,
ExpiresAt: &expirationTime,
VerificationResults: []VerificationResult{
{
Test: connectionTest,
Status: TestStatusPassed,
Duration: 1500 * time.Millisecond,
Message: "Database connection successful",
},
},
AuditTrail: []AuditEntry{
{Timestamp: time.Now(), Action: "rotation_started", Status: "success"},
{Timestamp: time.Now(), Action: "secret_generated", Status: "success"},
{Timestamp: time.Now(), Action: "service_updated", Status: "success"},
{Timestamp: time.Now(), Action: "verification_completed", Status: "success"},
},
}
type RotationStatus ¶
type RotationStatus string
RotationStatus represents the current state of secret rotation
const ( StatusPending RotationStatus = "pending" // Ready to rotate StatusRotating RotationStatus = "rotating" // In progress StatusVerifying RotationStatus = "verifying" // Verifying new value StatusCompleted RotationStatus = "completed" // Successfully rotated StatusFailed RotationStatus = "failed" // Rotation failed StatusRolledBack RotationStatus = "rolled_back" // Rolled back to old value StatusDeprecated RotationStatus = "deprecated" // Old version deprecated )
type RotationStatusInfo ¶
type RotationStatusInfo struct {
Status RotationStatus `json:"status"`
LastRotated *time.Time `json:"last_rotated,omitempty"`
NextRotation *time.Time `json:"next_rotation,omitempty"`
RotationVersion string `json:"rotation_version,omitempty"`
CanRotate bool `json:"can_rotate"`
Reason string `json:"reason,omitempty"`
}
RotationStatusInfo contains detailed information about rotation status
type RotationStorage ¶
type RotationStorage interface {
// StoreRotationResult saves a rotation result to persistent storage
StoreRotationResult(ctx context.Context, result RotationResult) error
// GetRotationHistory retrieves rotation history for a secret
GetRotationHistory(ctx context.Context, secret SecretInfo, limit int) ([]RotationResult, error)
// GetRotationStatus gets the current rotation status for a secret
GetRotationStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
// UpdateRotationStatus updates the current rotation status for a secret
UpdateRotationStatus(ctx context.Context, secret SecretInfo, status RotationStatusInfo) error
// ListSecrets returns all secrets that have rotation metadata
ListSecrets(ctx context.Context) ([]SecretInfo, error)
// Close closes any resources used by the storage
Close() error
}
RotationStorage provides persistent storage for rotation metadata and history
type RotationStrategy ¶
type RotationStrategy string
RotationStrategy defines the approach for rotating a secret
const ( // StrategyTwoKey maintains two active keys for zero-downtime rotation StrategyTwoKey RotationStrategy = "two-key" // StrategyVersioned creates new versions while old remain accessible StrategyVersioned RotationStrategy = "versioned" // StrategyImmediate replaces immediately (may cause downtime) StrategyImmediate RotationStrategy = "immediate" // StrategyOAuthRefresh uses refresh tokens for rotation StrategyOAuthRefresh RotationStrategy = "oauth-refresh" // StrategyOverlap creates new with validity overlap period StrategyOverlap RotationStrategy = "overlap" // StrategyEmergency for compromised secrets (immediate revoke) StrategyEmergency RotationStrategy = "emergency" )
type SchemaAwareRotator ¶
type SchemaAwareRotator interface {
// SetRepository sets the dsops-data repository for schema-aware rotation.
//
// This method provides access to the dsops-data repository containing
// community-maintained service definitions. Rotators implementing this
// interface can use these definitions to:
// - Determine supported credential types for services
// - Access rotation procedures and constraints
// - Use standardized verification tests
// - Follow service-specific best practices
//
// The repository is typically set once during rotator initialization
// and remains available for all subsequent rotation operations.
//
// Example:
//
// rotator := &GenericRotator{}
// repository, err := dsopsdata.LoadRepository("./dsops-data")
// if err != nil {
// return err
// }
//
// rotator.SetRepository(repository)
//
// // Rotator can now use service definitions
// result, err := rotator.Rotate(ctx, rotationRequest)
SetRepository(repository *dsopsdata.Repository)
}
SchemaAwareRotator defines strategies that can use dsops-data schema information.
This interface enables rotators to leverage community-maintained service definitions from the dsops-data repository. Instead of hardcoding service-specific rotation logic, rotators can use standardized schemas that define:
- Service connection patterns
- Credential types and formats
- Rotation procedures and constraints
- Verification tests and validation rules
Benefits of schema-aware rotation:
- Support hundreds of services without custom code
- Community-maintained and continuously updated
- Consistent rotation patterns across services
- Extensible through data-driven configuration
Example implementation:
type GenericSchemaRotator struct {
repository *dsopsdata.Repository
}
func (r *GenericSchemaRotator) SetRepository(repo *dsopsdata.Repository) {
r.repository = repo
}
func (r *GenericSchemaRotator) Rotate(ctx context.Context, req RotationRequest) (*RotationResult, error) {
// Get service definition from dsops-data
serviceDef, err := r.repository.GetServiceType(req.Secret.Provider)
if err != nil {
return nil, fmt.Errorf("unknown service type: %w", err)
}
// Use schema to determine rotation approach
switch serviceDef.RotationType {
case "api_key_rotation":
return r.rotateAPIKey(ctx, req, serviceDef)
case "password_rotation":
return r.rotatePassword(ctx, req, serviceDef)
default:
return nil, fmt.Errorf("unsupported rotation type: %s", serviceDef.RotationType)
}
}
Example ¶
Example demonstrates schema-aware rotation using dsops-data
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/systmms/dsops/internal/dsopsdata"
"github.com/systmms/dsops/pkg/rotation"
)
func main() {
// Create a generic rotator that uses dsops-data definitions
rotator := &MockSchemaAwareRotator{
name: "generic-data-driven",
}
// Mock dsops-data repository
postgresType := &dsopsdata.ServiceType{
APIVersion: "v1",
Kind: "ServiceType",
}
postgresType.Metadata.Name = "postgresql"
postgresType.Metadata.Description = "PostgreSQL Database"
postgresType.Metadata.Category = "database"
postgresType.Spec.CredentialKinds = []dsopsdata.CredentialKind{
{
Name: "password",
Description: "Database password",
Capabilities: []string{"read", "write"},
},
}
postgresType.Spec.Defaults.RotationStrategy = "two-key"
repository := &dsopsdata.Repository{
ServiceTypes: map[string]*dsopsdata.ServiceType{
"postgresql": postgresType,
},
}
// Set the repository for schema-aware operations
rotator.SetRepository(repository)
secretInfo := rotation.SecretInfo{
Key: "app_db_password",
Provider: "postgresql",
SecretType: rotation.SecretTypePassword,
}
ctx := context.Background()
// The rotator can now use service definitions from dsops-data
if rotator.SupportsSecret(ctx, secretInfo) {
fmt.Printf("Rotator supports %s using schema: %s\n",
secretInfo.Provider, "postgresql")
request := rotation.RotationRequest{
Secret: secretInfo,
Strategy: "generic-data-driven",
}
result, err := rotator.Rotate(ctx, request)
if err != nil {
log.Printf("Schema-aware rotation failed: %v", err)
} else {
fmt.Printf("Schema-aware rotation: %s\n", result.Status)
}
}
}
type MockSchemaAwareRotator struct {
name string
repository *dsopsdata.Repository
}
func (r *MockSchemaAwareRotator) Name() string {
return r.name
}
func (r *MockSchemaAwareRotator) SetRepository(repository *dsopsdata.Repository) {
r.repository = repository
}
func (r *MockSchemaAwareRotator) SupportsSecret(ctx context.Context, secret rotation.SecretInfo) bool {
if r.repository == nil {
return false
}
_, exists := r.repository.ServiceTypes[secret.Provider]
return exists
}
func (r *MockSchemaAwareRotator) Rotate(ctx context.Context, request rotation.RotationRequest) (*rotation.RotationResult, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationResult{
Secret: request.Secret,
Status: rotation.StatusCompleted,
RotatedAt: &fixedTime,
}, nil
}
func (r *MockSchemaAwareRotator) Verify(ctx context.Context, request rotation.VerificationRequest) error {
return nil
}
func (r *MockSchemaAwareRotator) Rollback(ctx context.Context, request rotation.RollbackRequest) error {
return nil
}
func (r *MockSchemaAwareRotator) GetStatus(ctx context.Context, secret rotation.SecretInfo) (*rotation.RotationStatusInfo, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationStatusInfo{
Status: rotation.StatusCompleted,
CanRotate: true,
LastRotated: &fixedTime,
}, nil
}
Output: Rotator supports postgresql using schema: postgresql Schema-aware rotation: completed
type SchemaMetadata ¶
type SchemaMetadata struct {
ServiceType string `json:"service_type,omitempty"`
CredentialKind string `json:"credential_kind,omitempty"`
Capabilities []string `json:"capabilities,omitempty"`
Constraints *CredentialConstraints `json:"constraints,omitempty"`
ServiceInstance *ServiceInstanceMeta `json:"service_instance,omitempty"`
}
SchemaMetadata contains information from dsops-data schemas
type ScriptInput ¶
type ScriptInput struct {
Action string `json:"action"`
SecretInfo SecretInfo `json:"secret_info"`
DryRun bool `json:"dry_run"`
Force bool `json:"force"`
NewValue *NewSecretValue `json:"new_value,omitempty"`
Config map[string]interface{} `json:"config,omitempty"`
Environment map[string]string `json:"environment,omitempty"`
SchemaMetadata *SchemaMetadata `json:"schema_metadata,omitempty"`
}
ScriptInput represents the JSON input passed to the script
type ScriptOutput ¶
type ScriptOutput struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
NewSecretRef *SecretReference `json:"new_secret_ref,omitempty"`
Error string `json:"error,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
ScriptOutput represents the expected JSON output from the script
type ScriptRotator ¶
type ScriptRotator struct {
// contains filtered or unexported fields
}
ScriptRotator implements rotation via custom scripts
func NewScriptRotator ¶
func NewScriptRotator(logger *logging.Logger) *ScriptRotator
NewScriptRotator creates a new custom script rotation strategy
func (*ScriptRotator) GetStatus ¶
func (s *ScriptRotator) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
GetStatus performs status check via custom script
func (*ScriptRotator) Rollback ¶
func (s *ScriptRotator) Rollback(ctx context.Context, request RollbackRequest) error
Rollback performs rollback via custom script
func (*ScriptRotator) Rotate ¶
func (s *ScriptRotator) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
Rotate performs rotation via custom script
func (*ScriptRotator) SetRepository ¶
func (s *ScriptRotator) SetRepository(repository *dsopsdata.Repository)
SetRepository sets the dsops-data repository for schema-aware rotation
func (*ScriptRotator) SupportsSecret ¶
func (s *ScriptRotator) SupportsSecret(ctx context.Context, secret SecretInfo) bool
SupportsSecret checks if this strategy can rotate the given secret
func (*ScriptRotator) Verify ¶
func (s *ScriptRotator) Verify(ctx context.Context, request VerificationRequest) error
Verify performs verification via custom script
type SecondarySecretRequest ¶
type SecondarySecretRequest struct {
Secret SecretInfo `json:"secret"`
NewValue *NewSecretValue `json:"new_value,omitempty"`
Config map[string]interface{} `json:"config,omitempty"`
}
SecondarySecretRequest for two-secret strategy
type SecretInfo ¶
type SecretInfo struct {
// Key is the logical name/identifier for this secret in the configuration.
// Used for logging, error messages, and referencing in rotation policies.
Key string `json:"key"`
// Provider identifies which secret store or service manages this secret.
// Examples: "aws.secretsmanager", "onepassword", "postgresql", "stripe"
Provider string `json:"provider"`
// ProviderRef contains the specific reference information needed to
// locate and retrieve this secret from the provider.
ProviderRef provider.Reference `json:"provider_ref"`
// SecretType categorizes what kind of secret this is, which determines
// the appropriate rotation strategy and validation procedures.
SecretType SecretType `json:"secret_type"`
// Metadata contains additional context about the secret, its usage,
// and rotation requirements. Common keys include:
// - "environment": deployment environment (prod, staging, dev)
// - "service": which service uses this secret
// - "owner": team or individual responsible
// - "criticality": importance level (high, medium, low)
Metadata map[string]string `json:"metadata"`
// Constraints define rotation policies, limits, and requirements
// specific to this secret. Nil if no special constraints apply.
Constraints *RotationConstraints `json:"constraints,omitempty"`
}
SecretInfo contains comprehensive information about the secret to be rotated.
This structure provides all the context needed for rotation strategies to:
- Identify the specific secret and its location
- Understand the type of secret and its constraints
- Access metadata for rotation planning
- Apply appropriate rotation policies
Example:
secretInfo := SecretInfo{
Key: "database_password",
Provider: "aws.secretsmanager",
ProviderRef: provider.Reference{
Provider: "aws.secretsmanager",
Key: "prod/db/password",
Version: "AWSCURRENT",
},
SecretType: SecretTypePassword,
Metadata: map[string]string{
"database": "postgresql",
"environment": "production",
"owner": "platform-team",
},
Constraints: &RotationConstraints{
MinRotationInterval: 7 * 24 * time.Hour, // Weekly minimum
MinValueLength: 16,
RequiredTests: []VerificationTest{
{Name: "connection", Type: TestTypeConnection, Required: true},
},
},
}
type SecretReference ¶
type SecretReference struct {
Provider string `json:"provider"`
Key string `json:"key"`
Version string `json:"version,omitempty"`
Identifier string `json:"identifier,omitempty"` // DB user, API key ID, etc.
Metadata map[string]string `json:"metadata,omitempty"`
}
SecretReference points to a specific version/instance of a secret
type SecretType ¶
type SecretType string
SecretType categorizes the type of secret being rotated
const ( SecretTypePassword SecretType = "password" // Database passwords SecretTypeAPIKey SecretType = "api_key" // API keys, tokens SecretTypeCertificate SecretType = "certificate" // TLS certificates SecretTypeOAuth SecretType = "oauth" // OAuth tokens, client secrets SecretTypeEncryption SecretType = "encryption" // Application encryption keys SecretTypeGeneric SecretType = "generic" // Other secrets )
type SecretValueRotator ¶
type SecretValueRotator interface {
// Name returns the unique strategy identifier.
//
// This should be a stable, lowercase identifier used to identify the rotator
// in configuration files and CLI commands. Examples: "postgres", "stripe",
// "github", "generic-api", "certificate".
//
// The name is used for:
// - Strategy registration and lookup
// - Configuration file references
// - Logging and audit trails
// - CLI command arguments
Name() string
// SupportsSecret determines if this rotator can handle the given secret.
//
// This method allows rotators to indicate which types of secrets they can
// rotate based on the secret's type, provider, metadata, or other attributes.
// The rotation engine uses this to select the appropriate rotator for each
// secret.
//
// Common criteria for support:
// - Secret type (password, api_key, certificate, etc.)
// - Provider compatibility
// - Service-specific metadata
// - Required configuration presence
//
// Example:
//
// func (r *PostgresRotator) SupportsSecret(ctx context.Context, secret SecretInfo) bool {
// // Only handle password secrets
// if secret.SecretType != SecretTypePassword {
// return false
// }
//
// // Check for required metadata
// if secret.Metadata["database_type"] != "postgresql" {
// return false
// }
//
// return true
// }
SupportsSecret(ctx context.Context, secret SecretInfo) bool
// Rotate performs the complete secret rotation lifecycle.
//
// This is the core method that orchestrates the entire rotation process:
// 1. Generate or retrieve new secret value
// 2. Update the target service with new credentials
// 3. Verify the new credentials work correctly
// 4. Update secret storage with new value
// 5. Clean up old credentials
// 6. Generate audit trail
//
// The method should handle errors gracefully and provide detailed results
// including timing information, verification results, and audit entries.
//
// For dry-run requests (request.DryRun = true), the method should:
// - Validate the rotation request
// - Check preconditions
// - Return what would be done without executing
//
// Example:
//
// func (r *PostgresRotator) Rotate(ctx context.Context, req RotationRequest) (*RotationResult, error) {
// if req.DryRun {
// return r.planRotation(ctx, req)
// }
//
// startTime := time.Now()
// result := &RotationResult{
// Secret: req.Secret,
// Status: StatusRotating,
// }
//
// // Generate new password
// newPassword, err := r.generatePassword(req)
// if err != nil {
// result.Status = StatusFailed
// result.Error = err.Error()
// return result, err
// }
//
// // Update database user
// if err := r.updateDatabasePassword(ctx, newPassword); err != nil {
// result.Status = StatusFailed
// result.Error = err.Error()
// return result, err
// }
//
// // Verify connection works
// if err := r.verifyConnection(ctx, newPassword); err != nil {
// // Attempt rollback
// r.rollbackPassword(ctx, req.Secret)
// result.Status = StatusFailed
// result.Error = err.Error()
// return result, err
// }
//
// // Update secret store
// if err := r.updateSecretStore(ctx, req, newPassword); err != nil {
// result.Status = StatusFailed
// result.Error = err.Error()
// return result, err
// }
//
// now := time.Now()
// result.Status = StatusCompleted
// result.RotatedAt = &now
// result.AuditTrail = r.generateAuditTrail(startTime, now)
//
// return result, nil
// }
Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
// Verify checks that new secret credentials are working correctly.
//
// This method performs validation tests to ensure that newly rotated
// credentials are functional before completing the rotation process.
// It's typically called during the Rotate method, but can also be
// invoked independently for verification.
//
// The verification request includes test specifications that define
// what checks to perform. Common verification tests:
// - Connection tests (can we connect with new credentials?)
// - Query tests (can we perform required operations?)
// - API tests (do API calls succeed with new tokens?)
// - Permission tests (do we have required access levels?)
//
// Example:
//
// func (r *PostgresRotator) Verify(ctx context.Context, req VerificationRequest) error {
// for _, test := range req.Tests {
// switch test.Type {
// case TestTypeConnection:
// if err := r.testConnection(ctx, req.NewSecretRef); err != nil {
// return fmt.Errorf("connection test failed: %w", err)
// }
//
// case TestTypeQuery:
// if err := r.testQuery(ctx, req.NewSecretRef, test.Config); err != nil {
// return fmt.Errorf("query test failed: %w", err)
// }
// }
// }
// return nil
// }
Verify(ctx context.Context, request VerificationRequest) error
// Rollback reverts to the previous secret value if possible.
//
// This method attempts to restore service functionality by reverting to
// a previous secret value when rotation fails or new credentials don't
// work as expected. Not all services support rollback - some may require
// manual intervention.
//
// Rollback scenarios:
// - New credentials fail verification
// - Service rejects new credentials after deployment
// - Manual rollback requested by operator
// - Rotation process encounters fatal error
//
// The method should:
// - Restore the service to use old credentials
// - Update secret storage if necessary
// - Generate audit trail for rollback operation
// - Return error if rollback is not possible
//
// Example:
//
// func (r *PostgresRotator) Rollback(ctx context.Context, req RollbackRequest) error {
// // Get old password from secret reference
// oldPassword, err := r.getSecretValue(ctx, req.OldSecretRef)
// if err != nil {
// return fmt.Errorf("cannot retrieve old password for rollback: %w", err)
// }
//
// // Restore database user password
// if err := r.updateDatabasePassword(ctx, oldPassword); err != nil {
// return fmt.Errorf("rollback failed: %w", err)
// }
//
// // Verify rollback worked
// if err := r.verifyConnection(ctx, oldPassword); err != nil {
// return fmt.Errorf("rollback verification failed: %w", err)
// }
//
// r.logRollback(req.Secret, req.Reason)
// return nil
// }
Rollback(ctx context.Context, request RollbackRequest) error
// GetStatus returns the current rotation status and metadata for a secret.
//
// This method provides information about when a secret was last rotated,
// when it should next be rotated, and whether rotation is currently possible.
// Used by status commands and rotation scheduling.
//
// The returned status should include:
// - Current rotation state
// - Last rotation timestamp
// - Next scheduled rotation time
// - Whether rotation is currently allowed
// - Any blocking conditions or reasons
//
// Example:
//
// func (r *PostgresRotator) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error) {
// // Check rotation history
// lastRotation, err := r.getLastRotationTime(ctx, secret)
// if err != nil {
// return nil, err
// }
//
// // Calculate next rotation time
// nextRotation := lastRotation.Add(r.getRotationInterval(secret))
//
// // Check if rotation is currently possible
// canRotate, reason := r.canRotateNow(ctx, secret)
//
// return &RotationStatusInfo{
// Status: StatusCompleted,
// LastRotated: &lastRotation,
// NextRotation: &nextRotation,
// CanRotate: canRotate,
// Reason: reason,
// }, nil
// }
GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
}
SecretValueRotator defines the interface for rotating actual secret values.
This interface is implemented by rotation strategies that handle the process of updating secret values within services (databases, APIs, etc.) and coordinating the update with secret storage systems.
Implementations should handle the complete rotation lifecycle:
- Generate or obtain new secret values
- Update the target service with new credentials
- Verify the new credentials work correctly
- Update the secret storage system
- Clean up old credentials
- Handle rollback if anything fails
Thread safety: Implementations must be thread-safe as multiple rotations may occur concurrently for different secrets.
Example (Basic) ¶
Example demonstrates basic secret rotation using a simple rotator
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/systmms/dsops/pkg/provider"
"github.com/systmms/dsops/pkg/rotation"
)
func main() {
// Create a mock rotator for demonstration
rotator := &MockDatabaseRotator{
name: "postgresql",
db: &MockDatabase{},
}
// Create rotation request
secretInfo := rotation.SecretInfo{
Key: "database_password",
Provider: "postgresql",
ProviderRef: provider.Reference{
Provider: "aws.secretsmanager",
Key: "prod/db/password",
},
SecretType: rotation.SecretTypePassword,
Metadata: map[string]string{
"database": "production",
"environment": "prod",
},
}
request := rotation.RotationRequest{
Secret: secretInfo,
Strategy: "postgresql",
DryRun: false,
Force: false,
}
ctx := context.Background()
// Check if rotator supports this secret
if !rotator.SupportsSecret(ctx, secretInfo) {
log.Fatalf("Rotator does not support this secret type")
}
// Perform the rotation
result, err := rotator.Rotate(ctx, request)
if err != nil {
log.Fatalf("Rotation failed: %v", err)
}
fmt.Printf("Rotation status: %s\n", result.Status)
fmt.Printf("Rotated at: %s\n", result.RotatedAt.Format("2006-01-02 15:04"))
fmt.Printf("Verification tests passed: %d\n", len(result.VerificationResults))
fmt.Printf("Audit trail entries: %d\n", len(result.AuditTrail))
}
type MockDatabaseRotator struct {
name string
db *MockDatabase
}
func (r *MockDatabaseRotator) Name() string {
return r.name
}
func (r *MockDatabaseRotator) SupportsSecret(ctx context.Context, secret rotation.SecretInfo) bool {
return secret.SecretType == rotation.SecretTypePassword
}
func (r *MockDatabaseRotator) Rotate(ctx context.Context, request rotation.RotationRequest) (*rotation.RotationResult, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
verificationResults := []rotation.VerificationResult{
{
Test: rotation.VerificationTest{
Name: "connection_test",
Type: rotation.TestTypeConnection,
},
Status: rotation.TestStatusPassed,
Duration: 100 * time.Millisecond,
Message: "Database connection successful",
},
}
if request.Secret.Constraints != nil && len(request.Secret.Constraints.RequiredTests) > 1 {
verificationResults = append(verificationResults, rotation.VerificationResult{
Test: rotation.VerificationTest{
Name: "permission_test",
Type: rotation.TestTypeQuery,
},
Status: rotation.TestStatusPassed,
Duration: 50 * time.Millisecond,
Message: "Permission check successful",
})
}
result := &rotation.RotationResult{
Secret: request.Secret,
Status: rotation.StatusCompleted,
RotatedAt: &fixedTime,
VerificationResults: verificationResults,
AuditTrail: []rotation.AuditEntry{
{Timestamp: fixedTime, Action: "rotation_started", Status: "success"},
{Timestamp: fixedTime, Action: "password_generated", Status: "success"},
{Timestamp: fixedTime, Action: "database_updated", Status: "success"},
{Timestamp: fixedTime, Action: "verification_completed", Status: "success"},
},
}
return result, nil
}
func (r *MockDatabaseRotator) Verify(ctx context.Context, request rotation.VerificationRequest) error {
if request.NewSecretRef.Key == "new_password" {
return fmt.Errorf("connection test failed: mock connection error")
}
return nil
}
func (r *MockDatabaseRotator) Rollback(ctx context.Context, request rotation.RollbackRequest) error {
return nil
}
func (r *MockDatabaseRotator) GetStatus(ctx context.Context, secret rotation.SecretInfo) (*rotation.RotationStatusInfo, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationStatusInfo{
Status: rotation.StatusCompleted,
LastRotated: &fixedTime,
CanRotate: true,
}, nil
}
type MockDatabase struct {
_ [0]func()
}
Output: Rotation status: completed Rotated at: 2025-11-15 12:00 Verification tests passed: 1 Audit trail entries: 4
Example (Verification) ¶
Example demonstrates comprehensive verification and rollback
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/systmms/dsops/pkg/rotation"
)
func main() {
rotator := &MockDatabaseRotator{
name: "postgresql-with-verification",
db: &MockDatabase{},
}
secretInfo := rotation.SecretInfo{
Key: "verified_password",
Provider: "postgresql",
SecretType: rotation.SecretTypePassword,
Constraints: &rotation.RotationConstraints{
RequiredTests: []rotation.VerificationTest{
{
Name: "connection_test",
Type: rotation.TestTypeConnection,
Required: true,
Timeout: 30 * time.Second,
},
{
Name: "permission_test",
Type: rotation.TestTypeQuery,
Required: true,
Config: map[string]interface{}{
"query": "SELECT 1",
},
},
},
},
}
ctx := context.Background()
// First, simulate a rotation that will fail verification
fmt.Println("Attempting rotation with failing verification...")
// Mock a verification request that will fail
verifyRequest := rotation.VerificationRequest{
Secret: secretInfo,
NewSecretRef: rotation.SecretReference{
Provider: "postgresql",
Key: "new_password",
},
Tests: secretInfo.Constraints.RequiredTests,
}
err := rotator.Verify(ctx, verifyRequest)
if err != nil {
fmt.Printf("Verification failed: %v\n", err)
// Rollback to previous secret
rollbackRequest := rotation.RollbackRequest{
Secret: secretInfo,
OldSecretRef: rotation.SecretReference{
Provider: "postgresql",
Key: "old_password",
},
Reason: "verification_failure",
}
fmt.Println("Rolling back to previous secret...")
if err := rotator.Rollback(ctx, rollbackRequest); err != nil {
log.Printf("Rollback failed: %v", err)
} else {
fmt.Println("Rollback successful")
}
}
// Now demonstrate successful rotation
fmt.Println("\nAttempting rotation with passing verification...")
request := rotation.RotationRequest{
Secret: secretInfo,
Strategy: "postgresql-with-verification",
}
result, err := rotator.Rotate(ctx, request)
if err != nil {
log.Printf("Rotation failed: %v", err)
} else {
fmt.Printf("Rotation successful: %s\n", result.Status)
// Show verification results
for _, vResult := range result.VerificationResults {
fmt.Printf("Test %s: %s (duration: %s)\n",
vResult.Test.Name, vResult.Status, vResult.Duration)
}
}
}
type MockDatabaseRotator struct {
name string
db *MockDatabase
}
func (r *MockDatabaseRotator) Name() string {
return r.name
}
func (r *MockDatabaseRotator) SupportsSecret(ctx context.Context, secret rotation.SecretInfo) bool {
return secret.SecretType == rotation.SecretTypePassword
}
func (r *MockDatabaseRotator) Rotate(ctx context.Context, request rotation.RotationRequest) (*rotation.RotationResult, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
verificationResults := []rotation.VerificationResult{
{
Test: rotation.VerificationTest{
Name: "connection_test",
Type: rotation.TestTypeConnection,
},
Status: rotation.TestStatusPassed,
Duration: 100 * time.Millisecond,
Message: "Database connection successful",
},
}
if request.Secret.Constraints != nil && len(request.Secret.Constraints.RequiredTests) > 1 {
verificationResults = append(verificationResults, rotation.VerificationResult{
Test: rotation.VerificationTest{
Name: "permission_test",
Type: rotation.TestTypeQuery,
},
Status: rotation.TestStatusPassed,
Duration: 50 * time.Millisecond,
Message: "Permission check successful",
})
}
result := &rotation.RotationResult{
Secret: request.Secret,
Status: rotation.StatusCompleted,
RotatedAt: &fixedTime,
VerificationResults: verificationResults,
AuditTrail: []rotation.AuditEntry{
{Timestamp: fixedTime, Action: "rotation_started", Status: "success"},
{Timestamp: fixedTime, Action: "password_generated", Status: "success"},
{Timestamp: fixedTime, Action: "database_updated", Status: "success"},
{Timestamp: fixedTime, Action: "verification_completed", Status: "success"},
},
}
return result, nil
}
func (r *MockDatabaseRotator) Verify(ctx context.Context, request rotation.VerificationRequest) error {
if request.NewSecretRef.Key == "new_password" {
return fmt.Errorf("connection test failed: mock connection error")
}
return nil
}
func (r *MockDatabaseRotator) Rollback(ctx context.Context, request rotation.RollbackRequest) error {
return nil
}
func (r *MockDatabaseRotator) GetStatus(ctx context.Context, secret rotation.SecretInfo) (*rotation.RotationStatusInfo, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationStatusInfo{
Status: rotation.StatusCompleted,
LastRotated: &fixedTime,
CanRotate: true,
}, nil
}
type MockDatabase struct {
_ [0]func()
}
Output: Attempting rotation with failing verification... Verification failed: connection test failed: mock connection error Rolling back to previous secret... Rollback successful Attempting rotation with passing verification... Rotation successful: completed Test connection_test: passed (duration: 100ms) Test permission_test: passed (duration: 50ms)
type ServiceInstanceMeta ¶
type ServiceInstanceMeta struct {
Type string `json:"type"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
Endpoint string `json:"endpoint"`
Auth string `json:"auth"`
Config map[string]interface{} `json:"config,omitempty"`
}
ServiceInstanceMeta contains relevant service instance metadata
type StoredRotationResult ¶
type StoredRotationResult struct {
RotationResult
StoredAt time.Time `json:"stored_at"`
ID string `json:"id"`
}
StoredRotationResult extends RotationResult with storage metadata
type StoredRotationStatus ¶
type StoredRotationStatus struct {
SecretKey string `json:"secret_key"`
Provider string `json:"provider"`
Status RotationStatusInfo `json:"status"`
UpdatedAt time.Time `json:"updated_at"`
}
StoredRotationStatus represents stored rotation status
type StrategyRegistry ¶
type StrategyRegistry struct {
// contains filtered or unexported fields
}
StrategyRegistry manages available rotation strategies
func NewStrategyRegistry ¶
func NewStrategyRegistry(logger *logging.Logger) *StrategyRegistry
NewStrategyRegistry creates a new strategy registry
func (*StrategyRegistry) CreateStrategy ¶
func (r *StrategyRegistry) CreateStrategy(name string) (SecretValueRotator, error)
CreateStrategy creates a new instance of the specified strategy
func (*StrategyRegistry) HasStrategy ¶
func (r *StrategyRegistry) HasStrategy(name string) bool
HasStrategy checks if a strategy is available
func (*StrategyRegistry) ListStrategies ¶
func (r *StrategyRegistry) ListStrategies() []string
ListStrategies returns all available strategy names
func (*StrategyRegistry) RegisterCustomStrategy ¶
func (r *StrategyRegistry) RegisterCustomStrategy(name string, factory func(*logging.Logger) SecretValueRotator) error
RegisterCustomStrategy allows registration of custom strategies
type StrategySelector ¶
type StrategySelector interface {
// SelectStrategy chooses the best strategy based on provider capabilities
SelectStrategy(ctx context.Context, secret SecretInfo, capabilities ProviderCapabilities) (RotationStrategy, error)
// ValidateStrategy checks if a strategy is compatible with provider
ValidateStrategy(strategy RotationStrategy, capabilities ProviderCapabilities) error
}
StrategySelector helps choose the best rotation strategy
type TestStatus ¶
type TestStatus string
TestStatus represents the result of a verification test
const ( TestStatusPassed TestStatus = "passed" TestStatusFailed TestStatus = "failed" TestStatusSkipped TestStatus = "skipped" TestStatusTimeout TestStatus = "timeout" )
type TestType ¶
type TestType string
TestType specifies the type of verification test
const ( TestTypeConnection TestType = "connection" // Test database/service connection TestTypeQuery TestType = "query" // Execute test query/request TestTypeAPI TestType = "api" // Test API endpoint TestTypePing TestType = "ping" // Simple connectivity test TestTypeCustom TestType = "custom" // Custom test script )
type TwoSecretMetadata ¶
type TwoSecretMetadata struct {
ActiveSecret SecretReference `json:"active_secret"`
InactiveSecret *SecretReference `json:"inactive_secret,omitempty"`
LastRotated *time.Time `json:"last_rotated,omitempty"`
NextRotation *time.Time `json:"next_rotation,omitempty"`
GracePeriod time.Duration `json:"grace_period"`
}
TwoSecretMetadata contains information about the two-secret setup
type TwoSecretRotator ¶
type TwoSecretRotator interface {
SecretValueRotator
// CreateSecondarySecret creates the inactive/secondary secret.
//
// This method creates a new version of the secret without affecting the
// current active version. The secondary secret should have the same
// permissions and access as the primary but with a new value.
//
// For databases, this might create a new user with identical permissions.
// For API services, this might generate a new API key while keeping the old one active.
// For certificates, this might issue a new certificate with the same subject.
//
// The returned SecretReference points to the newly created secondary secret
// and can be used in subsequent promote/deprecate operations.
//
// Example:
//
// req := SecondarySecretRequest{
// Secret: SecretInfo{
// Key: "api_key",
// Provider: "stripe",
// SecretType: SecretTypeAPIKey,
// },
// NewValue: &NewSecretValue{
// Type: ValueTypeGenerated,
// },
// }
//
// secondaryRef, err := rotator.CreateSecondarySecret(ctx, req)
// if err != nil {
// return fmt.Errorf("failed to create secondary secret: %w", err)
// }
CreateSecondarySecret(ctx context.Context, request SecondarySecretRequest) (*SecretReference, error)
// PromoteSecondarySecret makes the secondary secret the active/primary secret.
//
// This method transitions the system to use the secondary secret as the primary.
// After promotion, new connections and operations should use the secondary
// secret, but existing connections using the old primary may continue to work
// during the grace period.
//
// The implementation should:
// - Update service configuration to use secondary as primary
// - Maintain backward compatibility during grace period
// - Verify the promotion was successful
// - Roll back if promotion fails
//
// If VerifyFirst is true, the method should test the secondary secret
// before promoting it to avoid promoting non-functional credentials.
//
// Example:
//
// req := PromoteRequest{
// Secret: secretInfo,
// SecondaryRef: secondaryRef,
// GracePeriod: 5 * time.Minute,
// VerifyFirst: true,
// }
//
// if err := rotator.PromoteSecondarySecret(ctx, req); err != nil {
// return fmt.Errorf("failed to promote secondary secret: %w", err)
// }
PromoteSecondarySecret(ctx context.Context, request PromoteRequest) error
// DeprecatePrimarySecret marks the old primary secret as deprecated.
//
// This method is called after successful promotion and grace period to
// clean up the old primary secret. Depending on the service and HardDelete
// setting, this may:
// - Delete the old secret entirely
// - Mark it as inactive but retain for audit/rollback
// - Reduce its permissions or scope
// - Move it to a "deprecated" state
//
// The grace period allows time for:
// - Existing connections to naturally close
// - All services to pick up the new primary
// - Verification that no systems are still using old primary
//
// If HardDelete is false, the old secret should be retained in a disabled
// state for potential rollback scenarios.
//
// Example:
//
// req := DeprecateRequest{
// Secret: secretInfo,
// OldRef: oldPrimaryRef,
// GracePeriod: 10 * time.Minute,
// HardDelete: false, // Keep for rollback
// }
//
// if err := rotator.DeprecatePrimarySecret(ctx, req); err != nil {
// log.Warnf("Failed to deprecate old primary: %v", err)
// // This may not be fatal - new primary is already active
// }
DeprecatePrimarySecret(ctx context.Context, request DeprecateRequest) error
}
TwoSecretRotator extends SecretValueRotator for zero-downtime rotation.
This interface enables the two-key rotation strategy where both old and new credentials are valid simultaneously during the rotation process. This eliminates downtime by ensuring services can continue using old credentials while new ones are being deployed and verified.
The two-secret rotation process follows these phases:
- Create secondary (new) secret alongside existing primary
- Deploy and verify secondary secret works in all systems
- Promote secondary to primary (make it the active credential)
- Deprecate old primary after grace period
This strategy is ideal for:
- High-availability services that cannot tolerate downtime
- Complex distributed systems with multiple deployment phases
- Services where credential propagation takes time
- Systems requiring validation periods before full cutover
Example implementation:
type DatabaseTwoKeyRotator struct {
client DatabaseClient
}
func (r *DatabaseTwoKeyRotator) CreateSecondarySecret(ctx context.Context, req SecondarySecretRequest) (*SecretReference, error) {
// Generate new password
newPassword, err := r.generatePassword()
if err != nil {
return nil, err
}
// Create new database user with same permissions
newUsername := req.Secret.Key + "_new"
if err := r.client.CreateUser(ctx, newUsername, newPassword); err != nil {
return nil, err
}
return &SecretReference{
Provider: req.Secret.Provider,
Key: newUsername,
Identifier: newUsername,
}, nil
}
Example ¶
Example demonstrates two-secret rotation for zero-downtime
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/systmms/dsops/pkg/rotation"
)
func main() {
// Create a rotator that supports two-secret strategy
rotator := &MockTwoSecretRotator{
MockDatabaseRotator: MockDatabaseRotator{
name: "postgresql-zero-downtime",
db: &MockDatabase{},
},
secondarySecrets: make(map[string]string),
}
secretInfo := rotation.SecretInfo{
Key: "critical_db_password",
Provider: "postgresql",
SecretType: rotation.SecretTypePassword,
Metadata: map[string]string{
"criticality": "high",
"downtime": "not-acceptable",
},
}
ctx := context.Background()
// Phase 1: Create secondary secret
secondaryRequest := rotation.SecondarySecretRequest{
Secret: secretInfo,
NewValue: &rotation.NewSecretValue{
Type: rotation.ValueTypeRandom,
},
}
fmt.Println("Phase 1: Creating secondary secret...")
secondaryRef, err := rotator.CreateSecondarySecret(ctx, secondaryRequest)
if err != nil {
log.Fatalf("Failed to create secondary secret: %v", err)
}
fmt.Printf("Secondary secret created: %s\n", secondaryRef.Identifier)
// Phase 2: Promote secondary to primary (after verification)
promoteRequest := rotation.PromoteRequest{
Secret: secretInfo,
SecondaryRef: *secondaryRef,
GracePeriod: 5 * time.Minute,
VerifyFirst: true,
}
fmt.Println("Phase 2: Promoting secondary to primary...")
err = rotator.PromoteSecondarySecret(ctx, promoteRequest)
if err != nil {
log.Fatalf("Failed to promote secondary secret: %v", err)
}
fmt.Println("Secondary promoted to primary")
// Phase 3: Deprecate old primary (after grace period)
deprecateRequest := rotation.DeprecateRequest{
Secret: secretInfo,
OldRef: rotation.SecretReference{
Provider: "postgresql",
Key: "old_primary",
Identifier: "old_db_user",
},
GracePeriod: 1 * time.Minute,
HardDelete: false, // Keep for rollback
}
fmt.Println("Phase 3: Deprecating old primary...")
err = rotator.DeprecatePrimarySecret(ctx, deprecateRequest)
if err != nil {
log.Printf("Warning: Failed to deprecate old primary: %v", err)
} else {
fmt.Println("Old primary deprecated")
}
fmt.Println("Zero-downtime rotation completed!")
}
type MockDatabaseRotator struct {
name string
db *MockDatabase
}
func (r *MockDatabaseRotator) Name() string {
return r.name
}
func (r *MockDatabaseRotator) SupportsSecret(ctx context.Context, secret rotation.SecretInfo) bool {
return secret.SecretType == rotation.SecretTypePassword
}
func (r *MockDatabaseRotator) Rotate(ctx context.Context, request rotation.RotationRequest) (*rotation.RotationResult, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
verificationResults := []rotation.VerificationResult{
{
Test: rotation.VerificationTest{
Name: "connection_test",
Type: rotation.TestTypeConnection,
},
Status: rotation.TestStatusPassed,
Duration: 100 * time.Millisecond,
Message: "Database connection successful",
},
}
if request.Secret.Constraints != nil && len(request.Secret.Constraints.RequiredTests) > 1 {
verificationResults = append(verificationResults, rotation.VerificationResult{
Test: rotation.VerificationTest{
Name: "permission_test",
Type: rotation.TestTypeQuery,
},
Status: rotation.TestStatusPassed,
Duration: 50 * time.Millisecond,
Message: "Permission check successful",
})
}
result := &rotation.RotationResult{
Secret: request.Secret,
Status: rotation.StatusCompleted,
RotatedAt: &fixedTime,
VerificationResults: verificationResults,
AuditTrail: []rotation.AuditEntry{
{Timestamp: fixedTime, Action: "rotation_started", Status: "success"},
{Timestamp: fixedTime, Action: "password_generated", Status: "success"},
{Timestamp: fixedTime, Action: "database_updated", Status: "success"},
{Timestamp: fixedTime, Action: "verification_completed", Status: "success"},
},
}
return result, nil
}
func (r *MockDatabaseRotator) Verify(ctx context.Context, request rotation.VerificationRequest) error {
if request.NewSecretRef.Key == "new_password" {
return fmt.Errorf("connection test failed: mock connection error")
}
return nil
}
func (r *MockDatabaseRotator) Rollback(ctx context.Context, request rotation.RollbackRequest) error {
return nil
}
func (r *MockDatabaseRotator) GetStatus(ctx context.Context, secret rotation.SecretInfo) (*rotation.RotationStatusInfo, error) {
fixedTime := time.Date(2025, 11, 15, 12, 0, 0, 0, time.UTC)
return &rotation.RotationStatusInfo{
Status: rotation.StatusCompleted,
LastRotated: &fixedTime,
CanRotate: true,
}, nil
}
type MockTwoSecretRotator struct {
MockDatabaseRotator
secondarySecrets map[string]string
}
func (r *MockTwoSecretRotator) CreateSecondarySecret(ctx context.Context, request rotation.SecondarySecretRequest) (*rotation.SecretReference, error) {
identifier := "db_user_secondary"
r.secondarySecrets[request.Secret.Key] = identifier
return &rotation.SecretReference{
Provider: request.Secret.Provider,
Key: request.Secret.Key + "_secondary",
Identifier: identifier,
}, nil
}
func (r *MockTwoSecretRotator) PromoteSecondarySecret(ctx context.Context, request rotation.PromoteRequest) error {
return nil
}
func (r *MockTwoSecretRotator) DeprecatePrimarySecret(ctx context.Context, request rotation.DeprecateRequest) error {
return nil
}
type MockDatabase struct {
_ [0]func()
}
Output: Phase 1: Creating secondary secret... Secondary secret created: db_user_secondary Phase 2: Promoting secondary to primary... Secondary promoted to primary Phase 3: Deprecating old primary... Old primary deprecated Zero-downtime rotation completed!
type TwoSecretStrategy ¶
type TwoSecretStrategy struct {
// contains filtered or unexported fields
}
TwoSecretStrategy implements zero-downtime rotation using the two-secret approach pioneered by Doppler. It maintains two instances of each secret (active/inactive) and alternates between them during rotation.
func NewTwoSecretStrategy ¶
func NewTwoSecretStrategy(baseStrategy SecretValueRotator, logger *logging.Logger) *TwoSecretStrategy
NewTwoSecretStrategy wraps any SecretValueRotator with two-secret capabilities
func (*TwoSecretStrategy) GetStatus ¶
func (s *TwoSecretStrategy) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
GetStatus delegates to the base strategy
func (*TwoSecretStrategy) GetTwoSecretStatus ¶
func (s *TwoSecretStrategy) GetTwoSecretStatus(ctx context.Context, secret SecretInfo) (*TwoSecretMetadata, error)
GetTwoSecretStatus returns detailed status for two-secret rotation
func (*TwoSecretStrategy) Name ¶
func (s *TwoSecretStrategy) Name() string
Name returns the strategy name with two-secret prefix
func (*TwoSecretStrategy) Rollback ¶
func (s *TwoSecretStrategy) Rollback(ctx context.Context, request RollbackRequest) error
Rollback implements rollback by promoting the old secret back to primary
func (*TwoSecretStrategy) Rotate ¶
func (s *TwoSecretStrategy) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
Rotate implements zero-downtime rotation using the two-secret pattern
func (*TwoSecretStrategy) SupportsSecret ¶
func (s *TwoSecretStrategy) SupportsSecret(ctx context.Context, secret SecretInfo) bool
SupportsSecret checks if the base strategy supports this secret
func (*TwoSecretStrategy) Verify ¶
func (s *TwoSecretStrategy) Verify(ctx context.Context, request VerificationRequest) error
Verify uses the base strategy's verification
type ValueType ¶
type ValueType string
ValueType specifies how to generate new secret values
const ( ValueTypeRandom ValueType = "random" // Generate random value ValueTypeLiteral ValueType = "literal" // Use provided value ValueTypeFile ValueType = "file" // Read from file ValueTypeGenerated ValueType = "generated" // Provider generates (API key, cert) ValueTypeRotated ValueType = "rotated" // Provider rotates (OAuth refresh) )
type VerificationRequest ¶
type VerificationRequest struct {
Secret SecretInfo `json:"secret"`
NewSecretRef SecretReference `json:"new_secret_ref"`
Tests []VerificationTest `json:"tests"`
Timeout time.Duration `json:"timeout"`
}
VerificationRequest contains information for verifying a rotated secret
type VerificationResult ¶
type VerificationResult struct {
Test VerificationTest `json:"test"`
Status TestStatus `json:"status"`
Duration time.Duration `json:"duration"`
Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
Details map[string]interface{} `json:"details,omitempty"`
}
VerificationResult contains the outcome of a verification test
type VerificationTest ¶
type VerificationTest struct {
Name string `json:"name"`
Type TestType `json:"type"`
Config map[string]interface{} `json:"config,omitempty"`
Timeout time.Duration `json:"timeout,omitempty"`
Required bool `json:"required"`
Description string `json:"description,omitempty"`
}
VerificationTest defines how to verify a rotated secret works
type WebhookRequest ¶
type WebhookRequest struct {
Action string `json:"action"`
SecretInfo SecretInfo `json:"secret_info"`
DryRun bool `json:"dry_run"`
Force bool `json:"force"`
NewValue *NewSecretValue `json:"new_value,omitempty"`
Config map[string]interface{} `json:"config,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
WebhookRequest represents the request sent to the webhook
type WebhookResponse ¶
type WebhookResponse struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
NewSecretRef *SecretReference `json:"new_secret_ref,omitempty"`
Error string `json:"error,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
WebhookResponse represents the expected response from the webhook
type WebhookRotator ¶
type WebhookRotator struct {
// contains filtered or unexported fields
}
WebhookRotator implements rotation via HTTP webhook calls
func NewWebhookRotator ¶
func NewWebhookRotator(logger *logging.Logger) *WebhookRotator
NewWebhookRotator creates a new webhook rotation strategy
func (*WebhookRotator) GetStatus ¶
func (w *WebhookRotator) GetStatus(ctx context.Context, secret SecretInfo) (*RotationStatusInfo, error)
GetStatus performs status check via webhook call
func (*WebhookRotator) Rollback ¶
func (w *WebhookRotator) Rollback(ctx context.Context, request RollbackRequest) error
Rollback performs rollback via webhook call
func (*WebhookRotator) Rotate ¶
func (w *WebhookRotator) Rotate(ctx context.Context, request RotationRequest) (*RotationResult, error)
Rotate performs rotation via webhook call
func (*WebhookRotator) SupportsSecret ¶
func (w *WebhookRotator) SupportsSecret(ctx context.Context, secret SecretInfo) bool
SupportsSecret checks if this strategy can rotate the given secret
func (*WebhookRotator) Verify ¶
func (w *WebhookRotator) Verify(ctx context.Context, request VerificationRequest) error
Verify performs verification via webhook call