silw 0.0.1
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 +44 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +119 -0
- data/Rakefile +8 -0
- data/SILW_server_page_example.png +0 -0
- data/bin/silw +21 -0
- data/lib/silw.rb +35 -0
- data/lib/silw/agent.rb +17 -0
- data/lib/silw/cli.rb +165 -0
- data/lib/silw/command.rb +25 -0
- data/lib/silw/helpers.rb +54 -0
- data/lib/silw/plugin.rb +19 -0
- data/lib/silw/plugins/cpu.rb +55 -0
- data/lib/silw/plugins/diskio.rb +70 -0
- data/lib/silw/plugins/mem.rb +43 -0
- data/lib/silw/server.rb +46 -0
- data/lib/silw/version.rb +3 -0
- data/lib/silw/views/index.haml +30 -0
- data/lib/silw/views/layout.haml +50 -0
- data/lib/silw/views/partials/_cpu.haml +9 -0
- data/lib/silw/views/partials/_diskio.haml +12 -0
- data/lib/silw/views/partials/_mem.haml +12 -0
- data/silw.gemspec +46 -0
- data/spec/fixtures/cpu_t0.txt +11 -0
- data/spec/fixtures/cpu_t1.txt +11 -0
- data/spec/fixtures/diskstats.txt +48 -0
- data/spec/fixtures/mem.txt +25 -0
- data/spec/fixtures/silw.yaml +17 -0
- data/spec/fixtures/silw_with_logstash.yaml +22 -0
- data/spec/fixtures/top.txt +0 -0
- data/spec/silw_spec.rb +59 -0
- data/spec/spec_helper.rb +15 -0
- metadata +368 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 210ca5566b6b7422cbb3031c6b0d1c7d57c93663
|
4
|
+
data.tar.gz: 64a592b3f5ee4eebaf26fe26bb79c5eeaf16a832
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 779b1cb7d06bdd6b7f0366499d923258e67457cf4d736f46168440856d8e54d9e2ae3adf26840e47490bf4ecbe8c20a52376eb3840b827e0b259741ac13b955d
|
7
|
+
data.tar.gz: 9b9af3e1a6877a20ae716c6466d681d43b106ca0dec4f0cdcf45bf177734ec1f04ab2c189d6dca3d79a4f3ff5887ccdd21ae3189a12af0091a6e2bde713be970
|
data/.gitignore
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# the local logs folder, if any
|
2
|
+
log/
|
3
|
+
|
4
|
+
# rcov generated
|
5
|
+
coverage
|
6
|
+
coverage.data
|
7
|
+
|
8
|
+
# rdoc generated
|
9
|
+
rdoc
|
10
|
+
|
11
|
+
# yard generated
|
12
|
+
doc
|
13
|
+
.yardoc
|
14
|
+
|
15
|
+
# bundler
|
16
|
+
.bundle
|
17
|
+
|
18
|
+
# jeweler generated
|
19
|
+
pkg
|
20
|
+
|
21
|
+
# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
|
22
|
+
#
|
23
|
+
# * Create a file at ~/.gitignore
|
24
|
+
# * Include files you want ignored
|
25
|
+
# * Run: git config --global core.excludesfile ~/.gitignore
|
26
|
+
#
|
27
|
+
# After doing this, these files will be ignored in all your git projects,
|
28
|
+
# saving you from having to 'pollute' every project you touch with them
|
29
|
+
#
|
30
|
+
# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
|
31
|
+
#
|
32
|
+
# For MacOS:
|
33
|
+
#
|
34
|
+
.DS_Store
|
35
|
+
Gemfile.lock
|
36
|
+
|
37
|
+
# For TextMate
|
38
|
+
*.tmproj
|
39
|
+
tmtags
|
40
|
+
|
41
|
+
# Intellij
|
42
|
+
.idea/
|
43
|
+
*.iml
|
44
|
+
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Florin T.PATRASCU
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# SILW
|
2
|
+
This is a simple utility that allows one user to monitor several remote systems, provided he has the proper credentials and the authorization to execute remote commands. Inspired from [usagewatch](https://github.com/nethacker/usagewatch), SILW differs by targeting remote systems and exposing a simple and customizable Sinatra web app.
|
3
|
+
|
4
|
+
### SILW Plugins available in this release
|
5
|
+
|
6
|
+
- **Meminfo** - report the memory stats collected from a remote system.
|
7
|
+
- **CPU info** - report the total CPU usage for a remote system.
|
8
|
+
- **Diskio** - report the disk usage on a remote system.
|
9
|
+
|
10
|
+
In order to use SILW, you must be authorized to use the target systems, and make sure you can authenticate and that you are authorized to run various system commands i.e. `top`, `free`, read the content of the `/proc/meminfo` file.
|
11
|
+
|
12
|
+
### Using the gem in command line mode
|
13
|
+
First create a `silw.yaml` config file in your home directory. Excerpt from `~/silw.yaml`:
|
14
|
+
|
15
|
+
:monitoring:
|
16
|
+
router:
|
17
|
+
:freq: 10s
|
18
|
+
:plugins: cpu mem diskio
|
19
|
+
|
20
|
+
:authentication:
|
21
|
+
:username: username_with_remote_access
|
22
|
+
:password: ~/.ssh/id_dsa (or password!!)
|
23
|
+
|
24
|
+
:server:
|
25
|
+
:port: 8080
|
26
|
+
|
27
|
+
and use SILW for querying the remote servers defined under the `:monitoring:`:
|
28
|
+
|
29
|
+
silw exec cpu
|
30
|
+
silw exec mem cpu diskio
|
31
|
+
|
32
|
+
Or specify a different remote server, for example:
|
33
|
+
|
34
|
+
silw exec cpu mem -s router
|
35
|
+
|
36
|
+
{"host":"router","cpu":{"cpu_usage":"12.5%"}}
|
37
|
+
{"host":"router","mem":{"total":2075624.0,"active":538692.0,"free":1135688.0,"usagepercentage":26}}
|
38
|
+
|
39
|
+
Without a configuration file, you'll have to specify the remote server and a valid username and password combination, for example:
|
40
|
+
|
41
|
+
silw exec cpu -s honeypot -u florin -k ~/.ssh/xtraterrestrial_dsa
|
42
|
+
|
43
|
+
### Using the gem as a local web service
|
44
|
+
You can use SILW to monitor remote systems from your browser. For this you will have to create or modify the SILW configuration file: `silw.yaml`. Example:
|
45
|
+
|
46
|
+
:monitoring:
|
47
|
+
triba:
|
48
|
+
:freq: 1min
|
49
|
+
:plugins: mem, diskio, cpu
|
50
|
+
zorius:
|
51
|
+
:freq: 30s
|
52
|
+
:plugins: cpu, diskio
|
53
|
+
authentication:
|
54
|
+
:username: johndoe
|
55
|
+
:password: ~/.ssh/id_dsa
|
56
|
+
:server:
|
57
|
+
:port: 8080
|
58
|
+
|
59
|
+
Say the config file above is called: `silw.yaml` and it is created in your home folder. Then start the SILW server like this:
|
60
|
+
|
61
|
+
silw server start -c ~/silw.yaml
|
62
|
+
|
63
|
+
View the data collected in your web browser by pointing it to `http://0.0.0.0:8080`, if the `server.port` in the config file was `8080`. You will see a similar interface:
|
64
|
+
|
65
|
+

|
66
|
+
|
67
|
+
Run: `silw server stop`, to stop the server.
|
68
|
+
|
69
|
+
### Integration with [Logstash](http://logstash.net/)
|
70
|
+
Because you are using a centralized logging system for collecting your metrics :) SILW can be configured to echo the stats collected to [Logstash](http://logstash.net/).
|
71
|
+
|
72
|
+
# Echo the stats directly to logstash
|
73
|
+
:logstash:
|
74
|
+
:host: localhost
|
75
|
+
:port: 5228, udp
|
76
|
+
# or:
|
77
|
+
# :port: 5229, tcp
|
78
|
+
|
79
|
+
Excerpt from a SILW-logstash output:
|
80
|
+
|
81
|
+
{
|
82
|
+
"message" => "{\"@tags\":[],\"@fields\":{\"host\":\"honeypot\",\"diskio\":[0,24]},
|
83
|
+
\"@timestamp\":\"2014-04-18T13:48:00.663-04:00\",\"@version\":\"1\",\"severity\":\"INFO\"}",
|
84
|
+
"@version" => "1",
|
85
|
+
"@timestamp" => "2014-04-18T17:48:00.663Z",
|
86
|
+
"host" => "127.0.0.1:50719",
|
87
|
+
"type" => "silw",
|
88
|
+
"@tags" => [],
|
89
|
+
"@fields" => {
|
90
|
+
"host" => "honeypot",
|
91
|
+
"diskio" => [
|
92
|
+
[0] 0,
|
93
|
+
[1] 24
|
94
|
+
]
|
95
|
+
},
|
96
|
+
"severity" => "INFO"
|
97
|
+
}
|
98
|
+
|
99
|
+
|
100
|
+
SILW is using the [logstash-logger](https://github.com/dwbutler/logstash-logger) gem
|
101
|
+
|
102
|
+
### Testing
|
103
|
+
|
104
|
+
cd silw
|
105
|
+
bundle exec rake
|
106
|
+
|
107
|
+
### Contributing
|
108
|
+
|
109
|
+
* Fork it
|
110
|
+
* Create your feature branch (``git checkout -b my-new-feature``)
|
111
|
+
* Add some tests and please make sure they pass
|
112
|
+
* Commit your changes (``git commit -am 'Added some feature'``)
|
113
|
+
* Push to the branch (``git push origin my-new-feature``)
|
114
|
+
* Create new Pull Request
|
115
|
+
|
116
|
+
## License
|
117
|
+
Copyright (c) 2014 Florin T.Pătraşcu
|
118
|
+
|
119
|
+
[MIT License](LICENSE.txt)
|
data/Rakefile
ADDED
Binary file
|
data/bin/silw
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib]))
|
3
|
+
require "silw/cli"
|
4
|
+
require "logger"
|
5
|
+
require 'logstash-logger'
|
6
|
+
|
7
|
+
SILW_ENV = ENV['SILW_ENV'] || 'development'
|
8
|
+
LOGFILE = File.expand_path("../../log", __FILE__) + "/request.#{SILW_ENV}.log"
|
9
|
+
PLUGINS_LOGFILE = File.expand_path("../../log", __FILE__) + "/plugins.#{SILW_ENV}.log"
|
10
|
+
LOGS_DIR = File.dirname(LOGFILE)
|
11
|
+
PIDFILE = File.expand_path("../../log", __FILE__) + '/silw_server.pid'
|
12
|
+
|
13
|
+
# check if the log/ folder exists and create it if otherwise
|
14
|
+
FileUtils.mkdir(LOGS_DIR) unless File.directory?(LOGS_DIR)
|
15
|
+
LOG = Logger.new(LOGFILE, 'daily')
|
16
|
+
PLUGINS_LOG = Logger.new(PLUGINS_LOGFILE, 'daily')
|
17
|
+
|
18
|
+
# Suppress back-trace when exiting command
|
19
|
+
Signal.trap("INT") {}
|
20
|
+
|
21
|
+
Silw::Cli.start ARGV
|
data/lib/silw.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require "rubygems"
|
3
|
+
require 'sinatra'
|
4
|
+
require 'haml'
|
5
|
+
require 'rack/contrib'
|
6
|
+
require 'rack/cache'
|
7
|
+
|
8
|
+
require "silw/version"
|
9
|
+
require "silw/plugin"
|
10
|
+
require "silw/command"
|
11
|
+
require "silw/helpers"
|
12
|
+
require "silw/server"
|
13
|
+
|
14
|
+
module Silw
|
15
|
+
LOCALHOST = "localhost"
|
16
|
+
MY_KEY = '~/.ssh/id_dsa'
|
17
|
+
CONFIG_PATH = '~/silw.yaml'
|
18
|
+
|
19
|
+
Dir.chdir(File.expand_path(File.join("silw/plugins"), File.dirname(__FILE__))) do
|
20
|
+
Dir.entries(".").each do |f|
|
21
|
+
next if f[0..0].eql?(".")
|
22
|
+
require "silw/plugins/#{f}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Authenticate
|
27
|
+
def self.with(auth_options, &block)
|
28
|
+
cmd = Command.new(auth_options)
|
29
|
+
if block_given?
|
30
|
+
block.call(cmd)
|
31
|
+
end
|
32
|
+
cmd
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/silw/agent.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Silw
|
2
|
+
class Agent
|
3
|
+
attr_accessor :host, :plugins, :time
|
4
|
+
|
5
|
+
def initialize(host='')
|
6
|
+
@host = host
|
7
|
+
@plugins = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def collect_data(plugin_name, data={}, logger=nil)
|
11
|
+
logger.info data.to_json unless logger.nil?
|
12
|
+
@plugins[plugin_name]=data[plugin_name]
|
13
|
+
@time=Time.now
|
14
|
+
self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/silw/cli.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'chronic_duration'
|
3
|
+
require "yaml"
|
4
|
+
require "thor"
|
5
|
+
require "silw"
|
6
|
+
require "silw/agent"
|
7
|
+
|
8
|
+
Dir["./lib/**/*.rb"].each{|f| require f}
|
9
|
+
|
10
|
+
module Silw
|
11
|
+
class Cli < Thor
|
12
|
+
include Thor::Actions
|
13
|
+
|
14
|
+
method_option :config, :default => Silw::CONFIG_PATH, :aliases => "-c", :desc => "The Silw config file"
|
15
|
+
method_option :server, :aliases => "-s", :desc => "host name of the server to be monitored"
|
16
|
+
method_option :user, :aliases => "-u", :desc => "user that can be used with a specified public key"
|
17
|
+
method_option :key, :aliases => "-k", :desc => "a public key that works for the user specified. Example: ~/.ssh/id_dsa"
|
18
|
+
method_option :port, :aliases => "-p", :desc => "port number for the server (overriding the silw.yaml config file)"
|
19
|
+
method_option :help, :aliases => "-h", :desc => "showing the available command line options"
|
20
|
+
def initialize(*args); super; end
|
21
|
+
|
22
|
+
desc "exec plugin1 plugin2 [plugin3]", "execute sequentially the plugins specified"
|
23
|
+
def exec(*plugins)
|
24
|
+
opts = Silw::Cli::load_config(options[:config])
|
25
|
+
opts[:authentication][:username] = options[:user] if options[:user]
|
26
|
+
opts[:authentication][:password] = options[:key] if options[:key]
|
27
|
+
host = options[:server]
|
28
|
+
logstash_config = opts[:logstash]
|
29
|
+
logger = PLUGINS_LOG
|
30
|
+
|
31
|
+
unless logstash_config.nil?
|
32
|
+
logstash_host = logstash_config[:server] || '0.0.0.0'
|
33
|
+
logstash_port = (logstash_config[:port] || '5229, tcp').split(/\W+/)
|
34
|
+
logger = LogStashLogger.new logstash_host, logstash_port[0], logstash_port[1].to_sym
|
35
|
+
end
|
36
|
+
|
37
|
+
Authenticate.with(opts) do |user|
|
38
|
+
plugins.each do |n|
|
39
|
+
info = user.run(n.to_sym, :at => host).to_json
|
40
|
+
logger.info info
|
41
|
+
puts info
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "info", "server info"
|
47
|
+
def info
|
48
|
+
if pid = Cli.get_pid
|
49
|
+
puts "SILW process info:"
|
50
|
+
puts " pid: #{Cli.get_pid}"
|
51
|
+
else
|
52
|
+
puts "SILW server is not running."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# start a simple server for publishing the stats collected. The stats
|
57
|
+
# are those specified by the silw.yaml config file.
|
58
|
+
desc "server start|stop", "start or stop a Sinatra-based SILW server"
|
59
|
+
def server(cmd)
|
60
|
+
if cmd =~ /^start/
|
61
|
+
fork do
|
62
|
+
EM.run do
|
63
|
+
app_config = Silw::Cli::load_config(options[:config])
|
64
|
+
hosts = app_config[:monitoring]
|
65
|
+
app_container = 'thin'
|
66
|
+
host = '0.0.0.0'
|
67
|
+
port = ENV['PORT'] || app_config[:server][:port]
|
68
|
+
agents = {}
|
69
|
+
logstash_config = app_config[:logstash]
|
70
|
+
logger = PLUGINS_LOG
|
71
|
+
|
72
|
+
unless logstash_config.nil?
|
73
|
+
logstash_host = logstash_config[:server] || '0.0.0.0'
|
74
|
+
logstash_port = (logstash_config[:port] || '5229, tcp').split(/\W+/)
|
75
|
+
logger = LogStashLogger.new logstash_host, logstash_port[0], logstash_port[1].to_sym
|
76
|
+
end
|
77
|
+
|
78
|
+
app = Sinatra.new(Server)
|
79
|
+
app.set silw_config: app_config
|
80
|
+
app.set agents: agents
|
81
|
+
app.use ::Rack::CommonLogger, LOGFILE
|
82
|
+
|
83
|
+
$stdout.reopen(LOGFILE)
|
84
|
+
$stderr.reopen(LOGFILE)
|
85
|
+
|
86
|
+
begin
|
87
|
+
# Start the background monitoring tasks here ... if any defined?!
|
88
|
+
hosts.keys.each do |host|
|
89
|
+
EM.add_periodic_timer(ChronicDuration.parse(hosts[host][:freq])) do
|
90
|
+
Authenticate.with(app_config) do |user|
|
91
|
+
agent = Agent.new host
|
92
|
+
hosts[host][:plugins].scan(/(\w+)/).flatten.each do |n|
|
93
|
+
agent.collect_data n.to_sym, user.run(n.to_sym, :at => host), logger
|
94
|
+
end
|
95
|
+
agents[host] = agent
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
dispatch = Rack::Builder.app do
|
101
|
+
map '/' do
|
102
|
+
run app
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
Rack::Server.start({
|
107
|
+
app: dispatch,
|
108
|
+
server: app_container,
|
109
|
+
Host: host,
|
110
|
+
Port: port
|
111
|
+
})
|
112
|
+
|
113
|
+
File.open(PIDFILE, 'w') { |f| f << Process.pid }
|
114
|
+
# CTRL-C? Should we do something about that?
|
115
|
+
Signal.trap('INT') do
|
116
|
+
puts
|
117
|
+
Cli.stop
|
118
|
+
end
|
119
|
+
|
120
|
+
# stopped using the script itself?
|
121
|
+
Signal.trap('HUP') do
|
122
|
+
puts
|
123
|
+
Cli.stop
|
124
|
+
end
|
125
|
+
|
126
|
+
rescue => e
|
127
|
+
LOG.error "#{e} at #{e.backtrace.join("\n")}"
|
128
|
+
# $stderr.puts "Cannot load the silw server; error: #{$!.message}"
|
129
|
+
exit 1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end # fork
|
133
|
+
else
|
134
|
+
Cli.stop
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.stop
|
139
|
+
pid = self.get_pid
|
140
|
+
if pid
|
141
|
+
Process.kill('HUP', pid)
|
142
|
+
File.unlink(PIDFILE)
|
143
|
+
else
|
144
|
+
exit -1
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.get_pid
|
149
|
+
if File.exists?(PIDFILE)
|
150
|
+
pid = File.read(PIDFILE).to_i
|
151
|
+
pid > 0 ? pid : nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
def self.load_config(path=CONFIG_PATH)
|
157
|
+
config_file = File.expand_path(path)
|
158
|
+
if File.exists?(config_file)
|
159
|
+
::YAML.load_file config_file
|
160
|
+
else
|
161
|
+
{:authentication => {:username => 'nobody', :password => '~/.ssh/id_dsa'}}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|