spanx 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +10 -0
- data/Guardfile +4 -4
- data/README.md +37 -3
- data/conf/spanx-config.yml.example +5 -0
- data/lib/spanx.rb +1 -0
- data/lib/spanx/actor/analyzer.rb +1 -0
- data/lib/spanx/actor/log_reader.rb +18 -17
- data/lib/spanx/api.rb +7 -0
- data/lib/spanx/api/machine.rb +20 -0
- data/lib/spanx/api/resources/blocked_ips.rb +15 -0
- data/lib/spanx/api/resources/unblock_ip.rb +17 -0
- data/lib/spanx/cli.rb +12 -7
- data/lib/spanx/cli/analyze.rb +7 -6
- data/lib/spanx/cli/api.rb +70 -0
- data/lib/spanx/cli/disable.rb +2 -1
- data/lib/spanx/cli/enable.rb +2 -1
- data/lib/spanx/cli/flush.rb +27 -2
- data/lib/spanx/cli/report.rb +77 -0
- data/lib/spanx/cli/watch.rb +18 -11
- data/lib/spanx/helper/exit.rb +6 -2
- data/lib/spanx/helper/subclassing.rb +9 -0
- data/lib/spanx/logger.rb +1 -1
- data/lib/spanx/notifier/slack.rb +42 -0
- data/lib/spanx/runner.rb +1 -1
- data/lib/spanx/usage.rb +11 -7
- data/lib/spanx/version.rb +1 -1
- data/spanx.gemspec +3 -9
- data/spec/spanx/actor/analyzer_spec.rb +5 -5
- data/spec/spanx/actor/log_reader_spec.rb +78 -41
- data/spec/spanx/api/machine_spec.rb +33 -0
- data/spec/spanx/cli/cli_spec.rb +22 -0
- data/spec/spanx/config_spec.rb +2 -2
- data/spec/spanx/notifier/email_spec.rb +1 -1
- data/spec/spanx/notifier/slack_spec.rb +77 -0
- data/spec/spanx/runner_spec.rb +33 -33
- data/spec/spanx/usage_spec.rb +13 -0
- data/spec/spanx/whitelist_spec.rb +24 -24
- data/spec/spec_helper.rb +1 -0
- metadata +46 -111
- data/.pairs +0 -13
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5c017e3394baa42289958507173ac915803057d2
|
4
|
+
data.tar.gz: 207763061ed579e4bc95da27f12c81349e568b0c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c0b1431c0d6dd273e559dcc67a2d6a297b6212e1e7eca50cf61eca24569dd97677fb19548564d6070df087faa819c8d55d966aafec2030d4c460719028dc5a81
|
7
|
+
data.tar.gz: 6439de18b7f3fffadacd21739a63d614faec491e4085e7c85516e64cb0066fb9b4cadbc3401954648ee7e1703c4e92efdaa106595dbd3b25dce8b58a7affddbb
|
data/Gemfile
CHANGED
@@ -2,3 +2,13 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
# Specify your gem's dependencies in spanx.gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
group :test do
|
7
|
+
gem 'rspec', '~> 2.14'
|
8
|
+
gem 'fakeredis'
|
9
|
+
gem 'timecop'
|
10
|
+
gem 'webmock'
|
11
|
+
gem 'guard-rspec'
|
12
|
+
gem 'rb-fsevent'
|
13
|
+
gem 'webmachine-test'
|
14
|
+
end
|
data/Guardfile
CHANGED
@@ -4,10 +4,10 @@
|
|
4
4
|
# A sample Guardfile
|
5
5
|
# More info at https://github.com/guard/guard#readme
|
6
6
|
|
7
|
-
guard 'rspec' do
|
8
|
-
watch(%r{^spanx\.gemspec}) {
|
7
|
+
guard 'rspec', cmd: 'bundle exec rspec' do
|
8
|
+
watch(%r{^spanx\.gemspec}) { 'spec' }
|
9
9
|
watch(%r{^spec/.+_spec\.rb$})
|
10
|
-
watch(%r{^lib/(.+)\.rb$}) {
|
11
|
-
watch('spec/spec_helper.rb') {
|
10
|
+
watch(%r{^lib/(.+)\.rb$}) { 'spec' }
|
11
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
12
12
|
end
|
13
13
|
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
Spanx
|
2
2
|
=====
|
3
3
|
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/spanx.png)](http://badge.fury.io/rb/spanx)
|
4
5
|
[![Build status](https://secure.travis-ci.org/wanelo/spanx.png)](http://travis-ci.org/wanelo/spanx)
|
5
6
|
|
6
7
|
Spank down IP spam: IP-based rate limiting for web applications behind HTTP server such as nginx or Apache.
|
@@ -43,9 +44,7 @@ If you have multiple web servers, you need to run watcher on each server, and an
|
|
43
44
|
### Alerts
|
44
45
|
|
45
46
|
Besides actually writing out IPs to a block list file, Spanx supports notifiers that will be called when a new IP
|
46
|
-
is blocked. Currently supported are audit log notifier (that writes that information to a log file), a Campfire
|
47
|
-
Chat notifier, which will print IP blocking information into your Campfire chat room, and an Email notifier. It is
|
48
|
-
very easy to write additional notifiers.
|
47
|
+
is blocked. Currently supported are audit log notifier (that writes that information to a log file), both a Campfire and Slack chat notifier (which will print IP blocking information into each respective chat room), and an Email notifier. It is very easy to write additional notifiers.
|
49
48
|
|
50
49
|
## Installation
|
51
50
|
|
@@ -146,6 +145,41 @@ Usage: [bundle exec] spanx flush [options]
|
|
146
145
|
-h, --help Show this message
|
147
146
|
```
|
148
147
|
|
148
|
+
### api
|
149
|
+
|
150
|
+
This starts an HTTP server with endpoints for managing blocked ips. Your
|
151
|
+
application (or admin interface) can connect to this, for example.
|
152
|
+
|
153
|
+
```bash
|
154
|
+
Usage: [bundle exec] spanx api [options]
|
155
|
+
-c, --config CONFIG Path to config file (YML) (required)
|
156
|
+
-g, --debug Log status to STDOUT
|
157
|
+
-h, --help Show this message
|
158
|
+
-h, --host Host for the HTTP server to listen on
|
159
|
+
-p, --port Port for the HTTP server to listen on
|
160
|
+
```
|
161
|
+
|
162
|
+
#### Endpoints:
|
163
|
+
|
164
|
+
To retrieve a list of currently blocked ips:
|
165
|
+
|
166
|
+
```
|
167
|
+
GET /ips/blocked
|
168
|
+
[
|
169
|
+
"127.0.0.1",
|
170
|
+
"11.100.193.12"
|
171
|
+
]
|
172
|
+
```
|
173
|
+
|
174
|
+
To unblock a specific ip:
|
175
|
+
|
176
|
+
This will remove the IP from redis and shortly afterwards it will be removed
|
177
|
+
from the nginx block files.
|
178
|
+
|
179
|
+
```
|
180
|
+
DELETE /ips/blocked/11.100.193.12
|
181
|
+
```
|
182
|
+
|
149
183
|
## Examples
|
150
184
|
|
151
185
|
If you have only one load balancer, you may want to centralize all work into a single process, as such:
|
@@ -18,6 +18,7 @@
|
|
18
18
|
- "Spanx::Notifier::AuditLog"
|
19
19
|
- "Spanx::Notifier::Campfire"
|
20
20
|
- "Spanx::Notifier::Email"
|
21
|
+
- "Spanx::Notifier::Slack"
|
21
22
|
:period_checks:
|
22
23
|
- :period_seconds: 3600
|
23
24
|
:max_allowed: 2000
|
@@ -35,6 +36,10 @@
|
|
35
36
|
:room_id: 1111
|
36
37
|
:token: aaffdfsdfadfasdfasdfasdf
|
37
38
|
:account: test
|
39
|
+
:slack:
|
40
|
+
:enabled: true
|
41
|
+
:token: aaffdfsdfadfasdfasdfasdf
|
42
|
+
:base_url: 'https://wanelo.slack.com'
|
38
43
|
:email:
|
39
44
|
:enabled: true
|
40
45
|
:to: "everyone@mycompany.com"
|
data/lib/spanx.rb
CHANGED
data/lib/spanx/actor/analyzer.rb
CHANGED
@@ -3,36 +3,37 @@ require 'file-tail'
|
|
3
3
|
module Spanx
|
4
4
|
module Actor
|
5
5
|
class LogReader
|
6
|
-
attr_accessor :
|
6
|
+
attr_accessor :files, :queue, :whitelist, :threads
|
7
7
|
|
8
|
-
def initialize
|
9
|
-
@
|
10
|
-
@
|
11
|
-
|
8
|
+
def initialize files, queue, interval = 1, whitelist = nil
|
9
|
+
@files = Array(files).uniq.map { |file| Spanx::Actor::File.new(file) }
|
10
|
+
@files.each do |file|
|
11
|
+
file.interval = interval
|
12
|
+
file.backward(0)
|
13
|
+
end
|
12
14
|
@whitelist = whitelist
|
13
15
|
@queue = queue
|
16
|
+
@threads = []
|
14
17
|
end
|
15
18
|
|
16
19
|
def run
|
17
|
-
|
18
|
-
Thread.
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
files.each_with_index do |file, i|
|
21
|
+
threads << Thread.new do
|
22
|
+
Thread.current[:name] = "log_reader.#{i}"
|
23
|
+
Logger.log "tailing the log file #{file.path}...."
|
24
|
+
self.read(file) do |line|
|
25
|
+
queue << [line, Time.now.to_i] if line
|
26
|
+
end
|
22
27
|
end
|
23
28
|
end
|
24
29
|
end
|
25
30
|
|
26
|
-
def read
|
27
|
-
|
28
|
-
|
31
|
+
def read file
|
32
|
+
file.tail do |line|
|
33
|
+
yield extract_ip(line) unless whitelist && whitelist.match?(line)
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
32
|
-
def close
|
33
|
-
(@file.close if @file) rescue nil
|
34
|
-
end
|
35
|
-
|
36
37
|
def extract_ip line
|
37
38
|
matchers = line.match(/^((\d{1,3}\.?){4})/)
|
38
39
|
matchers[1] unless matchers.nil?
|
data/lib/spanx/api.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'webmachine'
|
2
|
+
|
3
|
+
require 'spanx/api/resources/blocked_ips'
|
4
|
+
require 'spanx/api/resources/unblock_ip'
|
5
|
+
|
6
|
+
module Spanx
|
7
|
+
module API
|
8
|
+
Machine = Webmachine::Application.new do |app|
|
9
|
+
app.routes do
|
10
|
+
# DELETE /ips/blocked/127.0.0.1
|
11
|
+
add ["ips", "blocked", :ip],
|
12
|
+
->(req) { req.method == "DELETE" },
|
13
|
+
Resources::UnblockIP
|
14
|
+
|
15
|
+
# GET /ips/blocked
|
16
|
+
add ["ips", "blocked"], Resources::BlockedIps
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Spanx
|
2
|
+
module API
|
3
|
+
module Resources
|
4
|
+
class UnblockIP < Webmachine::Resource
|
5
|
+
def allowed_methods
|
6
|
+
%W[DELETE]
|
7
|
+
end
|
8
|
+
|
9
|
+
def delete_resource
|
10
|
+
Spanx::IPChecker.new(request.path_info[:ip]).unblock
|
11
|
+
JSON.generate({ok: true})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
data/lib/spanx/cli.rb
CHANGED
@@ -20,9 +20,14 @@ module Spanx
|
|
20
20
|
private
|
21
21
|
|
22
22
|
def validate!
|
23
|
-
error_exit_with_msg(
|
23
|
+
error_exit_with_msg('No command given') if args.empty?
|
24
24
|
@command = args.first
|
25
|
-
|
25
|
+
if !@command.eql?('-h') && !@command.eql?('--help')
|
26
|
+
error_exit_with_msg("No command found matching #{@command}") unless Spanx::CLI.subclasses.include?(@command)
|
27
|
+
else
|
28
|
+
help_exit
|
29
|
+
end
|
30
|
+
|
26
31
|
end
|
27
32
|
|
28
33
|
def generate_config(argv)
|
@@ -35,13 +40,13 @@ module Spanx
|
|
35
40
|
end
|
36
41
|
|
37
42
|
Spanx::Logger.enable if config[:debug]
|
43
|
+
rescue OptionParser::InvalidOption => e
|
44
|
+
error_exit_with_msg "Whoops, #{e.message}"
|
38
45
|
end
|
39
46
|
|
40
47
|
end
|
41
48
|
end
|
42
49
|
|
43
|
-
|
44
|
-
require
|
45
|
-
|
46
|
-
require 'spanx/cli/enable'
|
47
|
-
require 'spanx/cli/flush'
|
50
|
+
Dir.glob("#{File.expand_path('../cli', __FILE__)}/*.rb").each do |file|
|
51
|
+
require file
|
52
|
+
end
|
data/lib/spanx/cli/analyze.rb
CHANGED
@@ -6,11 +6,12 @@ require 'spanx/runner'
|
|
6
6
|
|
7
7
|
class Spanx::CLI::Analyze < Spanx::CLI
|
8
8
|
|
9
|
-
banner
|
9
|
+
banner 'Usage: spanx analyze [options]'
|
10
|
+
description 'Analyze IP traffic and save blocked IPs into Redis'
|
10
11
|
|
11
12
|
option :daemonize,
|
12
|
-
:short =>
|
13
|
-
:long =>
|
13
|
+
:short => '-d',
|
14
|
+
:long => '--daemonize',
|
14
15
|
:boolean => true,
|
15
16
|
:default => false
|
16
17
|
|
@@ -35,9 +36,9 @@ class Spanx::CLI::Analyze < Spanx::CLI
|
|
35
36
|
:required => false
|
36
37
|
|
37
38
|
option :help,
|
38
|
-
:short =>
|
39
|
-
:long =>
|
40
|
-
:description =>
|
39
|
+
:short => '-h',
|
40
|
+
:long => '--help',
|
41
|
+
:description => 'Show this message',
|
41
42
|
:on => :tail,
|
42
43
|
:boolean => true,
|
43
44
|
:show_options => true,
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'mixlib/cli'
|
3
|
+
require 'daemons/daemonize'
|
4
|
+
require 'spanx/logger'
|
5
|
+
require 'spanx/api/machine'
|
6
|
+
|
7
|
+
class Spanx::CLI::Api < Spanx::CLI
|
8
|
+
|
9
|
+
banner 'Usage: spanx api [options]'
|
10
|
+
description 'Start HTTP server for controlling Spanx (experimental)'
|
11
|
+
|
12
|
+
option :daemonize,
|
13
|
+
:short => '-d',
|
14
|
+
:long => '--daemonize',
|
15
|
+
:boolean => true,
|
16
|
+
:default => false
|
17
|
+
|
18
|
+
option :config_file,
|
19
|
+
:short => '-c CONFIG',
|
20
|
+
:long => '--config CONFIG',
|
21
|
+
:description => 'Path to config file (YML)',
|
22
|
+
:required => true
|
23
|
+
|
24
|
+
option :debug,
|
25
|
+
:short => '-g',
|
26
|
+
:long => '--debug',
|
27
|
+
:description => 'Log status to STDOUT',
|
28
|
+
:boolean => true,
|
29
|
+
:required => false,
|
30
|
+
:default => false
|
31
|
+
|
32
|
+
option :host,
|
33
|
+
:short => '-h HOST',
|
34
|
+
:long => '--host HOST',
|
35
|
+
:description => 'Host for the api to listen on.',
|
36
|
+
:default => '127.0.0.1',
|
37
|
+
:required => false
|
38
|
+
|
39
|
+
option :port,
|
40
|
+
:short => '-p PORT',
|
41
|
+
:long => '--port PORT',
|
42
|
+
:description => "Port for the api to listen on.",
|
43
|
+
:default => 6060,
|
44
|
+
:required => false
|
45
|
+
|
46
|
+
option :help,
|
47
|
+
:short => "-h",
|
48
|
+
:long => "--help",
|
49
|
+
:description => "Show this message",
|
50
|
+
:on => :tail,
|
51
|
+
:boolean => true,
|
52
|
+
:show_options => true,
|
53
|
+
:exit => 0
|
54
|
+
|
55
|
+
def run(argv = ARGV)
|
56
|
+
generate_config(argv)
|
57
|
+
|
58
|
+
puts "Starting Spanx API on #{config[:host]}:#{config[:port]}.."
|
59
|
+
|
60
|
+
Daemonize.daemonize if config[:daemonize]
|
61
|
+
|
62
|
+
Spanx::API::Machine.configure do |c|
|
63
|
+
c.port = config[:port]
|
64
|
+
c.ip = config[:host]
|
65
|
+
end
|
66
|
+
|
67
|
+
Spanx::API::Machine.run
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
data/lib/spanx/cli/disable.rb
CHANGED
data/lib/spanx/cli/enable.rb
CHANGED
data/lib/spanx/cli/flush.rb
CHANGED
@@ -3,7 +3,20 @@ require 'spanx/logger'
|
|
3
3
|
|
4
4
|
class Spanx::CLI::Flush < Spanx::CLI
|
5
5
|
|
6
|
-
banner
|
6
|
+
banner 'Usage: spanx flush [ -a | -i IPADDRESS ] [options]'
|
7
|
+
description 'Remove a specific IP block, or all blocked IPs'
|
8
|
+
|
9
|
+
option :ip,
|
10
|
+
:short => '-i IPADDRESS',
|
11
|
+
:long => '--ip IPADDRESS',
|
12
|
+
:description => 'Unblock specific IP',
|
13
|
+
:required => false
|
14
|
+
|
15
|
+
option :all,
|
16
|
+
:short => '-a',
|
17
|
+
:long => '--all',
|
18
|
+
:description => 'Unblock all IPs',
|
19
|
+
:required => false
|
7
20
|
|
8
21
|
option :config_file,
|
9
22
|
:short => '-c CONFIG',
|
@@ -31,6 +44,18 @@ class Spanx::CLI::Flush < Spanx::CLI
|
|
31
44
|
|
32
45
|
def run(argv = ARGV)
|
33
46
|
generate_config(argv)
|
34
|
-
|
47
|
+
out = ''
|
48
|
+
keys = if config[:ip] =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
49
|
+
out << "unblocking ip #{config[:ip]}: "
|
50
|
+
Spanx::IPChecker.new(config[:ip]).unblock
|
51
|
+
elsif config[:all]
|
52
|
+
out << 'unblocking all IPs: ' if config[:debug]
|
53
|
+
Spanx::IPChecker.unblock_all
|
54
|
+
else
|
55
|
+
error_exit_with_msg 'Either -i or -a flag is required now'
|
56
|
+
end
|
57
|
+
out << "deleted #{keys} IPs that matched"
|
58
|
+
puts out if config[:debug]
|
59
|
+
out
|
35
60
|
end
|
36
61
|
end
|