ssh_scan_api 0.0.1.pre
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/.gitignore +24 -0
- data/.rspec +2 -0
- data/.travis.yml +30 -0
- data/CONTRIBUTING.md +47 -0
- data/Gemfile +4 -0
- data/README.md +59 -0
- data/Rakefile +17 -0
- data/bin/ssh_scan_api +16 -0
- data/lib/ssh_scan_api.rb +12 -0
- data/lib/ssh_scan_api/api.rb +231 -0
- data/lib/ssh_scan_api/authenticator.rb +30 -0
- data/lib/ssh_scan_api/database.rb +61 -0
- data/lib/ssh_scan_api/database/mongo.rb +67 -0
- data/lib/ssh_scan_api/database/sqlite.rb +91 -0
- data/lib/ssh_scan_api/job_queue.rb +24 -0
- data/lib/ssh_scan_api/stats.rb +52 -0
- data/lib/ssh_scan_api/version.rb +3 -0
- data/ssh_scan_api.gemspec +41 -0
- metadata +233 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3c72ff89102b44b67171f9770f217b45b2df1d94
|
4
|
+
data.tar.gz: 2c114be00642cc13fba1af1522c30e0581c1b244
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 934ae8b24626af9c471702f7ff7156b1d019c3f95264e3421bd9d165e84549a1a8f6d4eae7c2191cc0cb9559bfedde4739c4b484ca7913d5449cb61cbdeda954
|
7
|
+
data.tar.gz: 25084d857389c656fb392a9efe62600431e85650f2b9f36b03c9e826e56a9af4ffc3bac39555582952f550026f634ca904852caea30c2e7139a8dcffd2dcc25d
|
data/.gitignore
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
*.gem
|
2
|
+
*.db
|
3
|
+
/coverage/
|
4
|
+
|
5
|
+
## Documentation cache and generated files:
|
6
|
+
/.yardoc/
|
7
|
+
/_yardoc/
|
8
|
+
/doc/
|
9
|
+
/rdoc/
|
10
|
+
|
11
|
+
## Environment normalization:
|
12
|
+
/.bundle/
|
13
|
+
/vendor/bundle
|
14
|
+
/lib/bundler/man/
|
15
|
+
|
16
|
+
# for a library or gem, you might want to ignore these files since the code is
|
17
|
+
# intended to run in multiple environments; otherwise, check them in:
|
18
|
+
Gemfile.lock
|
19
|
+
# .ruby-version
|
20
|
+
# .ruby-gemset
|
21
|
+
|
22
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
23
|
+
.rvmrc
|
24
|
+
gh-pages/
|
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
language: ruby
|
2
|
+
matrix:
|
3
|
+
include:
|
4
|
+
- rvm: ruby-head
|
5
|
+
env:
|
6
|
+
- LABEL=unit_tests
|
7
|
+
services:
|
8
|
+
- mongodb
|
9
|
+
after_success:
|
10
|
+
- coveralls
|
11
|
+
- rvm: 2.3.0
|
12
|
+
env:
|
13
|
+
- LABEL=unit_tests
|
14
|
+
services:
|
15
|
+
- mongodb
|
16
|
+
- rvm: 2.2.0
|
17
|
+
env:
|
18
|
+
- LABEL=unit_tests
|
19
|
+
services:
|
20
|
+
- mongodb
|
21
|
+
- rvm: 2.1.3
|
22
|
+
env:
|
23
|
+
- LABEL=unit_tests
|
24
|
+
services:
|
25
|
+
- mongodb
|
26
|
+
- rvm: 2.0.0
|
27
|
+
env:
|
28
|
+
- LABEL=unit_tests
|
29
|
+
services:
|
30
|
+
- mongodb
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Contributing to ssh_scan_api
|
2
|
+
|
3
|
+
Thanks for your interest in contributing to ssh_scan_api.
|
4
|
+
|
5
|
+
If you could follow the following guidelines, you will make it much easier for
|
6
|
+
us to give feedback, help you find whatever problem you have and fix it.
|
7
|
+
|
8
|
+
## Issues
|
9
|
+
|
10
|
+
If you have questions of any kind, or are unsure of how something works, please
|
11
|
+
[create an issue](https://github.com/claudijd/ssh_scan_api/issues/new).
|
12
|
+
|
13
|
+
Please try to answer the following questions in your issue:
|
14
|
+
|
15
|
+
- What did you do?
|
16
|
+
- What did you expect to happen?
|
17
|
+
- What happened instead?
|
18
|
+
|
19
|
+
If you have identified a bug, it would be very helpful if you could include a
|
20
|
+
way to replicate the bug. Ideally a failing test would be perfect, but even a
|
21
|
+
simple script demonstrating the error would suffice.
|
22
|
+
|
23
|
+
Feature requests are great and if submitted they will be considered for
|
24
|
+
inclusion, but sending a pull request is much more awesome.
|
25
|
+
|
26
|
+
## Pull Requests
|
27
|
+
|
28
|
+
If you want your pull requests to be accepted, please follow the following guidelines:
|
29
|
+
|
30
|
+
- [**Add tests!**](http://rspec.info/) Your patch won't be accepted (or will be delayed) if it doesn't have tests.
|
31
|
+
|
32
|
+
- [**Document any change in behaviour**](http://yardoc.org/) Make sure the README and any other
|
33
|
+
relevant documentation are kept up-to-date.
|
34
|
+
|
35
|
+
- [**Create topic branches**](https://github.com/dchelimsky/rspec/wiki/Topic-Branches) Don't ask us to pull from your master branch.
|
36
|
+
|
37
|
+
- [**One pull request per feature**](https://help.github.com/articles/using-pull-requests) If you want to do more than one thing, send
|
38
|
+
multiple pull requests.
|
39
|
+
|
40
|
+
- [**Send coherent history**](http://stackoverflow.com/questions/6934752/git-combining-multiple-commits-before-pushing) Make sure each individual commit in your pull
|
41
|
+
request is meaningful. If you had to make multiple intermediate commits while
|
42
|
+
developing, please squash them before sending them to us.
|
43
|
+
|
44
|
+
- [**Follow coding conventions**](https://github.com/styleguide/ruby) The standard Ruby stuff, two spaces indent,
|
45
|
+
don't omit parens unless you have a good reason.
|
46
|
+
|
47
|
+
Thank you so much for contributing!
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# ssh_scan_api
|
2
|
+
|
3
|
+
A web api to scale ssh_scan operations
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
To install and run as a gem, type:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
gem install ssh_scan_api
|
11
|
+
ssh_scan_api
|
12
|
+
```
|
13
|
+
|
14
|
+
To install and run from source, type:
|
15
|
+
|
16
|
+
```bash
|
17
|
+
# clone repo
|
18
|
+
git clone https://github.com/mozilla/ssh_scan_api.git
|
19
|
+
cd ssh_scan_api
|
20
|
+
|
21
|
+
# install rvm,
|
22
|
+
# you might have to provide root to install missing packages
|
23
|
+
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
|
24
|
+
curl -sSL https://get.rvm.io | bash -s stable
|
25
|
+
|
26
|
+
# install Ruby 2.3.1 with rvm,
|
27
|
+
# again, you might have to install missing devel packages
|
28
|
+
rvm install 2.3.1
|
29
|
+
rvm use 2.3.1
|
30
|
+
|
31
|
+
# resolve dependencies
|
32
|
+
gem install bundler
|
33
|
+
bundle install
|
34
|
+
|
35
|
+
./bin/ssh_scan_api
|
36
|
+
```
|
37
|
+
|
38
|
+
## Rubies Supported
|
39
|
+
|
40
|
+
This project is integrated with [travis-ci](http://about.travis-ci.org/) and is regularly tested to work with the following rubies:
|
41
|
+
|
42
|
+
* [ruby-head](https://github.com/ruby/ruby)
|
43
|
+
* [2.3.0](https://github.com/ruby/ruby/tree/ruby_2_1)
|
44
|
+
* [2.2.0](https://github.com/ruby/ruby/tree/ruby_2_1)
|
45
|
+
* [2.1.3](https://github.com/ruby/ruby/tree/ruby_2_1)
|
46
|
+
* [2.1.0](https://github.com/ruby/ruby/tree/ruby_2_1)
|
47
|
+
* [2.0.0](https://github.com/ruby/ruby/tree/ruby_2_0_0)
|
48
|
+
|
49
|
+
To checkout the current build status for these rubies, click [here](https://travis-ci.org/#!/mozilla/ssh_scan).
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
|
53
|
+
If you are interested in contributing to this project, please see [CONTRIBUTING.md](https://github.com/mozilla/ssh_scan/blob/master/CONTRIBUTING.md).
|
54
|
+
|
55
|
+
## Credits
|
56
|
+
|
57
|
+
**Sources of Inspiration for ssh_scan**
|
58
|
+
|
59
|
+
- [**Mozilla OpenSSH Security Guide**](https://wiki.mozilla.org/Security/Guidelines/OpenSSH) - For providing a sane baseline policy recommendation for SSH configuration parameters (eg. Ciphers, MACs, and KexAlgos).
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rubygems/package_task'
|
4
|
+
require 'rspec'
|
5
|
+
require 'rspec/core'
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
require 'bundler/setup'
|
8
|
+
require 'ssh_scan_api/version'
|
9
|
+
|
10
|
+
$:.unshift File.join(File.dirname(__FILE__), "lib")
|
11
|
+
|
12
|
+
require 'ssh_scan'
|
13
|
+
|
14
|
+
task :default => :spec
|
15
|
+
|
16
|
+
desc "Run all specs in spec directory"
|
17
|
+
RSpec::Core::RakeTask.new(:spec)
|
data/bin/ssh_scan_api
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), "../lib")
|
4
|
+
|
5
|
+
require 'ssh_scan_api'
|
6
|
+
|
7
|
+
# Get the api config from command-line or via an example location
|
8
|
+
config_file = ARGV[0] ||
|
9
|
+
File.join(
|
10
|
+
File.dirname(__FILE__),
|
11
|
+
"../config/api/config.yml"
|
12
|
+
)
|
13
|
+
opts = YAML.load_file(config_file)
|
14
|
+
opts["config_file"] = config_file
|
15
|
+
|
16
|
+
SSHScan::API.run!(opts)
|
data/lib/ssh_scan_api.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#External Deps
|
2
|
+
require 'timeout'
|
3
|
+
require 'resolv'
|
4
|
+
require 'ssh_scan'
|
5
|
+
|
6
|
+
#Internal Deps
|
7
|
+
require 'ssh_scan_api/api'
|
8
|
+
require 'ssh_scan_api/version'
|
9
|
+
require 'ssh_scan_api/job_queue'
|
10
|
+
require 'ssh_scan_api/database'
|
11
|
+
require 'ssh_scan_api/stats'
|
12
|
+
require 'ssh_scan_api/authenticator'
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/namespace'
|
3
|
+
require 'json'
|
4
|
+
require 'haml'
|
5
|
+
require 'secure_headers'
|
6
|
+
require 'thin'
|
7
|
+
require 'securerandom'
|
8
|
+
require 'ssh_scan'
|
9
|
+
require 'ssh_scan_api/job_queue'
|
10
|
+
require 'ssh_scan_api/database'
|
11
|
+
|
12
|
+
module SSHScan
|
13
|
+
class API < Sinatra::Base
|
14
|
+
if ENV['RACK_ENV'] == 'test'
|
15
|
+
configure do
|
16
|
+
set :job_queue, SSHScan::JobQueue.new()
|
17
|
+
set :authentication, false
|
18
|
+
config_file = File.join(Dir.pwd, "./config/api/config.yml")
|
19
|
+
opts = YAML.load_file(config_file)
|
20
|
+
opts["config_file"] = config_file
|
21
|
+
set :db, SSHScan::Database.from_hash(opts)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Configure all the secure headers we want to use
|
26
|
+
use SecureHeaders::Middleware
|
27
|
+
SecureHeaders::Configuration.default do |config|
|
28
|
+
config.cookies = {
|
29
|
+
secure: true, # mark all cookies as "Secure"
|
30
|
+
httponly: true, # mark all cookies as "HttpOnly"
|
31
|
+
}
|
32
|
+
config.hsts = "max-age=31536000; includeSubdomains; preload"
|
33
|
+
config.x_frame_options = "DENY"
|
34
|
+
config.x_content_type_options = "nosniff"
|
35
|
+
config.x_xss_protection = "1; mode=block"
|
36
|
+
config.x_download_options = "noopen"
|
37
|
+
config.x_permitted_cross_domain_policies = "none"
|
38
|
+
config.referrer_policy = "no-referrer"
|
39
|
+
config.csp = {
|
40
|
+
default_src: ["'none'"],
|
41
|
+
frame_ancestors: ["'none'"],
|
42
|
+
upgrade_insecure_requests: true, # see https://www.w3.org/TR/upgrade-insecure-requests/
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
register Sinatra::Namespace
|
47
|
+
|
48
|
+
before do
|
49
|
+
headers "Server" => "ssh_scan_api"
|
50
|
+
headers "Cache-control" => "no-store"
|
51
|
+
headers "Pragma" => "no-cache"
|
52
|
+
end
|
53
|
+
|
54
|
+
helpers do
|
55
|
+
def cache_valid?(start_time)
|
56
|
+
(Time.now - Time.parse(start_time.to_s)) / (60 * 60 * 24) < 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Custom 404 handling
|
61
|
+
not_found do
|
62
|
+
content_type "text/plain"
|
63
|
+
"Invalid request, see API documentation here: \
|
64
|
+
https://github.com/mozilla/ssh_scan_api/wiki/ssh_scan-Web-API\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
get '/' do
|
68
|
+
content_type "text/plain"
|
69
|
+
"See API documentation here: \
|
70
|
+
https://github.com/mozilla/ssh_scan_api/wiki/ssh_scan-Web-API\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
get '/robots.txt' do
|
74
|
+
content_type "text/plain"
|
75
|
+
"User-agent: *\nDisallow: /\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
get '/contribute.json' do
|
79
|
+
content_type :json
|
80
|
+
SSHScan::Constants::CONTRIBUTE_JSON.to_json
|
81
|
+
end
|
82
|
+
|
83
|
+
get '/__version__' do
|
84
|
+
{
|
85
|
+
:ssh_scan_version => SSHScan::VERSION,
|
86
|
+
:api_version => SSHScan::API_VERSION,
|
87
|
+
}.to_json
|
88
|
+
end
|
89
|
+
|
90
|
+
namespace "/api/v1" do
|
91
|
+
before do
|
92
|
+
content_type :json
|
93
|
+
if settings.authentication == true
|
94
|
+
token = request.env['HTTP_SSH_SCAN_AUTH_TOKEN']
|
95
|
+
|
96
|
+
# If a token is not provided, only localhost can proceed
|
97
|
+
if token.nil? && request.ip != "127.0.0.1"
|
98
|
+
halt '{"error" : "authentication failure"}'
|
99
|
+
end
|
100
|
+
|
101
|
+
# If a token is provided, it must be valid to proceed
|
102
|
+
if token && settings.authenticator.valid_token?(token) == false
|
103
|
+
halt '{"error" : "authentication failure"}'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
post '/scan' do
|
109
|
+
options = {
|
110
|
+
:sockets => [],
|
111
|
+
:policy => File.join(Dir.pwd,
|
112
|
+
'/config/policies/mozilla_modern.yml'),
|
113
|
+
:timeout => 2,
|
114
|
+
:verbosity => nil,
|
115
|
+
#:fingerprint_database => "fingerprints.db",
|
116
|
+
}
|
117
|
+
options[:sockets] <<
|
118
|
+
"#{params[:target]}:#{params[:port] ? params[:port] : "22"}"
|
119
|
+
options[:policy_file] = options[:policy]
|
120
|
+
options[:force] = params[:force] ? params[:force] : false
|
121
|
+
|
122
|
+
unless options[:force] == 'true'
|
123
|
+
available_result = settings.db.fetch_cached_result(params)
|
124
|
+
unless available_result.nil?
|
125
|
+
if cache_valid?(available_result[:start_time])
|
126
|
+
return {
|
127
|
+
uuid: available_result[:uuid]
|
128
|
+
}.to_json
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
options[:uuid] = SecureRandom.uuid
|
133
|
+
settings.job_queue.add(options)
|
134
|
+
{
|
135
|
+
uuid: options[:uuid]
|
136
|
+
}.to_json
|
137
|
+
end
|
138
|
+
|
139
|
+
get '/scan/results' do
|
140
|
+
uuid = params[:uuid]
|
141
|
+
return {"scan" => "not found"}.to_json if uuid.nil? || uuid.empty?
|
142
|
+
result = settings.db.find_scan_result(uuid)
|
143
|
+
return {"scan" => "not found"}.to_json if result.nil?
|
144
|
+
return result.to_json
|
145
|
+
end
|
146
|
+
|
147
|
+
post '/scan/results/delete' do
|
148
|
+
uuid = params[:uuid]
|
149
|
+
|
150
|
+
if uuid.nil? || uuid.empty?
|
151
|
+
return {"deleted" => "false"}.to_json
|
152
|
+
else
|
153
|
+
scan = settings.db.find_scan_result(uuid)
|
154
|
+
if scan.empty?
|
155
|
+
return {"deleted" => "false"}.to_json
|
156
|
+
else
|
157
|
+
settings.db.delete_scan(uuid)
|
158
|
+
return {"deleted" => "true"}.to_json
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
get '/scan/results/delete/all' do
|
164
|
+
settings.db.delete_all
|
165
|
+
end
|
166
|
+
|
167
|
+
get '/work' do
|
168
|
+
worker_id = params[:worker_id]
|
169
|
+
logger.warn("Worker #{worker_id} polls for Job")
|
170
|
+
job = settings.job_queue.next
|
171
|
+
if job.nil?
|
172
|
+
logger.warn("Worker #{worker_id} didn't get any work")
|
173
|
+
{"work" => false}.to_json
|
174
|
+
else
|
175
|
+
logger.warn("Worker #{worker_id} got job #{job[:uuid]}")
|
176
|
+
{"work" => job}.to_json
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
post '/work/results/:worker_id/:uuid' do
|
181
|
+
worker_id = params['worker_id']
|
182
|
+
uuid = params['uuid']
|
183
|
+
result = JSON.parse(request.body.first).first
|
184
|
+
socket = {}
|
185
|
+
socket[:target] = result['ip']
|
186
|
+
socket[:port] = result['port']
|
187
|
+
|
188
|
+
if worker_id.empty? || uuid.empty?
|
189
|
+
return {"accepted" => "false"}.to_json
|
190
|
+
end
|
191
|
+
settings.stats.new_scan_request
|
192
|
+
settings.db.add_scan(worker_id, uuid, result, socket)
|
193
|
+
end
|
194
|
+
|
195
|
+
get '/stats' do
|
196
|
+
settings.stats.get_stats(settings.job_queue.size)
|
197
|
+
end
|
198
|
+
|
199
|
+
get '/__lbheartbeat__' do
|
200
|
+
{
|
201
|
+
:status => "OK",
|
202
|
+
:message => "Keep sending requests. I am still alive."
|
203
|
+
}.to_json
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.run!(options = {}, &block)
|
208
|
+
set options
|
209
|
+
|
210
|
+
configure do
|
211
|
+
enable :logging
|
212
|
+
set :bind, options["bind"] || '127.0.0.1'
|
213
|
+
set :server, "thin"
|
214
|
+
set :logger, Logger.new(STDOUT)
|
215
|
+
set :job_queue, JobQueue.new()
|
216
|
+
set :db, SSHScan::Database.from_hash(options)
|
217
|
+
set :results, {}
|
218
|
+
set :stats, SSHScan::Stats.new
|
219
|
+
set :authentication, options["authentication"]
|
220
|
+
set :authenticator, SSHScan::Authenticator.from_config_file(
|
221
|
+
options["config_file"]
|
222
|
+
)
|
223
|
+
end
|
224
|
+
|
225
|
+
super do |server|
|
226
|
+
# No SSL on app, SSL termination happens in nginx for a prod deployment
|
227
|
+
server.ssl = false
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SSHScan
|
2
|
+
class Authenticator
|
3
|
+
attr_reader :config
|
4
|
+
|
5
|
+
def initialize(config)
|
6
|
+
@config = config
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.from_config_file(config_file)
|
10
|
+
opts = YAML.load_file(config_file)
|
11
|
+
SSHScan::Authenticator.new(opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid_token?(token)
|
15
|
+
if @config["users"]
|
16
|
+
@config["users"].each do |user|
|
17
|
+
return true if user["token"] == token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if @config["workers"]
|
22
|
+
@config["workers"].each do |worker|
|
23
|
+
return true if worker["token"] == token
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'ssh_scan_api/database/mongo'
|
2
|
+
require 'ssh_scan_api/database/sqlite'
|
3
|
+
|
4
|
+
module SSHScan
|
5
|
+
class Database
|
6
|
+
attr_reader :database
|
7
|
+
|
8
|
+
# @param [SSHScan::Database::MongoDb, SSHScan::Database::SQLite] database
|
9
|
+
def initialize(database)
|
10
|
+
@database = database
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [Hash] opts
|
14
|
+
# @return [SSHScan::Database]
|
15
|
+
def self.from_hash(opts)
|
16
|
+
database_options = opts["database"]
|
17
|
+
|
18
|
+
# Figure out what database object to load
|
19
|
+
case database_options["type"]
|
20
|
+
when "sqlite"
|
21
|
+
database = SSHScan::DB::SQLite.from_hash(database_options)
|
22
|
+
when "mongodb"
|
23
|
+
database = SSHScan::DB::MongoDb.from_hash(database_options)
|
24
|
+
else
|
25
|
+
raise "Database type of #{database_options[:type].class} not supported"
|
26
|
+
end
|
27
|
+
|
28
|
+
SSHScan::Database.new(database)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param [String] worker_id
|
32
|
+
# @param [String] uuid
|
33
|
+
# @param [Hash] result
|
34
|
+
# @return [Nil]
|
35
|
+
def add_scan(worker_id, uuid, result, socket)
|
36
|
+
@database.add_scan(worker_id, uuid, result, socket)
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [String] uuid
|
41
|
+
# @return [Nil]
|
42
|
+
def delete_scan(uuid)
|
43
|
+
@database.delete_scan(uuid)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Nil]
|
47
|
+
def delete_all
|
48
|
+
@database.delete_all
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Hash] result
|
52
|
+
def find_scan_result(uuid)
|
53
|
+
@database.find_scan_result(uuid)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Hash] result
|
57
|
+
def fetch_cached_result(socket)
|
58
|
+
@database.fetch_cached_result(socket)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'mongo'
|
2
|
+
|
3
|
+
Mongo::Logger.logger.level = ::Logger::FATAL
|
4
|
+
|
5
|
+
module SSHScan
|
6
|
+
module DB
|
7
|
+
class MongoDb
|
8
|
+
attr_reader :collection
|
9
|
+
|
10
|
+
def initialize(client)
|
11
|
+
@client = client
|
12
|
+
@collection = @client[:ssh_scan]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Helps us create a SSHScan::DB::MongoDB object with a hash
|
16
|
+
def self.from_hash(opts)
|
17
|
+
name = opts["name"]
|
18
|
+
server = opts["server"]
|
19
|
+
port = opts["port"]
|
20
|
+
socket = server + ":" + port.to_s
|
21
|
+
|
22
|
+
client = Mongo::Client.new([socket], :database => name)
|
23
|
+
return SSHScan::DB::MongoDb.new(client)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [String] worker_id
|
27
|
+
# @param [String] uuid
|
28
|
+
# @param [Hash] result
|
29
|
+
def add_scan(worker_id, uuid, result, socket)
|
30
|
+
@collection.insert_one("uuid" => uuid,
|
31
|
+
"target" => socket[:target],
|
32
|
+
"port" => socket[:port],
|
33
|
+
"scan" => result,
|
34
|
+
"worker_id" => worker_id)
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_scan(uuid)
|
38
|
+
@collection.delete_one(:uuid => uuid)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_all
|
42
|
+
@collection.delete_many({})
|
43
|
+
end
|
44
|
+
|
45
|
+
# LEFT OFF HERE: the results of this method should be the exact same format as with SQLite
|
46
|
+
def find_scan_result(uuid)
|
47
|
+
@collection.find(:uuid => uuid).each do |doc|
|
48
|
+
return doc[:scan].to_hash
|
49
|
+
end
|
50
|
+
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch_cached_result(socket)
|
55
|
+
results = @collection.find(:target => socket[:target], :port => socket[:port])
|
56
|
+
results = results.skip(results.count() - 1)
|
57
|
+
return nil if results.count.zero?
|
58
|
+
result = {}
|
59
|
+
results.each do |result|
|
60
|
+
result[:uuid] = result[:uuid]
|
61
|
+
result[:start_time] = result[:scan][:start_time]
|
62
|
+
return result
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module SSHScan
|
5
|
+
module DB
|
6
|
+
class SQLite
|
7
|
+
attr_reader :database
|
8
|
+
|
9
|
+
def initialize(database)
|
10
|
+
@database = database # the SQLite database object
|
11
|
+
end
|
12
|
+
|
13
|
+
# Helps us create a SSHScan::DB::SQLite object with a hash
|
14
|
+
def self.from_hash(opts)
|
15
|
+
file_name = opts["file"]
|
16
|
+
|
17
|
+
if File.exist?(file_name)
|
18
|
+
db = ::SQLite3::Database.open(file_name)
|
19
|
+
else
|
20
|
+
db = ::SQLite3::Database.new(file_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
#Check to see if the schema is setup or not
|
24
|
+
result = db.execute <<-SQL
|
25
|
+
SELECT count(*) FROM sqlite_master WHERE type = 'table' AND name = 'ssh_scan';
|
26
|
+
SQL
|
27
|
+
|
28
|
+
# If not, create it
|
29
|
+
if result == [[0]]
|
30
|
+
# Create the schema for the database
|
31
|
+
db.execute <<-SQL
|
32
|
+
create table ssh_scan (
|
33
|
+
uuid varchar(100),
|
34
|
+
target varchar(100),
|
35
|
+
port varchar(100),
|
36
|
+
result json,
|
37
|
+
worker_id varchar(100)
|
38
|
+
);
|
39
|
+
SQL
|
40
|
+
end
|
41
|
+
|
42
|
+
return SSHScan::DB::SQLite.new(db)
|
43
|
+
end
|
44
|
+
|
45
|
+
def size
|
46
|
+
count = @database.execute("select count() from ssh_scan")
|
47
|
+
return count
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_scan(worker_id, uuid, result, socket)
|
51
|
+
@database.execute "insert into ssh_scan values ( ? , ? , ? , ? , ? )",
|
52
|
+
[uuid, socket[:target], socket[:port],
|
53
|
+
result.to_json, worker_id]
|
54
|
+
end
|
55
|
+
|
56
|
+
def delete_scan(uuid)
|
57
|
+
@database.execute(
|
58
|
+
"delete from ssh_scan where uuid = ?",
|
59
|
+
uuid
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def delete_all
|
64
|
+
@database.execute("delete from ssh_scan")
|
65
|
+
end
|
66
|
+
|
67
|
+
def find_scan_result(uuid)
|
68
|
+
@database.execute(
|
69
|
+
"select * from ssh_scan where uuid like ( ? )",
|
70
|
+
uuid
|
71
|
+
) do |row|
|
72
|
+
return JSON.parse(row[3])
|
73
|
+
end
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def fetch_cached_result(socket)
|
78
|
+
result = {}
|
79
|
+
results = @database.execute(
|
80
|
+
"select uuid, result from ssh_scan
|
81
|
+
where target like ( ? ) and port like ( ? )",
|
82
|
+
[socket[:target], socket[:port]]
|
83
|
+
)
|
84
|
+
return nil if results == []
|
85
|
+
result[:uuid] = results[result.length()-1][0]
|
86
|
+
result[:start_time] = JSON.parse(results[result.length()-1][1])["start_time"]
|
87
|
+
return result
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SSHScan
|
2
|
+
class JobQueue
|
3
|
+
def initialize
|
4
|
+
@queue = Queue.new
|
5
|
+
end
|
6
|
+
|
7
|
+
# @param [String] a socket we want to scan (Example: "192.168.1.1:22")
|
8
|
+
# @return [nil]
|
9
|
+
def add(socket)
|
10
|
+
@queue.push(socket)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String] a socket we want to scan (Example: "192.168.1.1:22")
|
14
|
+
def next
|
15
|
+
return nil if @queue.empty?
|
16
|
+
@queue.pop
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [FixNum] the number of jobs in the JobQueue
|
20
|
+
def size
|
21
|
+
@queue.size
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module SSHScan
|
2
|
+
class Stats
|
3
|
+
def initialize
|
4
|
+
@requests = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def new_scan_request
|
8
|
+
@requests << Time.now
|
9
|
+
# Purges the request queue of old requests
|
10
|
+
purge_old_requests
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_stats(queue_size)
|
14
|
+
{
|
15
|
+
:items_queued => queue_size,
|
16
|
+
:avg_requests_per_min => requests_avg_per,
|
17
|
+
:requests_per_min => requests_per
|
18
|
+
}.to_json
|
19
|
+
end
|
20
|
+
|
21
|
+
def size
|
22
|
+
@requests.size
|
23
|
+
end
|
24
|
+
|
25
|
+
# Purges the request queue of old requests, so we don't run the API out of memory
|
26
|
+
# @param [Fixnum] seconds
|
27
|
+
def purge_old_requests(seconds = 60)
|
28
|
+
@requests.delete_if {|request_time| request_time < Time.now - seconds}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Determines the number of requests in a second-based
|
32
|
+
# time period (up to 60 seconds)
|
33
|
+
# @param [Fixnum] seconds
|
34
|
+
# @return [Fixnum] request per time period
|
35
|
+
def requests_per(seconds = 60)
|
36
|
+
requests_per = 0
|
37
|
+
past_time = Time.now - seconds
|
38
|
+
|
39
|
+
@requests.each do |request_time|
|
40
|
+
requests_per += 1 if request_time >= past_time
|
41
|
+
end
|
42
|
+
|
43
|
+
return requests_per
|
44
|
+
end
|
45
|
+
|
46
|
+
# Determines average requests per time period
|
47
|
+
def requests_avg_per(seconds = 60)
|
48
|
+
requests_per = requests_per(seconds)
|
49
|
+
requests_per / seconds.to_f
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
$: << "lib"
|
2
|
+
require 'ssh_scan_api/version'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'ssh_scan_api'
|
7
|
+
s.version = SSHScan::API_VERSION
|
8
|
+
s.authors = ["Harsh Vardhan", "Rishabh Saxena", "Ashish Gaurav", "Jonathan Claudius" ]
|
9
|
+
s.date = Date.today.to_s
|
10
|
+
s.email = 'jclaudius@mozilla.com'
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.files = Dir.glob("lib/**/*") +
|
13
|
+
Dir.glob("bin/**/*") +
|
14
|
+
[".gitignore",
|
15
|
+
".rspec",
|
16
|
+
".travis.yml",
|
17
|
+
"CONTRIBUTING.md",
|
18
|
+
"Gemfile",
|
19
|
+
"Rakefile",
|
20
|
+
"README.md",
|
21
|
+
"ssh_scan_api.gemspec"]
|
22
|
+
s.license = "ruby"
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
s.executables = s.files.grep(%r{^bin/[^\/]+$}) { |f| File.basename(f) }
|
25
|
+
s.summary = 'ssh_scan API'
|
26
|
+
s.description = 'An API for performing SSH scans'
|
27
|
+
s.homepage = 'http://rubygems.org/gems/ssh_scan_api'
|
28
|
+
|
29
|
+
s.add_dependency('ssh_scan', '0.0.17.pre')
|
30
|
+
s.add_dependency('mongo')
|
31
|
+
s.add_dependency('sqlite3')
|
32
|
+
s.add_dependency('sinatra')
|
33
|
+
s.add_dependency('sinatra-contrib')
|
34
|
+
s.add_dependency('thin')
|
35
|
+
s.add_development_dependency('rack-test')
|
36
|
+
s.add_development_dependency('pry')
|
37
|
+
s.add_development_dependency('rspec', '~> 3.0')
|
38
|
+
s.add_development_dependency('rspec-its', '~> 1.2')
|
39
|
+
s.add_development_dependency('rake', '~> 10.3')
|
40
|
+
s.add_development_dependency('rubocop')
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ssh_scan_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Harsh Vardhan
|
8
|
+
- Rishabh Saxena
|
9
|
+
- Ashish Gaurav
|
10
|
+
- Jonathan Claudius
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2017-03-02 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: ssh_scan
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - '='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.0.17.pre
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.0.17.pre
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: mongo
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '0'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: sqlite3
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: sinatra
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
type: :runtime
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: sinatra-contrib
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
type: :runtime
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: thin
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
type: :runtime
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: rack-test
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
type: :development
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: pry
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
- !ruby/object:Gem::Dependency
|
129
|
+
name: rspec
|
130
|
+
requirement: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - "~>"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '3.0'
|
135
|
+
type: :development
|
136
|
+
prerelease: false
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - "~>"
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '3.0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: rspec-its
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - "~>"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '1.2'
|
149
|
+
type: :development
|
150
|
+
prerelease: false
|
151
|
+
version_requirements: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - "~>"
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '1.2'
|
156
|
+
- !ruby/object:Gem::Dependency
|
157
|
+
name: rake
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - "~>"
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '10.3'
|
163
|
+
type: :development
|
164
|
+
prerelease: false
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - "~>"
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '10.3'
|
170
|
+
- !ruby/object:Gem::Dependency
|
171
|
+
name: rubocop
|
172
|
+
requirement: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - ">="
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: '0'
|
177
|
+
type: :development
|
178
|
+
prerelease: false
|
179
|
+
version_requirements: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - ">="
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0'
|
184
|
+
description: An API for performing SSH scans
|
185
|
+
email: jclaudius@mozilla.com
|
186
|
+
executables:
|
187
|
+
- ssh_scan_api
|
188
|
+
extensions: []
|
189
|
+
extra_rdoc_files: []
|
190
|
+
files:
|
191
|
+
- ".gitignore"
|
192
|
+
- ".rspec"
|
193
|
+
- ".travis.yml"
|
194
|
+
- CONTRIBUTING.md
|
195
|
+
- Gemfile
|
196
|
+
- README.md
|
197
|
+
- Rakefile
|
198
|
+
- bin/ssh_scan_api
|
199
|
+
- lib/ssh_scan_api.rb
|
200
|
+
- lib/ssh_scan_api/api.rb
|
201
|
+
- lib/ssh_scan_api/authenticator.rb
|
202
|
+
- lib/ssh_scan_api/database.rb
|
203
|
+
- lib/ssh_scan_api/database/mongo.rb
|
204
|
+
- lib/ssh_scan_api/database/sqlite.rb
|
205
|
+
- lib/ssh_scan_api/job_queue.rb
|
206
|
+
- lib/ssh_scan_api/stats.rb
|
207
|
+
- lib/ssh_scan_api/version.rb
|
208
|
+
- ssh_scan_api.gemspec
|
209
|
+
homepage: http://rubygems.org/gems/ssh_scan_api
|
210
|
+
licenses:
|
211
|
+
- ruby
|
212
|
+
metadata: {}
|
213
|
+
post_install_message:
|
214
|
+
rdoc_options: []
|
215
|
+
require_paths:
|
216
|
+
- lib
|
217
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - ">="
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
223
|
+
requirements:
|
224
|
+
- - ">"
|
225
|
+
- !ruby/object:Gem::Version
|
226
|
+
version: 1.3.1
|
227
|
+
requirements: []
|
228
|
+
rubyforge_project:
|
229
|
+
rubygems_version: 2.6.2
|
230
|
+
signing_key:
|
231
|
+
specification_version: 4
|
232
|
+
summary: ssh_scan API
|
233
|
+
test_files: []
|