console1984 0.1.4 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +32 -10
  3. data/config/protections.yml +30 -0
  4. data/lib/console1984/command_executor.rb +90 -0
  5. data/lib/console1984/command_validator/forbidden_constant_reference_validation.rb +31 -0
  6. data/lib/console1984/command_validator/forbidden_reopening_validation.rb +29 -0
  7. data/lib/console1984/command_validator/parsed_command.rb +90 -0
  8. data/lib/console1984/command_validator/suspicious_terms_validation.rb +22 -0
  9. data/lib/console1984/command_validator.rb +71 -0
  10. data/lib/console1984/config.rb +21 -9
  11. data/lib/console1984/engine.rb +6 -8
  12. data/lib/console1984/errors.rb +10 -1
  13. data/lib/console1984/ext/active_record/protected_auditable_tables.rb +28 -0
  14. data/lib/console1984/ext/core/module.rb +15 -0
  15. data/lib/console1984/ext/core/object.rb +43 -0
  16. data/lib/console1984/ext/irb/commands.rb +16 -0
  17. data/lib/console1984/ext/irb/context.rb +20 -0
  18. data/lib/console1984/{protected_tcp_socket.rb → ext/socket/tcp_socket.rb} +10 -4
  19. data/lib/console1984/freezeable.rb +70 -0
  20. data/lib/console1984/{supervisor/input_output.rb → input_output.rb} +9 -3
  21. data/lib/console1984/messages.rb +0 -10
  22. data/lib/console1984/protections_config.rb +17 -0
  23. data/lib/console1984/refrigerator.rb +32 -0
  24. data/lib/console1984/sessions_logger/database.rb +3 -1
  25. data/lib/console1984/shield/method_invocation_shell.rb +52 -0
  26. data/lib/console1984/shield/modes/protected.rb +27 -0
  27. data/lib/console1984/shield/modes/unprotected.rb +8 -0
  28. data/lib/console1984/shield/modes.rb +60 -0
  29. data/lib/console1984/shield.rb +85 -0
  30. data/lib/console1984/supervisor.rb +27 -22
  31. data/lib/console1984/username/env_resolver.rb +2 -0
  32. data/lib/console1984/version.rb +1 -1
  33. data/lib/console1984.rb +43 -21
  34. metadata +66 -14
  35. data/config/routes.rb +0 -9
  36. data/lib/console1984/commands.rb +0 -16
  37. data/lib/console1984/frozen_methods.rb +0 -17
  38. data/lib/console1984/protected_auditable_tables.rb +0 -29
  39. data/lib/console1984/protected_context.rb +0 -18
  40. data/lib/console1984/supervisor/accesses/protected.rb +0 -10
  41. data/lib/console1984/supervisor/accesses/unprotected.rb +0 -5
  42. data/lib/console1984/supervisor/accesses.rb +0 -41
  43. data/lib/console1984/supervisor/executor.rb +0 -41
  44. data/lib/console1984/supervisor/protector.rb +0 -37
@@ -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
 
@@ -0,0 +1,52 @@
1
+ # Prevents invoking a configurable set of methods
2
+ class Console1984::Shield::MethodInvocationShell
3
+ include Console1984::Freezeable
4
+
5
+ class << self
6
+ def install_for(config)
7
+ Array(config[:user]).each { |invocation| self.new(invocation, only_for_user_commands: true).prevent_methods_invocation }
8
+ Array(config[:system]).each { |invocation| self.new(invocation, only_for_user_commands: false).prevent_methods_invocation }
9
+ end
10
+ end
11
+
12
+ attr_reader :class_name, :methods, :only_for_user_commands
13
+
14
+ def initialize(invocation, only_for_user_commands:)
15
+ @class_name, methods = invocation.to_a
16
+ @methods = Array(methods)
17
+ @only_for_user_commands = only_for_user_commands
18
+ end
19
+
20
+ def prevent_methods_invocation
21
+ class_name.constantize.prepend build_protection_module
22
+ end
23
+
24
+ def build_protection_module
25
+ source = protected_method_invocations_source
26
+ Module.new do
27
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
28
+ #{source}
29
+ RUBY
30
+ end
31
+ end
32
+
33
+ def protected_method_invocations_source
34
+ methods.collect { |method| protected_method_invocation_source_for(method) }.join("\n")
35
+ end
36
+
37
+ def protected_method_invocation_source_for(method)
38
+ <<~RUBY
39
+ def #{method}(*args)
40
+ if (!#{only_for_user_commands} || Console1984.command_executor.executing_user_command?) && caller.find do |line|
41
+ line_from_irb = line =~ /^[^\\/]/
42
+ break if !(line =~ /console1984\\/lib/ || line_from_irb)
43
+ line_from_irb
44
+ end
45
+ raise Console1984::Errors::ForbiddenCommand
46
+ else
47
+ super
48
+ end
49
+ end
50
+ RUBY
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ # An execution mode that protects encrypted information and connection to external systems.
2
+ class Console1984::Shield::Modes::Protected
3
+ include Console1984::Freezeable
4
+
5
+ delegate :protected_urls, to: Console1984
6
+
7
+ thread_mattr_accessor :currently_protected_urls, default: []
8
+
9
+ def execute(&block)
10
+ protecting(&block)
11
+ end
12
+
13
+ private
14
+ def protecting(&block)
15
+ protecting_connections do
16
+ ActiveRecord::Encryption.protecting_encrypted_data(&block)
17
+ end
18
+ end
19
+
20
+ def protecting_connections
21
+ old_currently_protected_urls = self.currently_protected_urls
22
+ self.currently_protected_urls = protected_urls
23
+ yield
24
+ ensure
25
+ self.currently_protected_urls = old_currently_protected_urls
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ # An execution mode that doesn't protect encrypted information or external systems.
2
+ class Console1984::Shield::Modes::Unprotected
3
+ include Console1984::Freezeable
4
+
5
+ def execute(&block)
6
+ block.call
7
+ end
8
+ end
@@ -0,0 +1,60 @@
1
+ # Console 1984 operates in two modes:
2
+ #
3
+ # * Protected: it won't reveal encrypted information, attempt to connect to protected urls will be prevented.
4
+ # * Unprotected: it will reveal encrypted information and let all connections go through.
5
+ #
6
+ # Tampering attempts (such as deleting audit trails) is prevented in both modes.
7
+ module Console1984::Shield::Modes
8
+ include Console1984::Messages, Console1984::InputOutput
9
+ include Console1984::Freezeable
10
+
11
+ PROTECTED_MODE = Protected.new
12
+ UNPROTECTED_MODE = Unprotected.new
13
+
14
+ # Switch to protected mode
15
+ #
16
+ # Pass +silent: true+ to hide an informative message when switching to this mode.
17
+ def enable_unprotected_mode(silent: false)
18
+ command_executor.run_as_system do
19
+ show_warning Console1984.enter_unprotected_encryption_mode_warning if !silent && protected_mode?
20
+ justification = ask_for_value "\nBefore you can access personal information, you need to ask for and get explicit consent from the user(s). #{current_username}, where can we find this consent (a URL would be great)?"
21
+ session_logger.start_sensitive_access justification
22
+ nil
23
+ end
24
+ ensure
25
+ @mode = UNPROTECTED_MODE
26
+ nil
27
+ end
28
+
29
+ # Switch to unprotected mode
30
+ #
31
+ # Pass +silent: true+ to hide an informative message when switching to this mode.
32
+ def enable_protected_mode(silent: false)
33
+ command_executor.run_as_system do
34
+ show_warning Console1984.enter_protected_mode_warning if !silent && unprotected_mode?
35
+ session_logger.end_sensitive_access
36
+ nil
37
+ end
38
+ ensure
39
+ @mode = PROTECTED_MODE
40
+ nil
41
+ end
42
+
43
+ # Executes the passed block in the configured mode (protected or unprotected).
44
+ def with_protected_mode(&block)
45
+ @mode.execute(&block)
46
+ end
47
+
48
+ def unprotected_mode?
49
+ @mode.is_a?(Unprotected)
50
+ end
51
+
52
+ def protected_mode?
53
+ !unprotected_mode?
54
+ end
55
+
56
+ private
57
+ def current_username
58
+ username_resolver.current
59
+ end
60
+ end
@@ -0,0 +1,85 @@
1
+ # The shield implements the protection mechanisms while using the console:
2
+ #
3
+ # * It extends different systems with console1984 extensions (including IRB itself).
4
+ # * It offers an API to the rest of the system to enable and disable protected modes and
5
+ # execute code on the configured mode.
6
+ #
7
+ # Protection happens at two levels:
8
+ #
9
+ # * External: preventing users from accessing encrypted data or protected systems while on
10
+ # protected mode.
11
+ # * Internal: preventing users from tampering Console 1984 itself.
12
+ class Console1984::Shield
13
+ include Modes
14
+ include Console1984::Freezeable
15
+
16
+ delegate :username_resolver, :session_logger, :command_executor, to: Console1984
17
+
18
+ # Installs the shield by extending several systems and freezing classes and modules
19
+ # that aren't mean to be modified once the console is running.
20
+ def install
21
+ extend_protected_systems
22
+ prevent_invoking_protected_methods
23
+
24
+ refrigerator.freeze_all
25
+ end
26
+
27
+ private
28
+ def extend_protected_systems
29
+ extend_irb
30
+ extend_core_ruby
31
+ extend_sockets
32
+ extend_active_record
33
+ end
34
+
35
+ def extend_irb
36
+ IRB::Context.prepend(Console1984::Ext::Irb::Context)
37
+ Rails::ConsoleMethods.include(Console1984::Ext::Irb::Commands)
38
+ end
39
+
40
+ def extend_core_ruby
41
+ Object.prepend Console1984::Ext::Core::Object
42
+ Module.prepend Console1984::Ext::Core::Module
43
+ end
44
+
45
+ def extend_sockets
46
+ socket_classes = [TCPSocket, OpenSSL::SSL::SSLSocket]
47
+ OpenSSL::SSL::SSLSocket.include(SSLSocketRemoteAddress)
48
+
49
+ if defined?(Redis::Connection)
50
+ socket_classes.push(*[Redis::Connection::TCPSocket, Redis::Connection::SSLSocket])
51
+ end
52
+
53
+ socket_classes.compact.each do |socket_klass|
54
+ socket_klass.prepend Console1984::Ext::Socket::TcpSocket
55
+ socket_klass.freeze
56
+ end
57
+ end
58
+
59
+ ACTIVE_RECORD_CONNECTION_ADAPTERS = %w[ActiveRecord::ConnectionAdapters::Mysql2Adapter ActiveRecord::ConnectionAdapters::PostgreSQLAdapter ActiveRecord::ConnectionAdapters::SQLite3Adapter]
60
+
61
+ def extend_active_record
62
+ ACTIVE_RECORD_CONNECTION_ADAPTERS.each do |class_string|
63
+ if Object.const_defined?(class_string)
64
+ klass = class_string.constantize
65
+ klass.prepend(Console1984::Ext::ActiveRecord::ProtectedAuditableTables)
66
+ klass.include(Console1984::Freezeable)
67
+ end
68
+ end
69
+ end
70
+
71
+ def prevent_invoking_protected_methods
72
+ MethodInvocationShell.install_for(Console1984.protections_config.forbidden_methods)
73
+ end
74
+
75
+ def refrigerator
76
+ @refrigerator ||= Console1984::Refrigerator.new
77
+ end
78
+
79
+ module SSLSocketRemoteAddress
80
+ # Serve remote address as TCPSocket so that our extension works with both.
81
+ def remote_address
82
+ Addrinfo.getaddrinfo(hostname, 443).first
83
+ end
84
+ end
85
+ end
@@ -1,28 +1,43 @@
1
- require 'colorized_string'
2
1
  require 'rails/console/app'
3
2
 
4
- # Protects console sessions and executes code in supervised mode.
3
+ # Entry point to the system. In charge of installing everything
4
+ # and starting and stopping sessions.
5
5
  class Console1984::Supervisor
6
- include Accesses, InputOutput, Executor, Protector
6
+ include Console1984::Freezeable, Console1984::InputOutput
7
7
 
8
- # Starts a console session extending IRB and several systems to inject
9
- # the protection logic, and notifies the session logger to record the
10
- # session.
11
- def start
12
- Console1984.config.freeze
13
- extend_protected_systems
14
- disable_access_to_encrypted_content(silent: true)
8
+ delegate :username_resolver, :session_logger, :shield, to: Console1984
15
9
 
16
- show_welcome_message
10
+ # Installs the console protections.
11
+ #
12
+ # See Console1984::Shield
13
+ def install
14
+ require_dependencies
15
+ shield.install
16
+ end
17
17
 
18
+ # Starts a console session.
19
+ #
20
+ # This will enable protected mode and log the new session in the configured
21
+ # {session logger}[rdoc-ref:Console1984::SessionsLogger::Database].
22
+ def start
23
+ shield.enable_protected_mode(silent: true)
24
+ show_welcome_message
18
25
  start_session
19
26
  end
20
27
 
28
+ # Stops a console session
21
29
  def stop
22
30
  stop_session
23
31
  end
24
32
 
25
33
  private
34
+ def require_dependencies
35
+ Kernel.silence_warnings do
36
+ require 'parser/current'
37
+ end
38
+ require 'colorized_string'
39
+ end
40
+
26
41
  def start_session
27
42
  session_logger.start_session current_username, ask_for_session_reason
28
43
  end
@@ -31,17 +46,7 @@ class Console1984::Supervisor
31
46
  session_logger.finish_session
32
47
  end
33
48
 
34
- def session_logger
35
- Console1984.session_logger
36
- end
37
-
38
49
  def current_username
39
- Console1984.username_resolver.current
50
+ username_resolver.current
40
51
  end
41
-
42
- def username_resolver
43
- Console1984.username_resolver
44
- end
45
-
46
- include Console1984::FrozenMethods
47
52
  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.8'
3
3
  end
data/lib/console1984.rb CHANGED
@@ -1,39 +1,61 @@
1
1
  require 'console1984/engine'
2
2
 
3
3
  require "zeitwerk"
4
- loader = Zeitwerk::Loader.for_gem
5
- loader.setup
6
-
4
+ class_loader = Zeitwerk::Loader.for_gem
5
+ class_loader.setup
6
+
7
+ # = Console 1984
8
+ #
9
+ # Console1984 is an IRB-based Rails console extension that does
10
+ # three things:
11
+ #
12
+ # * Record console sessions with their user, reason and commands.
13
+ # * Protect encrypted data by showing the ciphertexts when you visualize it.
14
+ # * Protect access to external systems that contain sensitive information (such as Redis
15
+ # or Elasticsearch).
16
+ #
17
+ # == Session logging
18
+ #
19
+ # The console will record the session, its user and the commands entered. The logic to
20
+ # persist sessions is handled by the configured session logger, which is
21
+ # Console1984::SessionsLogger::Database by default.
22
+ #
23
+ # == Execution of commands
24
+ #
25
+ # The console will work in two modes:
26
+ #
27
+ # * Protected: It won't show encrypted information (it will show the ciphertexts instead)
28
+ # and it won't allow connections to protected urls.
29
+ # * Unprotected: it allows access to encrypted information and protected urls. The commands
30
+ # executed in this mode as flagged as sensitive.
31
+ #
32
+ # Console1984::CommandExecutor handles the execution of commands applying the corresponding
33
+ # protection mechanisms.´
34
+ #
35
+ # == Internal tampering prevention
36
+ #
37
+ # Finally, console1984 includes protection mechanisms against internal tampering while using
38
+ # the console. For example, to prevent the user from deleting audit trails. See
39
+ # Console1984::Shield and Console1984::CommandValidator to learn more.
7
40
  module Console1984
8
- include Messages
41
+ include Messages, Freezeable
42
+
43
+ mattr_accessor :supervisor, default: Supervisor.new
9
44
 
10
- mattr_reader :supervisor, default: Supervisor.new
11
45
  mattr_reader :config, default: Config.new
12
46
 
13
- thread_mattr_accessor :currently_protected_urls, default: []
47
+ mattr_accessor :class_loader
14
48
 
15
49
  class << self
16
50
  Config::PROPERTIES.each do |property|
17
51
  delegate property, to: :config
18
52
  end
19
53
 
54
+ # Returns whether the console is currently running in protected mode or not.
20
55
  def running_protected_environment?
21
56
  protected_environments.collect(&:to_sym).include?(Rails.env.to_sym)
22
57
  end
23
-
24
- def protecting(&block)
25
- protecting_connections do
26
- ActiveRecord::Encryption.protecting_encrypted_data(&block)
27
- end
28
- end
29
-
30
- private
31
- def protecting_connections
32
- old_currently_protected_urls = self.currently_protected_urls
33
- self.currently_protected_urls = protected_urls
34
- yield
35
- ensure
36
- self.currently_protected_urls = old_currently_protected_urls
37
- end
38
58
  end
39
59
  end
60
+
61
+ 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.8
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-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: benchmark-ips
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +150,34 @@ dependencies:
136
150
  - - ">="
137
151
  - !ruby/object:Gem::Version
138
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: pg
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: mysql2
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
139
181
  description:
140
182
  email:
141
183
  - jorge@basecamp.com
@@ -153,26 +195,36 @@ files:
153
195
  - app/models/console1984/session.rb
154
196
  - app/models/console1984/session/incineratable.rb
155
197
  - app/models/console1984/user.rb
156
- - config/routes.rb
198
+ - config/protections.yml
157
199
  - db/migrate/20210517203931_create_console1984_tables.rb
158
200
  - lib/console1984.rb
159
- - lib/console1984/commands.rb
201
+ - lib/console1984/command_executor.rb
202
+ - lib/console1984/command_validator.rb
203
+ - lib/console1984/command_validator/forbidden_constant_reference_validation.rb
204
+ - lib/console1984/command_validator/forbidden_reopening_validation.rb
205
+ - lib/console1984/command_validator/parsed_command.rb
206
+ - lib/console1984/command_validator/suspicious_terms_validation.rb
160
207
  - lib/console1984/config.rb
161
208
  - lib/console1984/engine.rb
162
209
  - lib/console1984/errors.rb
163
- - lib/console1984/frozen_methods.rb
210
+ - lib/console1984/ext/active_record/protected_auditable_tables.rb
211
+ - lib/console1984/ext/core/module.rb
212
+ - lib/console1984/ext/core/object.rb
213
+ - lib/console1984/ext/irb/commands.rb
214
+ - lib/console1984/ext/irb/context.rb
215
+ - lib/console1984/ext/socket/tcp_socket.rb
216
+ - lib/console1984/freezeable.rb
217
+ - lib/console1984/input_output.rb
164
218
  - lib/console1984/messages.rb
165
- - lib/console1984/protected_auditable_tables.rb
166
- - lib/console1984/protected_context.rb
167
- - lib/console1984/protected_tcp_socket.rb
219
+ - lib/console1984/protections_config.rb
220
+ - lib/console1984/refrigerator.rb
168
221
  - lib/console1984/sessions_logger/database.rb
222
+ - lib/console1984/shield.rb
223
+ - lib/console1984/shield/method_invocation_shell.rb
224
+ - lib/console1984/shield/modes.rb
225
+ - lib/console1984/shield/modes/protected.rb
226
+ - lib/console1984/shield/modes/unprotected.rb
169
227
  - lib/console1984/supervisor.rb
170
- - lib/console1984/supervisor/accesses.rb
171
- - lib/console1984/supervisor/accesses/protected.rb
172
- - lib/console1984/supervisor/accesses/unprotected.rb
173
- - lib/console1984/supervisor/executor.rb
174
- - lib/console1984/supervisor/input_output.rb
175
- - lib/console1984/supervisor/protector.rb
176
228
  - lib/console1984/username/env_resolver.rb
177
229
  - lib/console1984/version.rb
178
230
  - test/fixtures/console1984/commands.yml