inspec-core 4.22.8 → 4.23.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/inspec-core.gemspec +3 -5
  4. data/lib/bundles/inspec-supermarket/cli.rb +1 -1
  5. data/lib/inspec/base_cli.rb +11 -1
  6. data/lib/inspec/cli.rb +4 -2
  7. data/lib/inspec/config.rb +19 -1
  8. data/lib/inspec/input.rb +4 -3
  9. data/lib/inspec/input_registry.rb +7 -1
  10. data/lib/inspec/plugin/v2/plugin_types/reporter.rb +4 -31
  11. data/lib/inspec/reporters.rb +0 -3
  12. data/lib/inspec/reporters/automate.rb +3 -3
  13. data/lib/inspec/reporters/base.rb +7 -29
  14. data/lib/inspec/resources/apt.rb +5 -5
  15. data/lib/inspec/resources/bridge.rb +1 -1
  16. data/lib/inspec/resources/host.rb +1 -1
  17. data/lib/inspec/resources/mysql_session.rb +9 -5
  18. data/lib/inspec/resources/postgres.rb +1 -1
  19. data/lib/inspec/resources/postgres_session.rb +5 -3
  20. data/lib/inspec/resources/processes.rb +1 -1
  21. data/lib/inspec/resources/windows_firewall.rb +110 -0
  22. data/lib/inspec/resources/windows_firewall_rule.rb +137 -0
  23. data/lib/inspec/rule.rb +8 -8
  24. data/lib/inspec/run_data/profile.rb +3 -2
  25. data/lib/inspec/schema/exec_json.rb +1 -1
  26. data/lib/inspec/shell.rb +3 -3
  27. data/lib/inspec/utils/parser.rb +1 -1
  28. data/lib/inspec/utils/run_data_filters.rb +104 -0
  29. data/lib/inspec/version.rb +1 -1
  30. data/lib/plugins/inspec-compliance/lib/inspec-compliance/api.rb +4 -4
  31. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +1 -1
  32. data/lib/plugins/inspec-init/templates/profiles/aws/README.md +1 -1
  33. data/lib/plugins/inspec-reporter-html2/README.md +1 -1
  34. data/lib/plugins/inspec-reporter-junit/README.md +17 -0
  35. data/lib/plugins/inspec-reporter-junit/lib/inspec-reporter-junit.rb +21 -0
  36. data/lib/plugins/inspec-reporter-junit/lib/inspec-reporter-junit/reporter.rb +155 -0
  37. data/lib/plugins/inspec-reporter-junit/lib/inspec-reporter-junit/version.rb +5 -0
  38. data/lib/plugins/shared/core_plugin_test_helper.rb +0 -16
  39. metadata +17 -34
  40. data/README.md +0 -474
  41. data/lib/inspec/reporters/junit.rb +0 -77
@@ -19,7 +19,7 @@ module Inspec::Resources
19
19
  @conf_path = File.join @conf_dir, "postgresql.conf"
20
20
  else
21
21
  @conf_path = nil
22
- return skip_resource "Seems like PostgreSQL is not installed on your system"
22
+ skip_resource "Seems like PostgreSQL is not installed on your system"
23
23
  end
24
24
  end
25
25
 
@@ -26,12 +26,13 @@ module Inspec::Resources
26
26
  supports platform: "windows"
27
27
  desc "Use the postgres_session InSpec audit resource to test SQL commands run against a PostgreSQL database."
28
28
  example <<~EXAMPLE
