dobby 0.1.0 → 0.1.2

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -0
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +42 -0
  5. data/.travis.yml +12 -0
  6. data/.yardopts +2 -0
  7. data/CHANGELOG.md +8 -0
  8. data/CONTRIBUTING.md +60 -0
  9. data/Gemfile +8 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +103 -0
  12. data/Rakefile +8 -0
  13. data/bin/console +7 -0
  14. data/bin/setup +8 -0
  15. data/config/default.yml +8 -0
  16. data/dobby.gemspec +58 -0
  17. data/lib/dobby.rb +51 -0
  18. data/lib/dobby/builtins.rb +17 -0
  19. data/lib/dobby/cli.rb +64 -0
  20. data/lib/dobby/configuration.rb +58 -0
  21. data/lib/dobby/database.rb +62 -0
  22. data/lib/dobby/defect.rb +74 -0
  23. data/lib/dobby/dpkg.rb +21 -0
  24. data/lib/dobby/error.rb +6 -0
  25. data/lib/dobby/flag_manager.rb +67 -0
  26. data/lib/dobby/flags.yml +8 -0
  27. data/lib/dobby/formatter/abstract_formatter.rb +25 -0
  28. data/lib/dobby/formatter/colorizable.rb +41 -0
  29. data/lib/dobby/formatter/formatter_set.rb +79 -0
  30. data/lib/dobby/formatter/json_formatter.rb +42 -0
  31. data/lib/dobby/formatter/simple_formatter.rb +54 -0
  32. data/lib/dobby/options.rb +149 -0
  33. data/lib/dobby/package.rb +156 -0
  34. data/lib/dobby/package_source/abstract_package_source.rb +17 -0
  35. data/lib/dobby/package_source/dpkg_status_file.rb +85 -0
  36. data/lib/dobby/runner.rb +152 -0
  37. data/lib/dobby/scanner.rb +128 -0
  38. data/lib/dobby/severity.rb +66 -0
  39. data/lib/dobby/strategy.rb +168 -0
  40. data/lib/dobby/update_response.rb +19 -0
  41. data/lib/dobby/version.rb +24 -0
  42. data/lib/dobby/vuln_source/abstract_vuln_source.rb +26 -0
  43. data/lib/dobby/vuln_source/debian.rb +166 -0
  44. data/lib/dobby/vuln_source/ubuntu.rb +229 -0
  45. metadata +45 -1
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # Contains information about dobby-provided strategies.
5
+ module Builtins
6
+ PACKAGE_SOURCES = {
7
+ 'dpkg' => PackageSource::DpkgStatusFile
8
+ }.freeze
9
+
10
+ VULN_SOURCES = {
11
+ 'debian' => VulnSource::Debian,
12
+ 'ubuntu' => VulnSource::Ubuntu
13
+ }.freeze
14
+
15
+ ALL = PACKAGE_SOURCES.values | VULN_SOURCES.values
16
+ end
17
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # The dobby CLI.
5
+ class CLI
6
+ STATUS_SUCCESS = 0
7
+ STATUS_WARNING = 1
8
+ STATUS_ERROR = 2
9
+
10
+ class Finished < RuntimeError; end
11
+
12
+ attr_reader :options
13
+
14
+ def initialize
15
+ @options = {}
16
+ end
17
+
18
+ def run(args = ARGV)
19
+ @options, files = Options.new.parse(args)
20
+ act_on_options
21
+ execute_runner(files)
22
+ rescue Finished
23
+ STATUS_SUCCESS
24
+ rescue OptionParser::InvalidOption => e
25
+ warn e.message
26
+ warn 'For usage information, use --help'
27
+ STATUS_ERROR
28
+ rescue StandardError, SyntaxError, LoadError => e
29
+ warn e.message
30
+ warn e.backtrace
31
+ STATUS_ERROR
32
+ end
33
+
34
+ private
35
+
36
+ def act_on_options
37
+ handle_exiting_options
38
+ # TODO: handle incompatible options (ubuntu source with source file specified)
39
+
40
+ if @options[:color]
41
+ Rainbow.enabled = true
42
+ elsif @options[:color] == false
43
+ Rainbow.enabled = false
44
+ end
45
+ end
46
+
47
+ def handle_exiting_options
48
+ return unless Options::EXITING_OPTIONS.any? { |o| @options.key? o }
49
+
50
+ puts Dobby::Version.version(true) if @options[:verbose_version]
51
+ puts Dobby::Version.version(false) if @options[:version]
52
+ raise Finished
53
+ end
54
+
55
+ def execute_runner(paths)
56
+ runner = Runner.new(@options)
57
+ all_passed = runner.run(paths) && !runner.aborting? && runner.errors.empty?
58
+
59
+ return STATUS_SUCCESS if all_passed
60
+
61
+ STATUS_WARNING
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # All available Package and Database strategies available to the library.
5
+ #
6
+ # @return [Array<Object>]
7
+ def self.strategies
8
+ @strategies ||= []
9
+ end
10
+
11
+ def self.config
12
+ Configuration.instance
13
+ end
14
+
15
+ def self.configure
16
+ yield config
17
+ end
18
+
19
+ def self.logger
20
+ config.logger
21
+ end
22
+
23
+ # Gem configuration.
24
+ class Configuration
25
+ include Singleton
26
+
27
+ def self.default_logger
28
+ logger = Logger.new(STDOUT)
29
+ logger.progname = 'dobby'
30
+ logger
31
+ end
32
+
33
+ def self.defaults
34
+ @defaults ||= {}
35
+ end
36
+
37
+ def initialize
38
+ self.class.defaults.each_pair { |k, v| send("#{k}=", v) }
39
+ end
40
+
41
+ ###########
42
+ # Callbacks
43
+ #
44
+ #
45
+ # def on_failure(&block)
46
+ # if block_given?
47
+ # @on_failure = block
48
+ # else
49
+ # @on_failure
50
+ # end
51
+ # end
52
+ #
53
+ # attr_writer :on_failure
54
+ # #########
55
+
56
+ attr_accessor :logger
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # A set of Defects that knows how to update itself.
5
+ class Database
6
+ include Enumerable
7
+
8
+ class InitializationError < Error; end
9
+
10
+ def initialize(strategy)
11
+ @records = Hash.new { |h, k| h[k] = [] }
12
+ @strategy = strategy
13
+ raise InitializationError, 'Strategy did not update at initialize!' unless update
14
+
15
+ # TODO: Make the flag path configurable
16
+ file = File.join(File.expand_path(__dir__), 'flags.yml')
17
+ flags = Psych.load_file(file)
18
+ flags.each do |flag, defects|
19
+ defects.each { |d| @records[d].flag = flag if @records.key?(d) }
20
+ end
21
+ end
22
+
23
+ # @param package [Package]
24
+ #
25
+ # @return [Array<Dobby::Defect>]
26
+ def defects_for(package)
27
+ @records[package.name] | @records[package.source]
28
+ end
29
+
30
+ # @param key [String] Package name
31
+ #
32
+ # @return [Array<Dobby::Defect>] if the package has any, an array of {Defect}s
33
+ # @return [nil] if the package has no {Defect}s
34
+ def [](key)
35
+ @records[key]
36
+ end
37
+
38
+ # @param key [String] Package name
39
+ #
40
+ # @return [Boolean] true if at least one {Defect} exists for key
41
+ def contains?(key)
42
+ @records.key? key
43
+ end
44
+
45
+ # Iterate over all {Package}s in the database and their associated {Defect}s
46
+ #
47
+ # @yield [Iterator]
48
+ def each(&block)
49
+ @records.each(&block)
50
+ end
51
+
52
+ # @return [true] if strategy reports it has new content
53
+ def update
54
+ response = @strategy.update
55
+ return false unless response.changed?
56
+
57
+ @records.clear
58
+ @records.merge!(response.content)
59
+ true
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # A vulnerability which affects a particular {Package}
5
+ class Defect
6
+ # @return [String] unique identifier for this defect, usually a CVE ID
7
+ attr_reader :identifier
8
+
9
+ # @return [String] a description of the defect
10
+ attr_reader :description
11
+
12
+ # Set of Packages representing the minimum fix version
13
+ # @return [Array<Package>]
14
+ attr_reader :fixed_in
15
+
16
+ # @return [String] (low, medium, high) the priority category assigned to
17
+ # this Defect
18
+ attr_reader :severity
19
+
20
+ attr_reader :link
21
+
22
+ attr_accessor :flag
23
+
24
+ # Simple hash serializer for a Defect
25
+ # @return [Hash]
26
+ def to_hash
27
+ {
28
+ identifier: identifier,
29
+ description: description,
30
+ severity: severity.to_s,
31
+ fixed_in: fixed_in.map(&:to_s)
32
+ }
33
+ end
34
+
35
+ # @param identifier [String]
36
+ # @param description [String]
37
+ # @param severity [Severity]
38
+ # @param fixed_in [Array<Package>]
39
+ # @param link [String] External reference for the defect
40
+ def initialize(identifier:, description:, severity:, link: nil, fixed_in: [])
41
+ @identifier = identifier
42
+ @description = description
43
+ @severity = severity
44
+ @fixed_in = fixed_in
45
+ @link = link
46
+ end
47
+
48
+ # The Defect has at least one released fix version
49
+ #
50
+ # @return [Boolean]
51
+ def fix_available?
52
+ fixed_in.any? { |v| v.version != Package::MAX_VERSION }
53
+ end
54
+
55
+ def filtered?(filter: :default, flag_filter: :default)
56
+ return true if flag_filtered?(flag_filter)
57
+ return false if filter == :default
58
+ return !fix_available? if filter == :only_fixed
59
+
60
+ raise UnknownFilterError, filter
61
+ end
62
+
63
+ def flagged?
64
+ !flag.nil?
65
+ end
66
+
67
+ def flag_filtered?(filter)
68
+ return !flag.nil? if filter == :default
69
+ return false if flag == filter
70
+
71
+ true
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # Generic Debian dpkg helper methods
5
+ module Dpkg
6
+ # Default location of the dpkg binary
7
+ DPKG = '/usr/bin/dpkg'
8
+ # Default location of the lsb_release binary
9
+ LSBR = '/usr/bin/lsb_release'
10
+
11
+ # @return [String] Debian codename for this system
12
+ def self.code_name
13
+ `#{LSBR} -sc`.chomp!
14
+ end
15
+
16
+ # @return [String] System architecture
17
+ def self.architecture
18
+ `#{DPKG} --print-architecture`.chomp!
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ class Error < StandardError; end
5
+ class UnknownFilterError < Error; end
6
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # Simple interface for managing whitelist/allowed flags of defects.
5
+ class FlagManager
6
+ attr_reader :flags
7
+
8
+ # @param file [String] Path to YML file describing current flags, and where
9
+ # flags will be stored on {write}
10
+ # @param who [String] User or person associated with a flag entry
11
+ def initialize(file, who)
12
+ flags = {
13
+ whitelist: {},
14
+ allowed: {}
15
+ }
16
+
17
+ @file = file
18
+ @flags = flags.merge!(Psych.load_file(file))
19
+ @who = who
20
+ end
21
+
22
+ def add(flag, id, ticket)
23
+ return false if @flags[flag].key?(id)
24
+
25
+ @flags[flag][id] = {
26
+ by: @who,
27
+ on: Time.now.utc,
28
+ ticket: ticket
29
+ }
30
+ end
31
+
32
+ def remove(flag, id)
33
+ return false unless @flags[flag].key?(id)
34
+
35
+ @flags[flag].delete id
36
+ end
37
+
38
+ def move(src, dst, id)
39
+ ticket = @flags[src][id][:ticket]
40
+ return false unless remove(src, id)
41
+
42
+ add(dst, id, ticket)
43
+ end
44
+
45
+ def bulk_add(flag, ids)
46
+ ids.each { |id| remove(flag, id) }
47
+ end
48
+
49
+ def builk_remove(flag, ids)
50
+ ids.each { |id| remove(flag, id) }
51
+ end
52
+
53
+ def bulk_move(src, dst, ids)
54
+ ids.each { |id| move(src, dst, id) }
55
+ end
56
+
57
+ def dump
58
+ @flags.to_yaml
59
+ end
60
+
61
+ def write
62
+ File.open(@file, 'w') do |f|
63
+ f.write(@flags.to_yaml)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,8 @@
1
+ # DO NOT EDIT THIS FILE! IT HAS BEEN AUTO-GENERATED.
2
+ #
3
+ # To make changes, use script/dobby_filter.rb
4
+ #
5
+ ---
6
+ :whitelist: {}
7
+ :allowed: {}
8
+
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ module Formatter
5
+ class AbstractFormatter
6
+ attr_reader :output
7
+ attr_reader :options
8
+
9
+ def initialize(output, options = {})
10
+ @output = output
11
+ @options = options
12
+ end
13
+
14
+ # Called once before starting work.
15
+ def started(target_files); end
16
+
17
+ # Called when starting work for a single file.
18
+ def file_started(file); end
19
+
20
+ def file_finished(file, results); end
21
+
22
+ def finished(files); end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ module Formatter
5
+ # This mix-in module provides string coloring methods for terminals.
6
+ # It automatically disables coloring if coloring is disabled in the process
7
+ # globally or the formatter's output is not a terminal.
8
+ module Colorizable
9
+ def rainbow
10
+ @rainbow ||= begin
11
+ rainbow = Rainbow.new
12
+ if options[:color]
13
+ rainbow.enabled = true
14
+ elsif options[:color] == false || !output.tty?
15
+ rainbow.enabled = false
16
+ end
17
+ rainbow
18
+ end
19
+ end
20
+
21
+ def colorize(string, *args)
22
+ rainbow.wrap(string).color(*args)
23
+ end
24
+
25
+ %i[
26
+ black
27
+ red
28
+ green
29
+ yellow
30
+ blue
31
+ magenta
32
+ cyan
33
+ white
34
+ ].each do |color|
35
+ define_method(color) do |string|
36
+ colorize(string, color)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end