inspec-core 5.18.14 → 5.22.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +19 -17
  3. data/inspec-core.gemspec +22 -22
  4. data/lib/inspec/base_cli.rb +19 -17
  5. data/lib/inspec/cli.rb +27 -25
  6. data/lib/inspec/dependencies/dependency_set.rb +2 -2
  7. data/lib/inspec/dsl.rb +9 -5
  8. data/lib/inspec/enhanced_outcomes.rb +19 -0
  9. data/lib/inspec/env_printer.rb +1 -1
  10. data/lib/inspec/exceptions.rb +2 -0
  11. data/lib/inspec/formatters/base.rb +69 -16
  12. data/lib/inspec/plugin/v2/loader.rb +19 -8
  13. data/lib/inspec/plugin/v2/plugin_types/reporter.rb +1 -0
  14. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +54 -0
  15. data/lib/inspec/profile.rb +9 -8
  16. data/lib/inspec/reporters/base.rb +1 -0
  17. data/lib/inspec/reporters/cli.rb +94 -3
  18. data/lib/inspec/reporters/json.rb +3 -1
  19. data/lib/inspec/reporters/yaml.rb +3 -1
  20. data/lib/inspec/reporters.rb +2 -1
  21. data/lib/inspec/resources/file.rb +1 -1
  22. data/lib/inspec/resources/http.rb +5 -5
  23. data/lib/inspec/resources/lxc.rb +65 -9
  24. data/lib/inspec/resources/mongodb_session.rb +5 -0
  25. data/lib/inspec/resources/nftables.rb +251 -0
  26. data/lib/inspec/resources/oracledb_session.rb +13 -4
  27. data/lib/inspec/resources/podman.rb +353 -0
  28. data/lib/inspec/resources/podman_container.rb +84 -0
  29. data/lib/inspec/resources/podman_image.rb +108 -0
  30. data/lib/inspec/resources/podman_network.rb +81 -0
  31. data/lib/inspec/resources/podman_pod.rb +101 -0
  32. data/lib/inspec/resources/podman_volume.rb +87 -0
  33. data/lib/inspec/resources/postgres_session.rb +2 -1
  34. data/lib/inspec/resources/service.rb +1 -1
  35. data/lib/inspec/resources.rb +1 -0
  36. data/lib/inspec/rule.rb +54 -17
  37. data/lib/inspec/run_data/control.rb +6 -0
  38. data/lib/inspec/run_data/statistics.rb +8 -2
  39. data/lib/inspec/runner.rb +18 -8
  40. data/lib/inspec/runner_rspec.rb +3 -2
  41. data/lib/inspec/schema/exec_json.rb +78 -2
  42. data/lib/inspec/schema/output_schema.rb +4 -1
  43. data/lib/inspec/schema/profile_json.rb +46 -0
  44. data/lib/inspec/schema.rb +91 -0
  45. data/lib/inspec/utils/convert.rb +8 -0
  46. data/lib/inspec/utils/podman.rb +24 -0
  47. data/lib/inspec/utils/simpleconfig.rb +10 -2
  48. data/lib/inspec/utils/waivers/csv_file_reader.rb +34 -0
  49. data/lib/inspec/utils/waivers/excel_file_reader.rb +39 -0
  50. data/lib/inspec/utils/waivers/json_file_reader.rb +15 -0
  51. data/lib/inspec/version.rb +1 -1
  52. data/lib/inspec/waiver_file_reader.rb +61 -0
  53. data/lib/matchers/matchers.rb +15 -2
  54. data/lib/plugins/inspec-init/templates/profiles/alicloud/README.md +27 -0
  55. data/lib/plugins/inspec-init/templates/profiles/alicloud/controls/example.rb +10 -0
  56. data/lib/plugins/inspec-init/templates/profiles/alicloud/inputs.yml +1 -0
  57. data/lib/plugins/inspec-init/templates/profiles/alicloud/inspec.yml +14 -0
  58. data/lib/plugins/inspec-reporter-html2/README.md +1 -1
  59. data/lib/plugins/inspec-reporter-html2/templates/body.html.erb +7 -1
  60. data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +10 -6
  61. data/lib/plugins/inspec-reporter-html2/templates/default.css +12 -0
  62. data/lib/plugins/inspec-reporter-html2/templates/selector.html.erb +7 -1
  63. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +5 -2
  64. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +39 -13
  65. metadata +26 -9
