console1984 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39d8147e2b8b8fa0a20c6aec1422a28633afe80567bac0ac0c98a7d6096ae430
4
- data.tar.gz: 760d761d4ced82752b89aa7bd1de44416d545a75bdb0f4266165718100d8a7a5
3
+ metadata.gz: 44d77add0193cc1bb27955cb70d0d3e58153a17524e6d3b93a15179700322c58
4
+ data.tar.gz: b36e25eb75d6b1d113ae3653118115da747122e10af23d38ea00ebd72f3427cc
5
5
  SHA512:
6
- metadata.gz: 3eedbf3cc3436469edd615b397af1c5ad03195f7f1eebfcc1ed6da859a90e0fc213327f8d487841640ddec8c4d39ed1562239e9dda10360a622f59aa809b6f9a
7
- data.tar.gz: bdd11a8580f29fd700c954c486e01dc1f8189e452565f37b281d9ee96b78bd375c2f02728df3c4ce405c047fc9fe52b5eac8b5b90a62519bfeaff4aa1465a5d9
6
+ metadata.gz: 07d029ed6dcd845cbc30c035ccdb2a9879983f1660c028530dfc8873673d212f9230ffc9ad8870b1806a981317dad06d48c95b53cdebe728a37bc585bd182e29
7
+ data.tar.gz: c9dfd6cd41b27c000b0f37a22ee9b1d4fbbdd32081b22735636d90c62175c2cb8f55b3ef94f90c9ac706a72838b67e8a8130e813943fe908f3860bf631365c8f
data/README.md CHANGED
@@ -10,7 +10,7 @@ A Rails console extension that protects sensitive accesses and makes them audita
10
10
 
