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.
- checksums.yaml +7 -0
- data/ChangeLog +13 -0
- data/LICENSE +24 -0
- data/Rakefile +31 -66
- data/autodrop +321 -0
- data/autodrop.conf.default +24 -0
- data/autodrop.rb +321 -0
- data/autodrop.sh +19 -0
- data/autodrop.txt +66 -0
- metadata +47 -41
- data/bin/autodrop +0 -202
- data/bin/autodrop.rb +0 -202
- data/conf/autodrop.conf.default +0 -17
- data/doc/README.jp.txt +0 -164
- data/doc/README.txt +0 -165
- data/misc/autodrop.sh +0 -18
@@ -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\\]"
|
data/autodrop.rb
ADDED
@@ -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
|
data/autodrop.sh
ADDED
@@ -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
|
data/autodrop.txt
ADDED
@@ -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:
|
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:
|
9
|
+
bindir: "."
|
10
10
|
cert_chain: []
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
33
|
+
files:
|
34
|
+
- "./autodrop"
|
25
35
|
- ChangeLog
|
36
|
+
- LICENSE
|
26
37
|
- Rakefile
|
27
|
-
-
|
28
|
-
-
|
29
|
-
-
|
30
|
-
-
|
31
|
-
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
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:
|
46
|
-
|
47
|
-
|
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:
|
52
|
-
version:
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
53
61
|
requirements: []
|
54
|
-
|
55
|
-
|
56
|
-
rubygems_version: 1.3.1
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 2.6.8
|
57
64
|
signing_key:
|
58
|
-
specification_version:
|
65
|
+
specification_version: 4
|
59
66
|
summary: Automatic iptables DROP daemon
|
60
67
|
test_files: []
|
61
|
-
|