@@ -0,0 +1,84 @@
1
+ require "inspec/resources/podman"
2
+ require_relative "docker_object"
3
+
4
+ # Change module if required
5
+ module Inspec::Resources
6
+ class PodmanContainer < Inspec.resource(1)
7
+ include Inspec::Resources::DockerObject
8
+ name "podman_container"
9
+ supports platform: "unix"
10
+
11
+ desc "Inspec core resource to retrieve information about podman container"
12
+
13
+ example <<~EXAMPLE
14
+ describe podman_container("sweet_mendeleev") do
15
+ it { should exist }
16
+ it { should be_running }
17
+ its("id") { should eq "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7" }
18
+ its("image") { should eq "docker.io/library/nginx:latest" }
19
+ its("labels") { should include "maintainer"=>"NGINX Docker Maintainers <docker-maint@nginx.com>" }
20
+ its("ports") { should eq nil }
21
+ end
22
+
23
+ describe podman_container(id: "591270d8d80d2667") do
24
+ it { should exist }
25
+ it { should be_running }
26
+ end
27
+ EXAMPLE
28
+
29
+ def initialize(opts = {})
30
+ skip_resource "The `podman_container` resource is not yet available on your OS." unless inspec.os.unix?
31
+
32
+ # if a string is provided, we expect it is the name
33
+ if opts.is_a?(String)
34
+ @opts = { name: opts }
35
+ else
36
+ @opts = opts
37
+ end
38
+ end
39
+
40
+ def running?
41
+ status.downcase.start_with?("up") if object_info.entries.length == 1
42
+ end
43
+
44
+ def status
45
+ object_info.status[0] if object_info.entries.length == 1
46
+ end
47
+
48
+ def labels
49
+ object_info.labels
50
+ end
51
+
52
+ def ports
53
+ object_info.ports[0] if object_info.entries.length == 1
54
+ end
55
+
56
+ def command
57
+ return unless object_info.entries.length == 1
58
+
59
+ object_info.commands[0]
60
+ end
61
+
62
+ def image
63
+ object_info.images[0] if object_info.entries.length == 1
64
+ end
65
+
66
+ def resource_id
67
+ object_info.ids[0] || @opts[:id] || @opts[:name] || ""
68
+ end
69
+
70
+ def to_s
71
+ name = @opts[:name] || @opts[:id]
72
+ "Podman Container #{name}"
73
+ end
74
+
75
+ private
76
+
77
+ def object_info
78
+ return @info if defined?(@info)
79
+
80
+ opts = @opts
81
+ @info = inspec.podman.containers.where { names == opts[:name] || (!id.nil? && !opts[:id].nil? && (id == opts[:id] || id.start_with?(opts[:id]))) }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,108 @@
1
+ require "inspec/resources/command"
2
+ require_relative "docker_object"
3
+ require "inspec/utils/podman"
4
+
5
+ module Inspec::Resources
6
+ class PodmanImage < Inspec.resource(1)
7
+ include Inspec::Resources::DockerObject
8
+ include Inspec::Utils::Podman
9
+
10
+ name "podman_image"
11
+ supports platform: "unix"
12
+
13
+ desc "InSpec core resource to retrieve information about podman image"
14
+
15
+ example <<~EXAMPLE
16
+ describe podman_image("docker.io/library/busybox") do
17
+ it { should exist }
18
+ its("repo_tags") { should include "docker.io/library/busybox:latest" }
19
+ its("size") { should eq 1636053 }
20
+ its("resource_id") { should eq "docker.io/library/busybox:latest" }
21
+ end
22
+
23
+ describe podman_image("docker.io/library/busybox:latest") do
24
+ it { should exist }
25
+ end
26
+
27
+ describe podman_image(repo: "docker.io/library/busybox", tag: "latest") do
28
+ it { should exist }
29
+ end
30
+
31
+ describe podman_image(id: "3c19bafed223") do
32
+ it { should exist }
33
+ end
34
+ EXAMPLE
35
+
36
+ attr_reader :opts, :image_info
37
+
38
+ def initialize(opts)
39
+ skip_resource "The `podman_image` resource is not yet available on your OS." unless inspec.os.unix?
40
+ opts = { image: opts } if opts.is_a?(String)
41
+ @opts = sanitize_options(opts)
42
+ raise Inspec::Exceptions::ResourceFailed, "Podman is not running. Please make sure it is installed and running." unless podman_running?
43
+
44
+ @image_info = get_image_info
45
+ end
46
+
47
+ LABELS = {
48
+ "id" => "ID",
49
+ "repo_tags" => "RepoTags",
50
+ "size" => "Size",
51
+ "digest" => "Digest",
52
+ "created_at" => "Created",
53
+ "version" => "Version",
54
+ "names_history" => "NamesHistory",
55
+ "repo_digests" => "RepoDigests",
56
+ "architecture" => "Architecture",
57
+ "os" => "Os",
58
+ "virtual_size" => "VirtualSize",
59
+ }.freeze
60
+
61
+ ## This creates all the required properties methods dynamically.
62
+ LABELS.each do |k, v|
63
+ define_method(k) do
64
+ image_info[k.to_s]
65
+ end
66
+ end
67
+
68
+ def exist?
69
+ ! image_info.empty?
70
+ end
71
+
72
+ def resource_id
73
+ opts[:id] || opts[:image] || ""
74
+ end
75
+
76
+ def to_s
77
+ "podman_image #{resource_id}"
78
+ end
79
+
80
+ private
81
+
82
+ def sanitize_options(opts)
83
+ opts.merge!(parse_components_from_image(opts[:image]))
84
+
85
+ # assume a "latest" tag if we don't have one
86
+ opts[:tag] ||= "latest"
87
+
88
+ # Assemble/reassemble the image from the repo and tag
89
+ opts[:image] = "#{opts[:repo]}:#{opts[:tag]}" unless opts[:repo].nil?
90
+
91
+ opts
92
+ end
93
+
94
+ def get_image_info
95
+ current_image = opts[:id] || opts[:image] || opts[:repo] + ":" + opts[:tag]
96
+ json_key_label = generate_go_template(LABELS)
97
+ podman_inspect_cmd = inspec.command("podman image inspect #{current_image} --format '{#{json_key_label}}'")
98
+
99
+ if podman_inspect_cmd.exit_status == 0
100
+ parse_command_output(podman_inspect_cmd.stdout)
101
+ elsif podman_inspect_cmd.stderr =~ /failed to find image/
102
+ {}
103
+ else
104
+ raise Inspec::Exceptions::ResourceFailed, "Unable to retrieve podman image information for #{current_image}.\nError message: #{podman_inspect_cmd.stderr}"
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,81 @@
1
+ require "inspec/resources/command"
2
+ require "inspec/utils/podman"
3
+ module Inspec::Resources
4
+ class PodmanNetwork < Inspec.resource(1)
5
+ include Inspec::Utils::Podman
6
+
7
+ name "podman_network"
8
+
9
+ supports platform: "unix"
10
+
11
+ desc "InSpec core resource to retrive information about the given Podman network"
12
+
13
+ example <<~EXAMPLE
14
+ describe podman_network("podman") do
15
+ it { should exist }
16
+ end
17
+ describe podman_network("3a7c94d937d5f3a0f1a9b1610589945aedfbe56207fd5d32fc8154aa1a8b007f") do
18
+ its("driver") { should eq bridge }
19
+ end
20
+ EXAMPLE
21
+
22
+ LABELS = {
23
+ id: "ID",
24
+ name: "Name",
25
+ driver: "Driver",
26
+ labels: "Labels",
27
+ options: "Options",
28
+ ipam_options: "IPAMOptions",
29
+ internal: "Internal",
30
+ created: "Created",
31
+ ipv6_enabled: "IPv6Enabled",
32
+ dns_enabled: "DNSEnabled",
33
+ network_interface: "NetworkInterface",
34
+ subnets: "Subnets",
35
+ }.freeze
36
+
37
+ attr_reader :param, :network_info
38
+ def initialize(param)
39
+ skip_resource "The `podman_network` resource is not yet available on your OS." unless inspec.os.unix?
40
+
41
+ @param = param
42
+ raise Inspec::Exceptions::ResourceFailed, "Podman is not running. Please make sure it is installed and running." unless podman_running?
43
+
44
+ @network_info = get_network_info
45
+ end
46
+
47
+ ## This creates all the required properties methods dynamically.
48
+ LABELS.each do |k, v|
49
+ define_method(k) do
50
+ network_info[k.to_s]
51
+ end
52
+ end
53
+
54
+ def exist?
55
+ !network_info.empty?
56
+ end
57
+
58
+ def resource_id
59
+ id || param || ""
60
+ end
61
+
62
+ def to_s
63
+ "podman_network #{resource_id}"
64
+ end
65
+
66
+ private
67
+
68
+ def get_network_info
69
+ go_template_format = generate_go_template(LABELS)
70
+ result = inspec.command("podman network inspect #{param} --format '{#{go_template_format}}'")
71
+
72
+ if result.exit_status == 0
73
+ parse_command_output(result.stdout)
74
+ elsif result.stderr =~ /network not found/
75
+ {}
76
+ else
77
+ raise Inspec::Exceptions::ResourceFailed, "Unable to retrieve podman network information for #{param}.\nError message: #{result.stderr}"
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,101 @@
1
+ require "inspec/resources/command"
2
+ require "inspec/utils/podman"
3
+
4
+ module Inspec::Resources
5
+ class PodmanPod < Inspec.resource(1)
6
+ include Inspec::Utils::Podman
7
+
8
+ name "podman_pod"
9
+ supports platform: "unix"
10
+
11
+ desc "InSpec core resource to retrieve information about podman pod"
12
+
13
+ example <<~EXAMPLE
14
+ describe podman_pod("nginx-frontend") do
15
+ it { should exist }
16
+ its("id") { should eq "fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4" }
17
+ its("name") { should eq "nginx-frontend" }
18
+ its("created_at") { should eq "2022-07-14T15:47:47.978078124+05:30" }
19
+ its("create_command") { should include "new:nginx-frontend" }
20
+ its("state") { should eq "Running" }
21
+ its("hostname") { should eq "" }
22
+ its("create_cgroup") { should eq true }
23
+ its("cgroup_parent") { should eq "user.slice" }
24
+ its("cgroup_path") { should eq "user.slice/user-libpod_pod_fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4.slice" }
25
+ its("create_infra") { should eq true }
26
+ its("infra_container_id") { should eq "727538044b32a165934729dc2d47d9d5e981b6496aebfad7de470f7e76ea4251" }
27
+ its("infra_config") { should include "DNSOption" }
28
+ its("shared_namespaces") { should include "ipc" }
29
+ its("num_containers") { should eq 2 }
30
+ its("containers") { should_not be nil }
31
+ end
32
+
33
+ describe podman_pod("non-existing-pod") do
34
+ it { should_not exist }
35
+ end
36
+ EXAMPLE
37
+
38
+ attr_reader :pod_info, :pod_id
39
+
40
+ def initialize(pod_id)
41
+ skip_resource "The `podman_pod` resource is not yet available on your OS." unless inspec.os.unix?
42
+ raise Inspec::Exceptions::ResourceFailed, "Podman is not running. Please make sure it is installed and running." unless podman_running?
43
+
44
+ @pod_id = pod_id
45
+ @pod_info = get_pod_info
46
+ end
47
+
48
+ LABELS = {
49
+ "id" => "ID",
50
+ "name" => "Name",
51
+ "created_at" => "Created",
52
+ "create_command" => "CreateCommand",
53
+ "state" => "State",
54
+ "hostname" => "Hostname",
55
+ "create_cgroup" => "CreateCgroup",
56
+ "cgroup_parent" => "CgroupParent",
57
+ "cgroup_path" => "CgroupPath",
58
+ "create_infra" => "CreateInfra",
59
+ "infra_container_id" => "InfraContainerID",
60
+ "infra_config" => "InfraConfig",
61
+ "shared_namespaces" => "SharedNamespaces",
62
+ "num_containers" => "NumContainers",
63
+ "containers" => "Containers",
64
+ }.freeze
65
+
66
+ # This creates all the required properties methods dynamically.
67
+ LABELS.each do |k, _|
68
+ define_method(k) do
69
+ pod_info[k.to_s]
70
+ end
71
+ end
72
+
73
+ def exist?
74
+ !pod_info.empty?
75
+ end
76
+
77
+ def resource_id
78
+ pod_id
79
+ end
80
+
81
+ def to_s
82
+ "Podman Pod #{resource_id}"
83
+ end
84
+
85
+ private
86
+
87
+ def get_pod_info
88
+ json_key_label = generate_go_template(LABELS)
89
+
90
+ inspect_pod_cmd = inspec.command("podman pod inspect #{pod_id} --format '{#{json_key_label}}'")
91
+
92
+ if inspect_pod_cmd.exit_status == 0
93
+ parse_command_output(inspect_pod_cmd.stdout)
94
+ elsif inspect_pod_cmd.stderr =~ /no pod with name or ID/
95
+ {}
96
+ else
97
+ raise Inspec::Exceptions::ResourceFailed, "Unable to retrieve podman pod information for #{pod_id}.\nError message: #{inspect_pod_cmd.stderr}"
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,87 @@
1
+ require "inspec/resources/command"
2
+ require "inspec/utils/podman"
3
+
4
+ module Inspec::Resources
5
+ class PodmanVolume < Inspec.resource(1)
6
+ include Inspec::Utils::Podman
7
+
8
+ name "podman_volume"
9
+ supports platform: "unix"
10
+
11
+ desc "InSpec core resource to retrieve information about podman volume"
12
+
13
+ example <<~EXAMPLE
14
+ describe podman_volume("my_volume") do
15
+ it { should exist }
16
+ its("name") { should eq "my_volume" }
17
+ its("driver") { should eq "local" }
18
+ its("mountpoint") { should eq "/var/home/core/.local/share/containers/storage/volumes/my_volume/_data" }
19
+ its("created_at") { should eq "2022-07-14T13:21:19.965421792+05:30" }
20
+ its("labels") { should eq({}) }
21
+ its("scope") { should eq "local" }
22
+ its("options") { should eq({}) }
23
+ its("mount_count") { should eq 0 }
24
+ its("needs_copy_up") { should eq true }
25
+ its("needs_chown") { should eq true }
26
+ end
27
+ EXAMPLE
28
+
29
+ attr_reader :volume_info, :volume_name
30
+
31
+ def initialize(volume_name)
32
+ skip_resource "The `podman_volume` resource is not yet available on your OS." unless inspec.os.unix?
33
+ raise Inspec::Exceptions::ResourceFailed, "Podman is not running. Please make sure it is installed and running." unless podman_running?
34
+
35
+ @volume_name = volume_name
36
+ @volume_info = get_volume_info
37
+ end
38
+
39
+ LABELS = {
40
+ "name" => "Name",
41
+ "driver" => "Driver",
42
+ "mountpoint" => "Mountpoint",
43
+ "created_at" => "CreatedAt",
44
+ "labels" => "Labels",
45
+ "scope" => "Scope",
46
+ "options" => "Options",
47
+ "mount_count" => "MountCount",
48
+ "needs_copy_up" => "NeedsCopyUp",
49
+ "needs_chown" => "NeedsChown",
50
+ }.freeze
51
+
52
+ # This creates all the required properties methods dynamically.
53
+ LABELS.each do |k, _|
54
+ define_method(k) do
55
+ volume_info[k.to_s]
56
+ end
57
+ end
58
+
59
+ def exist?
60
+ !volume_info.empty?
61
+ end
62
+
63
+ def resource_id
64
+ volume_name
65
+ end
66
+
67
+ def to_s
68
+ "podman_volume #{resource_id}"
69
+ end
70
+
71
+ private
72
+
73
+ def get_volume_info
74
+ json_key_label = generate_go_template(LABELS)
75
+
76
+ inspect_volume_cmd = inspec.command("podman volume inspect #{volume_name} --format '{#{json_key_label}}'")
77
+
78
+ if inspect_volume_cmd.exit_status == 0
79
+ parse_command_output(inspect_volume_cmd.stdout)
80
+ elsif inspect_volume_cmd.stderr =~ /inspecting object: no such/
81
+ {}
82
+ else
83
+ raise Inspec::Exceptions::ResourceFailed, "Unable to retrieve podman volume information for #{volume_name}.\nError message: #{inspect_volume_cmd.stderr}"
84
+ end
85
+ end
86
+ end
87
+ end
@@ -81,7 +81,8 @@ module Inspec::Resources
81
81
  # Socket path and empty host in the connection string establishes socket connection
