console1984 0.1.6 → 0.1.7
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.
- checksums.yaml +4 -4
- data/README.md +14 -1
- data/config/command_protections.yml +17 -0
- data/lib/console1984/command_executor.rb +94 -0
- data/lib/console1984/command_validator/forbidden_constant_reference_validation.rb +31 -0
- data/lib/console1984/command_validator/forbidden_reopening_validation.rb +29 -0
- data/lib/console1984/command_validator/parsed_command.rb +64 -0
- data/lib/console1984/command_validator/suspicious_terms_validation.rb +22 -0
- data/lib/console1984/command_validator.rb +71 -0
- data/lib/console1984/config.rb +9 -5
- data/lib/console1984/engine.rb +2 -0
- data/lib/console1984/errors.rb +10 -1
- data/lib/console1984/{protected_auditable_tables.rb → ext/active_record/protected_auditable_tables.rb} +2 -4
- data/lib/console1984/ext/core/object.rb +42 -0
- data/lib/console1984/ext/irb/commands.rb +16 -0
- data/lib/console1984/{protected_context.rb → ext/irb/context.rb} +5 -5
- data/lib/console1984/{protected_tcp_socket.rb → ext/socket/tcp_socket.rb} +3 -3
- data/lib/console1984/freezeable.rb +15 -5
- data/lib/console1984/{supervisor/input_output.rb → input_output.rb} +8 -2
- data/lib/console1984/messages.rb +0 -10
- data/lib/console1984/shield/modes/protected.rb +27 -0
- data/lib/console1984/shield/modes/unprotected.rb +8 -0
- data/lib/console1984/shield/modes.rb +60 -0
- data/lib/console1984/shield.rb +86 -0
- data/lib/console1984/supervisor.rb +24 -33
- data/lib/console1984/version.rb +1 -1
- data/lib/console1984.rb +36 -17
- metadata +61 -14
- data/config/routes.rb +0 -9
- data/lib/console1984/commands.rb +0 -16
- data/lib/console1984/protected_object.rb +0 -15
- data/lib/console1984/supervisor/accesses/protected.rb +0 -12
- data/lib/console1984/supervisor/accesses/unprotected.rb +0 -7
- data/lib/console1984/supervisor/accesses.rb +0 -41
- data/lib/console1984/supervisor/executor.rb +0 -65
- data/lib/console1984/supervisor/protector.rb +0 -55
@@ -0,0 +1,42 @@
|
|
1
|
+
# Prevents loading forbidden classes dynamically.
|
2
|
+
#
|
3
|
+
# There are classes that we don't want to allow loading dynamically
|
4
|
+
# during a console session. For example, we don't want users to reference
|
5
|
+
# the constant +Console1984+. We will prevent a direct constant reference
|
6
|
+
# but users could still do:
|
7
|
+
#
|
8
|
+
# MyConstant = ("Con" + "sole1984").constantize
|
9
|
+
#
|
10
|
+
# We prevent this by extending +Object#const_get+.
|
11
|
+
module Console1984::Ext::Core::Object
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
include Console1984::Freezeable
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
def const_get(*arguments)
|
18
|
+
if Console1984.command_executor.executing_user_command?
|
19
|
+
begin
|
20
|
+
# To validate if it's an invalid constant, we try to declare a class with it.
|
21
|
+
# We essentially leverage Console1984::CommandValidator::ForbiddenReopeningValidation here:
|
22
|
+
# We don't let referencing constants referring modules or classes we don't allow to extend.
|
23
|
+
#
|
24
|
+
# See the list +forbidden_reopening+ in +config/command_protections.yml+.
|
25
|
+
Console1984.command_executor.validate_command("class #{arguments.first}; end")
|
26
|
+
super
|
27
|
+
rescue Console1984::Errors::ForbiddenCommand
|
28
|
+
raise
|
29
|
+
rescue StandardError
|
30
|
+
super
|
31
|
+
end
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def banned_dynamic_constant_declaration?(arguments)
|
40
|
+
Console1984.command_executor.validate_command("class #{arguments.first}; end")
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Add Console 1984 commands to IRB sessions.
|
2
|
+
module Console1984::Ext::Irb::Commands
|
3
|
+
include Console1984::Freezeable
|
4
|
+
|
5
|
+
delegate :shield, to: Console1984
|
6
|
+
|
7
|
+
# Enter {unprotected mode}[rdoc-ref:Console1984::Shield::Modes] mode.
|
8
|
+
def decrypt!
|
9
|
+
shield.enable_unprotected_mode
|
10
|
+
end
|
11
|
+
|
12
|
+
# Enter {protected mode}[rdoc-ref:Console1984::Shield::Modes] mode.
|
13
|
+
def encrypt!
|
14
|
+
shield.enable_protected_mode
|
15
|
+
end
|
16
|
+
end
|
@@ -1,20 +1,20 @@
|
|
1
|
-
|
1
|
+
# Extends IRB execution contexts to hijack execution attempts and
|
2
|
+
# pass them through Console1984.
|
3
|
+
module Console1984::Ext::Irb::Context
|
2
4
|
include Console1984::Freezeable
|
3
5
|
|
4
6
|
# This method is invoked for showing returned objects in the console
|
5
7
|
# Overridden to make sure their evaluation is supervised.
|
6
8
|
def inspect_last_value
|
7
|
-
Console1984.
|
9
|
+
Console1984.command_executor.execute_in_protected_mode do
|
8
10
|
super
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
12
14
|
#
|
13
15
|
def evaluate(line, line_no, exception: nil)
|
14
|
-
Console1984.
|
16
|
+
Console1984.command_executor.execute(Array(line)) do
|
15
17
|
super
|
16
18
|
end
|
17
19
|
end
|
18
|
-
|
19
|
-
include Console1984::Freezeable
|
20
20
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
# Wraps socket methods to execute supervised.
|
2
|
-
module Console1984::
|
1
|
+
# Wraps socket methods to execute supervised when {protected mode}[rdoc-ref:Console1984::Shield::Modes].
|
2
|
+
module Console1984::Ext::Socket::TcpSocket
|
3
3
|
include Console1984::Freezeable
|
4
4
|
|
5
5
|
def write(*args)
|
@@ -28,7 +28,7 @@ module Console1984::ProtectedTcpSocket
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def protected_addresses
|
31
|
-
@protected_addresses ||= Console1984.currently_protected_urls.collect do |url|
|
31
|
+
@protected_addresses ||= Console1984::Shield::Modes::PROTECTED_MODE.currently_protected_urls.collect do |url|
|
32
32
|
host, port = host_and_port_from(url)
|
33
33
|
Array(Addrinfo.getaddrinfo(host, port)).collect { |addrinfo| ComparableAddress.new(addrinfo) if addrinfo.ip_address }
|
34
34
|
end.flatten.compact.uniq
|
@@ -1,7 +1,17 @@
|
|
1
|
-
# Prevents adding new methods to classes
|
1
|
+
# Prevents adding new methods to classes, changing class-state or
|
2
|
+
# accessing/overridden instance variables via reflection. This is meant to
|
3
|
+
# prevent manipulating certain Console1984 classes during a console session.
|
2
4
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
+
# Notice this won't prevent every state-modification command. You should
|
6
|
+
# handle special cases by overriding +#freeze+ (if necessary) and invoking
|
7
|
+
# freezing on the instance when it makes sense.
|
8
|
+
#
|
9
|
+
# For example: check Console1984::Config#freeze and Console1984::Shield#freeze_all.
|
10
|
+
#
|
11
|
+
# The "freezing" doesn't materialize when the mixin is included. When mixed in, it
|
12
|
+
# will store the host class or module in a list. Then a call to Console1984::Freezeable.freeze_all
|
13
|
+
# will look through all the modules/classes freezing them. This way, we can control
|
14
|
+
# the moment where we stop classes from being modifiable at setup time.
|
5
15
|
module Console1984::Freezeable
|
6
16
|
extend ActiveSupport::Concern
|
7
17
|
|
@@ -12,7 +22,7 @@ module Console1984::Freezeable
|
|
12
22
|
end
|
13
23
|
|
14
24
|
class_methods do
|
15
|
-
SENSITIVE_INSTANCE_METHODS = %i[ instance_variable_set ]
|
25
|
+
SENSITIVE_INSTANCE_METHODS = %i[ instance_variable_get instance_variable_set ]
|
16
26
|
|
17
27
|
def prevent_sensitive_overrides
|
18
28
|
SENSITIVE_INSTANCE_METHODS.each do |method|
|
@@ -23,7 +33,7 @@ module Console1984::Freezeable
|
|
23
33
|
private
|
24
34
|
def prevent_sensitive_method(method_name)
|
25
35
|
define_method method_name do |*arguments|
|
26
|
-
raise Console1984::Errors::
|
36
|
+
raise Console1984::Errors::ForbiddenCommand, "You can't invoke #{method_name} on #{self}"
|
27
37
|
end
|
28
38
|
end
|
29
39
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Console1984::
|
1
|
+
module Console1984::InputOutput
|
2
2
|
include Console1984::Freezeable, Console1984::Messages
|
3
3
|
|
4
4
|
private
|
@@ -16,7 +16,13 @@ module Console1984::Supervisor::InputOutput
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def show_commands
|
19
|
-
puts
|
19
|
+
puts <<~TXT
|
20
|
+
|
21
|
+
Commands:
|
22
|
+
|
23
|
+
#{COMMANDS.collect { |command, help_line| "* #{ColorizedString.new(command.to_s).light_blue}: #{help_line}" }.join("\n")}
|
24
|
+
|
25
|
+
TXT
|
20
26
|
end
|
21
27
|
|
22
28
|
def show_warning(message)
|
data/lib/console1984/messages.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'colorized_string'
|
2
|
-
|
3
1
|
module Console1984::Messages
|
4
2
|
DEFAULT_PRODUCTION_DATA_WARNING = <<~TXT
|
5
3
|
|
@@ -19,12 +17,4 @@ module Console1984::Messages
|
|
19
17
|
COMMANDS = {
|
20
18
|
"decrypt!": "enter unprotected mode with access to encrypted information"
|
21
19
|
}
|
22
|
-
|
23
|
-
COMMANDS_HELP = <<~TXT
|
24
|
-
|
25
|
-
Commands:
|
26
|
-
|
27
|
-
#{COMMANDS.collect { |command, help_line| "* #{ColorizedString.new(command.to_s).light_blue}: #{help_line}" }.join("\n")}
|
28
|
-
|
29
|
-
TXT
|
30
20
|
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,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,86 @@
|
|
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
|
+
freeze_all
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def extend_protected_systems
|
27
|
+
extend_irb
|
28
|
+
extend_core_ruby
|
29
|
+
extend_sockets
|
30
|
+
extend_active_record
|
31
|
+
end
|
32
|
+
|
33
|
+
def extend_irb
|
34
|
+
IRB::Context.prepend(Console1984::Ext::Irb::Context)
|
35
|
+
Rails::ConsoleMethods.include(Console1984::Ext::Irb::Commands)
|
36
|
+
end
|
37
|
+
|
38
|
+
def extend_core_ruby
|
39
|
+
Object.prepend Console1984::Ext::Core::Object
|
40
|
+
end
|
41
|
+
|
42
|
+
def extend_sockets
|
43
|
+
socket_classes = [TCPSocket, OpenSSL::SSL::SSLSocket]
|
44
|
+
OpenSSL::SSL::SSLSocket.include(SSLSocketRemoteAddress)
|
45
|
+
|
46
|
+
if defined?(Redis::Connection)
|
47
|
+
socket_classes.push(*[Redis::Connection::TCPSocket, Redis::Connection::SSLSocket])
|
48
|
+
end
|
49
|
+
|
50
|
+
socket_classes.compact.each do |socket_klass|
|
51
|
+
socket_klass.prepend Console1984::Ext::Socket::TcpSocket
|
52
|
+
socket_klass.freeze
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
ACTIVE_RECORD_CONNECTION_ADAPTERS = %w[ActiveRecord::ConnectionAdapters::Mysql2Adapter ActiveRecord::ConnectionAdapters::PostgreSQLAdapter ActiveRecord::ConnectionAdapters::SQLite3Adapter]
|
57
|
+
|
58
|
+
def extend_active_record
|
59
|
+
ACTIVE_RECORD_CONNECTION_ADAPTERS.each do |class_string|
|
60
|
+
if Object.const_defined?(class_string)
|
61
|
+
klass = class_string.constantize
|
62
|
+
klass.prepend(Console1984::Ext::ActiveRecord::ProtectedAuditableTables)
|
63
|
+
klass.include(Console1984::Freezeable)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def freeze_all
|
69
|
+
eager_load_all_classes
|
70
|
+
Console1984.config.freeze unless Console1984.config.test_mode
|
71
|
+
Console1984::Freezeable.freeze_all
|
72
|
+
Parser::CurrentRuby.freeze
|
73
|
+
end
|
74
|
+
|
75
|
+
def eager_load_all_classes
|
76
|
+
Rails.application.eager_load! unless Rails.application.config.eager_load
|
77
|
+
Console1984.class_loader.eager_load
|
78
|
+
end
|
79
|
+
|
80
|
+
module SSLSocketRemoteAddress
|
81
|
+
# Serve remote address as TCPSocket so that our extension works with both.
|
82
|
+
def remote_address
|
83
|
+
Addrinfo.getaddrinfo(hostname, 443).first
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -1,31 +1,43 @@
|
|
1
|
-
require 'colorized_string'
|
2
1
|
require 'rails/console/app'
|
3
2
|
|
4
|
-
#
|
3
|
+
# Entry point to the system. In charge of installing everything
|
4
|
+
# and starting and stopping sessions.
|
5
5
|
class Console1984::Supervisor
|
6
|
-
include
|
6
|
+
include Console1984::Freezeable, Console1984::InputOutput
|
7
7
|
|
8
|
+
delegate :username_resolver, :session_logger, :shield, to: Console1984
|
9
|
+
|
10
|
+
# Installs the console protections.
|
11
|
+
#
|
12
|
+
# See Console1984::Shield
|
8
13
|
def install
|
9
|
-
|
10
|
-
|
14
|
+
require_dependencies
|
15
|
+
shield.install
|
11
16
|
end
|
12
17
|
|
13
|
-
# Starts a console session
|
14
|
-
#
|
15
|
-
# session
|
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].
|
16
22
|
def start
|
17
|
-
|
18
|
-
|
23
|
+
shield.enable_protected_mode(silent: true)
|
19
24
|
show_welcome_message
|
20
|
-
|
21
25
|
start_session
|
22
26
|
end
|
23
27
|
|
28
|
+
# Stops a console session
|
24
29
|
def stop
|
25
30
|
stop_session
|
26
31
|
end
|
27
32
|
|
28
33
|
private
|
34
|
+
def require_dependencies
|
35
|
+
Kernel.silence_warnings do
|
36
|
+
require 'parser/current'
|
37
|
+
end
|
38
|
+
require 'colorized_string'
|
39
|
+
end
|
40
|
+
|
29
41
|
def start_session
|
30
42
|
session_logger.start_session current_username, ask_for_session_reason
|
31
43
|
end
|
@@ -34,28 +46,7 @@ class Console1984::Supervisor
|
|
34
46
|
session_logger.finish_session
|
35
47
|
end
|
36
48
|
|
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
|
-
|
48
|
-
def session_logger
|
49
|
-
Console1984.session_logger
|
50
|
-
end
|
51
|
-
|
52
49
|
def current_username
|
53
|
-
|
50
|
+
username_resolver.current
|
54
51
|
end
|
55
|
-
|
56
|
-
def username_resolver
|
57
|
-
Console1984.username_resolver
|
58
|
-
end
|
59
|
-
|
60
|
-
include Console1984::Freezeable
|
61
52
|
end
|