inspec-core 4.22.1 → 4.23.11

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 (43) 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 -25
  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 -23
  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/mount.rb +1 -1
  18. data/lib/inspec/resources/mysql_session.rb +31 -8
  19. data/lib/inspec/resources/postgres.rb +1 -1
  20. data/lib/inspec/resources/postgres_session.rb +6 -4
  21. data/lib/inspec/resources/processes.rb +1 -1
  22. data/lib/inspec/resources/service.rb +1 -1
  23. data/lib/inspec/resources/users.rb +1 -1
  24. data/lib/inspec/resources/windows_firewall.rb +110 -0
  25. data/lib/inspec/resources/windows_firewall_rule.rb +137 -0
  26. data/lib/inspec/run_data/profile.rb +3 -2
  27. data/lib/inspec/schema/exec_json.rb +1 -1
  28. data/lib/inspec/shell.rb +3 -3
  29. data/lib/inspec/utils/parser.rb +1 -1
  30. data/lib/inspec/utils/run_data_filters.rb +104 -0
  31. data/lib/inspec/version.rb +1 -1
  32. data/lib/plugins/inspec-compliance/lib/inspec-compliance/api.rb +4 -4
  33. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +1 -1
  34. data/lib/plugins/inspec-init/templates/profiles/aws/README.md +1 -1
  35. data/lib/plugins/inspec-reporter-html2/README.md +1 -1
  36. data/lib/plugins/inspec-reporter-junit/README.md +17 -0
  37. data/lib/plugins/inspec-reporter-junit/lib/inspec-reporter-junit.rb +21 -0
  38. data/lib/plugins/inspec-reporter-junit/lib/inspec-reporter-junit/reporter.rb +155 -0
  39. data/lib/plugins/inspec-reporter-junit/lib/inspec-reporter-junit/version.rb +5 -0
  40. data/lib/plugins/shared/core_plugin_test_helper.rb +0 -16
  41. metadata +17 -34
  42. data/README.md +0 -474
  43. 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,15 +40,16 @@ 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 = [])
49
51
  psql_cmd = create_psql_cmd(query, db)
50
- cmd = inspec.command(psql_cmd)
52
+ cmd = inspec.command(psql_cmd, redact_regex: /(PGPASSWORD=').+(' psql .*)/)
51
53
  out = cmd.stdout + "\n" + cmd.stderr
52
54
  if cmd.exit_status != 0 || out =~ /could not connect to .*/ || out.downcase =~ /^error:.*/
53
55
  Lines.new(out, "PostgreSQL query with errors: #{query}")
@@ -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,
@@ -141,7 +141,7 @@ module Inspec::Resources
141
141
  elsif version > 0
142
142
  SysV.new(inspec, service_ctl || "/usr/sbin/service")
143
143
  end
144
- when "redhat", "fedora", "centos", "oracle", "cloudlinux"
144
+ when "redhat", "fedora", "centos", "oracle", "cloudlinux", "scientific"
145
145
  version = os[:release].to_i
146
146
 
147
147
  systemd = ((platform != "fedora" && version >= 7) ||
@@ -20,7 +20,7 @@ module Inspec::Resources
20
20
  WindowsUser.new(inspec)
21
21
  elsif ["darwin"].include?(os[:family])
22
22
  DarwinUser.new(inspec)
23
- elsif ["freebsd"].include?(os[:family])
23
+ elsif ["bsd"].include?(os[:family])
24
24
  FreeBSDUser.new(inspec)
25
25
  elsif ["aix"].include?(os[:family])
26
26
  AixUser.new(inspec)
@@ -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
@@ -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