sidekiq-encrypted_args 1.2.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 +4 -4
- data/CHANGE_LOG.md +25 -0
- data/README.md +23 -9
- data/VERSION +1 -1
- data/lib/sidekiq/encrypted_args/client_middleware.rb +17 -4
- data/lib/sidekiq/encrypted_args/server_middleware.rb +11 -13
- data/lib/sidekiq/encrypted_args/version.rb +1 -0
- data/lib/sidekiq/encrypted_args.rb +136 -50
- data/sidekiq-encrypted_args.gemspec +4 -4
- metadata +6 -25
- data/benchmark.rb +0 -38
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 579403fba577dd83ce26ad13d8893ccdf86bc28883fff397fc029f265f0b7cd5
|
|
4
|
+
data.tar.gz: '08435986fca6664001ebb07dcd1b49a79a116b6cd78f26638a3c0a5d37e9c7b3'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 936274740450e2cbaf08f7f060df37f3a5a29e4aea47bc9985028403f2aca0f879f724167861e1b444f829b597682f2786dd2bf31f00b5ef5a26002ddda094d8
|
|
7
|
+
data.tar.gz: a59847233f9e50690fad84ed1cb071b204effe830f00bf738a08b2f7208211cd38d79e4b5504d1408c63dba930da4f87db31d0eb1d75c860b0dc1e2bc3bb53a9
|
data/CHANGE_LOG.md
CHANGED
|
@@ -4,6 +4,31 @@ 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
|
+
|
|
25
|
+
## 2.0.0
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- 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.
|
|
30
|
+
- Bumped minimum required Ruby version to 2.7 and Sidekiq to 6.3.
|
|
31
|
+
|
|
7
32
|
## 1.2.0
|
|
8
33
|
|
|
9
34
|
### 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
|
|
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
|
-
#
|
|
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
|
|
@@ -50,6 +51,15 @@ Sidekiq.configure_server do |config|
|
|
|
50
51
|
end
|
|
51
52
|
```
|
|
52
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
|
+
|
|
53
63
|
## Worker Configuration
|
|
54
64
|
|
|
55
65
|
To declare that a worker is using encrypted arguments, you must set the `encrypted_args` sidekiq option.
|
|
@@ -58,7 +68,7 @@ Setting the option to `true` will encrypt all the arguments passed to the `perfo
|
|
|
58
68
|
|
|
59
69
|
```ruby
|
|
60
70
|
class SecretWorker
|
|
61
|
-
include Sidekiq::
|
|
71
|
+
include Sidekiq::Job
|
|
62
72
|
|
|
63
73
|
sidekiq_options encrypted_args: true
|
|
64
74
|
|
|
@@ -67,20 +77,24 @@ class SecretWorker
|
|
|
67
77
|
end
|
|
68
78
|
```
|
|
69
79
|
|
|
70
|
-
You can also choose to only encrypt specific arguments
|
|
80
|
+
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
81
|
|
|
72
82
|
```ruby
|
|
73
|
-
# Pass in a
|
|
83
|
+
# Pass in a single argument name
|
|
84
|
+
sidekiq_options encrypted_args: :arg_2
|
|
85
|
+
# or as a string
|
|
86
|
+
sidekiq_options encrypted_args: "arg_2"
|
|
87
|
+
# or as an array
|
|
74
88
|
sidekiq_options encrypted_args: [:arg_2]
|
|
75
|
-
# or
|
|
76
|
-
sidekiq_options encrypted_args: ["arg_2"]
|
|
77
89
|
|
|
78
90
|
def perform(arg_1, arg_2, arg_3)
|
|
79
91
|
end
|
|
80
92
|
```
|
|
81
93
|
|
|
82
94
|
```ruby
|
|
83
|
-
# Pass in an
|
|
95
|
+
# Pass in an integer indicating which argument position should be encrypted (0-indexed)
|
|
96
|
+
sidekiq_options encrypted_args: 1
|
|
97
|
+
# or as an array
|
|
84
98
|
sidekiq_options encrypted_args: [1]
|
|
85
99
|
|
|
86
100
|
def perform(arg_1, arg_2, arg_3)
|
|
@@ -99,7 +113,7 @@ Sidekiq::EncryptedArgs.secret = ["CurrentSecret", "OldSecret", "EvenOlderSecret"
|
|
|
99
113
|
|
|
100
114
|
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
115
|
|
|
102
|
-
If you are using the `SIDEKIQ_ENCRYPTED_ARGS_SECRET` environment variable to specify your secret, you can
|
|
116
|
+
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
117
|
|
|
104
118
|
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
119
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2.0.2
|
|
@@ -10,7 +10,16 @@ module Sidekiq
|
|
|
10
10
|
#
|
|
11
11
|
# @see ServerMiddleware
|
|
12
12
|
class ClientMiddleware
|
|
13
|
-
|
|
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)
|
|
@@ -26,15 +35,19 @@ module Sidekiq
|
|
|
26
35
|
#
|
|
27
36
|
# Additionally, set `job["encrypted_args"]` to the canonicalized version (i.e. `Array<Integer>`)
|
|
28
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
|
+
#
|
|
29
41
|
# @param [Hash] job The Sidekiq job hash containing arguments and metadata
|
|
30
42
|
# @param [Array<Integer>] encrypted_args array of indexes in job to encrypt
|
|
31
43
|
# @return [void]
|
|
32
44
|
def encrypt_job_arguments!(job, encrypted_args)
|
|
33
45
|
if encrypted_args
|
|
34
|
-
|
|
35
|
-
job_args.each_with_index do |value, position|
|
|
46
|
+
job["args"] = job["args"].map.with_index do |value, position|
|
|
36
47
|
if encrypted_args.include?(position) && !EncryptedArgs.encrypted?(value)
|
|
37
|
-
|
|
48
|
+
EncryptedArgs.encrypt(value)
|
|
49
|
+
else
|
|
50
|
+
value
|
|
38
51
|
end
|
|
39
52
|
end
|
|
40
53
|
job["encrypted_args"] = encrypted_args
|
|
@@ -10,11 +10,20 @@ module Sidekiq
|
|
|
10
10
|
#
|
|
11
11
|
# @see ClientMiddleware
|
|
12
12
|
class ServerMiddleware
|
|
13
|
-
|
|
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 =
|
|
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
|
|
@@ -11,17 +11,18 @@ 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
|
+
@mutex = Mutex.new
|
|
16
|
+
|
|
14
17
|
# Error thrown when the secret is invalid
|
|
15
18
|
class InvalidSecretError < StandardError
|
|
16
|
-
def initialize
|
|
17
|
-
super("Cannot decrypt. Invalid secret provided.")
|
|
18
|
-
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
class << self
|
|
22
22
|
# Set the secret key used for encrypting arguments. If this is not set,
|
|
23
23
|
# the value will be loaded from the `SIDEKIQ_ENCRYPTED_ARGS_SECRET` environment
|
|
24
|
-
# variable. If that value is not set,
|
|
24
|
+
# variable. If that value is not set either, an InvalidSecretError will be
|
|
25
|
+
# raised when arguments are encrypted or decrypted.
|
|
25
26
|
#
|
|
26
27
|
# You can set multiple secrets by passing an array if you need to roll your secrets.
|
|
27
28
|
# The left most value in the array will be used as the encryption secret, but
|
|
@@ -39,7 +40,9 @@ module Sidekiq
|
|
|
39
40
|
# @param [String, Array<String>] value One or more secrets to use for encrypting arguments.
|
|
40
41
|
# @return [void]
|
|
41
42
|
def secret=(value)
|
|
42
|
-
@
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
@encryptors = make_encryptors(value).freeze
|
|
45
|
+
end
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
# Add the client and server middleware to the default Sidekiq
|
|
@@ -58,6 +61,7 @@ module Sidekiq
|
|
|
58
61
|
# @param [String] secret optionally set the secret here. See {.secret=}
|
|
59
62
|
def configure!(secret: nil)
|
|
60
63
|
self.secret = secret unless secret.nil?
|
|
64
|
+
encryptors # Calling encryptors will validate that a secret is set.
|
|
61
65
|
|
|
62
66
|
Sidekiq.configure_client do |config|
|
|
63
67
|
config.client_middleware do |chain|
|
|
@@ -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
|
-
|
|
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
|
|
@@ -127,7 +186,10 @@ module Sidekiq
|
|
|
127
186
|
# can be `true` or an array indicating if each positional argument should be encrypted, or a hash
|
|
128
187
|
# with keys for the argument position and true as the value.
|
|
129
188
|
#
|
|
130
|
-
# @
|
|
189
|
+
# @param [String, Class] worker_class The worker class or class name
|
|
190
|
+
# @param [Hash] job The Sidekiq job hash containing arguments and metadata
|
|
191
|
+
# @return [Array<Integer>, nil] Array of argument positions to encrypt, or nil if encryption is not configured
|
|
192
|
+
# @api private
|
|
131
193
|
def encrypted_args_option(worker_class, job)
|
|
132
194
|
option = job["encrypted_args"]
|
|
133
195
|
return nil if option.nil?
|
|
@@ -140,21 +202,30 @@ module Sidekiq
|
|
|
140
202
|
raise ArgumentError.new("Hash-based argument encryption is no longer supported.")
|
|
141
203
|
else
|
|
142
204
|
array_type = nil
|
|
143
|
-
Array(option).
|
|
205
|
+
Array(option).each do |val|
|
|
144
206
|
current_type = nil
|
|
145
207
|
if val.is_a?(Integer)
|
|
146
208
|
indexes << val
|
|
147
209
|
current_type = :integer
|
|
148
210
|
elsif val.is_a?(Symbol) || val.is_a?(String)
|
|
149
|
-
|
|
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
|
|
150
218
|
position = perform_method_parameter_index(worker_class, val)
|
|
151
|
-
|
|
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
|
|
152
223
|
current_type = :symbol
|
|
153
224
|
else
|
|
154
225
|
raise ArgumentError.new("Encrypted args must be specified as integers or symbols.")
|
|
155
226
|
end
|
|
156
227
|
|
|
157
|
-
if array_type && current_type
|
|
228
|
+
if array_type && current_type != array_type
|
|
158
229
|
raise ArgumentError.new("Encrypted args cannot mix integers and symbols.")
|
|
159
230
|
else
|
|
160
231
|
array_type ||= current_type
|
|
@@ -171,36 +242,52 @@ module Sidekiq
|
|
|
171
242
|
private_constant :SALT
|
|
172
243
|
|
|
173
244
|
def encrypt_string(value)
|
|
174
|
-
|
|
175
|
-
return value if encryptor.nil?
|
|
176
|
-
encryptor.encrypt(value)
|
|
245
|
+
encryptors.first.encrypt(value)
|
|
177
246
|
end
|
|
178
247
|
|
|
179
248
|
def decrypt_string(value)
|
|
180
|
-
return value if encryptors == [nil]
|
|
181
249
|
encryptors.each do |encryptor|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
# Not the right key, try the next one
|
|
186
|
-
end
|
|
250
|
+
return encryptor.decrypt(value)
|
|
251
|
+
rescue OpenSSL::Cipher::CipherError
|
|
252
|
+
# Not the right key, try the next one
|
|
187
253
|
end
|
|
188
|
-
|
|
254
|
+
|
|
255
|
+
# None of the keys worked
|
|
256
|
+
raise InvalidSecretError.new("Cannot decrypt. Invalid secret provided.")
|
|
189
257
|
end
|
|
190
258
|
|
|
191
259
|
def encryptors
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
269
|
+
|
|
270
|
+
@encryptors = make_encryptors(secret.split).freeze
|
|
271
|
+
end
|
|
272
|
+
@encryptors
|
|
194
273
|
end
|
|
195
|
-
@encryptors
|
|
196
274
|
end
|
|
197
275
|
|
|
276
|
+
# Create encryptors from secrets.
|
|
277
|
+
#
|
|
278
|
+
# @param [String, Array<String>] secrets One or more secrets to create encryptors from
|
|
279
|
+
# @return [Array<SecretKeys::Encryptor>, nil] Array of encryptors or nil if no secrets provided
|
|
198
280
|
def make_encryptors(secrets)
|
|
199
|
-
Array(secrets).
|
|
281
|
+
secrets = Array(secrets).reject { |val| val.nil? || val.to_s.empty? }
|
|
282
|
+
return nil if secrets.empty?
|
|
283
|
+
|
|
284
|
+
secrets.map { |val| SecretKeys::Encryptor.from_password(val, SALT) }
|
|
200
285
|
end
|
|
201
286
|
|
|
202
|
-
#
|
|
203
|
-
#
|
|
287
|
+
# Convert a string class name into the actual class constant.
|
|
288
|
+
#
|
|
289
|
+
# @param [String] class_name Name of a class (e.g., "MyModule::MyClass")
|
|
290
|
+
# @return [Class, nil] The class constant that was referenced by name, or nil if it is not defined
|
|
204
291
|
def constantize(class_name)
|
|
205
292
|
names = class_name.split("::")
|
|
206
293
|
# Clear leading :: for root namespace since we're already calling from object
|
|
@@ -208,28 +295,27 @@ module Sidekiq
|
|
|
208
295
|
# Map reduce to the constant. Use inherit=false to not accidentally search
|
|
209
296
|
# parent modules
|
|
210
297
|
names.inject(Object) { |constant, name| constant.const_get(name, false) }
|
|
298
|
+
rescue NameError
|
|
299
|
+
nil
|
|
211
300
|
end
|
|
212
301
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
302
|
+
# Get the index of a parameter in the worker's perform method.
|
|
303
|
+
#
|
|
304
|
+
# @param [Class] worker_class The worker class to inspect
|
|
305
|
+
# @param [String, Symbol] parameter The parameter name to find
|
|
306
|
+
# @return [Integer, nil] The zero-based index of the parameter, or nil if not found
|
|
307
|
+
def perform_method_parameter_index(worker_class, parameter)
|
|
308
|
+
return nil unless worker_class
|
|
217
309
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
encrypted_indexes << position if position
|
|
223
|
-
end
|
|
310
|
+
perform_method = begin
|
|
311
|
+
worker_class.instance_method(:perform)
|
|
312
|
+
rescue NameError
|
|
313
|
+
nil
|
|
224
314
|
end
|
|
225
|
-
|
|
226
|
-
end
|
|
315
|
+
return nil if perform_method.nil?
|
|
227
316
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
parameter = parameter.to_sym
|
|
231
|
-
worker_class.instance_method(:perform).parameters.find_index { |_, name| name == parameter }
|
|
232
|
-
end
|
|
317
|
+
parameter = parameter.to_sym
|
|
318
|
+
perform_method.parameters.find_index { |_, name| name == parameter }
|
|
233
319
|
end
|
|
234
320
|
end
|
|
235
321
|
end
|
|
@@ -18,9 +18,11 @@ 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
|
|
25
|
+
benchmark.rb
|
|
24
26
|
Rakefile
|
|
25
27
|
gemfiles/
|
|
26
28
|
spec/
|
|
@@ -33,10 +35,8 @@ Gem::Specification.new do |spec|
|
|
|
33
35
|
spec.bindir = "bin"
|
|
34
36
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
35
37
|
|
|
36
|
-
spec.required_ruby_version = ">= 2.
|
|
38
|
+
spec.required_ruby_version = ">= 2.7"
|
|
37
39
|
|
|
38
|
-
spec.add_dependency "sidekiq", ">=
|
|
40
|
+
spec.add_dependency "sidekiq", ">= 6.3"
|
|
39
41
|
spec.add_dependency "secret_keys"
|
|
40
|
-
|
|
41
|
-
spec.add_development_dependency "bundler", "~>2.0"
|
|
42
42
|
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:
|
|
4
|
+
version: 2.0.2
|
|
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:
|
|
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: '
|
|
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: '
|
|
26
|
+
version: '6.3'
|
|
28
27
|
- !ruby/object:Gem::Dependency
|
|
29
28
|
name: secret_keys
|
|
30
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -39,21 +38,6 @@ dependencies:
|
|
|
39
38
|
- - ">="
|
|
40
39
|
- !ruby/object:Gem::Version
|
|
41
40
|
version: '0'
|
|
42
|
-
- !ruby/object:Gem::Dependency
|
|
43
|
-
name: bundler
|
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
|
45
|
-
requirements:
|
|
46
|
-
- - "~>"
|
|
47
|
-
- !ruby/object:Gem::Version
|
|
48
|
-
version: '2.0'
|
|
49
|
-
type: :development
|
|
50
|
-
prerelease: false
|
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
-
requirements:
|
|
53
|
-
- - "~>"
|
|
54
|
-
- !ruby/object:Gem::Version
|
|
55
|
-
version: '2.0'
|
|
56
|
-
description:
|
|
57
41
|
email:
|
|
58
42
|
- bbdurand@gmail.com
|
|
59
43
|
- me@winstondurand.com
|
|
@@ -65,7 +49,6 @@ files:
|
|
|
65
49
|
- MIT_LICENSE.txt
|
|
66
50
|
- README.md
|
|
67
51
|
- VERSION
|
|
68
|
-
- benchmark.rb
|
|
69
52
|
- lib/sidekiq-encrypted_args.rb
|
|
70
53
|
- lib/sidekiq/encrypted_args.rb
|
|
71
54
|
- lib/sidekiq/encrypted_args/client_middleware.rb
|
|
@@ -79,7 +62,6 @@ metadata:
|
|
|
79
62
|
homepage_uri: https://github.com/bdurand/sidekiq-encrypted_args
|
|
80
63
|
source_code_uri: https://github.com/bdurand/sidekiq-encrypted_args
|
|
81
64
|
changelog_uri: https://github.com/bdurand/sidekiq-encrypted_args/blob/main/CHANGE_LOG.md
|
|
82
|
-
post_install_message:
|
|
83
65
|
rdoc_options: []
|
|
84
66
|
require_paths:
|
|
85
67
|
- lib
|
|
@@ -87,15 +69,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
87
69
|
requirements:
|
|
88
70
|
- - ">="
|
|
89
71
|
- !ruby/object:Gem::Version
|
|
90
|
-
version: '2.
|
|
72
|
+
version: '2.7'
|
|
91
73
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
74
|
requirements:
|
|
93
75
|
- - ">="
|
|
94
76
|
- !ruby/object:Gem::Version
|
|
95
77
|
version: '0'
|
|
96
78
|
requirements: []
|
|
97
|
-
rubygems_version:
|
|
98
|
-
signing_key:
|
|
79
|
+
rubygems_version: 4.0.3
|
|
99
80
|
specification_version: 4
|
|
100
81
|
summary: Support for encrypting arguments that contain sensitive information in sidekiq
|
|
101
82
|
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
|