inspec-core 4.52.9 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5fc9c31a9983866ff40fb11326a3c2bac04342e1d473b845e81639bbca88a8c
4
- data.tar.gz: 6fd55b54b5c1510572fa963d2d4833a8f3132ae5cbfb1871a4662f8b3da720d9
3
+ metadata.gz: 9930c475b5a1e282ab613db458f60c77561db82d6c009d6c7254f5a3789132f5
4
+ data.tar.gz: 8a11ae471b5954568be5bf6fc88e52d974a5ac9cb83caf2a6560dd3dd26d55fe
5
5
  SHA512:
6
- metadata.gz: d9f356b30301430dbeeeb599b696aacfc4ec1caaadafd61e6af40462db60fa400cd2bf4add6a682fe379c65ed4adb0f926a858e6b072066f06296821725f7f92
7
- data.tar.gz: 25381065952aa3d460cedd9c56a5b389625b2ed11841f2085047e81444c3aa1d788338468c640f5c27403b4074a5649b825a3289e0b63cdc96d70e8450cc664c
6
+ metadata.gz: 0106f09ddcc1077650b6781631188dcb4231829b4e2b737680f49c6736b3c08001523d34a9398eb7499147dde9d22c4bbdd9058721c484424fe69933535e7557
7
+ data.tar.gz: b04b2c02c6d1009cee30a5ca32521d4c62e62fcf72727d0f720af5fa54da01029dbcd1b25656460f73d9d4cb7f3bf3b94b7e1f049479770a0a4848aa1f25034f
data/Gemfile CHANGED
@@ -11,11 +11,6 @@ gem "inspec-bin", path: "./inspec-bin"
11
11
 
12
12
  gem "ffi", ">= 1.9.14", "!= 1.13.0", "!= 1.14.2"
13
13
 
14
- if Gem.ruby_version.to_s.start_with?("2.5")
15
- # 16.7.23 required ruby 2.6+
16
- gem "chef-utils", "< 16.7.23" # TODO: remove when we drop ruby 2.5
17
- end
18
-
19
14
  # inspec tests depend text output that changed in the 3.10 release
20
15
  # but our runtime dep is still 3.9+
21
16
  gem "rspec", ">= 3.10"
@@ -30,11 +25,7 @@ end
30
25
  group :test do
31
26
  gem "chefstyle", "~> 2.0.3"
32
27
  gem "concurrent-ruby", "~> 1.0"
33
- if Gem.ruby_version.to_s.start_with?("2.5")
34
- gem "html-proofer", "= 3.19.1" , platforms: :ruby # do not attempt to run proofer on windows
35
- else
36
- gem "html-proofer", platforms: :ruby # do not attempt to run proofer on windows
37
- end
28
+ gem "html-proofer", platforms: :ruby # do not attempt to run proofer on windows
38
29
  gem "json_schemer", ">= 0.2.1", "< 0.2.19"
39
30
  gem "m"
40
31
  gem "minitest-sprint", "~> 1.0"
@@ -45,7 +36,8 @@ group :test do
45
36
  gem "pry", "~> 0.10"
46
37
  gem "rake", ">= 10"
47
38
  gem "ruby-progressbar", "~> 1.8"
48
- gem "simplecov", "~> 0.18"
39
+ gem "simplecov", "~> 0.21"
40
+ gem "simplecov_json_formatter"
49
41
  gem "webmock", "~> 3.0"
50
42
  end
51
43
 
data/inspec-core.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
13
13
  spec.license = "Apache-2.0"
14
14
  spec.require_paths = ["lib"]
15
15
 
16
- spec.required_ruby_version = ">= 2.5"
16
+ spec.required_ruby_version = ">= 2.6"
17
17
 
18
18
  # the gemfile and gemspec are necessary for appbundler so don't remove it
19
19
  spec.files =
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency "thor", ">= 0.20", "< 2.0"
29
29
  spec.add_dependency "method_source", ">= 0.8", "< 2.0"
30
30
  spec.add_dependency "rubyzip", ">= 1.2.2", "< 3.0"
31
- spec.add_dependency "rspec", ">= 3.9", "< 3.11"
31
+ spec.add_dependency "rspec", ">= 3.9", "<= 3.11"
32
32
  spec.add_dependency "rspec-its", "~> 1.2"
33
33
  spec.add_dependency "pry", "~> 0.13"
34
34
  spec.add_dependency "hashie", ">= 3.4", "< 5.0"
data/lib/inspec/config.rb CHANGED
@@ -367,7 +367,11 @@ module Inspec
367
367
  .find_activators(plugin_type: :reporter)\
