sidekiq-encrypted_args 2.0.0 → 2.0.2

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: aed8d03eeb0b589556b436823f85c13331ab6c1703d732d425321b4cc1b2bc71
4
- data.tar.gz: 2f2a42257c669731f97ee25cfc52da48a4cbdbe18b59e0fd84bcebbe50820e89
3
+ metadata.gz: 579403fba577dd83ce26ad13d8893ccdf86bc28883fff397fc029f265f0b7cd5
4
+ data.tar.gz: '08435986fca6664001ebb07dcd1b49a79a116b6cd78f26638a3c0a5d37e9c7b3'
5
5
  SHA512:
6
- metadata.gz: cbdd4e2d8144eddf2e3699ac30ab52114de6ab8e09b97e75909e2e67defc81f5c4cd71e43efb22c404ac4890213abd6c300ee45117ae7e1428cf5b2b1794595c
7
- data.tar.gz: 6644bca285087c5de5b2c68aba368f4192b2061f3f5f6095125d8ca7e16888b40214e2ce7b9a3c2a2ec39b808be6ccadefc7a1c7777c0bd3edecf1ef935f7a18
6
+ metadata.gz: 936274740450e2cbaf08f7f060df37f3a5a29e4aea47bc9985028403f2aca0f879f724167861e1b444f829b597682f2786dd2bf31f00b5ef5a26002ddda094d8
7
+ data.tar.gz: a59847233f9e50690fad84ed1cb071b204effe830f00bf738a08b2f7208211cd38d79e4b5504d1408c63dba930da4f87db31d0eb1d75c860b0dc1e2bc3bb53a9
data/CHANGE_LOG.md CHANGED
@@ -4,6 +4,24 @@ 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.2
8
+
9
+ ### Changed
10
+
11
+ - An `ArgumentError` is now raised when a named encrypted argument cannot be matched to a parameter of the worker's `perform` method or when the worker class name cannot be resolved to a class. Previously these arguments were silently left unencrypted.
12
+
13
+ ### Fixed
14
+
15
+ - Setting the secret to an empty value (e.g. an empty array or empty string) no longer silently disables encryption. An `InvalidSecretError` is now raised when encrypting or decrypting if no secret is available.
16
+ - The client middleware no longer mutates the caller's arguments array in place when encrypting arguments.
17
+ - Lazily initializing the encryption keys from the `SIDEKIQ_ENCRYPTED_ARGS_SECRET` environment variable is now thread safe.
18
+
19
+ ## 2.0.1
20
+
21
+ ### Added
22
+
23
+ - Added initializer helper methods `Sidekiq::EncryptedArgs.encrypt_before` and `Sidekiq::EncryptedArgs.encrypt_after` to allow specifying middleware order for inserting the client encryption middleware and `Sidekiq::EncryptedArgs.decrypt_before` and `Sidekiq::EncryptedArgs.decrypt_after` to allow specifying middleware order for inserting the server decryption middleware.
24
+
7
25
  ## 2.0.0
8
26
 
9
27
  ### Changed
data/README.md CHANGED
@@ -51,6 +51,15 @@ Sidekiq.configure_server do |config|
51
51
  end
