console1984 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 985b93be5f065f2f12ec2e121a84d1951aa8d2d0f4e8bf628dccd4680a3581d1
4
+ data.tar.gz: e5e40eadddc7f44e25e1384c948897058b8fd2ddacafb6f4b2ef3f63e7cc20e8
5
+ SHA512:
6
+ metadata.gz: b6d2d32d9210d20a082e7844d873e514ba61ec9e18546d4d8e353ed8a1360685c0bcde0406140c635a79b2fb34519148c988e7e5ff1a3579afb97120f5930330
7
+ data.tar.gz: 7b32ac9d5cd80e4f7840d12c803122ddc521f5256028e03f6f0cd401081c12dee755934393cb005d26ebe5a30a24dfc79b934acb2ee1a6d44dfa9c978137f833
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Jorge Manrubia
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Console1984
2
+
3
+ A Rails Console that audits commands and protects users privacy.
4
+
5
+ > “If you want to keep a secret, you must also hide it from yourself.”
6
+ >
7
+ > ― George Orwell, 1984
8
+
9
+ ## Usage
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'console1984'
15
+ ```
16
+
17
+ By default, `console1984` will only work in `production`. [You can configure other environments](#protected-environments).
18
+
19
+ ## Features
20
+
21
+ ### Auditing
22
+
23
+ The console will ask for a reason for the console session, identifying the user via the environment
24
+ variable `CONSOLE_USER`.
25
+
26
+ After that, every command the user types will be captured and logged. `console1984` uses
27
+ [`rails-structured-logggin`](https://github.com/basecamp/rails-structured-logging) to form
28
+ a JSON entry that looks like this:
29
+
30
+ ```json
31
+ {
32
+ "@timestamp": "2020-05-15T15:05:45.845642+02:00",
33
+ "ecs": {
34
+ "version": "1.2.0"
35
+ },
36
+ "event": {
37
+ "action": "console.audit_trail",
38
+ "duration": {
39
+ "ms": 0.01
40
+ }
41
+ },
42
+ "console": {
43
+ "user": "Jorge",
44
+ "reason": "fix something",
45
+ "commands": "Account.first\n"
46
+ },
47
+ "rails": {
48
+ "application": "haystack",
49
+ "env": "beta"
50
+ },
51
+ "ruby": {
52
+ "allocations": {
53
+ "count": 0
54
+ }
55
+ },
56
+ "process": {
57
+ "pid": 8539,
58
+ "name": "rails_console",
59
+ "working_directory": "/Users/jorge/Work/basecamp/haystack"
60
+ },
61
+ "performance": {
62
+ "time": {
63
+ "cpu": {
64
+ "ms": 0.01
65
+ },
66
+ "idle": {
67
+ "ms": 0.0
68
+ }
69
+ }
70
+ },
71
+ "original": " Account Load (1.0ms) SELECT `accounts`.* FROM `accounts` ORDER BY `accounts`.`id` ASC LIMIT 1\n"
72
+ }
73
+ ```
74
+
75
+ ## Configuration
76
+
77
+ ### Protected environments
78
+
79
+ <a name="protected-environments"></a>
80
+
81
+ By default, `console1984` will only be enabled in `production`. You can configure the target environments with
82
+ `config.console1984.protected_environments`:
83
+
84
+ ```ruby
85
+ config.console1984.protected_environments = %i[ staging production ]
86
+ ```
87
+
88
+ ### Audit logger
89
+
90
+ By default, the console will output JSON entries for audit trails to STDOUT. You can configure the
91
+ used logger with `config.console1984.audit_logger`:
92
+
93
+ ```ruby
94
+ config.console1984.audit_logger = ActiveSupport::Logger.new("log/console.txt")
95
+ ```
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Console1984'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,17 @@
1
+ module Console1984
2
+ class IncinerationJob < ActiveJob::Base
3
+ queue_as { Console1984.incineration_queue }
4
+
5
+ discard_on ActiveRecord::RecordNotFound
6
+
7
+ def self.schedule(session)
8
+ set(wait: Console1984.incinerate_after).perform_later(session)
9
+ end
10
+
11
+ def perform(session)
12
+ session.incinerate
13
+ end
14
+ end
15
+ end
16
+
17
+ ActiveSupport.run_load_hooks(:console_1984_incineration_job, Console1984::IncinerationJob)
@@ -0,0 +1,7 @@
1
+ module Console1984
2
+ class Base < ApplicationRecord
3
+ self.abstract_class = true
4
+ end
5
+ end
6
+
7
+ ActiveSupport.run_load_hooks(:console_1984_base, Console1984::Base)
@@ -0,0 +1,14 @@
1
+ module Console1984
2
+ class Command < Base
3
+ belongs_to :session
4
+ belongs_to :sensitive_access, optional: true
5
+
6
+ encrypts :statements
7
+
8
+ scope :sorted_chronologically, -> { order(created_at: :asc, id: :asc) }
9
+
10
+ def sensitive?
11
+ sensitive_access.present?
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ module Console1984
2
+ class SensitiveAccess < Base
3
+ belongs_to :session
4
+ has_many :commands
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ module Console1984
2
+ class Session < Base
3
+ include Incineratable
4
+
5
+ belongs_to :user
6
+ has_many :commands, dependent: :destroy
7
+ has_many :sensitive_accesses, dependent: :destroy
8
+
9
+ def sensitive?
10
+ sensitive_accesses.any?
11
+ end
12
+ end
13
+ end
14
+
15
+ ActiveSupport.run_load_hooks(:console_1984_session, Console1984::Session)
@@ -0,0 +1,30 @@
1
+ module Console1984::Session::Incineratable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ after_create_commit :incinerate_later, if: -> { Console1984.incinerate }
6
+ end
7
+
8
+ def incinerate_later
9
+ Console1984::IncinerationJob.schedule self
10
+ end
11
+
12
+ def incinerate
13
+ if incineratable?
14
+ destroy
15
+ else
16
+ raise Console1984::Errors::ForbiddenIncineration,
17
+ "Session #{id} was created at #{created_at.utc}. It shouldn't be deleted"\
18
+ " until #{earliest_possible_incineration_date.utc}, and now it's #{Time.now.utc}"
19
+ end
20
+ end
21
+
22
+ private
23
+ def incineratable?
24
+ Time.now >= earliest_possible_incineration_date
25
+ end
26
+
27
+ def earliest_possible_incineration_date
28
+ created_at + Console1984.incinerate_after
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ module Console1984
2
+ class User < Base
3
+ has_many :sessions, dependent: :destroy
4
+ end
5
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ Console1984::Engine.routes.draw do
2
+ resources :sessions, only: %i[ index show ] do
3
+ resources :audits, only: %i[ create update ]
4
+ end
5
+
6
+ resource :filtered_sessions, only: %i[ update ]
7
+
8
+ root to: "sessions#index"
9
+ end
@@ -0,0 +1,35 @@
1
+ class CreateConsole1984Tables < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :console1984_sessions do |t|
4
+ t.text :reason
5
+ t.references :user, null: false
6
+ t.timestamps
7
+
8
+ t.index :created_at
9
+ t.index [ :user_id, :created_at ]
10
+ end
11
+
12
+ create_table :console1984_users do |t|
13
+ t.string :username, null: false
14
+ t.timestamps
15
+
16
+ t.index [:username]
17
+ end
18
+
19
+ create_table :console1984_commands do |t|
20
+ t.text :statements
21
+ t.references :sensitive_access
22
+ t.references :session, null: false
23
+ t.timestamps
24
+
25
+ t.index [ :session_id, :created_at, :sensitive_access_id ], name: "on_session_and_sensitive_chronologically"
26
+ end
27
+
28
+ create_table :console1984_sensitive_accesses do |t|
29
+ t.text :justification
30
+ t.references :session, null: false
31
+
32
+ t.timestamps
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,86 @@
1
+ require 'console1984/engine'
2
+
3
+ require "zeitwerk"
4
+ loader = Zeitwerk::Loader.for_gem
5
+ loader.setup
6
+
7
+ module Console1984
8
+ include Messages
9
+
10
+ mattr_accessor :supervisor
11
+ mattr_accessor :session_logger
12
+ mattr_accessor :username_resolver
13
+
14
+ mattr_accessor :protected_environments
15
+ mattr_reader :protected_urls, default: []
16
+
17
+ mattr_reader :production_data_warning, default: DEFAULT_PRODUCTION_DATA_WARNING
18
+ mattr_reader :enter_unprotected_encryption_mode_warning, default: DEFAULT_ENTER_UNPROTECTED_ENCRYPTION_MODE_WARNING
19
+ mattr_reader :enter_protected_mode_warning, default: DEFAULT_ENTER_PROTECTED_MODE_WARNING
20
+
21
+ mattr_accessor :incinerate, default: true
22
+ mattr_accessor :incinerate_after, default: 30.days
23
+ mattr_accessor :incineration_queue, default: "console1984_incineration"
24
+
25
+ mattr_accessor :debug, default: false
26
+
27
+ thread_mattr_accessor :currently_protected_urls, default: []
28
+
29
+ class << self
30
+ def install_support(config)
31
+ self.protected_environments ||= config.protected_environments
32
+ self.protected_urls.push(*config.protected_urls)
33
+ self.session_logger = config.session_logger || Console1984::SessionsLogger::Database.new
34
+ self.username_resolver = config.username_resolver || Console1984::Username::EnvResolver.new("CONSOLE_USER")
35
+
36
+ self.supervisor = Supervisor.new
37
+ self.protected_urls.freeze
38
+
39
+ extend_protected_systems
40
+ end
41
+
42
+ def running_protected_environment?
43
+ protected_environments.collect(&:to_sym).include?(Rails.env.to_sym)
44
+ end
45
+
46
+ def protecting(&block)
47
+ protecting_connections do
48
+ ActiveRecord::Encryption.protecting_encrypted_data(&block)
49
+ end
50
+ end
51
+
52
+ private
53
+ def extend_protected_systems
54
+ extend_active_record
55
+ extend_socket_classes
56
+ end
57
+
58
+ def extend_active_record
59
+ %w[ActiveRecord::ConnectionAdapters::Mysql2Adapter ActiveRecord::ConnectionAdapters::PostgreSQLAdapter ActiveRecord::ConnectionAdapters::SQLite3Adapter].each do |class_string|
60
+ if Object.const_defined?(class_string)
61
+ klass = class_string.constantize
62
+ klass.prepend(Console1984::ProtectedAuditableTables)
63
+ end
64
+ end
65
+ end
66
+
67
+ def extend_socket_classes
68
+ socket_classes = [TCPSocket, OpenSSL::SSL::SSLSocket]
69
+ if defined?(Redis::Connection)
70
+ socket_classes.push(*[Redis::Connection::TCPSocket, Redis::Connection::SSLSocket])
71
+ end
72
+
73
+ socket_classes.compact.each do |socket_klass|
74
+ socket_klass.prepend Console1984::ProtectedTcpSocket
75
+ end
76
+ end
77
+
78
+ def protecting_connections
79
+ old_currently_protected_urls = self.currently_protected_urls
80
+ self.currently_protected_urls = protected_urls
81
+ yield
82
+ ensure
83
+ self.currently_protected_urls = old_currently_protected_urls
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,14 @@
1
+ module Console1984::Commands
2
+ def decrypt!
3
+ supervisor.enable_access_to_encrypted_content
4
+ end
5
+
6
+ def encrypt!
7
+ supervisor.disable_access_to_encrypted_content
8
+ end
9
+
10
+ private
11
+ def supervisor
12
+ Console1984.supervisor
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ require 'irb'
2
+
3
+ module Console1984
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Console1984
6
+
7
+ config.console1984 = ActiveSupport::OrderedOptions.new
8
+ config.console1984.protected_environments ||= %i[ production ]
9
+ config.console1984.protected_urls ||= []
10
+
11
+ initializer "console1984.config" do
12
+ config.console1984.each do |key, value|
13
+ Console1984.send("#{key}=", value) unless %i[ protected_urls protected_environments ].include?(key.to_sym)
14
+ end
15
+ end
16
+
17
+ console do
18
+ Console1984.install_support(config.console1984)
19
+ Console1984.supervisor.start if Console1984.running_protected_environment?
20
+
21
+ class OpenSSL::SSL::SSLSocket
22
+ # Make it serve remote address as TCPSocket so that our extension works for it
23
+ def remote_address
24
+ Addrinfo.getaddrinfo(hostname, 443).first
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ class Console1984::EnvVariableUsername
2
+ def initialize(key)
3
+ @username = ENV[key]
4
+ end
5
+
6
+ def current_user_name
7
+ @username
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Console1984
2
+ module Errors
3
+ class ProtectedConnection < StandardError
4
+ def initialize(details)
5
+ super "A connection attempt was prevented because it represents a sensitive access."\
6
+ "Please run decrypt! and try again. You will be asked to justify this access: #{details}"
7
+ end
8
+ end
9
+
10
+ class ForbiddenCommand < StandardError; end
11
+ class ForbiddenIncineration < StandardError; end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ require 'colorized_string'
2
+
3
+ module Console1984::Messages
4
+ DEFAULT_PRODUCTION_DATA_WARNING = <<~TXT
5
+
6
+ You have access to production data here. That's a big deal. As part of our promise to keep customer data safe and private, we audit the commands you type here. Let's get started!
7
+ TXT
8
+
9
+ DEFAULT_ENTER_UNPROTECTED_ENCRYPTION_MODE_WARNING = <<~TXT
10
+ Ok! You have access to encrypted information now. We pay extra close attention to any commands entered while you have this access. You can go back to protected mode with 'encrypt!'
11
+
12
+ WARNING: Make sure you don't save objects that were loaded while in protected mode, as this can result in saving the encrypted texts.
13
+ TXT
14
+
15
+ DEFAULT_ENTER_PROTECTED_MODE_WARNING = <<~TXT
16
+ Great! You are back in protected mode. When we audit, we may reach out for a conversation about the commands you entered. What went well? Did you solve the problem without accessing personal data?
17
+ TXT
18
+
19
+ COMMANDS = {
20
+ "decrypt!": "enter unprotected mode with access to encrypted information",
21
+ "log '<reason>'": "provide further information about what you are going to do in the middle of a console session"
22
+ }
23
+
24
+ COMMANDS_HELP = <<~TXT
25
+
26
+ Commands:
27
+
28
+ #{COMMANDS.collect { |command, help_line| "* #{ColorizedString.new(command.to_s).light_blue}: #{help_line}" }.join("\n")}
29
+
30
+ TXT
31
+ end
@@ -0,0 +1,26 @@
1
+ module Console1984
2
+ module ProtectedAuditableTables
3
+ %i[ execute exec_query exec_insert exec_delete exec_update exec_insert_all ].each do |method|
4
+ define_method method do |*args|
5
+ sql = args.first
6
+ if Console1984.supervisor.executing_user_command? && sql =~ auditable_tables_regexp
7
+ raise Console1984::Errors::ForbiddenCommand, "#{sql}"
8
+ else
9
+ super(*args)
10
+ end
11
+ end
12
+ end
13
+
14
+ private
15
+ AUDITABLE_MODELS = [ Console1984::User, Console1984::Session, Console1984::Command, Console1984::SensitiveAccess ]
16
+
17
+ def auditable_tables_regexp
18
+ @auditable_tables_regexp ||= Regexp.new("#{auditable_tables.join("|")}")
19
+ end
20
+
21
+ def auditable_tables
22
+ # TODO: Not using Console1984::Base.descendants during development to make this work without eager loading
23
+ @auditable_tables ||= AUDITABLE_MODELS.collect(&:table_name)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ module Console1984::ProtectedContext
2
+ # Protect the code to show inspected objects too. This method is invoked
3
+ # for showing returned objects in the console
4
+ def inspect_last_value
5
+ Console1984.supervisor.execute do
6
+ super
7
+ end
8
+ end
9
+
10
+ def evaluate(line, line_no, exception: nil)
11
+ Console1984.supervisor.execute_supervised(Array(line)) do
12
+ super
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,56 @@
1
+ module Console1984::ProtectedTcpSocket
2
+ def write(*args)
3
+ protecting do
4
+ super
5
+ end
6
+ end
7
+
8
+ def write_nonblock(*args)
9
+ protecting do
10
+ super
11
+ end
12
+ end
13
+
14
+ private
15
+ def protecting
16
+ if protected?
17
+ raise Console1984::Errors::ProtectedConnection, remote_address.inspect
18
+ else
19
+ yield
20
+ end
21
+ end
22
+
23
+ def protected?
24
+ protected_addresses&.include?(ComparableAddress.new(remote_address))
25
+ end
26
+
27
+ def protected_addresses
28
+ @protected_addresses ||= Console1984.currently_protected_urls.collect do |url|
29
+ host, port = host_and_port_from(url)
30
+ Array(Addrinfo.getaddrinfo(host, port)).collect { |addrinfo| ComparableAddress.new(addrinfo) if addrinfo.ip_address }
31
+ end.flatten.compact.uniq
32
+ end
33
+
34
+ def host_and_port_from(url)
35
+ URI(url).then do |parsed_uri|
36
+ if parsed_uri.host
37
+ [parsed_uri.host, parsed_uri.port]
38
+ else
39
+ host_and_port_from_invalid_uri(url)
40
+ end
41
+ end
42
+ rescue URI::InvalidURIError
43
+ host_and_port_from_invalid_uri(url)
44
+ end
45
+
46
+ def host_and_port_from_invalid_uri(url)
47
+ host, _, port = url.rpartition(':')
48
+ [host, port]
49
+ end
50
+
51
+ ComparableAddress = Struct.new(:ip, :port) do
52
+ def initialize(addrinfo)
53
+ super(addrinfo.ip_address, addrinfo.ip_port)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,57 @@
1
+ class Console1984::SessionsLogger::Database
2
+ attr_reader :current_session, :current_sensitive_access
3
+
4
+ def start_session(username, reason)
5
+ silence_logging do
6
+ user = Console1984::User.create_or_find_by!(username: username)
7
+ @current_session = user.sessions.create! reason: reason
8
+ end
9
+ end
10
+
11
+ def finish_session
12
+ @current_session = nil
13
+ @current_sensitive_access = nil
14
+ end
15
+
16
+ def start_sensitive_access(justification)
17
+ silence_logging do
18
+ @current_sensitive_access = current_session.sensitive_accesses.create! justification: justification
19
+ end
20
+ end
21
+
22
+ def end_sensitive_access
23
+ @current_sensitive_access = nil
24
+ end
25
+
26
+ def before_executing(statements)
27
+ silence_logging do
28
+ @before_commands_count = @current_session.commands.count
29
+ record_statements statements
30
+ end
31
+ end
32
+
33
+ def after_executing(statements)
34
+ end
35
+
36
+ def suspicious_commands_attempted(statements)
37
+ silence_logging do
38
+ sensitive_access = start_sensitive_access "Suspicious commands attempted"
39
+ Console1984::Command.last.update! sensitive_access: sensitive_access
40
+ end
41
+ end
42
+
43
+ private
44
+ def record_statements(statements)
45
+ @current_session.commands.create! statements: Array(statements).join("\n"), sensitive_access: current_sensitive_access
46
+ end
47
+
48
+ def silence_logging(&block)
49
+ if Console1984.debug
50
+ block.call
51
+ else
52
+ Console1984::IncinerationJob.logger.silence do
53
+ Console1984::Base.logger.silence(&block)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,48 @@
1
+ require 'colorized_string'
2
+ require 'rails/console/app'
3
+
4
+ class Console1984::Supervisor
5
+ include Accesses, InputOutput, Executor
6
+
7
+ attr_reader :session_id
8
+ delegate :session_logger, :username_resolver, to: Console1984
9
+
10
+ def initialize
11
+ disable_access_to_encrypted_content(silent: true)
12
+ end
13
+
14
+ def start
15
+ show_production_data_warning
16
+ show_commands
17
+
18
+ extend_irb
19
+
20
+ session_logger.start_session current_username, ask_for_session_reason
21
+ end
22
+
23
+ def stop
24
+ session_logger.finish_session
25
+ end
26
+
27
+ private
28
+ def current_username
29
+ username_resolver.current
30
+ end
31
+
32
+ def show_production_data_warning
33
+ show_warning Console1984.production_data_warning
34
+ end
35
+
36
+ def extend_irb
37
+ IRB::Context.prepend(Console1984::ProtectedContext)
38
+ Rails::ConsoleMethods.include(Console1984::Commands)
39
+ end
40
+
41
+ def ask_for_session_reason
42
+ ask_for_value("#{current_username}, why are you using this console today?")
43
+ end
44
+
45
+ def show_commands
46
+ puts COMMANDS_HELP
47
+ end
48
+ end
@@ -0,0 +1,41 @@
1
+ module Console1984::Supervisor::Accesses
2
+ include Console1984::Messages
3
+
4
+ PROTECTED_ACCESS = Protected.new
5
+ UNPROTECTED_ACCESS = Unprotected.new
6
+
7
+ def enable_access_to_encrypted_content(silent: false)
8
+ run_system_command do
9
+ show_warning Console1984.enter_unprotected_encryption_mode_warning if !silent && protected_mode?
10
+ 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)?"
11
+ session_logger.start_sensitive_access justification
12
+ nil
13
+ end
14
+ ensure
15
+ @access = UNPROTECTED_ACCESS
16
+ nil
17
+ end
18
+
19
+ def disable_access_to_encrypted_content(silent: false)
20
+ run_system_command do
21
+ show_warning Console1984.enter_protected_mode_warning if !silent && unprotected_mode?
22
+ session_logger.end_sensitive_access
23
+ nil
24
+ end
25
+ ensure
26
+ @access = PROTECTED_ACCESS
27
+ nil
28
+ end
29
+
30
+ def with_encryption_mode(&block)
31
+ @access.execute(&block)
32
+ end
33
+
34
+ def unprotected_mode?
35
+ @access.is_a?(Unprotected)
36
+ end
37
+
38
+ def protected_mode?
39
+ !unprotected_mode?
40
+ end
41
+ end
@@ -0,0 +1,10 @@
1
+ class Console1984::Supervisor::Accesses::Protected
2
+ def execute(&block)
3
+ Console1984.protecting(&block)
4
+ end
5
+
6
+ private
7
+ def null_encryptor
8
+ @null_encryptor ||= ActiveRecord::Encryption::NullEncryptor.new
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class Console1984::Supervisor::Accesses::Unprotected
2
+ def execute(&block)
3
+ block.call
4
+ end
5
+ end
@@ -0,0 +1,41 @@
1
+ module Console1984::Supervisor::Executor
2
+ extend ActiveSupport::Concern
3
+
4
+ def execute_supervised(commands, &block)
5
+ run_system_command { session_logger.before_executing commands }
6
+ execute(&block)
7
+ rescue Console1984::Errors::ForbiddenCommand
8
+ puts "Forbidden command attempted: #{commands.join("\n")}"
9
+ run_system_command { session_logger.suspicious_commands_attempted commands }
10
+ nil
11
+ ensure
12
+ run_system_command { session_logger.after_executing commands }
13
+ end
14
+
15
+ def execute(&block)
16
+ run_user_command do
17
+ with_encryption_mode(&block)
18
+ end
19
+ end
20
+
21
+ def executing_user_command?
22
+ @executing_user_command
23
+ end
24
+
25
+ private
26
+ def run_user_command(&block)
27
+ run_command true, &block
28
+ end
29
+
30
+ def run_system_command(&block)
31
+ run_command false, &block
32
+ end
33
+
34
+ def run_command(run_by_user, &block)
35
+ original_value = @executing_user_command
36
+ @executing_user_command = run_by_user
37
+ block.call
38
+ ensure
39
+ @executing_user_command = original_value
40
+ end
41
+ end
@@ -0,0 +1,11 @@
1
+ module Console1984::Supervisor::InputOutput
2
+ def show_warning(message)
3
+ puts ColorizedString.new("\n#{message}\n").yellow
4
+ end
5
+
6
+ def ask_for_value(message)
7
+ puts ColorizedString.new("#{message}").green
8
+ reason = $stdin.gets.strip until reason.present?
9
+ reason
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ class Console1984::Username::EnvResolver
2
+ def initialize(key)
3
+ @key = key
4
+ end
5
+
6
+ def current
7
+ "#{username}"
8
+ end
9
+
10
+ private
11
+ def username
12
+ @username ||= ENV[@key]&.humanize || "Unnamed"
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Console1984
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: console1984
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jorge Manrubia
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-08-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: benchmark-ips
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mocha
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 1.18.4
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.18.4
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-performance
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-packaging
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description:
126
+ email:
127
+ - jorge@basecamp.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - MIT-LICENSE
133
+ - README.md
134
+ - Rakefile
135
+ - app/jobs/console1984/incineration_job.rb
136
+ - app/models/console1984/base.rb
137
+ - app/models/console1984/command.rb
138
+ - app/models/console1984/sensitive_access.rb
139
+ - app/models/console1984/session.rb
140
+ - app/models/console1984/session/incineratable.rb
141
+ - app/models/console1984/user.rb
142
+ - config/routes.rb
143
+ - db/migrate/20210517203931_create_console1984_tables.rb
144
+ - lib/console1984.rb
145
+ - lib/console1984/commands.rb
146
+ - lib/console1984/engine.rb
147
+ - lib/console1984/env_variable_username.rb
148
+ - lib/console1984/errors.rb
149
+ - lib/console1984/messages.rb
150
+ - lib/console1984/protected_auditable_tables.rb
151
+ - lib/console1984/protected_context.rb
152
+ - lib/console1984/protected_tcp_socket.rb
153
+ - lib/console1984/sessions_logger/database.rb
154
+ - lib/console1984/supervisor.rb
155
+ - lib/console1984/supervisor/accesses.rb
156
+ - lib/console1984/supervisor/accesses/protected.rb
157
+ - lib/console1984/supervisor/accesses/unprotected.rb
158
+ - lib/console1984/supervisor/executor.rb
159
+ - lib/console1984/supervisor/input_output.rb
160
+ - lib/console1984/username/env_resolver.rb
161
+ - lib/console1984/version.rb
162
+ homepage: http://github.com/basecamp/console1984
163
+ licenses:
164
+ - MIT
165
+ metadata:
166
+ allowed_push_host: https://rubygems.org
167
+ post_install_message:
168
+ rdoc_options: []
169
+ require_paths:
170
+ - lib
171
+ required_ruby_version: !ruby/object:Gem::Requirement
172
+ requirements:
173
+ - - ">="
174
+ - !ruby/object:Gem::Version
175
+ version: '0'
176
+ required_rubygems_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ requirements: []
182
+ rubygems_version: 3.1.4
183
+ signing_key:
184
+ specification_version: 4
185
+ summary: Your Rails console, 1984 style
186
+ test_files: []