fluent-plugin-pan-anonymizer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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