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

Added DNS zone check #29

Merged
merged 1 commit into from
Mar 29, 2018
Merged
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
3 changes: 0 additions & 3 deletions .kitchen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ platforms:
- name: debian-8

suites:
- name: ruby-21
driver:
image: ruby:2.1-slim
- name: ruby-22
driver:
image: ruby:2.2-slim
Expand Down
1 change: 0 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

MethodLength:
Max: 200

Expand Down
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ before_install:
install:
- bundle install
rvm:
- 2.1
- 2.2
- 2.3.0
- 2.4.1
Expand All @@ -32,7 +31,6 @@ deploy:
on:
tags: true
all_branches: true
rvm: 2.1
rvm: 2.2
rvm: 2.3.0
rvm: 2.4.1
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
This CHANGELOG follows the format listed [here](https://github.com/sensu-plugins/community/blob/master/HOW_WE_CHANGELOG.md)

## [Unreleased]
### Added
- Added many checks for DNS zone (@yuri-zubov sponsored by Actility, https://www.actility.com)

### Security
- updated yard dependency to `~> 0.9.11` per: https://nvd.nist.gov/vuln/detail/CVE-2017-17042 (@yuri-zubov sponsored by Actility, https://www.actility.com)
- updated rubocop dependency to `~> 0.51.0` per: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-8418. (@yuri-zubov sponsored by Actility, https://www.actility.com)


### Breaking Changes
- Dropping ruby `< 2.2` support (@yuri-zubov)

## [1.4.0] - 2018-03-21
### Added
Expand Down
193 changes: 193 additions & 0 deletions bin/check-dns-zone.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#! /usr/bin/env ruby
#
# check-dns
#
# DESCRIPTION:
# This plugin checks DNS Zone using dnsruby and whois.
#
# OUTPUT:
# plain text
#
# PLATFORMS:
# Linux, BSD
#
# DEPENDENCIES:
# gem: sensu-plugin
# gem: dnsruby
# gem: whois
#
# USAGE:
# example commands
#
# NOTES:
# Does it behave differently on specific platforms, specific use cases, etc
#
# LICENSE:
# Zubov Yuri <yury.zubau@gmail.com> sponsored by Actility, https://www.actility.com
# Released under the same terms as Sensu (the MIT license); see LICENSE
# for details.
#

require 'sensu-plugin/check/cli'
require 'dnsruby'
require 'whois'
require 'whois/parser'
require 'ipaddr'
require 'resolv'

#
# DNS
#
class DNSZone < Sensu::Plugin::Check::CLI
option :domain,
description: 'Domain to resolve (or ip if type PTR)',
short: '-d DOMAIN',
long: '--domain DOMAIN'

option :threshold,
description: 'Percentage of DNS queries that must succeed',
short: '-l PERCENT',
long: '--threshold PERCENT',
proc: proc(&:to_i),
default: 100

option :timeout,
description: 'Set timeout for query',
short: '-T TIMEOUT',
long: '--timeout TIMEOUT',
proc: proc(&:to_i),
default: 5

option :warn_only,
description: 'Warn instead of critical on failure',
short: '-w',
long: '--warn-only',
boolean: true

def resolve_ns
Resolv::DNS.new.getresources(config[:domain], Resolv::DNS::Resource::IN::NS).map { |e| e.name.to_s }
end

def check_whois(entries)
errors = []
success = []
record = Whois.whois(config[:domain])
parser = record.parser
additional_text = "(whois #{parser.nameservers.map(&:name)}) (dig #{entries})"
if Set.new(parser.nameservers.map(&:name)) == Set.new(entries)
success << "Resolved #{config[:domain]} #{config[:type]} equal with whois #{additional_text}"
else
errors << "Resolved #{config[:domain]} #{config[:type]} did not include #{config[:result]} #{additional_text}"
end
[errors, success]
end

def check_results(entries)
errors = []
success = []

%w[check_whois check_axfr soa_query].each do |check|
result = send(check, entries)
errors.push(*result[0])
success.push(*result[1])
end

result = check_dns_connection(entries, true)
errors.push(*result[0])
success.push(*result[1])

result = check_dns_connection(entries, false)
errors.push(*result[0])
success.push(*result[1])

[errors, success]
end

def check_dns_connection(entries, use_tcp = false)
errors = []
success = []

entries.each do |ns|
Dnsruby::Resolv.getaddresses(ns).each do |ip|
begin
resolv = Dnsruby::Resolver.new(nameserver: ip.to_s, do_caching: false, use_tcp: use_tcp)
resolv.query_timeout = config[:timeout]
resolv.query(config[:domain], Dnsruby::Types.ANY)
type_of_connection = use_tcp ? 'tcp' : 'udp'
success << "Resolved DNS #{ns}(#{ip}) uses #{type_of_connection}"
rescue StandardError
errors << "Resolved DNS #{ns}(#{ip}) doesn't use #{type_of_connection}"
end
end
end
[errors, success]
end

def check_axfr(entries)
errors = []
success = []

entries.each do |ns|
Dnsruby::Resolv.new.getaddresses(ns).each do |ip|
begin
resolv = Dnsruby::Resolver.new(nameserver: ip.to_s, do_caching: false)
resolv.query_timeout = config[:timeout]
resolv.query(config[:domain], 'AXFR', 'IN')

errors << "Resolved DNS #{ns}(#{ip}) has AXFR"
rescue StandardError
success << "Resolved DNS #{ns}(#{ip}) doesn't have AXFR"
end
end
end
[errors, success]
end

def soa_query(entries)
errors = []
success = []
resp = ::Resolv::DNS.new.getresources(config[:domain], Resolv::DNS::Resource::IN::SOA)
primary_serial_number = resp.map(&:serial).first
primary_server = resp.map { |r| r.mname.to_s }
primary_server_name = resp.map { |r| r.rname.to_s }

entries.each_with_index do |ns, _index|
::Resolv::DNS.new.getaddresses(ns).each do |ip|
serial_number = nil
server_name = nil
server = nil

::Resolv::DNS.open nameserver: ip.to_s, ndots: 1 do |dns|
resp = dns.getresources(config[:domain], Resolv::DNS::Resource::IN::SOA)
serial_number = resp.map(&:serial).first
server_name = resp.map { |r| r.rname.to_s }
server = resp.map { |r| r.mname.to_s }
end

if serial_number == primary_serial_number
success << "SOA Query correct for server #{ns}(#{ip})} SOA #{server_name} (#{serial_number}) #{server} - \
SOA primary server #{primary_server_name} (#{primary_serial_number}) #{primary_server}"
else
errors << "SOA Query wrong for server #{ns}(#{ip})} SOA #{server_name} (#{serial_number}) #{server} - \
SOA primary server #{primary_server_name} (#{primary_serial_number}) #{primary_server}"
end
end
end

[errors, success]
end

def run
unknown 'No domain specified' if config[:domain].nil?

entries = resolve_ns
errors, success = check_results(entries)
percent = success.count.to_f / (success.count.to_f + errors.count.to_f) * 100
if percent < config[:threshold]
output = "#{percent.to_i}% of tests succeeded: #{errors.uniq.join(", \n")}"
config[:warn_only] ? warning(output) : critical(output)
else
ok(success.uniq.join(", \n"))
end
end
end
4 changes: 2 additions & 2 deletions bin/check-dns.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def resolve_domain(server)
begin
entry = resolv.query(config[:domain], config[:type], config[:class])
resolv.query_timeout = config[:timeout]
rescue => e
rescue StandardError => e
entry = e
end
entries << entry
Expand All @@ -155,7 +155,7 @@ def check_against_regex(entries, regex)
if answer.match(regex)
ok "Resolved #{config[:domain]} #{config[:type]} matched #{regex}"
end
end # b.each()
end
critical "Resolved #{config[:domain]} #{config[:type]} did not match #{regex}"
end

Expand Down
2 changes: 1 addition & 1 deletion bin/metrics-dns.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def run
output "#{config[:scheme]}.#{config[:type]}.#{key_name}.response_time", result.to_f.round(8)
rescue Dnsruby::NXDomain
critical "Could not resolve #{config[:domain]} #{config[:type]} record"
rescue => e
rescue StandardError => e
unknown e
end

Expand Down
8 changes: 5 additions & 3 deletions sensu-plugins-dns.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
s.platform = Gem::Platform::RUBY
s.post_install_message = 'You can use the embedded Ruby by setting EMBEDDED_RUBY=true in /etc/default/sensu'
s.require_paths = ['lib']
s.required_ruby_version = '>= 2.0.0'
s.required_ruby_version = '>= 2.2.0'
# s.signing_key = File.expand_path(pvt_key) if $PROGRAM_NAME =~ /gem\z/
s.summary = 'Sensu plugins for dns'
s.test_files = s.files.grep(%r{^(test|spec|features)/})
Expand All @@ -38,6 +38,8 @@ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
s.add_runtime_dependency 'dnsruby', '~> 1.59', '>= 1.59.2'
s.add_development_dependency 'codeclimate-test-reporter', '~> 0.4'
s.add_development_dependency 'github-markup', '~> 1.3'
s.add_runtime_dependency 'whois', '~> 4.0'
s.add_runtime_dependency 'whois-parser', '~> 1.0'
s.add_development_dependency 'kitchen-docker', '~> 2.6'
s.add_development_dependency 'kitchen-localhost', '~> 0.3'
# locked to keep ruby 2.1 support, this is pulled in by test-kitchen
Expand All @@ -46,8 +48,8 @@ Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
s.add_development_dependency 'rake', '~> 10.0'
s.add_development_dependency 'redcarpet', '~> 3.2'
s.add_development_dependency 'rspec', '~> 3.1'
s.add_development_dependency 'rubocop', '~> 0.49.0'
s.add_development_dependency 'rubocop', '~> 0.51.0'
# intentionally locked as 1.17 requires ruby 2.3+
s.add_development_dependency 'test-kitchen', '~> 1.16.0'
s.add_development_dependency 'yard', '~> 0.8'
s.add_development_dependency 'yard', '~> 0.9.11'
end