52
52
  ```
53
53
 
54
+ If there is specific middleware you want to insert the encryption or decryption middleware before or after, you can use the following helper methods:
55
+
56
+ ```ruby
57
+ Sidekiq::EncryptedArgs.encrypt_before(SomeOtherMiddlewareClass)
58
+ Sidekiq::EncryptedArgs.encrypt_after(SomeOtherMiddlewareClass)
59
+ Sidekiq::EncryptedArgs.decrypt_before(SomeOtherMiddlewareClass)
60
+ Sidekiq::EncryptedArgs.decrypt_after(SomeOtherMiddlewareClass)
61
+ ```
62
+
54
63
  ## Worker Configuration
55
64
 
56
65
  To declare that a worker is using encrypted arguments, you must set the `encrypted_args` sidekiq option.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0
1
+ 2.0.2
@@ -35,15 +35,19 @@ module Sidekiq
35
35
  #
36
36
  # Additionally, set `job["encrypted_args"]` to the canonicalized version (i.e. `Array<Integer>`)
37
37
  #
38
+ # The args array is replaced rather than mutated in place since the array
39
+ # can be the same object passed in by the caller of Sidekiq::Client.push.
40
+ #
38
41
  # @param [Hash] job The Sidekiq job hash containing arguments and metadata
39
42
  # @param [Array<Integer>] encrypted_args array of indexes in job to encrypt
40
43
  # @return [void]
41
44
  def encrypt_job_arguments!(job, encrypted_args)
42
45
  if encrypted_args
43
- job_args = job["args"]
44
- job_args.each_with_index do |value, position|
46
+ job["args"] = job["args"].map.with_index do |value, position|
45
47
  if encrypted_args.include?(position) && !EncryptedArgs.encrypted?(value)
46
- job_args[position] = EncryptedArgs.encrypt(value)
48
+ EncryptedArgs.encrypt(value)
49
+ else
50
+ value
47
51
  end
48
52
  end
49
53
  job["encrypted_args"] = encrypted_args
@@ -12,6 +12,7 @@ module Sidekiq
12
12
  # personally identifiable information.
13
13
  module EncryptedArgs
14
14
  @encryptors = nil
15
+ @mutex = Mutex.new
15
16
 
16
17
  # Error thrown when the secret is invalid
17
18
  class InvalidSecretError < StandardError
@@ -20,7 +21,8 @@ module Sidekiq
20
21
  class << self
21
22
  # Set the secret key used for encrypting arguments. If this is not set,
22
23
  # the value will be loaded from the `SIDEKIQ_ENCRYPTED_ARGS_SECRET` environment
23
- # variable. If that value is not set, arguments will not be encrypted.
24
+ # variable. If that value is not set either, an InvalidSecretError will be
25
+ # raised when arguments are encrypted or decrypted.
24
26
  #
25
27
  # You can set multiple secrets by passing an array if you need to roll your secrets.
26
28
  # The left most value in the array will be used as the encryption secret, but
@@ -38,7 +40,9 @@ module Sidekiq
38
40
  # @param [String, Array<String>] value One or more secrets to use for encrypting arguments.
39
41
  # @return [void]
40
42
  def secret=(value)
41
- @encryptors = make_encryptors(value).freeze
43
+ @mutex.synchronize do
44
+ @encryptors = make_encryptors(value).freeze
45
+ end
42
46
  end
43
47
 
44
48
  # Add the client and server middleware to the default Sidekiq
@@ -75,6 +79,66 @@ module Sidekiq
75
79
  end
76
80
  end
77
81
 
82
+ # Insert the client encryption middleware before the specified middleware.
83
+ #
84
+ # @param [Class] middleware The middleware class to insert before
85
+ # @return [void]
86
+ def encrypt_before(middleware)
87
+ Sidekiq.configure_client do |config|
88
+ config.client_middleware do |chain|
89
+ chain.insert_before(middleware, Sidekiq::EncryptedArgs::ClientMiddleware)
90
+ end
91
+ end
92
+
93
+ Sidekiq.configure_server do |config|
94
+ config.client_middleware do |chain|
95
+ chain.insert_before(middleware, Sidekiq::EncryptedArgs::ClientMiddleware)
96
+ end
97
+ end
98
+ end
99
+
100
+ # Insert the client encryption middleware after the specified middleware.
101
+ #
102
+ # @param [Class] middleware The middleware class to insert after
103
+ # @return [void]
104
+ def encrypt_after(middleware)
105
+ Sidekiq.configure_client do |config|
106
+ config.client_middleware do |chain|
107
+ chain.insert_after(middleware, Sidekiq::EncryptedArgs::ClientMiddleware)
108
+ end
109
+ end
110
+
111
+ Sidekiq.configure_server do |config|
112
+ config.client_middleware do |chain|
113
+ chain.insert_after(middleware, Sidekiq::EncryptedArgs::ClientMiddleware)
114
+ end
115
+ end
116
+ end
117
+
118
+ # Insert the server decryption middleware before the specified middleware.
119
+ #
120
+ # @param [Class] middleware The middleware class to insert before
121
+ # @return [void]
122
+ def decrypt_before(middleware)
123
+ Sidekiq.configure_server do |config|
124
+ config.server_middleware do |chain|
125
+ chain.insert_before(middleware, Sidekiq::EncryptedArgs::ServerMiddleware)
126
+ end
127
+ end
128
+ end
129
+
130
+ # Insert the server decryption middleware after the specified middleware.
131
+ #
132
+ # @param [Class] middleware The middleware class to insert after
133
+ # @return [void]
134
+ def decrypt_after(middleware)
135
+ Sidekiq.configure_server do |config|
136
+ config.server_middleware do |chain|
137
+ chain.insert_after(middleware, Sidekiq::EncryptedArgs::ServerMiddleware)
138
+ end
139
+ end
140
+ end
141
+
78
142
  # Encrypt a value.
79
143
  #
80
144
  # @example Encrypting a simple value
@@ -85,17 +149,12 @@ module Sidekiq
85
149
  #
86
150
  # @param [#to_json, Object] data Data to encrypt. You can pass any JSON compatible data types or structures.
87
151
  #
88
- # @return [String]
152
+ # @return [String, nil]
89
153
  def encrypt(data)
90
154
  return nil if data.nil?
91
155
 
92
156
  json = (data.respond_to?(:to_json) ? data.to_json : JSON.generate(data))
93
- encrypted = encrypt_string(json)
94
- if encrypted == json
95
- data
96
- else
97
- encrypted
98
- end
157
+ encrypt_string(json)
99
158
  end
100
159
 
101
160
  # Decrypt data
@@ -143,15 +202,24 @@ module Sidekiq
143
202
  raise ArgumentError.new("Hash-based argument encryption is no longer supported.")
144
203
  else
145
204
  array_type = nil
146
- Array(option).each_with_index do |val, position|
205
+ Array(option).each do |val|
147
206
  current_type = nil
148
207
  if val.is_a?(Integer)
149
208
  indexes << val
150
209
  current_type = :integer
151
210
  elsif val.is_a?(Symbol) || val.is_a?(String)
152
- worker_class = constantize(worker_class) if worker_class.is_a?(String)
211
+ if worker_class.is_a?(String)
212
+ class_name = worker_class
213
+ worker_class = constantize(worker_class)
214
+ if worker_class.nil?
215
+ raise ArgumentError.new("Cannot resolve worker class #{class_name.inspect} to look up named encrypted args; use integer positions instead.")
216
+ end
217
+ end
153
218
  position = perform_method_parameter_index(worker_class, val)
154
- indexes << position if position
219
+ if position.nil?
220
+ raise ArgumentError.new("Encrypted arg #{val.inspect} is not a parameter of #{worker_class}#perform.")
221
+ end
222
+ indexes << position
155
223
  current_type = :symbol
156
224
  else
157
225
  raise ArgumentError.new("Encrypted args must be specified as integers or symbols.")
@@ -174,10 +242,7 @@ module Sidekiq
174
242
  private_constant :SALT
175
243
 
176
244
  def encrypt_string(value)
177
- encryptor = encryptors.first
178
- return value if encryptor.nil?
179
-
180
- encryptor.encrypt(value)
245
+ encryptors.first.encrypt(value)
181
246
  end
182
247
 
183
248
  def decrypt_string(value)
@@ -192,15 +257,20 @@ module Sidekiq
192
257
  end
193
258
 
194
259
  def encryptors
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
260
+ current = @encryptors
261
+ return current unless current.nil?
262
+
263
+ @mutex.synchronize do
264
+ if @encryptors.nil?
265
+ secret = ENV.fetch("SIDEKIQ_ENCRYPTED_ARGS_SECRET", "").strip
266
+ if secret.empty?
267
+ raise InvalidSecretError.new("Secret not set. Call Sidekiq::EncryptedArgs.secret= or set the SIDEKIQ_ENCRYPTED_ARGS_SECRET environment variable.")
268
+ end
200
269
 
201
- @encryptors = make_encryptors(secret.split).freeze
270
+ @encryptors = make_encryptors(secret.split).freeze
271
+ end
272
+ @encryptors
202
273
  end
203
- @encryptors
204
274
  end
205
275
 
206
276
  # Create encryptors from secrets.
@@ -208,15 +278,16 @@ module Sidekiq
208
278
  # @param [String, Array<String>] secrets One or more secrets to create encryptors from
209
279
  # @return [Array<SecretKeys::Encryptor>, nil] Array of encryptors or nil if no secrets provided
210
280
  def make_encryptors(secrets)
211
- return nil if secrets.nil?
281
+ secrets = Array(secrets).reject { |val| val.nil? || val.to_s.empty? }
282
+ return nil if secrets.empty?
212
283
 
213
- Array(secrets).map { |val| SecretKeys::Encryptor.from_password(val, SALT) }
284
+ secrets.map { |val| SecretKeys::Encryptor.from_password(val, SALT) }
214
285
  end
215
286
 
216
287
  # Convert a string class name into the actual class constant.
217
288
  #
218
289
  # @param [String] class_name Name of a class (e.g., "MyModule::MyClass")
219
- # @return [Class] The class constant that was referenced by name
290
+ # @return [Class, nil] The class constant that was referenced by name, or nil if it is not defined
220
291
  def constantize(class_name)
221
292
  names = class_name.split("::")
222
293
  # Clear leading :: for root namespace since we're already calling from object
@@ -224,6 +295,8 @@ module Sidekiq
224
295
  # Map reduce to the constant. Use inherit=false to not accidentally search
225
296
  # parent modules
226
297
  names.inject(Object) { |constant, name| constant.const_get(name, false) }
298
+ rescue NameError
299
+ nil
227
300
  end
228
301
 
229
302
  # Get the index of a parameter in the worker's perform method.
@@ -232,10 +305,17 @@ module Sidekiq
232
305
  # @param [String, Symbol] parameter The parameter name to find
233
306
  # @return [Integer, nil] The zero-based index of the parameter, or nil if not found
234
307
  def perform_method_parameter_index(worker_class, parameter)
235
- if worker_class
236
- parameter = parameter.to_sym
237
- worker_class.instance_method(:perform).parameters.find_index { |_, name| name == parameter }
308
+ return nil unless worker_class
309
+
310
+ perform_method = begin
311
+ worker_class.instance_method(:perform)
312
+ rescue NameError
313
+ nil
238
314
  end
315
+ return nil if perform_method.nil?
316
+
317
+ parameter = parameter.to_sym
318
+ perform_method.parameters.find_index { |_, name| name == parameter }
239
319
  end
240
320
  end
241
321
  end
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
18
18
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
19
  ignore_files = %w[
20
20
  .
21
+ benchmark.rb
21
22
  Appraisals
22
23
  Gemfile
23
24
  Gemfile.lock
@@ -38,6 +39,4 @@ Gem::Specification.new do |spec|
38
39
 
39
40
  spec.add_dependency "sidekiq", ">= 6.3"
40
41
  spec.add_dependency "secret_keys"
41
-
42
- spec.add_development_dependency "bundler"
43
42
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-encrypted_args
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: bundler
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
41
  email:
56
42
  - bbdurand@gmail.com
57
43
  - me@winstondurand.com