82
82
  # Socket connection only enabled for non-windows platforms
83
83
  # Windows does not support unix domain sockets
84
- "psql -d postgresql://#{@user}:#{@pass}@/#{dbs}?host=#{@socket_path} -A -t -w -c #{escaped_query(query)}"
84
+ option_port = @port.nil? ? "" : "-p #{@port}" # add explicit port if specified
85
+ "psql -d postgresql://#{@user}:#{@pass}@/#{dbs}?host=#{@socket_path} #{option_port} -A -t -w -c #{escaped_query(query)}"
85
86
  else
86
87
  # Host in connection string establishes tcp/ip connection
87
88
  if inspec.os.windows?
@@ -646,7 +646,7 @@ module Inspec::Resources
646
646
  return nil if srv.nil? || srv[0].nil?
647
647
 
648
648
  # extract values from service
649
- parsed_srv = /^(?<pid>[0-9-]+)\t(?<exit>[0-9]+)\t(?<name>\S*)$/.match(srv[0])
649
+ parsed_srv = /^(?<pid>[0-9-]+)\t(?<exit>[\-0-9]+)\t(?<name>\S*)$/.match(srv[0])
650
650
  enabled = !parsed_srv["name"].nil? # it's in the list
651
651
 
652
652
  # check if the service is running
