inspec-core 4.49.0 → 4.56.17

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -11
  3. data/inspec-core.gemspec +2 -2
  4. data/lib/bundles/inspec-supermarket/README.md +21 -2
  5. data/lib/bundles/inspec-supermarket/cli.rb +20 -3
  6. data/lib/bundles/inspec-supermarket/target.rb +3 -2
  7. data/lib/inspec/base_cli.rb +8 -0
  8. data/lib/inspec/cli.rb +12 -3
  9. data/lib/inspec/config.rb +5 -1
  10. data/lib/inspec/dependencies/requirement.rb +2 -1
  11. data/lib/inspec/formatters/base.rb +8 -6
  12. data/lib/inspec/globals.rb +5 -0
  13. data/lib/inspec/library_eval_context.rb +2 -0
  14. data/lib/inspec/plugin/v1/registry.rb +1 -1
  15. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +10 -0
  16. data/lib/inspec/profile.rb +2 -0
  17. data/lib/inspec/profile_context.rb +1 -6
  18. data/lib/inspec/reporters/automate.rb +1 -1
  19. data/lib/inspec/reporters/json.rb +1 -1
  20. data/lib/inspec/resources/auditd.rb +5 -4
  21. data/lib/inspec/resources/bash.rb +2 -0
  22. data/lib/inspec/resources/file.rb +38 -0
  23. data/lib/inspec/resources/firewalld.rb +83 -9
  24. data/lib/inspec/resources/grub_conf.rb +1 -1
  25. data/lib/inspec/resources/http.rb +135 -54
  26. data/lib/inspec/resources/ibmdb2_session.rb +2 -2
  27. data/lib/inspec/resources/iptables.rb +18 -2
  28. data/lib/inspec/resources/kernel_parameters.rb +58 -0
  29. data/lib/inspec/resources/mssql_session.rb +11 -3
  30. data/lib/inspec/resources/oracledb_session.rb +3 -1
  31. data/lib/inspec/resources/package.rb +74 -1
  32. data/lib/inspec/resources/packages.rb +21 -0
  33. data/lib/inspec/resources/registry_key.rb +30 -0
  34. data/lib/inspec/resources/selinux.rb +6 -1
  35. data/lib/inspec/resources/service.rb +58 -9
  36. data/lib/inspec/resources/ssl.rb +7 -0
  37. data/lib/inspec/resources/timezone.rb +65 -0
  38. data/lib/inspec/resources.rb +2 -0
  39. data/lib/inspec/runner_rspec.rb +30 -0
  40. data/lib/inspec/utils/filter.rb +46 -2
  41. data/lib/inspec/utils/run_data_filters.rb +1 -1
  42. data/lib/inspec/version.rb +1 -1
  43. data/lib/plugins/inspec-compliance/lib/inspec-compliance/api.rb +1 -1
  44. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +4 -3
  45. metadata +8 -5
@@ -11,6 +11,7 @@ module Inspec::Resources
11
11
  class Http < Inspec.resource(1)
12
12
  name "http"
13
13
  supports platform: "unix"
14
+ supports platform: "windows"
14
15
  desc "Use the http InSpec audit resource to test http call."
15
16
  example <<~EXAMPLE
16
17
  describe http('http://localhost:8080/ping', auth: {user: 'user', pass: 'test'}, params: {format: 'html'}) do
@@ -104,6 +105,7 @@ module Inspec::Resources
104
105
  opts[:data]
105
106
  end
106
107
 
108
+ # not supported on Windows
107
109
  def open_timeout
108
110
  opts.fetch(:open_timeout, 60)
109
111
  end
@@ -117,7 +119,11 @@ module Inspec::Resources
117
119
  end
118
120
 
119
121
  def max_redirects
120
- opts.fetch(:max_redirects, 0)
122
+ opts.fetch(:max_redirects, nil)
123
+ end
124
+
125
+ def proxy
126
+ opts.fetch(:proxy, nil)
121
127
  end
