ssh_guard 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem 'sequel'
6
+ gem 'sqlite3-ruby'
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "rspec", "~> 2.1.0"
11
+ gem "bundler", "~> 1.0.0"
12
+ gem "jeweler", "~> 1.5.1"
13
+ gem "rcov", ">= 0"
14
+ end
@@ -0,0 +1,32 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.5.1)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.8.7)
11
+ rcov (0.9.9)
12
+ rspec (2.1.0)
13
+ rspec-core (~> 2.1.0)
14
+ rspec-expectations (~> 2.1.0)
15
+ rspec-mocks (~> 2.1.0)
16
+ rspec-core (2.1.0)
17
+ rspec-expectations (2.1.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.1.0)
20
+ sequel (3.17.0)
21
+ sqlite3-ruby (1.3.2)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ bundler (~> 1.0.0)
28
+ jeweler (~> 1.5.1)
29
+ rcov
30
+ rspec (~> 2.1.0)
31
+ sequel
32
+ sqlite3-ruby
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jelle Helsen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ = SshGuard
2
+
3
+ SshGuard is guardian angel for ssh. It protects your server from ssh password guessing bots. It scans the log file for ssh errors containing 'Invalid user' of "authentication error". After 10 failed authentications the ip address of the failing host will be blocked by the firewall.
4
+
5
+ At the moment it only works on Mac OS X. Porting it to other unix based platforms should not be that hard.
6
+
7
+ == Installation
8
+
9
+ Installing SshGuard is simple:
10
+
11
+ sudo gem install ssh_guard
12
+
13
+ == TODO
14
+
15
+ * Create a lauchd plist file
16
+ * Port to other platforms
17
+
18
+ == Contributing to ssh_guard
19
+
20
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
21
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
22
+ * Fork the project
23
+ * Start a feature/bugfix branch
24
+ * Commit and push until you are happy with your contribution
25
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
26
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
27
+
28
+ == Copyright
29
+
30
+ Copyright (c) 2010 Jelle Helsen. See LICENSE.txt for
31
+ further details.
32
+
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "ssh_guard"
16
+ gem.homepage = "http://github.com/jellehelsen/ssh_guard"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{guardian angel for ssh}
19
+ gem.description = %Q{It protects your server from ssh password guessing bots.}
20
+ gem.email = "jelle.helsen@hcode.be"
21
+ gem.authors = ["Jelle Helsen"]
22
+ gem.executables = %w{ssh_guard}
23
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
24
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
25
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
26
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rspec/core'
31
+ require 'rspec/core/rake_task'
32
+ RSpec::Core::RakeTask.new(:spec) do |spec|
33
+ spec.pattern = FileList['spec/**/*_spec.rb']
34
+ end
35
+
36
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
37
+ spec.pattern = 'spec/**/*_spec.rb'
38
+ spec.rcov = true
39
+ end
40
+
41
+ task :default => :spec
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "ssh_guard #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,2 @@
1
+ Autotest.add_discovery { "rspec2" }
2
+
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ require "ssh_guard"
5
+
6
+ SshGuard::Core.new.start
@@ -0,0 +1,54 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'ssh_guard'))
2
+ require "logger"
3
+ require "database"
4
+ require "firewall_adapters"
5
+ module SshGuard
6
+ class Core
7
+ attr_reader :database
8
+ attr_reader :firewall
9
+ class Parser
10
+ def parse_line(line)
11
+ if line =~ /Did not receive identification string/ || line =~ /invalid user/i || line =~ /authentication error/
12
+ ip_address = line.match(/\d+\.\d+.\d+.\d+/).to_s
13
+ timestamp = Time.parse(line.match(/(^.+) mini/)[1])
14
+ {:ip_address => ip_address, :timestamp => timestamp}
15
+ end
16
+ end
17
+ end
18
+
19
+ def initialize
20
+ unless i_am_root?
21
+ raise "ssh_guard should be started as root!!!"
22
+ end
23
+ @database = Database.new
24
+ @parser = Parser.new
25
+ @firewall = FirewallAdapters::IPFWAdapter.new
26
+ @log_file = "/var/log/secure.log"
27
+ end
28
+
29
+ def <<(line)
30
+ if entry = @parser.parse_line(line)
31
+ if database.should_block? entry[:ip_address]
32
+ firewall.block_host entry[:ip_address] unless firewall.blocked?(entry[:ip_address])
33
+ else
34
+ database.add_entry(entry) unless firewall.blocked?(entry[:ip_address])
35
+ end
36
+ end
37
+ end
38
+
39
+ def start
40
+ IO.popen("tail -f #{@log_file}") do |f|
41
+ while line = f.gets
42
+ self << line if line =~ /sshd/
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.i_am_root?
48
+ `whoami` =~ /^root$/
49
+ end
50
+ def i_am_root?
51
+ self.class.i_am_root?
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ require "rubygems"
2
+ require "sequel"
3
+ module SshGuard
4
+ class Database
5
+ attr_reader :db
6
+
7
+ def initialize()
8
+ @db = Sequel.sqlite
9
+ @db.create_table :entries do
10
+ primary_key :id
11
+ String :ip_address
12
+ Time :timestamp
13
+ end
14
+ end
15
+
16
+ def add_entry(entry={})
17
+ db[:entries].insert(entry) unless entry.empty?
18
+ end
19
+
20
+ def should_block?(ip_address)
21
+ count = @db[:entries].where({:ip_address => ip_address}).count
22
+ count > 10
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module SshGuard
2
+ module FirewallAdapters
3
+ class IPFWAdapter
4
+ def initialize
5
+ @logger = Logger.new(STDOUT)
6
+ end
7
+ def block_host(host)
8
+ unless blocked?(host)
9
+ `ipfw add deny tcp from #{host} to me ssh`
10
+ @logger.warn("Blocking host #{host}!")
11
+ end
12
+ end
13
+
14
+ def blocked?(host)
15
+ `ipfw list | grep "deny tcp from #{host} to me dst-port 22"` =~ /deny tcp from #{host} to me dst-port 22$/ ? true : false
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ require "spec_helper"
2
+
3
+ describe SshGuard::Database do
4
+ it "should connect to an in-memory database" do
5
+ Sequel.should_receive(:sqlite).and_return(m=mock())
6
+ m.should_receive(:create_table)
7
+ @db = SshGuard::Database.new
8
+ end
9
+
10
+ it "should block the host if failed more than 10 times" do
11
+ @db = SshGuard::Database.new
12
+ @db.db.stub_chain(:[], :where, :count).and_return(11)
13
+ @db.should_block?("192.168.1.2").should be_true
14
+ end
15
+ it "should not block the host if failed less than 10 times" do
16
+ @db = SshGuard::Database.new
17
+ @db.db.stub_chain(:[], :where, :count).and_return(9)
18
+ @db.should_block?("192.168.1.2").should be_false
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ describe SshGuard::FirewallAdapters::IPFWAdapter do
4
+ before(:each) do
5
+ @firewall = SshGuard::FirewallAdapters::IPFWAdapter.new
6
+ end
7
+ it "should call ipfw" do
8
+ @firewall.stub(:blocked?).and_return(false)
9
+ @firewall.should_receive(:`).with("ipfw add deny tcp from 192.168.1.1 to me ssh")
10
+ @firewall.block_host('192.168.1.1')
11
+ end
12
+ it "should not block a host when it is already blocked" do
13
+ msg = "Nov 24 21:15:53 mini sshd[55670]: reverse mapping checking getaddrinfo for client-200.106.67.47.speedy.net.pe [200.106.67.47] failed - POSSIBLE BREAK-IN ATTEMPT!"
14
+ @firewall.should_not_receive(:`).with("ipfw add deny tcp from 192.168.1.1 to me ssh")
15
+ @firewall.should_receive(:blocked?).and_return(true)
16
+ @firewall.block_host('192.168.1.1')
17
+ end
18
+
19
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'ssh_guard'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,54 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe SshGuard::Core do
4
+ before(:each) do
5
+ SshGuard::Core.stub(:i_am_root?).and_return(:true)
6
+ @guard = SshGuard::Core.new
7
+ @guard.firewall.stub(:blocked?)
8
+ end
9
+
10
+ it "parses an input line" do
11
+ lambda { @guard << "input" }.should_not raise_error
12
+ end
13
+
14
+ it "should ignore other input" do
15
+ @guard.database.should_not_receive(:add_entry)
16
+ @guard << "other input"
17
+ end
18
+
19
+ it "should tell the firewall about host to block" do
20
+ @guard.database.stub(:should_block?).and_return(true)
21
+ @guard.stub(:firewall).and_return(m=mock())
22
+ m.stub(:blocked?).and_return(false)
23
+ m.should_receive(:block_host)
24
+ msg = "Nov 24 17:03:55 mini sshd[47367]: Invalid user staff from 221.13.5.92"
25
+ @guard << msg
26
+ end
27
+
28
+ describe "log messages" do
29
+ it "should add entry for failed authentication" do
30
+ msg = "Nov 24 21:15:55 mini sshd[55670]: error: PAM: authentication error for root from 200.106.67.47 via 192.168.1.2"
31
+ @guard.database.should_receive(:add_entry).with({:timestamp => Time.parse("Nov 24 21:15:55"), :ip_address => "200.106.67.47"})
32
+ @guard << msg
33
+ end
34
+
35
+ it "should add entry for invalid user" do
36
+ msg = "Nov 24 17:03:55 mini sshd[47367]: Invalid user staff from 221.13.5.92"
37
+ @guard.database.should_receive(:add_entry).with({:timestamp => Time.parse("Nov 24 17:03:55"), :ip_address => "221.13.5.92"})
38
+ @guard << msg
39
+ end
40
+
41
+ it "should add entry for no identification string" do
42
+ msg = "Nov 24 20:33:23 mini sshd[54157]: Did not receive identification string from 85.172.214.7"
43
+ @guard.database.should_receive(:add_entry).with({:timestamp => Time.parse("Nov 24 20:33:23"), :ip_address => "85.172.214.7"})
44
+ @guard << msg
45
+ end
46
+ end
47
+
48
+ describe "on osx" do
49
+ it "should tail /var/log/secure.log" do
50
+ IO.should_receive(:popen).with("tail -f /var/log/secure.log")
51
+ @guard.start
52
+ end
53
+ end
54
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ssh_guard
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Jelle Helsen
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-30 00:00:00 +01:00
19
+ default_executable: ssh_guard
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ hash: 3
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ name: sequel
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ hash: 3
42
+ segments:
43
+ - 0
44
+ version: "0"
45
+ type: :runtime
46
+ name: sqlite3-ruby
47
+ prerelease: false
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ requirement: &id003 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ hash: 11
56
+ segments:
57
+ - 2
58
+ - 1
59
+ - 0
60
+ version: 2.1.0
61
+ type: :development
62
+ name: rspec
63
+ prerelease: false
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ hash: 23
72
+ segments:
73
+ - 1
74
+ - 0
75
+ - 0
76
+ version: 1.0.0
77
+ type: :development
78
+ name: bundler
79
+ prerelease: false
80
+ version_requirements: *id004
81
+ - !ruby/object:Gem::Dependency
82
+ requirement: &id005 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ hash: 1
88
+ segments:
89
+ - 1
90
+ - 5
91
+ - 1
92
+ version: 1.5.1
93
+ type: :development
94
+ name: jeweler
95
+ prerelease: false
96
+ version_requirements: *id005
97
+ - !ruby/object:Gem::Dependency
98
+ requirement: &id006 !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ type: :development
108
+ name: rcov
109
+ prerelease: false
110
+ version_requirements: *id006
111
+ description: It protects your server from ssh password guessing bots.
112
+ email: jelle.helsen@hcode.be
113
+ executables:
114
+ - ssh_guard
115
+ extensions: []
116
+
117
+ extra_rdoc_files:
118
+ - LICENSE.txt
119
+ - README.rdoc
120
+ files:
121
+ - .document
122
+ - .rspec
123
+ - Gemfile
124
+ - Gemfile.lock
125
+ - LICENSE.txt
126
+ - README.rdoc
127
+ - Rakefile
128
+ - VERSION
129
+ - autotest/discover.rb
130
+ - bin/ssh_guard
131
+ - lib/ssh_guard.rb
132
+ - lib/ssh_guard/database.rb
133
+ - lib/ssh_guard/firewall_adapters.rb
134
+ - spec/database_spec.rb
135
+ - spec/ipfw_adapter_spec.rb
136
+ - spec/spec_helper.rb
137
+ - spec/ssh_guard_spec.rb
138
+ has_rdoc: true
139
+ homepage: http://github.com/jellehelsen/ssh_guard
140
+ licenses:
141
+ - MIT
142
+ post_install_message:
143
+ rdoc_options: []
144
+
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ hash: 3
153
+ segments:
154
+ - 0
155
+ version: "0"
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ hash: 3
162
+ segments:
163
+ - 0
164
+ version: "0"
165
+ requirements: []
166
+
167
+ rubyforge_project:
168
+ rubygems_version: 1.3.7
169
+ signing_key:
170
+ specification_version: 3
171
+ summary: guardian angel for ssh
172
+ test_files:
173
+ - spec/database_spec.rb
174
+ - spec/ipfw_adapter_spec.rb
175
+ - spec/spec_helper.rb
176
+ - spec/ssh_guard_spec.rb