368
368
  .map(&:activator_name).map(&:to_s)
369
369
 
370
- valid_types = rspec_built_in_formatters + inspec_reporters_that_are_not_yet_plugins + plugin_reporters
370
+ streaming_reporters = Inspec::Plugin::V2::Registry.instance\
371
+ .find_activators(plugin_type: :streaming_reporter)\
372
+ .map(&:activator_name).map(&:to_s)
373
+
374
+ valid_types = rspec_built_in_formatters + inspec_reporters_that_are_not_yet_plugins + plugin_reporters + streaming_reporters
371
375
 
372
376
  reporters.each do |reporter_name, reporter_config|
373
377
  raise NotImplementedError, "'#{reporter_name}' is not a valid reporter type." unless valid_types.include?(reporter_name)
@@ -102,7 +102,8 @@ module Inspec
102
102
  end
103
103
 
104
104
  def fetcher
105
- @fetcher ||= Inspec::CachedFetcher.new(opts, @cache)
105
+ @runner_options ||= (Inspec::Config.cached || {})
106
+ @fetcher ||= Inspec::CachedFetcher.new(opts, @cache, @runner_options)
106
107
  end
107
108
 
108
109
  # load dependencies of the dependency
@@ -70,6 +70,7 @@ module Inspec::Formatters
70
70
  name: platform(:name),
71
71
  release: platform(:release),
72
72
  target: backend_target,
73
+ target_id: platform(:uuid),
73
74
  }
74
75
  end
75
76
 
@@ -205,12 +206,13 @@ module Inspec::Formatters
205
206
  def platform(field)
206
207
  return nil if @backend.nil?
207
208
 
208
- begin
209
- @backend.platform[field]
210
- rescue Train::Error => e
211
- Inspec::Log.warn(e.message)
212
- nil
213
- end
209
+ @backend.platform[field]
210
+ rescue Train::PlatformUuidDetectionFailed
211
+ Inspec::Log.warn("Could not find platform target_id.")
212
+ nil
213
+ rescue Train::Error => e
214
+ Inspec::Log.warn(e.message)
215
+ nil
214
216
  end
215
217
 
216
218
  def backend_target
@@ -30,6 +30,8 @@ module Inspec
30
30
 
31
31
  c3 = Class.new do
32
32
  include Inspec::DSL::RequireOverride
33
+ include Inspec::Resources
34
+
33
35
  def initialize(require_loader)
34
36
  @require_loader = require_loader
35
37
  @inspec_binding = nil
@@ -0,0 +1,10 @@
1
+ module Inspec::Plugin::V2::PluginType
2
+ class StreamingReporter < Inspec::Plugin::V2::PluginBase # TBD Superclass may need to change
3
+ register_plugin_type(:streaming_reporter)
4
+
5
+ #====================================================================#
6
+ # StreamingReporter plugin type API
7
+ #====================================================================#
8
+ # Implementation classes must implement these methods.
9
+ end
10
+ end
@@ -68,6 +68,7 @@ module Inspec
68
68
  end
69
69
 
70
70
  def reload_dsl
71
+ @resource_registry.merge!(Inspec::Resource.new_registry)
71
72
  @control_eval_context = nil
72
73
  end
73
74
 
@@ -263,9 +264,3 @@ module Inspec
263
264
  end # DomainSpecificLunacy
264
265
  end # ProfileContext
265
266
  end
266
-
267
- if RUBY_VERSION < "2.5"
268
- class Module
269
- public :define_method
270
- end
271
- end
@@ -21,7 +21,7 @@ module Inspec::Reporters
21
21
  final_report[:type] = "inspec_report"
22
22
 
23
23
  final_report[:end_time] = Time.now.utc.strftime("%FT%TZ")
24
- final_report[:node_uuid] = @config["node_uuid"] || @config["target_id"]
24
+ final_report[:node_uuid] = report[:platform][:target_id] || @config["node_uuid"] || @config["target_id"]
25
25
  raise Inspec::ReporterError, "Cannot find a UUID for your node. Please specify one via json-config." if final_report[:node_uuid].nil?
26
26
 
27
27
  final_report[:report_uuid] = @config["report_uuid"] || uuid_from_string(final_report[:end_time] + final_report[:node_uuid])
@@ -29,7 +29,7 @@ module Inspec::Reporters
29
29
  {
30
30
  name: run_data[:platform][:name],
31
31
  release: run_data[:platform][:release],
32
- target_id: @config["target_id"],
32
+ target_id: run_data[:platform][:target_id] || @config["target_id"],
33
33
  }.reject { |_k, v| v.nil? }
