mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
315 lines
10 KiB
Go
315 lines
10 KiB
Go
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"cosmossdk.io/log"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
apiv1 "github.com/sonr-io/sonr/api/dwn/v1"
|
|
"github.com/sonr-io/sonr/x/dwn/types"
|
|
)
|
|
|
|
// KeyRotationPolicy defines the policy for automated key rotation
|
|
type KeyRotationPolicy struct {
|
|
// Time-based rotation
|
|
RotationInterval time.Duration // How often to rotate keys
|
|
NextRotationTime time.Time // When the next rotation is scheduled
|
|
|
|
// Usage-based rotation
|
|
MaxUsageCount uint64 // Maximum number of operations before rotation
|
|
CurrentUsageCount uint64 // Current operation count
|
|
|
|
// Event-based rotation triggers
|
|
RotateOnCompromise bool // Rotate immediately if compromise detected
|
|
RotateOnValidatorChange bool // Rotate when validator set changes significantly
|
|
|
|
// Configuration
|
|
Enabled bool // Whether automated rotation is enabled
|
|
GracePeriod time.Duration // Grace period before forcing rotation
|
|
}
|
|
|
|
// KeyRotationScheduler handles automated key rotation scheduling
|
|
type KeyRotationScheduler struct {
|
|
keeper *Keeper
|
|
logger log.Logger
|
|
policy *KeyRotationPolicy
|
|
}
|
|
|
|
// NewKeyRotationScheduler creates a new key rotation scheduler
|
|
func NewKeyRotationScheduler(keeper *Keeper) *KeyRotationScheduler {
|
|
return &KeyRotationScheduler{
|
|
keeper: keeper,
|
|
logger: keeper.Logger().With("module", "key-rotation-scheduler"),
|
|
policy: &KeyRotationPolicy{
|
|
RotationInterval: 30 * 24 * time.Hour, // 30 days default
|
|
MaxUsageCount: 1000000, // 1 million operations default
|
|
RotateOnCompromise: true,
|
|
RotateOnValidatorChange: true,
|
|
Enabled: true,
|
|
GracePeriod: 24 * time.Hour,
|
|
},
|
|
}
|
|
}
|
|
|
|
// CheckRotationPolicy checks all rotation policies and triggers rotation if needed
|
|
func (krs *KeyRotationScheduler) CheckRotationPolicy(ctx context.Context) error {
|
|
if !krs.policy.Enabled {
|
|
return nil
|
|
}
|
|
|
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
|
|
|
// Get current key state
|
|
keyState, err := krs.keeper.encryptionSubkeeper.getStoredKeyState(ctx)
|
|
if err != nil {
|
|
// No key state means we need initial rotation
|
|
krs.logger.Info("No key state found, triggering initial rotation")
|
|
return krs.TriggerRotation(ctx, "initial_setup")
|
|
}
|
|
|
|
// Check time-based rotation
|
|
if krs.shouldRotateByTime(keyState) {
|
|
krs.logger.Info("Time-based rotation triggered",
|
|
"last_rotation", keyState.LastRotation,
|
|
"interval", krs.policy.RotationInterval,
|
|
)
|
|
return krs.TriggerRotation(ctx, "scheduled_time_based")
|
|
}
|
|
|
|
// Check usage-based rotation
|
|
if krs.shouldRotateByUsage(keyState) {
|
|
krs.logger.Info("Usage-based rotation triggered",
|
|
"usage_count", keyState.UsageCount,
|
|
"max_usage", keyState.MaxUsageCount,
|
|
)
|
|
return krs.TriggerRotation(ctx, "usage_limit_reached")
|
|
}
|
|
|
|
// Check validator set changes
|
|
if krs.policy.RotateOnValidatorChange {
|
|
// Use hasValidatorSetChanged with a threshold (0.33 = 33% change)
|
|
if krs.keeper.encryptionSubkeeper.hasValidatorSetChanged(sdkCtx, 0.33) {
|
|
krs.logger.Info("Validator set change triggered rotation")
|
|
return krs.TriggerRotation(ctx, "validator_set_changed")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// shouldRotateByTime checks if time-based rotation is due
|
|
func (krs *KeyRotationScheduler) shouldRotateByTime(keyState *types.EncryptionKeyState) bool {
|
|
if keyState.RotationInterval <= 0 {
|
|
// Use default interval if not set
|
|
keyState.RotationInterval = int64(krs.policy.RotationInterval.Seconds())
|
|
}
|
|
|
|
lastRotation := time.Unix(keyState.LastRotation, 0)
|
|
nextRotation := lastRotation.Add(time.Duration(keyState.RotationInterval) * time.Second)
|
|
|
|
return time.Now().After(nextRotation)
|
|
}
|
|
|
|
// shouldRotateByUsage checks if usage-based rotation is due
|
|
func (krs *KeyRotationScheduler) shouldRotateByUsage(keyState *types.EncryptionKeyState) bool {
|
|
maxUsage := keyState.MaxUsageCount
|
|
if maxUsage == 0 {
|
|
maxUsage = krs.policy.MaxUsageCount
|
|
}
|
|
|
|
return keyState.UsageCount >= maxUsage
|
|
}
|
|
|
|
// TriggerRotation triggers an immediate key rotation
|
|
func (krs *KeyRotationScheduler) TriggerRotation(ctx context.Context, reason string) error {
|
|
krs.logger.Info("Triggering key rotation", "reason", reason)
|
|
|
|
// Perform the rotation through the encryption subkeeper
|
|
if err := krs.keeper.encryptionSubkeeper.InitiateKeyRotation(ctx, reason); err != nil {
|
|
return fmt.Errorf("failed to initiate key rotation: %w", err)
|
|
}
|
|
|
|
// Update usage counter
|
|
if err := krs.resetUsageCounter(ctx); err != nil {
|
|
krs.logger.Error("Failed to reset usage counter after rotation", "error", err)
|
|
}
|
|
|
|
// Emit rotation event
|
|
krs.emitRotationEvent(ctx, reason)
|
|
|
|
return nil
|
|
}
|
|
|
|
// IncrementUsageCount increments the usage counter for the current key
|
|
func (krs *KeyRotationScheduler) IncrementUsageCount(ctx context.Context) error {
|
|
keyState, err := krs.keeper.encryptionSubkeeper.getStoredKeyState(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get current key state: %w", err)
|
|
}
|
|
|
|
// Increment usage count
|
|
keyState.UsageCount++
|
|
|
|
// Convert to API type for ORM storage
|
|
apiKeyState := &apiv1.EncryptionKeyState{
|
|
KeyVersion: keyState.KeyVersion,
|
|
CurrentKey: keyState.CurrentKey,
|
|
ValidatorSet: keyState.ValidatorSet,
|
|
LastRotation: keyState.LastRotation,
|
|
NextRotation: keyState.NextRotation,
|
|
SingleNodeMode: keyState.SingleNodeMode,
|
|
UsageCount: keyState.UsageCount,
|
|
MaxUsageCount: keyState.MaxUsageCount,
|
|
RotationInterval: keyState.RotationInterval,
|
|
CreatedAt: keyState.CreatedAt,
|
|
PreviousKeyVersion: keyState.PreviousKeyVersion,
|
|
}
|
|
|
|
// Update the key state
|
|
if err := krs.keeper.OrmDB.EncryptionKeyStateTable().Update(ctx, apiKeyState); err != nil {
|
|
return fmt.Errorf("failed to update key state: %w", err)
|
|
}
|
|
|
|
// Check if rotation is needed after increment
|
|
if krs.shouldRotateByUsage(keyState) {
|
|
go func() {
|
|
// Async rotation to not block the current operation
|
|
if err := krs.TriggerRotation(ctx, "usage_limit_reached"); err != nil {
|
|
krs.logger.Error("Failed to trigger usage-based rotation", "error", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// resetUsageCounter resets the usage counter after rotation
|
|
func (krs *KeyRotationScheduler) resetUsageCounter(ctx context.Context) error {
|
|
keyState, err := krs.keeper.encryptionSubkeeper.getStoredKeyState(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
keyState.UsageCount = 0
|
|
|
|
// Convert to API type for ORM storage
|
|
apiKeyState := &apiv1.EncryptionKeyState{
|
|
KeyVersion: keyState.KeyVersion,
|
|
CurrentKey: keyState.CurrentKey,
|
|
ValidatorSet: keyState.ValidatorSet,
|
|
LastRotation: keyState.LastRotation,
|
|
NextRotation: keyState.NextRotation,
|
|
SingleNodeMode: keyState.SingleNodeMode,
|
|
UsageCount: keyState.UsageCount,
|
|
MaxUsageCount: keyState.MaxUsageCount,
|
|
RotationInterval: keyState.RotationInterval,
|
|
CreatedAt: keyState.CreatedAt,
|
|
PreviousKeyVersion: keyState.PreviousKeyVersion,
|
|
}
|
|
|
|
return krs.keeper.OrmDB.EncryptionKeyStateTable().Update(ctx, apiKeyState)
|
|
}
|
|
|
|
// emitRotationEvent emits a key rotation event
|
|
func (krs *KeyRotationScheduler) emitRotationEvent(ctx context.Context, reason string) {
|
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
|
|
|
sdkCtx.EventManager().EmitEvent(
|
|
sdk.NewEvent(
|
|
"key_rotation",
|
|
sdk.NewAttribute("reason", reason),
|
|
sdk.NewAttribute("timestamp", fmt.Sprintf("%d", time.Now().Unix())),
|
|
),
|
|
)
|
|
}
|
|
|
|
// SetRotationPolicy updates the rotation policy
|
|
func (krs *KeyRotationScheduler) SetRotationPolicy(policy *KeyRotationPolicy) {
|
|
krs.policy = policy
|
|
krs.logger.Info("Updated key rotation policy",
|
|
"interval", policy.RotationInterval,
|
|
"max_usage", policy.MaxUsageCount,
|
|
"enabled", policy.Enabled,
|
|
)
|
|
}
|
|
|
|
// GetRotationPolicy returns the current rotation policy
|
|
func (krs *KeyRotationScheduler) GetRotationPolicy() *KeyRotationPolicy {
|
|
return krs.policy
|
|
}
|
|
|
|
// ScheduleNextRotation schedules the next rotation based on the policy
|
|
func (krs *KeyRotationScheduler) ScheduleNextRotation(ctx context.Context) error {
|
|
keyState, err := krs.keeper.encryptionSubkeeper.getStoredKeyState(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get current key state: %w", err)
|
|
}
|
|
|
|
// Calculate next rotation time
|
|
nextRotation := time.Now().Add(krs.policy.RotationInterval)
|
|
keyState.NextRotation = nextRotation.Unix()
|
|
|
|
// Convert to API type for ORM storage
|
|
apiKeyState := &apiv1.EncryptionKeyState{
|
|
KeyVersion: keyState.KeyVersion,
|
|
CurrentKey: keyState.CurrentKey,
|
|
ValidatorSet: keyState.ValidatorSet,
|
|
LastRotation: keyState.LastRotation,
|
|
NextRotation: keyState.NextRotation,
|
|
SingleNodeMode: keyState.SingleNodeMode,
|
|
UsageCount: keyState.UsageCount,
|
|
MaxUsageCount: keyState.MaxUsageCount,
|
|
RotationInterval: keyState.RotationInterval,
|
|
CreatedAt: keyState.CreatedAt,
|
|
PreviousKeyVersion: keyState.PreviousKeyVersion,
|
|
}
|
|
|
|
// Update the key state
|
|
if err := krs.keeper.OrmDB.EncryptionKeyStateTable().Update(ctx, apiKeyState); err != nil {
|
|
return fmt.Errorf("failed to update next rotation time: %w", err)
|
|
}
|
|
|
|
krs.logger.Info("Scheduled next rotation",
|
|
"next_rotation", nextRotation,
|
|
"interval", krs.policy.RotationInterval,
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// HandleCompromiseEvent handles a potential key compromise event
|
|
func (krs *KeyRotationScheduler) HandleCompromiseEvent(ctx context.Context, details string) error {
|
|
if !krs.policy.RotateOnCompromise {
|
|
krs.logger.Warn(
|
|
"Key compromise detected but rotation on compromise is disabled",
|
|
"details",
|
|
details,
|
|
)
|
|
return nil
|
|
}
|
|
|
|
krs.logger.Error("Key compromise detected, triggering emergency rotation", "details", details)
|
|
return krs.TriggerRotation(ctx, fmt.Sprintf("compromise_detected: %s", details))
|
|
}
|
|
|
|
// BeginBlock runs rotation checks at the beginning of each block
|
|
func (krs *KeyRotationScheduler) BeginBlock(ctx context.Context) error {
|
|
// Check rotation policy every block
|
|
if err := krs.CheckRotationPolicy(ctx); err != nil {
|
|
krs.logger.Error("Failed to check rotation policy", "error", err)
|
|
// Don't fail the block, just log the error
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EndBlock runs cleanup at the end of each block
|
|
func (krs *KeyRotationScheduler) EndBlock(ctx context.Context) error {
|
|
// Any end-of-block cleanup can go here
|
|
return nil
|
|
}
|