autodrop 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ # configration for autodrop. -*-yaml-*-
2
+
3
+ input: /var/log/authfifo
4
+
5
+ log: syslog
6
+ # log: /var/log/autodrop.log
7
+ # verbose: yes
8
+
9
+ pidfile: /var/run/autodrop.pid
10
+
11
+ drop-command: /sbin/iptables -I INPUT -s %remote_address% -j DROP
12
+ # drop-command: /bin/echo %remote_address%
13
+
14
+ count: 3
15
+ duration: 10 # sec
16
+
17
+ # $1 must match with an IP address
18
+ patterns:
19
+ # OpenSSH
20
+ - "Address (.+) maps to [^\\s]+, but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!"
21
+ - "Did not receive identification string from (.+)"
22
+ - "Failed password for .* from (.+) port .+ ssh2"
23
+ - "Invalid user .+ from (.+)"
24
+ - "Received disconnect from (.+): 11: .* \\[preauth\\]"
@@ -0,0 +1,321 @@
1
+ #!/usr/bin/env ruby
2
+ require "syslog"
3
+ require "trad-getopt"
4
+ require "yaml"
5
+
6
+ class Autodrop
7
+
8
+ VERSION = "1.0.0"
9
+
10
+ class SyslogLogger
11
+ def initialize
12
+ Syslog.open "autodrop", Syslog::LOG_PID|Syslog::LOG_CONS, Syslog::LOG_AUTH
13
+ end
14
+ def log msg
15
+ Syslog.info msg
16
+ end
17
+ end
18
+
19
+ class StdoutLogger
20
+ def log msg
21
+ puts "#{Time.now}: #{msg}"
22
+ end
23
+ end
24
+
25
+ class FileLogger
26
+ def initialize file
27
+ @file = open file, "a+"
28
+ end
29
+ def log msg
30
+ @file.puts "#{Time.now}: #{msg}"
31
+ @file.flush
32
+ end
33
+ end
34
+
35
+ CONF_KEYS = [
36
+ KW_COUNT = "count",
37
+ KW_DAEMON = "daemon",
38
+ KW_DROP_COMMAND = "drop-command",
39
+ KW_DURATION = "duration",
40
+ KW_INPUT = "input",
41
+ KW_LOG = "log",
42
+ KW_PATTERNS = "patterns",
43
+ KW_PIDFILE = "pidfile",
44
+ KW_VERBOSE = "verbose",
45
+ ]
46
+
47
+ CONF_DEFAULTS = {
48
+ KW_COUNT => 3,
49
+ KW_DAEMON => true,
50
+ KW_DROP_COMMAND => [
51
+ "/sbin/iptables", "-I", "INPUT", "-s", "%remote_address%", "-j", "DROP" ],
52
+ KW_DURATION => 10,
53
+ KW_INPUT => "/var/log/authfifo",
54
+ KW_LOG => "syslog",
55
+ KW_PATTERNS => [],
56
+ KW_PIDFILE => "/var/run/autodrop.pid",
57
+ KW_VERBOSE => false,
58
+ }
59
+
60
+ def self.loadconf file
61
+ conf = YAML.load File.read file
62
+ raise "wrong format" unless conf.is_a? Hash
63
+ conf = CONF_DEFAULTS.merge conf
64
+
65
+ badkw = conf.keys - CONF_KEYS
66
+ raise "unknown keywords - #{badkw.join ","}" unless badkw.empty?
67
+
68
+ [ KW_COUNT, KW_DURATION ].each { |k|
69
+ unless conf[k].is_a? Integer
70
+ raise "integer required - #{k}: #{conf[k].inspect}"
71
+ end
72
+ }
73
+ [ KW_DROP_COMMAND, KW_INPUT, KW_LOG, KW_PIDFILE ].each { |k|
74
+ unless conf[k].is_a? String
75
+ raise "string required - #{k}: #{conf[k].inspect}"
76
+ end
77
+ }
78
+ conf[KW_DROP_COMMAND] = conf[KW_DROP_COMMAND].split
79
+
80
+ conf[KW_PATTERNS].map! { |item|
81
+ unless [ String, Regexp ].include? item.class
82
+ raise "string or regexp required - #{item.inspect}"
83
+ end
84
+ Regexp.new item
85
+ }
86
+
87
+ conf
88
+ end
89
+
90
+ def terminate whymsg, status = 0
91
+ @logger.log "terminate (#{whymsg})"
92
+ exit status
93
+ end
94
+ private :terminate
95
+
96
+ class Dog
97
+ def initialize addr, config
98
+ @addr = addr
99
+ @duration = config[KW_DURATION]
100
+ @expire = Time.now + config[KW_DURATION]
101
+ @count_max = config[KW_COUNT]
102
+ @count = 1
103
+ @drop_command = config[KW_DROP_COMMAND].map { |e|
104
+ e == "%remote_address%" ? addr : e
105
+ }
106
+ end
107
+ attr_reader :addr, :count_max, :drop_command, :duration
108
+ attr_accessor :count, :expire
109
+ end
110
+
111
+ def run config
112
+ @logger = nil
113
+ case config[KW_LOG]
114
+ when "syslog"
115
+ @logger = SyslogLogger.new
116
+ when "stdout"
117
+ @logger = StdoutLogger.new
118
+ else
119
+ begin
120
+ @logger = FileLogger.new config[KW_LOG]
121
+ rescue
122
+ warn "autodrop: can not open - #{config[KW_LOG]}"
123
+ exit 1
124
+ end
125
+ end
126
+
127
+ if config[KW_DAEMON]
128
+ if Process.respond_to? :daemon
129
+ Process.daemon
130
+ else
131
+ exit if fork
132
+ Process.setsid
133
+ exit if fork
134
+ Dir.chdir "/"
135
+ STDIN.reopen "/dev/null"
136
+ STDOUT.reopen "/dev/null"
137
+ STDERR.reopen "/dev/null"
138
+ end
139
+ File.write config[KW_PIDFILE], Process.pid
140
+ end
141
+
142
+ trap("SIGINT") { terminate "SIGINT" }
143
+ trap("SIGTERM") { terminate "SIGTERM" }
144
+ trap("SIGHUP") { terminate "SIGHUP" }
145
+
146
+ @logger.log "start watching - #{config[KW_INPUT]}"
147
+ unless File.ftype(config[KW_INPUT]) == "fifo"
148
+ raise "not a named pipe - #{config[KW_INPUT]}"
149
+ end
150
+ fifofp = File.open config[KW_INPUT], File::RDWR
151
+
152
+ @dogs = {}
153
+ loop {
154
+ ready = select [fifofp]
155
+ next unless ready
156
+
157
+ addr = nil
158
+ line = ready[0][0].gets
159
+ config[KW_PATTERNS].each { |pat|
160
+ if line =~ pat
161
+ addr = $1
162
+ break
163
+ end
164
+ }
165
+ next unless addr
166
+
167
+ dog = @dogs[addr]
168
+ if dog
169
+ dog.count += 1
170
+ dog.expire = Time.now + dog.duration
171
+ @logger.log "#{dog.addr} bark! (#{dog.count})" if config[KW_VERBOSE]
172
+ next
173
+ end
174
+
175
+ dog = Dog.new addr, config
176
+ Thread.new(dog) { |dog|
177
+ begin
178
+ @dogs[dog.addr] = dog
179
+
180
+ @logger.log "#{dog.addr} bark! (#{dog.count})" if config[KW_VERBOSE]
181
+
182
+ loop {
183
+ break if Time.now >= dog.expire
184
+
185
+ if dog.count >= dog.count_max
186
+ @logger.log "#{dog.addr} DROP"
187
+ out = IO.popen(dog.drop_command, "r+", :err => [ :child, :out ]) { |io|
188
+ buf = []
189
+ while l = io.gets
190
+ buf.push l.chomp
191
+ end
192
+ buf
193
+ }
194
+ st = $?.exitstatus
195
+ if st != 0
196
+ @logger.log "DROP fail. command exit status #{st}"
197
+ out.each { |l| @logger.log "|#{l}" }
198
+ end
199
+ break
200
+ end
201
+
202
+ # @logger.log "#{dog.addr} grrr" if config[KW_VERBOSE]
203
+ sleep 1
204
+ }
205
+ rescue => ex
206
+ @logger.log "error in worker"
207
+ @logger.log "|#{ex}"
208
+ ex.backtrace.each { |l| @logger.log "|#{l}" }
209
+ ensure
210
+ @dogs.delete dog.addr
211
+ @logger.log "#{dog.addr} leave" if config[KW_VERBOSE]
212
+ end
213
+ }
214
+ }
215
+ rescue => ex
216
+ @logger.log ex.message
217
+ ex.backtrace.each { |l| @logger.log "|#{l}" }
218
+ terminate "error", 1
219
+ ensure
220
+ File.unlink config[KW_PIDFILE] rescue nil
221
+ end
222
+ end
223
+
224
+ ### main
225
+
226
+ DEFAULT_CONFFILE = "/etc/autodrop.conf"
227
+
228
+ def usage
229
+ puts <<EOD
230
+ usage: autodrop [options]
231
+ -c, --config=FILE specify config file
232
+ -h, --help print this
233
+ -i, --input=FILE specify input source
234
+ -l, --log=FILE specify log file or `syslog'
235
+ -n, --no-daemon run as no daemon
236
+ -p, --pidfile=FILE specify PID file
237
+ -V, --version print version
238
+ EOD
239
+ end
240
+
241
+ conffile = nil
242
+ opts = "c:i:hl:np:V"
243
+ longopts = {
244
+ "config" => :required_argument,
245
+ "help" => :no_argument,
246
+ "input" => :required_argument,
247
+ "log" => :required_argument,
248
+ "no-daemon" => :no_argument,
249
+ "pidfile" => :required_argument,
250
+ "version" => :no_argument,
251
+ }
252
+
253
+ av = ARGV.dup
254
+ while (op, optarg = getopt(av, opts, longopts,
255
+ allow_empty_optarg:false, permute:true))
256
+ case op
257
+ when "c", "config"
258
+ if optarg[0] == "/"
259
+ conffile = optarg
260
+ else
261
+ conffile = File.join Dir.pwd, optarg
262
+ end
263
+ when "h", "help"
264
+ usage
265
+ exit
266
+ when "V", "version"
267
+ puts Autodrop::VERSION
268
+ exit
269
+ else
270
+ exit 1 if op.is_a? Symbol
271
+ end
272
+ end
273
+
274
+ conffile ||= DEFAULT_CONFFILE
275
+ begin
276
+ conf = Autodrop.loadconf conffile
277
+ rescue => ex
278
+ warn "autodrop: #{conffile} - #{ex}"
279
+ exit 1
280
+ end
281
+
282
+ while (op, optarg = getopt ARGV, opts, longopts, allow_empty_optarg:false)
283
+ case op
284
+ when "c", "config", "h", "help", "V", "version"
285
+ ;
286
+ when "i", "input"
287
+ if optarg[0] == "/"
288
+ conf[Autodrop::KW_INPUT] = optarg
289
+ else
290
+ conf[Autodrop::KW_INPUT] = File.join Dir.pwd, optarg
291
+ end
292
+ when "l", "log"
293
+ case optarg
294
+ when "syslog"
295
+ conf[Autodrop::KW_LOG] = "syslog"
296
+ else
297
+ if optarg[0] == "/"
298
+ conf[Autodrop::KW_LOG] = optarg
299
+ else
300
+ conf[Autodrop::KW_LOG] = File.join Dir.pwd, optarg
301
+ end
302
+ end
303
+ when "n", "no-daemon"
304
+ conf[Autodrop::KW_DAEMON] = false
305
+ conf[Autodrop::KW_LOG] = "stdout"
306
+ when "p", "pidfile"
307
+ if optarg[0] == "/"
308
+ conf[Autodrop::KW_PIDFILE] = optarg
309
+ else
310
+ conf[Autodrop::KW_PIDFILE] = File.join Dir.pwd, optarg
311
+ end
312
+ else
313
+ exit 1
314
+ end
315
+ end
316
+ unless ARGV.empty?
317
+ warn "autodrop: no arguments required - #{ARGV.join " "}"
318
+ exit 1
319
+ end
320
+
321
+ Autodrop.new.run conf
@@ -0,0 +1,19 @@
1
+ #!/bin/sh
2
+
3
+ AUTODROP=/usr/local/sbin/autodrop
4
+ CONF=/etc/autodrop.conf
5
+ FIFO=/var/log/authfifo
6
+ PIDFILE=/var/run/autodrop.pid
7
+
8
+ case "$1" in
9
+ start)
10
+ $AUTODROP --config="$CONF" --input="$FIFO" --pidfile="$PIDFILE"
11
+ ;;
12
+ stop)
13
+ kill -TERM `cat ${PIDFILE}`
14
+ ;;
15
+ *)
16
+ echo "usage: $0 { start | stop }" >&2
17
+ exit 1
18
+ ;;
19
+ esac
@@ -0,0 +1,66 @@
1
+ autodrop
2
+
3
+ ホストへのしつこいアクセスを iptables で弾くデーモン。
4
+
5
+ 名前つきパイプ経由の syslog のログを監視して、パターンにマッチするログ
6
+ を繰り返し生じさせるホストを DROP するルールを追加する。
7
+
8
+ syslogd が名前つきパイプへログを吐くよう設定する必要がある。
9
+
10
+ INPUT テーブルに DROP ルールが溜まるので定期的な手動でのクリアが必要。
11
+
12
+ この手のツールの常として自分を自分のホストから締め出すことも可能な点に
13
+ 注意。
14
+
15
+ = インストール・実行
16
+
17
+ gem を install したあと設定ファイルを /etc 等にコピー・編集。
18
+
19
+ 実行には同梱の autodrop.sh を使うとたぶん便利。
20
+
21
+ = autodrop.conf
22
+
23
+ フォーマットは YAML。
24
+
25
+ - count
26
+ DROP を実行するまでのマッチ回数。デフォルトは 3。
27
+
28
+ - drop-command
29
+ DROP に使うコマンド (とその引数)。
30
+ デフォルトは "/sbin/iptables -I INPUT -s %remote_address% -j DROP"。
31
+ "%remote_address%" の部分が DROP 対象とするホストの IP アドレスに置
32
+ き換えられる。
33
+
34
+ - duration
35
+ マッチしてから監視を継続する秒数。デフォルトは 10 秒。
36
+ マッチするたびにリセットされる。
37
+
38
+ - input
39
+ ログを読みとるパイプ。デフォルトは /var/log/authfifo。mkfifo(1) で作
40
+ り、syslogd 側の設定として syslog.conf に
41
+ ------------------------------------------
42
+ authpriv.* |/var/log/authfifo
43
+ ------------------------------------------
44
+ のように書く必要がある。詳しくは syslog.conf(5) で。
45
+
46
+ - log
47
+ 起動、終了、DROP の発生など autodrop 自身のログ出力先。デフォルトは
48
+ syslog。これ以外の値を設定するとファイル名と見なし、そのファイルに出
49
+ 力する。syslog のログにはプレフィクス `autodrop' をつける。
50
+
51
+ - patterns
52
+ ログにマッチさせる正規表現文字列のリスト。
53
+ $1 がリモートホストの IP アドレスにマッチするように書くこと。
54
+
55
+ - pidfile
56
+ PID ファイル名。PID ファイルはデーモン動作時にのみ作られる。
57
+ デフォルトは /var/run/autodrop.pid
58
+
59
+ - verbose
60
+ yes か no (boolean)。デフォルトは no。だいたいデバッグ用。
61
+
62
+ = ライセンス
63
+
64
+ BSD-2-Clause
65
+
66
+ #eof
metadata CHANGED
@@ -1,61 +1,67 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: autodrop
3
- version: !ruby/object:Gem::Version
4
- version: 0.1.0
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
5
  platform: ruby