34
34
  end
35
35
 
@@ -5,6 +5,8 @@ module Inspec::Resources
5
5
  class Bash < Cmd
6
6
  name "bash"
7
7
  supports platform: "unix"
8
+ supports platform: "esx"
9
+
8
10
  desc "Run a command or script in BASH."
9
11
  example <<~EXAMPLE
10
12
  describe bash('ls -al /') do
@@ -61,6 +61,24 @@ module Inspec::Resources
61
61
  res.force_encoding("utf-8")
62
62
  end
63
63
 
64
+ # returns hash containing list of users/groups and their file permissions.
65
+ def user_permissions
66
+ return {} unless exist?
67
+
68
+ return skip_reource"`user_permissions` is not supported on your OS yet." unless inspec.os.windows?
69
+
70
+ @perms_provider.user_permissions(file)
71
+ end
72
+
73
+ # returns true if inheritance is enabled on file or folder
74
+ def inherited?
75
+ return false unless exist?
76
+
77
+ return skip_resource "`inherited?` is not supported on your OS yet." unless inspec.os.windows?
78
+
79
+ @perms_provider.inherited?(file)
80
+ end
81
+
64
82
  def contain(*_)
65
83
  raise "Contain is not supported. Please use standard RSpec matchers."
66
84
  end
@@ -244,6 +262,26 @@ module Inspec::Resources
244
262
  end
245
263
 
246
264
  class WindowsFilePermissions < FilePermissions
265
+
266
+ def user_permissions(file)
267
+ script = <<-EOH
268
+ $Acl = Get-Acl -Path #{file.path}
269
+ $Result = foreach ($Access in $acl.Access) {
270
+ [PSCustomObject]@{
271
+ $Access.IdentityReference.Value = $Access.FileSystemRights.ToString()
272
+ }
273
+ }
274
+ $Result | ConvertTo-Json
275
+ EOH
276
+ result = inspec.powershell(script)
277
+ JSON.load(result.stdout).inject(&:merge) unless result.stdout.empty?
278
+ end
279
+
280
+ def inherited?(file)
281
+ cmd = inspec.command("(Get-Acl -Path #{file.path}).access| Where-Object {$_.IsInherited -eq $true} | measure | % { $_.Count }")
282
+ cmd.stdout.chomp == "0" ? false : true
283
+ end
284
+
247
285
  def check_file_permission_by_mask(_file, _access_type, _usergroup, _specific_user)
248
286
  raise "`check_file_permission_by_mask` is not supported on Windows"
249
287
  end
@@ -32,6 +32,17 @@ module Inspec::Resources
32
32
  .register_column(:interfaces, field: "interfaces")
33
33
  .register_column(:sources, field: "sources")
34
34
  .register_column(:services, field: "services")
35
+ .register_column(:target, field: "target")
36
+ .register_column(:ports, field: "ports")
37
+ .register_column(:protocols, field: "protocols")
38
+ .register_column(:forward_ports, field: "forward_ports")
39
+ .register_column(:source_ports, field: "source_ports")
40
+ .register_column(:icmp_blocks, field: "icmp_blocks")
41
+ .register_column(:rich_rules, field: "rich_rules")
42
+ .register_custom_matcher(:icmp_block_inversion?) { |x| x.params[0]["icmp_block_inversion"] }
43
+ .register_custom_matcher(:has_icmp_block_inversion_enabled?) { |x| x.params[0]["icmp_block_inversion"] }
44
+ .register_custom_matcher(:masquerade?) { |x| x.params[0]["masquerade"] }
45
+ .register_custom_matcher(:has_masquerade_enabled?) { |x| x.params[0]["masquerade"] }
35
46
 
36
47
  filter.install_filter_methods_on_resource(self, :params)
37
48
 
@@ -64,28 +75,28 @@ module Inspec::Resources
64
75
  end
65
76
 
66
77
  def has_service_enabled_in_zone?(query_service, query_zone = default_zone)
67
- firewalld_command("--zone=#{query_zone} --query-service=#{query_service}") == "yes"
78
+ firewalld_command("--permanent --zone=#{query_zone} --query-service=#{query_service}") == "yes"
68
79
  end
69
80
 
70
81
  def service_ports_enabled_in_zone(query_service, query_zone = default_zone)
71
82
  # return: String of ports open
72
83
  # example: ['22/tcp', '4722/tcp']
