inspec-core 5.18.14 → 5.21.29

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +19 -16
  3. data/inspec-core.gemspec +22 -22
  4. data/lib/inspec/base_cli.rb +2 -0
  5. data/lib/inspec/cli.rb +6 -2
  6. data/lib/inspec/dsl.rb +10 -4
  7. data/lib/inspec/enhanced_outcomes.rb +19 -0
  8. data/lib/inspec/env_printer.rb +1 -1
  9. data/lib/inspec/exceptions.rb +2 -0
  10. data/lib/inspec/formatters/base.rb +69 -16
  11. data/lib/inspec/plugin/v2/loader.rb +19 -8
  12. data/lib/inspec/plugin/v2/plugin_types/reporter.rb +1 -0
  13. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +54 -0
  14. data/lib/inspec/reporters/base.rb +1 -0
  15. data/lib/inspec/reporters/cli.rb +94 -3
  16. data/lib/inspec/reporters/json.rb +3 -1
  17. data/lib/inspec/reporters/yaml.rb +3 -1
  18. data/lib/inspec/reporters.rb +2 -1
  19. data/lib/inspec/resources/file.rb +1 -1
  20. data/lib/inspec/resources/http.rb +2 -2
  21. data/lib/inspec/resources/lxc.rb +65 -9
  22. data/lib/inspec/resources/oracledb_session.rb +13 -4
  23. data/lib/inspec/resources/podman.rb +353 -0
  24. data/lib/inspec/resources/podman_container.rb +84 -0
  25. data/lib/inspec/resources/podman_image.rb +108 -0
  26. data/lib/inspec/resources/podman_network.rb +81 -0
  27. data/lib/inspec/resources/podman_pod.rb +101 -0
  28. data/lib/inspec/resources/podman_volume.rb +87 -0
  29. data/lib/inspec/resources/service.rb +1 -1
  30. data/lib/inspec/rule.rb +54 -17
  31. data/lib/inspec/run_data/control.rb +6 -0
  32. data/lib/inspec/run_data/statistics.rb +8 -2
  33. data/lib/inspec/runner.rb +18 -8
  34. data/lib/inspec/runner_rspec.rb +3 -2
  35. data/lib/inspec/schema/exec_json.rb +78 -2
  36. data/lib/inspec/schema/output_schema.rb +4 -1
  37. data/lib/inspec/schema/profile_json.rb +46 -0
  38. data/lib/inspec/schema.rb +91 -0
  39. data/lib/inspec/utils/convert.rb +8 -0
  40. data/lib/inspec/utils/podman.rb +24 -0
  41. data/lib/inspec/utils/waivers/csv_file_reader.rb +34 -0
  42. data/lib/inspec/utils/waivers/excel_file_reader.rb +39 -0
  43. data/lib/inspec/utils/waivers/json_file_reader.rb +15 -0
  44. data/lib/inspec/version.rb +1 -1
  45. data/lib/inspec/waiver_file_reader.rb +61 -0
  46. data/lib/matchers/matchers.rb +7 -1
  47. data/lib/plugins/inspec-init/templates/profiles/alicloud/README.md +27 -0
  48. data/lib/plugins/inspec-init/templates/profiles/alicloud/controls/example.rb +10 -0
  49. data/lib/plugins/inspec-init/templates/profiles/alicloud/inputs.yml +1 -0
  50. data/lib/plugins/inspec-init/templates/profiles/alicloud/inspec.yml +14 -0
  51. data/lib/plugins/inspec-reporter-html2/README.md +1 -1
  52. data/lib/plugins/inspec-reporter-html2/templates/body.html.erb +7 -1
  53. data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +10 -6
  54. data/lib/plugins/inspec-reporter-html2/templates/default.css +12 -0
  55. data/lib/plugins/inspec-reporter-html2/templates/selector.html.erb +7 -1
  56. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +5 -2
  57. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +39 -13
  58. metadata +25 -9
@@ -9,6 +9,9 @@ module Inspec::Reporters
9
9
  "passed" => "\033[0;1;32m",
10
10
  "skipped" => "\033[0;37m",
11
11
  "reset" => "\033[0m",