6
- authors:
6
+ authors:
7
7
  - NOZAWA Hiromasa
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: "."
10
10
  cert_chain: []
11
-
12
- date: 2009-05-10 00:00:00 +09:00
13
- default_executable:
14
- dependencies: []
15
-
11
+ date: 2017-03-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: trad-getopt
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'
16
27
  description:
17
28
  email:
18
- executables:
29
+ executables:
19
30
  - autodrop
20
31
  extensions: []
21
-
22
32
  extra_rdoc_files: []
23
-
24
- files:
33
+ files:
34
+ - "./autodrop"
25
35
  - ChangeLog
36
+ - LICENSE
26
37
  - Rakefile
27
- - bin/autodrop
28
- - bin/autodrop.rb
29
- - conf/autodrop.conf.default
30
- - doc/README.jp.txt
31
- - doc/README.txt
32
- - misc/autodrop.sh
33
- has_rdoc: false
34
- homepage: http://rubyforge.org/projects/autodrop
38
+ - autodrop
39
+ - autodrop.conf.default
40
+ - autodrop.rb
41
+ - autodrop.sh
42
+ - autodrop.txt
43
+ homepage: https://github.com/noz/autodrop
44
+ licenses:
45
+ - BSD-2-Clause
46
+ metadata: {}
35
47
  post_install_message:
36
48
  rdoc_options: []
37
-
38
- require_paths:
39
- - []
40
-
41
- required_ruby_version: !ruby/object:Gem::Requirement
42
- requirements:
49
+ require_paths:
50
+ - "."
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
43
53
  - - ">="
44
- - !ruby/object:Gem::Version
45
- version: "0"
46
- version:
47
- required_rubygems_version: !ruby/object:Gem::Requirement
48
- requirements:
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
49
58
  - - ">="
50
- - !ruby/object:Gem::Version
51
- version: "0"
52
- version:
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
53
61
  requirements: []
54
-
55
- rubyforge_project: autodrop
56
- rubygems_version: 1.3.1
62
+ rubyforge_project:
63
+ rubygems_version: 2.6.8
57
64
  signing_key:
58
- specification_version: 2
65
+ specification_version: 4
59
66
  summary: Automatic iptables DROP daemon
60
67
  test_files: []
61
-