@@ -73,6 +73,7 @@ require "inspec/resources/mssql_sys_conf"
73
73
  require "inspec/resources/mysql"
74
74
  require "inspec/resources/mysql_conf"
75
75
  require "inspec/resources/mysql_session"
76
+ require "inspec/resources/nftables"
76
77
  require "inspec/resources/nginx"
77
78
  require "inspec/resources/nginx_conf"
78
79
  require "inspec/resources/npm"
data/lib/inspec/rule.rb CHANGED
@@ -8,13 +8,15 @@ require "inspec/impact"
8
8
  require "inspec/resource"
9
9
  require "inspec/resources/os"
10
10
  require "inspec/input_registry"
11
+ require "inspec/waiver_file_reader"
12
+ require "inspec/utils/convert"
11
13
 
12
14
  module Inspec
13
15
  class Rule
14
16
  include ::RSpec::Matchers
15
17
 
16
18
  attr_reader :__waiver_data
17
- attr_accessor :resource_dsl
19
+ attr_accessor :resource_dsl, :na_impact_freeze
18
20
  attr_reader :__profile_id
19
21
 
20
22
  def initialize(id, profile_id, resource_dsl, opts, &block)
@@ -38,6 +40,7 @@ module Inspec
38
40
  @__merge_count = 0