122
128
  end
123
129
 
@@ -139,12 +145,18 @@ module Inspec::Resources
139
145
  def response
140
146
  return @response if @response
141
147
 
148
+ Faraday.ignore_env_proxy = true if proxy == "disable"
149
+
142
150
  conn = Faraday.new(url: url, headers: request_headers, params: params, ssl: { verify: ssl_verify? }) do |builder|
143
151
  builder.request :url_encoded
144
- builder.use FaradayMiddleware::FollowRedirects, limit: max_redirects if max_redirects > 0
152
+ builder.use FaradayMiddleware::FollowRedirects, limit: max_redirects unless max_redirects.nil?
145
153
  builder.adapter Faraday.default_adapter
146
154
  end
147
155
 
156
+ unless proxy == "disable" || proxy.nil?
157
+ conn.proxy = proxy
158
+ end
159
+
148
160
  # set basic authentication
149
161
  conn.basic_auth username, password unless username.nil? || password.nil?
150
162
 
@@ -162,98 +174,167 @@ module Inspec::Resources
162
174
  attr_reader :inspec
163
175
 
164
176
  def initialize(inspec, http_method, url, opts)
165
- unless inspec.command("curl").exist?
177
+ http_cmd = inspec.os.windows? ? "Invoke-WebRequest" : "curl"
178
+ unless inspec.command(http_cmd).exist?
166
179
  raise Inspec::Exceptions::ResourceSkipped,
167
- "curl is not available on the target machine"
180
+ "#{http_cmd} is not available on the target machine"
168
181
  end
169
-
170
- @ran_curl = false
182
+ @ran_http = false
171
183
  @inspec = inspec
172
184
  super(http_method, url, opts)
173
185
  end
174
186
 
175
187
  def status
176
- run_curl
188
+ run_http
177
189
  @status
178
190
  end
179
191
 
180
192
  def body
181
- run_curl
193
+ run_http
182
194
  @body&.strip
183
195
  end
184
196
 
185
197
  def response_headers
186
- run_curl
198
+ run_http
187
199
  @response_headers
188
200
  end
189
201
 
190
202
  private
191
203
 
192
- def run_curl
193
- return if @ran_curl
204
+ def run_http
205
+ return if @ran_http
194
206
 
195
- cmd_result = inspec.command(curl_command)
207
+ cmd_result = inspec.command(http_command)
196
208
  response = cmd_result.stdout
197
- @ran_curl = true
209
+ @ran_http = true
198
210
  return if response.nil? || cmd_result.exit_status != 0
199
211
 
200
- # strip any carriage returns to normalize output
201
- response.delete!("\r")
212
+ if inspec.os.windows?
213
+ response = JSON.parse(response)
202
214
 
203
- # split the prelude (status line and headers) and the body
204
- prelude, remainder = response.split("\n\n", 2)
205
- loop do
206
- break unless remainder =~ %r{^HTTP/}
215
+ @status = response["StatusCode"]
216
+ @body = response["Content"]
207
217
 
208
- prelude, remainder = remainder.split("\n\n", 2)
209
- end
210
- @body = remainder
211
- prelude = prelude.lines
212
-
213
- # grab the status off of the first line of the prelude
214
- status_line = prelude.shift
215
- @status = status_line.split(" ", 3)[1].to_i
216
-
217
- # parse the rest of the prelude which will be all the HTTP headers
218
- @response_headers = {}
219
- prelude.each do |line|
220
- line.strip!
221
- key, value = line.split(":", 2)
222
- @response_headers[key] = value.strip
218
+ @response_headers = {}
219
+ response["Headers"].each do |name, value|
220
+ @response_headers["#{name}"] = value
221
+ end
222
+ else
223
+ # strip any carriage returns to normalize output
224
+ response.delete!("\r")
225
+
226
+ # split the prelude (status line and headers) and the body
227
+ prelude, remainder = response.split("\n\n", 2)
228
+ loop do
229
+ break unless remainder =~ %r{^HTTP/}
230
+
231
+ prelude, remainder = remainder.split("\n\n", 2)
232
+ end
233
+ @body = remainder
234
+ prelude = prelude.lines
235
+
236
+ # grab the status off of the first line of the prelude
237
+ status_line = prelude.shift
238
+ @status = status_line.split(" ", 3)[1].to_i
239
+
240
+ # parse the rest of the prelude which will be all the HTTP headers
241
+ @response_headers = {}
242
+ prelude.each do |line|
243
+ line.strip!
244
+ key, value = line.split(":", 2)
245
+ @response_headers[key] = value.strip
246
+ end
223
247
  end
