inspec-core 4.49.0 → 4.56.17

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