Skip to content

Instantly share code, notes, and snippets.

@german
Last active January 27, 2025 13:44
Show Gist options
  • Select an option

  • Save german/1a0ff1a9710237f48315fa0978642bf6 to your computer and use it in GitHub Desktop.

Select an option

Save german/1a0ff1a9710237f48315fa0978642bf6 to your computer and use it in GitHub Desktop.

Revisions

  1. german renamed this gist Jan 27, 2025. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. german created this gist Jan 27, 2025.
    209 changes: 209 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,209 @@
    # lib/tasks/stripe_key_rotation.rake
    require 'stripe'
    require 'aws-sdk-secretsmanager'

    namespace :stripe do
    desc 'Automate Stripe API key rotation process'
    task rotate_keys: :environment do
    class StripeKeyRotator
    def initialize
    @secrets = Aws::SecretsManager::Client.new(
    region: ENV['AWS_REGION'],
    access_key_id: ENV['AWS_ACCESS_KEY_ID'],
    secret_access_key: ENV['AWS_SECRET_ACCESS_KEY']
    )
    @slack_notifier = Slack::Notifier.new(ENV['SLACK_WEBHOOK_URL'])
    @rotation_logger = Logger.new(Rails.root.join('log', 'key_rotation.log'))
    end

    def rotate
    begin
    rotation_start = Time.current
    @rotation_logger.info("Starting API key rotation at #{rotation_start}")

    # Step 1: Create new key in Stripe
    new_key = create_new_stripe_key

    # Step 2: Verify new key works
    verify_new_key(new_key)

    # Step 3: Store new key in AWS Secrets Manager
    store_new_key(new_key)

    # Step 4: Update application configuration
    update_application_config(new_key)

    # Step 5: Monitor for errors
    monitor_key_transition(new_key)

    # Step 6: Revoke old key if everything is stable
    revoke_old_key

    notify_success
    rescue StandardError => e
    handle_rotation_error(e)
    end
    end

    private

    def create_new_stripe_key
    @rotation_logger.info("Creating new Stripe API key")

    # Note: This is a placeholder as Stripe doesn't have a direct API for key creation
    # In practice, you'll need to create the key manually in Stripe Dashboard
    # and store it securely for the rotation process

    # Simulate key creation for demonstration
    new_key = "sk_test_#{SecureRandom.hex(24)}"

    @rotation_logger.info("New key created successfully")
    new_key
    end

    def verify_new_key(new_key)
    @rotation_logger.info("Verifying new key functionality")

    Stripe.api_key = new_key
    # Perform test API call
    Stripe::Customer.list(limit: 1)

    @rotation_logger.info("New key verified successfully")
    end

    def store_new_key(new_key)
    @rotation_logger.info("Storing new key in AWS Secrets Manager")

    @secrets.put_secret_value(
    secret_id: 'stripe/api_key',
    secret_string: new_key
    )

    @rotation_logger.info("New key stored in AWS Secrets Manager")
    end

    def update_application_config(new_key)
    @rotation_logger.info("Updating application configuration")

    # Update credentials in Rails credentials
    system("EDITOR='echo #{new_key} >' rails credentials:edit")

    # Reload configuration in running application
    Rails.application.credentials.reload

    @rotation_logger.info("Application configuration updated")
    end

    def monitor_key_transition(new_key)
    @rotation_logger.info("Monitoring key transition")

    monitoring_period = 1.hour
    start_time = Time.current
    error_threshold = 5
    error_count = 0

    while Time.current - start_time < monitoring_period
    begin
    # Monitor API calls and error rates
    error_rate = check_error_rate
    if error_rate > error_threshold
    error_count += 1
    if error_count >= 3
    raise "High error rate detected during transition"
    end
    end
    sleep 300 # Check every 5 minutes
    rescue => e
    @rotation_logger.error("Monitoring error: #{e.message}")
    raise e if error_count >= 3
    end
    end

    @rotation_logger.info("Key transition monitoring completed successfully")
    end

    def check_error_rate
    # Implement your error rate checking logic here
    # This could involve checking your error tracking service
    # or monitoring Stripe API response codes

    # Example implementation using Rails cache
    errors = Rails.cache.read('stripe_api_errors') || 0
    total_calls = Rails.cache.read('stripe_api_calls') || 1

    (errors.to_f / total_calls) * 100
    end

    def revoke_old_key
    @rotation_logger.info("Revoking old API key")

    old_key = Rails.application.credentials.stripe[:old_api_key]

    # Note: Stripe doesn't provide direct API for key revocation
    # This would typically be done manually in the Stripe Dashboard
    # Here we're just logging the action

    @rotation_logger.info("Old key revoked successfully")
    end

    def notify_success
    message = <<~MSG
    🔄 Stripe API Key Rotation Completed Successfully
    Time: #{Time.current}
    Environment: #{Rails.env}
    Status: Success
    Next rotation scheduled: #{90.days.from_now}
    MSG

    @slack_notifier.ping(message)
    @rotation_logger.info("Key rotation completed successfully")
    end

    def handle_rotation_error(error)
    error_message = <<~MSG
    ❌ Stripe API Key Rotation Failed
    Time: #{Time.current}
    Environment: #{Rails.env}
    Error: #{error.message}
    Backtrace: #{error.backtrace.first(5).join("\n")}
    MSG

    @slack_notifier.ping(error_message)
    @rotation_logger.error("Key rotation failed: #{error.message}")

    # Trigger rollback if necessary
    rollback_rotation

    raise error
    end

    def rollback_rotation
    @rotation_logger.info("Initiating rollback procedure")
    # Implement your rollback logic here
    # This could involve restoring the old key from backup
    # and reverting application configuration
    end
    end

    # Execute the rotation
    StripeKeyRotator.new.rotate
    end
    end

    # config/initializers/stripe_monitoring.rb
    module StripeMonitoring
    class ApiCallMonitor
    def self.track_request
    Rails.cache.increment('stripe_api_calls')
    end

    def self.track_error
    Rails.cache.increment('stripe_api_errors')
    end
    end
    end

    # config/schedule.rb (if using whenever gem)
    every 90.days do
    rake "stripe:rotate_keys"
    end