├── 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 | --------------------------------------------------------------------------------