11
11
  If you are looking for the auditing tool, check [`audits1984`](https://github.com/basecamp/audits1984).
12
12
 
13
- ![console-session-reason](docs/images/console-session-reason.png)
13
+ ![Terminal screenshot showing console1984 asking for a reason for the session](docs/images/console-session-reason.png)
14
14
 
15
15
  ## Installation
16
16
 
@@ -69,7 +69,7 @@ To decrypt data, enter the command `decrypt!`. It will ask for a justification,
69
69
  irb(main)> Topic.last.name
70
70
  Topic Load (1.4ms) SELECT `topics`.* FROM `topics` ORDER BY `topics`.`id` DESC LIMIT 1
71
71
  => "{\"p\":\"iu6+LfnNlurC6sL++JyOIDvedjNSz/AvnZQ=\",\"h\":{\"iv\":\"BYa86+JNM/LdkC18\",\"at\":\"r4sQNoSyIlAjJdZEKHVMow==\",\"k\":{\"p\":\"7L1l/5UiYsFQqqo4jfMZtLwp90KqcrIgS7HqgteVjuM=\",\"h\":{\"iv\":\"ItwRYxZAerKIoSZ8\",\"at\":\"ZUSNVfvtm4wAYWLBKRAx/g==\",\"e\":\"QVNDSUktOEJJVA==\"}},\"i\":\"OTdiOQ==\"}}"
72
- irb(main):002:0> decrypt!
72
+ irb(main)> decrypt!
73
73
  ```
74
74
 
75
75
  ```
@@ -128,6 +128,10 @@ This will work for systems that use Ruby sockets as the underlying communication
128
128
 
129
129
  By default, sessions will be incinerated with a job 30 days after they are created. You can configure this period by setting `config.console1984.incinerate_after = 1.year` and you can disable incineration completely by setting `config.console1984.incinerate = false`.
130
130
 
131
+ ### Eager loading
132
+
133
+ When starting a console session, `console1984` will eager load all the application classes if necessary. In practice, production environments already load classes eagerly, so this won't represent any change for those.
134
+
131
135
  ## Configuration
132
136
 
133
137
  These config options are namespaced in `config.console1984`:
@@ -1,4 +1,6 @@
1
1
  module Console1984::Commands
2
+ include Console1984::Freezeable
3
+
2
4
  def decrypt!
3
5
  supervisor.enable_access_to_encrypted_content
4
6
  end
@@ -11,6 +13,4 @@ module Console1984::Commands
11
13
  def supervisor
12
14
  Console1984.supervisor
13
15
  end
14
-
15
- include Console1984::FrozenMethods
16
16
  end
@@ -1,13 +1,13 @@
1
1
  # Container for config options.
2
2
  class Console1984::Config
3
- include Console1984::Messages
3
+ include Console1984::Freezeable, Console1984::Messages
4
4
 
5
5
  PROPERTIES = %i[
6
6
  session_logger username_resolver
7
7
  protected_environments protected_urls
8
8
  production_data_warning enter_unprotected_encryption_mode_warning enter_protected_mode_warning
9
9
  incinerate incinerate_after incineration_queue
10
- debug freeze_config
10
+ debug test_mode
11
11
  ]
12
12
 
13
13
  attr_accessor(*PROPERTIES)
@@ -23,7 +23,7 @@ class Console1984::Config
23
23
  end
24
24
 
25
25
  def freeze
26
- super if freeze_config
26
+ super
27
27
  protected_urls.freeze
28
28
  end
29
29
 
@@ -44,6 +44,6 @@ class Console1984::Config
44
44
  self.incineration_queue = "console1984_incineration"
45
45
 
46
46
  self.debug = false
47
- self.freeze_config = true
47
+ self.test_mode = false
48
48
  end
49
49
  end
@@ -17,13 +17,9 @@ module Console1984
17
17
  console do
18
18
  Console1984.config.set_from(config.console1984)
19
19
 
20
- Console1984.supervisor.start if Console1984.running_protected_environment?
21
-
22
- class OpenSSL::SSL::SSLSocket
23
- # Make it serve remote address as TCPSocket so that our extension works for it
24
- def remote_address
25
- Addrinfo.getaddrinfo(hostname, 443).first
26
- end
20
+ if Console1984.running_protected_environment?
21
+ Console1984.supervisor.install
22
+ Console1984.supervisor.start
27
23
  end
28
24
  end
29
25
  end
@@ -9,6 +9,6 @@ module Console1984
9
9
 
10
10
  class ForbiddenCommand < StandardError; end
11
11
  class ForbiddenIncineration < StandardError; end
12
- class ForbiddenClassManipulation < StandardError; end
12
+ class ForbiddenCodeManipulation < StandardError; end
13
13
  end
14
14
  end
@@ -0,0 +1,54 @@
1
+ # Prevents adding new methods to classes.
2
+ #
3
+ # This prevents manipulating certain Console1984 classes
4
+ # during a console session.
5
+ module Console1984::Freezeable
6
+ extend ActiveSupport::Concern
7
+
8
+ mattr_reader :to_freeze, default: Set.new
9
+
10
+ included do
11
+ Console1984::Freezeable.to_freeze << self
12
+ end
13
+
14
+ class_methods do
15
+ SENSITIVE_INSTANCE_METHODS = %i[ instance_variable_set ]
16
+
17
+ def prevent_sensitive_overrides
18
+ SENSITIVE_INSTANCE_METHODS.each do |method|
19
+ prevent_sensitive_method method
20
+ end
21
+ end
22
+
23
+ private
24
+ def prevent_sensitive_method(method_name)
25
+ define_method method_name do |*arguments|
26
+ raise Console1984::Errors::ForbiddenCodeManipulation, "You can't invoke #{method_name} on #{self}"
27
+ end
28
+ end
29
+ end
30
+
31
+ class << self
32
+ def freeze_all
33
+ class_and_modules_to_freeze.each do |class_or_module|
34
+ freeze_class_or_module(class_or_module)
35
+ end
36
+ end
37
+
38
+ private
39
+ def class_and_modules_to_freeze
40
+ with_descendants(to_freeze)
41
+ end
42
+
43
+ def freeze_class_or_module(class_or_module)
44
+ class_or_module.prevent_sensitive_overrides
45
+ class_or_module.freeze
46
+ end
47
+
48
+ def with_descendants(classes_and_modules)
49
+ classes_and_modules + classes_and_modules.grep(Class).flat_map(&:descendants)
50
+ end
51
+ end
52
+
53
+ freeze
54
+ end
@@ -1,29 +1,30 @@
1
- module Console1984
2
- # Prevents accessing trail model tables when executing console commands.
3
- module ProtectedAuditableTables
4
- %i[ execute exec_query exec_insert exec_delete exec_update exec_insert_all ].each do |method|
5
- define_method method do |*args|
6
- sql = args.first
7
- if Console1984.supervisor.executing_user_command? && sql =~ auditable_tables_regexp
8
- raise Console1984::Errors::ForbiddenCommand, "#{sql}"
9
- else
10
- super(*args)
11
- end
1
+ # Prevents accessing trail model tables when executing console commands.
2
+ module Console1984::ProtectedAuditableTables
3
+ include Console1984::Freezeable
4
+
5
+ %i[ execute exec_query exec_insert exec_delete exec_update exec_insert_all ].each do |method|
6
+ define_method method do |*args|
7
+ sql = args.first
8
+ if Console1984.supervisor.executing_user_command? && sql =~ auditable_tables_regexp
9
+ raise Console1984::Errors::ForbiddenCommand, "#{sql}"
10
+ else
11
+ super(*args)
12
12
  end
13
13
  end
14
+ end
14
15
 
15
- private
16
- AUDITABLE_MODELS = [ Console1984::User, Console1984::Session, Console1984::Command, Console1984::SensitiveAccess ]
16
+ private
17
+ def auditable_tables_regexp
18
+ @auditable_tables_regexp ||= Regexp.new("#{auditable_tables.join("|")}")
19
+ end
17
20
 
18
- def auditable_tables_regexp
19
- @auditable_tables_regexp ||= Regexp.new("#{auditable_tables.join("|")}")
20
- end
21
+ def auditable_tables
22
+ @auditable_tables ||= auditable_models.collect(&:table_name)
23
+ end
21
24
 
22
- def auditable_tables
23
- # TODO: Not using Console1984::Base.descendants during development to make this work without eager loading
24
- @auditable_tables ||= AUDITABLE_MODELS.collect(&:table_name)
25
- end
26
- end
25
+ def auditable_models
26
+ @auditable_models ||= Console1984::Base.descendants
27
+ end
27
28
 
28
- include Console1984::FrozenMethods
29
+ include Console1984::Freezeable
29
30
  end
@@ -1,4 +1,6 @@
1
1
  module Console1984::ProtectedContext
2
+ include Console1984::Freezeable
3
+
2
4
  # This method is invoked for showing returned objects in the console
3
5
  # Overridden to make sure their evaluation is supervised.
4
6
  def inspect_last_value
@@ -14,5 +16,5 @@ module Console1984::ProtectedContext
14
16
  end
15
17
  end
16
18
 
17
- include Console1984::FrozenMethods
19
+ include Console1984::Freezeable
18
20
  end
@@ -0,0 +1,15 @@
1
+ module Console1984::ProtectedObject
2
+ extend ActiveSupport::Concern
3
+
4
+ include Console1984::Freezeable
5
+
6
+ class_methods do
7
+ def const_get(*arguments)
8
+ if Console1984.supervisor.executing_user_command? && arguments.first.to_s =~ /Console1984|ActiveRecord/
9
+ raise Console1984::Errors::ForbiddenCommand
10
+ else
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,7 @@
1
1
  # Wraps socket methods to execute supervised.
2
2
  module Console1984::ProtectedTcpSocket
3
+ include Console1984::Freezeable
4
+
3
5
  def write(*args)
4
6
  protecting do
5
7
  super
@@ -55,5 +57,5 @@ module Console1984::ProtectedTcpSocket
55
57
  end
56
58
  end
57
59
 
58
- include Console1984::FrozenMethods
60
+ include Console1984::Freezeable
59
61
  end
@@ -1,11 +1,13 @@
1
1
  # A session logger that saves audit trails in the database.
2
2
  class Console1984::SessionsLogger::Database
3
+ include Console1984::Freezeable
4
+
3
5
  attr_reader :current_session, :current_sensitive_access
4
6
 
5
7
  def start_session(username, reason)
6
8
  silence_logging do
7
9
  user = Console1984::User.find_or_create_by!(username: username)
8
- @current_session = user.sessions.create! reason: reason
10
+ @current_session = user.sessions.create!(reason: reason)
9
11
  end
10
12
  end
11
13
 
@@ -1,4 +1,6 @@
1
1
  class Console1984::Supervisor::Accesses::Protected
2
+ include Console1984::Freezeable
3
+
2
4
  def execute(&block)
3
5
  Console1984.protecting(&block)
4
6
  end
@@ -1,4 +1,6 @@
1
1
  class Console1984::Supervisor::Accesses::Unprotected
2
+ include Console1984::Freezeable
3
+
2
4
  def execute(&block)
3
5
  block.call
4
6
  end
@@ -1,5 +1,5 @@
1
1
  module Console1984::Supervisor::Accesses
2
- include Console1984::Messages
2
+ include Console1984::Messages, Console1984::Freezeable
3
3
 
4
4
  PROTECTED_ACCESS = Protected.new
5
5
  UNPROTECTED_ACCESS = Unprotected.new
@@ -1,13 +1,16 @@
1
1
  module Console1984::Supervisor::Executor
2
2
  extend ActiveSupport::Concern
3
3
 
4
+ include Console1984::Freezeable
5
+
4
6
  def execute_supervised(commands, &block)
5
7
  run_system_command { session_logger.before_executing commands }
8
+ validate_commands(commands)
6
9
  execute(&block)
7
- rescue Console1984::Errors::ForbiddenCommand, Console1984::Errors::ForbiddenClassManipulation
8
- puts "Forbidden command attempted: #{commands.join("\n")}"
9
- run_system_command { session_logger.suspicious_commands_attempted commands }
10
- nil
10
+ rescue Console1984::Errors::ForbiddenCommand, Console1984::Errors::ForbiddenCodeManipulation, FrozenError
11
+ flag_forbidden(commands)
12
+ rescue FrozenError
13
+ flag_forbidden(commands)
11
14
  ensure
12
15
  run_system_command { session_logger.after_executing commands }
13
16
  end
@@ -23,6 +26,12 @@ module Console1984::Supervisor::Executor
23
26
  end
24
27
 
25
28
  private
29
+ def flag_forbidden(commands)
30
+ puts "Forbidden command attempted: #{commands.join("\n")}"
31
+ run_system_command { session_logger.suspicious_commands_attempted commands }
32
+ nil
33
+ end
34
+
26
35
  def run_user_command(&block)
27
36
  run_command true, &block
28
37
  end
@@ -31,6 +40,21 @@ module Console1984::Supervisor::Executor
31
40
  run_command false, &block
32
41
  end
33
42
 
43
+ def validate_commands(commands)
44
+ if Array(commands).find { |command| forbidden_command?(command) }
45
+ raise Console1984::Errors::ForbiddenCommand
46
+ end
47
+ end
48
+
49
+ def forbidden_command?(command)
50
+ # This is a first protection layer. Very simple for now. We'll likely make this
51
+ # more sophisticated and configurable in future versions.
52
+ #
53
+ # We can't use our +Freezable+ concern in ActiveRecord since it relies on code
54
+ # generation on the fly.
55
+ command =~ /Console1984|console_1984|(class|module)\s+ActiveRecord::/
56
+ end
57
+
34
58
  def run_command(run_by_user, &block)
35
59
  original_value = @executing_user_command
36
60
  @executing_user_command = run_by_user
@@ -1,5 +1,5 @@
1
1
  module Console1984::Supervisor::InputOutput
2
- include Console1984::Messages
2
+ include Console1984::Freezeable, Console1984::Messages
3
3
 
4
4
  private
5
5
  def show_welcome_message
@@ -1,13 +1,20 @@
1
1
  module Console1984::Supervisor::Protector
2
2
  extend ActiveSupport::Concern
3
3
 
4
+ include Console1984::Freezeable
5
+
4
6
  private
5
7
  def extend_protected_systems
8
+ extend_object
6
9
  extend_irb
7
10
  extend_active_record
8
11
  extend_socket_classes
9
12
  end
10
13
 
14
+ def extend_object
15
+ Object.prepend Console1984::ProtectedObject
16
+ end
17
+
11
18
  def extend_irb
12
19
  IRB::Context.prepend(Console1984::ProtectedContext)
13
20
  Rails::ConsoleMethods.include(Console1984::Commands)
@@ -20,18 +27,29 @@ module Console1984::Supervisor::Protector
20
27
  if Object.const_defined?(class_string)
21
28
  klass = class_string.constantize
22
29
  klass.prepend(Console1984::ProtectedAuditableTables)
30
+ klass.include(Console1984::Freezeable)
23
31
  end
24
32
  end
25
33
  end
26
34
 
27
35
  def extend_socket_classes
28
36
  socket_classes = [TCPSocket, OpenSSL::SSL::SSLSocket]
37
+ OpenSSL::SSL::SSLSocket.include(SSLSocketRemoteAddress)
38
+
29
39
  if defined?(Redis::Connection)
30
40
  socket_classes.push(*[Redis::Connection::TCPSocket, Redis::Connection::SSLSocket])
31
41
  end
32
42
 
33
43
  socket_classes.compact.each do |socket_klass|
34
44
  socket_klass.prepend Console1984::ProtectedTcpSocket
45
+ socket_klass.freeze
46
+ end
47
+ end
48
+
49
+ module SSLSocketRemoteAddress
50
+ # Make it serve remote address as TCPSocket so that our extension works for it
51
+ def remote_address
52
+ Addrinfo.getaddrinfo(hostname, 443).first
35
53
  end
36
54
  end
37
55
  end
@@ -3,14 +3,17 @@ require 'rails/console/app'
3
3
 
4
4
  # Protects console sessions and executes code in supervised mode.
5
5
  class Console1984::Supervisor
6
- include Accesses, InputOutput, Executor, Protector
6
+ include Accesses, Console1984::Freezeable, Executor, InputOutput, Protector
7
+
8
+ def install
9
+ extend_protected_systems
10
+ freeze_all
11
+ end
7
12
 
8
13
  # Starts a console session extending IRB and several systems to inject
9
14
  # the protection logic, and notifies the session logger to record the
10
15
  # session.
11
16
  def start
12
- Console1984.config.freeze
13
- extend_protected_systems
14
17
  disable_access_to_encrypted_content(silent: true)
15
18
 
16
19
  show_welcome_message
@@ -31,6 +34,17 @@ class Console1984::Supervisor
31
34
  session_logger.finish_session
32
35
  end
33
36
 
37
+ def freeze_all
38
+ eager_load_all_classes
39
+ Console1984.config.freeze unless Console1984.config.test_mode
40
+ Console1984::Freezeable.freeze_all
41
+ end
42
+
43
+ def eager_load_all_classes
44
+ Rails.application.eager_load! unless Rails.application.config.eager_load
45
+ Console1984.class_loader.eager_load
46
+ end
47
+
34
48
  def session_logger
35
49
  Console1984.session_logger
36
50
  end
@@ -43,5 +57,5 @@ class Console1984::Supervisor
43
57
  Console1984.username_resolver
44
58
  end
45
59
 
46
- include Console1984::FrozenMethods
60
+ include Console1984::Freezeable
47
61
  end
@@ -1,6 +1,8 @@
1
1
  # A username resolver that returns the value of a given
2
2
  # environment variable.
3
3
  class Console1984::Username::EnvResolver
4
+ include Console1984::Freezeable
5
+
4
6
  def initialize(key)
5
7
  @key = key
6
8
  end
@@ -1,3 +1,3 @@
1
1
  module Console1984
2
- VERSION = '0.1.4'
2
+ VERSION = '0.1.5'
3
3
  end
data/lib/console1984.rb CHANGED
@@ -1,14 +1,15 @@
1
1
  require 'console1984/engine'
2
2
 
3
3
  require "zeitwerk"
4
- loader = Zeitwerk::Loader.for_gem
5
- loader.setup
4
+ class_loader = Zeitwerk::Loader.for_gem
5
+ class_loader.setup
6
6
 
7
7
  module Console1984
8
- include Messages
8
+ include Messages, Freezeable
9
9
 
10
- mattr_reader :supervisor, default: Supervisor.new
10
+ mattr_accessor :supervisor, default: Supervisor.new
11
11
  mattr_reader :config, default: Config.new
12
+ mattr_accessor :class_loader
12
13
 
13
14
  thread_mattr_accessor :currently_protected_urls, default: []
14
15
 
@@ -37,3 +38,5 @@ module Console1984
37
38
  end
38
39
  end
39
40
  end
41
+
42
+ Console1984.class_loader = class_loader
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: console1984
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jorge Manrubia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-19 00:00:00.000000000 Z
11
+ date: 2021-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -160,10 +160,11 @@ files:
160
160
  - lib/console1984/config.rb
161
161
  - lib/console1984/engine.rb
162
162
  - lib/console1984/errors.rb
163
- - lib/console1984/frozen_methods.rb
163
+ - lib/console1984/freezeable.rb
164
164
  - lib/console1984/messages.rb
165
165
  - lib/console1984/protected_auditable_tables.rb
166
166
  - lib/console1984/protected_context.rb
167
+ - lib/console1984/protected_object.rb
167
168
  - lib/console1984/protected_tcp_socket.rb
168
169
  - lib/console1984/sessions_logger/database.rb
169
170
  - lib/console1984/supervisor.rb
@@ -1,17 +0,0 @@
1
- # Prevents adding new methods to classes.
2
- #
3
- # This prevents manipulating certain Console1984 classes
4
- # during a console session.
5
- module Console1984::FrozenMethods
6
- extend ActiveSupport::Concern
7
-
8
- module ClassMethods
9
- def method_added(method_name)
10
- raise Console1984::Errors::ForbiddenClassManipulation, "Can't override #{name}##{method_name}"
11
- end
12
-
13
- def singleton_method_added(method_name)
14
- raise Console1984::Errors::ForbiddenClassManipulation, "Can't override #{name}.#{method_name}"
15
- end
16
- end
17
- end