29
- sql = postgres_session('username', 'password', 'host')
29
+ sql = postgres_session('username', 'password', 'host', 'port')
30
30
  query('sql_query', ['database_name'])` contains the query and (optional) database to execute
31
31
 
32
32
  # default values:
33
33
  # username: 'postgres'
34
34
  # host: 'localhost'
35
+ # port: 5432
35
36
  # db: databse == db_user running the sql query
36
37
 
37
38
  describe sql.query('SELECT * FROM pg_shadow WHERE passwd IS NULL;') do
@@ -39,10 +40,11 @@ module Inspec::Resources
39
40
  end
40
41
  EXAMPLE
41
42
 
42
- def initialize(user, pass, host = nil)
43
+ def initialize(user, pass, host = nil, port = nil)
43
44
  @user = user || "postgres"
44
45
  @pass = pass
45
46
  @host = host || "localhost"
47
+ @port = port || 5432
46
48
  end
47
49
 
48
50
  def query(query, db = [])
@@ -64,7 +66,7 @@ module Inspec::Resources
64
66
 
65
67
  def create_psql_cmd(query, db = [])
66
68
  dbs = db.map { |x| "-d #{x}" }.join(" ")
67
- "PGPASSWORD='#{@pass}' psql -U #{@user} #{dbs} -h #{@host} -A -t -c #{escaped_query(query)}"
69
+ "PGPASSWORD='#{@pass}' psql -U #{@user} #{dbs} -h #{@host} -p #{@port} -A -t -c #{escaped_query(query)}"
68
70
  end
69
71
  end
70
72
  end
@@ -138,7 +138,7 @@ module Inspec::Resources
138
138
  command: 8,
139
139
  }
140
140
  else
141
- command = "ps axo label,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user:32,command"
141
+ command = "ps wwaxo label,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user:32,command"
142
142
  regex = /^(.+?)\s+(\d+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(\w{3} \d{2}|\d{2}:\d{2}:\d{2})\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
143
143
  field_map = {
144
144
  label: 1,
@@ -0,0 +1,110 @@
1
+ module Inspec::Resources
2
+ class WindowsFirewall < Inspec.resource(1)
3
+ name "windows_firewall"
4
+ supports platform: "windows"
5
+ desc "Check properties of the Windows Firewall for a specific profile."
6
+ example <<~EXAMPLE
7
+ describe windows_firewall("Public") do
8
+ it { should be_enabled }
9
+ its("default_inbound_action") { should_not cmp "NotConfigured" }
10
+ its("num_rules") { should be 19 }
11
+ end
12
+ EXAMPLE
13
+
14
+ def initialize(profile = "Public")
15
+ @profile = profile
16
+ @state = {}
17
+
18
+ load_profile_cmd = load_firewall_profile(profile)
19
+ cmd = inspec.powershell(load_profile_cmd)
20
+
21
+ @state = JSON.load(cmd.stdout) unless cmd.stdout.empty?
22
+ end
23
+
24
+ def to_s
25
+ "Windows Firewall (Profile #{@profile})"
26
+ end
27
+
28
+ def exist?
29
+ !@state.empty?
30
+ end
31
+
32
+ def enabled?
33
+ @state["enabled"]
34
+ end
35
+
36
+ def default_inbound_allowed?
37
+ @state["default_inbound_action"] == "Allow"
38
+ end
39
+
40
+ def default_outbound_allowed?
41
+ @state["default_outbound_action"] == "Allow"
42
+ end
43
+
44
+ # Access to return values from Powershell via `its("PROPERTY")` and `have_PROPERTY "VALUE"`
45
+ def method_missing(method_name, *arguments, &_block)
46
+ property = normalize_for_have_access(method_name)
47
+
48
+ if method_name.to_s.start_with? "has_"
49
+ expected_value = arguments.first
50
+ respond_to_have(property, expected_value)
51
+ else
52
+ access_property(property)
53
+ end
54
+ end
55
+
56
+ def respond_to_missing?(method_name, _include_private = false)
57
+ property = normalize_for_have_access(method_name)
58
+
59
+ @state.key? property
60
+ end
61
+
62
+ private
63
+
64
+ def normalize_for_have_access(property)
65
+ property.to_s
66
+ .delete_prefix("has_")
67
+ .delete_suffix("?")
68
+ end
69
+
70
+ def access_property(property)
71
+ @state[property]
72
+ end
73
+
74
+ def respond_to_have(property, value)
75
+ @state[property] == value
76
+ end
77
+
78
+ def load_firewall_profile(profile_name)
79
+ <<-EOH
80
+ Remove-TypeData System.Array # workaround for PS bug here: https://bit.ly/2SRMQ8M
81
+ $profile = Get-NetFirewallProfile -Name "#{profile_name}"
82
+ $count = @($profile | Get-NetFirewallRule).Count
83
+ ([PSCustomObject]@{
84
+ profile_name = $profile.Name
85
+ profile = $profile.Profile.ToString()
86
+ description = $profile.Description
87
+ enabled = [bool]::Parse($profile.Enabled.ToString())
88
+ default_inbound_action = $profile.DefaultInboundAction.ToString()
89
+ default_outbound_action = $profile.DefaultOutboundAction.ToString()
90
+
91
+ allow_inbound_rules = $profile.AllowInboundRules.ToString()
92
+ allow_local_firewall_rules = $profile.AllowLocalFirewallRules.ToString()
93
+ allow_local_ipsec_rules = $profile.AllowLocalIPsecRules.ToString()
94
+ allow_user_apps = $profile.AllowUserApps.ToString()
95
+ allow_user_ports = $profile.AllowUserPorts.ToString()
96
+ allow_unicast_response_to_multicast = $profile.AllowUnicastResponseToMulticast.ToString()
97
+
98
+ notify_on_listen = $profile.NotifyOnListen.ToString()
99
+ enable_stealth_mode_for_ipsec = $profile.EnableStealthModeForIPsec.ToString()
100
+ log_max_size_kilobytes = $profile.LogMaxSizeKilobytes
101
+ log_allowed = $profile.LogAllowed.ToString()
102
+ log_blocked = $profile.LogBlocked.ToString()
103
+ log_ignored = $profile.LogIgnored.ToString()
104
+
105
+ num_rules = $count
106
+ }) | ConvertTo-Json
107
+ EOH
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,137 @@
1
+ module Inspec::Resources
2
+ class WindowsFirewallRule < Inspec.resource(1)
3
+ name "windows_firewall_rule"
4
+ supports platform: "windows"
5
+ desc "Check properties of a Windows Firewall rule."
6
+ example <<~EXAMPLE
7
+ describe windows_firewall_rule("Name") do
8
+ it { should exist }
9
+ it { should be_enabled }
10
+
11
+ it { should be_outbound}
12
+ it { should be_tcp }
13
+ it { should have_remote_port 80 }
14
+ end
15
+ EXAMPLE
16
+
17
+ def initialize(name)
18
+ @name = name
19
+ @state = {}
20
+
21
+ query = load_firewall_state(name)
22
+ cmd = inspec.powershell(query)
23
+ @state = JSON.load(cmd.stdout) unless cmd.stdout.empty?
24
+ end
25
+
26
+ def to_s
27
+ "Windows Firewall Rule #{@name}"
28
+ end
29
+
30
+ def exist?
31
+ !@state.empty?
32
+ end
33
+
34
+ def enabled?
35
+ @state["enabled"]
36
+ end
37
+
38
+ def allowed?
39
+ @state["action"] == "Allow"
40
+ end
41
+
42
+ def inbound?
43
+ @state["direction"] == "Inbound"
44
+ end
45
+
46
+ def outbound?
47
+ ! inbound?
48
+ end
49
+
50
+ def tcp?
51
+ @state["protocol"] == "TCP"
52
+ end
53
+
54
+ def udp?
55
+ @state["protocol"] == "UDP"
56
+ end
57
+
58
+ def icmp?
59
+ @state["protocol"].start_with? "ICMP"
60
+ end
61
+
62
+ def icmpv4?
63
+ @state["protocol"] == "ICMPv4"
64
+ end
65
+
66
+ def icmpv6?
67
+ @state["protocol"] == "ICMPv6"
68
+ end
69
+
70
+ # Access to return values from Powershell via `its("PROPERTY")` and `have_PROPERTY? "VALUE"`
71
+ def method_missing(method_name, *arguments, &_block)
72
+ property = normalize_for_have_access(method_name)
73
+
74
+ if method_name.to_s.start_with? "has_"
75
+ expected_value = arguments.first
76
+ respond_to_have(property, expected_value)
77
+ else
78
+ access_property(property)
79
+ end
80
+ end
81
+
82
+ def respond_to_missing?(method_name, _include_private = false)
83
+ property = normalize_for_have_access(method_name)
84
+
85
+ @state.key? property
86
+ end
87
+
88
+ private
89
+
90
+ def normalize_for_have_access(property)
91
+ property.to_s
92
+ .delete_prefix("has_")
93
+ .delete_suffix("?")
94
+ end
95
+
96
+ def access_property(property)
97
+ @state[property]
98
+ end
99
+
100
+ def respond_to_have(property, value)
101
+ @state[property] == value
102
+ end
103
+
104
+ # Taken from Chef, but changed `firewall_action` to `action` for consistency
105
+ # @see https://github.com/chef/chef/blob/master/lib/chef/resource/windows_firewall_rule.rb
106
+ def load_firewall_state(rule_name)
107
+ <<-EOH
108
+ Remove-TypeData System.Array # workaround for PS bug here: https://bit.ly/2SRMQ8M
109
+ $rule = Get-NetFirewallRule -Name "#{rule_name}"
110
+ $addressFilter = $rule | Get-NetFirewallAddressFilter
111
+ $portFilter = $rule | Get-NetFirewallPortFilter
112
+ $applicationFilter = $rule | Get-NetFirewallApplicationFilter
113
+ $serviceFilter = $rule | Get-NetFirewallServiceFilter
114
+ $interfaceTypeFilter = $rule | Get-NetFirewallInterfaceTypeFilter
115
+ ([PSCustomObject]@{
116
+ rule_name = $rule.Name
117
+ description = $rule.Description
118
+ displayname = $rule.DisplayName
119
+ group = $rule.Group
120
+ local_address = $addressFilter.LocalAddress
121
+ local_port = $portFilter.LocalPort
122
+ remote_address = $addressFilter.RemoteAddress
123
+ remote_port = $portFilter.RemotePort
124
+ direction = $rule.Direction.ToString()
125
+ protocol = $portFilter.Protocol
126
+ icmp_type = $portFilter.IcmpType
127
+ action = $rule.Action.ToString()
128
+ profile = $rule.Profile.ToString()
129
+ program = $applicationFilter.Program
130
+ service = $serviceFilter.Service
131
+ interface_type = $interfaceTypeFilter.InterfaceType.ToString()
132
+ enabled = [bool]::Parse($rule.Enabled.ToString())
133
+ }) | ConvertTo-Json
134
+ EOH
135
+ end
136
+ end
137
+ end
@@ -343,14 +343,8 @@ module Inspec
343
343
  __waiver_data["skipped_due_to_waiver"] = false
344
344
  __waiver_data["message"] = ""
345
345
 
346
- # Waivers should have a hash value with keys possibly including "run" and
347
- # expiration_date. We only care here if it has a "run" key and it
348
- # is false-like, since all non-skipped waiver operations are handled
349
- # during reporting phase.
350
- return unless __waiver_data.key?("run") && !__waiver_data["run"]
351
-
352
- # OK, the intent is to skip. Does it have an expiration date, and
353
- # if so, is it in the future?
346
+ # Does it have an expiration date, and if so, is it in the future?
347
+ # This sets a waiver message before checking `run: true`
354
348
  expiry = __waiver_data["expiration_date"]
355
349
  if expiry
356
350
  # YAML will automagically give us a Date or a Time.
@@ -370,6 +364,12 @@ module Inspec
370
364
  end
371
365
  end
372
366
 
367
+ # Waivers should have a hash value with keys possibly including "run" and
368
+ # expiration_date. We only care here if it has a "run" key and it
369
+ # is false-like, since all non-skipped waiver operations are handled
370
+ # during reporting phase.
371
+ return unless __waiver_data.key?("run") && !__waiver_data["run"]
372
+
373
373
  # OK, apply a skip.
374
374
  @__skip_rule[:result] = true
375
375
  @__skip_rule[:type] = :waiver
@@ -96,11 +96,12 @@ module Inspec
96
96
  # There are probably others
97
97
  :value,
98
98
  :type,
99
- :required
99
+ :required,
100
+ :sensitive
100
101
  ) do
101
102
  include HashLikeStruct
102
103
  def initialize(raw_opts_data)
103
- %i{value type required}.each { |f| self[f] = raw_opts_data[f] }
104
+ %i{value type required sensitive}.each { |f| self[f] = raw_opts_data[f] }
104
105
  end
105
106
  end
106
107
  end
@@ -74,7 +74,7 @@ module Inspec
74
74
  },
75
75
  }, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT])
76
76
 
77
- # Based loosely on https://www.inspec.io/docs/reference/profiles/ as of July 3, 2019
77
+ # Based loosely on https://docs.chef.io/inspec/profiles/ as of July 3, 2019
78
78
  # However, concessions were made to the reality of current reporters, specifically
79
79
  # with how description is omitted and version/inspec_version aren't as advertised online
80
80
  PROFILE = Primitives::SchemaType.new("Exec JSON Profile", {
@@ -1,4 +1,4 @@
1
- require "pry"
1
+ autoload :Pry, "pry"
2
2
 
3
3
  module Inspec
4
4
  # A pry based shell for inspec. Given a runner (with a configured backend and
@@ -137,7 +137,7 @@ module Inspec
137
137
  end
138
138
 
139
139
  info += "#{mark "Web Reference:"}\n\n"
140
- info += "https://www.inspec.io/docs/reference/resources/#{topic}\n\n"
140
+ info += "https://docs.chef.io/inspec/resources/#{topic}\n\n"
141
141
  puts info
142
142
  else
143
143
  begin
@@ -208,7 +208,7 @@ module Inspec
208
208
 
209
209
  its('content') { should_not match /^MyKey:\\s+some value/ }
210
210
 
211
- For more examples, see: https://www.inspec.io/docs/reference/matchers/
211
+ For more examples, see: https://docs.chef.io/inspec/matchers/
212
212
 
213
213
  EOL
214
214
  end
@@ -84,7 +84,7 @@ module Inspec
84
84
  end
85
85
 
86
86
  # parse device and type
87
- mount_options = { device: mount[0], type: mount[4] }
87
+ mount_options = { device: mount[0], type: mount[4] }
88
88
 
89
89
  if compatibility == false
90
90
  # parse options as array
@@ -0,0 +1,104 @@
1
+ module Inspec
2
+ module Utils
3
+ # RunDataFilters is a mixin for core Reporters and plugin reporters.
4
+ # The methods operate on the run_data Hash (prior to any conversion to a
5
+ # full RunData object).
6
+ # All methods here operate using the run_data accessor and modify
7
+ # its contents in place (if needed).
8
+ module RunDataFilters
9
+
10
+ # Long name, but we want to be clear this operates on the Hash
11
+ # This is the only method that client libraries need to call; any future
12
+ # feature growth should be handled internally here.
13
+ def apply_run_data_filters_to_hash
14
+ @config[:runtime_config] = Inspec::Config.cached || {}
15
+ apply_report_resize_options
16
+ redact_sensitive_inputs
17
+ suppress_diff_output
18
+ sort_controls
19
+ end
20
+
21
+ # Apply options such as message truncation and removal of backtraces
22
+ def apply_report_resize_options
23
+ runtime_config = @config[:runtime_config]
24
+
25
+ message_truncation = runtime_config[:reporter_message_truncation] || "ALL"
26
+ @trunc = message_truncation == "ALL" ? -1 : message_truncation.to_i
27
+ include_backtrace = runtime_config[:reporter_backtrace_inclusion].nil? ? true : runtime_config[:reporter_backtrace_inclusion]
28
+
29
+ @run_data[:profiles]&.each do |p|
30
+ p[:controls].each do |c|
31
+ c[:results]&.map! do |r|
32
+ r.delete(:backtrace) unless include_backtrace
33
+ process_message_truncation(r)
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ # Find any inputs with :sensitive = true and replace their values with "***"
40
+ def redact_sensitive_inputs
41
+ @run_data[:profiles]&.each do |p|
42
+ p[:inputs]&.each do |i|
43
+ next unless i[:options][:sensitive]
44
+
45
+ i[:options][:value] = "***"
46
+ end
47
+ end
48
+ end
49
+
50
+ # Optionally suppress diff output in the message field
51
+ def suppress_diff_output
52
+ return if @config[:runtime_config][:diff]
53
+
54
+ @run_data[:profiles]&.each do |p|
55
+ p[:controls]&.each do |c|
56
+ c[:results]&.each do |r|
57
+ next unless r[:message] # :message only set on failure
58
+
59
+ pos = r[:message].index("\n\nDiff:")
60
+ next unless pos # Only textual tests get Diffs
61
+
62
+ r[:message] = r[:message].slice(0, pos)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ # Optionally sort controls within each profile in report
69
+ def sort_controls
70
+ sort_type = @config[:runtime_config][:sort_results_by]
71
+ return unless sort_type
72
+ return if sort_type == "none"
73
+
74
+ @run_data[:profiles]&.each do |p|
75
+ p[:controls] ||= []
76
+ p[:groups] ||= []
77
+
78
+ case sort_type
79
+ when "control"
80
+ p[:controls].sort_by! { |c| c[:id] }
81
+ when "random"
82
+ p[:controls].shuffle!
83
+ when "file"
84
+ # Sort the controls by file, but preserve order within the file.
85
+ # Files are called "groups" in the run_data, and the filename is in the id.
86
+ sorted_control_ids = p[:groups].sort_by { |g| g[:id] }.map { |g| g[:controls] }.flatten
87
+ controls_by_id = {}
88
+ p[:controls].each { |c| controls_by_id[c[:id]] = c }
89
+ p[:controls] = sorted_control_ids.map { |cid| controls_by_id[cid] }
90
+ end
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def process_message_truncation(result)
97
+ if result.key?(:message) && result[:message] != "" && @trunc > -1 && result[:message].length > @trunc
98
+ result[:message] = result[:message][0...@trunc] + "[Truncated to #{@trunc} characters]"
99
+ end
100
+ result
101
+ end
102
+ end
103
+ end
104
+ end