sidekiq-encryptor 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.pryrc +1 -1
- data/.travis.yml +1 -3
- data/CHANGELOG.md +28 -0
- data/README.md +10 -1
- data/lib/sidekiq/encryptor/version.rb +4 -2
- data/lib/sidekiq/encryptor.rb +107 -13
- data/sidekiq-encryptor.gemspec +2 -1
- data/spec/sidekiq/encryptor_spec.rb +45 -17
- data/spec/spec_helper.rb +1 -1
- metadata +14 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc401443775158ce8894d1474e2e68d9f72e0e3d
|
4
|
+
data.tar.gz: ad006a7f9a3b5969b524ac67e13b8e5b3657290f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d8c9e5439e8223dc1c03ab4b7565520cad982f2707f8971dff14de3c0e748efa0874571c611a2381f1c66c124697ec499c85b432467837a6ffdc24c2a46397f
|
7
|
+
data.tar.gz: 1516b7eb16752e7526f4298ba6250cd104033ad777e1cb9edf7b92e8c8fa9d897319a5c8163269c3a3dbb2a665cb020dd166310e59c11986e6666a016d64f81c
|
data/.pryrc
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# sidekiq-encryptor changelog
|
2
|
+
|
3
|
+
## 0.1.0
|
4
|
+
|
5
|
+
Backwards incompatible changes:
|
6
|
+
|
7
|
+
* Protocol version change from 0 to 1. Jobs encrypted by previous
|
8
|
+
versions cannot be decrypted by this version.
|
9
|
+
|
10
|
+
Changes:
|
11
|
+
|
12
|
+
* Switch to Fernet 2.0
|
13
|
+
* Use a distinct protocol version number
|
14
|
+
* Support binary, hexadecimal, and base64 keys. Note that many ASCII
|
15
|
+
keys will be detected as base64. Base64 assumes RFC 2045.
|
16
|
+
* Enforce 256 bit key length
|
17
|
+
* Recommend 32 byte keys in README
|
18
|
+
* Allow an optional `adapter` to be passed in. The adapter must follow
|
19
|
+
the following standards:
|
20
|
+
* Define a `encrypt` and `decrypt` class methods that accept two
|
21
|
+
arguments: `String key, String data`. `key` will always have length
|
22
|
+
32, `data` will be of arbitrary length.
|
23
|
+
* `encrypt` must return a String.
|
24
|
+
* `decrypt` must return a String or nil if the decryption fails.
|
25
|
+
|
26
|
+
## 0.0.1
|
27
|
+
|
28
|
+
Initial release
|
data/README.md
CHANGED
@@ -48,9 +48,18 @@ Sidekiq.configure_client do |config|
|
|
48
48
|
end
|
49
49
|
```
|
50
50
|
|
51
|
+
You should also set `SIDEKIQ_ENCRYPTION_KEY` to something sufficiently
|
52
|
+
random. The `openssl` tool is a good choice for this:
|
53
|
+
|
54
|
+
```sh
|
55
|
+
echo SIDEKIQ_ENCRYPTION_KEY=$(openssl rand -base64 32) >>.env
|
56
|
+
heroku config:set SIDEKIQ_ENCRYPTION_KEY=$(openssl rand -base64 32)
|
57
|
+
```
|
58
|
+
|
51
59
|
## Contributing
|
52
60
|
|
53
|
-
Pull requests gladly accepted. Please write tests for any code changes
|
61
|
+
Pull requests gladly accepted. Please write tests for any code changes,
|
62
|
+
though I'll admit my test coverage is completely awful right now.
|
54
63
|
|
55
64
|
## License
|
56
65
|
|
data/lib/sidekiq/encryptor.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
# stdlib
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
# gems
|
1
5
|
require 'sidekiq'
|
2
|
-
require '
|
3
|
-
require 'active_support/message_verifier'
|
6
|
+
require 'fernet'
|
4
7
|
|
8
|
+
# local files
|
5
9
|
require 'sidekiq/encryptor/version'
|
6
10
|
|
7
11
|
module Sidekiq
|
@@ -11,35 +15,125 @@ module Sidekiq
|
|
11
15
|
DecryptionError = Class.new(Error)
|
12
16
|
VersionChangeError = Class.new(DecryptionError)
|
13
17
|
|
18
|
+
module FernetAdapter
|
19
|
+
def self.encrypt(key, data)
|
20
|
+
Fernet.generate(Base64.urlsafe_encode64(key), data)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.decrypt(key, data)
|
24
|
+
verifier = Fernet::Verifier.new(
|
25
|
+
token: data,
|
26
|
+
secret: Base64.urlsafe_encode64(key),
|
27
|
+
enforce_ttl: false)
|
28
|
+
verifier.valid? ? verifier.message : nil
|
29
|
+
rescue OpenSSL::Cipher::CipherError
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
14
34
|
class Base
|
15
35
|
def initialize(options = {})
|
16
|
-
@key = options[:key]
|
17
|
-
@
|
36
|
+
@key = validate_key(compact_key(options[:key]))
|
37
|
+
@adapter = options[:adapter] || FernetAdapter
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect
|
41
|
+
"#<#{self.class.inspect}> @key=[masked] @adapter=#{@adapter.inspect}>"
|
42
|
+
end
|
43
|
+
alias to_s inspect
|
44
|
+
|
45
|
+
def enabled?
|
46
|
+
!@key.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def compact_key(key)
|
52
|
+
flat_key = key.to_s.delete("\r\n")
|
53
|
+
case flat_key
|
54
|
+
# empty
|
55
|
+
when ""
|
56
|
+
nil
|
57
|
+
# hexadecimal
|
58
|
+
when /^[\da-f]+$/i
|
59
|
+
[flat_key].pack('H*')
|
60
|
+
# base64
|
61
|
+
when /^[A-Za-z\d\+\/=]+$/
|
62
|
+
key.unpack('m*').first
|
63
|
+
# assume binary otherwise
|
64
|
+
else
|
65
|
+
key
|
66
|
+
end
|
18
67
|
end
|
68
|
+
|
69
|
+
def validate_key(key)
|
70
|
+
if key.nil?
|
71
|
+
$stderr.puts '[sidekiq-encryptor] ERROR: no key provided, encryption disabled'
|
72
|
+
elsif key.length < 32
|
73
|
+
$stderr.puts '[sidekiq-encryptor] ERROR: key length less than 256 bits, encryption disabled'
|
74
|
+
else
|
75
|
+
key[0,32]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
19
79
|
end
|
20
80
|
|
21
81
|
class Client < Base
|
22
82
|
def call(worker, msg, queue)
|
23
|
-
return yield unless
|
24
|
-
msg['args'] =
|
83
|
+
return yield unless enabled?
|
84
|
+
msg['args'] = payload(msg['args'])
|
25
85
|
yield
|
26
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def payload(input)
|
91
|
+
[
|
92
|
+
'Sidekiq::Encryptor',
|
93
|
+
Sidekiq::Encryptor::PROTOCOL_VERSION,
|
94
|
+
encrypt(input)
|
95
|
+
]
|
96
|
+
end
|
97
|
+
|
98
|
+
def encrypt(input)
|
99
|
+
@adapter.encrypt(@key, Sidekiq.dump_json(input))
|
100
|
+
end
|
101
|
+
|
27
102
|
end
|
28
103
|
|
29
104
|
class Server < Base
|
30
105
|
def call(worker, msg, queue)
|
31
|
-
return yield unless
|
32
|
-
|
33
|
-
|
106
|
+
return yield unless enabled?
|
107
|
+
msg['args'] = validate_and_decrypt(msg['args'])
|
108
|
+
yield
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def validate_and_decrypt(payload)
|
114
|
+
if encrypted?(payload)
|
115
|
+
if version_changed?(payload)
|
34
116
|
raise VersionChangeError, 'incompatible change detected'
|
35
117
|
else
|
36
|
-
|
118
|
+
data = decrypt(payload) or
|
119
|
+
raise DecryptionError, 'key not identical or data was corrupted'
|
120
|
+
Sidekiq.load_json(data)
|
37
121
|
end
|
38
122
|
end
|
39
|
-
yield
|
40
|
-
rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveSupport::MessageEncryptor::InvalidMessage
|
41
|
-
raise DecryptionError, 'key not identical or data was corrupted'
|
42
123
|
end
|
124
|
+
|
125
|
+
def encrypted?(input)
|
126
|
+
input.is_a?(Array) && input.size == 3 && input.first == 'Sidekiq::Encryptor'
|
127
|
+
end
|
128
|
+
|
129
|
+
def version_changed?(input)
|
130
|
+
input[1] != Sidekiq::Encryptor::PROTOCOL_VERSION
|
131
|
+
end
|
132
|
+
|
133
|
+
def decrypt(input)
|
134
|
+
@adapter.decrypt(@key, input[2])
|
135
|
+
end
|
136
|
+
|
43
137
|
end
|
44
138
|
|
45
139
|
end # Encryptor
|
data/sidekiq-encryptor.gemspec
CHANGED
@@ -11,13 +11,14 @@ Gem::Specification.new do |gem|
|
|
11
11
|
gem.description = %q{Sidekiq middleware that encrypts your job data into and out of Redis.}
|
12
12
|
gem.summary = %q{Sidekiq::Encryptor is a middleware for Sidekiq that keeps your information safe by using 2-way encryption when storing and retrieving jobs from Redis.}
|
13
13
|
gem.homepage = 'https://github.com/wuputah/sidekiq-encryptor'
|
14
|
+
gem.license = 'MIT'
|
14
15
|
|
15
16
|
gem.files = `git ls-files`.split($/)
|
16
17
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
19
|
gem.require_paths = ['lib']
|
19
20
|
|
20
|
-
gem.add_dependency '
|
21
|
+
gem.add_dependency 'fernet', '>= 2.0rc1', '< 3.0'
|
21
22
|
gem.add_dependency 'sidekiq', '>= 2.5', '< 3.0'
|
22
23
|
|
23
24
|
gem.add_development_dependency 'rake'
|
@@ -1,31 +1,59 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'securerandom'
|
2
3
|
|
3
4
|
[Sidekiq::Encryptor::Client, Sidekiq::Encryptor::Server].each do |klass|
|
4
5
|
|
5
6
|
describe klass do
|
6
7
|
|
7
|
-
|
8
|
-
described_class.new
|
9
|
-
end
|
8
|
+
raw_key = SecureRandom.random_bytes(32)
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
{
|
11
|
+
'base64' => [raw_key].pack('m*'),
|
12
|
+
'hex' => raw_key.unpack('H*').first,
|
13
|
+
'binary' => raw_key
|
14
|
+
}.each_pair do |key_type, key|
|
14
15
|
|
15
|
-
|
16
|
-
{
|
17
|
-
args: 'Clint Eastwood'
|
18
|
-
}
|
19
|
-
end
|
16
|
+
describe "with #{key_type} key" do
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
subject(:middleware) do
|
19
|
+
described_class.new(key: key)
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:worker) do
|
23
|
+
RegularWorker.new
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:data) do
|
27
|
+
['Clint Eastwood']
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:args) do
|
31
|
+
{
|
32
|
+
Sidekiq::Encryptor::Client => data,
|
33
|
+
Sidekiq::Encryptor::Server => [
|
34
|
+
'Sidekiq::Encryptor',
|
35
|
+
1,
|
36
|
+
Fernet.generate(Base64.urlsafe_encode64(raw_key), JSON.dump(data))
|
37
|
+
]
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:message) do
|
42
|
+
{ 'args' => args[klass] }
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:queue) do
|
46
|
+
'default'
|
47
|
+
end
|
48
|
+
|
49
|
+
it { should be_enabled }
|
24
50
|
|
25
|
-
|
51
|
+
describe '#call' do
|
26
52
|
|
27
|
-
|
28
|
-
|
53
|
+
it 'yields' do
|
54
|
+
expect { |b| middleware.call(worker, message, queue, &b) }.to yield_with_no_args
|
55
|
+
end
|
56
|
+
end
|
29
57
|
end
|
30
58
|
end
|
31
59
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -19,7 +19,7 @@ require 'sidekiq/encryptor'
|
|
19
19
|
# Autoload every worker for the test suite that sits in spec/app/workers
|
20
20
|
Dir[File.join(WORKERS, '*.rb')].sort.each do |file|
|
21
21
|
name = File.basename(file, '.rb')
|
22
|
-
|
22
|
+
require name
|
23
23
|
end
|
24
24
|
|
25
25
|
RSpec.configure do |config|
|
metadata
CHANGED
@@ -1,29 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-encryptor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Dance
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-08-
|
11
|
+
date: 2013-08-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: fernet
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.0rc1
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - '>='
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
29
|
+
version: 2.0rc1
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: sidekiq
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -265,6 +271,7 @@ files:
|
|
265
271
|
- .pryrc
|
266
272
|
- .travis.yml
|
267
273
|
- .yardopts
|
274
|
+
- CHANGELOG.md
|
268
275
|
- Gemfile
|
269
276
|
- Guardfile
|
270
277
|
- LICENSE.txt
|
@@ -280,7 +287,8 @@ files:
|
|
280
287
|
- spec/spec_helper.rb
|
281
288
|
- spec/support/sidekiq.rb
|
282
289
|
homepage: https://github.com/wuputah/sidekiq-encryptor
|
283
|
-
licenses:
|
290
|
+
licenses:
|
291
|
+
- MIT
|
284
292
|
metadata: {}
|
285
293
|
post_install_message:
|
286
294
|
rdoc_options: []
|