12
+ "error" => "\033[34m",
13
+ "not_applicable" => "\033[36m",
14
+ "not_reviewed" => "\033[33m",
12
15
  }.freeze
13
16
 
14
17
  # Most currently available Windows terminals have poor support
@@ -18,6 +21,9 @@ module Inspec::Reporters
18
21
  "skipped" => "[SKIP]",
19
22
  "passed" => "[PASS]",
20
23
  "unknown" => "[UNKN]",
24
+ "error" => "[ERR]",
25
+ "not_applicable" => "[N/A]",
26
+ "not_reviewed" => "[N/R]",
21
27
  }.freeze
22
28
  else
23
29
  # Extended colors for everyone else
@@ -26,6 +32,9 @@ module Inspec::Reporters
26
32
  "passed" => "\033[38;5;41m",
27
33
  "skipped" => "\033[38;5;247m",
28
34
  "reset" => "\033[0m",
35
+ "error" => "\033[0;38;5;21m",
36
+ "not_applicable" => "\033[0;38;5;117m",
37
+ "not_reviewed" => "\033[0;38;5;214m",
29
38
  }.freeze
30
39
 
31
40
  # Groovy UTF-8 characters for everyone else...
@@ -35,6 +44,9 @@ module Inspec::Reporters
35
44
  "skipped" => "↺",
36
45
  "passed" => "✔",
37
46
  "unknown" => "?",
47
+ "error" => "ERR",
48
+ "not_applicable" => "N/A",
49
+ "not_reviewed" => "N/R",
38
50
  }.freeze
39
51
  end
40
52
 
@@ -63,7 +75,11 @@ module Inspec::Reporters
63
75
  end
64
76
 
65
77
  output("")
66
- print_profile_summary
78
+ if enhanced_outcomes
79
+ print_control_outcomes_summary
80
+ else
81
+ print_profile_summary
82
+ end
67
83
  print_tests_summary
68
84
  end
69
85
 
@@ -88,6 +104,7 @@ module Inspec::Reporters
88
104
  def print_standard_control_results(profile)
89
105
  standard_controls_from_profile(profile).each do |control_from_profile|
90
106
  control = Control.new(control_from_profile)
107
+ control.enhanced_outcomes = enhanced_outcomes
91
108
  next if control.results.nil?
92
109
 
93
110
  output(format_control_header(control))
@@ -122,7 +139,7 @@ module Inspec::Reporters
122
139
  end
123
140
 
124
141
  def format_control_header(control)
125
- impact = control.impact_string
142
+ impact = enhanced_outcomes ? control.impact_string_for_enhanced_outcomes : control.impact_string
126
143
  format_message(
127
144
  color: impact,
128
145
  indicator: impact,
@@ -292,6 +309,68 @@ module Inspec::Reporters
292
309
  }
293
310
  end
294
311
 
312
+ def control_outcomes_summary
313
+ failed = 0
314
+ passed = 0
315
+ error = 0
316
+ not_reviewed = 0
317
+ not_applicable = 0
318
+
319
+ all_unique_controls.each do |control|
320
+ next if control[:status].empty?
321
+
322
+ if control[:status] == "failed"
323
+ failed += 1
324
+ elsif control[:status] == "error"
325
+ error += 1
326
+ elsif control[:status] == "not_reviewed"
327
+ not_reviewed += 1
328
+ elsif control[:status] == "not_applicable"
329
+ not_applicable += 1
330
+ else
331
+ passed += 1
332
+ end
333
+ end
334
+
335
+ total = failed + passed + error + not_reviewed + not_applicable
336
+
337
+ {
338
+ "total" => total,
339
+ "failed" => failed,
340
+ "passed" => passed,
341
+ "error" => error,
342
+ "not_reviewed" => not_reviewed,
343
+ "not_applicable" => not_applicable,
344
+ }
345
+ end
346
+
347
+ def print_control_outcomes_summary
348
+ summary = control_outcomes_summary
349
+ return unless summary["total"] > 0
350
+
351
+ success_str = summary["passed"] == 1 ? "1 successful control" : "#{summary["passed"]} successful controls"
352
+ failed_str = summary["failed"] == 1 ? "1 control failure" : "#{summary["failed"]} control failures"
353
+ error_str = summary["error"] == 1 ? "1 control has error" : "#{summary["error"]} controls have error"
354
+ not_rev_str = summary["not_reviewed"] == 1 ? "1 control not reviewed" : "#{summary["not_reviewed"]} controls not reviewed"
355
+ not_app_str = summary["not_applicable"] == 1 ? "1 control not applicable" : "#{summary["not_applicable"]} controls not applicable"
356
+
357
+ success_color = summary["passed"] > 0 ? "passed" : "no_color"
358
+ failed_color = summary["failed"] > 0 ? "failed" : "no_color"
359
+ error_color = summary["error"] > 0 ? "error" : "no_color"
360
+ not_rev_color = summary["not_reviewed"] > 0 ? "not_reviewed" : "no_color"
361
+ not_app_color = summary["not_applicable"] > 0 ? "not_applicable" : "no_color"
362
+
363
+ s = format(
364
+ "Profile Summary: %s, %s, %s, %s, %s",
365
+ format_with_color(success_color, success_str),
366
+ format_with_color(failed_color, failed_str),
367
+ format_with_color(not_rev_color, not_rev_str),
368
+ format_with_color(not_app_color, not_app_str),
369
+ format_with_color(error_color, error_str)
370
+ )
371
+ output(s) if summary["total"] > 0
372
+ end
373
+
295
374
  def print_profile_summary
296
375
  summary = profile_summary
297
376
  return unless summary["total"] > 0
@@ -350,6 +429,7 @@ module Inspec::Reporters
350
429
 
351
430
  class Control
352
431
  attr_reader :data
432
+ attr_accessor :enhanced_outcomes
353
433
 
354
434
  def initialize(control_hash)
355
435
  @data = control_hash
@@ -379,6 +459,10 @@ module Inspec::Reporters
379
459
  id.start_with?("(generated from ")
380
460
  end
381
461
 
462
+ def status
463
+ data[:status]
464
+ end
465
+
382
466
  def title_for_report
383
467
  # if this is an anonymous control, just grab the resource title from any result entry
384
468
  return results.first[:resource_title] if anonymous?
@@ -392,10 +476,17 @@ module Inspec::Reporters
392
476
  # append a failure summary if appropriate.
393
477
  title_for_report += " (#{failure_count} failed)" if failure_count > 0
394
478
  title_for_report += " (#{skipped_count} skipped)" if skipped_count > 0
395
-
396
479
  title_for_report
397
480
  end
398
481
 
482
+ def impact_string_for_enhanced_outcomes
483
+ if impact.nil?
484
+ "unknown"
485
+ else
486
+ status
487
+ end
488
+ end
489
+
399
490
  def impact_string
400
491
  if anonymous?
401
492
  nil
@@ -114,7 +114,7 @@ module Inspec::Reporters
114
114
 
115
115
  def profile_controls(profile)
116
116
  (profile[:controls] || []).map { |c|
117
- {
117
+ control_hash = {
118
118
  id: c[:id],
119
119
  title: c[:title],
120
120
  desc: c.dig(:descriptions, :default),
@@ -130,6 +130,8 @@ module Inspec::Reporters
130
130
  waiver_data: c[:waiver_data] || {},
131
131
  results: profile_results(c),
132
132
  }
133
+ control_hash.merge!({ status: c[:status] }) if enhanced_outcomes
134
+ control_hash
133
135
  }
134
136
  end
135
137
 
@@ -3,7 +3,9 @@ require "yaml"
3
3
  module Inspec::Reporters
4
4
  class Yaml < Base
5
5
  def render
6
- output(Inspec::Reporters::Json.new({ run_data: run_data }).report.to_yaml, false)
6
+ json_reporter_obj = Inspec::Reporters::Json.new({ run_data: run_data })
7
+ json_reporter_obj.enhanced_outcomes = enhanced_outcomes
8
+ output(json_reporter_obj.report.to_yaml, false)
7
9
  end
8
10
 
9
11
  def report
@@ -7,7 +7,7 @@ require "inspec/reporters/yaml"
7
7
 
8
8
  module Inspec::Reporters
9
9
  # rubocop:disable Metrics/CyclomaticComplexity
10
- def self.render(reporter, run_data)
10
+ def self.render(reporter, run_data, enhanced_outcomes = false)
11
11
  name, config = reporter.dup
12
12
  config[:run_data] = run_data
13
13
  case name
@@ -29,6 +29,7 @@ module Inspec::Reporters
29
29
  activator.activate!
30
30
  reporter = activator.implementation_class.new(config)
31
31
  end
32
+ reporter.enhanced_outcomes = enhanced_outcomes
32
33
 
33
34
  # optional send_report method on reporter
34
35
  return reporter.send_report if defined?(reporter.send_report)
@@ -66,7 +66,7 @@ module Inspec::Resources
66
66
  def user_permissions
67
67
  return {} unless exist?
68
68
 
69
- return skip_reource"`user_permissions` is not supported on your OS yet." unless inspec.os.windows?
69
+ return skip_resource "`user_permissions` is not supported on your OS yet." unless inspec.os.windows?
70
70
 
71
71
  @perms_provider.user_permissions(file)
72
72
  end
@@ -4,7 +4,7 @@
4
4
 
5
5
  require "inspec/resources/command"
6
6
  require "faraday" unless defined?(Faraday)
7
- require "faraday_middleware"
7
+ require "faraday/follow_redirects"
8
8
  require "hashie"
9
9
 
10
10
  module Inspec::Resources
@@ -153,7 +153,7 @@ module Inspec::Resources
153
153
 
154
154
  conn = Faraday.new(url: url, headers: request_headers, params: params, ssl: { verify: ssl_verify? }) do |builder|
155
155
  builder.request :url_encoded
156
- builder.use FaradayMiddleware::FollowRedirects, limit: max_redirects unless max_redirects.nil?
156
+ builder.use Faraday::FollowRedirects::Middleware, limit: max_redirects unless max_redirects.nil?
157
157
  builder.adapter Faraday.default_adapter
158
158
  end
159
159
 
@@ -9,14 +9,26 @@ module Inspec::Resources
9
9
  describe lxc("ubuntu-container") do
10
10
  it { should exist }
11
11
  it { should be_running }
12
+ its("name") { should eq "ubuntu-container" }
13
+ its("status") { should cmp "Running" }
14
+ its("type") { should eq "container" }
15
+ its("architecture") { should eq "x86_64" }
16
+ its("pid") { should eq 1378 }
17
+ its("created_at") { should eq "2022/08/16 12:07 UTC" }
18
+ its("last_used_at") { should eq "2022/08/17 05:06 UTC" }
19
+ its("resources") { should include "Disk usage" }
12
20
  end
13
21
  EXAMPLE
14
22
 
23
+ attr_reader :container_info, :container_name
24
+
15
25
  # Resource initialization.
16
26
  def initialize(container_name)
17
27
  @container_name = container_name
18
28
 
19
29
  raise Inspec::Exceptions::ResourceSkipped, "The `lxc` resource is not supported on your OS yet." unless inspec.os.linux?
30
+
31
+ @container_info = populate_container_info
20
32
  end
21
33
 
22
34
  def resource_id
@@ -28,17 +40,60 @@ module Inspec::Resources
28
40
  end
29
41
 
30
42
  def exists?
31
- lxc_info_cmd.exit_status.to_i == 0
43
+ !@container_info.empty?
32
44
  end
33
45
 
34
46
  def running?
35
- container_info = lxc_info_cmd.stdout.split(":").map(&:strip)
36
- container_info[0] == "Status" && container_info[1] == "Running"
47
+ @container_info.key?("Status") && @container_info["Status"].casecmp("Running") == 0
48
+ end
49
+
50
+ def name
51
+ @container_info["Name"]
52
+ end
53
+
54
+ def status
55
+ @container_info["Status"]
56
+ end
57
+
58
+ def type
59
+ @container_info["Type"]
60
+ end
61
+
62
+ def architecture
63
+ @container_info["Architecture"]
64
+ end
65
+
66
+ def pid
67
+ @container_info["PID"]
68
+ end
69
+
70
+ def created_at
71
+ @container_info["Created"]
72
+ end
73
+
74
+ def last_used_at
75
+ @container_info["Last Used"]
76
+ end
77
+
78
+ def resources
79
+ @container_info["Resources"]
37
80
  end
38
81
 
39
82
  private
40
83
 
41
- # Method to find lxc
84
+ def populate_container_info
85
+ lxc_util = find_lxc_or_error
86
+ lxc_info_cmd = inspec.command("#{lxc_util} info #{@container_name}")
87
+
88
+ if lxc_info_cmd.exit_status.to_i == 0
89
+ parse_command_output(lxc_info_cmd.stdout)
90
+ elsif lxc_info_cmd.stderr =~ /Error: Instance not found/
91
+ {}
92
+ else
93
+ raise Inspec::Exceptions::ResourceFailed, "Unable to retrieve information for #{container_name}.\n#{lxc_info_cmd.stderr}"
94
+ end
95
+ end
96
+
42
97
  def find_lxc_or_error
43
98
  %w{/usr/sbin/lxc /sbin/lxc lxc}.each do |cmd|
44
99
  return cmd if inspec.command(cmd).exist?
@@ -47,11 +102,12 @@ module Inspec::Resources
47
102
  raise Inspec::Exceptions::ResourceFailed, "Could not find `lxc`"
48
103
  end
49
104
 
50
- def lxc_info_cmd
51
- bin = find_lxc_or_error
52
- info_cmd = "info #{@container_name} | grep -i Status"
53
- lxc_cmd = format("%s %s", bin, info_cmd).strip
54
- inspec.command(lxc_cmd)
105
+ def parse_command_output(output)
106
+ require "yaml" unless defined?(YAML)
107
+ YAML.load(output)
108
+ rescue Psych::SyntaxError => e
109
+ warn "Could not parse the command output.\n#{e.message}"
110
+ {}
55
111
  end
56
112
  end
57
113
  end
@@ -101,22 +101,31 @@ module Inspec::Resources
101
101
  verified_query = verify_query(escaped_query)
102
102
  end
103
103
 
104
- sql_prefix, sql_postfix = "", ""
104
+ sql_prefix, sql_postfix, oracle_echo_str = "", "", ""
105
105
  if inspec.os.windows?
106
106
  sql_prefix = %{@'\n#{format_options}\n#{verified_query}\nEXIT\n'@ | }
107
107
  else
108
108
  sql_postfix = %{ <<'EOC'\n#{format_options}\n#{verified_query}\nEXIT\nEOC}
109
+ # oracle_query_string is echoed to be able to extract the query output clearly
110
+ oracle_echo_str = %{echo 'oracle_query_string';}
111
+ end
112
+
113
+ # Resetting sql_postfix if system is using AIX OS and C shell installation for oracle
114
+ if inspec.os.aix?
115
+ command_to_fetch_shell = @su_user ? %{su - #{@su_user} -c "env | grep SHELL"} : %{env | grep SHELL}
116
+ shell_is_csh = inspec.command(command_to_fetch_shell).stdout&.include? "/csh"
117
+ sql_postfix = %{ <<'EOC'\n#{format_options}\n#{verified_query}\nEXIT\n'EOC'} if shell_is_csh
109
118
  end
110
119
 
111
120
  if @db_role.nil?
112
- %{#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service}#{sql_postfix}}
121
+ %{#{oracle_echo_str}#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service}#{sql_postfix}}
113
122
  elsif @su_user.nil?
114
- %{#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service} as #{@db_role}#{sql_postfix}}
123
+ %{#{oracle_echo_str}#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service} as #{@db_role}#{sql_postfix}}
115
124
  else
116
125
  # oracle_query_string is echoed to be able to extract the query output clearly
117
126
  # su - su_user in certain versions of oracle returns a message
118
127
  # Example of msg with query output: The Oracle base remains unchanged with value /oracle\n\nVALUE\n3\n
119
- %{su - #{@su_user} -c "echo 'oracle_query_string'; env ORACLE_SID=#{@service} #{@bin} / as #{@db_role}#{sql_postfix}"}
128
+ %{su - #{@su_user} -c "#{oracle_echo_str} env ORACLE_SID=#{@service} #{@bin} / as #{@db_role}#{sql_postfix}"}
120
129
  end
121
130
  end
122
131