logasm 0.9.0 → 0.9.1
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/README.md +14 -6
- data/benchmark/whitelisting.rb +38 -37
- data/lib/logasm/preprocessors/blacklist.rb +3 -2
- data/lib/logasm/preprocessors/strategies/mask.rb +43 -0
- data/lib/logasm/preprocessors/strategies/prune.rb +44 -0
- data/lib/logasm/preprocessors/whitelist.rb +16 -30
- data/logasm.gemspec +1 -1
- data/spec/preprocessors/whitelist_spec.rb +135 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 976cb0da3fd9228fcfc8131274424fa54ea76117
|
4
|
+
data.tar.gz: 638a7ed3dee8b7756b97fbc4f45c08cc2ddb6993
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53b084eeb0aec7ebaa7830d8677b295d9a4b4c31b472929df8e6c35b8ef54c7719d08879e8653335e4c4cae0e80b3cfd26d3640167e8ac8f24b08e7893150efc
|
7
|
+
data.tar.gz: c054f7d41cb28d47abc5610368e9a5a918c8bd5a2d62abe07dd133c1edd8fba706c6437b9ef3c4baf96cd3f92d9c9eb9e9dd782788668be4673dd0b2f3a0aff8
|
data/README.md
CHANGED
@@ -73,9 +73,10 @@ You can specify the name of the field and which action to take on it.
|
|
73
73
|
Nested hashes of any level are preprocessed as well.
|
74
74
|
|
75
75
|
Available actions:
|
76
|
-
|
77
|
-
*
|
78
|
-
* mask - replaces every character from the original value with `*`.
|
76
|
+
|
77
|
+
* `prune` (default) - fully excludes the field and its value from the hash.
|
78
|
+
* `mask` - replaces every character from the original value with `*`.
|
79
|
+
In case of `array`, `hash` or `boolean` value is replaced with one `*`.
|
79
80
|
|
80
81
|
#### Configuration
|
81
82
|
|
@@ -85,7 +86,7 @@ preprocessors:
|
|
85
86
|
fields:
|
86
87
|
- key: password
|
87
88
|
- key: phone
|
88
|
-
|
89
|
+
action: mask
|
89
90
|
```
|
90
91
|
|
91
92
|
#### Usage
|
@@ -106,16 +107,23 @@ Received request {"info":{"phone":"************"}}
|
|
106
107
|
|
107
108
|
### Whitelist
|
108
109
|
|
109
|
-
|
110
|
+
Prunes or masks all the fields except those whitelisted in the configuration using [JSON Pointer](https://tools.ietf.org/html/rfc6901).
|
110
111
|
Only simple values(`string`, `number`, `boolean`) can be whitelisted.
|
111
112
|
Whitelisting array and hash elements can be done using wildcard symbol `~`.
|
112
113
|
|
114
|
+
Available actions:
|
115
|
+
|
116
|
+
* `mask` (default) - replaces every character from the original value with `*`.
|
117
|
+
In case of `array`, `hash` or `boolean` value is replaced with one `*`.
|
118
|
+
* `prune` - fully excludes the field and its value from the hash.
|
119
|
+
|
113
120
|
#### Configuration
|
114
121
|
|
115
122
|
```yaml
|
116
123
|
preprocessors:
|
117
124
|
whitelist:
|
118
125
|
pointers: ['/info/phone', '/addresses/~/host']
|
126
|
+
action: prune
|
119
127
|
```
|
120
128
|
|
121
129
|
#### Usage
|
@@ -132,4 +140,4 @@ Logger output:
|
|
132
140
|
|
133
141
|
```
|
134
142
|
Received request {password: "********", "info": {"phone": "+12055555555"}, "addresses": [{"host": "example.com","path": "****"}]}
|
135
|
-
```
|
143
|
+
```
|
data/benchmark/whitelisting.rb
CHANGED
@@ -10,43 +10,44 @@ pointers = %w[
|
|
10
10
|
/nested_array/~/deep_array/~
|
11
11
|
]
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
Benchmark.ips do |x|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
13
|
+
%w[prune mask].each do |action|
|
14
|
+
preprocessor = Logasm::Preprocessors::Whitelist.new(pointers: pointers, action: action)
|
15
|
+
|
16
|
+
Benchmark.ips do |x|
|
17
|
+
x.config(time: 5, warmup: 2)
|
18
|
+
|
19
|
+
x.report("Scalar value whitelisting (action=#{action})") do
|
20
|
+
preprocessor.process(scalar: 'value', bad_scalar: 'value', hash: {})
|
21
|
+
end
|
22
|
+
|
23
|
+
x.report("Flat hash whitelisting (action=#{action})") do
|
24
|
+
preprocessor.process(flat_hash: { scalar: 'value', array: [1, 2], hash: {} })
|
25
|
+
end
|
26
|
+
|
27
|
+
x.report("Nested hash whitelisting (action=#{action})") do
|
28
|
+
preprocessor.process(
|
29
|
+
nested_hash: {
|
30
|
+
next_level_hash: {
|
31
|
+
deep_hash: { scalar: 'value', array: [1, 2] }
|
32
|
+
},
|
33
|
+
next_level_hash2: {
|
34
|
+
deep_hash: { scalar: 'value', array: [1, 2] }
|
35
|
+
},
|
36
|
+
next_level_hash3: {
|
37
|
+
deep_hash: { scalar: 'value', array: [1, 2] }
|
38
|
+
}
|
38
39
|
}
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
x.report("Flat array whitelisting (action=#{action})") do
|
44
|
+
preprocessor.process(
|
45
|
+
nested_array: [
|
46
|
+
{ deep_array: [1, 2, 3] },
|
47
|
+
{ deep_array: [1, 2, 3] },
|
48
|
+
{ deep_array: [1, 2, 3] }
|
49
|
+
]
|
50
|
+
)
|
51
|
+
end
|
51
52
|
end
|
52
53
|
end
|
@@ -2,7 +2,7 @@ class Logasm
|
|
2
2
|
module Preprocessors
|
3
3
|
class Blacklist
|
4
4
|
|
5
|
-
DEFAULT_ACTION = '
|
5
|
+
DEFAULT_ACTION = 'prune'
|
6
6
|
MASK_SYMBOL = '*'
|
7
7
|
MASKED_VALUE = MASK_SYMBOL * 5
|
8
8
|
|
@@ -52,9 +52,10 @@ class Logasm
|
|
52
52
|
data.merge(key => MASKED_VALUE)
|
53
53
|
end
|
54
54
|
|
55
|
-
def
|
55
|
+
def prune_field(data, *)
|
56
56
|
data
|
57
57
|
end
|
58
|
+
alias_method :exclude_field, :prune_field
|
58
59
|
end
|
59
60
|
end
|
60
61
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Logasm
|
2
|
+
module Preprocessors
|
3
|
+
module Strategies
|
4
|
+
class Mask
|
5
|
+
MASK_SYMBOL = '*'.freeze
|
6
|
+
MASKED_VALUE = MASK_SYMBOL * 5
|
7
|
+
|
8
|
+
def initialize(trie)
|
9
|
+
@trie = trie
|
10
|
+
end
|
11
|
+
|
12
|
+
def process(data, pointer = '')
|
13
|
+
return MASKED_VALUE unless @trie.include?(pointer)
|
14
|
+
|
15
|
+
case data
|
16
|
+
when Hash
|
17
|
+
process_hash(data, pointer)
|
18
|
+
|
19
|
+
when Array
|
20
|
+
process_array(data, pointer)
|
21
|
+
|
22
|
+
else
|
23
|
+
data
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def process_hash(data, parent_pointer)
|
30
|
+
data.each_with_object({}) do |(key, value), result|
|
31
|
+
result[key] = process(value, "#{parent_pointer}/#{key}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_array(data, parent_pointer)
|
36
|
+
data.each_with_index.map do |value, index|
|
37
|
+
process(value, "#{parent_pointer}/#{index}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Logasm
|
2
|
+
module Preprocessors
|
3
|
+
module Strategies
|
4
|
+
class Prune
|
5
|
+
def initialize(trie)
|
6
|
+
@trie = trie
|
7
|
+
end
|
8
|
+
|
9
|
+
def process(data, pointer = '')
|
10
|
+
return nil unless @trie.include?(pointer)
|
11
|
+
|
12
|
+
case data
|
13
|
+
when Hash
|
14
|
+
process_hash(data, pointer)
|
15
|
+
|
16
|
+
when Array
|
17
|
+
process_array(data, pointer)
|
18
|
+
|
19
|
+
else
|
20
|
+
data
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def process_hash(data, parent_pointer)
|
27
|
+
data.each_with_object({}) do |(key, value), result|
|
28
|
+
path = "#{parent_pointer}/#{key}"
|
29
|
+
|
30
|
+
result[key] = process(value, path) if @trie.include?(path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_array(data, parent_pointer)
|
35
|
+
data.each_with_index.each_with_object([]) do |(value, index), result|
|
36
|
+
path = "#{parent_pointer}/#{index}"
|
37
|
+
|
38
|
+
result << process(value, path) if @trie.include?(path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'logasm/preprocessors/json_pointer_trie'
|
2
|
+
require 'logasm/preprocessors/strategies/mask'
|
3
|
+
require 'logasm/preprocessors/strategies/prune'
|
2
4
|
|
3
5
|
class Logasm
|
4
6
|
module Preprocessors
|
@@ -7,21 +9,23 @@ class Logasm
|
|
7
9
|
MASK_SYMBOL = '*'.freeze
|
8
10
|
MASKED_VALUE = MASK_SYMBOL * 5
|
9
11
|
|
12
|
+
PRUNE_ACTION_NAMES = %w[prune exclude].freeze
|
13
|
+
|
10
14
|
class InvalidPointerFormatException < Exception
|
11
15
|
end
|
12
16
|
|
13
17
|
def initialize(config = {})
|
14
|
-
|
15
|
-
|
16
|
-
@trie = pointers.reduce(JSONPointerTrie.new(config)) do |trie, pointer|
|
17
|
-
validate_pointer(pointer)
|
18
|
+
trie = build_trie(config)
|
18
19
|
|
19
|
-
|
20
|
-
|
20
|
+
@strategy = if PRUNE_ACTION_NAMES.include?(config[:action].to_s)
|
21
|
+
Strategies::Prune.new(trie)
|
22
|
+
else
|
23
|
+
Strategies::Mask.new(trie)
|
24
|
+
end
|
21
25
|
end
|
22
26
|
|
23
27
|
def process(data)
|
24
|
-
|
28
|
+
@strategy.process(data)
|
25
29
|
end
|
26
30
|
|
27
31
|
private
|
@@ -38,31 +42,13 @@ class Logasm
|
|
38
42
|
.gsub('~0', '~')
|
39
43
|
end
|
40
44
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
case data
|
45
|
-
when Hash
|
46
|
-
process_hash(parent_pointer, data)
|
47
|
-
|
48
|
-
when Array
|
49
|
-
process_array(parent_pointer, data)
|
50
|
-
|
51
|
-
else
|
52
|
-
data
|
53
|
-
end
|
54
|
-
end
|
45
|
+
def build_trie(config)
|
46
|
+
pointers = (config[:pointers] || []) + DEFAULT_WHITELIST
|
55
47
|
|
56
|
-
|
57
|
-
|
58
|
-
processed = process_data("#{parent_pointer}/#{key}", value)
|
59
|
-
result[key] = processed
|
60
|
-
end
|
61
|
-
end
|
48
|
+
pointers.reduce(JSONPointerTrie.new(config)) do |trie, pointer|
|
49
|
+
validate_pointer(pointer)
|
62
50
|
|
63
|
-
|
64
|
-
array.each_with_index.map do |value, index|
|
65
|
-
process_data("#{parent_pointer}/#{index}", value)
|
51
|
+
trie.insert(decode(pointer))
|
66
52
|
end
|
67
53
|
end
|
68
54
|
end
|
data/logasm.gemspec
CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "logasm"
|
7
|
-
gem.version = '0.9.
|
7
|
+
gem.version = '0.9.1'
|
8
8
|
gem.authors = ["Salemove"]
|
9
9
|
gem.email = ["support@salemove.com"]
|
10
10
|
gem.description = %q{It's logasmic}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require_relative '../../lib/logasm/preprocessors/whitelist'
|
3
3
|
|
4
|
-
describe Logasm::Preprocessors::Whitelist do
|
4
|
+
RSpec.describe Logasm::Preprocessors::Whitelist, 'when :action is :mask or omitted' do
|
5
5
|
subject(:processed_data) { described_class.new(config).process(data) }
|
6
6
|
|
7
7
|
let(:config) { { pointers: pointers } }
|
@@ -199,3 +199,137 @@ describe Logasm::Preprocessors::Whitelist do
|
|
199
199
|
described_class.new(pointers: pointers).process(data)
|
200
200
|
end
|
201
201
|
end
|
202
|
+
|
203
|
+
RSpec.describe Logasm::Preprocessors::Whitelist, 'when :action is :exclude or :prune' do
|
204
|
+
subject(:processed_data) { described_class.new(config).process(data) }
|
205
|
+
|
206
|
+
let(:config) { { pointers: pointers, action: :prune } }
|
207
|
+
let(:pointers) { [] }
|
208
|
+
let(:data) do
|
209
|
+
{
|
210
|
+
field: 'secret',
|
211
|
+
data: {
|
212
|
+
field: 'secret'
|
213
|
+
},
|
214
|
+
array: [{ field: 'secret' }, { field2: 'secret' }]
|
215
|
+
}
|
216
|
+
end
|
217
|
+
|
218
|
+
context 'when pointers is empty' do
|
219
|
+
it 'prunes all fields from the input' do
|
220
|
+
expect(processed_data).to eq({})
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'with whitelisted field' do
|
225
|
+
let(:pointers) { ['/field'] }
|
226
|
+
|
227
|
+
it 'includes the field' do
|
228
|
+
expect(processed_data).to eq(field: 'secret')
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context 'with whitelisted nested field' do
|
233
|
+
let(:pointers) { ['/data/field'] }
|
234
|
+
|
235
|
+
it 'includes nested field' do
|
236
|
+
expect(processed_data).to eq(data: { field: 'secret' })
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
context 'with whitelisted array element field' do
|
241
|
+
let(:pointers) { ['/array/0/field'] }
|
242
|
+
|
243
|
+
it 'includes array element' do
|
244
|
+
expect(processed_data).to eq(array: [{ field: 'secret' }])
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'with whitelisted hash' do
|
249
|
+
it 'includes all whitelisted hash elements' do
|
250
|
+
source = { foo: { bar: 'baz' } }
|
251
|
+
target = { foo: { bar: 'baz' } }
|
252
|
+
expect(process(['/foo/~'], source)).to eq(target)
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'does not include nested elements' do
|
256
|
+
source = { foo: { bar: { baz: 'asd' } } }
|
257
|
+
target = { foo: { bar: {} } }
|
258
|
+
expect(process(['/foo/~'], source)).to eq(target)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context 'with whitelisted array elements field with wildcard' do
|
263
|
+
let(:data) do
|
264
|
+
{
|
265
|
+
array: [
|
266
|
+
{ field: 'data1', secret: 'secret1' },
|
267
|
+
{ field: 'data2', secret: 'secret2' }
|
268
|
+
]
|
269
|
+
}
|
270
|
+
end
|
271
|
+
let(:pointers) { ['/array/~/field'] }
|
272
|
+
|
273
|
+
it 'includes array elements field' do
|
274
|
+
expect(processed_data).to include(
|
275
|
+
array: [
|
276
|
+
{ field: 'data1' },
|
277
|
+
{ field: 'data2' }
|
278
|
+
]
|
279
|
+
)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context 'with whitelisted string array elements with wildcard' do
|
284
|
+
let(:data) do
|
285
|
+
{ array: %w[secret1 secret2] }
|
286
|
+
end
|
287
|
+
let(:pointers) { ['/array/~'] }
|
288
|
+
|
289
|
+
it 'includes array elements' do
|
290
|
+
expect(processed_data).to include(array: %w[secret1 secret2])
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'with whitelisted string array elements in an array with wildcard' do
|
295
|
+
let(:data) do
|
296
|
+
{
|
297
|
+
nested: [{ array: %w[secret1 secret2] }]
|
298
|
+
}
|
299
|
+
end
|
300
|
+
let(:pointers) { ['/nested/~/array/~'] }
|
301
|
+
|
302
|
+
it 'includes array elements' do
|
303
|
+
expect(processed_data).to include(nested: [{ array: %w[secret1 secret2] }])
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
context 'with whitelisted array element' do
|
309
|
+
let(:pointers) { ['/array/0'] }
|
310
|
+
|
311
|
+
it 'masks array element' do
|
312
|
+
expect(processed_data).to eq(array: [{}])
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
context 'with whitelisted array' do
|
317
|
+
let(:pointers) { ['/array'] }
|
318
|
+
|
319
|
+
it 'masks array' do
|
320
|
+
expect(processed_data).to include(array: [])
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
context 'with whitelisted hash' do
|
325
|
+
let(:pointers) { ['/data'] }
|
326
|
+
|
327
|
+
it 'masks hash' do
|
328
|
+
expect(processed_data).to include(data: {})
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def process(pointers, data)
|
333
|
+
described_class.new(pointers: pointers, action: :prune).process(data)
|
334
|
+
end
|
335
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logasm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Salemove
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-12-
|
11
|
+
date: 2017-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inflecto
|
@@ -120,6 +120,8 @@ files:
|
|
120
120
|
- lib/logasm/preprocessors.rb
|
121
121
|
- lib/logasm/preprocessors/blacklist.rb
|
122
122
|
- lib/logasm/preprocessors/json_pointer_trie.rb
|
123
|
+
- lib/logasm/preprocessors/strategies/mask.rb
|
124
|
+
- lib/logasm/preprocessors/strategies/prune.rb
|
123
125
|
- lib/logasm/preprocessors/whitelist.rb
|
124
126
|
- lib/logasm/utils.rb
|
125
127
|
- logasm.gemspec
|