Skip to content

Commit

Permalink
add readonly mode to production console
Browse files Browse the repository at this point in the history
  • Loading branch information
grosser committed Apr 19, 2019
1 parent 97e91cd commit e73b130
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .irbrc.rb
@@ -0,0 +1,6 @@
# frozen_string_literal: true
# loaded by marco-polo, cannot be in console.rb
if Rails.env.production?
puts "Running in readonly mode. Use Samson::ReadonlyDb.disable to switch to writable."
Samson::ReadonlyDb.enable
end
3 changes: 3 additions & 0 deletions config/initializers/console.rb
@@ -1,8 +1,11 @@
# frozen_string_literal: true
Rails.application.console do
Rails::ConsoleMethods.send(:prepend, Samson::ConsoleExtensions)

puts "Samson version: #{SAMSON_VERSION.first(7)}" if SAMSON_VERSION

ActiveRecord::Base.logger = Rails.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
Rails.logger.level = :info if ENV['PROFILE']

Audited.store[:audited_user] = "rails console #{ENV.fetch("USER")}"
end
50 changes: 50 additions & 0 deletions lib/samson/readonly_db.rb
@@ -0,0 +1,50 @@
# frozen_string_literal: true

# Make Activerecord readonly by blocking write requests
# NOTE: not perfect and can be circumvented with multiline sql statements or `;`
module Samson
module ReadonlyDb
ALLOWED = ["SELECT ", "SHOW ", "SET @@SESSION.", "EXPLAIN "].freeze
PROMPT_CHANGE = ["(", "(readonly "].freeze
PROMPTS = [:PROMPT_I, :PROMPT_N].freeze

class << self
def enable
return if @subscriber
@subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload|
sql = payload.fetch(:sql)
next if sql.start_with?(*ALLOWED)
raise ActiveRecord::ReadOnlyRecord, <<~MSG
Database is in readonly mode, cannot execute query
Switch off readonly with #{self}.#{method(:disable).name}
#{sql}
MSG
end
update_prompt
end

def disable
return unless @subscriber
ActiveSupport::Notifications.unsubscribe @subscriber
@subscriber = nil
update_prompt
end

private

# TODO: modify normal prompt when marco-polo is not enabled
# TODO: pry support
def update_prompt
return unless defined?(IRB) # uncovered
return unless prompt = IRB.conf.dig(:PROMPT, :RAILS_ENV)

PROMPTS.each do |prompt_key|
value = prompt.fetch(prompt_key) # change marco-polo prompt
change = (@subscriber ? PROMPT_CHANGE : PROMPT_CHANGE.reverse)
value.sub!(*change) || raise("Unable to change prompt #{prompt_key} #{value.inspect}")
end
nil
end
end
end
end

0 comments on commit e73b130

Please sign in to comment.