73
- firewalld_command("--zone=#{query_zone} --service=#{query_service} --get-ports --permanent").split(" ")
84
+ firewalld_command("--permanent --zone=#{query_zone} --service=#{query_service} --get-ports").split(" ")
74
85
  end
75
86
 
76
87
  def service_protocols_enabled_in_zone(query_service, query_zone = default_zone)
77
- # return: String of protocoals open
88
+ # return: String of protocols open
78
89
  # example: ['icmp', 'ipv4', 'igmp']
79
- firewalld_command("--zone=#{query_zone} --service=#{query_service} --get-protocols --permanent").split(" ")
90
+ firewalld_command("--permanent --zone=#{query_zone} --service=#{query_service} --get-protocols").split(" ")
80
91
  end
81
92
 
82
93
  def has_port_enabled_in_zone?(query_port, query_zone = default_zone)
83
- firewalld_command("--zone=#{query_zone} --query-port=#{query_port}") == "yes"
94
+ firewalld_command("--permanent --zone=#{query_zone} --query-port=#{query_port}") == "yes"
84
95
  end
85
96
 
86
97
  def has_rule_enabled?(rule, query_zone = default_zone)
87
98
  rule = "rule #{rule}" unless rule.start_with?("rule")
88
- firewalld_command("--zone=#{query_zone} --query-rich-rule='#{rule}'") == "yes"
99
+ firewalld_command("--permanent --zone=#{query_zone} --query-rich-rule='#{rule}'") == "yes"
89
100
  end
90
101
 
91
102
  def to_s
@@ -120,19 +131,82 @@ module Inspec::Resources
120
131
  "interfaces" => line.split(":")[1].split(" "),
121
132
  "services" => services_bound(zone),
122
133
  "sources" => sources_bound(zone),
134
+ "target" => target_bound(zone),
135
+ "icmp_block_inversion" => icmp_block_inversion_bound?(zone),
136
+ "ports" => ports_bound(zone),
137
+ "protocols" => protocols_bound(zone),
138
+ "masquerade" => masquerade_bound?(zone),
139
+ "forward_ports" => forward_ports_bound(zone),
140
+ "source_ports" => source_ports_bound(zone),
141
+ "icmp_blocks" => icmp_blocks_bound(zone),
142
+ "rich_rules" => rich_rules_bound(zone),
123
143
  }
124
144
  end
125
145
 
146
+ def target_bound(query_zone)
147
+ # result: a target bound for the zone
148
+ # example: 'DROP'
149
+ firewalld_command("--permanent --zone=#{query_zone} --get-target").strip
150
+ end
151
+
152
+ def icmp_block_inversion_bound?(query_zone)
153
+ # result: true/false whether inversion of icmp blocks has been enabled for a zone
154
+ # example: true
155
+ firewalld_command("--permanent --zone=#{query_zone} --query-icmp-block-inversion") == "yes"
156
+ end
157
+
158
+ def ports_bound(query_zone)
159
+ # result: a list of ports bound for a zone
160
+ # example: ['80/tcp', '443/tcp']
161
+ firewalld_command("--permanent --zone=#{query_zone} --list-ports").split(" ")
162
+ end
163
+
164
+ def protocols_bound(query_zone)
165
+ # result: a list of protocols added for a zone
166
+ # example: ['icmp', 'ipv4', 'igmp']
167
+ firewalld_command("--permanent --zone=#{query_zone} --list-protocols").split(" ")
168
+ end
169
+
170
+ def masquerade_bound?(query_zone)
171
+ # result: true/false whether IPv4 masquerading has been enabled for a zone
172
+ # example: true
173
+ firewalld_command("--permanent --zone=#{query_zone} --query-masquerade") == "yes"
174
+ end
175
+
176
+ def forward_ports_bound(query_zone)
177
+ # result: a list of IPv4 forward ports bound to a zone
178
+ # example: ['port=80:proto=tcp:toport=88', 'port=12345:proto=tcp:toport=54321:toaddr=192.168.1.3']
179
+ firewalld_command("--permanent --zone=#{query_zone} --list-forward-ports").split("\n")
180
+ end
181
+
182
+ def source_ports_bound(query_zone)
183
+ # result: a list of source ports bound to a zone
184
+ # example: ['80/tcp', '8080/tcp']
185
+ firewalld_command("--permanent --zone=#{query_zone} --list-source-ports").split(" ")
186
+ end
187
+
188
+ def icmp_blocks_bound(query_zone)
189
+ # result: a list of internet ICMP type blocks bound to a zone
190
+ # example: ['echo-request', 'echo-reply']
191
+ firewalld_command("--permanent --zone=#{query_zone} --list-icmp-blocks").split(" ")
192
+ end
193
+
194
+ def rich_rules_bound(query_zone)
195
+ # result: a list of rich language rules bound to a zone
196
+ # example: ['rule protocol value="ah" accept', 'rule service name="ftp" log limit value="1/m" audit accept']
197
+ firewalld_command("--permanent --zone=#{query_zone} --list-rich-rules").split("\n")
198
+ end
199
+
126
200
  def sources_bound(query_zone)
