console1984 0.1.7 → 0.1.8

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: 498f37bd3803f7f76e073d91f51e2dc69fc8a00fc575392c3869b2ae4d65d9bf
4
- data.tar.gz: 1675df589a603042a54e6de8b835e4434f5a112158521379553e9875b25c9eeb
3
+ metadata.gz: bc64d037f2de5570292e0b09710b4543a68ba1af12759150cc68e7b7f4dd6e16
4
+ data.tar.gz: c5929af5061393a32c4df38022535d53c62aaaabe951727798e340322fb0d950
5
5
  SHA512:
6
- metadata.gz: aa0edb371ac31cd2b87e1b8ddb833482516bed91ff3a5c85c12c10c2206ce7dbe8aba514a6a3aecc2e5d594eee527db78b270cd0789484960c7c98e55ca504aa
7
- data.tar.gz: bc595388d7ca433c22a1ccc17a5bb28759cc0b0dd8b5267a061456ad0e17de58be0f95a129689e7fbe88915c709af82d0efd70a3f410f4b40cb0f7e767fa7cb6
6
+ metadata.gz: b64f422bcdd421e7a874af965c9ee615f6d873e0d2a685d68667216fd1ee91d4e8e4815d4dd9e78ef7838a75763fb898be1f328c8466a137f1782a4121531da6
7
+ data.tar.gz: ce7e1f60bfdb666abe3c85a02ddaa1ac25344c994a1471ec64d07cd15f8aac5735bcd0608497e71d9813cf111cdb76f414a2373eb8dbef14b983757ba3876812
data/README.md CHANGED
@@ -106,11 +106,7 @@ irb(main)> Topic.last.name
106
106
  => "{\"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==\"}}"
107
107
  ```
108
108
 
109
- While in protected mode, you can't modify encrypted data, but can save unencrypted attributes normally. If you try to modify an encrypted column it will raise an error:
110
-
111
- ```ruby
112
- irb(main)> Rails.cache.read("some key") # raises Console1984::Errors::ProtectedConnection
113
- ```
109
+ While in protected mode, you can't modify encrypted data, but can save unencrypted attributes normally. If you try to modify an encrypted column it will raise an error.
114
110
 
115
111
  ### Access to external systems
116
112
 
@@ -122,7 +118,13 @@ To protect the access to such systems, you can add their URLs to `config.console
122
118
  config.console1984.protected_urls = [ "https://my-app-us-east-1-whatever.us-east-1.es.amazonaws.com", "redis://my-app-cache-1.whatever.cache.amazonaws.com:6379" ]
