dobby 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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