224
248
  end
225
249
 
226
- def curl_command # rubocop:disable Metrics/AbcSize
227
- cmd = ["curl -i"]
228
-
229
- # Use curl's --head option when the method requested is HEAD. Otherwise,
230
- # the user may experience a timeout when curl does not properly close
231
- # the connection after the response is received.
232
- if http_method.casecmp("HEAD") == 0
233
- cmd << "--head"
250
+ def http_command # rubocop:disable Metrics/AbcSize
251
+ if inspec.os.windows?
252
+ load_powershell_command
234
253
  else
235
- cmd << "-X #{http_method}"
254
+ cmd = ["curl -i"]
255
+
256
+ # Use curl's --head option when the method requested is HEAD. Otherwise,
257
+ # the user may experience a timeout when curl does not properly close
258
+ # the connection after the response is received.
259
+ if http_method.casecmp("HEAD") == 0
260
+ cmd << "--head"
261
+ else
262
+ cmd << "-X #{http_method}"
263
+ end
264
+
265
+ cmd << "--noproxy '*'" if proxy == "disable"
266
+ unless proxy == "disable" || proxy.nil?
267
+ if proxy.is_a?(Hash)
268
+ cmd << "--proxy #{proxy[:uri]} --proxy-user #{proxy[:user]}:#{proxy[:password]}"
269
+ else
270
+ cmd << "--proxy #{proxy}"
271
+ end
272
+ end
273
+ cmd << "--connect-timeout #{open_timeout}"
274
+ cmd << "--max-time #{open_timeout + read_timeout}"
275
+ cmd << "--user \'#{username}:#{password}\'" unless username.nil? || password.nil?
276
+ cmd << "--insecure" unless ssl_verify?
277
+ cmd << "--data #{Shellwords.shellescape(request_body)}" unless request_body.nil?
278
+ cmd << "--location" unless max_redirects.nil?
279
+ cmd << "--max-redirs #{max_redirects}" unless max_redirects.nil?
280
+
281
+ request_headers.each do |k, v|
282
+ cmd << "-H '#{k}: #{v}'"
283
+ end
284
+
285
+ if params.nil?
286
+ cmd << "'#{url}'"
287
+ else
288
+ cmd << "'#{url}?#{params.map { |e| e.join("=") }.join("&")}'"
289
+ end
290
+
291
+ cmd.join(" ")
236
292
  end
293
+ end
237
294
 
238
- cmd << "--connect-timeout #{open_timeout}"
239
- cmd << "--max-time #{open_timeout + read_timeout}"
240
- cmd << "--user \'#{username}:#{password}\'" unless username.nil? || password.nil?
241
- cmd << "--insecure" unless ssl_verify?
242
- cmd << "--data #{Shellwords.shellescape(request_body)}" unless request_body.nil?
243
- cmd << "--location" if max_redirects > 0
244
- cmd << "--max-redirs #{max_redirects}" if max_redirects > 0
245
-
295
+ def load_powershell_command
296
+ cmd = ["Invoke-WebRequest"]
297
+ cmd << "-Method #{http_method}"
298
+ # Missing connect-timeout
299
+ cmd << "-TimeoutSec #{open_timeout + read_timeout}"
300
+ # Insecure not supported simply https://stackoverflow.com/questions/11696944/powershell-v3-invoke-webrequest-https-error
301
+ cmd << "-MaximumRedirection #{max_redirects}" unless max_redirects.nil?
302
+ request_headers["Authorization"] = """ '\"Basic ' + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(\"#{username}:#{password}\")) +'\"' """ unless username.nil? || password.nil?
303
+ request_header_string = nil
246
304
  request_headers.each do |k, v|