39
41
  @__merge_changes = []
40
42
  @__skip_only_if_eval = opts[:skip_only_if_eval]
43
+ @__na_rule = {}
41
44
 
42
45
  # evaluate the given definition
43
46
  return unless block_given?
@@ -73,10 +76,13 @@ module Inspec
73
76
  end
74
77
 
75
78
  def impact(v = nil)
76
- if v.is_a?(String)
77
- @impact = Inspec::Impact.impact_from_string(v)
78
- elsif !v.nil?
79
- @impact = v
79
+ # N/A impact freeze is required when only_applicable_if block has reset impact value to zero"
80
+ unless na_impact_freeze
81
+ if v.is_a?(String)
82
+ @impact = Inspec::Impact.impact_from_string(v)
83
+ elsif !v.nil?
84
+ @impact = v
85
+ end
80
86
  end
81
87
 
82
88
  @impact
@@ -133,15 +139,28 @@ module Inspec
133
139
  #
134
140
  # @param [Type] &block returns true if tests are added, false otherwise
135
141
  # @return [nil]
136
- def only_if(message = nil)
142
+ def only_if(message = nil, impact: nil)
137
143
  return unless block_given?
138
144
  return if @__skip_only_if_eval == true
139
145
 
146
+ self.impact(impact) if impact && !yield
140
147
  @__skip_rule[:result] ||= !yield
