├── test
├── dummy-app
│ ├── log
│ │ ├── .keep
│ │ └── development.log
│ ├── tmp
│ │ ├── .keep
│ │ └── restart.txt
│ ├── db
│ │ ├── test.sqlite3
│ │ └── seeds.rb
│ ├── lib
│ │ ├── assets
│ │ │ └── .keep
│ │ └── tasks
│ │ │ └── .keep
│ ├── public
│ │ ├── favicon.ico
│ │ ├── apple-touch-icon.png
│ │ ├── apple-touch-icon-precomposed.png
│ │ ├── robots.txt
│ │ ├── 500.html
│ │ ├── 422.html
│ │ └── 404.html
│ ├── test
│ │ ├── helpers
│ │ │ ├── .keep
│ │ │ ├── sidekiq_helpers.rb
│ │ │ ├── redis_helpers.rb
│ │ │ └── utils.rb
│ │ ├── mailers
│ │ │ └── .keep
│ │ ├── models
│ │ │ └── .keep
│ │ ├── controllers
│ │ │ └── .keep
│ │ ├── fixtures
│ │ │ ├── .keep
│ │ │ └── files
│ │ │ │ └── .keep
│ │ ├── integration
│ │ │ └── .keep
│ │ ├── test_helper.rb
│ │ └── workers
│ │ │ └── sidekiq_crypt
│ │ │ ├── safe_worker_test.rb
│ │ │ ├── encrypted_worker_with_keys_test.rb
│ │ │ └── encrypted_worker_test.rb
│ ├── app
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ └── .keep
│ │ │ ├── javascripts
│ │ │ │ ├── channels
│ │ │ │ │ └── .keep
│ │ │ │ ├── cable.js
│ │ │ │ └── application.js
│ │ │ ├── config
│ │ │ │ └── manifest.js
│ │ │ └── stylesheets
│ │ │ │ └── application.css
│ │ ├── models
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ └── application_record.rb
│ │ ├── controllers
│ │ │ ├── concerns
│ │ │ │ └── .keep
│ │ │ └── application_controller.rb
│ │ ├── views
│ │ │ └── layouts
│ │ │ │ ├── mailer.text.erb
│ │ │ │ ├── mailer.html.erb
│ │ │ │ └── application.html.erb
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ ├── jobs
│ │ │ └── application_job.rb
│ │ ├── channels
│ │ │ └── application_cable
│ │ │ │ ├── channel.rb
│ │ │ │ └── connection.rb
│ │ ├── mailers
│ │ │ └── application_mailer.rb
│ │ └── workers
│ │ │ └── sidekiq_crypt
│ │ │ ├── safe_worker.rb
│ │ │ ├── encrypted_worker.rb
│ │ │ └── encrypted_worker_with_keys.rb
│ ├── vendor
│ │ └── assets
│ │ │ ├── javascripts
│ │ │ └── .keep
│ │ │ └── stylesheets
│ │ │ └── .keep
│ ├── bin
│ │ ├── rake
│ │ ├── bundle
│ │ ├── rails
│ │ ├── update
│ │ └── setup
│ ├── config
│ │ ├── cable.yml
│ │ ├── spring.rb
│ │ ├── boot.rb
│ │ ├── routes.rb
│ │ ├── initializers
│ │ │ ├── session_store.rb
│ │ │ ├── filter_parameter_logging.rb
│ │ │ └── backtrace_silencers.rb
│ │ ├── environment.rb
│ │ ├── database.yml
│ │ ├── application.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── environments
│ │ │ ├── test.rb
│ │ │ ├── development.rb
│ │ │ └── production.rb
│ │ ├── secrets.yml
│ │ └── puma.rb
│ ├── config.ru
│ ├── Rakefile
│ ├── README.md
│ ├── Gemfile
│ └── Gemfile.lock
├── helpers
│ └── test_jobs.rb
├── sidekiq-crypt
│ ├── configuration_test.rb
│ ├── cipher_test.rb
│ ├── traverser_test.rb
│ ├── server_middleware_test.rb
│ └── client_middleware_test.rb
├── test_helper.rb
└── sidekiq-crypt_test.rb
├── lib
├── sidekiq-crypt
│ ├── version.rb
│ ├── worker.rb
│ ├── configuration.rb
│ ├── traverser.rb
│ ├── cipher.rb
│ ├── client_middleware.rb
│ └── server_middleware.rb
└── sidekiq-crypt.rb
├── gemfiles
├── rails-5.0-stable.gemfile
├── rails-5.2-stable.gemfile
├── rails-6.0-stable.gemfile
├── rails-4.2-stable.gemfile
├── rails-4.2-stable.gemfile.lock
├── rails-5.0-stable.gemfile.lock
├── rails-5.2-stable.gemfile.lock
└── rails-6.0-stable.gemfile.lock
├── bin
├── setup
└── console
├── .gitignore
├── Gemfile
├── Rakefile
├── sidekiq-crypt.gemspec
├── .rubocop.yml
├── LICENSE.txt
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── Gemfile.lock
└── README.md
/test/dummy-app/log/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/tmp/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/db/test.sqlite3:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/lib/assets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/lib/tasks/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/test/helpers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/test/mailers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/test/models/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/tmp/restart.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/app/assets/images/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/test/controllers/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/test/fixtures/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/test/integration/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/app/models/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/dummy-app/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/test/dummy-app/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationHelper
4 | end
5 |
--------------------------------------------------------------------------------
/test/dummy-app/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationJob < ActiveJob::Base
4 | end
5 |
--------------------------------------------------------------------------------
/lib/sidekiq-crypt/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sidekiq
4 | module Crypt
5 | VERSION = '0.1.1'
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/dummy-app/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/gemfiles/rails-5.0-stable.gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | gemspec path: '..'
6 |
7 | gem 'rails', '~> 5.0.0'
8 |
--------------------------------------------------------------------------------
/gemfiles/rails-5.2-stable.gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | gemspec path: '..'
6 |
7 | gem 'rails', '~> 5.2'
8 |
--------------------------------------------------------------------------------
/gemfiles/rails-6.0-stable.gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | gemspec path: '..'
6 |
7 | gem 'rails', '~> 6.0.0'
8 |
--------------------------------------------------------------------------------
/test/dummy-app/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require_relative '../config/boot'
5 | require 'rake'
6 | Rake.application.run
7 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /vendor/bundle/
3 | /.yardoc
4 | /_yardoc/
5 | /log/
6 | /coverage/
7 | /doc/
8 | /pkg/
9 | /spec/reports/
10 | /tmp/
11 | *.gem
12 | *.log
13 |
--------------------------------------------------------------------------------
/test/dummy-app/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationRecord < ActiveRecord::Base
4 | self.abstract_class = true
5 | end
6 |
--------------------------------------------------------------------------------
/test/dummy-app/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Channel < ActionCable::Channel::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/test/dummy-app/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 |
--------------------------------------------------------------------------------
/gemfiles/rails-4.2-stable.gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | gemspec path: '..'
6 |
7 | gem 'rails', github: 'rails/rails', branch: '4-2-stable'
8 |
--------------------------------------------------------------------------------
/test/dummy-app/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Connection < ActionCable::Connection::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/test/dummy-app/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
5 | load Gem.bin_path('bundler', 'bundle')
6 |
--------------------------------------------------------------------------------
/test/dummy-app/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::Base
4 | protect_from_forgery with: :exception
5 | end
6 |
--------------------------------------------------------------------------------
/test/dummy-app/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationMailer < ActionMailer::Base
4 | default from: 'from@example.com'
5 | layout 'mailer'
6 | end
7 |
--------------------------------------------------------------------------------
/test/dummy-app/config/spring.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | %w[
4 | .ruby-version
5 | .rbenv-vars
6 | tmp/restart.txt
7 | tmp/caching-dev.txt
8 | ].each { |path| Spring.watch(path) }
9 |
--------------------------------------------------------------------------------
/test/dummy-app/config.ru:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file is used by Rack-based servers to start the application.
4 |
5 | require_relative 'config/environment'
6 |
7 | run Rails.application
8 |
--------------------------------------------------------------------------------
/test/dummy-app/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__)
4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5 |
--------------------------------------------------------------------------------
/test/dummy-app/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | APP_PATH = File.expand_path('../config/application', __dir__)
5 | require_relative '../config/boot'
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/test/dummy-app/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.routes.draw do
4 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
5 | end
6 |
--------------------------------------------------------------------------------
/test/dummy-app/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | Rails.application.config.session_store :cookie_store, key: '_dummy-railties_session'
6 |
--------------------------------------------------------------------------------
/test/dummy-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4 |
5 | # Specify your gem's dependencies in sidekiq-crypt.gemspec
6 | gemspec
7 |
8 | gem "rails", '~> 5.2'
9 | gem "sqlite3", "~> 1.3.6"
10 |
--------------------------------------------------------------------------------
/test/dummy-app/app/workers/sidekiq_crypt/safe_worker.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SidekiqCrypt
4 | class SafeWorker
5 | include Sidekiq::Worker
6 |
7 | def perform(params)
8 | 1 / params['divider']
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/helpers/test_jobs.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SafeWorker; end
4 |
5 | class SecretWorker
6 | include Sidekiq::Crypt::Worker
7 | end
8 |
9 | class SecretWorkerWithKey
10 | include Sidekiq::Crypt::Worker
11 |
12 | encrypted_keys :some_key
13 | end
14 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'bundler/gem_tasks'
4 | require 'rake/testtask'
5 |
6 | Rake::TestTask.new(:test) do |t, _args|
7 | t.libs << 'test'
8 | t.libs << 'lib'
9 | t.test_files = FileList['test/**/*_test.rb']
10 | end
11 |
12 | task default: :test
13 |
--------------------------------------------------------------------------------
/test/dummy-app/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Configure sensitive parameters which will be filtered from the log file.
6 | Rails.application.config.filter_parameters += %i[password secret_key]
7 |
--------------------------------------------------------------------------------
/test/dummy-app/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Add your own tasks in files placed in lib/tasks ending in .rake,
4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5 |
6 | require File.expand_path('config/application', __dir__)
7 |
8 | Rails.application.load_tasks
9 |
--------------------------------------------------------------------------------
/test/dummy-app/app/workers/sidekiq_crypt/encrypted_worker.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SidekiqCrypt
4 | class EncryptedWorker
5 | include Sidekiq::Worker
6 | include Sidekiq::Crypt::Worker
7 |
8 | def perform(params)
9 | 1 / params['divider']
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/test/dummy-app/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/test/dummy-app/test/helpers/sidekiq_helpers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SidekiqHelpers
4 | def execute_job
5 | opts = { concurrency: 1, queues: ['default'] }
6 | boss = Sidekiq::Manager.new(opts)
7 | processor = Sidekiq::Processor.new(boss)
8 |
9 | processor.process_one
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/test/dummy-app/app/workers/sidekiq_crypt/encrypted_worker_with_keys.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module SidekiqCrypt
4 | class EncryptedWorkerWithKeys
5 | include Sidekiq::Worker
6 | include Sidekiq::Crypt::Worker
7 |
8 | encrypted_keys :password, :divider
9 |
10 | def perform(params)
11 | 1 / params['divider']
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/test/dummy-app/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # # Load the Rails application.
4 | # require_relative 'application'
5 |
6 | # # Initialize the Rails application.
7 | # Rails.application.initialize!
8 |
9 | # Load the rails application.
10 | require File.expand_path('application', __dir__)
11 |
12 | # Initialize the rails application.
13 | DummyRailties::Application.initialize!
14 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "sidekiq/crypt"
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require "irb"
14 | IRB.start(__FILE__)
15 |
--------------------------------------------------------------------------------
/test/dummy-app/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DummyRailties
5 | <%= csrf_meta_tags %>
6 |
7 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
8 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
9 |
10 |
11 |
12 | <%= yield %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/dummy-app/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # This file should contain all the record creation needed to seed the database with its default values.
3 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
4 | #
5 | # Examples:
6 | #
7 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
8 | # Character.create(name: 'Luke', movie: movies.first)
9 |
--------------------------------------------------------------------------------
/test/dummy-app/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the rails generate channel command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/test/dummy-app/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | ENV['RAILS_ENV'] ||= 'test'
4 | require File.expand_path('../config/environment', __dir__)
5 | require 'rails/test_help'
6 | $TESTING = true # rubocop:disable Style/GlobalVars
7 |
8 | require 'sidekiq/cli'
9 | require 'sidekiq/manager'
10 | require 'sidekiq'
11 | require 'logger'
12 |
13 | # hides sidekiq retry errors
14 | Sidekiq.logger.level = Logger::ERROR unless ENV['DEBUG']
15 |
--------------------------------------------------------------------------------
/lib/sidekiq-crypt/worker.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sidekiq
4 | module Crypt
5 | module Worker
6 | def self.included(base)
7 | base.extend(ClassMethods)
8 | end
9 |
10 | module ClassMethods
11 | attr_reader :sidekiq_crypt_worker_filters
12 |
13 | def encrypted_keys(*filter_keys)
14 | @sidekiq_crypt_worker_filters = filter_keys
15 | end
16 | end
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/test/dummy-app/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | This README would normally document whatever steps are necessary to get the
4 | application up and running.
5 |
6 | Things you may want to cover:
7 |
8 | * Ruby version
9 |
10 | * System dependencies
11 |
12 | * Configuration
13 |
14 | * Database creation
15 |
16 | * Database initialization
17 |
18 | * How to run the test suite
19 |
20 | * Services (job queues, cache servers, search engines, etc.)
21 |
22 | * Deployment instructions
23 |
24 | * ...
25 |
--------------------------------------------------------------------------------
/test/dummy-app/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
6 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
7 |
8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
9 | Rails.backtrace_cleaner.remove_silencers!
10 |
--------------------------------------------------------------------------------
/test/dummy-app/test/helpers/redis_helpers.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module RedisHelpers
4 | def sidekiq_crypt_header(job_id)
5 | header = Sidekiq.redis { |conn| conn.get("sidekiq-crpyt-header:#{job_id}") }
6 | header && JSON.parse(header)
7 | end
8 |
9 | def decrypt_retried_job_param(job_id:, key:)
10 | header = sidekiq_crypt_header(job_id)
11 |
12 | Sidekiq::Crypt::Cipher.decrypt(
13 | retried_job_args[key],
14 | Base64.decode64(header['nonce']),
15 | header['key_version']
16 | )
17 | end
18 |
19 | def retried_job_args
20 | Sidekiq::RetrySet.new.first.args[0]
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/test/dummy-app/test/helpers/utils.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Utils
4 | def configure_sidekiq_crypt(filters = [], exclude_rails_filters: false)
5 | Rails.application.config.filter_parameters = %i[password secret_key]
6 | config = Sidekiq::Crypt.configuration
7 | config.filters = []
8 | config.send(:include_rails_filter_parameters, exclude_rails_filters)
9 |
10 | Sidekiq::Crypt.configure(exclude_rails_filters: exclude_rails_filters) do |configuration|
11 | configuration.current_key_version = 'V1'
12 | configuration.key_store = { V1: ENV['CIPHER_KEY'] }
13 | configuration.filters << filters
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/test/dummy-app/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/test/dummy-app/config/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require File.expand_path('boot', __dir__)
4 |
5 | require 'rails/railtie' # Only for Rails >= 4.2
6 | require 'rails/test_unit/railtie'
7 |
8 | # Require the gems listed in Gemfile, including any gems
9 | # you've limited to :test, :development, or :production.
10 | Bundler.require(:default)
11 |
12 | module DummyRailties
13 | class Application < Rails::Application
14 | # Settings in config/environments/* take precedence over those specified here.
15 | # Application configuration should go into files in config/initializers
16 | # -- all .rb files in that directory are automatically loaded.
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/dummy-app/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # To learn more, please read the Rails Internationalization guide
20 | # available at http://guides.rubyonrails.org/i18n.html.
21 |
22 | en:
23 | hello: "Hello world"
24 |
--------------------------------------------------------------------------------
/test/dummy-app/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/test/dummy-app/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require turbolinks
16 | //= require_tree .
17 |
--------------------------------------------------------------------------------
/test/dummy-app/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'pathname'
5 | require 'fileutils'
6 | include FileUtils
7 |
8 | # path to your application root.
9 | APP_ROOT = Pathname.new File.expand_path('..', __dir__)
10 |
11 | def system!(*args)
12 | system(*args) || abort("\n== Command #{args} failed ==")
13 | end
14 |
15 | chdir APP_ROOT do
16 | # This script is a way to update your development environment automatically.
17 | # Add necessary update steps to this file.
18 |
19 | puts '== Installing dependencies =='
20 | system! 'gem install bundler --conservative'
21 | system('bundle check') || system!('bundle install')
22 |
23 | puts "\n== Updating database =="
24 | system! 'bin/rails db:migrate'
25 |
26 | puts "\n== Removing old logs and tempfiles =="
27 | system! 'bin/rails log:clear tmp:clear'
28 |
29 | puts "\n== Restarting application server =="
30 | system! 'bin/rails restart'
31 | end
32 |
--------------------------------------------------------------------------------
/sidekiq-crypt.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require File.expand_path('lib/sidekiq-crypt/version', __dir__)
4 | require 'rake'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = 'sidekiq-crypt'
8 | spec.version = Sidekiq::Crypt::VERSION
9 | spec.date = '2020-04-12'
10 | spec.summary = 'encrypts confidential sidekiq parameters on redis'
11 | spec.description = 'A gem to manage confidential job parameters'
12 | spec.authors = ['Murat Toygar']
13 | spec.email = ['toygar-murat@hotmail.com']
14 | spec.files = FileList['lib/**/*.rb'].to_a
15 | spec.homepage = 'https://rubygems.org/gems/sidekiq-crypt'
16 | spec.license = 'MIT'
17 |
18 | spec.require_paths = ['lib']
19 |
20 | spec.add_dependency 'sidekiq', '~> 5.0'
21 | spec.add_development_dependency 'bundler', '~> 1.17'
22 | spec.add_development_dependency 'minitest', '~> 5.0'
23 | spec.add_development_dependency 'rake', '~> 13.0'
24 | spec.add_development_dependency 'rubocop'
25 | end
26 |
--------------------------------------------------------------------------------
/test/dummy-app/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | DummyRailties::Application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # The test environment is used exclusively to run your application's
7 | # test suite. You never need to work with it otherwise. Remember that
8 | # your test database is "scratch space" for the test suite and is wiped
9 | # and recreated between test runs. Don't rely on the data there!
10 | config.cache_classes = true
11 |
12 | # Do not eager load code on boot. This avoids loading your whole application
13 | # just for the purpose of running a single test. If you are using a tool that
14 | # preloads Rails for running tests, you may have to set it to true.
15 | config.eager_load = false
16 |
17 | # Raise exceptions instead of rendering exception templates.
18 | config.action_dispatch.show_exceptions = false
19 |
20 | # Raises error for missing translations
21 | # config.action_view.raise_on_missing_translations = true
22 | end
23 |
--------------------------------------------------------------------------------
/test/dummy-app/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rails secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: 33f81f120a1373ebefbe59f872fd3e90c0a115a3fad52bb07fc2af313c3e69d34836d2042d15b2ff666cb0a0f190cd72a45e562d63bae163cb16192bc92a6f9f
15 |
16 | test:
17 | secret_key_base: 28510712efe3b981ba72eeebafc34727055a789fb51bef7f2f7dd16beee64bff04306e9691526335a51056436b9618105daed635e78af543ed523b293bb39ce4
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | AllCops:
2 | Exclude:
3 | - 'Gemfile'
4 | - 'db/*'
5 | - 'config/*'
6 | - 'script/**/*'
7 | - 'test/dummy-app/bin/*'
8 | - 'test/dummy-app/config/**/*'
9 | - 'test/dummy-app/db/*'
10 | - 'bin/*'
11 | - vendor/**/*
12 |
13 | Layout/LineLength:
14 | Max: 100
15 |
16 | Metrics/MethodLength:
17 | Exclude:
18 | - 'test/**/*_test.rb'
19 |
20 | Metrics/AbcSize:
21 | Exclude:
22 | - 'test/**/*_test.rb'
23 |
24 | Style/ClassAndModuleChildren:
25 | Exclude:
26 | - 'test/**/*_test.rb'
27 |
28 | Style/Documentation:
29 | Enabled: false
30 |
31 | Naming/FileName:
32 | Enabled: false
33 |
34 | # safe navigation is not a valid syntax for ruby 2.2
35 | Style/SafeNavigation:
36 | Enabled: false
37 |
38 | # below should be explicitly stated for rubocop 0.81.0
39 | Lint/RaiseException:
40 | Enabled: true
41 |
42 | Lint/StructNewOverride:
43 | Enabled: true
44 |
45 | Style/HashEachMethods:
46 | Enabled: true
47 |
48 | Style/HashTransformKeys:
49 | Enabled: true
50 |
51 | Style/HashTransformValues:
52 | Enabled: true
53 |
--------------------------------------------------------------------------------
/test/sidekiq-crypt/configuration_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class ConfigurationTest < Sidekiq::Crypt::TestCase
6 | def test_sets_current_key_version
7 | config = Sidekiq::Crypt::Configuration.new(dummy_key_attributes)
8 |
9 | assert_equal('ThisPasswordIsReallyHardToGuess!', config.current_key)
10 | end
11 |
12 | def test_returns_secret_key_for_given_versioon
13 | config = Sidekiq::Crypt::Configuration.new(dummy_key_attributes)
14 |
15 | assert_equal('old_key', config.key_by_version('V1'))
16 | end
17 |
18 | def test_stringfy_key_store_keys
19 | config = Sidekiq::Crypt::Configuration.new(dummy_key_attributes)
20 |
21 | assert_equal(Base64.strict_encode64('old_key'), config.key_store['V1'])
22 | assert_equal(Base64.strict_encode64('ThisPasswordIsReallyHardToGuess!'), config.key_store['V2'])
23 | end
24 |
25 | private
26 |
27 | def dummy_key_attributes
28 | {
29 | current_key_version: 'V2',
30 | key_store: { V1: Base64.strict_encode64('old_key'), V2: ENV['CIPHER_KEY'] }
31 | }
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/test/dummy-app/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require 'pathname'
5 | require 'fileutils'
6 | include FileUtils
7 |
8 | # path to your application root.
9 | APP_ROOT = Pathname.new File.expand_path('..', __dir__)
10 |
11 | def system!(*args)
12 | system(*args) || abort("\n== Command #{args} failed ==")
13 | end
14 |
15 | chdir APP_ROOT do
16 | # This script is a starting point to setup your application.
17 | # Add necessary setup steps to this file.
18 |
19 | puts '== Installing dependencies =='
20 | system! 'gem install bundler --conservative'
21 | system('bundle check') || system!('bundle install')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:setup'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/test/sidekiq-crypt/cipher_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class CipherTest < Sidekiq::Crypt::TestCase
6 | def setup
7 | super
8 | configure_sidekiq_crypt
9 | end
10 |
11 | def test_encrypts_given_string
12 | assert_equal("PdHia8epi6I8IUs+Ya9WIQ==\n", cipher.encrypt('A SECRET', valid_iv))
13 | end
14 |
15 | def test_encrypts_string_with_non_ascii_characters
16 | assert_equal(
17 | "71TQLRRaSwTjc7/1UJfvjznx20ymLDc7diwNAmd650w=\n", cipher.encrypt('ÜĞİŞÇÖöçşiğı', valid_iv)
18 | )
19 | end
20 |
21 | def test_decrypts_given_parameter
22 | assert_equal('A SECRET', cipher.decrypt('PdHia8epi6I8IUs+Ya9WIQ==', valid_iv, 'V1'))
23 | end
24 |
25 | def test_decrypts_string_with_non_ascii_characters
26 | assert_equal(
27 | 'ÜĞİŞÇÖöçşiğı', cipher.decrypt('71TQLRRaSwTjc7/1UJfvjznx20ymLDc7diwNAmd650w=', valid_iv, 'V1')
28 | )
29 | end
30 |
31 | private
32 |
33 | def cipher
34 | Sidekiq::Crypt::Cipher
35 | end
36 |
37 | def valid_iv
38 | # a valid iv should be at least 16 bytes
39 | '1' * 16
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Murat Toygar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/sidekiq-crypt/configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sidekiq
4 | module Crypt
5 | class Configuration
6 | attr_accessor :filters, :current_key_version
7 | attr_reader :key_store
8 |
9 | def initialize(options = {})
10 | @filters = []
11 | @current_key_version = options.fetch(:current_key_version, nil)
12 | @key_store = options.fetch(:key_store, {}).transform_keys(&:to_s)
13 |
14 | include_rails_filter_parameters(options[:exclude_rails_filters])
15 | end
16 |
17 | def current_key
18 | Base64.strict_decode64(key_store[current_key_version])
19 | end
20 |
21 | def key_by_version(given_key)
22 | Base64.strict_decode64(key_store[given_key])
23 | end
24 |
25 | def key_store=(key_store_hash)
26 | @key_store = (key_store_hash || {}).transform_keys(&:to_s)
27 | end
28 |
29 | private
30 |
31 | def include_rails_filter_parameters(exclude_rails_filters)
32 | return unless defined?(::Rails)
33 | return if exclude_rails_filters
34 |
35 | @filters = ::Rails.application.config.filter_parameters
36 | end
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/test/dummy-app/test/workers/sidekiq_crypt/safe_worker_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require File.expand_path('../../test_helper', __dir__)
4 | require File.expand_path('../../helpers/redis_helpers', __dir__)
5 | require File.expand_path('../../helpers/utils', __dir__)
6 |
7 | class SidekiqCrypt::SafeWorkerTest < ActiveSupport::TestCase
8 | include RedisHelpers
9 | include SidekiqHelpers
10 | include Utils
11 |
12 | def setup
13 | Sidekiq.redis(&:flushdb)
14 | configure_sidekiq_crypt
15 | end
16 |
17 | def test_safe_worker_does_not_write_header_on_redis
18 | job_id = SidekiqCrypt::SafeWorker.perform_async(password: '123456', divider: 0)
19 |
20 | assert_nil(sidekiq_crypt_header(job_id))
21 |
22 | assert_raises('ZeroDivisionError') do
23 | SidekiqCrypt::SafeWorker.drain
24 | end
25 | end
26 |
27 | def test_safe_worker_retries
28 | Sidekiq::Testing.disable! do
29 | SidekiqCrypt::SafeWorker.perform_async(password: '123456', divider: 0)
30 |
31 | assert_raises('ZeroDivisionError') do
32 | execute_job
33 | end
34 |
35 | assert_equal('123456', retried_job_args['password'])
36 | assert_equal(0, retried_job_args['divider'])
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | ENV['RAILS_ENV'] = 'test'
4 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
5 | $LOAD_PATH.unshift File.expand_path(__FILE__)
6 |
7 | require 'sidekiq-crypt'
8 | require 'sidekiq'
9 |
10 | require 'minitest/autorun'
11 | require 'dummy-app/config/environment'
12 | require 'helpers/test_jobs'
13 |
14 | module Sidekiq
15 | module Crypt
16 | class TestCase < Minitest::Test
17 | def setup
18 | super
19 | flush_redis
20 | reset_configured_filters
21 | end
22 |
23 | private
24 |
25 | def flush_redis
26 | Sidekiq.redis(&:flushall)
27 | sleep 0.05
28 | end
29 |
30 | def reset_configured_filters
31 | Sidekiq::Crypt.configuration.filters.map! { |_filter| [] }.flatten!
32 | end
33 |
34 | def configure_sidekiq_crypt(filters: [/^secret.*/], options: config_key_attrs)
35 | Sidekiq::Crypt.configure do |config|
36 | config.current_key_version = options[:current_key_version]
37 | config.key_store = options[:key_store]
38 | config.filters << filters
39 | end
40 | end
41 |
42 | def config_key_attrs
43 | { current_key_version: 'V1', key_store: { V1: ENV['CIPHER_KEY'] } }
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 |
3 | services:
4 | - redis-server
5 |
6 | rvm:
7 | - 2.2.10
8 | - 2.3.8
9 | - 2.4.5
10 | - 2.5.3
11 | - 2.6.0
12 | - ruby-head
13 |
14 | gemfile:
15 | - Gemfile
16 | - gemfiles/rails-6.0-stable.gemfile
17 | - gemfiles/rails-5.2-stable.gemfile
18 | - gemfiles/rails-5.0-stable.gemfile
19 | - gemfiles/rails-4.2-stable.gemfile
20 |
21 | matrix:
22 | exclude:
23 | - rvm: 2.2.10
24 | gemfile: Gemfile
25 | - rvm: 2.2.10
26 | gemfile: gemfiles/rails-6.0-stable.gemfile
27 | - rvm: 2.2.10
28 | gemfile: gemfiles/rails-5.2-stable.gemfile
29 | - rvm: 2.3.8
30 | gemfile: gemfiles/rails-6.0-stable.gemfile
31 | - rvm: 2.4.5
32 | gemfile: gemfiles/rails-6.0-stable.gemfile
33 | - rvm: 2.6.0
34 | gemfile: gemfiles/rails-4.2-stable.gemfile
35 | - rvm: ruby-head
36 | gemfile: gemfiles/rails-4.2-stable.gemfile
37 | allow_failures:
38 | - rvm: ruby-head
39 | - gemfile: gemfiles/rails-6.0-stable.gemfile
40 |
41 | cache: bundler
42 |
43 | env:
44 | matrix:
45 | - CIPHER_KEY=VGhpc1Bhc3N3b3JkSXNSZWFsbHlIYXJkVG9HdWVzcyE=
46 |
47 | before_install:
48 | - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
49 | - gem install bundler -v '< 2'
50 | - "rm ${BUNDLE_GEMFILE}.lock"
51 |
52 | before_script: "bundle update"
53 |
54 | script: "bundle exec rake test"
55 |
--------------------------------------------------------------------------------
/lib/sidekiq-crypt/traverser.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'uri'
4 |
5 | module Sidekiq
6 | module Crypt
7 | class Traverser
8 | def initialize(filters)
9 | @filters = filters
10 | end
11 |
12 | def traverse!(args, proc)
13 | args.each_with_index do |arg, index|
14 | next unless arg.is_a?(Hash) || arg.is_a?(Array)
15 |
16 | # override the params
17 | args[index] = traverse(arg, proc)
18 | end
19 | end
20 |
21 | private
22 |
23 | def traverse(object, proc)
24 | # sidekiq arguments must be serialized as JSON.
25 | # Therefore, object could be hash, array or primitives like string/number
26 | # Also, recursive hashes or arrays is not possible.
27 | case object
28 | when Hash
29 | clean_hash = {}
30 |
31 | object.each do |key, value|
32 | clean_hash[key] = filter_match?(key) ? proc.call(key, value) : traverse(value, proc)
33 | end
34 |
35 | clean_hash
36 | when Array then object.map { |element| traverse(element, proc) }
37 | else object
38 | end
39 | end
40 |
41 | def filter_match?(key)
42 | @filters.any? do |filter|
43 | case filter
44 | when Regexp then key.to_s.match(filter)
45 | else key.to_s == filter.to_s
46 | end
47 | end
48 | end
49 | end
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/lib/sidekiq-crypt/cipher.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'openssl'
4 | require 'base64'
5 |
6 | module Sidekiq
7 | module Crypt
8 | module Cipher
9 | class << self
10 | def encrypt(confidential_param, initialization_vector)
11 | encryptor = encryption_cipher(initialization_vector)
12 | # use base64 to prevent Encoding::UndefinedConversionError
13 | Base64.encode64(encryptor.update(confidential_param.to_s) + encryptor.final)
14 | end
15 |
16 | def decrypt(confidential_param, initialization_vector, key_version)
17 | decryptor_cipher = decryption_cipher(initialization_vector, key_version)
18 |
19 | decrypted = decryptor_cipher.update(Base64.decode64(confidential_param.to_s)) +
20 | decryptor_cipher.final
21 |
22 | decrypted.force_encoding('UTF-8')
23 | end
24 |
25 | def random_iv
26 | OpenSSL::Cipher::AES.new(256, :CBC).encrypt.random_iv
27 | end
28 |
29 | private
30 |
31 | def encryption_cipher(initialization_vector)
32 | cipher = OpenSSL::Cipher::AES.new(256, :CBC).encrypt
33 | cipher.key = Sidekiq::Crypt.configuration.current_key
34 | cipher.iv = initialization_vector
35 |
36 | cipher
37 | end
38 |
39 | def decryption_cipher(initialization_vector, key_version)
40 | cipher = OpenSSL::Cipher::AES.new(256, :CBC).decrypt
41 | cipher.key = Sidekiq::Crypt.configuration.key_by_version(key_version)
42 | cipher.iv = initialization_vector
43 |
44 | cipher
45 | end
46 | end
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/test/dummy-app/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/test/dummy-app/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy-app/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/test/dummy-app/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
6 | gem 'rails', '~> 5.0.0'
7 | # Use sqlite3 as the database for Active Record
8 | gem 'sqlite3'
9 | # Use Puma as the app server
10 | gem 'puma', '~> 3.12'
11 | # Use SCSS for stylesheets
12 | gem 'sass-rails', '~> 5.0'
13 | # Use Uglifier as compressor for JavaScript assets
14 | gem 'uglifier', '>= 1.3.0'
15 | # Use CoffeeScript for .coffee assets and views
16 | gem 'coffee-rails', '~> 4.2'
17 | # See https://github.com/rails/execjs#readme for more supported runtimes
18 | # gem 'therubyracer', platforms: :ruby
19 |
20 | # Use jquery as the JavaScript library
21 | gem 'jquery-rails'
22 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
23 | gem 'turbolinks', '~> 5'
24 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
25 | gem 'jbuilder', '~> 2.5'
26 | # Use Redis adapter to run Action Cable in production
27 | # gem 'redis', '~> 3.0'
28 | # Use ActiveModel has_secure_password
29 | # gem 'bcrypt', '~> 3.1.7'
30 |
31 | # Use Capistrano for deployment
32 | # gem 'capistrano-rails', group: :development
33 |
34 | group :development, :test do
35 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
36 | gem 'byebug', platform: :mri
37 | end
38 |
39 | group :development do
40 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
41 | gem 'listen', '~> 3.0.5'
42 | gem 'web-console'
43 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
44 | gem 'spring'
45 | gem 'spring-watcher-listen', '~> 2.0.0'
46 | end
47 |
48 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
49 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
50 |
--------------------------------------------------------------------------------
/test/dummy-app/test/workers/sidekiq_crypt/encrypted_worker_with_keys_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require File.expand_path('../../test_helper', __dir__)
4 | require File.expand_path('../../helpers/redis_helpers', __dir__)
5 | require File.expand_path('../../helpers/sidekiq_helpers', __dir__)
6 | require File.expand_path('../../helpers/utils', __dir__)
7 |
8 | class SidekiqCrypt::EncryptedWorkerWithKeysTest < ActiveSupport::TestCase
9 | include RedisHelpers
10 | include SidekiqHelpers
11 | include Utils
12 |
13 | def setup
14 | Sidekiq.redis(&:flushdb)
15 | configure_sidekiq_crypt
16 | end
17 |
18 | def test_safe_worker_does_not_write_header_on_redis
19 | Sidekiq::Testing.disable! do
20 | job_id = SidekiqCrypt::EncryptedWorkerWithKeys.perform_async(password: '123456', divider: '0')
21 |
22 | assert_equal(%w[password divider], sidekiq_crypt_header(job_id)['encrypted_keys'])
23 |
24 | assert_raises('TypeError') do
25 | execute_job
26 | end
27 |
28 | assert_not_nil(sidekiq_crypt_header(job_id))
29 | assert_equal('123456', decrypt_retried_job_param(job_id: job_id, key: 'password'))
30 | assert_equal('0', decrypt_retried_job_param(job_id: job_id, key: 'divider'))
31 | end
32 | end
33 |
34 | def test_encrypted_worker_with_key_does_not_consider_rails_filter_params
35 | configure_sidekiq_crypt
36 |
37 | Sidekiq::Testing.disable! do
38 | job_id = SidekiqCrypt::EncryptedWorkerWithKeys.perform_async(
39 | secret_key: '123456', divider: '0'
40 | )
41 |
42 | assert_equal(['divider'], sidekiq_crypt_header(job_id)['encrypted_keys'])
43 |
44 | assert_raises('TypeError') do
45 | execute_job
46 | end
47 |
48 | assert_not_nil(sidekiq_crypt_header(job_id))
49 | assert_equal('123456', retried_job_args['secret_key'])
50 | assert_equal('0', decrypt_retried_job_param(job_id: job_id, key: 'divider'))
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/test/sidekiq-crypt/traverser_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 | require 'sidekiq-crypt/traverser'
5 |
6 | class TraverserTest < Sidekiq::Crypt::TestCase
7 | def test_override_filtered_parameter
8 | args = [{ a: 123, b: 1 }]
9 | traverse_with_args(args)
10 | assert_equal([{ a: 246, b: 1 }], args)
11 | end
12 |
13 | def test_override_filtered_parameter_inside_an_array
14 | args = [[1, { a: 123, b: 1 }]]
15 | traverse_with_args(args)
16 | assert_equal([[1, { a: 246, b: 1 }]], args)
17 | end
18 |
19 | def test_override_multiple_filtered_parameter
20 | args = [{ a: 123, b: 1 }]
21 | traverse_with_args(args, %w[a b])
22 | assert_equal([{ a: 246, b: 2 }], args)
23 | end
24 |
25 | def test_override_multiple_filtered_with_separate_wrapper
26 | args = [{ a: 123, d: 1 }, [{ a: 1, b: 2 }], { c: 3 }]
27 | traverse_with_args(args, %w[a b c])
28 | assert_equal([{ a: 246, d: 1 }, [{ a: 2, b: 4 }], { c: 6 }], args)
29 | end
30 |
31 | def test_override_params_with_a_regex_filter
32 | args = [{ secret_key: 123, secret_value: 1 }]
33 | traverse_with_args(args, [/secret.*/])
34 | assert_equal([{ secret_key: 246, secret_value: 2 }], args)
35 | end
36 |
37 | def test_dont_override_hash_if_keys_not_filtered
38 | args = [{ b: 123, c: 1234 }]
39 | traverse_with_args(args)
40 | assert_equal([{ b: 123, c: 1234 }], args)
41 | end
42 |
43 | def test_dont_process_primitive_fields
44 | args = [1, nil, 'string']
45 | traverse_with_args(args)
46 | assert_equal([1, nil, 'string'], args)
47 | end
48 |
49 | def test_dont_remove_or_change_primitives_from_arrays
50 | args = [[1, nil, 'string']]
51 | traverse_with_args(args)
52 | assert_equal([[1, nil, 'string']], args)
53 | end
54 |
55 | private
56 |
57 | def traverse_with_args(args, filters = ['a'], proc = double_proc)
58 | traverser = Sidekiq::Crypt::Traverser.new(filters)
59 | traverser.traverse!(args, proc)
60 | end
61 |
62 | def double_proc
63 | proc { |_key, param| param * 2 }
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/test/dummy-app/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | DummyRailties::Application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded on
7 | # every request. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable/disable caching. By default caching is disabled.
18 | if Rails.root.join('tmp/caching-dev.txt').exist?
19 | config.action_controller.perform_caching = true
20 |
21 | config.cache_store = :memory_store
22 | config.public_file_server.headers = {
23 | 'Cache-Control' => 'public, max-age=172800'
24 | }
25 | else
26 | config.action_controller.perform_caching = false
27 |
28 | config.cache_store = :null_store
29 | end
30 |
31 | # Don't care if the mailer can't send.
32 | config.action_mailer.raise_delivery_errors = false
33 |
34 | config.action_mailer.perform_caching = false
35 |
36 | # Print deprecation notices to the Rails logger.
37 | config.active_support.deprecation = :log
38 |
39 | # Raise an error on page load if there are pending migrations.
40 | config.active_record.migration_error = :page_load
41 |
42 | # Debug mode disables concatenation and preprocessing of assets.
43 | # This option may cause significant delays in view rendering with a large
44 | # number of complex assets.
45 | config.assets.debug = true
46 |
47 | # Suppress logger output for asset requests.
48 | config.assets.quiet = true
49 |
50 | # Raises error for missing translations
51 | # config.action_view.raise_on_missing_translations = true
52 |
53 | # Use an evented file watcher to asynchronously detect changes in source code,
54 | # routes, locales, etc. This feature depends on the listen gem.
55 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
56 | end
57 |
--------------------------------------------------------------------------------
/lib/sidekiq-crypt/client_middleware.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'sidekiq-crypt/traverser'
4 | require 'set'
5 |
6 | module Sidekiq
7 | module Crypt
8 | class ClientMiddleware
9 | def initialize(opts = {})
10 | @configuration = opts[:configuration]
11 | @encrypted_keys = Set.new
12 | end
13 |
14 | def call(worker_class, job, _queue, _redis_pool)
15 | if encrypted_worker?(worker_class, job)
16 | @iv = Cipher.random_iv
17 | traverser.traverse!(job['args'], encryption_proc)
18 | write_encryption_header_to_redis(job['jid'], encrypted_keys)
19 | end
20 |
21 | yield
22 | end
23 |
24 | private
25 |
26 | attr_reader :configuration, :encrypted_keys
27 |
28 | def encrypted_worker?(worker_class, job)
29 | @worker_klass = worker_class(worker_class, job)
30 | @worker_klass && @worker_klass.ancestors.include?(Sidekiq::Crypt::Worker)
31 | end
32 |
33 | def traverser
34 | Traverser.new(@worker_klass.sidekiq_crypt_worker_filters || configuration.filters)
35 | end
36 |
37 | def write_encryption_header_to_redis(job_id, encrypted_keys)
38 | Sidekiq.redis do |conn|
39 | conn.set(
40 | "sidekiq-crpyt-header:#{job_id}",
41 | JSON.generate(
42 | nonce: Base64.encode64(@iv),
43 | encrypted_keys: encrypted_keys.to_a,
44 | key_version: Sidekiq::Crypt.configuration.current_key_version
45 | )
46 | )
47 | end
48 | end
49 |
50 | def encryption_proc
51 | proc do |key, param|
52 | encrypted_keys << key
53 | Cipher.encrypt(param, @iv)
54 | end
55 | end
56 |
57 | def worker_class(worker_class, job)
58 | klass = begin
59 | job['args'][0]['job_class'] || worker_class
60 | rescue StandardError
61 | worker_class
62 | end
63 |
64 | if klass.is_a?(Class)
65 | klass
66 | elsif Module.const_defined?(klass)
67 | Module.const_get(klass)
68 | end
69 | end
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/test/dummy-app/config/puma.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Puma can serve each request in a thread from an internal thread pool.
4 | # The `threads` method setting takes two numbers a minimum and maximum.
5 | # Any libraries that use thread pools should be configured to match
6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
7 | # and maximum, this matches the default thread size of Active Record.
8 | #
9 | threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 }.to_i
10 | threads threads_count, threads_count
11 |
12 | # Specifies the `port` that Puma will listen on to receive requests, default is 3000.
13 | #
14 | port ENV.fetch('PORT') { 3000 }
15 |
16 | # Specifies the `environment` that Puma will run in.
17 | #
18 | environment ENV.fetch('RAILS_ENV') { 'development' }
19 |
20 | # Specifies the number of `workers` to boot in clustered mode.
21 | # Workers are forked webserver processes. If using threads and workers together
22 | # the concurrency of the application would be max `threads` * `workers`.
23 | # Workers do not work on JRuby or Windows (both of which do not support
24 | # processes).
25 | #
26 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
27 |
28 | # Use the `preload_app!` method when specifying a `workers` number.
29 | # This directive tells Puma to first boot the application and load code
30 | # before forking the application. This takes advantage of Copy On Write
31 | # process behavior so workers use less memory. If you use this option
32 | # you need to make sure to reconnect any threads in the `on_worker_boot`
33 | # block.
34 | #
35 | # preload_app!
36 |
37 | # The code in the `on_worker_boot` will be called if you are using
38 | # clustered mode by specifying a number of `workers`. After each worker
39 | # process is booted this block will be run, if you are using `preload_app!`
40 | # option you will want to use this block to reconnect to any threads
41 | # or connections that may have been created at application boot, Ruby
42 | # cannot share connections between processes.
43 | #
44 | # on_worker_boot do
45 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
46 | # end
47 |
48 | # Allow puma to be restarted by `rails restart` command.
49 | plugin :tmp_restart
50 |
--------------------------------------------------------------------------------
/lib/sidekiq-crypt/server_middleware.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Sidekiq
4 | module Crypt
5 | class ServerMiddleware
6 | FILTERED = '[FILTERED]'
7 |
8 | def call(_worker_class, job, _queue, _redis_pool = {})
9 | return yield unless encrypted_worker?(job)
10 |
11 | encrypt_secret_params(job)
12 |
13 | yield
14 |
15 | delete_encryption_header(job['jid'])
16 | rescue StandardError => e
17 | if encrypted_worker?(job)
18 | Traverser.new(@encryption_header[:encrypted_keys]).traverse!(job['args'], filter_proc)
19 | end
20 |
21 | raise e
22 | end
23 |
24 | private
25 |
26 | def encrypt_secret_params(job)
27 | @encryption_header = read_encryption_header_from_redis(job['jid'])
28 | Traverser.new(@encryption_header[:encrypted_keys]).traverse!(job['args'], decryption_proc)
29 | end
30 |
31 | def encrypted_worker?(job)
32 | klass = worker_klass(job)
33 | klass && klass.ancestors.include?(Sidekiq::Crypt::Worker)
34 | end
35 |
36 | def worker_klass(job)
37 | klass = begin
38 | job['args'][0]['job_class'] || job['class']
39 | rescue StandardError
40 | job['class']
41 | end
42 | klass.is_a?(Class) ? klass : Module.const_get(klass)
43 | end
44 |
45 | def decryption_proc
46 | proc do |_key, param|
47 | Cipher.decrypt(param, @encryption_header[:iv], @encryption_header[:key_version])
48 | end
49 | end
50 |
51 | def filter_proc
52 | proc { |_key, _param| FILTERED }
53 | end
54 |
55 | def read_encryption_header_from_redis(job_id)
56 | parsed_header = JSON.parse(read_encryption_header(job_id))
57 |
58 | {
59 | iv: Base64.decode64(parsed_header['nonce']),
60 | encrypted_keys: parsed_header['encrypted_keys'],
61 | key_version: parsed_header['key_version']
62 | }
63 | end
64 |
65 | def read_encryption_header(job_id)
66 | Sidekiq.redis do |conn|
67 | conn.get("sidekiq-crpyt-header:#{job_id}")
68 | end
69 | end
70 |
71 | def delete_encryption_header(job_id)
72 | Sidekiq.redis do |conn|
73 | conn.del("sidekiq-crpyt-header:#{job_id}")
74 | end
75 | end
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/lib/sidekiq-crypt.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'sidekiq-crypt/version'
4 | require_relative 'sidekiq-crypt/configuration'
5 | require_relative 'sidekiq-crypt/cipher'
6 | require_relative 'sidekiq-crypt/client_middleware'
7 | require_relative 'sidekiq-crypt/server_middleware'
8 | require_relative 'sidekiq-crypt/worker'
9 |
10 | module Sidekiq
11 | module Crypt
12 | class << self
13 | def configuration(options = {})
14 | @configuration ||= Configuration.new(options)
15 | end
16 |
17 | def configure(options = {})
18 | block_given? ? yield(configuration(options)) : configuration(options)
19 |
20 | configuration.filters.flatten!
21 | validate_configuration!
22 |
23 | inject_sidekiq_middlewares
24 | end
25 |
26 | def inject_sidekiq_middlewares
27 | inject_client_middleware
28 | inject_server_middleware
29 | end
30 |
31 | private
32 |
33 | def inject_client_middleware
34 | ::Sidekiq.configure_client do |config|
35 | config.client_middleware do |chain|
36 | chain.add Sidekiq::Crypt::ClientMiddleware, configuration: configuration
37 | end
38 | end
39 | end
40 |
41 | def inject_server_middleware
42 | ::Sidekiq.configure_server do |config|
43 | config.client_middleware do |chain|
44 | chain.add Sidekiq::Crypt::ClientMiddleware, configuration: configuration
45 | end
46 |
47 | config.server_middleware do |chain|
48 | chain.add Sidekiq::Crypt::ServerMiddleware
49 | end
50 | end
51 | end
52 |
53 | def validate_configuration!
54 | raise 'you must specify current key version' if configuration.current_key_version.blank?
55 | raise 'you must specify a hash for key store' if configuration.key_store.blank?
56 | raise "current_key_version can't be found in key_store" if current_raw_key.nil?
57 | raise 'current key is not valid for encryption' if invalid_key?
58 | end
59 |
60 | def current_raw_key
61 | # returns the base64 encoded version of the key
62 | configuration.key_store[configuration.current_key_version]
63 | end
64 |
65 | def invalid_key?
66 | Sidekiq::Crypt::Cipher.encrypt('dummy_str', Sidekiq::Crypt::Cipher.random_iv)
67 | false
68 | rescue StandardError
69 | true
70 | end
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/test/dummy-app/test/workers/sidekiq_crypt/encrypted_worker_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require File.expand_path('../../test_helper', __dir__)
4 | require File.expand_path('../../helpers/redis_helpers', __dir__)
5 | require File.expand_path('../../helpers/sidekiq_helpers', __dir__)
6 | require File.expand_path('../../helpers/utils', __dir__)
7 |
8 | class SidekiqCrypt::EncryptedWorkerTest < ActiveSupport::TestCase
9 | include RedisHelpers
10 | include SidekiqHelpers
11 | include Utils
12 |
13 | def setup
14 | Sidekiq.redis(&:flushdb)
15 | end
16 |
17 | def test_encrypted_worker_does_not_write_header_on_redis
18 | configure_sidekiq_crypt([:divider])
19 |
20 | Sidekiq::Testing.disable! do
21 | job_id = SidekiqCrypt::EncryptedWorker.perform_async(password: '123456', divider: '0')
22 |
23 | assert_not_nil(sidekiq_crypt_header(job_id))
24 |
25 | assert_raises('TypeError') do
26 | execute_job
27 | end
28 |
29 | assert_not_nil(sidekiq_crypt_header(job_id))
30 | assert_equal('123456', decrypt_retried_job_param(job_id: job_id, key: 'password'))
31 | assert_equal('0', decrypt_retried_job_param(job_id: job_id, key: 'divider'))
32 | end
33 | end
34 |
35 | def test_encrypted_worker_considers_rails_filter_params_by_default
36 | configure_sidekiq_crypt
37 |
38 | Sidekiq::Testing.disable! do
39 | job_id = SidekiqCrypt::EncryptedWorker.perform_async(password: '123456', divider: '0')
40 |
41 | assert_equal(['password'], sidekiq_crypt_header(job_id)['encrypted_keys'])
42 |
43 | assert_raises('TypeError') do
44 | execute_job
45 | end
46 |
47 | assert_not_nil(sidekiq_crypt_header(job_id))
48 | assert_equal('123456', decrypt_retried_job_param(job_id: job_id, key: 'password'))
49 | assert_equal('0', retried_job_args['divider'])
50 | end
51 | end
52 |
53 | def test_encrypted_worker_excludes_rails_filter_params
54 | configure_sidekiq_crypt([:divider], exclude_rails_filters: true)
55 |
56 | Sidekiq::Testing.disable! do
57 | job_id = SidekiqCrypt::EncryptedWorker.perform_async(password: '123456', divider: '0')
58 |
59 | assert_equal(['divider'], sidekiq_crypt_header(job_id)['encrypted_keys'])
60 |
61 | assert_raises('TypeError') do
62 | execute_job
63 | end
64 |
65 | assert_not_nil(sidekiq_crypt_header(job_id))
66 | assert_equal('123456', retried_job_args['password'])
67 | assert_equal('0', decrypt_retried_job_param(job_id: job_id, key: 'divider'))
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/test/sidekiq-crypt_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class Sidekiq::CryptTest < Sidekiq::Crypt::TestCase
6 | def test_that_it_has_a_version_number
7 | assert_equal('0.1.1', ::Sidekiq::Crypt::VERSION)
8 | end
9 |
10 | def test_adds_filters_using_configure
11 | configure_sidekiq_crypt(filters: ['key', /^secret.*/])
12 |
13 | assert_equal(['key', /^secret.*/], Sidekiq::Crypt.configuration.filters)
14 | end
15 |
16 | def test_raises_error_if_no_key_version_specified
17 | assert_raised_error('you must specify current key version') do
18 | configure_sidekiq_crypt(options: {})
19 | end
20 | end
21 |
22 | def test_raises_error_if_no_key_store_specified
23 | assert_raised_error('you must specify a hash for key store') do
24 | configure_sidekiq_crypt(options: { current_key_version: 'V3' })
25 | end
26 | end
27 |
28 | def test_raises_error_if_empty_key_store_specified
29 | assert_raised_error('you must specify a hash for key store') do
30 | configure_sidekiq_crypt(options: { current_key_version: 'V3', key_store: {} })
31 | end
32 | end
33 |
34 | def test_raises_error_if_key_store_and_current_version_does_not_match
35 | assert_raised_error("current_key_version can't be found in key_store") do
36 | configure_sidekiq_crypt(
37 | options: {
38 | current_key_version: 'V2', key_store: { 'V1' => Base64.strict_encode64('123') }
39 | }
40 | )
41 | end
42 | end
43 |
44 | def test_raises_error_if_current_key_is_not_valid_for_encryption
45 | assert_raised_error('current key is not valid for encryption') do
46 | configure_sidekiq_crypt(
47 | options: {
48 | current_key_version: 'V1', key_store: { 'V1' => Base64.strict_encode64('123') }
49 | }
50 | )
51 | end
52 | end
53 |
54 | def test_stringfy_key_store_keys_when_configure_used
55 | configure_sidekiq_crypt(options: {
56 | current_key_version: 'V1',
57 | key_store: {
58 | V1: Base64.strict_encode64('ThisPasswordIsReallyHardToGuess!'),
59 | V2: Base64.strict_encode64('246')
60 | }
61 | })
62 |
63 | config = Sidekiq::Crypt.configuration
64 |
65 | assert_equal(
66 | Base64.strict_encode64('ThisPasswordIsReallyHardToGuess!'), config.key_store['V1']
67 | )
68 | assert_equal(Base64.strict_encode64('246'), config.key_store['V2'])
69 | end
70 |
71 | private
72 |
73 | def assert_raised_error(error_message)
74 | error = assert_raises do
75 | yield
76 | end
77 |
78 | assert_equal(error_message, error.message)
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at toygar-murat@hotmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/gemfiles/rails-4.2-stable.gemfile.lock:
--------------------------------------------------------------------------------
1 | GIT
2 | remote: git://github.com/rails/rails.git
3 | revision: e9d6b85f3e834ceea2aeabe4cbaa96a7c73eb896
4 | branch: 4-2-stable
5 | specs:
6 | actionmailer (4.2.11.1)
7 | actionpack (= 4.2.11.1)
8 | actionview (= 4.2.11.1)
9 | activejob (= 4.2.11.1)
10 | mail (~> 2.5, >= 2.5.4)
11 | rails-dom-testing (~> 1.0, >= 1.0.5)
12 | actionpack (4.2.11.1)
13 | actionview (= 4.2.11.1)
14 | activesupport (= 4.2.11.1)
15 | rack (~> 1.6)
16 | rack-test (~> 0.6.2)
17 | rails-dom-testing (~> 1.0, >= 1.0.5)
18 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
19 | actionview (4.2.11.1)
20 | activesupport (= 4.2.11.1)
21 | builder (~> 3.1)
22 | erubis (~> 2.7.0)
23 | rails-dom-testing (~> 1.0, >= 1.0.5)
24 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
25 | activejob (4.2.11.1)
26 | activesupport (= 4.2.11.1)
27 | globalid (>= 0.3.0)
28 | activemodel (4.2.11.1)
29 | activesupport (= 4.2.11.1)
30 | builder (~> 3.1)
31 | activerecord (4.2.11.1)
32 | activemodel (= 4.2.11.1)
33 | activesupport (= 4.2.11.1)
34 | arel (~> 6.0)
35 | activesupport (4.2.11.1)
36 | i18n (~> 0.7)
37 | minitest (~> 5.1)
38 | thread_safe (~> 0.3, >= 0.3.4)
39 | tzinfo (~> 1.1)
40 | rails (4.2.11.1)
41 | actionmailer (= 4.2.11.1)
42 | actionpack (= 4.2.11.1)
43 | actionview (= 4.2.11.1)
44 | activejob (= 4.2.11.1)
45 | activemodel (= 4.2.11.1)
46 | activerecord (= 4.2.11.1)
47 | activesupport (= 4.2.11.1)
48 | bundler (>= 1.3.0, < 2.0)
49 | railties (= 4.2.11.1)
50 | sprockets-rails
51 | railties (4.2.11.1)
52 | actionpack (= 4.2.11.1)
53 | activesupport (= 4.2.11.1)
54 | rake (>= 0.8.7)
55 | thor (>= 0.18.1, < 2.0)
56 |
57 | PATH
58 | remote: ..
59 | specs:
60 | sidekiq-crypt (0.1.0)
61 | sidekiq (~> 5.0)
62 |
63 | GEM
64 | remote: https://rubygems.org/
65 | specs:
66 | arel (6.0.4)
67 | builder (3.2.3)
68 | concurrent-ruby (1.1.5)
69 | connection_pool (2.2.2)
70 | crass (1.0.5)
71 | erubis (2.7.0)
72 | globalid (0.4.2)
73 | activesupport (>= 4.2.0)
74 | i18n (0.9.5)
75 | concurrent-ruby (~> 1.0)
76 | loofah (2.3.1)
77 | crass (~> 1.0.2)
78 | nokogiri (>= 1.5.9)
79 | mail (2.7.1)
80 | mini_mime (>= 0.1.1)
81 | mini_mime (1.0.2)
82 | mini_portile2 (2.4.0)
83 | minitest (5.12.2)
84 | nokogiri (1.10.4)
85 | mini_portile2 (~> 2.4.0)
86 | rack (1.6.11)
87 | rack-protection (2.0.7)
88 | rack
89 | rack-test (0.6.3)
90 | rack (>= 1.0)
91 | rails-deprecated_sanitizer (1.0.3)
92 | activesupport (>= 4.2.0.alpha)
93 | rails-dom-testing (1.0.9)
94 | activesupport (>= 4.2.0, < 5.0)
95 | nokogiri (~> 1.6)
96 | rails-deprecated_sanitizer (>= 1.0.1)
97 | rails-html-sanitizer (1.3.0)
98 | loofah (~> 2.3)
99 | rake (10.5.0)
100 | redis (4.1.3)
101 | sidekiq (5.2.7)
102 | connection_pool (~> 2.2, >= 2.2.2)
103 | rack (>= 1.5.0)
104 | rack-protection (>= 1.5.0)
105 | redis (>= 3.3.5, < 5)
106 | sprockets (3.7.2)
107 | concurrent-ruby (~> 1.0)
108 | rack (> 1, < 3)
109 | sprockets-rails (3.2.1)
110 | actionpack (>= 4.0)
111 | activesupport (>= 4.0)
112 | sprockets (>= 3.0.0)
113 | thor (0.20.3)
114 | thread_safe (0.3.6)
115 | tzinfo (1.2.5)
116 | thread_safe (~> 0.1)
117 |
118 | PLATFORMS
119 | ruby
120 |
121 | DEPENDENCIES
122 | bundler (~> 1.17)
123 | minitest (~> 5.0)
124 | rails!
125 | rake (~> 10.0)
126 | sidekiq-crypt!
127 |
128 | BUNDLED WITH
129 | 1.17.3
130 |
--------------------------------------------------------------------------------
/gemfiles/rails-5.0-stable.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | sidekiq-crypt (0.1.0)
5 | sidekiq (~> 5.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (5.0.7.2)
11 | actionpack (= 5.0.7.2)
12 | nio4r (>= 1.2, < 3.0)
13 | websocket-driver (~> 0.6.1)
14 | actionmailer (5.0.7.2)
15 | actionpack (= 5.0.7.2)
16 | actionview (= 5.0.7.2)
17 | activejob (= 5.0.7.2)
18 | mail (~> 2.5, >= 2.5.4)
19 | rails-dom-testing (~> 2.0)
20 | actionpack (5.0.7.2)
21 | actionview (= 5.0.7.2)
22 | activesupport (= 5.0.7.2)
23 | rack (~> 2.0)
24 | rack-test (~> 0.6.3)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
27 | actionview (5.0.7.2)
28 | activesupport (= 5.0.7.2)
29 | builder (~> 3.1)
30 | erubis (~> 2.7.0)
31 | rails-dom-testing (~> 2.0)
32 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
33 | activejob (5.0.7.2)
34 | activesupport (= 5.0.7.2)
35 | globalid (>= 0.3.6)
36 | activemodel (5.0.7.2)
37 | activesupport (= 5.0.7.2)
38 | activerecord (5.0.7.2)
39 | activemodel (= 5.0.7.2)
40 | activesupport (= 5.0.7.2)
41 | arel (~> 7.0)
42 | activesupport (5.0.7.2)
43 | concurrent-ruby (~> 1.0, >= 1.0.2)
44 | i18n (>= 0.7, < 2)
45 | minitest (~> 5.1)
46 | tzinfo (~> 1.1)
47 | arel (7.1.4)
48 | builder (3.2.3)
49 | concurrent-ruby (1.1.5)
50 | connection_pool (2.2.2)
51 | crass (1.0.5)
52 | erubis (2.7.0)
53 | globalid (0.4.2)
54 | activesupport (>= 4.2.0)
55 | i18n (1.7.0)
56 | concurrent-ruby (~> 1.0)
57 | loofah (2.3.1)
58 | crass (~> 1.0.2)
59 | nokogiri (>= 1.5.9)
60 | mail (2.7.1)
61 | mini_mime (>= 0.1.1)
62 | method_source (0.9.2)
63 | mini_mime (1.0.2)
64 | mini_portile2 (2.4.0)
65 | minitest (5.12.2)
66 | nio4r (2.5.2)
67 | nokogiri (1.10.4)
68 | mini_portile2 (~> 2.4.0)
69 | rack (2.0.7)
70 | rack-protection (2.0.7)
71 | rack
72 | rack-test (0.6.3)
73 | rack (>= 1.0)
74 | rails (5.0.7.2)
75 | actioncable (= 5.0.7.2)
76 | actionmailer (= 5.0.7.2)
77 | actionpack (= 5.0.7.2)
78 | actionview (= 5.0.7.2)
79 | activejob (= 5.0.7.2)
80 | activemodel (= 5.0.7.2)
81 | activerecord (= 5.0.7.2)
82 | activesupport (= 5.0.7.2)
83 | bundler (>= 1.3.0)
84 | railties (= 5.0.7.2)
85 | sprockets-rails (>= 2.0.0)
86 | rails-dom-testing (2.0.3)
87 | activesupport (>= 4.2.0)
88 | nokogiri (>= 1.6)
89 | rails-html-sanitizer (1.3.0)
90 | loofah (~> 2.3)
91 | railties (5.0.7.2)
92 | actionpack (= 5.0.7.2)
93 | activesupport (= 5.0.7.2)
94 | method_source
95 | rake (>= 0.8.7)
96 | thor (>= 0.18.1, < 2.0)
97 | rake (10.5.0)
98 | redis (4.1.3)
99 | sidekiq (5.2.7)
100 | connection_pool (~> 2.2, >= 2.2.2)
101 | rack (>= 1.5.0)
102 | rack-protection (>= 1.5.0)
103 | redis (>= 3.3.5, < 5)
104 | sprockets (3.7.2)
105 | concurrent-ruby (~> 1.0)
106 | rack (> 1, < 3)
107 | sprockets-rails (3.2.1)
108 | actionpack (>= 4.0)
109 | activesupport (>= 4.0)
110 | sprockets (>= 3.0.0)
111 | thor (0.20.3)
112 | thread_safe (0.3.6)
113 | tzinfo (1.2.5)
114 | thread_safe (~> 0.1)
115 | websocket-driver (0.6.5)
116 | websocket-extensions (>= 0.1.0)
117 | websocket-extensions (0.1.4)
118 |
119 | PLATFORMS
120 | ruby
121 |
122 | DEPENDENCIES
123 | bundler (~> 1.17)
124 | minitest (~> 5.0)
125 | rails (~> 5.0.0)
126 | rake (~> 10.0)
127 | sidekiq-crypt!
128 |
129 | BUNDLED WITH
130 | 1.17.3
131 |
--------------------------------------------------------------------------------
/test/dummy-app/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | DummyRailties::Application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.cache_classes = true
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Disable serving static files from the `/public` folder by default since
20 | # Apache or NGINX already handles this.
21 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
22 |
23 | # Compress JavaScripts and CSS.
24 | config.assets.js_compressor = :uglifier
25 | # config.assets.css_compressor = :sass
26 |
27 | # Do not fallback to assets pipeline if a precompiled asset is missed.
28 | config.assets.compile = false
29 |
30 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
31 |
32 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
33 | # config.action_controller.asset_host = 'http://assets.example.com'
34 |
35 | # Specifies the header that your server uses for sending files.
36 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
37 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
38 |
39 | # Mount Action Cable outside main process or domain
40 | # config.action_cable.mount_path = nil
41 | # config.action_cable.url = 'wss://example.com/cable'
42 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
43 |
44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
45 | # config.force_ssl = true
46 |
47 | # Use the lowest log level to ensure availability of diagnostic information
48 | # when problems arise.
49 | config.log_level = :debug
50 |
51 | # Prepend all log lines with the following tags.
52 | config.log_tags = [:request_id]
53 |
54 | # Use a different cache store in production.
55 | # config.cache_store = :mem_cache_store
56 |
57 | # Use a real queuing backend for Active Job (and separate queues per environment)
58 | # config.active_job.queue_adapter = :resque
59 | # config.active_job.queue_name_prefix = "dummy-railties_#{Rails.env}"
60 | config.action_mailer.perform_caching = false
61 |
62 | # Ignore bad email addresses and do not raise email delivery errors.
63 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
64 | # config.action_mailer.raise_delivery_errors = false
65 |
66 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
67 | # the I18n.default_locale when a translation cannot be found).
68 | config.i18n.fallbacks = true
69 |
70 | # Send deprecation notices to registered listeners.
71 | config.active_support.deprecation = :notify
72 |
73 | # Use default logging formatter so that PID and timestamp are not suppressed.
74 | config.log_formatter = ::Logger::Formatter.new
75 |
76 | # Use a different logger for distributed setups.
77 | # require 'syslog/logger'
78 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
79 |
80 | if ENV['RAILS_LOG_TO_STDOUT'].present?
81 | logger = ActiveSupport::Logger.new(STDOUT)
82 | logger.formatter = config.log_formatter
83 | config.logger = ActiveSupport::TaggedLogging.new(logger)
84 | end
85 |
86 | # Do not dump schema after migrations.
87 | config.active_record.dump_schema_after_migration = false
88 | end
89 |
--------------------------------------------------------------------------------
/gemfiles/rails-5.2-stable.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | sidekiq-crypt (0.1.0)
5 | sidekiq (~> 5.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (5.2.3)
11 | actionpack (= 5.2.3)
12 | nio4r (~> 2.0)
13 | websocket-driver (>= 0.6.1)
14 | actionmailer (5.2.3)
15 | actionpack (= 5.2.3)
16 | actionview (= 5.2.3)
17 | activejob (= 5.2.3)
18 | mail (~> 2.5, >= 2.5.4)
19 | rails-dom-testing (~> 2.0)
20 | actionpack (5.2.3)
21 | actionview (= 5.2.3)
22 | activesupport (= 5.2.3)
23 | rack (~> 2.0)
24 | rack-test (>= 0.6.3)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
27 | actionview (5.2.3)
28 | activesupport (= 5.2.3)
29 | builder (~> 3.1)
30 | erubi (~> 1.4)
31 | rails-dom-testing (~> 2.0)
32 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
33 | activejob (5.2.3)
34 | activesupport (= 5.2.3)
35 | globalid (>= 0.3.6)
36 | activemodel (5.2.3)
37 | activesupport (= 5.2.3)
38 | activerecord (5.2.3)
39 | activemodel (= 5.2.3)
40 | activesupport (= 5.2.3)
41 | arel (>= 9.0)
42 | activestorage (5.2.3)
43 | actionpack (= 5.2.3)
44 | activerecord (= 5.2.3)
45 | marcel (~> 0.3.1)
46 | activesupport (5.2.3)
47 | concurrent-ruby (~> 1.0, >= 1.0.2)
48 | i18n (>= 0.7, < 2)
49 | minitest (~> 5.1)
50 | tzinfo (~> 1.1)
51 | arel (9.0.0)
52 | builder (3.2.3)
53 | concurrent-ruby (1.1.5)
54 | connection_pool (2.2.2)
55 | crass (1.0.5)
56 | erubi (1.9.0)
57 | globalid (0.4.2)
58 | activesupport (>= 4.2.0)
59 | i18n (1.7.0)
60 | concurrent-ruby (~> 1.0)
61 | loofah (2.3.1)
62 | crass (~> 1.0.2)
63 | nokogiri (>= 1.5.9)
64 | mail (2.7.1)
65 | mini_mime (>= 0.1.1)
66 | marcel (0.3.3)
67 | mimemagic (~> 0.3.2)
68 | method_source (0.9.2)
69 | mimemagic (0.3.3)
70 | mini_mime (1.0.2)
71 | mini_portile2 (2.4.0)
72 | minitest (5.12.2)
73 | nio4r (2.5.2)
74 | nokogiri (1.10.4)
75 | mini_portile2 (~> 2.4.0)
76 | rack (2.0.7)
77 | rack-protection (2.0.7)
78 | rack
79 | rack-test (1.1.0)
80 | rack (>= 1.0, < 3)
81 | rails (5.2.3)
82 | actioncable (= 5.2.3)
83 | actionmailer (= 5.2.3)
84 | actionpack (= 5.2.3)
85 | actionview (= 5.2.3)
86 | activejob (= 5.2.3)
87 | activemodel (= 5.2.3)
88 | activerecord (= 5.2.3)
89 | activestorage (= 5.2.3)
90 | activesupport (= 5.2.3)
91 | bundler (>= 1.3.0)
92 | railties (= 5.2.3)
93 | sprockets-rails (>= 2.0.0)
94 | rails-dom-testing (2.0.3)
95 | activesupport (>= 4.2.0)
96 | nokogiri (>= 1.6)
97 | rails-html-sanitizer (1.3.0)
98 | loofah (~> 2.3)
99 | railties (5.2.3)
100 | actionpack (= 5.2.3)
101 | activesupport (= 5.2.3)
102 | method_source
103 | rake (>= 0.8.7)
104 | thor (>= 0.19.0, < 2.0)
105 | rake (10.5.0)
106 | redis (4.1.3)
107 | sidekiq (5.2.7)
108 | connection_pool (~> 2.2, >= 2.2.2)
109 | rack (>= 1.5.0)
110 | rack-protection (>= 1.5.0)
111 | redis (>= 3.3.5, < 5)
112 | sprockets (4.0.0)
113 | concurrent-ruby (~> 1.0)
114 | rack (> 1, < 3)
115 | sprockets-rails (3.2.1)
116 | actionpack (>= 4.0)
117 | activesupport (>= 4.0)
118 | sprockets (>= 3.0.0)
119 | thor (0.20.3)
120 | thread_safe (0.3.6)
121 | tzinfo (1.2.5)
122 | thread_safe (~> 0.1)
123 | websocket-driver (0.7.1)
124 | websocket-extensions (>= 0.1.0)
125 | websocket-extensions (0.1.4)
126 |
127 | PLATFORMS
128 | ruby
129 |
130 | DEPENDENCIES
131 | bundler (~> 1.17)
132 | minitest (~> 5.0)
133 | rails (~> 5.2)
134 | rake (~> 10.0)
135 | sidekiq-crypt!
136 |
137 | BUNDLED WITH
138 | 1.17.3
139 |
--------------------------------------------------------------------------------
/test/sidekiq-crypt/server_middleware_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 | require 'sidekiq/testing'
5 |
6 | class ServerMiddlewareTest < Sidekiq::Crypt::TestCase
7 | def test_decrypts_filtered_job_params
8 | params = job_params # shallow copy params from job_params to assert later
9 | call_middleware(params) {}
10 |
11 | assert_equal('1234123412341234', params['args'][1]['secret_key1'])
12 | assert_equal('A SECRET', params['args'][1]['secret_key2'])
13 | end
14 |
15 | def test_filters_job_params_on_job_failure
16 | params = job_params # shallow copy params from job_params to assert later
17 |
18 | error = StandardError.new
19 | middleware_error = assert_raises do
20 | call_middleware(params) { raise error }
21 | end
22 |
23 | assert_equal('[FILTERED]', params['args'][1]['secret_key1'])
24 | assert_equal('[FILTERED]', params['args'][1]['secret_key2'])
25 | assert_equal('123', params['args'][1]['some_key'])
26 | assert_equal(middleware_error, error)
27 | end
28 |
29 | def test_decrypts_filtered_params_using_expired_key
30 | params = job_params # shallow copy params from job_params to assert later
31 | key_attrs = {
32 | current_key_version: 'V2', key_store: { 'V1' => ENV['CIPHER_KEY'], 'V2' => valid_key }
33 | }
34 |
35 | call_middleware(params, config_attrs: key_attrs) {}
36 |
37 | assert_equal('1234123412341234', params['args'][1]['secret_key1'])
38 | assert_equal('A SECRET', params['args'][1]['secret_key2'])
39 | end
40 |
41 | def test_deletes_sidekiq_crypt_header_from_redis
42 | call_middleware(job_params) {}
43 |
44 | Sidekiq.redis do |conn|
45 | assert_nil(conn.get("sidekiq-crpyt-header:#{job_id}"))
46 | end
47 | end
48 |
49 | def test_does_not_delete_sidekiq_crypt_header_from_redis_on_job_failure
50 | assert_raises do
51 | call_middleware(job_params) { raise error }
52 | end
53 |
54 | Sidekiq.redis do |conn|
55 | refute_nil(conn.get("sidekiq-crpyt-header:#{job_id}"))
56 | end
57 | end
58 |
59 | def test_does_not_decrypt_when_sidekiq_crypt_worker_not_included
60 | params = job_params('SafeWorker') # shallow copy params from job_params to assert later
61 | server_middleware.call(SafeWorker, params, 'default', nil) {}
62 |
63 | assert_equal('zrZcSf2pQZR5P1yBvYa9GdSmW0N+TMT1z6JzrPrgxWg=', params['args'][1]['secret_key1'])
64 | assert_equal('PdHia8epi6I8IUs+Ya9WIQ==', params['args'][1]['secret_key2'])
65 | assert_equal('123', params['args'][1]['some_key'])
66 | end
67 |
68 | private
69 |
70 | def call_middleware(params, config_attrs: config_key_attrs, &block)
71 | configure_sidekiq_crypt(options: config_attrs)
72 | Sidekiq.redis do |conn|
73 | conn.set("sidekiq-crpyt-header:#{job_id}", JSON.generate(
74 | nonce: Base64.encode64(valid_iv),
75 | encrypted_keys: [:secret_key1, 'secret_key2'],
76 | key_version: 'V1'
77 | ))
78 | end
79 | sleep 0.2
80 |
81 | server_middleware.call(SecretWorker, params, 'default', nil, &block)
82 | end
83 |
84 | def server_middleware
85 | Sidekiq::Crypt::ServerMiddleware.new
86 | end
87 |
88 | def valid_iv
89 | # a valid iv should be at least 16 bytes
90 | '1' * 16
91 | end
92 |
93 | def valid_key
94 | Base64.strict_encode64('1' * 32)
95 | end
96 |
97 | def job_params(worker_name = 'SecretWorker')
98 | {
99 | 'class' => worker_name,
100 | 'args' => [3, {
101 | 'secret_key1' => 'zrZcSf2pQZR5P1yBvYa9GdSmW0N+TMT1z6JzrPrgxWg=',
102 | 'secret_key2' => 'PdHia8epi6I8IUs+Ya9WIQ==',
103 | 'some_key' => '123'
104 | }],
105 | 'retry' => false,
106 | 'queue' => 'default',
107 | 'jid' => job_id,
108 | 'created_at' => 1_570_907_859.9315178
109 | }
110 | end
111 |
112 | def job_id
113 | '5178fe171bdb2e925b3b2020'
114 | end
115 | end
116 |
--------------------------------------------------------------------------------
/test/sidekiq-crypt/client_middleware_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 | require 'sidekiq/testing'
5 |
6 | class ClientMiddlewareTest < Sidekiq::Crypt::TestCase
7 | def setup
8 | super
9 | configure_sidekiq_crypt
10 | end
11 |
12 | def test_writes_nonce_to_encryption_header_on_redis
13 | stub_iv_creation do
14 | client_middleware.call(SecretWorker, job_params, 'default', nil) {}
15 |
16 | assert_equal(Base64.encode64(valid_iv), JSON.parse(nonce_payload)['nonce'])
17 | end
18 | end
19 |
20 | def test_writes_encrypted_keys_to_encryption_header_on_redis
21 | stub_iv_creation do
22 | client_middleware.call(SecretWorker, job_params, 'default', nil) {}
23 |
24 | assert_equal(%w[secret_key1 secret_key2], JSON.parse(nonce_payload)['encrypted_keys'])
25 | end
26 | end
27 |
28 | def test_writes_encryption_key_version_to_encryption_header_on_redis
29 | stub_iv_creation do
30 | client_middleware.call(SecretWorker, job_params, 'default', nil) {}
31 |
32 | assert_equal('V1', JSON.parse(nonce_payload)['key_version'])
33 | end
34 | end
35 |
36 | def test_encrypts_filtered_params
37 | stub_iv_creation do
38 | # shallow copy params from job_params to assert later
39 | params = job_params
40 | client_middleware.call(SecretWorker, params, 'default', nil) {}
41 |
42 | assert_equal(encrypted_value('1234123412341234'), params['args'][1]['secret_key1'])
43 | assert_equal(encrypted_value('A SECRET'), params['args'][1]['secret_key2'])
44 | assert_equal('123', params['args'][1]['some_key'])
45 | end
46 | end
47 |
48 | def test_encrypts_params_with_stated_keys
49 | stub_iv_creation do
50 | # shallow copy params from job_params to assert later
51 | params = job_params('SecretWorkerWithKey')
52 | client_middleware.call(SecretWorkerWithKey, params, 'default', nil) {}
53 |
54 | assert_equal('1234123412341234', params['args'][1]['secret_key1'])
55 | assert_equal('A SECRET', params['args'][1]['secret_key2'])
56 | assert_equal(encrypted_value('123'), params['args'][1]['some_key'])
57 | end
58 | end
59 |
60 | def test_does_not_encrypt_filtered_params_if_sidekiq_crypt_worker_is_not_included
61 | # shallow copy params from job_params to assert later
62 | params = job_params('SafeWorker')
63 | client_middleware.call(SafeWorker, params, 'default', nil) {}
64 |
65 | assert_equal('1234123412341234', params['args'][1]['secret_key1'])
66 | assert_equal('A SECRET', params['args'][1]['secret_key2'])
67 | assert_equal('123', params['args'][1]['some_key'])
68 | end
69 |
70 | def test_does_not_write_encryption_header_on_redis_if_sidekiq_crypt_worker_is_not_included
71 | client_middleware.call(SafeWorker, job_params('SafeWorker'), 'default', nil) {}
72 |
73 | assert_nil(nonce_payload)
74 | end
75 |
76 | private
77 |
78 | def client_middleware
79 | Sidekiq::Crypt::ClientMiddleware.new(configuration: config)
80 | end
81 |
82 | def nonce_payload
83 | Sidekiq.redis { |conn| conn.get('sidekiq-crpyt-header:5178fe171bdb2e925b3b2020') }
84 | end
85 |
86 | def stub_iv_creation
87 | OpenSSL::Random.stub(:random_bytes, valid_iv) do
88 | yield
89 | end
90 | end
91 |
92 | def valid_iv
93 | # a valid iv should be at least 16 bytes
94 | '1' * 16
95 | end
96 |
97 | def job_params(worker_name = 'SecretWorker')
98 | {
99 | 'class' => worker_name,
100 | 'args' => [3, {
101 | 'secret_key1' => '1234123412341234',
102 | 'secret_key2' => 'A SECRET',
103 | 'some_key' => '123'
104 | }],
105 | 'retry' => false,
106 | 'queue' => 'default',
107 | 'jid' => '5178fe171bdb2e925b3b2020',
108 | 'created_at' => 1_570_907_859.9315178
109 | }
110 | end
111 |
112 | def config
113 | config = Sidekiq::Crypt::Configuration.new(config_key_attrs)
114 | config.filters << [/^secret.*/]
115 | config.filters.flatten!
116 |
117 | config
118 | end
119 |
120 | def encrypted_value(value)
121 | cipher = OpenSSL::Cipher::AES.new(256, :CBC).encrypt
122 | cipher.key = Base64.strict_decode64(ENV['CIPHER_KEY'])
123 | cipher.iv = valid_iv
124 |
125 | Base64.encode64(cipher.update(value.to_s) + cipher.final)
126 | end
127 | end
128 |
--------------------------------------------------------------------------------
/gemfiles/rails-6.0-stable.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | sidekiq-crypt (0.1.0)
5 | sidekiq (~> 5.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (6.0.0)
11 | actionpack (= 6.0.0)
12 | nio4r (~> 2.0)
13 | websocket-driver (>= 0.6.1)
14 | actionmailbox (6.0.0)
15 | actionpack (= 6.0.0)
16 | activejob (= 6.0.0)
17 | activerecord (= 6.0.0)
18 | activestorage (= 6.0.0)
19 | activesupport (= 6.0.0)
20 | mail (>= 2.7.1)
21 | actionmailer (6.0.0)
22 | actionpack (= 6.0.0)
23 | actionview (= 6.0.0)
24 | activejob (= 6.0.0)
25 | mail (~> 2.5, >= 2.5.4)
26 | rails-dom-testing (~> 2.0)
27 | actionpack (6.0.0)
28 | actionview (= 6.0.0)
29 | activesupport (= 6.0.0)
30 | rack (~> 2.0)
31 | rack-test (>= 0.6.3)
32 | rails-dom-testing (~> 2.0)
33 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
34 | actiontext (6.0.0)
35 | actionpack (= 6.0.0)
36 | activerecord (= 6.0.0)
37 | activestorage (= 6.0.0)
38 | activesupport (= 6.0.0)
39 | nokogiri (>= 1.8.5)
40 | actionview (6.0.0)
41 | activesupport (= 6.0.0)
42 | builder (~> 3.1)
43 | erubi (~> 1.4)
44 | rails-dom-testing (~> 2.0)
45 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
46 | activejob (6.0.0)
47 | activesupport (= 6.0.0)
48 | globalid (>= 0.3.6)
49 | activemodel (6.0.0)
50 | activesupport (= 6.0.0)
51 | activerecord (6.0.0)
52 | activemodel (= 6.0.0)
53 | activesupport (= 6.0.0)
54 | activestorage (6.0.0)
55 | actionpack (= 6.0.0)
56 | activejob (= 6.0.0)
57 | activerecord (= 6.0.0)
58 | marcel (~> 0.3.1)
59 | activesupport (6.0.0)
60 | concurrent-ruby (~> 1.0, >= 1.0.2)
61 | i18n (>= 0.7, < 2)
62 | minitest (~> 5.1)
63 | tzinfo (~> 1.1)
64 | zeitwerk (~> 2.1, >= 2.1.8)
65 | builder (3.2.3)
66 | concurrent-ruby (1.1.5)
67 | connection_pool (2.2.2)
68 | crass (1.0.5)
69 | erubi (1.9.0)
70 | globalid (0.4.2)
71 | activesupport (>= 4.2.0)
72 | i18n (1.7.0)
73 | concurrent-ruby (~> 1.0)
74 | loofah (2.3.1)
75 | crass (~> 1.0.2)
76 | nokogiri (>= 1.5.9)
77 | mail (2.7.1)
78 | mini_mime (>= 0.1.1)
79 | marcel (0.3.3)
80 | mimemagic (~> 0.3.2)
81 | method_source (0.9.2)
82 | mimemagic (0.3.3)
83 | mini_mime (1.0.2)
84 | mini_portile2 (2.4.0)
85 | minitest (5.12.2)
86 | nio4r (2.5.2)
87 | nokogiri (1.10.4)
88 | mini_portile2 (~> 2.4.0)
89 | rack (2.0.7)
90 | rack-protection (2.0.7)
91 | rack
92 | rack-test (1.1.0)
93 | rack (>= 1.0, < 3)
94 | rails (6.0.0)
95 | actioncable (= 6.0.0)
96 | actionmailbox (= 6.0.0)
97 | actionmailer (= 6.0.0)
98 | actionpack (= 6.0.0)
99 | actiontext (= 6.0.0)
100 | actionview (= 6.0.0)
101 | activejob (= 6.0.0)
102 | activemodel (= 6.0.0)
103 | activerecord (= 6.0.0)
104 | activestorage (= 6.0.0)
105 | activesupport (= 6.0.0)
106 | bundler (>= 1.3.0)
107 | railties (= 6.0.0)
108 | sprockets-rails (>= 2.0.0)
109 | rails-dom-testing (2.0.3)
110 | activesupport (>= 4.2.0)
111 | nokogiri (>= 1.6)
112 | rails-html-sanitizer (1.3.0)
113 | loofah (~> 2.3)
114 | railties (6.0.0)
115 | actionpack (= 6.0.0)
116 | activesupport (= 6.0.0)
117 | method_source
118 | rake (>= 0.8.7)
119 | thor (>= 0.20.3, < 2.0)
120 | rake (10.5.0)
121 | redis (4.1.3)
122 | sidekiq (5.2.7)
123 | connection_pool (~> 2.2, >= 2.2.2)
124 | rack (>= 1.5.0)
125 | rack-protection (>= 1.5.0)
126 | redis (>= 3.3.5, < 5)
127 | sprockets (4.0.0)
128 | concurrent-ruby (~> 1.0)
129 | rack (> 1, < 3)
130 | sprockets-rails (3.2.1)
131 | actionpack (>= 4.0)
132 | activesupport (>= 4.0)
133 | sprockets (>= 3.0.0)
134 | thor (0.20.3)
135 | thread_safe (0.3.6)
136 | tzinfo (1.2.5)
137 | thread_safe (~> 0.1)
138 | websocket-driver (0.7.1)
139 | websocket-extensions (>= 0.1.0)
140 | websocket-extensions (0.1.4)
141 | zeitwerk (2.2.0)
142 |
143 | PLATFORMS
144 | ruby
145 |
146 | DEPENDENCIES
147 | bundler (~> 1.17)
148 | minitest (~> 5.0)
149 | rails (~> 6.0.0)
150 | rake (~> 10.0)
151 | sidekiq-crypt!
152 |
153 | BUNDLED WITH
154 | 1.17.3
155 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | sidekiq-crypt (0.1.1)
5 | sidekiq (~> 5.0)
6 |
7 | GEM
8 | remote: https://rubygems.org/
9 | specs:
10 | actioncable (5.2.3)
11 | actionpack (= 5.2.3)
12 | nio4r (~> 2.0)
13 | websocket-driver (>= 0.6.1)
14 | actionmailer (5.2.3)
15 | actionpack (= 5.2.3)
16 | actionview (= 5.2.3)
17 | activejob (= 5.2.3)
18 | mail (~> 2.5, >= 2.5.4)
19 | rails-dom-testing (~> 2.0)
20 | actionpack (5.2.3)
21 | actionview (= 5.2.3)
22 | activesupport (= 5.2.3)
23 | rack (~> 2.0)
24 | rack-test (>= 0.6.3)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
27 | actionview (5.2.3)
28 | activesupport (= 5.2.3)
29 | builder (~> 3.1)
30 | erubi (~> 1.4)
31 | rails-dom-testing (~> 2.0)
32 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
33 | activejob (5.2.3)
34 | activesupport (= 5.2.3)
35 | globalid (>= 0.3.6)
36 | activemodel (5.2.3)
37 | activesupport (= 5.2.3)
38 | activerecord (5.2.3)
39 | activemodel (= 5.2.3)
40 | activesupport (= 5.2.3)
41 | arel (>= 9.0)
42 | activestorage (5.2.3)
43 | actionpack (= 5.2.3)
44 | activerecord (= 5.2.3)
45 | marcel (~> 0.3.1)
46 | activesupport (5.2.3)
47 | concurrent-ruby (~> 1.0, >= 1.0.2)
48 | i18n (>= 0.7, < 2)
49 | minitest (~> 5.1)
50 | tzinfo (~> 1.1)
51 | arel (9.0.0)
52 | ast (2.4.0)
53 | builder (3.2.3)
54 | concurrent-ruby (1.1.5)
55 | connection_pool (2.2.2)
56 | crass (1.0.5)
57 | erubi (1.9.0)
58 | globalid (0.4.2)
59 | activesupport (>= 4.2.0)
60 | i18n (1.7.0)
61 | concurrent-ruby (~> 1.0)
62 | jaro_winkler (1.5.4)
63 | loofah (2.3.1)
64 | crass (~> 1.0.2)
65 | nokogiri (>= 1.5.9)
66 | mail (2.7.1)
67 | mini_mime (>= 0.1.1)
68 | marcel (0.3.3)
69 | mimemagic (~> 0.3.2)
70 | method_source (0.9.2)
71 | mimemagic (0.3.3)
72 | mini_mime (1.0.2)
73 | mini_portile2 (2.4.0)
74 | minitest (5.12.2)
75 | nio4r (2.5.2)
76 | nokogiri (1.10.9)
77 | mini_portile2 (~> 2.4.0)
78 | parallel (1.19.1)
79 | parser (2.7.1.0)
80 | ast (~> 2.4.0)
81 | rack (2.0.7)
82 | rack-protection (2.0.7)
83 | rack
84 | rack-test (1.1.0)
85 | rack (>= 1.0, < 3)
86 | rails (5.2.3)
87 | actioncable (= 5.2.3)
88 | actionmailer (= 5.2.3)
89 | actionpack (= 5.2.3)
90 | actionview (= 5.2.3)
91 | activejob (= 5.2.3)
92 | activemodel (= 5.2.3)
93 | activerecord (= 5.2.3)
94 | activestorage (= 5.2.3)
95 | activesupport (= 5.2.3)
96 | bundler (>= 1.3.0)
97 | railties (= 5.2.3)
98 | sprockets-rails (>= 2.0.0)
99 | rails-dom-testing (2.0.3)
100 | activesupport (>= 4.2.0)
101 | nokogiri (>= 1.6)
102 | rails-html-sanitizer (1.3.0)
103 | loofah (~> 2.3)
104 | railties (5.2.3)
105 | actionpack (= 5.2.3)
106 | activesupport (= 5.2.3)
107 | method_source
108 | rake (>= 0.8.7)
109 | thor (>= 0.19.0, < 2.0)
110 | rainbow (3.0.0)
111 | rake (13.0.1)
112 | redis (4.1.3)
113 | rexml (3.2.4)
114 | rubocop (0.81.0)
115 | jaro_winkler (~> 1.5.1)
116 | parallel (~> 1.10)
117 | parser (>= 2.7.0.1)
118 | rainbow (>= 2.2.2, < 4.0)
119 | rexml
120 | ruby-progressbar (~> 1.7)
121 | unicode-display_width (>= 1.4.0, < 2.0)
122 | ruby-progressbar (1.10.1)
123 | sidekiq (5.2.7)
124 | connection_pool (~> 2.2, >= 2.2.2)
125 | rack (>= 1.5.0)
126 | rack-protection (>= 1.5.0)
127 | redis (>= 3.3.5, < 5)
128 | sprockets (4.0.0)
129 | concurrent-ruby (~> 1.0)
130 | rack (> 1, < 3)
131 | sprockets-rails (3.2.1)
132 | actionpack (>= 4.0)
133 | activesupport (>= 4.0)
134 | sprockets (>= 3.0.0)
135 | sqlite3 (1.3.13)
136 | thor (0.20.3)
137 | thread_safe (0.3.6)
138 | tzinfo (1.2.5)
139 | thread_safe (~> 0.1)
140 | unicode-display_width (1.7.0)
141 | websocket-driver (0.7.1)
142 | websocket-extensions (>= 0.1.0)
143 | websocket-extensions (0.1.4)
144 |
145 | PLATFORMS
146 | ruby
147 |
148 | DEPENDENCIES
149 | bundler (~> 1.17)
150 | minitest (~> 5.0)
151 | rails (~> 5.2)
152 | rake (~> 13.0)
153 | rubocop
154 | sidekiq-crypt!
155 | sqlite3 (~> 1.3.6)
156 |
157 | BUNDLED WITH
158 | 1.17.3
159 |
--------------------------------------------------------------------------------
/test/dummy-app/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actioncable (5.0.7.2)
5 | actionpack (= 5.0.7.2)
6 | nio4r (>= 1.2, < 3.0)
7 | websocket-driver (~> 0.6.1)
8 | actionmailer (5.0.7.2)
9 | actionpack (= 5.0.7.2)
10 | actionview (= 5.0.7.2)
11 | activejob (= 5.0.7.2)
12 | mail (~> 2.5, >= 2.5.4)
13 | rails-dom-testing (~> 2.0)
14 | actionpack (5.0.7.2)
15 | actionview (= 5.0.7.2)
16 | activesupport (= 5.0.7.2)
17 | rack (~> 2.0)
18 | rack-test (~> 0.6.3)
19 | rails-dom-testing (~> 2.0)
20 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
21 | actionview (5.0.7.2)
22 | activesupport (= 5.0.7.2)
23 | builder (~> 3.1)
24 | erubis (~> 2.7.0)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
27 | activejob (5.0.7.2)
28 | activesupport (= 5.0.7.2)
29 | globalid (>= 0.3.6)
30 | activemodel (5.0.7.2)
31 | activesupport (= 5.0.7.2)
32 | activerecord (5.0.7.2)
33 | activemodel (= 5.0.7.2)
34 | activesupport (= 5.0.7.2)
35 | arel (~> 7.0)
36 | activesupport (5.0.7.2)
37 | concurrent-ruby (~> 1.0, >= 1.0.2)
38 | i18n (>= 0.7, < 2)
39 | minitest (~> 5.1)
40 | tzinfo (~> 1.1)
41 | arel (7.1.4)
42 | bindex (0.8.1)
43 | builder (3.2.3)
44 | byebug (11.0.1)
45 | coffee-rails (4.2.2)
46 | coffee-script (>= 2.2.0)
47 | railties (>= 4.0.0)
48 | coffee-script (2.4.1)
49 | coffee-script-source
50 | execjs
51 | coffee-script-source (1.12.2)
52 | concurrent-ruby (1.1.5)
53 | crass (1.0.5)
54 | erubis (2.7.0)
55 | execjs (2.7.0)
56 | ffi (1.11.1)
57 | globalid (0.4.2)
58 | activesupport (>= 4.2.0)
59 | i18n (1.7.0)
60 | concurrent-ruby (~> 1.0)
61 | jbuilder (2.9.1)
62 | activesupport (>= 4.2.0)
63 | jquery-rails (4.3.5)
64 | rails-dom-testing (>= 1, < 3)
65 | railties (>= 4.2.0)
66 | thor (>= 0.14, < 2.0)
67 | listen (3.0.8)
68 | rb-fsevent (~> 0.9, >= 0.9.4)
69 | rb-inotify (~> 0.9, >= 0.9.7)
70 | loofah (2.3.1)
71 | crass (~> 1.0.2)
72 | nokogiri (>= 1.5.9)
73 | mail (2.7.1)
74 | mini_mime (>= 0.1.1)
75 | method_source (0.9.2)
76 | mini_mime (1.0.2)
77 | mini_portile2 (2.4.0)
78 | minitest (5.12.2)
79 | nio4r (2.5.2)
80 | nokogiri (1.10.5)
81 | mini_portile2 (~> 2.4.0)
82 | puma (3.12.4)
83 | rack (2.2.2)
84 | rack-test (0.6.3)
85 | rack (>= 1.0)
86 | rails (5.0.7.2)
87 | actioncable (= 5.0.7.2)
88 | actionmailer (= 5.0.7.2)
89 | actionpack (= 5.0.7.2)
90 | actionview (= 5.0.7.2)
91 | activejob (= 5.0.7.2)
92 | activemodel (= 5.0.7.2)
93 | activerecord (= 5.0.7.2)
94 | activesupport (= 5.0.7.2)
95 | bundler (>= 1.3.0)
96 | railties (= 5.0.7.2)
97 | sprockets-rails (>= 2.0.0)
98 | rails-dom-testing (2.0.3)
99 | activesupport (>= 4.2.0)
100 | nokogiri (>= 1.6)
101 | rails-html-sanitizer (1.3.0)
102 | loofah (~> 2.3)
103 | railties (5.0.7.2)
104 | actionpack (= 5.0.7.2)
105 | activesupport (= 5.0.7.2)
106 | method_source
107 | rake (>= 0.8.7)
108 | thor (>= 0.18.1, < 2.0)
109 | rake (13.0.0)
110 | rb-fsevent (0.10.3)
111 | rb-inotify (0.10.0)
112 | ffi (~> 1.0)
113 | sass (3.7.4)
114 | sass-listen (~> 4.0.0)
115 | sass-listen (4.0.0)
116 | rb-fsevent (~> 0.9, >= 0.9.4)
117 | rb-inotify (~> 0.9, >= 0.9.7)
118 | sass-rails (5.0.7)
119 | railties (>= 4.0.0, < 6)
120 | sass (~> 3.1)
121 | sprockets (>= 2.8, < 4.0)
122 | sprockets-rails (>= 2.0, < 4.0)
123 | tilt (>= 1.1, < 3)
124 | spring (2.1.0)
125 | spring-watcher-listen (2.0.1)
126 | listen (>= 2.7, < 4.0)
127 | spring (>= 1.2, < 3.0)
128 | sprockets (3.7.2)
129 | concurrent-ruby (~> 1.0)
130 | rack (> 1, < 3)
131 | sprockets-rails (3.2.1)
132 | actionpack (>= 4.0)
133 | activesupport (>= 4.0)
134 | sprockets (>= 3.0.0)
135 | sqlite3 (1.4.1)
136 | thor (0.20.3)
137 | thread_safe (0.3.6)
138 | tilt (2.0.10)
139 | turbolinks (5.2.1)
140 | turbolinks-source (~> 5.2)
141 | turbolinks-source (5.2.0)
142 | tzinfo (1.2.5)
143 | thread_safe (~> 0.1)
144 | uglifier (4.2.0)
145 | execjs (>= 0.3.0, < 3)
146 | web-console (3.7.0)
147 | actionview (>= 5.0)
148 | activemodel (>= 5.0)
149 | bindex (>= 0.4.0)
150 | railties (>= 5.0)
151 | websocket-driver (0.6.5)
152 | websocket-extensions (>= 0.1.0)
153 | websocket-extensions (0.1.4)
154 |
155 | PLATFORMS
156 | ruby
157 |
158 | DEPENDENCIES
159 | byebug
160 | coffee-rails (~> 4.2)
161 | jbuilder (~> 2.5)
162 | jquery-rails
163 | listen (~> 3.0.5)
164 | puma (~> 3.12)
165 | rails (~> 5.0.0)
166 | sass-rails (~> 5.0)
167 | spring
168 | spring-watcher-listen (~> 2.0.0)
169 | sqlite3
170 | turbolinks (~> 5)
171 | tzinfo-data
172 | uglifier (>= 1.3.0)
173 | web-console
174 |
175 | BUNDLED WITH
176 | 1.17.3
177 |
--------------------------------------------------------------------------------
/test/dummy-app/log/development.log:
--------------------------------------------------------------------------------
1 | Started GET "/" for ::1 at 2019-10-14 22:43:01 +0300
2 |
3 | Gem::LoadError (Specified 'sqlite3' for database adapter, but the gem is not loaded. Add `gem 'sqlite3'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord).):
4 |
5 | activerecord (5.0.7.2) lib/active_record/connection_adapters/connection_specification.rb:176:in `rescue in spec'
6 | activerecord (5.0.7.2) lib/active_record/connection_adapters/connection_specification.rb:173:in `spec'
7 | activerecord (5.0.7.2) lib/active_record/connection_handling.rb:53:in `establish_connection'
8 | activerecord (5.0.7.2) lib/active_record/railtie.rb:125:in `block (2 levels) in '
9 | activesupport (5.0.7.2) lib/active_support/lazy_load_hooks.rb:69:in `instance_eval'
10 | activesupport (5.0.7.2) lib/active_support/lazy_load_hooks.rb:69:in `block in execute_hook'
11 | activesupport (5.0.7.2) lib/active_support/lazy_load_hooks.rb:60:in `with_execution_control'
12 | activesupport (5.0.7.2) lib/active_support/lazy_load_hooks.rb:65:in `execute_hook'
13 | activesupport (5.0.7.2) lib/active_support/lazy_load_hooks.rb:50:in `block in run_load_hooks'
14 | activesupport (5.0.7.2) lib/active_support/lazy_load_hooks.rb:49:in `each'
15 | activesupport (5.0.7.2) lib/active_support/lazy_load_hooks.rb:49:in `run_load_hooks'
16 | activerecord (5.0.7.2) lib/active_record/base.rb:324:in `'
17 | activerecord (5.0.7.2) lib/active_record/base.rb:24:in `'
18 | activesupport (5.0.7.2) lib/active_support/dependencies.rb:293:in `require'
19 | activesupport (5.0.7.2) lib/active_support/dependencies.rb:293:in `block in require'
20 | activesupport (5.0.7.2) lib/active_support/dependencies.rb:259:in `load_dependency'
21 | activesupport (5.0.7.2) lib/active_support/dependencies.rb:293:in `require'
22 | activerecord (5.0.7.2) lib/active_record/migration.rb:559:in `connection'
23 | activerecord (5.0.7.2) lib/active_record/migration.rb:546:in `call'
24 | actionpack (5.0.7.2) lib/action_dispatch/middleware/callbacks.rb:38:in `block in call'
25 | activesupport (5.0.7.2) lib/active_support/callbacks.rb:97:in `__run_callbacks__'
26 | activesupport (5.0.7.2) lib/active_support/callbacks.rb:750:in `_run_call_callbacks'
27 | activesupport (5.0.7.2) lib/active_support/callbacks.rb:90:in `run_callbacks'
28 | actionpack (5.0.7.2) lib/action_dispatch/middleware/callbacks.rb:36:in `call'
29 | actionpack (5.0.7.2) lib/action_dispatch/middleware/executor.rb:12:in `call'
30 | actionpack (5.0.7.2) lib/action_dispatch/middleware/remote_ip.rb:79:in `call'
31 | actionpack (5.0.7.2) lib/action_dispatch/middleware/debug_exceptions.rb:49:in `call'
32 | web-console (3.7.0) lib/web_console/middleware.rb:135:in `call_app'
33 | web-console (3.7.0) lib/web_console/middleware.rb:30:in `block in call'
34 | web-console (3.7.0) lib/web_console/middleware.rb:20:in `catch'
35 | web-console (3.7.0) lib/web_console/middleware.rb:20:in `call'
36 | actionpack (5.0.7.2) lib/action_dispatch/middleware/show_exceptions.rb:31:in `call'
37 | railties (5.0.7.2) lib/rails/rack/logger.rb:36:in `call_app'
38 | railties (5.0.7.2) lib/rails/rack/logger.rb:24:in `block in call'
39 | activesupport (5.0.7.2) lib/active_support/tagged_logging.rb:69:in `block in tagged'
40 | activesupport (5.0.7.2) lib/active_support/tagged_logging.rb:26:in `tagged'
41 | activesupport (5.0.7.2) lib/active_support/tagged_logging.rb:69:in `tagged'
42 | railties (5.0.7.2) lib/rails/rack/logger.rb:24:in `call'
43 | sprockets-rails (3.2.1) lib/sprockets/rails/quiet_assets.rb:13:in `call'
44 | actionpack (5.0.7.2) lib/action_dispatch/middleware/request_id.rb:24:in `call'
45 | rack (2.0.7) lib/rack/method_override.rb:22:in `call'
46 | rack (2.0.7) lib/rack/runtime.rb:22:in `call'
47 | activesupport (5.0.7.2) lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
48 | actionpack (5.0.7.2) lib/action_dispatch/middleware/executor.rb:12:in `call'
49 | actionpack (5.0.7.2) lib/action_dispatch/middleware/static.rb:136:in `call'
50 | rack (2.0.7) lib/rack/sendfile.rb:111:in `call'
51 | railties (5.0.7.2) lib/rails/engine.rb:522:in `call'
52 | puma (3.12.1) lib/puma/configuration.rb:227:in `call'
53 | puma (3.12.1) lib/puma/server.rb:660:in `handle_request'
54 | puma (3.12.1) lib/puma/server.rb:474:in `process_client'
55 | puma (3.12.1) lib/puma/server.rb:334:in `block in run'
56 | puma (3.12.1) lib/puma/thread_pool.rb:135:in `block in spawn_thread'
57 | Rendering /Users/mtoygar/.rvm/gems/ruby-2.6.3/gems/actionpack-5.0.7.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout
58 | Rendering /Users/mtoygar/.rvm/gems/ruby-2.6.3/gems/actionpack-5.0.7.2/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
59 | Rendered /Users/mtoygar/.rvm/gems/ruby-2.6.3/gems/actionpack-5.0.7.2/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (1.5ms)
60 | Rendering /Users/mtoygar/.rvm/gems/ruby-2.6.3/gems/actionpack-5.0.7.2/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
61 | Rendered /Users/mtoygar/.rvm/gems/ruby-2.6.3/gems/actionpack-5.0.7.2/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (1.2ms)
62 | Rendering /Users/mtoygar/.rvm/gems/ruby-2.6.3/gems/actionpack-5.0.7.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
63 | Rendered /Users/mtoygar/.rvm/gems/ruby-2.6.3/gems/actionpack-5.0.7.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (0.6ms)
64 | Rendered /Users/mtoygar/.rvm/gems/ruby-2.6.3/gems/actionpack-5.0.7.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (28.4ms)
65 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sidekiq::Crypt
2 |
3 | sidekiq-crypt enables you to encryt your secret keys on redis. It is an alternative to [sidekiq's enterprise encryption](https://github.com/mperham/sidekiq/wiki/Ent-Encryption) feature. If you or your project has enough resources, you should prefer that option.
4 |
5 | After sidekiq-crypt parameters of your secret worker would look like below.
6 |
7 | > 79, {"credit_card_number"=>"agBCqI8vlvn4mx0L8vkbrJr1nstV459w4d6hVNqZC1A=\n", "name_on_credit_card"=>"h6fdq3kbXNXhfx/iKIy5fA==\n", "cvc"=>"wEAB4pCISRUvWVXtDPaOKA==\n", "expiration"=>"cgOI/Ks7BfldTlB+6F23LQ==\n", "installments"=>1}
8 |
9 | ## Installation
10 |
11 | Add this line to your application's Gemfile:
12 |
13 | ```ruby
14 | gem 'sidekiq-crypt'
15 | ```
16 |
17 | And then execute:
18 |
19 | $ bundle
20 |
21 | Or install it yourself as:
22 |
23 | $ gem install sidekiq-crypt
24 |
25 | ## Compatibility
26 |
27 | Tested with
28 |
29 | - Ruby 2.2.10+
30 |
31 | - Rails 4.2+
32 |
33 | - Sidekiq 5.2.7
34 |
35 | ## Usage
36 |
37 | You should add below block in an initializer. Since you can bump your encryption keys, the current key version is required. The key store hash enables sidekiq crypt to access encryption keys by version.
38 |
39 | ```ruby
40 | Sidekiq::Crypt.configure(current_key_version: current_key_version, key_store: key_store)
41 | ```
42 |
43 | Alternatively you can use below style too.
44 | ```ruby
45 | Sidekiq::Crypt.configure do |config|
46 | config.current_key_version = options[:current_key_version]
47 | config.key_store = options[:key_store]
48 | end
49 | ```
50 |
51 | For example, if you set current_key_version as `'V2'` and key_store as `{ V1: 'a_key', V2: 'yet_another_key' }`, sidekiq-crypt will use `'yet_another_key'` to encrypted new jobs. However, if you have jobs encrypted with `V1` key version in redis sidekiq-crypt will decrypt them by using `'a_key'`. When you make sure that you no longer have any job encryted with first key version, you can safely remove `V1: 'a_key'` from the key_store hash.
52 |
53 | Additionally, to use sidekiq crypt in a worker you must include `Sidekiq::Crypt::Worker` module to your worker class.
54 |
55 | ```ruby
56 | class SecretWorker
57 | include Sidekiq::Worker
58 | include Sidekiq::Crypt::Worker
59 | ...
60 | ```
61 |
62 | sidekiq-crypt automatically traverse all parameters sent to sidekiq to find a hash key that are configured to be encrypted. There are 2 ways to configure encrpyted hash.
63 |
64 | #### 1. Explicit way (Recommended)
65 | ```ruby
66 | class SecretWorker
67 | include Sidekiq::Worker
68 | include Sidekiq::Crypt::Worker
69 |
70 | # explicitly state which keys are gonna be encrypted
71 | encrypted_keys :credit_card_number, :cvc, /^secret.*/
72 | ```
73 | As stated, sidekiq-crypt automatically traverse all parameters. For example in below case it will find and encrypt `credit_card_number`, `cvc` and `secret_key`.
74 | `SecretWorker.perform_async([1, credit_card_number: '1234567812345678'], { cvc: 123, secret_key: 'CONFIDENTIAL' })`
75 |
76 | Note: This method overrides filters stated on initialization. (see below)
77 |
78 | #### 2. State filters in initialization
79 |
80 | ```ruby
81 | Sidekiq::Crypt.configure do |config|
82 | config.current_key_version = options[:current_key_version]
83 | config.key_store = options[:key_store]
84 | config.filters << [:credit_card_number, :cvc, /^secret.*/]
85 | end
86 | ```
87 |
88 | By default Sidekiq::Crypt initialize config.filters with `Rails.application.config.filter_parameters`. You can add additional filters to it by stating a filter array like above.
89 |
90 |
91 | With the above config `credit_card_number`, `password` and `secret_key` will be encrypted, assuming `password` is included in rails filter params.
92 | `credit_card_number`, `cvc` and `secret_key`.
93 | `SecretWorker.perform_async([1, credit_card_number: '1234567812345678'], { password: 123, secret_key: 'CONFIDENTIAL' })`
94 |
95 | To disable rails filter params inclusion, you must call configure method with `exclude_rails_filters` parameter.
96 |
97 | ```ruby
98 | Sidekiq::Crypt.configure(exclude_rails_filters: true)
99 | ```
100 |
101 | ## Key Generation
102 |
103 | You should generate a key compatible with OpenSSL's `aes-256-cbc` and Base64 encode it(specifically with Base64.strict_encode64). Below snippet can guide you for this purpose.
104 |
105 | ```ruby
106 | # generate a key and Base64 encode it in the strict mode
107 | # most easy and robust way to generate a key is the random_key method.
108 | encoded_random_key = Base64.strict_encode64(OpenSSL::Cipher::AES.new(256, :CBC).random_key)
109 |
110 | # store encoded_random_key string in an environment variable, or somewhere else provided that it is safe.
111 | # in an initializer initialize sidekiq-crypt with this variable.
112 | # sidekiq-crypt will decode encoded_random_key and use it for encrytion and decrytion purposes.
113 | # below ENV['SIDEKIQ_CRYPT_KEY_V1'] should return encoded_random_key
114 | Sidekiq::Crypt.configure(current_key_version: 'V1', key_store: { V1: ENV['SIDEKIQ_CRYPT_KEY_V1'] })
115 |
116 | # you should bump current version up when you want to change your secret.
117 | # It is important to keep V1 version in the key store
118 | # until you are certain that no job is stored in redis encrypted with V1 version
119 | Sidekiq::Crypt.configure(current_key_version: 'V2', key_store: { V1: ENV['SIDEKIQ_CRYPT_KEY_V1'], V2: ENV['SIDEKIQ_CRYPT_KEY_V2'] })
120 | ```
121 |
122 | ## Notes
123 |
124 | - sidekiq-crypt is a [sidekiq middleware](https://github.com/mperham/sidekiq/wiki/Middleware). You should be careful about middleware ordering. Start sidekiq in verbose mode to see where sidekiq-crypt is in the middleware chain.
125 |
126 | ```ruby
127 | bundle exec sidekiq -v
128 | ```
129 |
130 | - sidekiq-cypyt uses OpenSSL's aes-256-cbc Cipher encryption.
131 |
132 | ## Caveats
133 |
134 | Right now, only string attributes are accepted. If you try to encrypted an integer string version will be return to the worker. For example if you send `secret_key: 0` to your worker, sidekiq-crypt will encrypt it on redis, but return `secret_key: '0'` to the worker.
135 |
136 | ## Development
137 |
138 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
139 |
140 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
141 |
142 | ## Contributing
143 |
144 | Bug reports and pull requests are welcome on GitHub at https://github.com/mtoygar/sidekiq-crypt. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
145 |
146 | ## License
147 |
148 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
149 |
150 | ## Code of Conduct
151 |
152 | Everyone interacting in the Sidekiq::Crypt project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/mtoygar/sidekiq-crypt/blob/master/CODE_OF_CONDUCT.md).
153 |
154 |
--------------------------------------------------------------------------------