247
- cmd << "-H '#{k}: #{v}'"
305
+ request_header_string << " #{k} = #{v}"
248
306
  end
249
-
307
+ cmd << "-Headers @{#{request_header_string.join(";")}}" unless request_header_string.nil?
250
308
  if params.nil?
251
309
  cmd << "'#{url}'"
252
310
  else
253
311
  cmd << "'#{url}?#{params.map { |e| e.join("=") }.join("&")}'"
254
312
  end
255
313
 
256
- cmd.join(" ")
314
+ proxy_script = ""
315
+ unless proxy == "disable" || proxy.nil?
316
+ cmd << "-Proxy #{proxy[:uri]}"
317
+ cmd << "-ProxyCredential $proxyCreds"
318
+ proxy_script = <<-EOH
319
+ $secPasswd = ConvertTo-SecureString "#{proxy[:password]}" -AsPlainText -Force
320
+ $proxyCreds = New-Object System.Management.Automation.PSCredential -ArgumentList "#{proxy[:user]}",$secPasswd
321
+ EOH
322
+ end
323
+
324
+ command = cmd.join(" ")
325
+ body = "\'#{request_body}\'"
326
+ script = <<-EOH
327
+ $body = #{body.strip unless request_body.nil?}
328
+ $Body = $body | ConvertFrom-Json
329
+ #convert to hashtable
330
+ $HashTable = @{}
331
+ foreach ($property in $Body.PSObject.Properties) {
332
+ $HashTable[$property.Name] = $property.Value
333
+ }
334
+ $response = #{command} -Body $HashTable -UseBasicParsing
335
+ $response | Select-Object -Property * | ConvertTo-json # We use `Select-Object -Property * ` to get around an odd PowerShell error
336
+ EOH
337
+ proxy_script.strip + "\n" + script.strip
257
338
  end
258
339
  end
259
340
  end
@@ -46,12 +46,12 @@ module Inspec::Resources
46
46
 
47
47
  # check if following specific error is there. Sourcing the db2profile to resolve the error.
48
48
  if cmd.exit_status != 0 && out =~ /SQL10007N Message "-1390" could not be retrieved. Reason code: "3"/
49
- cmd = inspec.command(". ~/sqllib/db2profile\; #{@db2_executable_file_path} attach to #{@db_instance}\; #{@db2_executable_file_path} connect to #{@db_name}\; #{@db2_executable_file_path} #{q}\;")
49
+ cmd = inspec.command(". ~/sqllib/db2profile\; #{@db2_executable_file_path} attach to #{@db_instance}\; #{@db2_executable_file_path} connect to #{@db_name}\; #{@db2_executable_file_path} \"#{q}\"\;")
50
50
  out = cmd.stdout + "\n" + cmd.stderr
51
51
  end
52
52
  elsif inspec.os.platform?("windows")
53
53
  # set-item command set the powershell to run the db2 commands.
54
- cmd = inspec.command("set-item -path env:DB2CLP -value \"**$$**\"\; db2 connect to #{@db_name}\; db2 #{q}\;")
54
+ cmd = inspec.command("set-item -path env:DB2CLP -value \"**$$**\"\; db2 connect to #{@db_name}\; db2 \"#{q}\"\;")
55
55
  out = cmd.stdout + "\n" + cmd.stderr
56
56
  end
57
57
 
@@ -33,6 +33,7 @@ module Inspec::Resources
33
33
  def initialize(params = {})
34
34
  @table = params[:table]
35
35
  @chain = params[:chain]
