console1984 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +95 -0
- data/Rakefile +32 -0
- data/app/jobs/console1984/incineration_job.rb +17 -0
- data/app/models/console1984/base.rb +7 -0
- data/app/models/console1984/command.rb +14 -0
- data/app/models/console1984/sensitive_access.rb +6 -0
- data/app/models/console1984/session.rb +15 -0
- data/app/models/console1984/session/incineratable.rb +30 -0
- data/app/models/console1984/user.rb +5 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20210517203931_create_console1984_tables.rb +35 -0
- data/lib/console1984.rb +86 -0
- data/lib/console1984/commands.rb +14 -0
- data/lib/console1984/engine.rb +29 -0
- data/lib/console1984/env_variable_username.rb +9 -0
- data/lib/console1984/errors.rb +13 -0
- data/lib/console1984/messages.rb +31 -0
- data/lib/console1984/protected_auditable_tables.rb +26 -0
- data/lib/console1984/protected_context.rb +15 -0
- data/lib/console1984/protected_tcp_socket.rb +56 -0
- data/lib/console1984/sessions_logger/database.rb +57 -0
- data/lib/console1984/supervisor.rb +48 -0
- data/lib/console1984/supervisor/accesses.rb +41 -0
- data/lib/console1984/supervisor/accesses/protected.rb +10 -0
- data/lib/console1984/supervisor/accesses/unprotected.rb +5 -0
- data/lib/console1984/supervisor/executor.rb +41 -0
- data/lib/console1984/supervisor/input_output.rb +11 -0
- data/lib/console1984/username/env_resolver.rb +14 -0
- data/lib/console1984/version.rb +3 -0
- metadata +186 -0
    
        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,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,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
         | 
    
        data/config/routes.rb
    ADDED
    
    
| @@ -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
         | 
    
        data/lib/console1984.rb
    ADDED
    
    | @@ -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,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,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,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
         | 
    
        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: []
         |