Skip to content

Commit

Permalink
POC
Browse files Browse the repository at this point in the history
  • Loading branch information
kubicek committed Mar 9, 2024
1 parent cc14ca7 commit dc5540d
Show file tree
Hide file tree
Showing 13 changed files with 496 additions and 12 deletions.
110 changes: 110 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
PATH
remote: .
specs:
xmon (0.1.0)
activesupport (~> 7.0.8)
colorize
dnsruby
httparty
ruby-nmap
slop
whois-parser

GEM
remote: https://rubygems.org/
specs:
activesupport (7.0.8.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
ast (2.4.2)
coderay (1.1.3)
colorize (1.1.0)
command_mapper (0.3.2)
concurrent-ruby (1.2.3)
dnsruby (1.70.0)
simpleidn (~> 0.2.1)
httparty (0.21.0)
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
i18n (1.14.4)
concurrent-ruby (~> 1.0)
json (2.7.1)
language_server-protocol (3.17.0.3)
lint_roller (1.1.0)
method_source (1.0.0)
mini_mime (1.1.5)
minitest (5.22.2)
multi_xml (0.6.0)
nokogiri (1.16.2-arm64-darwin)
racc (~> 1.4)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
racc
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
racc (1.7.3)
rainbow (3.1.1)
rake (13.1.0)
regexp_parser (2.9.0)
rexml (3.2.6)
rubocop (1.62.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-performance (1.20.2)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
ruby-nmap (1.0.3)
command_mapper (~> 0.3)
nokogiri (~> 1.3)
ruby-progressbar (1.13.0)
simpleidn (0.2.1)
unf (~> 0.1.4)
slop (4.10.1)
standard (1.34.0)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.60)
standard-custom (~> 1.0.0)
standard-performance (~> 1.3)
standard-custom (1.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.50)
standard-performance (1.3.1)
lint_roller (~> 1.1)
rubocop-performance (~> 1.20.2)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.9.1)
unicode-display_width (2.5.0)
whois (5.1.1)
whois-parser (2.0.0)
activesupport (>= 4)
whois (>= 4.1.0)

PLATFORMS
arm64-darwin-22

DEPENDENCIES
pry
rake (~> 13.0)
standard (~> 1.3)
xmon!

BUNDLED WITH
2.4.22
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Xmon

