autodrop 0.1.0 → 1.0.0

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,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
-