36
+ @ignore_comments = params[:ignore_comments] || false
36
37
 
37
38
  # we're done if we are on linux
38
39
  return if inspec.os.linux?
@@ -59,8 +60,13 @@ module Inspec::Resources
59
60
  cmd = inspec.command(iptables_cmd)
60
61
  return [] if cmd.exit_status.to_i != 0
61
62
 
62
- # split rules, returns array or rules
63
- @iptables_cache = cmd.stdout.split("\n").map(&:strip)
63
+ if @ignore_comments
64
+ # split rules, returns array or rules without any comment
65
+ @iptables_cache = remove_comments_from_rules(cmd.stdout.split("\n"))
66
+ else
67
+ # split rules, returns array or rules
68
+ @iptables_cache = cmd.stdout.split("\n").map(&:strip)
69
+ end
64
70
  end
65
71
 
66
72
  def to_s
@@ -69,6 +75,16 @@ module Inspec::Resources
69
75
 
70
76
  private
71
77
 
78
+ def remove_comments_from_rules(rules)
79
+ rules.each do |rule|
80
+ next if rule.nil?
81
+
82
+ rule.gsub!(/ -m comment --comment "([^"]*)"/, "")
83
+ rule.strip
84
+ end
85
+ rules
86
+ end
87
+
72
88
  def find_iptables_or_error
73
89
  %w{/usr/sbin/iptables /sbin/iptables iptables}.each do |cmd|
74
90
  return cmd if inspec.command(cmd).exist?
@@ -0,0 +1,58 @@
1
+ module Inspec::Resources
2
+ class KernelParameters < Inspec.resource(1)
3
+ name "kernel_parameters"
4
+ supports platform: "unix"
5
+ desc "Use the kernel_parameters InSpec audit resource to test kernel parameters on Linux platforms."
6
+ example <<~EXAMPLE
7
+ describe kernel_parameters.where(parameter: /^net./ ) do
8
+ its('parameters') { should include 'net.ipv4.conf.all.forwarding' }
9
+ end
10
+
11
+ describe kernel_parameters.where(parameter: "net.ipv4.conf.all.forwarding") do
12
+ its('values') { should eq [0] }
13
+ end
14
+
15
+ describe kernel_parameters do
16
+ its('parameters') { should include 'net.ipv4.conf.all.forwarding' }
17
+ its('values') { should include 0 }
18
+ end
19
+ EXAMPLE
20
+
21
+ filter = FilterTable.create
22
+ filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
23
+ filter.register_column(:parameters, field: "parameter")
24
+ .register_column(:values, field: "value")
25
+ filter.install_filter_methods_on_resource(self, :params)
26
+
27
+ def initialize
28
+ # this resource is only supported on Linux
29
+ return skip_resource "The `kernel_parameters` resource is not supported on your OS." unless inspec.os.linux?
30
+ end
31
+
32
+ def to_s
33
+ "Kernel Parameters"
34
+ end
35
+
36
+ private
37
+
38
+ def params
39
+ cmd = inspec.command("/sbin/sysctl -a")
40
+ cmd.exit_status != 0 ? [] : parse_kernel_paramater(cmd.stdout)
41
+ end
42
+
43
+ def parse_kernel_paramater(stdout)
44
+ result = []
45
+ stdout.split("\n").each do |out|
46
+ splitted_output = out.split("=").map(&:strip)
47
+ result.push(
48
+ {
49
+ "parameter" => splitted_output[0],
50
+ "value" => splitted_output[1].to_i,
51
+ }
52
+ )
53
+ end
54
+ result
55
+ end
56
+
57
+ end
58
+ end
@@ -76,7 +76,7 @@ module Inspec::Resources
76
76
  if cmd.exit_status != 0 || out =~ /Sqlcmd: Error/
77
77
  raise Inspec::Exceptions::ResourceFailed, "Could not execute the sql query #{out}"
78
78
  else