TODO: Delete this and the text below, and describe your gem
Yet another network monitoring tool

Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/xmon`. To experiment with that code, run `bin/console` for an interactive prompt.
Use DSL to describe your network and services, run periodic checks and get notified when something changes.

## Installation

Expand All @@ -18,7 +18,13 @@ If bundler is not being used to manage dependencies, install the gem by executin

## Usage

TODO: Write usage instructions here
Create definition file like <examples/nic.rb>

Run monitor

```
bin/xmon -d examples/nic.rb
```

## Development

Expand Down
30 changes: 30 additions & 0 deletions bin/xmon
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/setup"
require "xmon"
require "slop"
require "irb"

begin
opts = Slop.parse do |o|
o.banner = "Usage: xmon [options]"
o.array '-d', '--definition', 'Definition file to load', required: true
o.on '-v', '--version' do
puts "1.1.1"
exit
end

o.on '-h', '--help' do
puts o
exit
end
end
rescue Slop::Error => e
puts e
exit
end

opts[:definition].each do |file|
Xmon.load(file).check
end
27 changes: 27 additions & 0 deletions examples/nic.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
describe "nic.cz", type: :domain do
describe :rdap do
status :server_transfer_prohibited
registrar "REG-CZNIC"
registrant "CZ-NIC"
expires "2027-03-14"
end

describe :dns do
dnssec :valid
nameservers ["a.ns.nic.cz", "b.ns.nic.cz", "d.ns.nic.cz"]
record "www", :a, "217.31.205.50"
record "www", :aaaa, "2001:1488:0:3::2"
end
end

describe "217.31.205.50", type: :ipv4 do
port 80, type: :tcp do
status :open
end
port 443, type: :tcp, protocol: :https do
host "www.nic.cz"
server "nginx"
status_code 200
cert_sn "04E732C227971E1E92114CE4E26657CD6258"
end
end
40 changes: 39 additions & 1 deletion lib/xmon.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
# frozen_string_literal: true

require "pry"
require "colorize"
# require 'net/protocol'

require_relative "xmon/version"
require_relative "xmon/descriptions"

require_relative "xmon/tcp"
require_relative "xmon/ssl"
require_relative "xmon/dns"
require_relative "xmon/rdap"

module Xmon
class Error < StandardError; end
# Your code goes here...

def self.compare(a, b)
if a == b
[:ok, a]
else
[:fail, a, b]
end
end

def self.print_results(results)
# puts "Results: #{results.inspect}".colorize(:blue)
results.each do |res|
res.each do |r|
if r.is_a?(Array)
if r[0] == :ok
puts "OK: #{r[1]}".colorize(:green)
else
puts "FAIL: #{r[1]} != #{r[2]}".colorize(:red)
end
end
end
end
end

def self.load(file)
d = Description.new
d.instance_eval(File.read(file))
d
end
end
94 changes: 94 additions & 0 deletions lib/xmon/descriptions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
module Xmon
class Description
def initialize
@descriptions = []
end

def define_attributes(attributes)
attributes.each do |m|
self.class.send(:define_method, m) do |*args|
if args[0]
instance_variable_set(:"@#{m}", args[0])
else
instance_variable_get(:"@#{m}")
end
end
end
end

def describe(*args, **kwargs, &)
unless @description
if kwargs[:type] == :domain
@description = DomainDescription.new(args[0])
elsif kwargs[:type] == :ipv4
@description = IPv4Description.new(args[0])
else
puts "unknown block given with args: #{args} and kwargs: #{kwargs}"
@description = Description.new
end
end

if block_given?
@description.instance_eval(&)
else
@description = @description.class.new(*args, **kwargs)
end
@descriptions ||= []
@descriptions << @description
@description = nil
end

def status(status) # standard:disable Style/TrivialAccessors
@status = status
end

def check
@results = []
(@descriptions || []).each { |d|
puts "#{d.class} #{@name}".colorize(:yellow)
res = d.check
@results << res

Xmon.print_results([res])
}
end
end

class DomainDescription < Description
def initialize(name, *)
@name = name
end

def describe(*args, **kwarg)
if args == [:rdap]
@description = Xmon::RDAP.new(@name)
elsif args == [:dns]
@description = Xmon::DNS.new(@name)
end
super
end
end

class IPv4Description < Description
def initialize(address, *)
@address = address
end

def describe(*, **kwargs)
if kwargs[:type] == :tcp
@description = if kwargs[:protocol] == :https
Xmon::SSL.new(@address, *, **kwargs)
else
Xmon::TCP.new(@address, *, **kwargs)
end
elsif kwargs[:type] == :udp
@description = Xmon::UDP.new(*, **kwargs)
end
super
end

def port(...)
describe(...)
end
end
end
28 changes: 28 additions & 0 deletions lib/xmon/dns.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require "dnsruby"

module Xmon
class DNS < Description
def initialize(domain)
@domain = domain
define_attributes([:nameservers, :records, :dnssec])
end

def record(name, type, value)
@records ||= []
@records << {name: name, type: type, value: value}
end

def fetch(record, type = "A")
Dnsruby::Resolver.new.query(record, type).answer.map { |a| a.respond_to?(:address) ? a.address : a.rdata }.flatten.map(&:to_s).sort
end

def check
r = [Xmon.compare(@nameservers, fetch(@domain, "NS"))]

@records.each do |record|
r << Xmon.compare(record[:value], fetch(record[:name] + "." + @domain, record[:type].to_s.upcase).sort.join(","))
end
r
end
end
end
30 changes: 30 additions & 0 deletions lib/xmon/rdap.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require "httparty"

module Xmon
class RDAP < Description
def initialize(domain, **opts)
@domain = domain
define_attributes([:registrant, :registrar, :expires])
end

def fetch(record)
response = HTTParty.get("https://rdap.nic.cz/domain/#{record}")
response = JSON.parse(response.body, symbolize_names: true)
{
registrant: response[:entities].detect { |a| a[:roles] == ["registrant"] }[:handle],
registrar: response[:entities].detect { |a| a[:roles] == ["registrar"] }[:handle],
nameservers: response[:nameservers].map { |a| a[:ldhName] }.sort,
expiration: response[:events].detect { |a| a[:eventAction] == "expiration" }[:eventDate],
status: response[:status].map { |s| s.split(" ").join("_") }.sort.join("_").to_sym
}
end

def check
checker = fetch(@domain)
[Xmon.compare(@status, checker[:status]),
Xmon.compare(@registrant, checker[:registrant]),
Xmon.compare(@registrar, checker[:registrar]),
Xmon.compare(@expires, checker[:expiration][0, 10])]
end
end
end
Loading

0 comments on commit dc5540d

Please sign in to comment.