Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Fail2ban notifier #206

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,34 @@ The API token to allow access to your Campfire account.

For more options to set Campfire, like _ssl_, check [here](https://github.com/collectiveidea/tinder/blob/master/lib/tinder/campfire.rb#L17).

### Fail2ban notifier

This notifier creates a log file you can then parse with [fail2ban](http://www.fail2ban.org/)

#### Usage

To configure the notifier, you don't need anything, although you can customise the `logifile` value, like this:

```ruby
Whatever::Application.config.middleware.use ExceptionNotification::Rack,
:email => {
:email_prefix => "[Whatever] ",
:sender_address => %{"notifier" <[email protected]>},
:exception_recipients => %w{[email protected]}
},
:fail2ban => {
:logfile => '/path/to/logs/fail2ban.log'
}
```

#### Options

##### logfile

*String, not required*

An alternative log file location. By default Rails.root.join('log', 'fail2ban.log')


### HipChat notifier

Expand Down
52 changes: 52 additions & 0 deletions lib/exception_notifier/fail2ban_notifer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# This notifier outputs exception details in a log file that you can parse with
# fail2ban to detect possible attacks..
#
# Fail2ban jail configuration (append to /etc/fail2ban/jail.local):
#
# [rails-app]
# enabled = true
# port = http,https
# filter = rails-app
# logpath = /path/to/app/log/fail2ban.log
# bantime = 3600
# findtime = 600
# maxretry = 10
#
#
# Fail2ban filter configuration (save in /etc/fail2ban/filters.d/rails-app.conf):
# [Definition]
# failregex = : <HOST> :
# ignoreregex =
#
require 'action_dispatch'

module ExceptionNotifier
class Fail2banNotifier

# This notifier only accepts a :logfile option that should point to a valid
# file that will be used to log exception entries. Point fail2ban to this
# file
def initialize(options)
@default_options = options
@default_options[:logfile] ||= Rails.root.join('log', 'fail2ban.log')

# Roll over every 30M, keep 10 files
@logger ||= Logger.new(@default_options[:logfile], 10, 30*1024*1024)
end

def call(exception, options={})
env = options[:env]
request = ActionDispatch::Request.new(env)

# <ip> : <exception class> : <method> <path> -- <params>
msg = "%s : %s : %s %s -- %s" % [
request.remote_ip,
exception.class,
request.request_method,
env["PATH_INFO"],
request.filtered_parameters.inspect
]
@logger.error(msg)
end
end
end
58 changes: 58 additions & 0 deletions test/exception_notifier/fail2ban_notifier_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require 'test_helper'
require 'httparty'

class Fail2banNotifierTest < ActiveSupport::TestCase
test "should write exception notification to fail2ban log" do
custom_log = Rails.root.join('tmp/fail2ban.log')

ExceptionNotifier::Fail2banNotifier.stubs(:new).returns(Object.new)
fail2ban = ExceptionNotifier::Fail2banNotifier.new({:logfile => custom_log})
fail2ban.stubs(:call).returns(fake_response)

notif = fail2ban.call(fake_exception)

assert File.exists?(custom_log)
last_line = File.read_lines(custom_log).last

# Very basic test that just verifies we've got a remote IP and an
# exception class in no particular order
assert last_line.include? fake_response[:body][:request][:ip_address]
assert last_line.include? fake_response[:body][:exception][:error_class]
end

private

def fake_response
{
:status => 200,
:body => {
:exception => {
:error_class => 'ZeroDivisionError',
:message => 'divided by 0',
:backtrace => '/exception_notification/test/webhook_notifier_test.rb:48:in `/'
},
:data => {
:extra_data => {:data_item1 => "datavalue1", :data_item2 => "datavalue2"}
},
:request => {
:cookies => {:cookie_item1 => 'cookieitemvalue1', :cookie_item2 => 'cookieitemvalue2'},
:url => 'http://example.com/example',
:ip_address => '192.168.1.1',
:environment => {:env_item1 => "envitem1", :env_item2 => "envitem2"},
:controller => '#<ControllerName:0x007f9642a04d00>',
:session => {:session_item1 => "sessionitem1", :session_item2 => "sessionitem2"},
:parameters => {:action =>"index", :controller =>"projects"}
}
}
}
end

def fake_exception
exception = begin
5/0
rescue Exception => e
e
end
end

end