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