diff --git a/.github/workflows/rubocop.yml b/.github/workflows/rubocop.yml new file mode 100644 index 0000000..1a88c37 --- /dev/null +++ b/.github/workflows/rubocop.yml @@ -0,0 +1,26 @@ +name: rubocop Linting +on: + pull_request: + push: + branches: + - master +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libapt-pkg-dev + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3 + bundler-cache: true + - name: Setup rubocop + run: | + bundle install --with=rubocop + - name: Execute rubocop + run: | + bundle exec rubocop diff --git a/.github/workflows/spec_tests.yml b/.github/workflows/spec_tests.yml new file mode 100644 index 0000000..6d4aab2 --- /dev/null +++ b/.github/workflows/spec_tests.yml @@ -0,0 +1,26 @@ +name: Spec Tests +on: + pull_request: + push: + branches: + - master +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y libapt-pkg-dev + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.3 + bundler-cache: true + - name: Setup bundle + run: | + bundle install --with=test + - name: Run rspec + run: | + bundle exec rspec diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2d149ce..25f3ed1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,6 +24,3 @@ jobs: - name: Run tests run: | bundle exec ruby test/all.rb - - name: Execute rubocop - run: | - bundle exec rubocop diff --git a/.gitignore b/.gitignore index d822f05..2f12eb5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ debian_config.json debian_errata.json ubuntu_config.json ubuntu_errata.json +coverage/ diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/.rubocop.yml b/.rubocop.yml index 88b2c37..b6d41ab 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,6 +2,9 @@ AllCops: TargetRubyVersion: 3.3 NewCops: enable +require: + - rubocop-rspec + Layout/LineLength: Max: 159 @@ -37,6 +40,15 @@ Metrics/PerceivedComplexity: Max: 25 +RSpec/MultipleDescribes: + Exclude: + - 'spec/lib/deb_erratum_rss_spec.rb' + + +Style/BlockComments: + Exclude: + - 'spec/spec_helper.rb' + Style/ClassVars: Exclude: - 'debRelease.rb' diff --git a/Gemfile b/Gemfile index f145e01..baed4b1 100644 --- a/Gemfile +++ b/Gemfile @@ -18,14 +18,30 @@ gem "bzip2-ffi", "~> 1.0", group: %i[build test] # Added at 2018-09-07 16:41:14 +0200 by markus: gem "test-unit", "~> 3.5", group: [:test] +gem "rspec", "~> 3.13", group: :test + +gem "simplecov-lcov", "~> 0.8.0", group: :test + +gem "simplecov", "~> 0.22.0", group: :test + +gem "rspec-collection_matchers", "~> 1.2", group: :test + # Added at 2021-11-12 17:19:23 +0200 by bernhard: gem 'parallel', '~> 1.20', '< 1.21', group: %i[build test] # Added at 2018-12-05 19:28:10 +0100 by markus: group :rubocop, optional: true do gem "rubocop", "~> 1.51.0" + gem "rubocop-rspec", "~> 3.0" end +gem "json-streamer", "~> 2.1" + group :development, optional: true do gem "byebug", "~> 11.1" + gem "pry", "~> 0.14.2" end + +gem "feedjira", "~> 3.2", group: :monitor + +gem "faraday", "~> 2.12", group: :monitor diff --git a/Gemfile.lock b/Gemfile.lock index c8f941a..ed71c0f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,17 +5,60 @@ GEM byebug (11.1.3) bzip2-ffi (1.1.1) ffi (~> 1.0) - ffi (1.17.0-x86_64-linux-gnu) + coderay (1.1.3) + crass (1.0.6) + diff-lcs (1.5.1) + docile (1.4.1) + faraday (2.12.0) + faraday-net_http (>= 2.0, < 3.4) + json + logger + faraday-net_http (3.3.0) + net-http + feedjira (3.2.3) + loofah (>= 2.3.1, < 3) + sax-machine (>= 1.0, < 2) + ffi (1.17.0) json (2.7.2) + json-stream (1.0.0) + json-streamer (2.1.0) + json-stream + logger (1.6.1) + loofah (2.23.1) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + method_source (1.1.0) + net-http (0.4.1) + uri + nokogiri (1.16.7-x86_64-linux) + racc (~> 1.4) parallel (1.20.1) parser (3.3.3.0) ast (~> 2.4.1) racc power_assert (2.0.3) + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) racc (1.8.0) rainbow (3.1.1) regexp_parser (2.9.2) rexml (3.3.9) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-collection_matchers (1.2.1) + rspec-expectations (>= 2.99.0.beta1) + rspec-core (3.13.2) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) rubocop (1.51.0) json (~> 2.3) parallel (~> 1.10) @@ -28,12 +71,23 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.31.3) parser (>= 3.3.1.0) + rubocop-rspec (3.0.0) + rubocop (~> 1.40) ruby-debian (0.3.8) ruby-progressbar (1.13.0) ruby-xz (1.0.3) + sax-machine (1.3.2) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.13.1) + simplecov-lcov (0.8.0) + simplecov_json_formatter (0.1.4) test-unit (3.6.2) power_assert unicode-display_width (2.5.0) + uri (0.13.1) PLATFORMS x86_64-linux @@ -41,10 +95,19 @@ PLATFORMS DEPENDENCIES byebug (~> 11.1) bzip2-ffi (~> 1.0) + faraday (~> 2.12) + feedjira (~> 3.2) + json-streamer (~> 2.1) parallel (~> 1.20, < 1.21) + pry (~> 0.14.2) + rspec (~> 3.13) + rspec-collection_matchers (~> 1.2) rubocop (~> 1.51.0) + rubocop-rspec (~> 3.0) ruby-debian (~> 0.3.8) ruby-xz (~> 1.0) + simplecov (~> 0.22.0) + simplecov-lcov (~> 0.8.0) test-unit (~> 3.5) BUNDLED WITH diff --git a/README.md b/README.md index 464915a..60caa05 100644 --- a/README.md +++ b/README.md @@ -270,3 +270,18 @@ The `` is somewhat simpler than that for Debian: A single USN URL will suffice as an upstream source of information. `whitelists` and `aliases` are structurally identical to those for Debian. + + +## Analyze created JSON files + +To have an idea about how many errata and also how many packages per errata on avarage were created the following tool can be used: + + bundle exec analyzer.rb + +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. + +This requires the `monitor` gem-group to be installed by bundler + + bundle config set with 'monitor' + bundle diff --git a/analyzer.rb b/analyzer.rb new file mode 100755 index 0000000..4011152 --- /dev/null +++ b/analyzer.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +require_relative 'lib/errata_statistics' + +output_type = nil + +output_type = ARGV.shift.slice(2..).to_sym if ARGV[0].start_with? '--' + +stats = ErrataStatistics.new ARGV[0] +stats.calculate + +case output_type +when :yaml then puts stats.to_yaml +when :json then puts stats.to_json +else stats.print_all +end 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 diff --git a/lib/deb_erratum_rss.rb b/lib/deb_erratum_rss.rb new file mode 100755 index 0000000..d9a44b9 --- /dev/null +++ b/lib/deb_erratum_rss.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'net/http' +require 'feedjira' + +# module to inject #erratum_id into Feedjira's feed-entries +module ErrataEntryUtilities + def erratum_id + title.split.first.slice(/[\w-]+/) + end +end +Feedjira::Parser::RSSEntry.include ErrataEntryUtilities + +# Generic class to extract information from Debian/Ubuntu 'errata' RSS-feeds (security announcements) +class LatestErratum + ACCEPTED_CONTENT_TYPES = [ + 'application/rss+xml', + 'text/xml' + ].freeze + def initialize(url) + res = Net::HTTP.get_response(URI(url)) + raise "Unsupported content-type: #{res['content-type']}" unless ACCEPTED_CONTENT_TYPES.include? res['content-type'].split(';').first + + @feed = Feedjira.parse(res.body) + end + + def latest + entries.first + end + + def latest_published + latest.published + end + + def latest_id + latest.erratum_id + end + + def since_yesterday + @feed.entries.select { |entry| entry.published + 1 >= Time.now } + end + + # returns array of the latest 'num' feed-entries + # TODO: assumes the feed is sorted by date + def recent(num=1) + entries.slice(0, num) + end + + def entries + @feed.entries + end +end + +# read and parse latest Debian Security Announcements (DSA) RSS-feed +class LatestDsaErratum < LatestErratum + def initialize + super('https://www.debian.org/security/dsa') + end +end + +# read and parse latest Debian LTS Security Announcements (DLA) RSS-feed +class LatestDlaErratum < LatestErratum + def initialize + super('https://www.debian.org/lts/security/dla') + end +end + +# read and parse latest Ubuntu Security Notices (USN) RSS-feed +class LatestUsnErratum < LatestErratum + def initialize + super('https://ubuntu.com/security/notices/rss.xml') + end +end diff --git a/lib/errata_statistics.rb b/lib/errata_statistics.rb new file mode 100644 index 0000000..9b52159 --- /dev/null +++ b/lib/errata_statistics.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require 'json/streamer' +require 'date' +require 'yaml' +require 'json' + +# Class to analyze errata.json files created by parser +# collects statistics and can be used to search for specific errata +class ErrataStatistics + attr_reader :pkg_errata, :per_release, :errata_file, :avg_rel_errata + + CHUNK_SIZE = 4096 + STAT_HASH_INIT = { + avg: 0, + max: nil, + min: nil, + max_erratum: nil, + latest_erratum: nil, + latest_erratum_date: nil, + num_errata: 0 + }.freeze + + def initialize(errata_file) + @pkg_errata = STAT_HASH_INIT.clone + @per_release = {} + @avg_rel_errata = 0 + @errata_file = errata_file + end + + def moving_average(avg, new, total) + (new.to_f + (avg * (total - 1))) / total + # new.to_f/total + avg*(1-1.0/total) + end + + def calc_stats_data(stat_hash, packages, erratum) + pkgs = packages.length + stat_hash[:num_errata] += 1 + stat_hash[:avg] = moving_average(stat_hash[:avg], pkgs, stat_hash[:num_errata]) + if stat_hash[:max].nil? || pkgs > stat_hash[:max] + stat_hash[:max] = pkgs + stat_hash[:max_erratum] = erratum['name'] + end + stat_hash[:min] = pkgs if stat_hash[:min].nil? || pkgs < stat_hash[:min] + end + + def latest_erratum(stat_hash, erratum) + date = Date.parse(erratum['issued']) + return unless stat_hash[:latest_erratum_date].nil? || stat_hash[:latest_erratum_date] < date + + stat_hash[:latest_erratum] = erratum['name'] + stat_hash[:latest_erratum_date] = date + end + + def print_stats(stat_hash, indent=0) + s = ' ' * indent + puts "#{s}Errata: #{stat_hash[:num_errata]}" + puts "#{s}Newest: #{stat_hash[:latest_erratum]} (#{stat_hash[:latest_erratum_date]})" + puts "#{s}Packages:" + puts "#{s} average per Erratum: #{stat_hash[:avg].round(2)}" + puts "#{s} max per Erratum: #{stat_hash[:max]} (#{stat_hash[:max_erratum]})" + puts "#{s} min per Erratum: #{stat_hash[:min]}" + end + + def print_all + print_stats(pkg_errata) + + per_release.each do |release, stats| + puts "#{release.inspect}:" + print_stats(stats, 2) + end + end + + def to_h + per_release.merge({ 'all' => pkg_errata }) + end + + def to_yaml + to_h.to_yaml + end + + def to_json(*_args) + to_h.to_json + end + + def each(&) + File.open(errata_file, 'r') do |file| + streamer = Json::Streamer.parser(file_io: file, chunk_size: CHUNK_SIZE) + streamer.get(nesting_level: 1, &) + end + end + + # returns Array of errata whose names are in query_name (Array) + def search_for_name(query_name) + query = query_name.clone + res = [] + each do |erratum| + next unless query_name.include? erratum['name'] + + res << erratum + query.delete erratum['name'] + + break if query.empty? + end + res + end + + def calculate + each do |erratum| + packages = erratum['packages'] + releases = packages.map { |p| p['release'] }.uniq + + # Per Release data + releases.each do |release| + per_release[release] = STAT_HASH_INIT.clone unless per_release.key? release + + pkgs = packages.select { |pkg| pkg['release'] == release } + h = per_release[release] + + # Packages per errata + calc_stats_data(h, pkgs, erratum) + # Latest erratum + latest_erratum(h, erratum) + end + + # Packages per errata + calc_stats_data(pkg_errata, packages, erratum) + + # Average number of releases per erratum + @avg_rel_errata = moving_average(avg_rel_errata, releases.length, pkg_errata[:num_errata]) + + # Latest erratum + latest_erratum(pkg_errata, erratum) + end + end +end diff --git a/lib/ubuntu_security_api.rb b/lib/ubuntu_security_api.rb new file mode 100644 index 0000000..30196b5 --- /dev/null +++ b/lib/ubuntu_security_api.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'faraday' +require 'date' + +# API doc: https://ubuntu.com/security/api/docs +class UbuntuSecurityApi + attr_accessor :retry + + def initialize(opts={}) + @base_url = 'https://ubuntu.com' + @retry = opts[:retry] + end + + def yesterdays_erratum(release=nil) + # ASSUMPTION: among the latest 5 USNs there should be at least one older than one day + res = latest_errata(release, 5).select { |usn| Date.parse(usn['published']) < Date.today } + + raise 'Could not find USN older than one day among the newest 5' if res.empty? + + res.first + end + + def latest_errata(release=nil, limit=nil) + opts = { + order: 'newest', + show_hidden: false + } + opts[:release] = release unless release.nil? + + opts[:limit] = limit unless limit.nil? + + attempt = @retry + begin + res = conn.get('/security/notices.json', opts) + res.body['notices'] + rescue Faraday::ServerError + raise if attempt.nil? || attempt.zero? + + attempt -= 1 + retry + end + end + + def latest_erratum(release=nil) + latest_errata(release, 1).first + end + + private + + def conn + @conn ||= Faraday.new( + url: @base_url, + headers: { 'Content-Type' => 'application/json' } + ) do |builder| + # Sets the Content-Type header to application/json on each request. + # Also, if the request body is a Hash, it will automatically be encoded as JSON. + builder.request :json + + # Parses JSON response bodies. + # If the response body is not valid JSON, it will raise a Faraday::ParsingError. + builder.response :json + + # Raises an error on 4xx and 5xx responses. + builder.response :raise_error + + # Logs requests and responses. + # By default, it only logs the request method and URL, and the request/response headers. + # builder.response :logger + end + end +end diff --git a/spec/fixtures/dsa.xml b/spec/fixtures/dsa.xml new file mode 100644 index 0000000..06d79d8 --- /dev/null +++ b/spec/fixtures/dsa.xml @@ -0,0 +1,223 @@ + + + +Debian Security +https://www.debian.org/security/dsa.rdf +2024-10-25T11:32:10Z +Debian Security Advisories + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +DSA-5795-1 python-sql - security update +https://lists.debian.org/debian-security-announce/2024/msg00209.html +2024-10-21 +<a href="https://security-tracker.debian.org/tracker/DSA-5795-1">https://security-tracker.debian.org/tracker/DSA-5795-1</a> + + +DSA-5794-1 openjdk-17 - security update +https://lists.debian.org/debian-security-announce/2024/msg00208.html +2024-10-21 +<a href="https://security-tracker.debian.org/tracker/DSA-5794-1">https://security-tracker.debian.org/tracker/DSA-5794-1</a> + + +DSA-5793-1 chromium - security update +https://lists.debian.org/debian-security-announce/2024/msg00207.html +2024-10-20 +<a href="https://security-tracker.debian.org/tracker/DSA-5793-1">https://security-tracker.debian.org/tracker/DSA-5793-1</a> + + +DSA-5792-1 webkit2gtk - security update +https://lists.debian.org/debian-security-announce/2024/msg00206.html +2024-10-14 +<a href="https://security-tracker.debian.org/tracker/DSA-5792-1">https://security-tracker.debian.org/tracker/DSA-5792-1</a> + + +DSA-5791-1 python-reportlab - security update +https://lists.debian.org/debian-security-announce/2024/msg00205.html +2024-10-13 +<a href="https://security-tracker.debian.org/tracker/DSA-5791-1">https://security-tracker.debian.org/tracker/DSA-5791-1</a> + + +DSA-5790-1 node-dompurify - security update +https://lists.debian.org/debian-security-announce/2024/msg00204.html +2024-10-13 +<a href="https://security-tracker.debian.org/tracker/DSA-5790-1">https://security-tracker.debian.org/tracker/DSA-5790-1</a> + + +DSA-5789-1 thunderbird - security update +https://lists.debian.org/debian-security-announce/2024/msg00203.html +2024-10-12 +<a href="https://security-tracker.debian.org/tracker/DSA-5789-1">https://security-tracker.debian.org/tracker/DSA-5789-1</a> + + +DSA-5788-1 firefox-esr - security update +https://lists.debian.org/debian-security-announce/2024/msg00202.html +2024-10-10 +<a href="https://security-tracker.debian.org/tracker/DSA-5788-1">https://security-tracker.debian.org/tracker/DSA-5788-1</a> + + +DSA-5787-1 chromium - security update +https://lists.debian.org/debian-security-announce/2024/msg00201.html +2024-10-09 +<a href="https://security-tracker.debian.org/tracker/DSA-5787-1">https://security-tracker.debian.org/tracker/DSA-5787-1</a> + + +DSA-5729-2 apache2 - regression update +https://lists.debian.org/debian-security-announce/2024/msg00200.html +2024-10-08 +<a href="https://security-tracker.debian.org/tracker/DSA-5729-2">https://security-tracker.debian.org/tracker/DSA-5729-2</a> + + +DSA-5786-1 libgsf - security update +https://lists.debian.org/debian-security-announce/2024/msg00199.html +2024-10-05 +<a href="https://security-tracker.debian.org/tracker/DSA-5786-1">https://security-tracker.debian.org/tracker/DSA-5786-1</a> + + +DSA-5785-1 mediawiki - security update +https://lists.debian.org/debian-security-announce/2024/msg00198.html +2024-10-05 +<a href="https://security-tracker.debian.org/tracker/DSA-5785-1">https://security-tracker.debian.org/tracker/DSA-5785-1</a> + + +DSA-5784-1 oath-toolkit - security update +https://lists.debian.org/debian-security-announce/2024/msg00197.html +2024-10-04 +<a href="https://security-tracker.debian.org/tracker/DSA-5784-1">https://security-tracker.debian.org/tracker/DSA-5784-1</a> + + +DSA-5783-1 firefox-esr - security update +https://lists.debian.org/debian-security-announce/2024/msg00196.html +2024-10-04 +<a href="https://security-tracker.debian.org/tracker/DSA-5783-1">https://security-tracker.debian.org/tracker/DSA-5783-1</a> + + +DSA-5782-1 linux - security update +https://lists.debian.org/debian-security-announce/2024/msg00195.html +2024-10-03 +<a href="https://security-tracker.debian.org/tracker/DSA-5782-1">https://security-tracker.debian.org/tracker/DSA-5782-1</a> + + +DSA-5781-1 chromium - security update +https://lists.debian.org/debian-security-announce/2024/msg00194.html +2024-10-03 +<a href="https://security-tracker.debian.org/tracker/DSA-5781-1">https://security-tracker.debian.org/tracker/DSA-5781-1</a> + + +DSA-5780-1 php8.2 - security update +https://lists.debian.org/debian-security-announce/2024/msg00193.html +2024-10-02 +<a href="https://security-tracker.debian.org/tracker/DSA-5780-1">https://security-tracker.debian.org/tracker/DSA-5780-1</a> + + +DSA-5779-1 cups - security update +https://lists.debian.org/debian-security-announce/2024/msg00192.html +2024-09-29 +<a href="https://security-tracker.debian.org/tracker/DSA-5779-1">https://security-tracker.debian.org/tracker/DSA-5779-1</a> + + +DSA-5778-1 cups-filters - security update +https://lists.debian.org/debian-security-announce/2024/msg00191.html +2024-09-29 +<a href="https://security-tracker.debian.org/tracker/DSA-5778-1">https://security-tracker.debian.org/tracker/DSA-5778-1</a> + + +DSA-5777-1 booth - security update +https://lists.debian.org/debian-security-announce/2024/msg00190.html +2024-09-27 +<a href="https://security-tracker.debian.org/tracker/DSA-5777-1">https://security-tracker.debian.org/tracker/DSA-5777-1</a> + + +DSA-5776-1 tryton-server - security update +https://lists.debian.org/debian-security-announce/2024/msg00189.html +2024-09-27 +<a href="https://security-tracker.debian.org/tracker/DSA-5776-1">https://security-tracker.debian.org/tracker/DSA-5776-1</a> + + +DSA-5775-1 chromium - security update +https://lists.debian.org/debian-security-announce/2024/msg00188.html +2024-09-26 +<a href="https://security-tracker.debian.org/tracker/DSA-5775-1">https://security-tracker.debian.org/tracker/DSA-5775-1</a> + + +DSA-5774-1 ruby-saml - security update +https://lists.debian.org/debian-security-announce/2024/msg00187.html +2024-09-20 +<a href="https://security-tracker.debian.org/tracker/DSA-5774-1">https://security-tracker.debian.org/tracker/DSA-5774-1</a> + + +DSA-5773-1 chromium - security update +https://lists.debian.org/debian-security-announce/2024/msg00186.html +2024-09-19 +<a href="https://security-tracker.debian.org/tracker/DSA-5773-1">https://security-tracker.debian.org/tracker/DSA-5773-1</a> + + +DSA-5772-1 libreoffice - security update +https://lists.debian.org/debian-security-announce/2024/msg00185.html +2024-09-17 +<a href="https://security-tracker.debian.org/tracker/DSA-5772-1">https://security-tracker.debian.org/tracker/DSA-5772-1</a> + + +DSA-5771-1 php-twig - security update +https://lists.debian.org/debian-security-announce/2024/msg00184.html +2024-09-17 +<a href="https://security-tracker.debian.org/tracker/DSA-5771-1">https://security-tracker.debian.org/tracker/DSA-5771-1</a> + + +DSA-5770-1 expat - security update +https://lists.debian.org/debian-security-announce/2024/msg00183.html +2024-09-17 +<a href="https://security-tracker.debian.org/tracker/DSA-5770-1">https://security-tracker.debian.org/tracker/DSA-5770-1</a> + + +DSA-5769-1 git - security update +https://lists.debian.org/debian-security-announce/2024/msg00182.html +2024-09-13 +<a href="https://security-tracker.debian.org/tracker/DSA-5769-1">https://security-tracker.debian.org/tracker/DSA-5769-1</a> + + +DSA-5768-1 chromium - security update +https://lists.debian.org/debian-security-announce/2024/msg00181.html +2024-09-11 +<a href="https://security-tracker.debian.org/tracker/DSA-5768-1">https://security-tracker.debian.org/tracker/DSA-5768-1</a> + + +DSA-5767-1 thunderbird - security update +https://lists.debian.org/debian-security-announce/2024/msg00180.html +2024-09-08 +<a href="https://security-tracker.debian.org/tracker/DSA-5767-1">https://security-tracker.debian.org/tracker/DSA-5767-1</a> + + diff --git a/spec/lib/deb_erratum_rss_spec.rb b/spec/lib/deb_erratum_rss_spec.rb new file mode 100644 index 0000000..c0e6996 --- /dev/null +++ b/spec/lib/deb_erratum_rss_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'rspec/collection_matchers' +require './lib/deb_erratum_rss' + +RSpec.describe Feedjira::Parser::RSSEntry do + subject(:entry) { described_class.new } + + it { is_expected.to respond_to :erratum_id } + + [ + ['default', "DSA-0815-1\nsomething", 'DSA-0815-1'], + ['whitespaces', " DSA-4711-1 \nfoo", 'DSA-4711-1'], + ['USN', "USN-0815-1:\nsomething", 'USN-0815-1'], + ['USN-whitespaces', " USN-4711-1: \nfoo", 'USN-4711-1'] + ].each do |testname, input, output| + describe "with #{testname}" do + subject { entry.erratum_id } + + # rubocop:disable RSpec/SubjectStub + before { allow(entry).to receive(:title).and_return(input) } + # rubocop:enable RSpec/SubjectStub + + it { is_expected.to eq(output) } + end + end +end + +RSpec.describe LatestDlaErratum do + subject { described_class.new } + + it { is_expected.to be_a LatestErratum } +end + +RSpec.describe LatestDsaErratum do + subject { described_class.new } + + it { is_expected.to be_a LatestErratum } +end + +RSpec.describe LatestUsnErratum do + subject { described_class.new } + + it { is_expected.to be_a LatestErratum } +end + +RSpec.describe LatestErratum do + subject(:latest_errata) { described_class.new('url') } + + let(:http_response) { instance_double(Net::HTTPResponse) } + + before do + allow(http_response).to receive(:[]).with('content-type').and_return('application/rss+xml') + allow(http_response).to receive(:body).and_return(File.read('spec/fixtures/dsa.xml')) + allow(Net::HTTP).to receive(:get_response).and_return(http_response) + end + + describe '#entries' do + subject { latest_errata.entries } + + it { is_expected.not_to be_empty } + it { is_expected.to be_a(Array) } + end + + describe '#recent' do + subject { latest_errata.recent(num) } + + let(:num) { 2 } + + it { is_expected.not_to be_empty } + it { is_expected.to have(num).entries } + end + + it "accepts content-type 'text/xml'" do + allow(http_response).to receive(:[]).with('content-type').and_return('text/xml') + + expect(described_class.new('url')).to be_a described_class + end + + it 'fails unknown content-type' do + allow(http_response).to receive(:[]).with('content-type').and_return('text/html') + + expect { described_class.new('url') }.to raise_error(RuntimeError) + end +end diff --git a/spec/lib/errata_statistics_spec.rb b/spec/lib/errata_statistics_spec.rb new file mode 100644 index 0000000..3cb8667 --- /dev/null +++ b/spec/lib/errata_statistics_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' +require './lib/errata_statistics' + +RSpec.describe ErrataStatistics do + subject { described_class.new('dummy.json') } + + describe '#moving_average' do + subject { described_class.new('dummy.json').moving_average(*args) } + + [ + ['simple', [5, 15, 2], 10], + ['float', [5, 10, 4], 6.25], + ['float', [6.25, 5, 5], 6.0] + ].each do |testname, input, output| + describe "with #{testname}" do + let(:args) { input } + + it { is_expected.to eq output } + it { is_expected.to be_a Float } + end + end + end +end diff --git a/spec/lib/ubuntu_security_api_spec.rb b/spec/lib/ubuntu_security_api_spec.rb new file mode 100644 index 0000000..1ada91c --- /dev/null +++ b/spec/lib/ubuntu_security_api_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' +require './lib/ubuntu_security_api' + +RSpec.describe UbuntuSecurityApi do + let(:usa) { described_class.new(retry: 3) } + + context 'when get' do + let(:conn) { instance_double(Faraday::Connection) } + let(:notice) { { 'id' => 'USN-1337-1', 'published' => '2024-10-23T19:45:29.964Z' } } + let(:notices) do + [ + notice, + { 'id' => 'USN-1237-1', 'published' => '2024-10-22T18:45:29.964Z' }, + { 'id' => 'USN-1236-1', 'published' => '2024-10-22T17:45:29.964Z' }, + { 'id' => 'USN-1235-1', 'published' => '2024-10-22T16:45:29.964Z' }, + { 'id' => 'USN-1234-1', 'published' => '2024-10-22T15:45:29.964Z' } + ] + end + let(:response) { instance_double(Faraday::Response) } + + before do + allow(usa).to receive(:conn).and_return(conn) + allow(conn).to receive(:get).and_return(response) + allow(response).to receive(:body).and_return({ 'notices' => notices }) + end + + it 'latest errata' do + expect(usa.latest_errata).to eq(notices) + end + + it 'latest n errata' do + allow(response).to receive(:body).and_return({ 'notices' => notices.append({ 'id' => 'too_old' }) }) + expect(usa.latest_errata(nil, 5)).to eq(notices) + end + + it 'latest release erratua' do + usa.latest_errata('bionic') + expect(conn).to have_received(:get).with('/security/notices.json', + { release: 'bionic', + order: 'newest', + show_hidden: false }) + end + + it 'latest erratum' do + allow(response).to receive(:body).and_return({ 'notices' => notices }) + + expect(usa.latest_erratum).to eq(notice) + end + + it "yesterday's erratum" do + notice_today = { 'id' => 'USN-4711-1', 'published' => '2024-10-24T19:45:29.964Z' } + allow(response).to receive(:body).and_return({ 'notices' => [notice_today, *notices.slice(0, 4)] }) + allow(Date).to receive(:today).and_return(Date.new(2024, 10, 24)) + + expect(usa.yesterdays_erratum).to eq(notice) + end + + it "no yesterday's erratum" do + allow(Date).to receive(:today).and_return(Date.new(2024, 10, 22)) + allow(response).to receive(:body).and_return({ 'notices' => notices }) + + expect { usa.yesterdays_erratum }.to raise_error(RuntimeError) + end + + # rubocop:disable RSpec/ExampleLength + it 'retries on server-error 5XX' do + raise_server_error = 2 + + allow(conn).to receive(:get).thrice do + if raise_server_error.zero? + response + else + raise_server_error -= 1 + raise Faraday::ServerError + end + end + + expect(usa.latest_errata).to eq(notices) + end + # rubocop:enable RSpec/ExampleLength + + it 'fails on client-error 4XX' do + allow(response).to receive(:body).and_return({ 'notices' => [] }) + allow(conn).to receive(:get).and_raise(Faraday::ClientError).once + + expect { usa.latest_errata }.to raise_error(Faraday::ClientError) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..731727e --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'simplecov' +SimpleCov.start do + add_filter '/test/' + add_filter '/spec/' + add_filter '/vendor/' +end + +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end