123
119
  ```
124
120
 
125
- As with encryption data, running `decrypt!` will let you access these systems normally. The system will ask for a justfication and will flag those accesses as sensitive.
121
+ In the default protected mode, trying to read data from a protected system will be aborted with an error:
122
+
123
+ ```ruby
124
+ irb(main)> Rails.cache.read("some key") # raises Console1984::Errors::ProtectedConnection
125
+ ```
126
+
127
+ Running `decrypt!` will switch you to unprotected mode and let you access these systems normally. The system will ask for a justfication and will flag those accesses as sensitive.
126
128
 
127
129
  This will work for systems that use Ruby sockets as the underlying communication mechanism.
128
130
 
@@ -0,0 +1,30 @@
1
+ static_validations:
2
+ forbidden_reopening:
3
+ - ActiveRecord
4
+ - Console1984
5
+ - PG
6
+ - Mysql2
7
+ forbidden_constant_reference:
8
+ always:
9
+ - Console1984
10
+ protected:
11
+ - PG
12
+ - Mysql2
13
+ - ActiveRecord::ActiveRecordEncryption
14
+ suspicious_terms:
15
+ - console_1984
16
+ - Console1984
17
+ - secret
18
+ - credentials
19
+ forbidden_methods:
20
+ always:
21
+ user:
22
+ Kernel:
23
+ - eval
24
+ Object:
25
+ - eval
26
+ BasicObject:
27
+ - eval
28
+ - instance_eval
29
+ Module:
30
+ - class_eval
@@ -19,13 +19,11 @@ class Console1984::CommandExecutor
19
19
  run_as_system { session_logger.before_executing commands }
20
20
  validate_command commands
21
21
  execute_in_protected_mode(&block)
22
- rescue Console1984::Errors::ForbiddenCommand, FrozenError
22
+ rescue Console1984::Errors::ForbiddenCommand, FrozenError => e
23
23
  flag_suspicious(commands)
24
24
  rescue Console1984::Errors::SuspiciousCommand
25
25
  flag_suspicious(commands)
26
26
  execute_in_protected_mode(&block)
27
- rescue FrozenError
28
- flag_suspicious(commands)
29
27
  ensure
30
28
  run_as_system { session_logger.after_executing commands }
31
29
  end
@@ -68,14 +66,12 @@ class Console1984::CommandExecutor
68
66
  end
69
67
 
70
68
  private
71
- COMMAND_VALIDATOR_CONFIG_FILE_PATH = Console1984::Engine.root.join("config/command_protections.yml")
72
-
73
69
  def command_validator
74
70
  @command_validator ||= build_command_validator
75
71
  end
76
72
 
77
73
  def build_command_validator
78
- Console1984::CommandValidator.from_config(YAML.safe_load(File.read(COMMAND_VALIDATOR_CONFIG_FILE_PATH)).symbolize_keys)
74
+ Console1984::CommandValidator.from_config(Console1984.protections_config.static_validations)
79
75
  end
80
76
 
81
77
  def flag_suspicious(commands)
@@ -24,7 +24,7 @@ class Console1984::CommandValidator::ForbiddenConstantReferenceValidation
24
24
 
25
25
  private
26
26
  def contains_invalid_const_reference?(parsed_command, banned_constants)
27
- parsed_command.constants.find do |constant_name|
27
+ (parsed_command.constants + parsed_command.constant_assignments).find do |constant_name|
28
28
  banned_constants.find { |banned_constant| "#{constant_name}::".start_with?("#{banned_constant}::") }
29
29
  end
30
30
  end
@@ -18,7 +18,7 @@ class Console1984::CommandValidator::ForbiddenReopeningValidation
18
18
 
19
19
  private
20
20
  def contains_invalid_class_or_module_declaration?(parsed_command)
21
- parsed_command.declared_classes_or_modules.find { |class_or_module_name| banned?(class_or_module_name) }
21
+ (parsed_command.declared_classes_or_modules + parsed_command.constant_assignments).find { |class_or_module_name| banned?(class_or_module_name) }
22
22
  end
23
23
 
24
24
  def banned?(class_or_module_name)
@@ -6,7 +6,7 @@ class Console1984::CommandValidator::ParsedCommand
6
6
 
7
7
  attr_reader :raw_command
8
8
 
9
- delegate :declared_classes_or_modules, :constants, to: :processed_ast
9
+ delegate :declared_classes_or_modules, :constants, :constant_assignments, to: :processed_ast
10
10
 
11
11
  def initialize(raw_command)
12
12
  @raw_command = Array(raw_command).join("\n")
@@ -26,20 +26,33 @@ class Console1984::CommandValidator::ParsedCommand
26
26
  include AST::Processor::Mixin
27
27
  include Console1984::Freezeable
28
28
 
29
- attr_reader :constants, :declared_classes_or_modules
30
-
31
29
  def initialize
32
30
  @constants = []
33
31
  @declared_classes_or_modules = []
32
+ @constant_assignments = []
33
+ end
34
+
35
+ # We define accessors to define lists without duplicates. We are not using a +SortedSet+ because we want
36
+ # to mutate strings in the list while the processing is happening. And we don't use metapgroamming to define the
37
+ # accessors to prevent having problems with freezable and its instance_variable* protection.
38
+
39
+ def constants
40
+ @constants.uniq
41
+ end
42
+
43
+ def declared_classes_or_modules
44
+ @declared_classes_or_modules.uniq
45
+ end
46
+
47
+ def constant_assignments
48
+ @constant_assignments.uniq
34
49
  end
35
50
 
36
51
  def on_class(node)
37
52
  super
38
53
  const_declaration, _, _ = *node
39
-
40
- processor = self.class.new
41
- processor.process(const_declaration)
42
- @declared_classes_or_modules << processor.constants.first if processor.constants.present?
54
+ constant = extract_constants(const_declaration).first
55
+ @declared_classes_or_modules << constant if constant.present?
43
56
  end
44
57
 
45
58
  alias_method :on_module, :on_class
@@ -60,5 +73,18 @@ class Console1984::CommandValidator::ParsedCommand
60
73
  last_constant << "::#{const_name}"
61
74
  end
62
75
  end
76
+
77
+ def on_casgn(node)
78
+ super
79
+ scope_node, name, value_node = *node
80
+ @constant_assignments.push(*extract_constants(value_node))
81
+ end
82
+
83
+ private
84
+ def extract_constants(node)
85
+ self.class.new.tap do |processor|
86
+ processor.process(node)
87
+ end.constants
88
+ end
63
89
  end
64
90
  end
@@ -4,11 +4,14 @@
4
4
  class Console1984::Config
5
5
  include Console1984::Freezeable, Console1984::Messages
6
6
 
7
+ PROTECTIONS_CONFIG_FILE_PATH = Console1984::Engine.root.join("config/protections.yml")
8
+
7
9
  PROPERTIES = %i[
8
10
  session_logger username_resolver shield command_executor
9
11
  protected_environments protected_urls
10
12
  production_data_warning enter_unprotected_encryption_mode_warning enter_protected_mode_warning
11
13
  incinerate incinerate_after incineration_queue
14
+ protections_config
12
15
  debug test_mode
13
16
  ]
14
17
 
@@ -24,9 +27,14 @@ class Console1984::Config
24
27
  end
25
28
  end
26
29
 
30
+ # Initialize lazily so that it only gets instantiated during console sessions
31
+ def protections_config
32
+ @protections_config ||= Console1984::ProtectionsConfig.new(YAML.safe_load(File.read(PROTECTIONS_CONFIG_FILE_PATH)).symbolize_keys)
33
+ end
34
+
27
35
  def freeze
28
36
  super
29
- [ protected_urls ].each(&:freeze)
37
+ [ protected_urls, protections_config ].each(&:freeze)
30
38
  end
31
39
 
32
40
  private
@@ -10,7 +10,7 @@ module Console1984
10
10
 
11
11
  initializer "console1984.config" do
12
12
  config.console1984.each do |key, value|
13
- Console1984.config.send("#{key}=", value) unless %i[ protected_urls protected_environments ].include?(key.to_sym)
13
+ Console1984.config.send("#{key}=", value)
14
14
  end
15
15
  end
16
16
 
@@ -19,7 +19,7 @@ module Console1984::Ext::ActiveRecord::ProtectedAuditableTables
19
19
  end
20
20
 
21
21
  def auditable_tables
22
- @auditable_tables ||= auditable_models.collect(&:table_name)
22
+ @auditable_tables ||= Console1984.command_executor.run_as_system { auditable_models.collect(&:table_name) }
23
23
  end
24
24
 
25
25
  def auditable_models
@@ -0,0 +1,15 @@
1
+ # Extends +Module+ to prevent invoking class_eval in user commands.
2
+ #
3
+ # We don't use the built-in configurable system from protections.yml because we use
4
+ # class_eval ourselves to implement it!
5
+ module Console1984::Ext::Core::Module
6
+ extend ActiveSupport::Concern
7
+
8
+ def instance_eval(*)
9
+ if Console1984.command_executor.executing_user_command?
10
+ raise Console1984::Errors::ForbiddenCommand
11
+ else
12
+ super
13
+ end
14
+ end
15
+ end
@@ -12,6 +12,7 @@ module Console1984::Ext::Core::Object
12
12
  extend ActiveSupport::Concern
13
13
 
14
14
  include Console1984::Freezeable
15
+ self.prevent_instance_data_manipulation_after_freezing = false
15
16
 
16
17
  class_methods do
17
18
  def const_get(*arguments)
@@ -28,12 +28,16 @@ module Console1984::Ext::Socket::TcpSocket
28
28
  end
29
29
 
30
30
  def protected_addresses
31
- @protected_addresses ||= Console1984::Shield::Modes::PROTECTED_MODE.currently_protected_urls.collect do |url|
31
+ @protected_addresses ||= 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
35
35
  end
36
36
 
37
+ def protected_urls
38
+ Console1984::Shield::Modes::PROTECTED_MODE.currently_protected_urls || []
39
+ end
40
+
37
41
  def host_and_port_from(url)
38
42
  URI(url).then do |parsed_uri|
39
43
  if parsed_uri.host
@@ -13,18 +13,24 @@
13
13
  # will look through all the modules/classes freezing them. This way, we can control
14
14
  # the moment where we stop classes from being modifiable at setup time.
15
15
  module Console1984::Freezeable
16
- extend ActiveSupport::Concern
17
-
18
16
  mattr_reader :to_freeze, default: Set.new
19
17
 
20
- included do
21
- Console1984::Freezeable.to_freeze << self
18
+ # Not using ActiveSupport::Concern because a bunch of classes skip its +.invoked+ hook which
19
+ # is terrible for our purposes. This happened because it was being included in parent classes
20
+ # (such as Object), so it was skipping the include block.
21
+ def self.included(base)
22
+ Console1984::Freezeable.to_freeze << base
23
+ base.extend ClassMethods
24
+
25
+ # Flag to control manipulating instance data via instance_variable_get and instance_variable_set.
26
+ # true by default.
27
+ base.thread_mattr_accessor :prevent_instance_data_manipulation_after_freezing, default: true
22
28
  end
23
29
 
24
- class_methods do
30
+ module ClassMethods
25
31
  SENSITIVE_INSTANCE_METHODS = %i[ instance_variable_get instance_variable_set ]
26
32
 
27
- def prevent_sensitive_overrides
33
+ def prevent_instance_data_manipulation
28
34
  SENSITIVE_INSTANCE_METHODS.each do |method|
29
35
  prevent_sensitive_method method
30
36
  end
@@ -51,7 +57,7 @@ module Console1984::Freezeable
51
57
  end
52
58
 
53
59
  def freeze_class_or_module(class_or_module)
54
- class_or_module.prevent_sensitive_overrides
60
+ class_or_module.prevent_instance_data_manipulation if class_or_module.prevent_instance_data_manipulation_after_freezing
55
61
  class_or_module.freeze
56
62
  end
57
63
 
@@ -0,0 +1,17 @@
1
+ class Console1984::ProtectionsConfig
2
+ include Console1984::Freezeable
3
+
4
+ delegate :static_validations, to: :instance
5
+
6
+ attr_reader :config
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ %i[ static_validations forbidden_methods ].each do |method_name|
13
+ define_method method_name do
14
+ config[method_name].symbolize_keys
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,32 @@
1
+ # Freezes classes to prevent tampering them
2
+ class Console1984::Refrigerator
3
+ include Console1984::Freezeable
4
+
5
+ def freeze_all
6
+ eager_load_all_classes
7
+ freeze_internal_instances # internal modules and classes are frozen by including Console1984::Freezable
8
+ freeze_external_modules_and_classes
9
+
10
+ Console1984::Freezeable.freeze_all
11
+ end
12
+
13
+ private
14
+ EXTERNAL_MODULES_AND_CLASSES_TO_FREEZE = [Parser::CurrentRuby]
15
+
16
+ def freeze_internal_instances
17
+ Console1984.config.freeze unless Console1984.config.test_mode
18
+ end
19
+
20
+ def freeze_external_modules_and_classes
21
+ EXTERNAL_MODULES_AND_CLASSES_TO_FREEZE.each { |klass| klass.include(Console1984::Freezeable) }
22
+ end
23
+
24
+ def eager_load_all_classes
25
+ Rails.application.eager_load! unless Rails.application.config.eager_load
26
+ Console1984.class_loader.eager_load
27
+ end
28
+ end
29
+
30
+ class Parser::Ruby27
31
+ include Console1984::Freezeable
32
+ end
@@ -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
@@ -19,7 +19,9 @@ class Console1984::Shield
19
19
  # that aren't mean to be modified once the console is running.
20
20
  def install
21
21
  extend_protected_systems
22
- freeze_all
22
+ prevent_invoking_protected_methods
23
+
24
+ refrigerator.freeze_all
23
25
  end
24
26
 
25
27
  private
@@ -37,6 +39,7 @@ class Console1984::Shield
37
39
 
38
40
  def extend_core_ruby
39
41
  Object.prepend Console1984::Ext::Core::Object
42
+ Module.prepend Console1984::Ext::Core::Module
40
43
  end
41
44
 
42
45
  def extend_sockets
@@ -65,16 +68,12 @@ class Console1984::Shield
65
68
  end
66
69
  end
67
70
 
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
71
+ def prevent_invoking_protected_methods
72
+ MethodInvocationShell.install_for(Console1984.protections_config.forbidden_methods)
73
73
  end
74
74
 
75
- def eager_load_all_classes
76
- Rails.application.eager_load! unless Rails.application.config.eager_load
77
- Console1984.class_loader.eager_load
75
+ def refrigerator
76
+ @refrigerator ||= Console1984::Refrigerator.new
78
77
  end
79
78
 
80
79
  module SSLSocketRemoteAddress
@@ -1,3 +1,3 @@
1
1
  module Console1984
2
- VERSION = '0.1.7'
2
+ VERSION = '0.1.8'
3
3
  end
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.7
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-09-04 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
@@ -195,7 +195,7 @@ files:
195
195
  - app/models/console1984/session.rb
196
196
  - app/models/console1984/session/incineratable.rb
197
197
  - app/models/console1984/user.rb
198
- - config/command_protections.yml
198
+ - config/protections.yml
199
199
  - db/migrate/20210517203931_create_console1984_tables.rb
200
200
  - lib/console1984.rb
201
201
  - lib/console1984/command_executor.rb
@@ -208,6 +208,7 @@ files:
208
208
  - lib/console1984/engine.rb
209
209
  - lib/console1984/errors.rb
210
210
  - lib/console1984/ext/active_record/protected_auditable_tables.rb
211
+ - lib/console1984/ext/core/module.rb
211
212
  - lib/console1984/ext/core/object.rb
212
213
  - lib/console1984/ext/irb/commands.rb
213
214
  - lib/console1984/ext/irb/context.rb
@@ -215,8 +216,11 @@ files:
215
216
  - lib/console1984/freezeable.rb
216
217
  - lib/console1984/input_output.rb
217
218
  - lib/console1984/messages.rb
219
+ - lib/console1984/protections_config.rb
220
+ - lib/console1984/refrigerator.rb
218
221
  - lib/console1984/sessions_logger/database.rb
219
222
  - lib/console1984/shield.rb
223
+ - lib/console1984/shield/method_invocation_shell.rb
220
224
  - lib/console1984/shield/modes.rb
221
225
  - lib/console1984/shield/modes/protected.rb
222
226
  - lib/console1984/shield/modes/unprotected.rb
@@ -1,17 +0,0 @@
1
- forbidden_reopening:
2
- - ActiveRecord
3
- - Console1984
4
- - PG
5
- - Mysql2
6
- forbidden_constant_reference:
7
- always:
8
- - Console1984
9
- protected:
10
- - PG
11
- - Mysql2
12
- - ActiveRecord::ActiveRecordEncryption
13
- suspicious_terms:
14
- - console_1984
15
- - Console1984
16
- - secret
17
- - credentials