79
- DatabaseHelper::SQLQueryResult.new(cmd, parse_csv_result(cmd))
79
+ DatabaseHelper::SQLQueryResult.new(cmd, parse_csv_result(cmd.stdout))
80
80
  end
81
81
  end
82
82
 
@@ -94,9 +94,17 @@ module Inspec::Resources
94
94
  !query("select getdate()").empty?
95
95
  end
96
96
 
97
- def parse_csv_result(cmd)
97
+ def parse_csv_result(stdout)
98
98
  require "csv" unless defined?(CSV)
99
- table = CSV.parse(cmd.stdout, headers: true)
99
+
100
+ # replaces \n with \r since multiline data in older versions of database returns faulty
101
+ # formatted multiline data, example name\r\n----\r\nThis is\na multiline field\r\n
102
+ out = stdout.gsub("\n", "\r")
103
+ out = out.gsub("\r\r", "\r")
104
+
105
+ # row separator used since row delimiters \n (in linux) or \r\n (in windows)
106
+ # are converted to \r for consistency and handling faulty formatted multiline data
107
+ table = CSV.parse(out, headers: true, row_sep: "\r")
100
108
 
101
109
  # remove first row, since it will be a seperator line
102
110
  table.delete(0)
@@ -118,7 +118,9 @@ module Inspec::Resources
118
118
  output = output.sub(/\r/, "").strip.gsub(",", "comma_query_sub")
119
119
  converter = ->(header) { header.downcase }
120
120
  CSV.parse(output, headers: true, header_converters: converter).map do |row|
121
- revised_row = row.entries.flatten.map { |entry| entry.gsub("comma_query_sub", ",") }
121
+ next if row.entries.flatten.empty?
122
+
123
+ revised_row = row.entries.flatten.map { |entry| entry&.gsub("comma_query_sub", ",") }
122
124
  Hashie::Mash.new([revised_row].to_h)
123
125
  end
124
126
  end
@@ -26,6 +26,7 @@ module Inspec::Resources
26
26
  @cache = nil
27
27
  # select package manager
28
28
  @pkgman = nil
29
+ @latest_version = nil
29
30
 
30
31
  os = inspec.os
31
32
  if os.debian?
@@ -60,6 +61,15 @@ module Inspec::Resources
60
61
  info[:installed] == true
61
62
  end
62
63
 
64
+ def latest?(_provider = nil, _version = nil)
65
+ os = inspec.os
66
+ if os.solaris? || (%w{hpux aix}.include? os[:family])
67
+ raise Inspec::Exceptions::ResourceSkipped, "The `be_latest` matcher is not supported on your OS yet."
68
+ end
69
+
70
+ (!info[:only_version_no].nil? && !latest_version.nil?) && (info[:only_version_no] == latest_version)
71
+ end
72
+
63
73
  # returns true it the package is held (if the OS supports it)
64
74
  def held?(_provider = nil, _version = nil)
65
75
  info[:held] == true
@@ -82,6 +92,10 @@ module Inspec::Resources
82
92
  info[:version]
83
93
  end
84
94
 
95
+ def latest_version
96
+ @latest_version ||= ( @pkgman.latest_version(@package_name) || info[:latest_version] )
97
+ end
98
+
85
99
  def to_s
86
100
  "System Package #{@package_name}"
87
101
  end
@@ -107,6 +121,21 @@ module Inspec::Resources
107
121
  # combined into a `ResourceSkipped` exception message.
108
122
  []
109
123
  end
124
+
125
+ private
126
+
127
+ def fetch_latest_version(cmd_string)
128
+ cmd = inspec.command(cmd_string)
129
+ if cmd.exit_status != 0
130
+ raise Inspec::Exceptions::ResourceFailed, "Failed to fetch latest version. Error: #{cmd.stderr}"
131
+ else
132
+ fetch_version_no(cmd.stdout)
133
+ end
134
+ end
135
+
136
+ def fetch_version_no(output)
137
+ output.scan(/(?:(?:\d+)[.]){2,}(?:\d+)/).max_by { |s| Gem::Version.new(s) } unless output.nil?
138
+ end
110
139
  end
111
140
 
112
141
  # Debian / Ubuntu
@@ -124,14 +153,21 @@ module Inspec::Resources
124
153
  # If the package is installed and marked hold, Status is "hold ok installed"
125
154
  # If the package is removed and not purged, Status is "deinstall ok config-files" with exit_status 0
126
155
  # If the package is purged cmd fails with non-zero exit status
156
+
127
157
  {
128
158
  name: params["Package"],
129
159
  installed: params["Status"].split(" ")[2] == "installed",
130
160
  held: params["Status"].split(" ")[0] == "hold",
131
161
  version: params["Version"],
132
162
  type: "deb",
163
+ only_version_no: fetch_version_no(params["Version"]),
133
164
  }
134
165
  end
166
+
167
+ def latest_version(package_name)
168
+ cmd_string = "apt list #{package_name} -a"
169
+ fetch_latest_version(cmd_string)
170
+ end
135
171
  end
136
172
 
137
173
  # RHEL family
@@ -181,9 +217,15 @@ module Inspec::Resources
181
217
  installed: true,
182
218
  version: "#{v}-#{r}",
183
219
  type: "rpm",
220
+ only_version_no: "#{v}",
184
221
  }
185
222
  end
186
223
 
224
+ def latest_version(package_name)
225
+ cmd_string = "yum list #{package_name}"
226
+ fetch_latest_version(cmd_string)
227
+ end
228
+
187
229
  private
188
230
 
189
231
  def rpm_command(package_name)
@@ -216,11 +258,17 @@ module Inspec::Resources
216
258
  installed: true,
217
259
  version: pkg["installed"][0]["version"],
218
260
  type: "brew",
261
+ latest_version: pkg["versions"]["stable"],
262
+ only_version_no: pkg["installed"][0]["version"],
219
263
  }
220
264
  rescue JSON::ParserError => e
221
265
  raise Inspec::Exceptions::ResourceFailed,
222
266
  "Failed to parse JSON from `brew` command. Error: #{e}"
223
267
  end
268
+
269
+ def latest_version(package_name)
270
+ nil
271
+ end
224
272
  end
225
273
 
226
274
  # Arch Linux
@@ -240,8 +288,14 @@ module Inspec::Resources
240
288
  installed: true,
241
289
  version: params["Version"],
242
290
  type: "pacman",
291
+ only_version_no: fetch_version_no(params["Version"]),
243
292
  }
244
293
  end
294
+
295
+ def latest_version(package_name)
296
+ cmd_string = "pacman -Ss #{package_name} | grep #{package_name} | grep installed"
297
+ fetch_latest_version(cmd_string)
298
+ end
245
299
  end
246
300
 
247
301
  class HpuxPkg < PkgManagement
@@ -267,13 +321,20 @@ module Inspec::Resources
267
321
  pkg_info = cmd.stdout.split("\n").delete_if { |e| e =~ /^WARNING/i }
268
322
  pkg = pkg_info[0].split(" - ")[0]
269
323
 
324
+ version = pkg.partition("-")[2]
270
325
  {
271
326
  name: pkg.partition("-")[0],
272
327
  installed: true,
273
- version: pkg.partition("-")[2],
328
+ version: version,
274
329
  type: "pkg",
330
+ only_version_no: fetch_version_no(version),
275
331
  }
276
332
  end
333
+
334
+ def latest_version(package_name)
335
+ cmd_string = "apk info #{package_name}"
336
+ fetch_latest_version(cmd_string)
337
+ end
277
338
  end
278
339
 
279
340
  class FreebsdPkg < PkgManagement
@@ -292,8 +353,14 @@ module Inspec::Resources
292
353
  installed: true,
293
354
  version: params["Version"],
294
355
  type: "pkg",
356
+ only_version_no: params["Version"],
295
357
  }
