Class: Bootprint::Rules::Rule

Inherits:
Object
  • Object
show all
Defined in:
lib/bootprint/rules/rule.rb

Constant Summary collapse

SEVERITIES =
%i[info warning error critical].freeze
PATH_ALIASES =
{
  "ruby_version" => "runtime.ruby_version",
  "ruby_platform" => "runtime.platform",
  "bundler_version" => "dependencies.toolchain.bundler_version",
  "rubygems_version" => "dependencies.toolchain.rubygems_version",
  "openssl_version" => "native_libraries.openssl.runtime"
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id, severity: nil) ⇒ Rule

Returns a new instance of Rule.



18
19
20
21
22
23
24
25
# File 'lib/bootprint/rules/rule.rb', line 18

def initialize(id, severity: nil)
  @id = id.to_s
  @human_name = id.to_s.split("-").map(&:capitalize).join(" ")
  @category_name = :general
  @severity_name = (severity || :warning).to_sym
  @metadata_value = {}
  @reference_values = []
end

Instance Attribute Details

#category_nameObject (readonly)

Returns the value of attribute category_name.



15
16
17
# File 'lib/bootprint/rules/rule.rb', line 15

def category_name
  @category_name
end

#human_nameObject (readonly)

Returns the value of attribute human_name.



15
16
17
# File 'lib/bootprint/rules/rule.rb', line 15

def human_name
  @human_name
end

#idObject (readonly)

Returns the value of attribute id.



15
16
17
# File 'lib/bootprint/rules/rule.rb', line 15

def id
  @id
end

#metadata_valueObject (readonly)

Returns the value of attribute metadata_value.



15
16
17
# File 'lib/bootprint/rules/rule.rb', line 15

def 
  @metadata_value
end

#reference_valuesObject (readonly)

Returns the value of attribute reference_values.



15
16
17
# File 'lib/bootprint/rules/rule.rb', line 15

def reference_values
  @reference_values
end

#severity_nameObject (readonly)

Returns the value of attribute severity_name.



15
16
17
# File 'lib/bootprint/rules/rule.rb', line 15

def severity_name
  @severity_name
end

#source_location_valueObject (readonly)

Returns the value of attribute source_location_value.



15
16
17
# File 'lib/bootprint/rules/rule.rb', line 15

def source_location_value
  @source_location_value
end

Instance Method Details

#category(value = nil) ⇒ Object



32
33
34
35
# File 'lib/bootprint/rules/rule.rb', line 32

def category(value = nil)
  @category_name = value.to_sym if value
  @category_name
end

#compare(path) ⇒ Object

Compatibility with the 0.1 single-path rule API.



76
77
78
79
# File 'lib/bootprint/rules/rule.rb', line 76

def compare(path)
  @compare_path = PATH_ALIASES.fetch(path.to_s, path.to_s)
  detect { |source, target| dig(source, @compare_path) != dig(target, @compare_path) }
end

#condition(&block) ⇒ Object



81
82
83
# File 'lib/bootprint/rules/rule.rb', line 81

def condition(&block)
  @condition = block
end

#detect(&block) ⇒ Object



47
48
49
50
# File 'lib/bootprint/rules/rule.rb', line 47

def detect(&block)
  @detector = block if block
  @detector
end

#evaluate(source_snapshot, target_snapshot, policy:) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/bootprint/rules/rule.rb', line 85

def evaluate(source_snapshot, target_snapshot, policy:)
  source = environment(source_snapshot)
  target = environment(target_snapshot)
  evidence = detection_result(source, target, policy)
  return unless evidence

  explanation_arguments = if @compare_path
                            [dig(source, @compare_path), dig(target, @compare_path), evidence]
                          else
                            [source, target, evidence]
                          end
  details = Schema.stringify(invoke(@explainer, *explanation_arguments) || {})
  details = { "summary" => details.to_s, "evidence" => evidence } unless details.is_a?(Hash)
  remediation = Schema.stringify(invoke(@remediator, source, target, evidence) || {})
  effective_severity = policy.severity_for(id, severity_name)
  suppressed = policy.ignored?(id) || policy.disabled?(id)
  Finding.new(
    rule_id: id,
    title: details["title"] || human_name,
    category: category_name,
    severity: effective_severity,
    summary: details["summary"] || "#{human_name} was detected.",
    cause: details["cause"],
    impact: details["impact"],
    evidence: details["evidence"] || normalize_evidence(evidence),
    remediation: remediation,
    references: reference_values,
    metadata: .merge("path" => @compare_path).compact,
    source_location: details["source_location"] || source_location_value,
    suppressed:,
    suppression_reason: suppressed ? policy.suppression_reason(id) : nil
  )
end

#explain(message = nil, &block) ⇒ Object



52
53
54
# File 'lib/bootprint/rules/rule.rb', line 52

def explain(message = nil, &block)
  @explainer = block || ->(_source, _target, evidence = nil) { { "summary" => message.to_s, "evidence" => evidence } }
end

#metadata(value = nil, **pairs) ⇒ Object



62
63
64
# File 'lib/bootprint/rules/rule.rb', line 62

def (value = nil, **pairs)
  @metadata_value.merge!(Schema.stringify(value || {}).merge(Schema.stringify(pairs)))
end

#name(value = nil) ⇒ Object



27
28
29
30
# File 'lib/bootprint/rules/rule.rb', line 27

def name(value = nil)
  @human_name = value.to_s if value
  @human_name
end

#references(*values) ⇒ Object



66
67
68
# File 'lib/bootprint/rules/rule.rb', line 66

def references(*values)
  @reference_values.concat(values.flatten.map(&:to_s))
end

#remediate(summary = nil, commands: [], files: [], &block) ⇒ Object



56
57
58
59
60
# File 'lib/bootprint/rules/rule.rb', line 56

def remediate(summary = nil, commands: [], files: [], &block)
  @remediator = block || lambda do |_source, _target, _evidence = nil|
    { "summary" => summary.to_s, "commands" => commands, "files" => files }
  end
end

#severity(value = nil) ⇒ Object



37
38
39
40
41
42
43
44
45
# File 'lib/bootprint/rules/rule.rb', line 37

def severity(value = nil)
  if value
    candidate = value.to_sym
    raise ConfigurationError, "Invalid severity #{value.inspect} for #{id}" unless SEVERITIES.include?(candidate)

    @severity_name = candidate
  end
  @severity_name
end

#source_location(path = nil, line: nil) ⇒ Object



70
71
72
73
# File 'lib/bootprint/rules/rule.rb', line 70

def source_location(path = nil, line: nil)
  @source_location_value = { "path" => path.to_s, "line" => line }.compact if path
  @source_location_value
end