Last active
January 27, 2025 13:44
-
-
Save german/1a0ff1a9710237f48315fa0978642bf6 to your computer and use it in GitHub Desktop.
Stripe API keys rotation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment