console1984 0.1.4 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
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