sidekiq-encryptor 0.0.1 → 0.1.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
  SHA1:
3
- metadata.gz: fbf6216993306970a0046e788f4b033a8565db1e
4
- data.tar.gz: db90b1555b00991e53aca580cb923872b5c5e99c
3
+ metadata.gz: cc401443775158ce8894d1474e2e68d9f72e0e3d
4
+ data.tar.gz: ad006a7f9a3b5969b524ac67e13b8e5b3657290f
5
5
  SHA512:
6
- metadata.gz: 5dc4b9b01365ad77d9c833bedc238c64c7a5a4a7e6e25d4ba0660f573f891f8082bedc1991b8da88ac1a92ae95f37211d7c8e94f3486c439ffb091ead339853c
7
- data.tar.gz: f7dc81e8b26fcd47c5d244a6fca04f23ae0ae34155140acbdf1a3e12a0a050af09142ce5d3beaad5670757a9791bbae6bcdedc85421d283409a9aa1ce500adff
6
+ metadata.gz: 6d8c9e5439e8223dc1c03ab4b7565520cad982f2707f8971dff14de3c0e748efa0874571c611a2381f1c66c124697ec499c85b432467837a6ffdc24c2a46397f
7
+ data.tar.gz: 1516b7eb16752e7526f4298ba6250cd104033ad777e1cb9edf7b92e8c8fa9d897319a5c8163269c3a3dbb2a665cb020dd166310e59c11986e6666a016d64f81c
data/.pryrc CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  require 'pathname'
6
6
  $LOAD_PATH.unshift(Pathname.getwd.join('lib').to_s)
7
- require 'sidekiq/throttler'
7
+ require 'sidekiq/encryptor'
8
8
 
9
9
  def reload!
10
10
  Dir["#{Dir.pwd}/lib/**/*.rb"].each { |f| load f }
data/.travis.yml CHANGED
@@ -8,9 +8,7 @@ branches:
8
8
  only:
9
9
  - master
10
10
  notifications:
11
- email:
12
- recipients:
13
- - gabe@ga.be
11
+ email: false
14
12
  matrix:
15
13
  allow_failures:
16
14
  - rvm: jruby-19mode
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
 
@@ -3,11 +3,13 @@ module Sidekiq
3
3
 
4
4
  module Version
5
5
  MAJOR = 0
6
- MINOR = 0
7
- PATCH = 1
6
+ MINOR = 1
7
+ PATCH = 0
8
8
  SUFFIX = ""
9
9
  end
10
10
 
11
+ PROTOCOL_VERSION = 1
12
+
11
13
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::PATCH}#{Version::SUFFIX}"
12
14
 
13
15
  end
@@ -1,7 +1,11 @@
1
+ # stdlib
2
+ require 'base64'
3
+
4
+ # gems
1
5
  require 'sidekiq'
2
- require 'active_support/message_encryptor'
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
- @encryptor = ActiveSupport::MessageEncryptor.new(@key) if @key
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 @key
24
- msg['args'] = ['Sidekiq::Encryptor', Sidekiq::Encryptor::Version::MAJOR, @encryptor.encrypt_and_sign(Sidekiq.dump_json(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 @key
32
- if msg['args'][0] == 'Sidekiq::Encryptor'
33
- if msg['args'][1] != Sidekiq::Encryptor::Version::MAJOR
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
- msg['args'] = Sidekiq.load_json(@encryptor.decrypt_and_verify(msg['args'][2]))
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
@@ -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 'activesupport'
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
- subject(:middleware) do
8
- described_class.new
9
- end
8
+ raw_key = SecureRandom.random_bytes(32)
10
9
 
11
- let(:worker) do
12
- RegularWorker.new
13
- end
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
- let(:message) do
16
- {
17
- args: 'Clint Eastwood'
18
- }
19
- end
16
+ describe "with #{key_type} key" do
20
17
 
21
- let(:queue) do
22
- 'default'
23
- end
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
- describe '#call' do
51
+ describe '#call' do
26
52
 
27
- it 'yields' do
28
- expect { |b| middleware.call(worker, message, queue, &b) }.to yield_with_no_args
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
- autoload name.camelize.to_sym, name
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.1
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-20 00:00:00.000000000 Z
11
+ date: 2013-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: fernet
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - '>='
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
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: '0'
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: []