127
201
  # result: a list containing either an ip address or ip address with a mask, or a ipset or an ipset with the ipset prefix.
128
202
  # example: ['192.168.0.4', '192.168.0.0/16', '2111:DB28:ABC:12::', '2111:db89:ab3d:0112::0/64']
129
- firewalld_command("--zone=#{query_zone} --list-sources").split(" ")
203
+ firewalld_command("--permanent --zone=#{query_zone} --list-sources").split(" ")
130
204
  end
131
205
 
132
206
  def services_bound(query_zone)
133
207
  # result: a list of services bound to a zone.
134
208
  # example: ['ssh', 'dhcpv6-client']
135
- firewalld_command("--zone=#{query_zone} --list-services").split(" ")
209
+ firewalld_command("--permanent --zone=#{query_zone} --list-services").split(" ")
136
210
  end
137
211
 
138
212
  def firewalld_command(command)
@@ -145,4 +219,4 @@ module Inspec::Resources
145
219
  result.stdout.strip
146
220
  end
147
221
  end
148
- end
222
+ end
@@ -162,7 +162,7 @@ module Inspec::Resources
162
162
 
163
163
  current_kernel = file_line.split(" ", 2)[1]
164
164
  lines.drop(index + 1).each do |kernel_line|
165
- if kernel_line =~ /^\s.*/
165
+ if kernel_line =~ /(?:^\s*\w+)/ && !(kernel_line =~ /^title.*/)
166
166
  option_type = kernel_line.split(" ")[0]
167
167
  line_options = kernel_line.split(" ").drop(1)
168
168
  if (menu_entry == conf["default"].to_i && @kernel == "default") || current_kernel == @kernel
@@ -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)
@@ -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
@@ -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
@@ -84,8 +84,13 @@ module Inspec::Resources
84
84
 
85
85
  def initialize(selinux_path = "/etc/selinux/config")
86
86
  @path = selinux_path
87
- cmd = inspec.command("sestatus")
87
+ if inspec.os.redhat? && inspec.os.name == "amazon"
88
+ lcmd = "/usr/sbin/sestatus"
89
+ else
90
+ lcmd = "sestatus"
91
+ end
88
92
 
93
+ cmd = inspec.command(lcmd)
89
94
  if cmd.exit_status != 0
90
95
  # `sestatus` command not found error message comes in stdout so handling both here
91
96
  out = cmd.stdout + "\n" + cmd.stderr
@@ -0,0 +1,65 @@
1
+ require "inspec/resources/command"
2
+
3
+ module Inspec::Resources
4
+ class TimeZone < Cmd
5
+ name "timezone"
6
+ supports platform: "unix"
7
+ supports platform: "windows"
8
+
9
+ desc "Check for timezone configurations"
10
+ example <<~EXAMPLE
11
+ describe timezone do
12
+ its('identifier') { should eq 'Asia/Kolkata' }
13
+ its('name') { should eq 'IST' }
14
+ its('time_offset') { should eq '+0530' }
15
+ end
16
+ EXAMPLE
17
+
18
+ def initialize
19
+ @output = {}
20
+ os = inspec.os
21
+ cmd = if os.windows?
22
+ inspec.command("Get-TimeZone")
23
+ else
24
+ inspec.command("timedatectl status | grep -i 'Time zone'")
25
+ end
26
+ if cmd.exit_status != 0
27
+ raise Inspec::Exceptions::ResourceFailed, "Time Zone resource with error: #{cmd.stderr}"
28
+ else
29
+ if os.windows?
30
+ splitted_output = cmd.stdout.strip.gsub(/\r/, "").split("\n").select { |out| (out.include? "Id") || (out.include? "DisplayName") || (out.include? "BaseUtcOffset") }
31
+ @output["identifier"] = split_and_fetch_last(splitted_output[1])
32
+ @output["name"] = split_and_fetch_last(splitted_output[0])
33
+ @output["time_offset"] = split_and_fetch_last(splitted_output[2])
34
+ else
35
+ splitted_output = cmd.stdout.split(":")[-1]&.strip&.gsub(/[(),^]*/, "")&.split(" ") || []
36
+ @output["identifier"] = splitted_output[0]
37
+ @output["name"] = splitted_output[1]
38
+ @output["time_offset"] = splitted_output[2]
39
+ end
40
+ end
41
+ end
42
+
43
+ def identifier
44
+ @output["identifier"]
45
+ end
46
+
47
+ def name
48
+ @output["name"]
49
+ end
50
+
51
+ def time_offset
52
+ @output["time_offset"]
53
+ end
54
+
55
+ def to_s
56
+ "Time Zone resource"
57
+ end
58
+
59
+ private
60
+
61
+ def split_and_fetch_last(string_value)
62
+ string_value.split(" :")[-1].strip
63
+ end
64
+ end
65
+ end
@@ -41,6 +41,7 @@ require "inspec/resources/cassandradb_session"
41
41
  require "inspec/resources/cassandradb_conf"
