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 +4 -4
- data/README.md +6 -2
- data/lib/console1984/commands.rb +2 -2
- data/lib/console1984/config.rb +4 -4
- data/lib/console1984/engine.rb +3 -7
- data/lib/console1984/errors.rb +1 -1
- data/lib/console1984/freezeable.rb +54 -0
- data/lib/console1984/protected_auditable_tables.rb +23 -22
- data/lib/console1984/protected_context.rb +3 -1
- data/lib/console1984/protected_object.rb +15 -0
- data/lib/console1984/protected_tcp_socket.rb +3 -1
- data/lib/console1984/sessions_logger/database.rb +3 -1
- data/lib/console1984/supervisor/accesses/protected.rb +2 -0
- data/lib/console1984/supervisor/accesses/unprotected.rb +2 -0
- data/lib/console1984/supervisor/accesses.rb +1 -1
- data/lib/console1984/supervisor/executor.rb +28 -4
- data/lib/console1984/supervisor/input_output.rb +1 -1
- data/lib/console1984/supervisor/protector.rb +18 -0
- data/lib/console1984/supervisor.rb +18 -4
- data/lib/console1984/username/env_resolver.rb +2 -0
- data/lib/console1984/version.rb +1 -1
- data/lib/console1984.rb +7 -4
- metadata +4 -3
- data/lib/console1984/frozen_methods.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44d77add0193cc1bb27955cb70d0d3e58153a17524e6d3b93a15179700322c58
|
4
|
+
data.tar.gz: b36e25eb75d6b1d113ae3653118115da747122e10af23d38ea00ebd72f3427cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
![
|
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)
|
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`:
|
data/lib/console1984/commands.rb
CHANGED
@@ -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
|
data/lib/console1984/config.rb
CHANGED
@@ -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
|
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
|
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.
|
47
|
+
self.test_mode = false
|
48
48
|
end
|
49
49
|
end
|
data/lib/console1984/engine.rb
CHANGED
@@ -17,13 +17,9 @@ module Console1984
|
|
17
17
|
console do
|
18
18
|
Console1984.config.set_from(config.console1984)
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
data/lib/console1984/errors.rb
CHANGED
@@ -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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
16
|
-
|
16
|
+
private
|
17
|
+
def auditable_tables_regexp
|
18
|
+
@auditable_tables_regexp ||= Regexp.new("#{auditable_tables.join("|")}")
|
19
|
+
end
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
def auditable_tables
|
22
|
+
@auditable_tables ||= auditable_models.collect(&:table_name)
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
end
|
25
|
+
def auditable_models
|
26
|
+
@auditable_models ||= Console1984::Base.descendants
|
27
|
+
end
|
27
28
|
|
28
|
-
|
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::
|
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::
|
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!
|
10
|
+
@current_session = user.sessions.create!(reason: reason)
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
@@ -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::
|
8
|
-
|
9
|
-
|
10
|
-
|
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,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,
|
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::
|
60
|
+
include Console1984::Freezeable
|
47
61
|
end
|
data/lib/console1984/version.rb
CHANGED
data/lib/console1984.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
require 'console1984/engine'
|
2
2
|
|
3
3
|
require "zeitwerk"
|
4
|
-
|
5
|
-
|
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
|
-
|
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
|
+
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-
|
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/
|
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
|