141
148
  @__skip_rule[:type] = :only_if
142
149
  @__skip_rule[:message] = message
143
150
  end
144
151
 
152
+ def only_applicable_if(message = nil)
153
+ return unless block_given?
154
+ return if yield
155
+
156
+ impact(0.0)
157
+ self.na_impact_freeze = true # this flag prevents impact value to reset to any other value
158
+
159
+ @__na_rule[:result] ||= !yield
160
+ @__na_rule[:type] = :only_applicable_if
161
+ @__na_rule[:message] = message
162
+ end
163
+
145
164
  # Describe will add one or more tests to this control. There is 2 ways
146
165
  # of calling it:
147
166
  #
@@ -252,6 +271,10 @@ module Inspec
252
271
  rule.instance_variable_get(:@__skip_rule)
253
272
  end
254
273
 
274
+ def self.na_status(rule)
275
+ rule.instance_variable_get(:@__na_rule)
276
+ end
277
+
255
278
  def self.set_skip_rule(rule, value, message = nil, type = :only_if)
256
279
  rule.instance_variable_set(:@__skip_rule,
257
280
  {
@@ -273,16 +296,26 @@ module Inspec
273
296
  # creates a dummay array of "checks" with a skip outcome
274
297
  def self.prepare_checks(rule)
275
298
  skip_check = skip_status(rule)
276
- return checks(rule) unless skip_check[:result].eql?(true)
299
+ na_check = na_status(rule)
300
+ return checks(rule) unless skip_check[:result].eql?(true) || na_check[:result].eql?(true)
277
301
 
278
- if skip_check[:message]
279
- msg = "Skipped control due to #{skip_check[:type]} condition: #{skip_check[:message]}"
302
+ resource = rule.noop
303
+ if skip_check[:result].eql?(true)
304
+ if skip_check[:message]
305
+ msg = "Skipped control due to #{skip_check[:type]} condition: #{skip_check[:message]}"
306
+ else
307
+ msg = "Skipped control due to #{skip_check[:type]} condition."
308
+ end
309
+ resource.skip_resource(msg)
280
310
  else
281
- msg = "Skipped control due to #{skip_check[:type]} condition."
311
+ if na_check[:message]
312
+ msg = "N/A control due to #{na_check[:type]} condition: #{na_check[:message]}"
313
+ else
314
+ msg = "N/A control due to #{na_check[:type]} condition."
315
+ end
316
+ resource.fail_resource(msg)
282
317
  end
283
318
 
284
- resource = rule.noop
285
- resource.skip_resource(msg)
286
319
  [["describe", [resource], nil]]
287
320
  end
288
321
 
@@ -337,17 +370,20 @@ module Inspec
337
370
  # only_if mechanism)
338
371
  # Double underscore: not intended to be called as part of the DSL
339
372
  def __apply_waivers
340
- input_name = @__rule_id # TODO: control ID slugging
341
- registry = Inspec::InputRegistry.instance
342
- input = registry.inputs_by_profile.dig(__profile_id, input_name)
343
- return unless input && input.has_value? && input.value.is_a?(Hash)
373
+ control_id = @__rule_id # TODO: control ID slugging
374
+ waiver_files = Inspec::Config.cached.final_options["waiver_file"] if Inspec::Config.cached.respond_to?(:final_options)
375
+
376
+ waiver_data_by_profile = Inspec::WaiverFileReader.fetch_waivers_by_profile(__profile_id, waiver_files) unless waiver_files.nil?
377
+
378
+ return unless waiver_data_by_profile && waiver_data_by_profile[control_id] && waiver_data_by_profile[control_id].is_a?(Hash)
344
379
 
345
380
  # An InSpec Input is a datastructure that tracks a profile parameter
346
381
  # over time. Its value can be set by many sources, and it keeps a
347
382
  # log of each "set" event so that when it is collapsed to a value,
348
383
  # it can determine the correct (highest priority) value.
349
384
  # Store in an instance variable for.. later reading???
350
- @__waiver_data = input.value
385
+ @__waiver_data = waiver_data_by_profile[control_id]
386
+
351
387
  __waiver_data["skipped_due_to_waiver"] = false
352
388
  __waiver_data["message"] = ""
353
389
 
@@ -376,6 +412,7 @@ module Inspec
376
412
  # expiration_date. We only care here if it has a "run" key and it
377
413
  # is false-like, since all non-skipped waiver operations are handled
378
414
  # during reporting phase.
415
+ __waiver_data["run"] = Converter.to_boolean(__waiver_data["run"]) if __waiver_data.key?("run")
379
416
  return unless __waiver_data.key?("run") && !__waiver_data["run"]
380
417
 
381
418
  # OK, apply a skip.
@@ -1,3 +1,5 @@
1
+ require "inspec/enhanced_outcomes"
2
+
1
3
  module Inspec
2
4
  class RunData
3
5
  Control = Struct.new(
@@ -31,6 +33,10 @@ module Inspec
31
33
  ].each do |field|
32
34
  self[field] = raw_ctl_data[field]
33
35
  end
36
+
37
+ def status
38
+ Inspec::EnhancedOutcomes.determine_status(results, impact)
39
+ end
34
40
  end
35
41
  end
36
42
 
@@ -16,14 +16,20 @@ module Inspec
16
16
  :total,
17
17
  :passed,
18
18
  :skipped,
19
- :failed
19
+ :failed,
20
+ :not_reviewed,
21
+ :not_applicable,
22
+ :error
20
23
  ) do
21
24
  include HashLikeStruct
22
25
  def initialize(raw_stat_ctl_data)
23
26
  self.total = raw_stat_ctl_data[:total]
24
27
  self.passed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:passed][:total])
25
- self.skipped = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:skipped][:total])
26
28
  self.failed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:failed][:total])
29
+ self.skipped = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:skipped][:total]) if raw_stat_ctl_data[:skipped]
30
+ self.not_reviewed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:not_reviewed][:total]) if raw_stat_ctl_data[:not_reviewed]
31
+ self.not_applicable = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:not_applicable][:total]) if raw_stat_ctl_data[:not_applicable]
32
+ self.error = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:error][:total]) if raw_stat_ctl_data[:error]
27
33
  end
28
34
  end
29
35
  class Controls