rotation

package
v0.2.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 8, 2026 License: MIT Imports: 21 Imported by: 0

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:

  1. **Secret Store Providers** (pkg/provider) - Where secrets are stored
  2. **Service Integrations** (this package) - What uses secrets and supports rotation
  3. **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:

  1. **Planning**: Validate request, check constraints, select strategy
  2. **Generation**: Create new secret value according to requirements
  3. **Service Update**: Update target service with new credentials
  4. **Verification**: Test that new credentials work correctly
  5. **Storage Update**: Store new secret in secret management system
  6. **Cleanup**: Remove or deprecate old credentials
  7. **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:

  1. Generate new secret value
  2. Update service configuration
  3. Verify new credentials work
  4. Update secret storage
  5. Clean up old credentials

Suitable for: Development environments, non-critical services, maintenance windows

## Two-Key Strategy

Maintains two valid credentials during transition:

  1. Create secondary secret alongside primary
  2. Deploy secondary to all systems
  3. Verify secondary works everywhere
  4. Promote secondary to primary
  5. Deprecate old primary after grace period

Suitable for: High-availability services, distributed systems, critical databases

## Overlap Strategy

Gradual transition with configurable overlap:

  1. Create new credentials
  2. Begin directing percentage of traffic to new credentials
  3. Gradually increase percentage over time
  4. Monitor for issues and rollback if needed
  5. 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:

  1. **Secret Store Providers** (pkg/provider) - Where secrets are stored (Vault, AWS, etc.)
  2. **Service Integrations** (this package) - What uses secrets (PostgreSQL, Stripe, etc.)
  3. **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:

  1. Implement SecretValueRotator interface
  2. Optionally implement TwoSecretRotator for zero-downtime support
  3. Implement SchemaAwareRotator to use dsops-data definitions
  4. 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

Examples

Constants

This section is empty.

Variables

View Source
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

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

GetStatus delegates to base rotator

func (*ImmediateRotationStrategy) Name

Name returns the strategy name

func (*ImmediateRotationStrategy) Rollback

Rollback attempts to restore the previous value

func (*ImmediateRotationStrategy) Rotate

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

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

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

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

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) Name

func (r *RandomRotator) Name() string

Name returns the strategy name

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) Name

func (s *ScriptRotator) Name() string

Name returns the strategy name

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:

  1. Generate or obtain new secret values
  2. Update the target service with new credentials
  3. Verify the new credentials work correctly
  4. Update the secret storage system
  5. Clean up old credentials
  6. 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:

  1. Create secondary (new) secret alongside existing primary
  2. Deploy and verify secondary secret works in all systems
  3. Promote secondary to primary (make it the active credential)
  4. 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

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) Name

func (w *WebhookRotator) Name() string

Name returns the strategy name

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL