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.
- checksums.yaml +4 -4
- data/.gitignore +16 -0
- data/.rubocop.yml +30 -0
- data/.rubocop_todo.yml +42 -0
- data/.travis.yml +12 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +8 -0
- data/CONTRIBUTING.md +60 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +103 -0
- data/Rakefile +8 -0
- data/bin/console +7 -0
- data/bin/setup +8 -0
- data/config/default.yml +8 -0
- data/dobby.gemspec +58 -0
- data/lib/dobby.rb +51 -0
- data/lib/dobby/builtins.rb +17 -0
- data/lib/dobby/cli.rb +64 -0
- data/lib/dobby/configuration.rb +58 -0
- data/lib/dobby/database.rb +62 -0
- data/lib/dobby/defect.rb +74 -0
- data/lib/dobby/dpkg.rb +21 -0
- data/lib/dobby/error.rb +6 -0
- data/lib/dobby/flag_manager.rb +67 -0
- data/lib/dobby/flags.yml +8 -0
- data/lib/dobby/formatter/abstract_formatter.rb +25 -0
- data/lib/dobby/formatter/colorizable.rb +41 -0
- data/lib/dobby/formatter/formatter_set.rb +79 -0
- data/lib/dobby/formatter/json_formatter.rb +42 -0
- data/lib/dobby/formatter/simple_formatter.rb +54 -0
- data/lib/dobby/options.rb +149 -0
- data/lib/dobby/package.rb +156 -0
- data/lib/dobby/package_source/abstract_package_source.rb +17 -0
- data/lib/dobby/package_source/dpkg_status_file.rb +85 -0
- data/lib/dobby/runner.rb +152 -0
- data/lib/dobby/scanner.rb +128 -0
- data/lib/dobby/severity.rb +66 -0
- data/lib/dobby/strategy.rb +168 -0
- data/lib/dobby/update_response.rb +19 -0
- data/lib/dobby/version.rb +24 -0
- data/lib/dobby/vuln_source/abstract_vuln_source.rb +26 -0
- data/lib/dobby/vuln_source/debian.rb +166 -0
- data/lib/dobby/vuln_source/ubuntu.rb +229 -0
- 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
|
data/lib/dobby/cli.rb
ADDED
@@ -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
|
data/lib/dobby/defect.rb
ADDED
@@ -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
|
data/lib/dobby/dpkg.rb
ADDED
@@ -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
|
data/lib/dobby/error.rb
ADDED
@@ -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
|
data/lib/dobby/flags.yml
ADDED
@@ -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
|