sidekiq-encrypted_args 1.2.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcde4f8a47a8b5b7e5a4c0d6fe8f351a5d62ec8b9390caac4697359b7efe0912
4
- data.tar.gz: 135e9fd5a910bf450812fa8406b25579857bcd320c93ea6b6660ce4242376650
3
+ metadata.gz: aed8d03eeb0b589556b436823f85c13331ab6c1703d732d425321b4cc1b2bc71
4
+ data.tar.gz: 2f2a42257c669731f97ee25cfc52da48a4cbdbe18b59e0fd84bcebbe50820e89
5
5
  SHA512:
6
- metadata.gz: 1cb7e169a1dd94f2c081e75147a467984d1e9d0457ca0337b44a8b6cfd4db5e6217a1d41261f52f7a81ed1f9dbcfa837a7ef1d9a4498d694472e4df43289f321
7
- data.tar.gz: e238bdd98f374db76e6363b46abd0b8ea2269723d780e30e8150fbb9aca958410fb759f12148abae4ffd418323fa58005b6229ed063db1bbbe02d59cd916e9e0
6
+ metadata.gz: cbdd4e2d8144eddf2e3699ac30ab52114de6ab8e09b97e75909e2e67defc81f5c4cd71e43efb22c404ac4890213abd6c300ee45117ae7e1428cf5b2b1794595c
7
+ data.tar.gz: 6644bca285087c5de5b2c68aba368f4192b2061f3f5f6095125d8ca7e16888b40214e2ce7b9a3c2a2ec39b808be6ccadefc7a1c7777c0bd3edecf1ef935f7a18
data/CHANGE_LOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 2.0.0
8
+
9
+ ### Changed
10
+
11
+ - A secret key is now required to be set. Previously the code would silently fail if no key was set. This change improves security by protecting against misconfiguration leaking data into Redis.
12
+ - Bumped minimum required Ruby version to 2.7 and Sidekiq to 6.3.
13
+
7
14
  ## 1.2.0
8
15
 
9
16
  ### Removed
data/README.md CHANGED
@@ -22,7 +22,7 @@ To use the gem, you will need to specify a secret that will be used to encrypt t
22
22
  Sidekiq::EncryptedArgs.configure!(secret: "YourSecretKey")
23
23
  ```
24
24
 
25
- If the secret is not set, the value of the `SIDEKIQ_ENCRYPTED_ARGS_SECRET` environment variable will be used as the secret. If this variable is not set, job arguments will not be encrypted.
25
+ If the secret is not set explicitly, the value of the `SIDEKIQ_ENCRYPTED_ARGS_SECRET` environment variable will be used as the secret. If you call `configure!` without setting a secret (either explicitly or via the environment variable), an error will be raised.
26
26
 
27
27
  The call to `Sidekiq::EncryptedArgs.configure!` will **prepend** the client encryption middleware and **append** server decryption middleware. By doing this, any other middleware you register will only receive the encrypted parameters (e.g. logging middleware will receive the encrypted parameters).
28
28
 
@@ -42,7 +42,8 @@ Sidekiq.configure_server do |config|
42
42
  chain.add Sidekiq::EncryptedArgs::ServerMiddleware
43
43
  end
44
44
 
45
- # register client middleware on the server so that starting jobs in a Sidekiq::Worker also get encrypted args
45
+ # Register client middleware on the server so that starting jobs from within
46
+ # another also get encrypted args.
46
47
  # https://github.com/mperham/sidekiq/wiki/Middleware#client-middleware-registered-in-both-places
47
48
  config.client_middleware do |chain|
48
49
  chain.prepend Sidekiq::EncryptedArgs::ClientMiddleware
@@ -58,7 +59,7 @@ Setting the option to `true` will encrypt all the arguments passed to the `perfo
58
59
 
59
60
  ```ruby
60
61
  class SecretWorker
61
- include Sidekiq::Worker
62
+ include Sidekiq::Job
62
63
 
63
64
  sidekiq_options encrypted_args: true
64
65
 
@@ -67,20 +68,24 @@ class SecretWorker
67
68
  end
68
69
  ```
69
70
 