42
42
  require "inspec/resources/cassandra"
43
43
  require "inspec/resources/crontab"
44
+ require "inspec/resources/timezone"
44
45
  require "inspec/resources/dh_params"
45
46
  require "inspec/resources/directory"
46
47
  require "inspec/resources/docker"
@@ -72,6 +73,7 @@ require "inspec/resources/ip6tables"
72
73
  require "inspec/resources/iptables"
73
74
  require "inspec/resources/kernel_module"
74
75
  require "inspec/resources/kernel_parameter"
76
+ require "inspec/resources/kernel_parameters"
75
77
  require "inspec/resources/key_rsa"
76
78
  require "inspec/resources/ksh"
77
79
  require "inspec/resources/limits_conf"
@@ -123,6 +123,8 @@ module Inspec
123
123
  def set_optional_formatters
124
124
  return if @conf["reporter"].nil?
125
125
 
126
+ # This is a slightly modified version of the default RSpec JSON formatter
127
+ # No one in their right mind should be using this because we have a much better JSON reporter - named "json"
126
128
  if @conf["reporter"].key?("json-rspec")
127
129
  # We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
128
130
  if @conf["reporter"]["json-rspec"]&.[]("file").nil?
@@ -133,6 +135,7 @@ module Inspec
133
135
  @conf["reporter"].delete("json-rspec")
134
136
  end
135
137
 
138
+ # These are built-in to rspec
136
139
  formats = @conf["reporter"].select { |k, _v| %w{documentation progress html}.include?(k) }
137
140
  formats.each do |k, v|
138
141
  # We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
@@ -143,6 +146,33 @@ module Inspec
143
146
  end
144
147
  @conf["reporter"].delete(k)
145
148
  end
149
+
150
+ # Here we need to look for reporter names in the reporter option that
151
+ # are names of streaming reporter plugins. We load them, then tell RSpec to add them as formatters.
152
+ # They will have already been detected at this point (see v2_loader.load_all in cli.rb)
153
+ # but they will not be activated activated at this point.
154
+ # then list all plugins by type by name
155
+ reg = Inspec::Plugin::V2::Registry.instance
156
+ streaming_reporters = reg\
157
+ .find_activators(plugin_type: :streaming_reporter)\
158
+ .map(&:activator_name).map(&:to_s)
159
+
160
+ @conf["reporter"].each do |streaming_reporter_name, file_target|
161
+ # It could be a non-streaming reporter
162
+ next unless streaming_reporters.include? streaming_reporter_name
163
+
164
+ # Activate the plugin so the formatter ID gets registered with RSpec, presumably
165
+ activator = reg.find_activator(plugin_type: :streaming_reporter, activator_name: streaming_reporter_name.to_sym)
166
+ activator.activate!
167
+
168
+ # We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
169
+ if file_target&.[]("file").nil?
170
+ RSpec.configuration.add_formatter(activator.implementation_class)
171
+ else
172
+ RSpec.configuration.add_formatter(activator.implementation_class, file_target["file"])
173
+ end
174
+ @conf["reporter"].delete(streaming_reporter_name)
175
+ end
146
176
  end
147
177
 
148
178
  # Configure the output formatter and stream to be used with RSpec.
@@ -114,6 +114,7 @@ module FilterTable
114
114
  raise(ArgumentError, "'#{decorate_symbols(raw_field_name)}' is not a recognized criterion - expected one of #{decorate_symbols(list_fields).join(", ")}'") unless field?(raw_field_name)
115
115
 
116
116
  populate_lazy_field(raw_field_name, desired_value) if is_field_lazy?(raw_field_name)
