fluent-plugin-dos_block_acl 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1c642a31a3a9ceceaea76768fd6b5df1b4d2cce1
4
+ data.tar.gz: 549d4678f4a934ee1727b6f0431a13d81e1b2108
5
+ SHA512:
6
+ metadata.gz: 6b1ef004c755912178c81d4be04e78adb6d5b73999561d679fda813029ce99a1cb6e992da91c5e03518e5ed3b968e0f0ab73b8d026a8235350c7fbec483ed06d
7
+ data.tar.gz: c7934fff246674fe9d02d2a6749ce775d47d34846555a672a76f0a0d6eb1af98992a3d6c57e54167799b91ddd53f78da1db032b60a15dc5b2ba60be5265d9ce4
data/.document ADDED
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ Gemfile.lock
2
+ doc/
3
+ pkg/
4
+ **/*.gem
5
+ .yardoc/
6
+ .bundle/
7
+ vendor/bundle/
8
+ .tags
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1
7
+ - ruby-head
8
+
9
+ os:
10
+ - linux
11
+ - osx
12
+
13
+ gemfile:
14
+ - Gemfile
15
+
16
+ script: 'bundle exec rake test'
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown --title "fluent-plugin-dos_block_acl Documentation" --protected
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'kramdown'
7
+ end
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # fluent-plugin-dos_block_acl [![Build Status](https://secure.travis-ci.org/toyama0919/fluent-plugin-dos_block_acl.png?branch=master)](http://travis-ci.org/toyama0919/fluent-plugin-dos_block_acl)
2
+
3
+ access block by aws network acl.
4
+
5
+ aggregate unit is time_slice_format.
6
+
7
+ ## Installation
8
+ ```
9
+ fluent-gem install fluent-plugin-dos_block_acl
10
+ ```
11
+
12
+ ## Examples(more than 10000 access per hour)
13
+ ```
14
+ <match dos_block_acl.exsample>
15
+ type dos_block_acl
16
+ network_acl_id acl-xxxxxxx
17
+ ip_address_key ip_address
18
+ dos_threshold 10000
19
+ buffer_chunk_limit 256m
20
+ region ap-northeast-1
21
+ deny_rule_numbers_range 1..10
22
+ time_slice_format %Y%m%d_%H
23
+ buffer_path /tmp/dos_block_acl_hourly*.log
24
+ state_file /var/log/td-agent/buffer/dos_block_acl_state.log
25
+ </match>
26
+ ```
27
+
28
+ ## Examples(more than 100000 access per day)
29
+ ```
30
+ <match dos_block_acl.exsample>
31
+ type dos_block_acl
32
+ network_acl_id acl-xxxxxxx
33
+ ip_address_key ip_address
34
+ dos_threshold 10000
35
+ buffer_chunk_limit 256m
36
+ region ap-northeast-1
37
+ deny_rule_numbers_range 11..18
38
+ time_slice_format %Y%m%d
39
+ buffer_path /tmp/dos_block_acl_daily*.log
40
+ state_file /var/log/td-agent/buffer/dos_block_acl_state.log
41
+ </match>
42
+ ```
43
+
44
+ ## parameter
45
+
46
+ |param | default|exsample|
47
+ |--------|--------|--------|
48
+ |network_acl_id||acl-xxxxxx|
49
+ |dryrun| false|true|
50
+ |ip_address_key||ip_address|
51
+ |dos_threshold||1000|
52
+ |time_slice_format |%Y%m%d|%Y%m%d_%H|
53
+ |aws_key_id| nil||
54
+ |aws_sec_key| nil||
55
+ |region| nil|ap-northeast-1|
56
+ |white_list| '127.0.0.1'|127.0.0.1,192.168.0.1,192.168.0.2|
57
+ |deny_rule_numbers_range| '1..18'||
58
+ |state_file| nil|/var/log/td-agent/dos_block_acl_state.log|
59
+
60
+
61
+
62
+ ## Notes
63
+
64
+ default network acl entry limit is 20.([see](http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_Appendix_Limits.html))
65
+
66
+
67
+ ## Contributing
68
+
69
+ 1. Fork it
70
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
71
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
72
+ 4. Push to the branch (`git push origin my-new-feature`)
73
+ 5. Create new [Pull Request](../../pull/new/master)
74
+
75
+ ## Information
76
+
77
+ * [Homepage](https://github.com/toyama0919/fluent-plugin-dos_block_acl)
78
+ * [Issues](https://github.com/toyama0919/fluent-plugin-dos_block_acl/issues)
79
+ * [Documentation](http://rubydoc.info/gems/fluent-plugin-dos_block_acl/frames)
80
+ * [Email](mailto:toyama0919@gmail.com)
81
+
82
+ ## Copyright
83
+
84
+ Copyright (c) 2015 Hiroshi Toyama
85
+
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+ end
9
+
10
+ task :default => :test
11
+
@@ -0,0 +1,17 @@
1
+ <source>
2
+ type forward
3
+ port 24224
4
+ </source>
5
+
6
+ <match dos_block_acl.exsample>
7
+ type dos_block_acl
8
+ dryrun true
9
+ network_acl_id acl-xxxxxxx
10
+ ip_address_key ip_address
11
+ dos_threshold 10000
12
+ buffer_chunk_limit 256m
13
+ region ap-northeast-1
14
+ deny_rule_numbers_range 1..10
15
+ time_slice_format %Y%m%d_%H%M
16
+ state_file /var/log/td-agent/buffer/dos_block_acl_state.log
17
+ </match>
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "fluent-plugin-dos_block_acl"
5
+ gem.version = "0.0.1"
6
+ gem.summary = %q{DosBlockAcl plugin for fluentd}
7
+ gem.description = %q{DosBlockAcl plugin for fluentd}
8
+ gem.license = "MIT"
9
+ gem.authors = ["Hiroshi Toyama"]
10
+ gem.email = "toyama0919@gmail.com"
11
+ gem.homepage = "https://github.com/toyama0919/fluent-plugin-dos_block_acl"
12
+
13
+ gem.files = `git ls-files`.split($/)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.require_paths = ['lib']
17
+
18
+ gem.add_runtime_dependency "aws-sdk"
19
+ gem.add_development_dependency 'bundler', '~> 1.7.2'
20
+ gem.add_development_dependency 'fluentd', '~> 0.10.58'
21
+ gem.add_development_dependency 'pry', '~> 0.10.1'
22
+ gem.add_development_dependency 'rake', '~> 10.3.2'
23
+ gem.add_development_dependency 'rubocop', '~> 0.24.1'
24
+ gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
25
+ gem.add_development_dependency 'yard', '~> 0.8'
26
+ end
@@ -0,0 +1,171 @@
1
+ module Fluent
2
+ class DosBlockAclOutput < TimeSlicedOutput
3
+ Fluent::Plugin.register_output('dos_block_acl', self)
4
+
5
+ config_param :network_acl_id, :string
6
+ config_param :dryrun, :bool, :default => false
7
+ config_param :ip_address_key, :string
8
+ config_param :dos_threshold, :integer
9
+ config_param :aws_key_id, :string, :default => nil
10
+ config_param :aws_sec_key, :string, :default => nil
11
+ config_param :region, :string, :default => nil
12
+ config_param :white_list, :string, :default => '127.0.0.1'
13
+ config_param :deny_rule_numbers_range, :string, :default => '1..18'
14
+ config_param :state_file, :string, :default => nil
15
+
16
+ def initialize
17
+ super
18
+ require 'aws-sdk'
19
+ require 'pathname'
20
+ end
21
+
22
+ def configure(conf)
23
+ super
24
+ @white_list = @white_list.split(',')
25
+ unless eval(@deny_rule_numbers_range).class == Range
26
+ raise Fluent::ConfigError, "out_dos_block_acl: @deny_rule_numbers_range is not Range!"
27
+ end
28
+ @deny_rule_numbers_range = eval(@deny_rule_numbers_range).to_a
29
+ @acl_entry_limit = @deny_rule_numbers_range.size
30
+ end
31
+
32
+ def start
33
+ super
34
+ AWS.config(access_key_id: @aws_key_id, secret_access_key: @aws_sec_key, region: @region)
35
+ @ec2 = AWS::EC2::Client.new
36
+ acls = @ec2.describe_network_acls(network_acl_ids: [@network_acl_id])
37
+ @allow_any_rule_number = acls[:network_acl_set].first[:entry_set].select {|r|
38
+ !r[:egress] && r[:cidr_block] == "0.0.0.0/0" && r[:rule_action] == "allow"
39
+ }.first[:rule_number]
40
+
41
+ state = @state_file ? load_status(@state_file) : nil
42
+
43
+ if state.nil?
44
+ @rule_numbers = get_deny_rule_numbers
45
+ @next_rule_index = get_next_rule_index
46
+ else
47
+ @rule_numbers = state[:rule_numbers]
48
+ @next_rule_index = state[:next_rule_index]
49
+ end
50
+ $log.info("out_dos_block_acl: use deny rule numbers => #{@rule_numbers}")
51
+ end
52
+
53
+ def shutdown
54
+ super
55
+ unless @state_file.nil?
56
+ save_status(@state_file)
57
+ end
58
+ end
59
+
60
+ def format(tag, time, record)
61
+ [tag, time, record].to_msgpack
62
+ end
63
+
64
+ def write(chunk)
65
+ begin
66
+ ip_addresses = []
67
+ chunk.msgpack_each do |(tag,time,record)|
68
+ ip_addresses << record[@ip_address_key]
69
+ end
70
+ counts = group_count(ip_addresses)
71
+ dos_hash = counts.select {|k, v| v >= @dos_threshold }
72
+ regist_deny_acl(dos_hash.keys)
73
+ rescue => e
74
+ $log.error("\n#{e.message}\n#{e.backtrace.join("\n")}")
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def group_count(list)
81
+ Hash[list.group_by{ |e| e }.map{ |k, v| [k, v.length] }]
82
+ end
83
+
84
+ def regist_deny_acl(ip_addresses)
85
+ unless ip_addresses.empty?
86
+ ip_addresses.each do |ip_address|
87
+ next if @white_list.include?(ip_address)
88
+ deny_rules = get_deny_rules
89
+ unless deny_rules.any? {|r| r[:cidr_block] == "#{ip_address}/32"}
90
+ @next_rule_index = 0 if @rule_numbers.size == @next_rule_index
91
+ rule_number = @rule_numbers[@next_rule_index]
92
+ if deny_rules.any? {|r| r[:rule_number] == rule_number}
93
+ delete_deny_acl(rule_number)
94
+ end
95
+ option = {
96
+ network_acl_id: @network_acl_id,
97
+ rule_number: rule_number,
98
+ rule_action: 'deny',
99
+ protocol: '-1',
100
+ cidr_block: "#{ip_address}/32",
101
+ egress: false
102
+ }
103
+ @ec2.create_network_acl_entry(option) unless @dryrun
104
+ $log.info("netowork acl regist! deny ip_address => #{ip_address}, rule_number => #{rule_number}")
105
+ @next_rule_index = @next_rule_index + 1
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ def delete_deny_acl(rule_number)
112
+ option = {
113
+ network_acl_id: @network_acl_id,
114
+ rule_number: rule_number,
115
+ egress: false
116
+ }
117
+ $log.info("delete, rule_number => #{rule_number}")
118
+ @ec2.delete_network_acl_entry(option) unless @dryrun
119
+ end
120
+
121
+ def get_deny_rules
122
+ get_all_rules.select {|r|
123
+ !r[:egress] &&
124
+ r[:rule_number] < @allow_any_rule_number &&
125
+ @deny_rule_numbers_range.include?(r[:rule_number])
126
+ }.sort_by {|r| r[:rule_number]}
127
+ end
128
+
129
+ def get_deny_rule_numbers
130
+ get_deny_rules.map { |r| r[:rule_number]}.concat(@deny_rule_numbers_range).uniq.first(@acl_entry_limit)
131
+ end
132
+
133
+ def get_all_rules
134
+ acls = @ec2.describe_network_acls(network_acl_ids: [@network_acl_id])
135
+ acls[:network_acl_set].first[:entry_set]
136
+ end
137
+
138
+ def get_next_rule_index
139
+ deny_rules = get_deny_rules
140
+ next_rule_index = 0
141
+ if get_all_rules.size >= @acl_entry_limit
142
+ next_rule_index = 0
143
+ else
144
+ next_rule_index = deny_rules.empty? ? 0 : deny_rules.size
145
+ end
146
+ next_rule_index
147
+ end
148
+
149
+ def save_status(file_path)
150
+ begin
151
+ Pathname.new(file_path).open('wb') do |f|
152
+ Marshal.dump({
153
+ rule_numbers: @rule_numbers,
154
+ next_rule_index: @next_rule_index
155
+ }, f)
156
+ end
157
+ rescue => e
158
+ $log.warn "out_dos_block_acl: Can't write store_file #{e.class} #{e.message}"
159
+ end
160
+ end
161
+
162
+ def load_status(file_path)
163
+ return nil unless File.exist?(file_path)
164
+ state = Marshal.load(File.read(file_path))
165
+ state[:rule_numbers] = state[:rule_numbers].concat((1..@acl_entry_limit).to_a).uniq.first(@acl_entry_limit)
166
+ state[:next_rule_index] = state[:next_rule_index] > @acl_entry_limit ? 0 : state[:next_rule_index]
167
+ state
168
+ end
169
+ end
170
+ end
171
+
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+ require 'fluent/plugin/out_dos_block_acl'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,59 @@
1
+ require 'helper'
2
+ class DosBlockAclOutputTest < Test::Unit::TestCase
3
+ def setup
4
+ Fluent::Test.setup
5
+ end
6
+
7
+ DEFAULT_CONFIG = %[
8
+ ]
9
+
10
+ def stub_seed_values
11
+ time = Time.parse("2014-01-01 00:00:00 UTC").to_i
12
+ records = [{"a" => 1}, {"a" => 2}]
13
+ return time, records
14
+ end
15
+
16
+ def create_driver(conf = DEFAULT_CONFIG)
17
+ config = %[
18
+ network_acl_id acl-xxxxxx
19
+ ip_address_key ip_address
20
+ dos_threshold 1000
21
+ time_slice_wait 10s
22
+ buffer_path tmp/buffer
23
+ ] + conf
24
+
25
+ Fluent::Test::TimeSlicedOutputTestDriver.new(Fluent::DosBlockAclOutput) do
26
+ def write(chunk)
27
+ chunk.instance_variable_set(:@key, @key)
28
+ super(chunk)
29
+ end
30
+ end.configure(config)
31
+ end
32
+
33
+ def test_configure
34
+ d = create_driver
35
+
36
+ {
37
+ :@dos_threshold => 1000,
38
+ :@deny_rule_numbers_range => (1..18).to_a,
39
+ :@ip_address_key => 'ip_address'
40
+ }.each { |k, v|
41
+ assert_equal(d.instance.instance_variable_get(k), v)
42
+ }
43
+ end
44
+
45
+ def test_configure_error
46
+ assert_raise(Fluent::ConfigError) do
47
+ create_driver(%[deny_rule_numbers_range 1])
48
+ end
49
+
50
+ assert_raise(Fluent::ConfigError) do
51
+ create_driver(%[deny_rule_numbers_range 1.10])
52
+ end
53
+ end
54
+
55
+ def test_emit
56
+ d = create_driver
57
+ time, records = stub_seed_values
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-dos_block_acl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi Toyama
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
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: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.7.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.7.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: fluentd
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.10.58
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.10.58
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.10.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 10.3.2
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 10.3.2
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.24.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.24.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubygems-tasks
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.2'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: yard
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.8'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.8'
125
+ description: DosBlockAcl plugin for fluentd
126
+ email: toyama0919@gmail.com
127
+ executables: []
128
+ extensions: []
129
+ extra_rdoc_files: []
130
+ files:
131
+ - ".document"
132
+ - ".gitignore"
133
+ - ".travis.yml"
134
+ - ".yardopts"
135
+ - Gemfile
136
+ - README.md
137
+ - Rakefile
138
+ - exsample/exsample.conf
139
+ - fluent-plugin-dos_block_acl.gemspec
140
+ - lib/fluent/plugin/out_dos_block_acl.rb
141
+ - test/helper.rb
142
+ - test/plugin/test_out_dos_block_acl.rb
143
+ homepage: https://github.com/toyama0919/fluent-plugin-dos_block_acl
144
+ licenses:
145
+ - MIT
146
+ metadata: {}
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubyforge_project:
163
+ rubygems_version: 2.4.2
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: DosBlockAcl plugin for fluentd
167
+ test_files:
168
+ - test/helper.rb
169
+ - test/plugin/test_out_dos_block_acl.rb
170
+ has_rdoc: