inspec-core 5.18.14 → 5.21.29

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