117
+ populate_lazy_instance_field(raw_field_name, desired_value) if is_field_lazy_instance?(raw_field_name)
117
118
  new_criteria_string += " #{raw_field_name} == #{desired_value.inspect}"
118
119
  filtered_raw_data = filter_raw_data(filtered_raw_data, raw_field_name, desired_value)
119
120
  end
@@ -188,6 +189,8 @@ module FilterTable
188
189
  is_field ||= list_fields.include?(proposed_field.to_sym)
189
190
  is_field ||= is_field_lazy?(proposed_field.to_s)
190
191
  is_field ||= is_field_lazy?(proposed_field.to_sym)
192
+ is_field ||= is_field_lazy_instance?(proposed_field.to_s)
193
+ is_field ||= is_field_lazy_instance?(proposed_field.to_sym)
191
194
 
192
195
  is_field
193
196
  end
@@ -210,6 +213,23 @@ module FilterTable
210
213
  mark_lazy_field_populated(field_name)
211
214
  end
212
215
 
216
+ def populate_lazy_instance_field(field_name, criterion)
217
+ return unless is_field_lazy_instance?(field_name)
218
+ return if field_populated?(field_name)
219
+
220
+ raw_data.each do |row|
221
+ next if row.key?(field_name) # skip row if pre-existing data is present
222
+
223
+ lazy_caller = callback_for_lazy_instance_field(field_name)
224
+ if lazy_caller.is_a?(Proc)
225
+ lazy_caller.call(row, criterion, resource_instance)
226
+ elsif lazy_caller.is_a?(Symbol)
227
+ resource_instance.send(lazy_caller, row, criterion, self)
228
+ end
229
+ end
230
+ mark_lazy_field_populated(field_name)
231
+ end
232
+
213
233
  def is_field_lazy?(sought_field_name)
214
234
  custom_properties_schema.values.any? do |property_struct|
215
235
  sought_field_name == property_struct.field_name && \
@@ -217,6 +237,13 @@ module FilterTable
217
237
  end
218
238
  end
219
239
 
240
+ def is_field_lazy_instance?(sought_field_name)
241
+ custom_properties_schema.values.any? do |property_struct|
242
+ sought_field_name == property_struct.field_name && \
243
+ property_struct.opts[:lazy_instance]
244
+ end
245
+ end
246
+
220
247
  def callback_for_lazy_field(field_name)
221
248
  return unless is_field_lazy?(field_name)
222
249
 
@@ -225,6 +252,14 @@ module FilterTable
225
252
  end.opts[:lazy]
226
253
  end
227
254
 
255
+ def callback_for_lazy_instance_field(field_name)
256
+ return unless is_field_lazy_instance?(field_name)
257
+
258
+ custom_properties_schema.values.find do |property_struct|
259
+ property_struct.field_name == field_name
260
+ end.opts[:lazy_instance]
261
+ end
262
+
228
263
  def field_populated?(field_name)
229
264
  @populated_lazy_columns[field_name]
230
265
  end
@@ -349,12 +384,18 @@ module FilterTable
349
384
  # args of the row struct; also the Struct class will already have provided
350
385
  # a setter for each field.
351
386
  @custom_properties.values.each do |property_info|
352
- next unless property_info.opts[:lazy]
387
+ next unless property_info.opts[:lazy] || property_info.opts[:lazy_instance]
353
388
 
354
389
  field_name = property_info.field_name.to_sym
355
390
  row_eval_context_type.send(:define_method, field_name) do
356
391
  unless filter_table.field_populated?(field_name)
357
- filter_table.populate_lazy_field(field_name, NoCriteriaProvided) # No access to criteria here
392
+ if property_info.opts[:lazy]
393
+ filter_table.populate_lazy_field(field_name, NoCriteriaProvided)
394
+ end # No access to criteria here
395
+ if property_info.opts[:lazy_instance]
396
+ filter_table.populate_lazy_instance_field(field_name,
397
+ NoCriteriaProvided)
398
+ end
358
399
  # OK, the underlying raw data has the value in the first row
359
400
  # (because we would trigger population only on the first row)
360
401
  # We could just return the value, but we need to set it on this Struct in case it is referenced multiple times
@@ -449,7 +490,10 @@ module FilterTable
449
490
  result = where(nil)
450
491
  if custom_property_struct.opts[:lazy]
451
492
  result.populate_lazy_field(custom_property_struct.field_name, filter_criteria_value)
