From 3db69486ad9a995a82bfe760fbd48eb37cbef148 Mon Sep 17 00:00:00 2001 From: Markus Bucher Date: Wed, 23 Oct 2024 22:02:13 +0200 Subject: [PATCH] Add check for comparing parsed errata with upstream --- README.md | 7 ++- check_latest_errata.rb | 140 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100755 check_latest_errata.rb diff --git a/README.md b/README.md index df52480..60caa05 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,10 @@ To have an idea about how many errata and also how many packages per errata on a bundle exec analyzer.rb -This requires the development gem-group to be installed by bundler +It is also possible now to check if the latest security-notices that can be found on the RSS-feeds are already part of the errata-file. +The `check_latest_errata.rb` command returns a nagios-compliant output and return-code. - bundle config set with 'development' +This requires the `monitor` gem-group to be installed by bundler + + bundle config set with 'monitor' bundle diff --git a/check_latest_errata.rb b/check_latest_errata.rb new file mode 100755 index 0000000..aae9dfc --- /dev/null +++ b/check_latest_errata.rb @@ -0,0 +1,140 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +require 'optparse' +require_relative 'lib/deb_erratum_rss' +require_relative 'lib/errata_statistics' + +# get Ubuntu-ESM errata for specific releases from USN-API instead of RSS-feed, +# because RSS-feed probably does not hold latest errata for Ubuntu-ESM releases +USE_USN_API = true + +search_num = 5 +warn_num = 3 +crit_num = 1 +@debug = false +@res = {} +@perf_data = [] +@long_text = [] + +def eval_result(id, found, searched, warn, crit) + @res[id] = case found.length + when (crit + 1)..warn then :warning + when 0..crit then :critical + end + + # see https://nagios-plugins.org/doc/guidelines.html#AEN200 + @perf_data << "'#{id}'=#{found.length};#{warn};#{crit};0;#{searched.length}" + + @long_text << if found.length == searched.length + "#{id} errata up-to-date" + else + "#{id} errata miss #{(searched - found).join(', ')}" + end +end + +optparse = OptionParser.new do |opts| + opts.banner = 'Usage: check_latest_errata.rb [options] ' + + opts.on('-q', '--query-num NUM', Integer, "Number of errata that should be queried (default: #{search_num})") do |v| + search_num = v + end + opts.on('-w', '--warn NUM', Integer, "Threshold for warning (default: #{warn_num})") do |v| + raise 'warning threshold must be positive' if v.negative? + + warn_num = v + end + opts.on('-c', '--crit NUM', Integer, "Threshold for critical (default: #{crit_num})") do |v| + raise 'critical threshold must be positive' if v.negative? + + crit_num = v + end + opts.on('-d', '--[no-]debug', 'Debug output for testing') do |v| + @debug = v + end +end +optparse.parse! + +path = ARGV.pop +unless path + warn optparse + exit 3 +end + +raise 'critical threshold must be smaller than query-num' if crit_num > search_num +raise 'warning threshold must be smaller than query-num' if warn_num > search_num +raise 'critical threshold must be smaller than warning threshold' if crit_num > warn_num +raise 'errata file path is not a directory' unless File.directory? path + +begin + # Debian + dsa = LatestDsaErratum.new + dla = LatestDlaErratum.new + warn "Latest DLA Erratum #{dla.latest_id} (#{dla.latest_published})" if @debug + warn "Latest DSA Erratum #{dsa.latest_id} (#{dsa.latest_published})" if @debug + deb_stats = ErrataStatistics.new File.join(path, 'debian_errata.json') + search_ids = dla.recent(search_num).map(&:erratum_id) + search_ids += dsa.recent(search_num).map(&:erratum_id) + found = deb_stats.search_for_name(search_ids).map { |x| x['name'] } + warn "Searched for #{search_ids}\nFound #{found.inspect}" if @debug + eval_result('debian', found, search_ids, warn_num * 2, crit_num * 2) + + # Ubuntu + usn = LatestUsnErratum.new + warn "Latest USN Erratum #{usn.latest_id} (#{usn.latest_published})" if @debug + stats = ErrataStatistics.new File.join(path, 'ubuntu_errata.json') + search_ids = usn.recent(search_num).map(&:erratum_id) + warn "Search for #{search_ids}" if @debug + found = stats.search_for_name(search_ids).map { |x| x['name'] } + warn "Found #{found.inspect}" if @debug + eval_result('ubuntu', found, search_ids, warn_num, crit_num) + + # Ubuntu-ESM + search_ids = if USE_USN_API + require_relative 'lib/ubuntu_security_api' + esm_releases = [ + 'xenial', + 'bionic' + ] + usa = UbuntuSecurityApi.new(retry: 3) + esm_releases.map do |rel| + usa.latest_errata(rel, search_num).map { |notice| notice['id'] } + end.flatten.uniq + else + usn = LatestUsnErratum.new + warn "Latest USN Erratum #{usn.latest_id} (#{usn.latest_published})" if @debug + usn.recent(search_num * 2).map(&:erratum_id) + end + stats = ErrataStatistics.new File.join(path, 'ubuntu-esm_errata.json') + # Look for the doubled amount since it is less likely that USNs are found for ESM releases + warn "Search for #{search_ids}" if @debug + found = stats.search_for_name(search_ids).map { |x| x['name'] } + warn "Found #{found.map { |e| e['name'] }.inspect}" if @debug + factor = search_ids.length / search_num + eval_result('ubuntu-esm', found, search_ids, warn_num * factor, crit_num * factor) + + out = StringIO.new + out << 'ERRATA ' + critical = @res.map { |_k, v| v == :critical }.first + warning = @res.map { |_k, v| v == :warning }.first + out << if critical + "CRIT #{critical} misses errata" + elsif warning + "WARN #{warning} misses errata" + else + 'OK' + end + out << '; |' << @perf_data.shift << "\n" + out << @long_text.join(";\n") + out << '; | ' + out << @perf_data.join("\n") + + puts out.string +rescue StandardError => e + puts "ERRATA EXCEPTION #{e}" + exit 3 +end + +exit 2 if @res.values.include? :critical +exit 1 if @res.values.include? :warning