sidekiq-field-encryptor 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +2 -0
- data/.github/workflows/test.yml +49 -0
- data/.rspec +2 -1
- data/.rubocop-https---raw-githubusercontent-com-aptible-dryer-lint-main--rubocop-base-yml +353 -0
- data/.rubocop.yml +16 -0
- data/.rubocop_ignore.yml +11 -0
- data/Gemfile +2 -0
- data/README.md +2 -3
- data/Rakefile +2 -3
- data/SECURITY.md +23 -0
- data/lib/sidekiq-field-encryptor/encryptor.rb +54 -7
- data/lib/sidekiq-field-encryptor/version.rb +3 -1
- data/lib/sidekiq-field-encryptor.rb +2 -0
- data/sidekiq-field-encryptor.gemspec +6 -5
- data/spec/sidekiq-field-encryptor/encryptor_spec.rb +55 -15
- data/spec/spec_helper.rb +2 -0
- metadata +22 -19
- data/.travis.yml +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 17ebe672dfa0a9ce9941c411ae902205222c07606a74cb4515fcd69bea950a00
|
4
|
+
data.tar.gz: 6e5a0a1942a16b0a59a04ff8b6f31d4a60637bf7036634286ed7892c726ec705
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 223e413322218a774fad26818770f62a3604e2461e8a3941f52f4670a211c1a0ccacdb57642a7abd8d79d34a25a0d49dd44d6dacd044808f6ff3b69580ced6b2
|
7
|
+
data.tar.gz: 0bccf7635de692edb8c21bf39dc86f15c7fb96015df2df2623f3636922b72ab7a7c822f99f2357eaae9245ffd6f525229594029cf8053eeb193779f3187a7a8e
|
data/.github/CODEOWNERS
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
- master
|
8
|
+
push:
|
9
|
+
branches:
|
10
|
+
- main
|
11
|
+
- master
|
12
|
+
|
13
|
+
jobs:
|
14
|
+
test:
|
15
|
+
name: Test Ruby ${{ matrix.RUBY_VERSION }}
|
16
|
+
runs-on: ubuntu-24.04
|
17
|
+
strategy:
|
18
|
+
fail-fast: false
|
19
|
+
matrix:
|
20
|
+
RUBY_VERSION: ["2.6", "3.3"]
|
21
|
+
steps:
|
22
|
+
- name: Check out code
|
23
|
+
uses: actions/checkout@v4
|
24
|
+
|
25
|
+
- name: Install Ruby
|
26
|
+
uses: ruby/setup-ruby@v1
|
27
|
+
with:
|
28
|
+
ruby-version: ${{ matrix.RUBY_VERSION }}
|
29
|
+
bundler-cache: true
|
30
|
+
|
31
|
+
- name: Run Lint
|
32
|
+
run: bundle exec rubocop
|
33
|
+
|
34
|
+
- name: Run Tests
|
35
|
+
run: bundle exec rspec
|
36
|
+
|
37
|
+
results:
|
38
|
+
if: ${{ always() }}
|
39
|
+
runs-on: ubuntu-latest
|
40
|
+
name: Final Results
|
41
|
+
needs: [test]
|
42
|
+
steps:
|
43
|
+
- run: exit 1
|
44
|
+
# see https://stackoverflow.com/a/67532120/4907315
|
45
|
+
if: >-
|
46
|
+
${{
|
47
|
+
contains(needs.*.result, 'failure')
|
48
|
+
|| contains(needs.*.result, 'cancelled')
|
49
|
+
}}
|
data/.rspec
CHANGED
@@ -0,0 +1,353 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rspec
|
3
|
+
|
4
|
+
Gemspec/DeprecatedAttributeAssignment:
|
5
|
+
Enabled: true
|
6
|
+
|
7
|
+
Gemspec/RequireMFA:
|
8
|
+
Enabled: true
|
9
|
+
|
10
|
+
Layout/LineContinuationLeadingSpace:
|
11
|
+
Enabled: true
|
12
|
+
|
13
|
+
Layout/LineContinuationSpacing:
|
14
|
+
Enabled: true
|
15
|
+
|
16
|
+
Layout/LineEndStringConcatenationIndentation:
|
17
|
+
Enabled: true
|
18
|
+
|
19
|
+
Layout/SpaceBeforeBrackets:
|
20
|
+
Enabled: true
|
21
|
+
|
22
|
+
Lint/AmbiguousAssignment:
|
23
|
+
Enabled: true
|
24
|
+
|
25
|
+
Lint/AmbiguousOperatorPrecedence:
|
26
|
+
Enabled: true
|
27
|
+
|
28
|
+
Lint/AmbiguousRange:
|
29
|
+
Enabled: true
|
30
|
+
|
31
|
+
Lint/ConstantOverwrittenInRescue:
|
32
|
+
Enabled: true
|
33
|
+
|
34
|
+
Lint/DeprecatedConstants:
|
35
|
+
Enabled: true
|
36
|
+
|
37
|
+
Lint/DuplicateBranch:
|
38
|
+
Enabled: true
|
39
|
+
|
40
|
+
Lint/DuplicateMagicComment:
|
41
|
+
Enabled: true
|
42
|
+
|
43
|
+
Lint/DuplicateRegexpCharacterClassElement:
|
44
|
+
Enabled: true
|
45
|
+
|
46
|
+
Lint/EmptyBlock:
|
47
|
+
Enabled: false
|
48
|
+
|
49
|
+
Lint/EmptyClass:
|
50
|
+
Enabled: true
|
51
|
+
|
52
|
+
Lint/EmptyInPattern:
|
53
|
+
Enabled: true
|
54
|
+
|
55
|
+
Lint/IncompatibleIoSelectWithFiberScheduler:
|
56
|
+
Enabled: true
|
57
|
+
|
58
|
+
Lint/LambdaWithoutLiteralBlock:
|
59
|
+
Enabled: true
|
60
|
+
|
61
|
+
Lint/NoReturnInBeginEndBlocks:
|
62
|
+
Enabled: true
|
63
|
+
|
64
|
+
Lint/NonAtomicFileOperation:
|
65
|
+
Enabled: true
|
66
|
+
|
67
|
+
Lint/NumberedParameterAssignment:
|
68
|
+
Enabled: true
|
69
|
+
|
70
|
+
Lint/OrAssignmentToConstant:
|
71
|
+
Enabled: true
|
72
|
+
|
73
|
+
Lint/RedundantDirGlobSort:
|
74
|
+
Enabled: true
|
75
|
+
|
76
|
+
Lint/RefinementImportMethods:
|
77
|
+
Enabled: true
|
78
|
+
|
79
|
+
Lint/RequireRangeParentheses:
|
80
|
+
Enabled: true
|
81
|
+
|
82
|
+
Lint/RequireRelativeSelfPath:
|
83
|
+
Enabled: true
|
84
|
+
|
85
|
+
Lint/SymbolConversion:
|
86
|
+
Enabled: true
|
87
|
+
|
88
|
+
Lint/ToEnumArguments:
|
89
|
+
Enabled: true
|
90
|
+
|
91
|
+
Lint/TripleQuotes:
|
92
|
+
Enabled: true
|
93
|
+
|
94
|
+
Lint/UnexpectedBlockArity:
|
95
|
+
Enabled: true
|
96
|
+
|
97
|
+
Lint/UnmodifiedReduceAccumulator:
|
98
|
+
Enabled: true
|
99
|
+
|
100
|
+
Lint/UselessRuby2Keywords:
|
101
|
+
Enabled: true
|
102
|
+
|
103
|
+
Security/CompoundHash:
|
104
|
+
Enabled: true
|
105
|
+
|
106
|
+
Security/IoMethods:
|
107
|
+
Enabled: true
|
108
|
+
|
109
|
+
Style/ArgumentsForwarding:
|
110
|
+
Enabled: true
|
111
|
+
|
112
|
+
Style/CollectionCompact:
|
113
|
+
Enabled: true
|
114
|
+
|
115
|
+
Style/DocumentDynamicEvalDefinition:
|
116
|
+
Enabled: true
|
117
|
+
|
118
|
+
Style/EmptyHeredoc:
|
119
|
+
Enabled: true
|
120
|
+
|
121
|
+
Style/EndlessMethod:
|
122
|
+
Enabled: true
|
123
|
+
|
124
|
+
Style/EnvHome:
|
125
|
+
Enabled: true
|
126
|
+
|
127
|
+
Style/FetchEnvVar:
|
128
|
+
Enabled: true
|
129
|
+
|
130
|
+
Style/FileRead:
|
131
|
+
Enabled: true
|
132
|
+
|
133
|
+
Style/FileWrite:
|
134
|
+
Enabled: true
|
135
|
+
|
136
|
+
# TODO - remove this when this has been released on rubocop
|
137
|
+
Style/GuardClause:
|
138
|
+
Enabled: false
|
139
|
+
|
140
|
+
Style/HashConversion:
|
141
|
+
Enabled: true
|
142
|
+
|
143
|
+
Style/HashExcept:
|
144
|
+
Enabled: true
|
145
|
+
|
146
|
+
Style/IfWithBooleanLiteralBranches:
|
147
|
+
Enabled: true
|
148
|
+
|
149
|
+
Style/InPatternThen:
|
150
|
+
Enabled: true
|
151
|
+
|
152
|
+
Style/MagicCommentFormat:
|
153
|
+
Enabled: true
|
154
|
+
|
155
|
+
Style/MapCompactWithConditionalBlock:
|
156
|
+
Enabled: true
|
157
|
+
|
158
|
+
Style/MapToHash:
|
159
|
+
Enabled: true
|
160
|
+
|
161
|
+
Style/MultilineInPatternThen:
|
162
|
+
Enabled: true
|
163
|
+
|
164
|
+
Style/NegatedIfElseCondition:
|
165
|
+
Enabled: true
|
166
|
+
|
167
|
+
Style/NestedFileDirname:
|
168
|
+
Enabled: true
|
169
|
+
|
170
|
+
Style/NilLambda:
|
171
|
+
Enabled: true
|
172
|
+
|
173
|
+
Style/NumberedParameters:
|
174
|
+
Enabled: true
|
175
|
+
|
176
|
+
Style/NumberedParametersLimit:
|
177
|
+
Enabled: true
|
178
|
+
|
179
|
+
Style/NumericLiterals:
|
180
|
+
Enabled: false
|
181
|
+
|
182
|
+
Style/ObjectThen:
|
183
|
+
Enabled: true
|
184
|
+
|
185
|
+
Style/OpenStructUse:
|
186
|
+
Enabled: true
|
187
|
+
|
188
|
+
Style/OperatorMethodCall:
|
189
|
+
Enabled: true
|
190
|
+
|
191
|
+
Style/QuotedSymbols:
|
192
|
+
Enabled: true
|
193
|
+
|
194
|
+
Style/RedundantArgument:
|
195
|
+
Enabled: true
|
196
|
+
|
197
|
+
Style/RedundantEach:
|
198
|
+
Enabled: true
|
199
|
+
|
200
|
+
Style/RedundantInitialize:
|
201
|
+
Enabled: true
|
202
|
+
|
203
|
+
Style/RedundantSelfAssignmentBranch:
|
204
|
+
Enabled: true
|
205
|
+
|
206
|
+
Style/RedundantStringEscape:
|
207
|
+
Enabled: true
|
208
|
+
|
209
|
+
Style/SelectByRegexp:
|
210
|
+
Enabled: true
|
211
|
+
|
212
|
+
Style/StringChars:
|
213
|
+
Enabled: true
|
214
|
+
|
215
|
+
Style/SwapValues:
|
216
|
+
Enabled: true
|
217
|
+
|
218
|
+
RSpec/BeEq:
|
219
|
+
Enabled: true
|
220
|
+
|
221
|
+
RSpec/BeNil:
|
222
|
+
Enabled: true
|
223
|
+
|
224
|
+
RSpec/ChangeByZero:
|
225
|
+
Enabled: true
|
226
|
+
|
227
|
+
RSpec/ClassCheck:
|
228
|
+
Enabled: true
|
229
|
+
|
230
|
+
RSpec/ExcessiveDocstringSpacing:
|
231
|
+
Enabled: true
|
232
|
+
|
233
|
+
RSpec/IdenticalEqualityAssertion:
|
234
|
+
Enabled: true
|
235
|
+
|
236
|
+
RSpec/SortMetadata:
|
237
|
+
Enabled: true
|
238
|
+
|
239
|
+
RSpec/SubjectDeclaration:
|
240
|
+
Enabled: true
|
241
|
+
|
242
|
+
RSpec/StubbedMock:
|
243
|
+
Enabled: false
|
244
|
+
|
245
|
+
RSpec/VerifiedDoubleReference:
|
246
|
+
Enabled: true
|
247
|
+
|
248
|
+
###############################################################################
|
249
|
+
## Customizations ##
|
250
|
+
###############################################################################
|
251
|
+
|
252
|
+
# Try to keep a balance between the fact that some people are
|
253
|
+
# working from laptops and the fact that unnecessarily short
|
254
|
+
# lines make code hard to read.
|
255
|
+
Layout/LineLength:
|
256
|
+
Enabled: true
|
257
|
+
Max: 120
|
258
|
+
|
259
|
+
# TODO: We have enough places where this isn't used that it would
|
260
|
+
# be hard to switch, but maybe we should enable in the future?
|
261
|
+
RSpec/NamedSubject:
|
262
|
+
Enabled: false
|
263
|
+
|
264
|
+
# Sometimes our tests are only confirming that
|
265
|
+
# an exception isn't raised.
|
266
|
+
RSpec/NoExpectationExample:
|
267
|
+
Enabled: false
|
268
|
+
|
269
|
+
# These sometimes make tests look nicer.
|
270
|
+
RSpec/NestedGroups:
|
271
|
+
Enabled: false
|
272
|
+
|
273
|
+
RSpec/RepeatedExampleGroupBody:
|
274
|
+
Enabled: false
|
275
|
+
|
276
|
+
RSpec/MultipleMemoizedHelpers:
|
277
|
+
Max: 20
|
278
|
+
|
279
|
+
RSpec/SubjectStub:
|
280
|
+
Enabled: false
|
281
|
+
|
282
|
+
RSpec/MessageSpies:
|
283
|
+
Enabled: false
|
284
|
+
|
285
|
+
RSpec/ExpectInHook:
|
286
|
+
Enabled: false
|
287
|
+
|
288
|
+
RSpec/MultipleExpectations:
|
289
|
+
Enabled: false
|
290
|
+
|
291
|
+
RSpec/ExampleLength:
|
292
|
+
Enabled: false
|
293
|
+
|
294
|
+
# These aren't effective enough at calculating the right
|
295
|
+
# kinds of complexity.
|
296
|
+
Metrics/CyclomaticComplexity:
|
297
|
+
Enabled: false
|
298
|
+
|
299
|
+
Metrics/AbcSize:
|
300
|
+
Enabled: false
|
301
|
+
|
302
|
+
Metrics/PerceivedComplexity:
|
303
|
+
Enabled: false
|
304
|
+
|
305
|
+
# Favor explicit over implicit.
|
306
|
+
Metrics/ParameterLists:
|
307
|
+
Max: 20
|
308
|
+
|
309
|
+
Style/HashSyntax:
|
310
|
+
Enabled: false
|
311
|
+
|
312
|
+
Naming/BlockForwarding:
|
313
|
+
Enabled: true
|
314
|
+
EnforcedStyle: explicit
|
315
|
+
|
316
|
+
# This probably isn't right to disable, but at the same time, there
|
317
|
+
# are places where we're using (valid) boilerplate code that adds up.
|
318
|
+
Metrics/MethodLength:
|
319
|
+
Enabled: false
|
320
|
+
|
321
|
+
# This probably isn't best to change either, but some modules and corresponding
|
322
|
+
# helpers will have a fair number of lines and this seems arbitrarily limiting
|
323
|
+
Metrics/ModuleLength:
|
324
|
+
Enabled: false
|
325
|
+
|
326
|
+
# We may want to re-enable this in the future with a reasonable value,
|
327
|
+
# but right now this is overly prescriptive, especially in scenarios
|
328
|
+
# where we have a lot of boilerplate code.
|
329
|
+
Metrics/ClassLength:
|
330
|
+
Enabled: false
|
331
|
+
|
332
|
+
# This is fine being a judgement call.
|
333
|
+
Metrics/BlockLength:
|
334
|
+
Enabled: false
|
335
|
+
|
336
|
+
# This is overly prescriptive with minimal gain.
|
337
|
+
RSpec/ContextWording:
|
338
|
+
Enabled: false
|
339
|
+
|
340
|
+
# This is allowing something implicit, which isn't ideal,
|
341
|
+
# but it's also something we heavily leverage.
|
342
|
+
RSpec/LetSetup:
|
343
|
+
Enabled: false
|
344
|
+
|
345
|
+
# This doesn't feel great to add, but unfortunately it's making
|
346
|
+
# incorrect assumptions about some of our function names
|
347
|
+
# which causes a lot of false positives.
|
348
|
+
RSpec/PredicateMatcher:
|
349
|
+
Enabled: false
|
350
|
+
|
351
|
+
# There are valid reasons to do this.
|
352
|
+
RSpec/InstanceVariable:
|
353
|
+
Enabled: false
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
inherit_from:
|
2
|
+
- https://raw.githubusercontent.com/aptible/dryer-lint/main/.rubocop.base.yml
|
3
|
+
- .rubocop_ignore.yml
|
4
|
+
|
5
|
+
Style/GuardClause:
|
6
|
+
Enabled: false
|
7
|
+
|
8
|
+
AllCops:
|
9
|
+
TargetRubyVersion: 3.1
|
10
|
+
NewCops: disable
|
11
|
+
Include:
|
12
|
+
- !ruby/regexp /\.rb$/
|
13
|
+
- !ruby/regexp /Gemfile$/
|
14
|
+
- !ruby/regexp /\.gemspec$/
|
15
|
+
- !ruby/regexp /\.rake$/
|
16
|
+
- !ruby/regexp /Rakefile$/
|
data/.rubocop_ignore.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# ![](https://raw.github.com/aptible/straptible/master/lib/straptible/rails/templates/public.api/icon-60px.png) Sidekiq::Field::Encryptor
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/sidekiq-field-encryptor.png)](https://rubygems.org/gems/sidekiq-field-encryptor)
|
4
|
-
[![Build Status](https://travis-ci.org/aptible/sidekiq-field-encryptor.png?branch=master)](https://travis-ci.org/aptible/sidekiq-field-encryptor)
|
5
4
|
[![Dependency Status](https://gemnasium.com/aptible/sidekiq-field-encryptor.png)](https://gemnasium.com/aptible/sidekiq-field-encryptor)
|
6
5
|
|
7
6
|
This is a utility which is intended to be used for encrypting sensitive data in Sidekiq jobs. The data is encrypted before sending it to Redis, and decrypted right before the Sidekiq job is executed.
|
@@ -39,11 +38,11 @@ decrypt the values inside the client before the job is executed.
|
|
39
38
|
|
40
39
|
1. Fork the project.
|
41
40
|
1. Commit your changes, with specs.
|
42
|
-
1. Ensure that your code passes specs (`
|
41
|
+
1. Ensure that your code passes specs (`bundle exec rspec`) and meets Aptible's Ruby style guide (`bundle exec rubocop`).
|
43
42
|
1. Create a new pull request on GitHub.
|
44
43
|
|
45
44
|
## Copyright and License
|
46
45
|
|
47
46
|
MIT License, see [LICENSE](LICENSE.md) for details.
|
48
47
|
|
49
|
-
Copyright (c)
|
48
|
+
Copyright (c) 2024 [Aptible](https://www.aptible.com), Blake Pettersson, and contributors.
|
data/Rakefile
CHANGED
data/SECURITY.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Aptible Open Source Security Policies and Procedures
|
2
|
+
|
3
|
+
This document outlines security procedures and general policies for the Aptible open source projects as found on https://github.com/aptible.
|
4
|
+
|
5
|
+
* [Reporting a Vulnerability](#reporting-a-vulnerability)
|
6
|
+
* [Responsible Disclosure Policy](#responsible-disclosure-policy)
|
7
|
+
|
8
|
+
## Reporting a Vulnerability
|
9
|
+
|
10
|
+
The Aptible team and community take all security vulnerabilities
|
11
|
+
seriously. Thank you for improving the security of our open source software. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
|
12
|
+
|
13
|
+
Report security vulnerabilities by emailing the Aptible security team at:
|
14
|
+
|
15
|
+
security@aptible.com
|
16
|
+
|
17
|
+
Security researchers can also privately report security vulnerabilities to repository maintainers using the GitHub "Report a Vulnerability" feature. [See how-to here](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability).
|
18
|
+
|
19
|
+
The Aptible team will acknowledge your email within 24 business hours and send a detailed response within 48 business hours indicating the next steps in handling your report. The Aptible security team will keep you informed of the progress and may ask for additional information or guidance.
|
20
|
+
|
21
|
+
## Responsible Disclosure Policy
|
22
|
+
|
23
|
+
Please see Aptible's Responsible Disclosure Policy here: https://www.aptible.com/legal/responsible-disclosure/
|
@@ -1,5 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'base64'
|
2
4
|
require 'encryptor'
|
5
|
+
require 'json'
|
3
6
|
require 'sidekiq-field-encryptor/version'
|
4
7
|
|
5
8
|
# This middleware configures encryption of any fields that can contain sensitive
|
@@ -21,52 +24,95 @@ require 'sidekiq-field-encryptor/version'
|
|
21
24
|
# Will encrypt the values {'x' => 1} and 'b' when storing the job in Redis and
|
22
25
|
# decrypt the values inside the client before the job is executed.
|
23
26
|
module SidekiqFieldEncryptor
|
27
|
+
SERIALIZE_JSON = 'json'
|
28
|
+
SERIALIZE_MARHSALL = 'marshal'
|
29
|
+
|
30
|
+
# Shared methods between client and base
|
24
31
|
class Base
|
25
32
|
def initialize(options = {})
|
26
33
|
@encryption_key = options[:encryption_key]
|
27
34
|
@encrypted_fields = options[:encrypted_fields] || {}
|
28
35
|
@encryption_algorithm = options[:encryption_algorithm] || 'aes-256-gcm'
|
36
|
+
|
37
|
+
@serialization_method = options[:serialization_method] || SERIALIZE_JSON
|
38
|
+
@serialization_compat = options[:serialization_compat]
|
29
39
|
end
|
30
40
|
|
31
41
|
def assert_key_configured
|
32
42
|
raise 'Encryption key not configured' if @encryption_key.nil?
|
33
43
|
end
|
34
44
|
|
45
|
+
def serialize(value)
|
46
|
+
case @serialization_method
|
47
|
+
when SERIALIZE_JSON
|
48
|
+
JSON.generate(value, quirks_mode: true)
|
49
|
+
when SERIALIZE_MARHSALL
|
50
|
+
Marshal.dump(value)
|
51
|
+
else
|
52
|
+
raise "Invalid serialization_method: #{@serialization_method}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def deserialize(method, value)
|
57
|
+
if !@serialization_compat && method != @serialization_method
|
58
|
+
raise "Invalid serialization_method received: #{method}"
|
59
|
+
end
|
60
|
+
|
61
|
+
case method
|
62
|
+
when SERIALIZE_JSON
|
63
|
+
JSON.parse(value, quirks_mode: true)
|
64
|
+
when SERIALIZE_MARHSALL, nil
|
65
|
+
# No method used to be Marshall, so we respect this here
|
66
|
+
Marshal.load(value) # rubocop:disable Security/MarshalLoad
|
67
|
+
else
|
68
|
+
raise "Invalid serialization_method: #{@serialization_method}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
35
72
|
def encrypt(value)
|
36
|
-
plaintext =
|
37
|
-
iv = OpenSSL::Cipher
|
73
|
+
plaintext = serialize(value)
|
74
|
+
iv = OpenSSL::Cipher.new(@encryption_algorithm).random_iv
|
38
75
|
args = { key: @encryption_key, iv: iv, algorithm: @encryption_algorithm }
|
39
76
|
ciphertext = ::Encryptor.encrypt(plaintext, **args)
|
40
|
-
[
|
77
|
+
[
|
78
|
+
::Base64.encode64(ciphertext),
|
79
|
+
::Base64.encode64(iv),
|
80
|
+
@serialization_method
|
81
|
+
]
|
41
82
|
end
|
42
83
|
|
43
84
|
def decrypt(encrypted)
|
44
|
-
|
85
|
+
base64_ciphertext, base64_iv, serialization_method = encrypted
|
86
|
+
ciphertext = ::Base64.decode64(base64_ciphertext)
|
87
|
+
iv = ::Base64.decode64(base64_iv)
|
45
88
|
args = { key: @encryption_key, iv: iv, algorithm: @encryption_algorithm }
|
46
89
|
plaintext = ::Encryptor.decrypt(ciphertext, **args)
|
47
|
-
|
90
|
+
deserialize(serialization_method, plaintext)
|
48
91
|
end
|
49
92
|
|
50
93
|
def process_message(message)
|
51
94
|
fields = @encrypted_fields[message['class']]
|
52
95
|
return unless fields
|
96
|
+
|
53
97
|
assert_key_configured
|
54
98
|
message['args'].size.times.each do |arg_index|
|
55
99
|
to_encrypt = fields[arg_index]
|
56
100
|
next unless to_encrypt
|
101
|
+
|
57
102
|
raw_value = message['args'][arg_index]
|
58
103
|
if to_encrypt == true
|
59
104
|
message['args'][arg_index] = yield(raw_value)
|
60
105
|
elsif to_encrypt.is_a?(Array) && raw_value.is_a?(Hash)
|
61
|
-
message['args'][arg_index] =
|
106
|
+
message['args'][arg_index] = raw_value.to_h do |key, value|
|
62
107
|
value = yield(value) if to_encrypt.member?(key.to_s)
|
63
108
|
[key, value]
|
64
|
-
end
|
109
|
+
end
|
65
110
|
end
|
66
111
|
end
|
67
112
|
end
|
68
113
|
end
|
69
114
|
|
115
|
+
# Used when encrypting fields
|
70
116
|
class Client < Base
|
71
117
|
def call(_, message, _, _)
|
72
118
|
process_message(message) { |value| encrypt(value) }
|
@@ -74,6 +120,7 @@ module SidekiqFieldEncryptor
|
|
74
120
|
end
|
75
121
|
end
|
76
122
|
|
123
|
+
# Used when decrypting fields
|
77
124
|
class Server < Base
|
78
125
|
def call(_, message, _)
|
79
126
|
process_message(message) { |value| decrypt(value) }
|
@@ -1,5 +1,6 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
|
5
6
|
require 'English'
|
@@ -16,13 +17,13 @@ Gem::Specification.new do |spec|
|
|
16
17
|
spec.license = 'MIT'
|
17
18
|
|
18
19
|
spec.files = `git ls-files`.split($RS)
|
19
|
-
spec.test_files = spec.files.grep(%r{^spec/})
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
22
|
spec.add_dependency 'encryptor'
|
23
23
|
|
24
24
|
spec.add_development_dependency 'bundler'
|
25
|
-
spec.add_development_dependency 'aptible-tasks'
|
26
25
|
spec.add_development_dependency 'rake'
|
27
|
-
spec.add_development_dependency 'rspec'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.12'
|
27
|
+
spec.add_development_dependency 'rubocop-rspec'
|
28
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
28
29
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
describe SidekiqFieldEncryptor::Client do
|
4
|
-
let(:key) { OpenSSL::Cipher
|
6
|
+
let(:key) { OpenSSL::Cipher.new('aes-256-cbc').random_key }
|
5
7
|
let(:message) do
|
6
8
|
{ 'class' => 'FooJob', 'args' => [1, 2, { 'a' => 'A', 'b' => 'B' }] }
|
7
9
|
end
|
@@ -10,8 +12,9 @@ describe SidekiqFieldEncryptor::Client do
|
|
10
12
|
it "doesn't fail when encryption isn't attempted" do
|
11
13
|
subject.call('FooJob', message, nil, nil) {}
|
12
14
|
end
|
15
|
+
|
13
16
|
it 'fails when encryption is attempted' do
|
14
|
-
client =
|
17
|
+
client = described_class.new(
|
15
18
|
encrypted_fields: { 'FooJob' => { 1 => true } }
|
16
19
|
)
|
17
20
|
expect { client.call('FooJob', message, nil, nil) {} }
|
@@ -21,9 +24,9 @@ describe SidekiqFieldEncryptor::Client do
|
|
21
24
|
|
22
25
|
describe 'with an encryption key' do
|
23
26
|
subject do
|
24
|
-
|
27
|
+
described_class.new(
|
25
28
|
encryption_key: key,
|
26
|
-
encrypted_fields: { 'FooJob' => { 1 => true, 2 => %w
|
29
|
+
encrypted_fields: { 'FooJob' => { 1 => true, 2 => %w[b d] } }
|
27
30
|
)
|
28
31
|
end
|
29
32
|
|
@@ -37,16 +40,16 @@ describe SidekiqFieldEncryptor::Client do
|
|
37
40
|
end
|
38
41
|
|
39
42
|
it 'supports setting the encryption algorithm' do
|
40
|
-
key = OpenSSL::Cipher
|
41
|
-
fields = { 'FooJob' => { 1 => true, 2 => %w
|
43
|
+
key = OpenSSL::Cipher.new('aes-128-cbc').random_key
|
44
|
+
fields = { 'FooJob' => { 1 => true, 2 => %w[b d] } }
|
42
45
|
|
43
|
-
ko =
|
46
|
+
ko = described_class.new(
|
44
47
|
encryption_key: key,
|
45
48
|
encryption_algorithm: 'aes-256-cbc',
|
46
49
|
encrypted_fields: fields
|
47
50
|
)
|
48
51
|
|
49
|
-
ok =
|
52
|
+
ok = described_class.new(
|
50
53
|
encryption_key: key,
|
51
54
|
encryption_algorithm: 'aes-128-cbc',
|
52
55
|
encrypted_fields: fields
|
@@ -60,7 +63,7 @@ describe SidekiqFieldEncryptor::Client do
|
|
60
63
|
end
|
61
64
|
|
62
65
|
describe SidekiqFieldEncryptor::Server do
|
63
|
-
let(:key) { OpenSSL::Cipher
|
66
|
+
let(:key) { OpenSSL::Cipher.new('aes-256-cbc').random_key }
|
64
67
|
let(:message) do
|
65
68
|
{ 'class' => 'FooJob', 'args' => [1, 2, { 'a' => 'A', 'b' => 'B' }] }
|
66
69
|
end
|
@@ -69,8 +72,9 @@ describe SidekiqFieldEncryptor::Server do
|
|
69
72
|
it "doesn't fail when decryption isn't attempted" do
|
70
73
|
subject.call('FooJob', message, nil) {}
|
71
74
|
end
|
75
|
+
|
72
76
|
it 'fails when decryption is attempted' do
|
73
|
-
server =
|
77
|
+
server = described_class.new(
|
74
78
|
encrypted_fields: { 'FooJob' => { 1 => true } }
|
75
79
|
)
|
76
80
|
expect { server.call('FooJob', message, nil) {} }
|
@@ -80,9 +84,9 @@ describe SidekiqFieldEncryptor::Server do
|
|
80
84
|
|
81
85
|
describe 'with an encryption key' do
|
82
86
|
subject do
|
83
|
-
|
87
|
+
described_class.new(
|
84
88
|
encryption_key: key,
|
85
|
-
encrypted_fields: { 'FooJob' => { 1 => true, 2 => %w
|
89
|
+
encrypted_fields: { 'FooJob' => { 1 => true, 2 => %w[b d] } }
|
86
90
|
)
|
87
91
|
end
|
88
92
|
|
@@ -95,16 +99,16 @@ describe SidekiqFieldEncryptor::Server do
|
|
95
99
|
end
|
96
100
|
|
97
101
|
it 'supports setting the encryption algorithm' do
|
98
|
-
key = OpenSSL::Cipher
|
102
|
+
key = OpenSSL::Cipher.new('aes-128-cbc').random_key
|
99
103
|
fields = { 'FooJob' => { 1 => true } }
|
100
104
|
|
101
|
-
ko =
|
105
|
+
ko = described_class.new(
|
102
106
|
encryption_key: key,
|
103
107
|
encryption_algorithm: 'aes-256-cbc',
|
104
108
|
encrypted_fields: fields
|
105
109
|
)
|
106
110
|
|
107
|
-
ok =
|
111
|
+
ok = described_class.new(
|
108
112
|
encryption_key: key,
|
109
113
|
encryption_algorithm: 'aes-128-cbc',
|
110
114
|
encrypted_fields: fields
|
@@ -117,5 +121,41 @@ describe SidekiqFieldEncryptor::Server do
|
|
117
121
|
|
118
122
|
ok.call('FooJob', message, nil) {}
|
119
123
|
end
|
124
|
+
|
125
|
+
it 'fails if the serialization methods are different' do
|
126
|
+
r = described_class.new(
|
127
|
+
encryption_key: key,
|
128
|
+
encrypted_fields: { 'FooJob' => { 1 => true } },
|
129
|
+
serialization_method: SidekiqFieldEncryptor::SERIALIZE_JSON
|
130
|
+
)
|
131
|
+
|
132
|
+
e = SidekiqFieldEncryptor::Client.new(
|
133
|
+
encryption_key: key,
|
134
|
+
encrypted_fields: { 'FooJob' => { 1 => true } },
|
135
|
+
serialization_method: SidekiqFieldEncryptor::SERIALIZE_MARHSALL
|
136
|
+
)
|
137
|
+
|
138
|
+
message['args'][1] = e.encrypt(message['args'][1])
|
139
|
+
expect { r.call('FooJob', message, nil) {} }
|
140
|
+
.to raise_error(/invalid serialization_method/i)
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'allows compat serialization' do
|
144
|
+
r = described_class.new(
|
145
|
+
encryption_key: key,
|
146
|
+
encrypted_fields: { 'FooJob' => { 1 => true } },
|
147
|
+
serialization_method: SidekiqFieldEncryptor::SERIALIZE_JSON,
|
148
|
+
serialization_compat: true
|
149
|
+
)
|
150
|
+
|
151
|
+
e = SidekiqFieldEncryptor::Client.new(
|
152
|
+
encryption_key: key,
|
153
|
+
encrypted_fields: { 'FooJob' => { 1 => true } },
|
154
|
+
serialization_method: SidekiqFieldEncryptor::SERIALIZE_MARHSALL
|
155
|
+
)
|
156
|
+
|
157
|
+
message['args'][1] = e.encrypt(message['args'][1])
|
158
|
+
r.call('FooJob', message, nil) {}
|
159
|
+
end
|
120
160
|
end
|
121
161
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq-field-encryptor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Blake Pettersson
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: encryptor
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -53,21 +53,21 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '3.12'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '3.12'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name: rspec
|
70
|
+
name: rubocop-rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -87,13 +87,18 @@ executables: []
|
|
87
87
|
extensions: []
|
88
88
|
extra_rdoc_files: []
|
89
89
|
files:
|
90
|
+
- ".github/CODEOWNERS"
|
91
|
+
- ".github/workflows/test.yml"
|
90
92
|
- ".gitignore"
|
91
93
|
- ".rspec"
|
92
|
-
- ".
|
94
|
+
- ".rubocop-https---raw-githubusercontent-com-aptible-dryer-lint-main--rubocop-base-yml"
|
95
|
+
- ".rubocop.yml"
|
96
|
+
- ".rubocop_ignore.yml"
|
93
97
|
- Gemfile
|
94
98
|
- LICENSE.md
|
95
99
|
- README.md
|
96
100
|
- Rakefile
|
101
|
+
- SECURITY.md
|
97
102
|
- lib/sidekiq-field-encryptor.rb
|
98
103
|
- lib/sidekiq-field-encryptor/encryptor.rb
|
99
104
|
- lib/sidekiq-field-encryptor/version.rb
|
@@ -103,8 +108,9 @@ files:
|
|
103
108
|
homepage: https://github.com/aptible/sidekiq-field-encryptor
|
104
109
|
licenses:
|
105
110
|
- MIT
|
106
|
-
metadata:
|
107
|
-
|
111
|
+
metadata:
|
112
|
+
rubygems_mfa_required: 'true'
|
113
|
+
post_install_message:
|
108
114
|
rdoc_options: []
|
109
115
|
require_paths:
|
110
116
|
- lib
|
@@ -119,11 +125,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
125
|
- !ruby/object:Gem::Version
|
120
126
|
version: '0'
|
121
127
|
requirements: []
|
122
|
-
|
123
|
-
|
124
|
-
signing_key:
|
128
|
+
rubygems_version: 3.5.3
|
129
|
+
signing_key:
|
125
130
|
specification_version: 4
|
126
131
|
summary: Selectively encrypt fields sent into Sidekiq
|
127
|
-
test_files:
|
128
|
-
- spec/sidekiq-field-encryptor/encryptor_spec.rb
|
129
|
-
- spec/spec_helper.rb
|
132
|
+
test_files: []
|
data/.travis.yml
DELETED