296
358
  end
359
+
360
+ def latest_version(package_name)
361
+ cmd_string = "pkg version -v | grep #{package_name}"
362
+ fetch_latest_version(cmd_string)
363
+ end
297
364
  end
298
365
 
299
366
  # Determines the installed packages on Windows using the Windows package registry entries.
@@ -339,8 +406,14 @@ module Inspec::Resources
339
406
  installed: true,
340
407
  version: package["DisplayVersion"],
341
408
  type: "windows",
409
+ only_version_no: package["DisplayVersion"],
342
410
  }
343
411
  end
412
+
413
+ def latest_version(package_name)
414
+ cmd_string = "Get-Package #{package_name} -AllVersions"
415
+ fetch_latest_version(cmd_string)
416
+ end
344
417
  end
345
418
 
346
419
  # AIX
@@ -26,6 +26,8 @@ module Inspec::Resources
26
26
  @pkgs = Debs.new(inspec)
27
27
  elsif os.redhat? || %w{suse amazon fedora}.include?(os[:family])
28
28
  @pkgs = Rpms.new(inspec)
29
+ elsif ["alpine"].include?(os[:name])
30
+ @pkgs = AlpinePkgs.new(inspec)
29
31
  else
30
32
  return skip_resource "The packages resource is not yet supported on OS #{inspec.os.name}"
31
33
  end
@@ -108,4 +110,23 @@ module Inspec::Resources
108
110
  end
109
111
  end
110
112
  end
113
+
114
+ # RedHat family
115
+ class AlpinePkgs < PkgsManagement
116
+ def build_package_list
117
+ command = "apk list --no-network --installed"
118
+ cmd = inspec.command(command)
119
+ all = cmd.stdout.split("\n")
120
+ return [] if all.nil? || cmd.exit_status.to_i != 0
121
+
122
+ all.map do |m|
123
+ next if m =~ /^WARNING/i
124
+
125
+ a = m.split(" ")
126
+ version = a[0].split("-")[-2]
127
+ name = a[2].gsub(/[{}^]*/, "")
128
+ PackageStruct.new("installed", name, version, a[1])
129
+ end
130
+ end
131
+ end
111
132
  end
@@ -105,6 +105,21 @@ module Inspec::Resources
105
105
  children_keys(@options[:path], filter)
106
106
  end
107
107
 
108
+ # returns hash containing users / groups and their permission
109
+ def user_permissions
110
+ return {} unless exists?
111
+
112
+ get_permissions(@options[:path])
113
+ end
114
+
115
+ # returns true if inheritance is enabled for registry key.
116
+ def inherited?
117
+ return false unless exists?
118
+
119
+ cmd = inspec.command("(Get-Acl -Path 'Registry::#{@options[:path]}').access| Where-Object {$_.IsInherited -eq $true} | measure | % { $_.Count }")
120
+ cmd.stdout.chomp == "0" ? false : true
121
+ end
122
+
108
123
  # returns nil, if not existent or value
109
124
  def method_missing(*keys)
110
125
  # allow the use of array syntax in an `its` block so that users
@@ -283,6 +298,21 @@ module Inspec::Resources
283
298
 
284
299
  key.start_with?("\\") ? key : "\\#{key}"
285
300
  end
301
+
302
+ def get_permissions(path)
303
+ script = <<~EOH
304
+ $path = '#{path}'
305
+ $Acl = Get-Acl -Path ('Registry::' + $path)
306
+ $Result = foreach ($Access in $acl.Access) {
307
+ [PSCustomObject]@{
308
+ $Access.IdentityReference = $Access.RegistryRights.ToString()
309
+ }
310
+ }
311
+ $Result | ConvertTo-Json
312
+ EOH
313
+ result = inspec.powershell(script)
314
+ JSON.load(result.stdout).inject(&:merge) unless result.stdout.empty?
315
+ end
286
316
  end
287
317
 
288
318
  class WindowsRegistryKey < RegistryKey