fluent-plugin-pan-anonymizer 0.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e2a2096566adc99fe1643de5c70647c02ee9c8fd
4
+ data.tar.gz: 6298e53f30b28d786a0a0e63c016d50f07ffe332
5
+ SHA512:
6
+ metadata.gz: '09d07c442d389a04d1c312fee7b4617bee74f0152a901bbf8bc0fef214d75d726ca0825c9e0087e17e09dc1a4e91b0c120a2c91ad63163773d4bce4001568316'
7
+ data.tar.gz: 16f0a355f3156c24011fe90bce23036dbd3a9a87f1cc616d14a9b1f7e83beeba79e613f1b7270c019ee7e78c2c77d2e18702b804c075e824fa8b5c079bec95b7
@@ -0,0 +1,6 @@
1
+ .ruby-version
2
+ *.gem
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
6
+ vendor/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-sendgrid-event.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2018- Kanmu, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,67 @@
1
+ # fluent-plugin-pan-anonymizer
2
+
3
+ A Fluent filter plugin to anonymize records which have PAN (Primary Account Number = Credit card number). The plugin validates PAN using [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) after matching.
4
+
5
+ Inspired by [fluent-plugin-anonymizer](https://github.com/y-ken/fluent-plugin-anonymizer).
6
+
7
+ # Requirements
8
+
9
+ - fluentd: v0.14.x or later
10
+ - Ruby: 2.4 or later
11
+
12
+ # Installation
13
+
14
+ ```
15
+ gem install fluent-plugin-pan-anonymizer
16
+ ```
17
+
18
+ # Configuration
19
+
20
+ NOTE: Card numbers in the example don't exist in the world.
21
+
22
+ ```
23
+ <source>
24
+ @type dummy
25
+ tag dummy
26
+ dummy [
27
+ {"time": 12345678901234567, "subject": "xxxxxx", "user_inquiry": "hi, my card number is 4019249331712145 !"},
28
+ {"time": 12345678901234568, "subject": "xxxxxx", "user_inquiry": "hello inquiry code is 4567890123456789"},
29
+ {"time": 12345678901234569, "subject": "I am 4019 2493 3171 2145", "user_inquiry": "4019-2493-3171-2145 is my number"},
30
+ {"time": 14019249331712145, "subject": "ユーザーです", "user_inquiry": "4019249331712145 のカードを使っています"}
31
+ ]
32
+ </source>
33
+
34
+ <filter **>
35
+ @type pan_anonymizer
36
+ ignore_keys time
37
+ <pan>
38
+ formats /4\d{15}/, /4[0-9]{15}/
39
+ checksum_algorithm luhn
40
+ mask 9999999999999999
41
+ </pan>
42
+ <pan>
43
+ formats /4\d{3}-\d{4}-\d{4}-\d{4}/, /4\d{3}\s*\d{4}\s*\d{4}\s*\d{4}/
44
+ checksum_algorithm luhn
45
+ mask xxxx-xxxx-xxxx-xxxx
46
+ </pan>
47
+ </filter>
48
+
49
+ <match **>
50
+ @type stdout
51
+ </match>
52
+ ```
53
+
54
+ ## The result of the example given above
55
+
56
+ ```
57
+ 2018-11-13 22:01:35.074963000 +0900 dummy: {"time":12345678901234567,"subject":"xxxxxx","user_inquiry":"hi, my card number is 9999999999999999 !"}
58
+ 2018-11-13 22:01:36.001053000 +0900 dummy: {"time":12345678901234568,"subject":"xxxxxx","user_inquiry":"hello inquiry code is 4567890123456789"}
59
+ 2018-11-13 22:01:37.021032000 +0900 dummy: {"time":12345678901234569,"subject":"I am xxxx-xxxx-xxxx-xxxx","user_inquiry":"xxxx-xxxx-xxxx-xxxx is my number"}
60
+ 2018-11-13 22:01:38.050578000 +0900 dummy: {"time":14019249331712145,"subject":"ユーザーです","user_inquiry":"9999999999999999 のカードを使っています"}
61
+ ```
62
+
63
+ Card numbers were masked with given configuration except `time` key and `4567890123456789` in "hello inquiry code is 4567890123456789". `4567890123456789` is not a valid card number.
64
+
65
+ # License
66
+
67
+ Apache License, Version 2.0
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ Rake::TestTask.new(:test) do |test|
4
+ test.libs << 'lib' << 'test'
5
+ test.pattern = 'test/**/test_*.rb'
6
+ test.verbose = true
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "fluent-plugin-pan-anonymizer"
7
+ spec.version = "0.0.1"
8
+ spec.authors = ["Hiroaki Sano"]
9
+ spec.email = ["hiroaki.sano.9stories@gmail.com"]
10
+
11
+ spec.summary = %q{Fluentd filter plugin to anonymize credit card numbers.}
12
+ spec.homepage = "https://github.com/kanmu/fluent-plugin-pan-anonymizer"
13
+ spec.license = "Apache License, Version 2.0"
14
+
15
+ spec.files = `git ls-files`.split("\n")
16
+ spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "test-unit"
23
+
24
+ spec.add_runtime_dependency "fluentd", ">= 0.14.0"
25
+ end
@@ -0,0 +1,44 @@
1
+ require 'fluent/plugin/filter'
2
+ require 'fluent/plugin/pan/masker'
3
+
4
+ module Fluent::Plugin
5
+ class PANAnonymizerFilter < Filter
6
+ Fluent::Plugin.register_filter("pan_anonymizer", self)
7
+
8
+ config_section :pan, param_name: :pan_configs, required: true, multi: true do
9
+ config_param :formats, :array, value_type: :regexp, default: []
10
+ config_param :checksum_algorithm, :enum, list: Fluent::PAN::Masker::CHECKSUM_FUNC.keys, default: :luhn
11
+ config_param :mask, :string, default: "****"
12
+ config_param :force, :bool, default: false
13
+ end
14
+ config_param :ignore_keys, :array, default: []
15
+
16
+ def initialize
17
+ super
18
+ end
19
+
20
+ def configure(conf)
21
+ super
22
+
23
+ @pan_masker = @pan_configs.map do |i|
24
+ i[:formats].map do |format|
25
+ Fluent::PAN::Masker.new(format, i[:checksum_algorithm], i[:mask], i[:force])
26
+ end
27
+ end.flatten
28
+ end
29
+
30
+ def filter(tag, time, record)
31
+ record.map do |key, value|
32
+ if @ignore_keys.include? key.to_s
33
+ [key, value]
34
+ else
35
+ _value = value
36
+ @pan_masker.each do |i|
37
+ _value = i.mask_if_found_pan(_value)
38
+ end
39
+ [key, _value]
40
+ end
41
+ end.to_h
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,71 @@
1
+ module Fluent::PAN
2
+ class Masker
3
+
4
+ CHECKSUM_FUNC = {
5
+ luhn: ->(digits){
6
+ sum = 0
7
+ alt = false
8
+ digits.reverse.each do |i|
9
+ if alt
10
+ i *= 2
11
+ if i > 9
12
+ i -= 9
13
+ end
14
+ end
15
+ sum += i
16
+ alt = !alt
17
+ end
18
+ (sum % 10).zero?
19
+ },
20
+ none: ->digits{
21
+ # Do nothing. always satisfied.
22
+ true
23
+ }
24
+ }
25
+
26
+ def initialize(regexp, checksum_algorithm, mask, force=false)
27
+ @regexp = regexp
28
+ @mask = mask
29
+ @force = force
30
+ @checksum_func = CHECKSUM_FUNC[checksum_algorithm]
31
+ end
32
+
33
+ def mask_if_found_pan(orgval)
34
+ filtered = orgval.to_s.gsub(@regexp) do |match|
35
+ pan = match.split("").select { |i| i =~ /\d/ }.map { |j| j.to_i }
36
+
37
+ if valid?(pan)
38
+ match = @mask
39
+ else
40
+ match
41
+ end
42
+ end
43
+
44
+ retval = filtered
45
+ if orgval.is_a? Integer
46
+ if numerals_mask?
47
+ retval = filtered.to_i
48
+ else
49
+ if @force
50
+ retval = filtered
51
+ else
52
+ retval = orgval
53
+ end
54
+ end
55
+ end
56
+ retval
57
+ end
58
+
59
+ def valid?(pan)
60
+ @checksum_func.call(pan)
61
+ end
62
+
63
+ def numerals_mask?
64
+ if @mask.to_s =~ /^\d+$/
65
+ true
66
+ else
67
+ false
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,7 @@
1
+ require 'bundler/setup'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.join(__dir__, '..', 'lib'))
5
+ $LOAD_PATH.unshift(__dir__)
6
+ require 'fluent/test'
7
+ require 'fluent/test/helpers'
@@ -0,0 +1,308 @@
1
+ require_relative '../helper'
2
+ require 'fluent/test/driver/filter'
3
+
4
+ require 'fluent/plugin/filter_pan_anonymizer'
5
+
6
+ # NOTE: The card number in the test doesn't exist in the world!
7
+
8
+ class PANAnonymizerFilterTest < Test::Unit::TestCase
9
+ def setup
10
+ Fluent::Test::setup
11
+ @time = Fluent::Engine.now
12
+ end
13
+
14
+ CONFIG = %[
15
+ <pan>
16
+ formats /4\\d{15}/
17
+ checksum_algorithm luhn
18
+ mask xxxx
19
+ </pan>
20
+ <pan>
21
+ formats /4\\d{15}/
22
+ checksum_algorithm none
23
+ mask xxxx
24
+ </pan>
25
+ <pan>
26
+ formats /4019-\\d{4}-\\d{4}-\\d{4}/
27
+ checksum_algorithm luhn
28
+ mask xxxx
29
+ </pan>
30
+ <pan>
31
+ formats /4019\\d{10}/, /4019-\\d{4}-\\d{4}-\\d{4}/
32
+ checksum_algorithm luhn
33
+ mask xxxx
34
+ </pan>
35
+ ignore_keys ignore1, ignore2
36
+ ]
37
+
38
+ def create_driver(conf=CONFIG)
39
+ Fluent::Test::Driver::Filter.new(Fluent::Plugin::PANAnonymizerFilter).configure(conf)
40
+ end
41
+
42
+ def filter(conf, messages)
43
+ d = create_driver(conf)
44
+ d.run(default_tag: 'test') do
45
+ messages.each do |message|
46
+ d.feed(message)
47
+ end
48
+ end
49
+ d.filtered_records
50
+ end
51
+
52
+ sub_test_case 'configured with invalid configuration' do
53
+ test 'empty configuration' do
54
+ assert_raise(Fluent::ConfigError) do
55
+ create_driver("")
56
+ end
57
+ end
58
+ test '<pan></pan> is required' do
59
+ conf = %[
60
+ ]
61
+ assert_raise(Fluent::ConfigError) do
62
+ create_driver(conf)
63
+ end
64
+ end
65
+ test 'ok if <pan> exists' do
66
+ conf = %[
67
+ <pan>
68
+ </pan>
69
+ ]
70
+ assert_nothing_raised(Fluent::ConfigError) do
71
+ create_driver(conf)
72
+ end
73
+ end
74
+ test 'valid config' do
75
+ conf = %[
76
+ <pan>
77
+ formats /4\d{15}/
78
+ checksum_algorithm luhn
79
+ mask xxxx
80
+ </pan>
81
+ ignore_keys key1, key2
82
+ ]
83
+ assert_nothing_raised(Fluent::ConfigError) do
84
+ create_driver(conf)
85
+ end
86
+ end
87
+ test 'multi <pan> block' do
88
+ conf = %[
89
+ <pan>
90
+ formats /40192491\d{8}/
91
+ checksum_algorithm luhn
92
+ mask xxxx
93
+ </pan>
94
+ <pan>
95
+ formats /40192492\d{8}/
96
+ checksum_algorithm luhn
97
+ mask xxxx
98
+ </pan>
99
+ ignore_keys key1, key2
100
+ ]
101
+ assert_nothing_raised(Fluent::ConfigError) do
102
+ create_driver(conf)
103
+ end
104
+ end
105
+ test 'multi <pan> block with multi formats fields' do
106
+ conf = %[
107
+ <pan>
108
+ formats /40192491\d{8}/, /4019-2491-\d{4}-\d{4}/
109
+ checksum_algorithm luhn
110
+ mask xxxx
111
+ </pan>
112
+ <pan>
113
+ formats /40192492\d{8}/, /4019-2492-\d{4}-\d{4}/
114
+ checksum_algorithm luhn
115
+ mask xxxx
116
+ </pan>
117
+ ignore_keys key1, key2
118
+ ]
119
+ assert_nothing_raised(Fluent::ConfigError) do
120
+ create_driver(conf)
121
+ end
122
+ end
123
+ end
124
+
125
+ sub_test_case 'normal case' do
126
+ test "in case of nnnnnnnnnnnnnnnn" do
127
+ conf = %[
128
+ <pan>
129
+ formats /4\\d{15}/
130
+ checksum_algorithm luhn
131
+ mask xxxx
132
+ </pan>
133
+ ]
134
+ messages = [
135
+ {
136
+ "key": "9994019249331712145999"
137
+ }
138
+ ]
139
+ expected = [
140
+ {
141
+ "key": "999xxxx999"
142
+ }
143
+ ]
144
+ filtered = filter(conf, messages)
145
+ assert_equal(expected, filtered)
146
+ end
147
+ test "in case of nnnn-nnnn-nnnn-nnnn" do
148
+ conf = %[
149
+ <pan>
150
+ formats /4\\d{3}-\\d{4}-\\d{4}-\\d{4}/
151
+ checksum_algorithm luhn
152
+ mask xxxx
153
+ </pan>
154
+ ]
155
+ messages = [
156
+ {
157
+ "key": "9994019-2493-3171-2145999"
158
+ }
159
+ ]
160
+ expected = [
161
+ {
162
+ "key": "999xxxx999"
163
+ }
164
+ ]
165
+ filtered = filter(conf, messages)
166
+ assert_equal(expected, filtered)
167
+ end
168
+ end
169
+
170
+ sub_test_case 'checksum_algorithm' do
171
+ test "not be masked if PAN is not satisfied luhn" do
172
+ conf = %[
173
+ <pan>
174
+ formats /4\\d{15}/
175
+ checksum_algorithm luhn
176
+ mask xxxx
177
+ </pan>
178
+ ]
179
+ messages = [
180
+ {
181
+ "key": "9994019111122223333999"
182
+ }
183
+ ]
184
+ expected = [
185
+ {
186
+ "key": "9994019111122223333999"
187
+ }
188
+ ]
189
+ filtered = filter(conf, messages)
190
+ assert_equal(expected, filtered)
191
+ end
192
+ test "be masked if checksum_algorithm is none" do
193
+ conf = %[
194
+ <pan>
195
+ formats /4\\d{15}/
196
+ checksum_algorithm none
197
+ mask xxxx
198
+ </pan>
199
+ ]
200
+ messages = [
201
+ {
202
+ "key": "9994019111122223333999"
203
+ }
204
+ ]
205
+ expected = [
206
+ {
207
+ "key": "999xxxx999"
208
+ }
209
+ ]
210
+ filtered = filter(conf, messages)
211
+ assert_equal(expected, filtered)
212
+ end
213
+ end
214
+
215
+ sub_test_case 'integer value' do
216
+ test "not be masked if mask is string" do
217
+ conf = %[
218
+ <pan>
219
+ formats /4\\d{15}/
220
+ checksum_algorithm luhn
221
+ mask xxxx
222
+ </pan>
223
+ ]
224
+ messages = [
225
+ {
226
+ "key": 9994019249331712145999
227
+ }
228
+ ]
229
+ expected = [
230
+ {
231
+ "key": 9994019249331712145999
232
+ }
233
+ ]
234
+ filtered = filter(conf, messages)
235
+ assert_equal(expected, filtered)
236
+ end
237
+ test "be masked if force flag exists" do
238
+ conf = %[
239
+ <pan>
240
+ formats /4\\d{15}/
241
+ checksum_algorithm luhn
242
+ mask xxxx
243
+ force true
244
+ </pan>
245
+ ]
246
+ messages = [
247
+ {
248
+ "key": 9994019249331712145999
249
+ }
250
+ ]
251
+ expected = [
252
+ {
253
+ "key": "999xxxx999"
254
+ }
255
+ ]
256
+ filtered = filter(conf, messages)
257
+ assert_equal(expected, filtered)
258
+ end
259
+ test "be masked if mask is integer value" do
260
+ conf = %[
261
+ <pan>
262
+ formats /4\\d{15}/
263
+ checksum_algorithm luhn
264
+ mask 1111111111111111
265
+ </pan>
266
+ ]
267
+ messages = [
268
+ {
269
+ "key": 9994019249331712145999
270
+ }
271
+ ]
272
+ expected = [
273
+ {
274
+ "key": 9991111111111111111999
275
+ }
276
+ ]
277
+ filtered = filter(conf, messages)
278
+ assert_equal(expected, filtered)
279
+ end
280
+ end
281
+
282
+ sub_test_case 'ignore keys' do
283
+ test "not be masked" do
284
+ conf = %[
285
+ <pan>
286
+ formats /4\\d{15}/
287
+ checksum_algorithm luhn
288
+ mask 9999999999999999
289
+ </pan>
290
+ ignore_keys time
291
+ ]
292
+ messages = [
293
+ {
294
+ "time": 40192493317121459,
295
+ "key": 40192493317121459
296
+ }
297
+ ]
298
+ expected = [
299
+ {
300
+ "time": 40192493317121459,
301
+ "key": 99999999999999999
302
+ }
303
+ ]
304
+ filtered = filter(conf, messages)
305
+ assert_equal(expected, filtered)
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,184 @@
1
+ require 'helper'
2
+ require 'fluent/plugin/pan/masker'
3
+
4
+ # NOTE: The card number in the test doesn't exist in the world!
5
+
6
+ class PANMaskerTest < Test::Unit::TestCase
7
+
8
+ sub_test_case "valid?" do
9
+ test "valid digits" do
10
+ valid_card_number = [4, 0, 1, 9, 2, 4, 9, 3, 3, 1, 7, 1, 2, 1, 4, 5]
11
+ f = Fluent::PAN::Masker.new(//, :luhn, "")
12
+ assert_equal(true, f.valid?(valid_card_number))
13
+ end
14
+
15
+ test "invalid digits" do
16
+ invalid_card_number = [4, 0, 1, 9, 2, 4, 9, 3, 9, 9, 9, 9, 9, 9, 9, 9]
17
+ f = Fluent::PAN::Masker.new(//, :luhn, "")
18
+ assert_equal(false, f.valid?(invalid_card_number))
19
+ end
20
+
21
+ test "always true when checksum algorithm is none" do
22
+ valid_card_number = [4, 0, 1, 9, 2, 4, 9, 3, 3, 1, 7, 1, 2, 1, 4, 5]
23
+ f = Fluent::PAN::Masker.new(//, :none, "")
24
+ assert_equal(true, f.valid?(valid_card_number))
25
+
26
+ invalid_card_number = [4, 0, 1, 9, 2, 4, 9, 3, 9, 9, 9, 9, 9, 9, 9, 9]
27
+ f = Fluent::PAN::Masker.new(//, :none, "")
28
+ assert_equal(true, f.valid?(invalid_card_number))
29
+
30
+ invalid_card_number = [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
31
+ f = Fluent::PAN::Masker.new(//, :none, "")
32
+ assert_equal(true, f.valid?(invalid_card_number))
33
+ end
34
+ end
35
+
36
+ sub_test_case "numerals_mask?" do
37
+ test "true" do
38
+ mask = 0
39
+ f = Fluent::PAN::Masker.new(//, :none, mask)
40
+ assert_equal(true, f.numerals_mask?)
41
+
42
+ mask = 00
43
+ f = Fluent::PAN::Masker.new(//, :none, mask)
44
+ assert_equal(true, f.numerals_mask?)
45
+
46
+ mask = 100
47
+ f = Fluent::PAN::Masker.new(//, :none, mask)
48
+ assert_equal(true, f.numerals_mask?)
49
+
50
+ mask = "0"
51
+ f = Fluent::PAN::Masker.new(//, :none, mask)
52
+ assert_equal(true, f.numerals_mask?)
53
+
54
+ mask = "00"
55
+ f = Fluent::PAN::Masker.new(//, :none, mask)
56
+ assert_equal(true, f.numerals_mask?)
57
+
58
+ mask = "100"
59
+ f = Fluent::PAN::Masker.new(//, :none, mask)
60
+ assert_equal(true, f.numerals_mask?)
61
+ end
62
+
63
+ test "false" do
64
+ mask = "*"
65
+ f = Fluent::PAN::Masker.new(//, :none, mask)
66
+ assert_equal(false, f.numerals_mask?)
67
+
68
+ mask = "*00"
69
+ f = Fluent::PAN::Masker.new(//, :none, mask)
70
+ assert_equal(false, f.numerals_mask?)
71
+
72
+ mask = "100*"
73
+ f = Fluent::PAN::Masker.new(//, :none, mask)
74
+ assert_equal(false, f.numerals_mask?)
75
+ end
76
+ end
77
+
78
+ sub_test_case "mask_if_pan_found?" do
79
+ test "with numerals string mask" do
80
+ mask = "0000000000000000"
81
+ f = Fluent::PAN::Masker.new(/4\d{15}/, :luhn, mask)
82
+
83
+ filtered = f.mask_if_found_pan("4019249331712145")
84
+ assert_equal(String, filtered.class)
85
+ assert_equal("#{mask}", filtered)
86
+
87
+ filtered = f.mask_if_found_pan("XXXX4019249331712145XXXX")
88
+ assert_equal(String, filtered.class)
89
+ assert_equal("XXXX#{mask}XXXX", filtered)
90
+
91
+ filtered = f.mask_if_found_pan(4019249331712145)
92
+ assert_equal(Integer, filtered.class)
93
+ assert_equal("#{mask}".to_i, filtered)
94
+
95
+ filtered = f.mask_if_found_pan(140192493317121459)
96
+ assert_equal(Integer, filtered.class)
97
+ assert_equal("1#{mask}9".to_i, filtered)
98
+ end
99
+
100
+ test "with numerals mask" do
101
+ mask = 4019111111111111
102
+ f = Fluent::PAN::Masker.new(/4\d{15}/, :luhn, mask)
103
+
104
+ filtered = f.mask_if_found_pan("4019249331712145")
105
+ assert_equal(String, filtered.class)
106
+ assert_equal("#{mask}", filtered)
107
+
108
+ filtered = f.mask_if_found_pan("XXXX4019249331712145XXXX")
109
+ assert_equal(String, filtered.class)
110
+ assert_equal("XXXX#{mask}XXXX", filtered)
111
+
112
+ filtered = f.mask_if_found_pan(4019249331712145)
113
+ assert_equal(Integer, filtered.class)
114
+ assert_equal("#{mask}".to_i, filtered)
115
+
116
+ filtered = f.mask_if_found_pan(140192493317121459)
117
+ assert_equal(Integer, filtered.class)
118
+ assert_equal("1#{mask}9".to_i, filtered)
119
+ end
120
+
121
+ test "with 0000000000000000 mask" do
122
+ mask = 0000000000000000
123
+ f = Fluent::PAN::Masker.new(/4\d{15}/, :luhn, mask)
124
+
125
+ filtered = f.mask_if_found_pan("4019249331712145")
126
+ assert_equal(String, filtered.class)
127
+ assert_equal("0", filtered)
128
+
129
+ filtered = f.mask_if_found_pan("XXXX4019249331712145XXXX")
130
+ assert_equal(String, filtered.class)
131
+ assert_equal("XXXX0XXXX", filtered)
132
+
133
+ filtered = f.mask_if_found_pan(4019249331712145)
134
+ assert_equal(Integer, filtered.class)
135
+ assert_equal(0, filtered)
136
+
137
+ filtered = f.mask_if_found_pan(140192493317121459)
138
+ assert_equal(Integer, filtered.class)
139
+ assert_equal(109, filtered)
140
+ end
141
+
142
+ test "with string mask" do
143
+ mask = "****"
144
+ f = Fluent::PAN::Masker.new(/4\d{15}/, :luhn, mask)
145
+
146
+ filtered = f.mask_if_found_pan("4019249331712145")
147
+ assert_equal(String, filtered.class)
148
+ assert_equal("#{mask}", filtered)
149
+
150
+ filtered = f.mask_if_found_pan("XXXX4019249331712145XXXX")
151
+ assert_equal(String, filtered.class)
152
+ assert_equal("XXXX#{mask}XXXX", filtered)
153
+
154
+ filtered = f.mask_if_found_pan(4019249331712145)
155
+ assert_equal(Integer, filtered.class)
156
+ assert_equal(4019249331712145, filtered)
157
+
158
+ filtered = f.mask_if_found_pan(140192493317121459)
159
+ assert_equal(Integer, filtered.class)
160
+ assert_equal(140192493317121459, filtered)
161
+ end
162
+
163
+ test "with string mask and force: true" do
164
+ mask = "****"
165
+ f = Fluent::PAN::Masker.new(/4\d{15}/, :luhn, mask, force: true)
166
+
167
+ filtered = f.mask_if_found_pan("4019249331712145")
168
+ assert_equal(String, filtered.class)
169
+ assert_equal("#{mask}", filtered)
170
+
171
+ filtered = f.mask_if_found_pan("XXXX4019249331712145XXXX")
172
+ assert_equal(String, filtered.class)
173
+ assert_equal("XXXX#{mask}XXXX", filtered)
174
+
175
+ filtered = f.mask_if_found_pan(4019249331712145)
176
+ assert_equal(String, filtered.class)
177
+ assert_equal("#{mask}", filtered)
178
+
179
+ filtered = f.mask_if_found_pan(140192493317121459)
180
+ assert_equal(String, filtered.class)
181
+ assert_equal("1#{mask}9", filtered)
182
+ end
183
+ end
184
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-pan-anonymizer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Hiroaki Sano
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-11-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: test-unit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fluentd
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 0.14.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 0.14.0
69
+ description:
70
+ email:
71
+ - hiroaki.sano.9stories@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - LICENSE
79
+ - README.md
80
+ - Rakefile
81
+ - fluent-plugin-pan-anonymizer.gemspec
82
+ - lib/fluent/plugin/filter_pan_anonymizer.rb
83
+ - lib/fluent/plugin/pan/masker.rb
84
+ - test/helper.rb
85
+ - test/plugin/test_filter_pan_anonymizer.rb
86
+ - test/plugin/test_pan_masker.rb
87
+ homepage: https://github.com/kanmu/fluent-plugin-pan-anonymizer
88
+ licenses:
89
+ - Apache License, Version 2.0
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.6.14.1
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Fluentd filter plugin to anonymize credit card numbers.
111
+ test_files:
112
+ - test/helper.rb
113
+ - test/plugin/test_filter_pan_anonymizer.rb
114
+ - test/plugin/test_pan_masker.rb