493
+ elsif custom_property_struct.opts[:lazy_instance]
494
+ result.populate_lazy_instance_field(custom_property_struct.field_name, filter_criteria_value)
452
495
  end
496
+
453
497
  result = where(nil).get_column_values(custom_property_struct.field_name) # TODO: the where(nil). is likely unneeded
454
498
  result = result.flatten.uniq.compact if custom_property_struct.opts[:style] == :simple
455
499
  result
@@ -65,7 +65,7 @@ module Inspec
65
65
  c[:results]&.each do |r|
66
66
  next unless r[:message] # :message only set on failure
67
67
 
68
- pos = r[:message].index("\n\nDiff:")
68
+ pos = r[:message].index(/\n{1,2}Diff.*:/)
69
69
  next unless pos # Only textual tests get Diffs
70
70
 
71
71
  r[:message] = r[:message].slice(0, pos)
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "4.52.9".freeze
2
+ VERSION = "4.56.17".freeze
3
3
  end
@@ -84,7 +84,7 @@ module InspecPlugins
84
84
  return {} if data.nil? || data.empty?
85
85
 
86
86
  parsed = JSON.parse(data)
87
- return {} unless parsed.key?("version") && !parsed["version"].empty?
87
+ return {} unless parsed.key?("build_timestamp") && !parsed["build_timestamp"].empty?
88
88
 
89
89
  parsed
90
90
  end
@@ -219,9 +219,10 @@ module InspecPlugins
219
219
  def version
220
220
  config = InspecPlugins::Compliance::Configuration.new
221
221
  info = InspecPlugins::Compliance::API.version(config)
222
- if !info.nil? && info["version"]
223
- puts "Name: #{info["api"]}"
224
- puts "Version: #{info["version"]}"
222
+ if !info.nil? && info["build_timestamp"]
223
+ # key info["api"] is not longer available in latest version api response
224
+ puts "Name: automate"
225
+ puts "Version: #{info["build_timestamp"]}"
225
226
  else
226
227
  puts "Could not determine server version."
227
228
  exit 1
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.52.9
4
+ version: 4.56.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef InSpec Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-20 00:00:00.000000000 Z
11
+ date: 2022-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef-telemetry
@@ -117,7 +117,7 @@ dependencies:
117
117
  - - ">="
118
118
  - !ruby/object:Gem::Version
119
119
  version: '3.9'
120
- - - "<"
120
+ - - "<="
121
121
  - !ruby/object:Gem::Version
122
122
  version: '3.11'
123
123
  type: :runtime
@@ -127,7 +127,7 @@ dependencies:
127
127
  - - ">="
128
128
  - !ruby/object:Gem::Version
129
129
  version: '3.9'
130
- - - "<"
130
+ - - "<="
131
131
  - !ruby/object:Gem::Version
132
132
  version: '3.11'
133
133
  - !ruby/object:Gem::Dependency
@@ -479,6 +479,7 @@ files:
479
479
  - lib/inspec/plugin/v2/plugin_types/input.rb
480
480
  - lib/inspec/plugin/v2/plugin_types/mock.rb
481
481
  - lib/inspec/plugin/v2/plugin_types/reporter.rb
482
+ - lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb
482
483
  - lib/inspec/plugin/v2/registry.rb
483
484
  - lib/inspec/plugin/v2/status.rb
484
485
  - lib/inspec/profile.rb
@@ -554,6 +555,7 @@ files:
554
555
  - lib/inspec/resources/json.rb
555
556
  - lib/inspec/resources/kernel_module.rb
556
557
  - lib/inspec/resources/kernel_parameter.rb
558
+ - lib/inspec/resources/kernel_parameters.rb
557
559
  - lib/inspec/resources/key_rsa.rb
558
560
  - lib/inspec/resources/ksh.rb
559
561
  - lib/inspec/resources/launchd_service.rb
@@ -619,6 +621,7 @@ files:
619
621
  - lib/inspec/resources/sys_info.rb
620
622
  - lib/inspec/resources/systemd_service.rb
621
623
  - lib/inspec/resources/sysv_service.rb
624
+ - lib/inspec/resources/timezone.rb
622
625
  - lib/inspec/resources/toml.rb
623
626
  - lib/inspec/resources/upstart_service.rb
624
627
  - lib/inspec/resources/user.rb
@@ -788,7 +791,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
788
791
  requirements:
789
792
  - - ">="
790
793
  - !ruby/object:Gem::Version
791
- version: '2.5'
794
+ version: '2.6'
792
795
  required_rubygems_version: !ruby/object:Gem::Requirement
793
796
  requirements:
794
797
  - - ">="