70
- You can also choose to only encrypt specific arguments with an array of either argument names (symbols or strings) or indexes. This is useful to preserve visibility into non-sensitive arguments for troubleshooting or other reasons. Both of these examples encrypt just the second argument to the `perform` method.
71
+ You can also choose to only encrypt specific arguments by specifying either argument names (as symbols or strings) or argument positions (as integers). This is useful to preserve visibility into non-sensitive arguments for troubleshooting or other reasons. All of these examples encrypt just the second argument to the `perform` method.
71
72
 
72
73
  ```ruby
73
- # Pass in a list of argument names that should be encrypted
74
+ # Pass in a single argument name
75
+ sidekiq_options encrypted_args: :arg_2
76
+ # or as a string
77
+ sidekiq_options encrypted_args: "arg_2"
78
+ # or as an array
74
79
  sidekiq_options encrypted_args: [:arg_2]
75
- # or
76
- sidekiq_options encrypted_args: ["arg_2"]
77
80
 
78
81
  def perform(arg_1, arg_2, arg_3)
79
82
  end
80
83
  ```
81
84
 
82
85
  ```ruby
83
- # Pass in an array of integers indicating which argument positions should be encrypted
86
+ # Pass in an integer indicating which argument position should be encrypted (0-indexed)
87
+ sidekiq_options encrypted_args: 1
88
+ # or as an array
84
89
  sidekiq_options encrypted_args: [1]
85
90
 
86
91
  def perform(arg_1, arg_2, arg_3)
@@ -99,7 +104,7 @@ Sidekiq::EncryptedArgs.secret = ["CurrentSecret", "OldSecret", "EvenOlderSecret"
99
104
 
100
105
  The first (left most) key will be considered the current key, and is used for encrypting arguments. When decrypting, we iterate over the secrets list until we find the correct one. This allows you to switch you secret keys without breaking jobs already enqueued in Redis.
101
106
 
102
- If you are using the `SIDEKIQ_ENCRYPTED_ARGS_SECRET` environment variable to specify your secret, you can delimit multiple keys with a spaces.
107
+ If you are using the `SIDEKIQ_ENCRYPTED_ARGS_SECRET` environment variable to specify your secret, you can separate multiple keys with whitespace (spaces or tabs).
103
108
 
104
109
  You can also safely add encryption to an existing worker. Any jobs that are already enqueued will still run even without having the arguments encrypted in Redis.
105
110
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.0
1
+ 2.0.0
@@ -10,7 +10,16 @@ module Sidekiq
10
10
  #
11
11
  # @see ServerMiddleware
12
12
  class ClientMiddleware
13
- # Encrypt specified arguments before they're sent off to the queue
13
+ include Sidekiq::ClientMiddleware if defined?(Sidekiq::ClientMiddleware)
14
+
15
+ # Encrypt specified arguments before they're sent off to the queue.
16
+ #
17
+ # @param [String, Class] worker_class The worker class or class name
18
+ # @param [Hash] job The Sidekiq job hash containing arguments and metadata
19
+ # @param [String] queue The name of the queue
20
+ # @param [Object, nil] redis_pool Optional Redis connection pool (legacy parameter)
21
+ # @return [void]
22
+ # @yield Passes control to the next middleware in the chain
14
23
  def call(worker_class, job, queue, redis_pool = nil)
15
24
  if job.include?("encrypted_args")
16
25
  encrypted_args = EncryptedArgs.encrypted_args_option(worker_class, job)
@@ -10,11 +10,20 @@ module Sidekiq
10
10
  #
11
11
  # @see ClientMiddleware
12
12
  class ServerMiddleware
13
- # Wrap the server process to decrypt incoming arguments
13
+ include Sidekiq::ServerMiddleware if defined?(Sidekiq::ServerMiddleware)
14
+
15
+ # Wrap the server process to decrypt incoming arguments.
16
+ #
17
+ # @param [Object] worker The worker instance that will process the job
18
+ # @param [Hash] job The Sidekiq job hash containing arguments and metadata
19
+ # @param [String] queue The name of the queue
20
+ # @return [void]
21
+ # @yield Passes control to the worker's perform method
14
22
  def call(worker, job, queue)
15
23
  encrypted_args = job["encrypted_args"]
24
+
16
25
  if encrypted_args
17
- encrypted_args = backward_compatible_encrypted_args(encrypted_args, worker.class, job)
26
+ encrypted_args = EncryptedArgs.encrypted_args_option(worker.class, job)
18
27
  job_args = job["args"]
19
28
  encrypted_args.each do |position|
20
29
  value = job_args[position]
@@ -24,17 +33,6 @@ module Sidekiq
24
33
 
25
34
  yield
26
35
  end
27
-
28
- private
29
-
30
- # Ensure that the encrypted args is an array of integers. If not re-read it from the class
31
- # definition since gem version 1.0.2 and earlier did not update the encrypted_args on the job.
32
- def backward_compatible_encrypted_args(encrypted_args, worker_class, job)
33
- unless encrypted_args.is_a?(Array) && encrypted_args.all? { |position| position.is_a?(Integer) }
34
- encrypted_args = EncryptedArgs.encrypted_args_option(worker_class, job)
35
- end
36
- encrypted_args
37
- end
38
36
  end
39
37
  end
40
38
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Sidekiq
4
4
  module EncryptedArgs
5
+ # The current version of the sidekiq-encrypted_args gem.
5
6
  VERSION = File.read(File.join(__dir__, "..", "..", "..", "VERSION")).chomp.freeze
6
7
  end
7
8
  end
@@ -11,11 +11,10 @@ module Sidekiq
11
11
  # in Redis to protect sensitive information like API keys, passwords, or
12
12
  # personally identifiable information.
13
13
  module EncryptedArgs
14
+ @encryptors = nil
15
+
14
16
  # Error thrown when the secret is invalid
15
17
  class InvalidSecretError < StandardError
16
- def initialize
17
- super("Cannot decrypt. Invalid secret provided.")
18
- end
19
18
  end
20
19
 
21
20
  class << self
@@ -39,7 +38,7 @@ module Sidekiq
39
38
  # @param [String, Array<String>] value One or more secrets to use for encrypting arguments.
40
39
  # @return [void]
41
40
  def secret=(value)
42
- @encryptors = make_encryptors(value)
41
+ @encryptors = make_encryptors(value).freeze
43
42
  end
44
43
 
45
44
  # Add the client and server middleware to the default Sidekiq
@@ -58,6 +57,7 @@ module Sidekiq
58
57
  # @param [String] secret optionally set the secret here. See {.secret=}
59
58
  def configure!(secret: nil)
60
59
  self.secret = secret unless secret.nil?
60
+ encryptors # Calling encryptors will validate that a secret is set.
61
61
 
62
62
  Sidekiq.configure_client do |config|
63
63
  config.client_middleware do |chain|
@@ -127,7 +127,10 @@ module Sidekiq
127
127
  # can be `true` or an array indicating if each positional argument should be encrypted, or a hash
128
128
  # with keys for the argument position and true as the value.
129
129
  #
130
- # @private
130
+ # @param [String, Class] worker_class The worker class or class name
131
+ # @param [Hash] job The Sidekiq job hash containing arguments and metadata
132
+ # @return [Array<Integer>, nil] Array of argument positions to encrypt, or nil if encryption is not configured
133
+ # @api private
131
134
  def encrypted_args_option(worker_class, job)
132
135
  option = job["encrypted_args"]
133
136
  return nil if option.nil?
@@ -154,7 +157,7 @@ module Sidekiq
154
157
  raise ArgumentError.new("Encrypted args must be specified as integers or symbols.")
155
158
  end
156
159
 
157
- if array_type && current_type
160
+ if array_type && current_type != array_type
158
161
  raise ArgumentError.new("Encrypted args cannot mix integers and symbols.")
159
162
  else
160
163
  array_type ||= current_type
@@ -173,34 +176,47 @@ module Sidekiq
173
176
  def encrypt_string(value)
174
177
  encryptor = encryptors.first
175
178
  return value if encryptor.nil?
179
+
176
180
  encryptor.encrypt(value)
177
181
  end
178
182
 
179
183
  def decrypt_string(value)
180
- return value if encryptors == [nil]
181
184
  encryptors.each do |encryptor|
182
- begin
183
- return encryptor.decrypt(value) if encryptor
184
- rescue OpenSSL::Cipher::CipherError
185
- # Not the right key, try the next one
186
- end
185
+ return encryptor.decrypt(value)
186
+ rescue OpenSSL::Cipher::CipherError
187
+ # Not the right key, try the next one
187
188
  end
188
- raise InvalidSecretError
189
+
190
+ # None of the keys worked
191
+ raise InvalidSecretError.new("Cannot decrypt. Invalid secret provided.")
189
192
  end
190
193
 
191
194
  def encryptors
192
- if !defined?(@encryptors) || @encryptors.empty?
193
- @encryptors = make_encryptors(ENV["SIDEKIQ_ENCRYPTED_ARGS_SECRET"].to_s.split)
195
+ if @encryptors.nil?
196
+ secret = ENV.fetch("SIDEKIQ_ENCRYPTED_ARGS_SECRET", "").strip
197
+ if secret.empty?
198
+ raise InvalidSecretError.new("Secret not set. Call Sidekiq::EncryptedArgs.secret= or set the SIDEKIQ_ENCRYPTED_ARGS_SECRET environment variable.")
199
+ end
200
+
201
+ @encryptors = make_encryptors(secret.split).freeze
194
202
  end
195
203
  @encryptors
196
204
  end
197
205
 
206
+ # Create encryptors from secrets.
207
+ #
208
+ # @param [String, Array<String>] secrets One or more secrets to create encryptors from
209
+ # @return [Array<SecretKeys::Encryptor>, nil] Array of encryptors or nil if no secrets provided
198
210
  def make_encryptors(secrets)
199
- Array(secrets).map { |val| val.nil? ? nil : SecretKeys::Encryptor.from_password(val, SALT) }
211
+ return nil if secrets.nil?
212
+
213
+ Array(secrets).map { |val| SecretKeys::Encryptor.from_password(val, SALT) }
200
214
  end
201
215
 
202
- # @param [String] class_name name of a class
203
- # @return [Class] class that was referenced by name
216
+ # Convert a string class name into the actual class constant.
217
+ #
218
+ # @param [String] class_name Name of a class (e.g., "MyModule::MyClass")
219
+ # @return [Class] The class constant that was referenced by name
204
220
  def constantize(class_name)
205
221
  names = class_name.split("::")
206
222
  # Clear leading :: for root namespace since we're already calling from object
@@ -210,21 +226,11 @@ module Sidekiq
210
226
  names.inject(Object) { |constant, name| constant.const_get(name, false) }
211
227
  end
212
228
 
213
- def replace_argument_positions(worker_class, encrypt_option_hash)
214
- encrypted_indexes = []
215
- encrypt_option_hash.each do |key, value|
216
- next unless value
217
-
218
- if key.is_a?(Integer) || (key.is_a?(String) && key.match?(INTEGER_PATTERN))
219
- encrypted_indexes << key.to_i
220
- elsif key.is_a?(Symbol) || key.is_a?(String)
221
- position = perform_method_parameter_index(worker_class, key)
222
- encrypted_indexes << position if position
223
- end
224
- end
225
- encrypted_indexes
226
- end
227
-
229
+ # Get the index of a parameter in the worker's perform method.
230
+ #
231
+ # @param [Class] worker_class The worker class to inspect
232
+ # @param [String, Symbol] parameter The parameter name to find
233
+ # @return [Integer, nil] The zero-based index of the parameter, or nil if not found
228
234
  def perform_method_parameter_index(worker_class, parameter)
229
235
  if worker_class
230
236
  parameter = parameter.to_sym
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
21
21
  Appraisals
22
22
  Gemfile
23
23
  Gemfile.lock
24
+ benchmark.rb
24
25
  Rakefile
25
26
  gemfiles/
26
27
  spec/
@@ -33,10 +34,10 @@ Gem::Specification.new do |spec|
33
34
  spec.bindir = "bin"
34
35
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
35
36
 
36
- spec.required_ruby_version = ">= 2.4"
37
+ spec.required_ruby_version = ">= 2.7"
37
38
 
38
- spec.add_dependency "sidekiq", ">= 4.0"
39
+ spec.add_dependency "sidekiq", ">= 6.3"
39
40
  spec.add_dependency "secret_keys"
40
41
 
41
- spec.add_development_dependency "bundler", "~>2.0"
42
+ spec.add_development_dependency "bundler"
42
43
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-encrypted_args
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  - Winston Durand
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2025-08-30 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: sidekiq
@@ -17,14 +16,14 @@ dependencies:
17
16
  requirements:
18
17
  - - ">="
19
18
  - !ruby/object:Gem::Version
20
- version: '4.0'
19
+ version: '6.3'
21
20
  type: :runtime
22
21
  prerelease: false
23
22
  version_requirements: !ruby/object:Gem::Requirement
24
23
  requirements:
25
24
  - - ">="
26
25
  - !ruby/object:Gem::Version
27
- version: '4.0'
26
+ version: '6.3'
28
27
  - !ruby/object:Gem::Dependency
29
28
  name: secret_keys
30
29
  requirement: !ruby/object:Gem::Requirement
@@ -43,17 +42,16 @@ dependencies:
43
42
  name: bundler
44
43
  requirement: !ruby/object:Gem::Requirement
45
44
  requirements:
46
- - - "~>"
45
+ - - ">="
47
46
  - !ruby/object:Gem::Version
48
- version: '2.0'
47
+ version: '0'
49
48
  type: :development
50
49
  prerelease: false
51
50
  version_requirements: !ruby/object:Gem::Requirement
52
51
  requirements:
53
- - - "~>"
52
+ - - ">="
54
53
  - !ruby/object:Gem::Version
55
- version: '2.0'
56
- description:
54
+ version: '0'
57
55
  email:
58
56
  - bbdurand@gmail.com
59
57
  - me@winstondurand.com
@@ -65,7 +63,6 @@ files:
65
63
  - MIT_LICENSE.txt
66
64
  - README.md
67
65
  - VERSION
68
- - benchmark.rb
69
66
  - lib/sidekiq-encrypted_args.rb
70
67
  - lib/sidekiq/encrypted_args.rb
71
68
  - lib/sidekiq/encrypted_args/client_middleware.rb
@@ -79,7 +76,6 @@ metadata:
79
76
  homepage_uri: https://github.com/bdurand/sidekiq-encrypted_args
80
77
  source_code_uri: https://github.com/bdurand/sidekiq-encrypted_args
81
78
  changelog_uri: https://github.com/bdurand/sidekiq-encrypted_args/blob/main/CHANGE_LOG.md
82
- post_install_message:
83
79
  rdoc_options: []
84
80
  require_paths:
85
81
  - lib
@@ -87,15 +83,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
87
83
  requirements:
88
84
  - - ">="
89
85
  - !ruby/object:Gem::Version
90
- version: '2.4'
86
+ version: '2.7'
91
87
  required_rubygems_version: !ruby/object:Gem::Requirement
92
88
  requirements:
93
89
  - - ">="
94
90
  - !ruby/object:Gem::Version
95
91
  version: '0'
96
92
  requirements: []
97
- rubygems_version: 3.4.10
98
- signing_key:
93
+ rubygems_version: 4.0.3
99
94
  specification_version: 4
100
95
  summary: Support for encrypting arguments that contain sensitive information in sidekiq
101
96
  jobs.
data/benchmark.rb DELETED
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/setup"
4
-
5
- require "benchmark"
6
- require "sidekiq"
7
- require_relative "lib/sidekiq/encrypted_args"
8
-
9
- class WorkerWithoutEncryption
10
- include Sidekiq::Worker
11
-
12
- def perform(arg_1, arg_2, arg_3)
13
- end
14
- end
15
-
16
- class WorkerWithEncryption
17
- include Sidekiq::Worker
18
-
19
- sidekiq_options encrypted_args: [true, true, true]
20
-
21
- def perform(arg_1, arg_2, arg_3)
22
- end
23
- end
24
-
25
- middleware = ->(worker_class) {
26
- job = {"args" => ["foo", "bar", "baz"]}
27
- Sidekiq::EncryptedArgs::ClientMiddleware.new.call(worker_class, job, "default") do
28
- worker = worker_class.new
29
- Sidekiq::EncryptedArgs::ServerMiddleware.new.call(worker, job, "default") do
30
- worker.perform(*job["args"])
31
- end
32
- end
33
- }
34
-
35
- Benchmark.bm do |benchmark|
36
- benchmark.report("No Encryption: ") { 10000.times { middleware.call(WorkerWithoutEncryption) } }
37
- benchmark.report("With Encryption:") { 10000.times { middleware.call(WorkerWithEncryption) } }
38
- end