inspec-core 4.18.51 → 4.18.85

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +61 -0
  3. data/README.md +3 -3
  4. data/inspec-core.gemspec +51 -0
  5. data/lib/bundles/inspec-supermarket/cli.rb +1 -0
  6. data/lib/inspec/backend.rb +49 -47
  7. data/lib/inspec/base_cli.rb +2 -2
  8. data/lib/inspec/cached_fetcher.rb +4 -0
  9. data/lib/inspec/cli.rb +5 -0
  10. data/lib/inspec/config.rb +1 -1
  11. data/lib/inspec/control_eval_context.rb +131 -199
  12. data/lib/inspec/dependencies/requirement.rb +1 -1
  13. data/lib/inspec/dependencies/resolver.rb +46 -0
  14. data/lib/inspec/dsl_shared.rb +25 -3
  15. data/lib/inspec/fetcher.rb +0 -3
  16. data/lib/inspec/fetcher/git.rb +4 -0
  17. data/lib/inspec/fetcher/url.rb +1 -2
  18. data/lib/inspec/file_provider.rb +4 -2
  19. data/lib/inspec/library_eval_context.rb +37 -37
  20. data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +27 -0
  21. data/lib/inspec/plugin/v1/plugins.rb +0 -1
  22. data/lib/inspec/profile.rb +8 -6
  23. data/lib/inspec/profile_context.rb +74 -9
  24. data/lib/inspec/profile_vendor.rb +48 -3
  25. data/lib/inspec/resource.rb +192 -41
  26. data/lib/inspec/resources/aide_conf.rb +1 -1
  27. data/lib/inspec/resources/apache_conf.rb +15 -31
  28. data/lib/inspec/resources/command.rb +1 -1
  29. data/lib/inspec/resources/crontab.rb +56 -56
  30. data/lib/inspec/resources/etc_fstab.rb +1 -1
  31. data/lib/inspec/resources/etc_group.rb +1 -1
  32. data/lib/inspec/resources/etc_hosts.rb +2 -3
  33. data/lib/inspec/resources/etc_hosts_allow_deny.rb +1 -1
  34. data/lib/inspec/resources/file.rb +2 -2
  35. data/lib/inspec/resources/filesystem.rb +4 -4
  36. data/lib/inspec/resources/groups.rb +16 -2
  37. data/lib/inspec/resources/iis_app.rb +1 -1
  38. data/lib/inspec/resources/ini.rb +1 -2
  39. data/lib/inspec/resources/mount.rb +2 -2
  40. data/lib/inspec/resources/oracledb_session.rb +1 -1
  41. data/lib/inspec/resources/package.rb +22 -0
  42. data/lib/inspec/resources/passwd.rb +1 -1
  43. data/lib/inspec/resources/platform.rb +36 -36
  44. data/lib/inspec/resources/port.rb +1 -1
  45. data/lib/inspec/resources/postfix_conf.rb +1 -1
  46. data/lib/inspec/resources/service.rb +23 -15
  47. data/lib/inspec/resources/users.rb +3 -3
  48. data/lib/inspec/resources/virtualization.rb +15 -11
  49. data/lib/inspec/resources/x509_certificate.rb +18 -4
  50. data/lib/inspec/resources/xinetd_conf.rb +1 -1
  51. data/lib/inspec/resources/xml.rb +1 -2
  52. data/lib/inspec/rspec_extensions.rb +12 -0
  53. data/lib/inspec/rule.rb +63 -22
  54. data/lib/inspec/utils/filter.rb +2 -0
  55. data/lib/inspec/utils/parser.rb +244 -240
  56. data/lib/inspec/utils/simpleconfig.rb +1 -1
  57. data/lib/inspec/version.rb +1 -1
  58. data/lib/matchers/matchers.rb +11 -10
  59. data/lib/plugins/inspec-compliance/lib/inspec-compliance.rb +3 -0
  60. data/lib/plugins/inspec-habitat/lib/inspec-habitat/profile.rb +2 -2
  61. data/lib/plugins/inspec-init/templates/profiles/aws/README.md +192 -0
  62. data/lib/plugins/inspec-init/templates/profiles/aws/attributes.yml +2 -0
  63. data/lib/plugins/inspec-init/templates/profiles/aws/controls/example.rb +39 -0
  64. data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +22 -0
  65. data/lib/plugins/inspec-init/templates/profiles/azure/README.md +56 -0
  66. data/lib/plugins/inspec-init/templates/profiles/azure/controls/example.rb +14 -0
  67. data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +14 -0
  68. data/lib/plugins/inspec-init/templates/profiles/gcp/README.md +66 -0
  69. data/lib/plugins/inspec-init/templates/profiles/gcp/attributes.yml +2 -0
  70. data/lib/plugins/inspec-init/templates/profiles/gcp/controls/example.rb +27 -0
  71. data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +19 -0
  72. data/lib/source_readers/inspec.rb +1 -1
  73. metadata +87 -74
  74. data/lib/inspec/plugin/v1/plugin_types/resource.rb +0 -176
  75. data/lib/plugins/inspec-init/templates/profiles/os/libraries/.gitkeep +0 -0
@@ -703,7 +703,7 @@ module Inspec::Resources
703
703
  end
704
704
 
705
705
  class SolarisPorts < FreeBsdPorts
706
- include SolarisNetstatParser
706
+ include Inspec::Utils::SolarisNetstatParser
707
707
 
708
708
  def info
709
709
  # extract all port info
@@ -4,7 +4,7 @@ require "inspec/utils/simpleconfig"
4
4
  module Inspec::Resources
5
5
  class PostfixConf < IniConfig
6
6
  name "postfix_conf"
7
- supports platform: "linux"
7
+ supports platform: "unix"
8
8
  desc "Use the postfix_conf Inspec audit resource to test the configuration of the Postfix Mail Transfer Agent"
9
9
 
10
10
  # Allow user to specify a custom configuration path, use default Postfix configuration path if no custom path is provided
@@ -112,21 +112,23 @@ module Inspec::Resources
112
112
  # Ubuntu < 15.04 : Upstart
113
113
  # Upstart runs with PID 1 as /sbin/init.
114
114
  # Systemd runs with PID 1 as /lib/systemd/systemd.
115
- if %w{ubuntu}.include?(platform)
115
+
116
+ case platform
117
+ when "ubuntu"
116
118
  version = os[:release].to_f
117
119
  if version < 15.04
118
120
  Upstart.new(inspec, service_ctl)
119
121
  else
120
122
  Systemd.new(inspec, service_ctl)
121
123
  end
122
- elsif %w{linuxmint}.include?(platform)
124
+ when "linuxmint"
123
125
  version = os[:release].to_f
124
126
  if version < 18
125
127
  Upstart.new(inspec, service_ctl)
126
128
  else
127
129
  Systemd.new(inspec, service_ctl)
128
130
  end
129
- elsif %w{debian}.include?(platform)
131
+ when "debian"
130
132
  if os[:release] == "buster/sid"
131
133
  version = 10
132
134
  else
@@ -137,41 +139,47 @@ module Inspec::Resources
137
139
  elsif version > 0
138
140
  SysV.new(inspec, service_ctl || "/usr/sbin/service")
139
141
  end
140
- elsif %w{redhat fedora centos oracle cloudlinux}.include?(platform)
142
+ when "redhat", "fedora", "centos", "oracle", "cloudlinux"
141
143
  version = os[:release].to_i
142
- if (%w{redhat centos oracle cloudlinux}.include?(platform) && version >= 7) || (platform == "fedora" && version >= 15)
144
+
145
+ systemd = ((platform != "fedora" && version >= 7) ||
146
+ (platform == "fedora" && version >= 15))
147
+
148
+ if systemd
143
149
  Systemd.new(inspec, service_ctl)
144
150
  else
145
151
  SysV.new(inspec, service_ctl || "/sbin/service")
146
152
  end
147
- elsif %w{wrlinux}.include?(platform)
153
+ when "wrlinux"
148
154
  SysV.new(inspec, service_ctl)
149
- elsif %w{mac_os_x}.include?(platform)
155
+ when "mac_os_x"
150
156
  LaunchCtl.new(inspec, service_ctl)
151
- elsif os.windows?
157
+ when "windows"
152
158
  WindowsSrv.new(inspec)
153
- elsif %w{freebsd}.include?(platform)
159
+ when "freebsd"
154
160
  BSDInit.new(inspec, service_ctl)
155
- elsif %w{arch}.include?(platform)
161
+ when "arch"
156
162
  Systemd.new(inspec, service_ctl)
157
- elsif %w{coreos}.include?(platform)
163
+ when "coreos"
158
164
  Systemd.new(inspec, service_ctl)
159
- elsif %w{suse opensuse}.include?(platform)
165
+ when "suse", "opensuse"
160
166
  if os[:release].to_i >= 12
161
167
  Systemd.new(inspec, service_ctl)
162
168
  else
163
169
  SysV.new(inspec, service_ctl || "/sbin/service")
164
170
  end
165
- elsif %w{aix}.include?(platform)
171
+ when "aix"
166
172
  SrcMstr.new(inspec)
167
- elsif %w{amazon}.include?(platform)
173
+ when "amazon"
168
174
  if os[:release] =~ /^20\d\d/
169
175
  Upstart.new(inspec, service_ctl)
170
176
  else
171
177
  Systemd.new(inspec, service_ctl)
172
178
  end
173
- elsif os.solaris?
179
+ when "solaris", "smartos", "omnios", "openindiana", "opensolaris", "nexentacore"
174
180
  Svcs.new(inspec)
181
+ when "yocto"
182
+ Systemd.new(inspec, service_ctl)
175
183
  end
176
184
  end
177
185
 
@@ -442,8 +442,8 @@ module Inspec::Resources
442
442
  end
443
443
 
444
444
  class LinuxUser < UnixUser
445
- include PasswdParser
446
- include CommentParser
445
+ include Inspec::Utils::PasswdParser
446
+ include Inspec::Utils::CommentParser
447
447
 
448
448
  def meta_info(username)
449
449
  cmd = inspec.command("getent passwd #{username}")
@@ -589,7 +589,7 @@ module Inspec::Resources
589
589
  # - chpass(1) A flexible tool for changing user database information.
590
590
  # - passwd(1) The command-line tool to change user passwords.
591
591
  class FreeBSDUser < UnixUser
592
- include PasswdParser
592
+ include Inspec::Utils::PasswdParser
593
593
 
594
594
  def meta_info(username)
595
595
  cmd = inspec.command("pw usershow #{username} -7")
@@ -3,8 +3,8 @@ require "hashie/mash"
3
3
  module Inspec::Resources
4
4
  class Virtualization < Inspec.resource(1)
5
5
  name "virtualization"
6
- supports platform: "linux"
7
- desc "Use the virtualization InSpec audit resource to test the virtualization platform on which the system is running"
6
+ supports platform: "unix"
7
+ desc "Use the virtualization InSpec audit resource to test various virtualization platforms."
8
8
  example <<~EXAMPLE
9
9
  describe virtualization do
10
10
  its('system') { should eq 'docker' }
@@ -23,19 +23,21 @@ module Inspec::Resources
23
23
  EXAMPLE
24
24
 
25
25
  def initialize
26
+ # TODO: no need for hashie here... in fact, no reason for a hash at all
26
27
  @virtualization_data = Hashie::Mash.new
27
28
  collect_data_linux
28
29
  end
29
30
 
30
31
  # add helper methods for easy access of properties
31
32
  # allows users to use virtualization.role, virtualization.system
32
- %w{role system}.each do |property|
33
- define_method(property.to_sym) do
34
- @virtualization_data[property.to_sym]
33
+ %i{role system}.each do |property|
34
+ define_method(property) do
35
+ @virtualization_data[property]
35
36
  end
36
37
  end
37
38
 
38
39
  def params
40
+ # TODO: this is broken. cannot return anything but nil
39
41
  collect_data_linux
40
42
  end
41
43
 
@@ -215,6 +217,7 @@ module Inspec::Resources
215
217
 
216
218
  @virtualization_data[:system] = "docker"
217
219
  @virtualization_data[:role] = "guest"
220
+ # TODO: needs to support host
218
221
  true
219
222
  end
220
223
 
@@ -233,23 +236,24 @@ module Inspec::Resources
233
236
  true
234
237
  end
235
238
 
236
- def collect_data_linux # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
239
+ def collect_data_linux
237
240
  # This avoids doing multiple detections in a single test
238
241
  return unless @virtualization_data.empty?
239
242
 
240
243
  # each detect method will return true if it matched and was successfully
241
244
  # able to populate @virtualization_data with stuff.
242
245
  return if detect_xen
246
+ return if detect_docker
243
247
  return if detect_virtualbox
244
- return if detect_openstack
248
+ return if detect_lxd
249
+ return if detect_lxc_docker
250
+ return if detect_linux_vserver
245
251
  return if detect_kvm_from_cpuinfo
246
252
  return if detect_kvm_from_sys
247
253
  return if detect_openvz
254
+ return if detect_openstack
248
255
  return if detect_parallels
249
- return if detect_linux_vserver
250
- return if detect_lxc_docker
251
- return if detect_docker
252
- return if detect_lxd
256
+ # TODO: where is vmware?
253
257
  end
254
258
  end
255
259
  end
@@ -34,13 +34,15 @@ module Inspec::Resources
34
34
  include FileReader
35
35
 
36
36
  # @see https://tools.ietf.org/html/rfc5280#page-23
37
- def initialize(filename)
38
- @certpath = filename
37
+ def initialize(opts)
38
+ @opts = options(opts)
39
39
  @issuer = nil
40
40
  @parsed_subject = nil
41
41
  @parsed_issuer = nil
42
42
  @extensions = nil
43
- @cert = OpenSSL::X509::Certificate.new read_file_content(@certpath)
43
+ @content = @opts[:content]
44
+ @content ||= read_file_content(@opts[:filepath])
45
+ @cert = OpenSSL::X509::Certificate.new @content
44
46
  end
45
47
 
46
48
  # Forward these methods directly to OpenSSL::X509::Certificate instance
@@ -137,7 +139,19 @@ module Inspec::Resources
137
139
  end
138
140
 
139
141
  def to_s
140
- "x509_certificate #{@certpath}"
142
+ cert = @opts[:filepath]
143
+ cert ||= subject.CN
144
+ "x509_certificate #{cert}"
145
+ end
146
+
147
+ private
148
+
149
+ def options(opts)
150
+ if opts.is_a?(String)
151
+ { filepath: opts }
152
+ else
153
+ opts
154
+ end
141
155
  end
142
156
  end
143
157
  end
@@ -17,7 +17,7 @@ module Inspec::Resources
17
17
  end
18
18
  EXAMPLE
19
19
 
20
- include XinetdParser
20
+ include Inspec::Utils::XinetdParser
21
21
  include FileReader
22
22
 
23
23
  def initialize(conf_path = "/etc/xinetd.conf")
@@ -3,8 +3,7 @@ require "inspec/resources/json"
3
3
  module Inspec::Resources
4
4
  class XmlConfig < JsonConfig
5
5
  name "xml"
6
- supports platform: "unix"
7
- supports platform: "windows"
6
+ supports platform: "os"
8
7
  desc "Use the xml InSpec resource to test configuration data in an XML file"
9
8
  example <<~EXAMPLE
10
9
  describe xml('default.xml') do
@@ -114,3 +114,15 @@ class RSpec::Core::ExampleGroup
114
114
  # So, we use prepend.
115
115
  prepend Inspec::TestDslLazyLoader
116
116
  end
117
+
118
+ class ResourceInspector < RSpec::Support::ObjectFormatter::BaseInspector
119
+ def self.can_inspect?(object)
120
+ Inspec::Resource === object
121
+ end
122
+
123
+ def inspect # do NOT use default inspect in rspec w/ resources
124
+ object.to_s
125
+ end
126
+ end
127
+
128
+ RSpec::Support::ObjectFormatter::INSPECTOR_CLASSES.unshift ResourceInspector
@@ -13,32 +13,20 @@ module Inspec
13
13
  class Rule
14
14
  include ::RSpec::Matchers
15
15
 
16
- #
17
- # Include any resources from the given resource DSL. The passed
18
- # resource_dsl will also be included in any Inspec::Expect objects
19
- # we make.
20
- #
21
- # @params resource_dsl [Module]
22
- # @returns [TrueClass]
23
- #
24
- def self.with_resource_dsl(resource_dsl)
25
- include resource_dsl
26
- @resource_dsl = resource_dsl
27
- true
28
- end
29
-
30
- def self.resource_dsl # rubocop:disable Style/TrivialAccessors
31
- @resource_dsl
32
- end
33
-
34
16
  attr_reader :__waiver_data
35
- def initialize(id, profile_id, opts, &block)
17
+ attr_accessor :resource_dsl
18
+ attr_reader :__profile_id
19
+
20
+ def initialize(id, profile_id, resource_dsl, opts, &block)
36
21
  @impact = nil
37
22
  @title = nil
38
23
  @descriptions = {}
39
24
  @refs = []
40
25
  @tags = {}
41
26
 
27
+ @resource_dsl = resource_dsl
28
+ extend resource_dsl # TODO: remove! do it via method_missing
29
+
42
30
  # not changeable by the user:
43
31
  @__code = nil
44
32
  @__block = block
@@ -168,7 +156,7 @@ module Inspec
168
156
  # @return [nil|DescribeBase] if called without arguments, returns DescribeBase
169
157
  def describe(*values, &block)
170
158
  if values.empty? && !block_given?
171
- dsl = self.class.ancestors[1]
159
+ dsl = resource_dsl
172
160
  Class.new(DescribeBase) do
173
161
  include dsl
174
162
  end.new(method(:__add_check))
@@ -183,6 +171,59 @@ module Inspec
183
171
  target
184
172
  end
185
173
 
174
+ # allow attributes to be accessed within control blocks
175
+ def input(input_name, options = {})
176
+ if options.empty?
177
+ # Simply an access, no event here
178
+ Inspec::InputRegistry.find_or_register_input(input_name, __profile_id).value
179
+ else
180
+ options[:priority] ||= 20
181
+ options[:provider] = :inline_control_code
182
+ evt = Inspec::Input.infer_event(options)
183
+ Inspec::InputRegistry.find_or_register_input(input_name, __profile_id, event: evt).value
184
+ end
185
+ end
186
+
187
+ # Find the Input object, but don't collapse to a value.
188
+ # Will return nil on a miss.
189
+ def input_object(input_name)
190
+ Inspec::InputRegistry.find_or_register_input(input_name, __profile_id)
191
+ end
192
+
193
+ def attribute(name, options = {})
194
+ Inspec.deprecate(:attrs_dsl, "Input name: #{name}, Profile: #{__profile_id}")
195
+ input(name, options)
196
+ end
197
+
198
+ # Support for Control DSL plugins.
199
+ # This is called when an unknown method is encountered
200
+ # within a control block.
201
+ def method_missing(method_name, *arguments, &block)
202
+ # Check to see if there is a control_dsl plugin activator hook with the method name
203
+ registry = Inspec::Plugin::V2::Registry.instance
204
+ hook = registry.find_activators(plugin_type: :control_dsl, activator_name: method_name).first
205
+ if hook
206
+ # OK, load the hook if it hasn't been already. We'll then know a module,
207
+ # which we can then inject into the context
208
+ hook.activate
209
+
210
+ # Inject the module's methods into the context.
211
+ # implementation_class is the field name, but this is actually a module.
212
+ self.class.include(hook.implementation_class)
213
+ # Now that the module is loaded, it defined one or more methods
214
+ # (presumably the one we were looking for.)
215
+ # We still haven't called it, so do so now.
216
+ send(method_name, *arguments, &block)
217
+ else
218
+ begin
219
+ Inspec::DSL.method_missing_resource(inspec, method_name, *arguments)
220
+ rescue LoadError
221
+ super
222
+ end
223
+ end
224
+ end
225
+
226
+ # TODO: figure out why these violations exist and nuke them.
186
227
  def self.rule_id(rule)
187
228
  rule.instance_variable_get(:@__rule_id)
188
229
  end
@@ -290,7 +331,7 @@ module Inspec
290
331
  def __apply_waivers
291
332
  input_name = @__rule_id # TODO: control ID slugging
292
333
  registry = Inspec::InputRegistry.instance
293
- input = registry.inputs_by_profile.dig(@__profile_id, input_name)
334
+ input = registry.inputs_by_profile.dig(__profile_id, input_name)
294
335
  return unless input
295
336
 
296
337
  # An InSpec Input is a datastructure that tracks a profile parameter
@@ -347,7 +388,7 @@ module Inspec
347
388
  def with_dsl(block)
348
389
  return nil if block.nil?
349
390
 
350
- dsl = self.class.resource_dsl
391
+ dsl = resource_dsl
351
392
 
352
393
  return block unless dsl
353
394
 
@@ -1,3 +1,5 @@
1
+ require "inspec/exceptions"
2
+
1
3
  module FilterTable
2
4
  # This is used as a sentinel value in custom property filtering
3
5
  module NoCriteriaProvided; end
@@ -1,277 +1,281 @@
1
1
  require "inspec/resources/command"
2
2
 
3
- module PasswdParser
4
- # Parse /etc/passwd files.
5
- #
6
- # @param [String] content the raw content of /etc/passwd
7
- # @return [Array] Collection of passwd entries
8
- def parse_passwd(content)
9
- content.to_s.split("\n").map do |line|
10
- next if line[0] == "#"
11
-
12
- parse_passwd_line(line)
13
- end.compact
14
- end
15
-
16
- # Parse a line of /etc/passwd
17
- #
18
- # @param [String] line a line of /etc/passwd
19
- # @return [Hash] Map of entries in this line
20
- def parse_passwd_line(line)
21
- x = line.split(":")
22
- {
23
- # rubocop:disable Layout/AlignHash
24
- "user" => x[0],
25
- "password" => x[1],
26
- "uid" => x[2],
27
- "gid" => x[3],
28
- "desc" => x[4],
29
- "home" => x[5],
30
- "shell" => x[6],
31
- }
32
- end
33
- end
3
+ module Inspec
4
+ module Utils
5
+ module PasswdParser
6
+ # Parse /etc/passwd files.
7
+ #
8
+ # @param [String] content the raw content of /etc/passwd
9
+ # @return [Array] Collection of passwd entries
10
+ def parse_passwd(content)
11
+ content.to_s.split("\n").map do |line|
12
+ next if line[0] == "#"
13
+
14
+ parse_passwd_line(line)
15
+ end.compact
16
+ end
34
17
 
35
- module CommentParser
36
- # Parse a line with a command. For example: `a = b # comment`.
37
- # Retrieves the actual content.
38
- #
39
- # @param [String] raw the content lines you want to be parsed
40
- # @param [Hash] opts optional configuration
41
- # @return [Array] contains the actual line and the position of the line end
42
- def parse_comment_line(raw, opts)
43
- idx_nl = raw.index("\n")
44
- idx_comment = raw.index(opts[:comment_char])
45
- idx_nl = raw.length if idx_nl.nil?
46
- idx_comment = idx_nl + 1 if idx_comment.nil?
47
- line = ""
48
-
49
- # is a comment inside this line
50
- if idx_comment < idx_nl && idx_comment != 0
51
- line = raw[0..(idx_comment - 1)]
52
- # in case we don't allow comments at the end
53
- # of an assignment/statement, ignore it and fall
54
- # back to treating this as a regular line
55
- if opts[:standalone_comments] && !is_empty_line(line)
56
- line = raw[0..(idx_nl - 1)]
18
+ # Parse a line of /etc/passwd
19
+ #
20
+ # @param [String] line a line of /etc/passwd
21
+ # @return [Hash] Map of entries in this line
22
+ def parse_passwd_line(line)
23
+ x = line.split(":")
24
+ {
25
+ # rubocop:disable Layout/AlignHash
26
+ "user" => x[0],
27
+ "password" => x[1],
28
+ "uid" => x[2],
29
+ "gid" => x[3],
30
+ "desc" => x[4],
31
+ "home" => x[5],
32
+ "shell" => x[6],
33
+ }
57
34
  end
58
- # if there is no comment in this line
59
- elsif idx_comment > idx_nl && idx_nl != 0
60
- line = raw[0..(idx_nl - 1)]
61
35
  end
62
- [line, idx_nl]
63
- end
64
- end
65
36
 
66
- module LinuxMountParser
67
- # this parses the output of mount command (only tested on linux)
68
- # this method expects only one line of the mount output
69
- def parse_mount_options(mount_line, compatibility = false)
70
- if includes_whitespaces?(mount_line)
71
- # Device-/Sharenames and Mountpoints including whitespaces require special treatment:
72
- # We use the keyword ' type ' to split up and rebuild the desired array of fields
73
- type_split = mount_line.split(" type ")
74
- fs_path = type_split[0]
75
- other_opts = type_split[1]
76
- fs, path = fs_path.match(%r{^(.+?)\son\s(/.+?)$}).captures
77
- mount = [fs, "on", path, "type"]
78
- mount.concat(other_opts.scan(/\S+/))
79
- else
80
- # ... otherwise we just split the fields by whitespaces
81
- mount = mount_line.scan(/\S+/)
37
+ module CommentParser
38
+ # Parse a line with a command. For example: `a = b # comment`.
39
+ # Retrieves the actual content.
40
+ #
41
+ # @param [String] raw the content lines you want to be parsed
42
+ # @param [Hash] opts optional configuration
43
+ # @return [Array] contains the actual line and the position of the line end
44
+ def parse_comment_line(raw, opts)
45
+ idx_nl = raw.index("\n")
46
+ idx_comment = raw.index(opts[:comment_char])
47
+ idx_nl = raw.length if idx_nl.nil?
48
+ idx_comment = idx_nl + 1 if idx_comment.nil?
49
+ line = ""
50
+
51
+ # is a comment inside this line
52
+ if idx_comment < idx_nl && idx_comment != 0
53
+ line = raw[0..(idx_comment - 1)]
54
+ # in case we don't allow comments at the end
55
+ # of an assignment/statement, ignore it and fall
56
+ # back to treating this as a regular line
57
+ if opts[:standalone_comments] && !is_empty_line(line)
58
+ line = raw[0..(idx_nl - 1)]
59
+ end
60
+ # if there is no comment in this line
61
+ elsif idx_comment > idx_nl && idx_nl != 0
62
+ line = raw[0..(idx_nl - 1)]
63
+ end
64
+ [line, idx_nl]
65
+ end
82
66
  end
83
67
 
84
- # parse device and type
85
- mount_options = { device: mount[0], type: mount[4] }
86
-
87
- if compatibility == false
88
- # parse options as array
89
- mount_options[:options] = mount[5].gsub(/\(|\)/, "").split(",")
90
- else
91
- Inspec.deprecate(:mount_parser_serverspec_compat, "Parsing mount options in this fashion is deprecated")
92
- mount_options[:options] = {}
93
- mount[5].gsub(/\(|\)/, "").split(",").each do |option|
94
- name, val = option.split("=")
95
- if val.nil?
96
- val = true
97
- elsif val =~ /^\d+$/
98
- # parse numbers
99
- val = val.to_i
68
+ module LinuxMountParser
69
+ # this parses the output of mount command (only tested on linux)
70
+ # this method expects only one line of the mount output
71
+ def parse_mount_options(mount_line, compatibility = false)
72
+ if includes_whitespaces?(mount_line)
73
+ # Device-/Sharenames and Mountpoints including whitespaces require special treatment:
74
+ # We use the keyword ' type ' to split up and rebuild the desired array of fields
75
+ type_split = mount_line.split(" type ")
76
+ fs_path = type_split[0]
77
+ other_opts = type_split[1]
78
+ fs, path = fs_path.match(%r{^(.+?)\son\s(/.+?)$}).captures
79
+ mount = [fs, "on", path, "type"]
80
+ mount.concat(other_opts.scan(/\S+/))
81
+ else
82
+ # ... otherwise we just split the fields by whitespaces
83
+ mount = mount_line.scan(/\S+/)
100
84
  end
101
- mount_options[:options][name.to_sym] = val
85
+
86
+ # parse device and type
87
+ mount_options = { device: mount[0], type: mount[4] }
88
+
89
+ if compatibility == false
90
+ # parse options as array
91
+ mount_options[:options] = mount[5].gsub(/\(|\)/, "").split(",")
92
+ else
93
+ Inspec.deprecate(:mount_parser_serverspec_compat, "Parsing mount options in this fashion is deprecated")
94
+ mount_options[:options] = {}
95
+ mount[5].gsub(/\(|\)/, "").split(",").each do |option|
96
+ name, val = option.split("=")
97
+ if val.nil?
98
+ val = true
99
+ elsif val =~ /^\d+$/
100
+ # parse numbers
101
+ val = val.to_i
102
+ end
103
+ mount_options[:options][name.to_sym] = val
104
+ end
105
+ end
106
+
107
+ mount_options
102
108
  end
103
- end
104
109
 
105
- mount_options
106
- end
110
+ # Device-/Sharename or Mountpoint includes whitespaces?
111
+ def includes_whitespaces?(mount_line)
112
+ ws = mount_line.match(/^(.+)\son\s(.+)\stype\s.*$/)
113
+ ws.captures[0].include?(" ") || ws.captures[1].include?(" ")
114
+ end
115
+ end
107
116
 
108
- # Device-/Sharename or Mountpoint includes whitespaces?
109
- def includes_whitespaces?(mount_line)
110
- ws = mount_line.match(/^(.+)\son\s(.+)\stype\s.*$/)
111
- ws.captures[0].include?(" ") || ws.captures[1].include?(" ")
112
- end
113
- end
117
+ module BsdMountParser
118
+ # this parses the output of mount command (only tested on freebsd)
119
+ # this method expects only one line of the mount output
120
+ def parse_mount_options(mount_line, _compatibility = false)
121
+ return {} if mount_line.nil? || mount_line.empty?
114
122
 
115
- module BsdMountParser
116
- # this parses the output of mount command (only tested on freebsd)
117
- # this method expects only one line of the mount output
118
- def parse_mount_options(mount_line, _compatibility = false)
119
- return {} if mount_line.nil? || mount_line.empty?
123
+ mount = mount_line.chomp.split(" ", 4)
124
+ options = mount[3].tr("()", "").split(", ")
120
125
 
121
- mount = mount_line.chomp.split(" ", 4)
122
- options = mount[3].tr("()", "").split(", ")
126
+ # parse device and type
127
+ { device: mount[0], type: options.shift, options: options }
128
+ end
129
+ end
123
130
 
124
- # parse device and type
125
- { device: mount[0], type: options.shift, options: options }
126
- end
127
- end
131
+ module SolarisNetstatParser
132
+ # takes this as a input and parses the values
133
+ # UDP: IPv4
134
+ # Local Address Remote Address State
135
+ # -------------------- -------------------- ----------
136
+ # *.* Unbound
137
+ def parse_netstat(content) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
138
+ return [] if content.nil? || content.empty?
128
139
 
129
- module SolarisNetstatParser
130
- # takes this as a input and parses the values
131
- # UDP: IPv4
132
- # Local Address Remote Address State
133
- # -------------------- -------------------- ----------
134
- # *.* Unbound
135
- def parse_netstat(content) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
136
- return [] if content.nil? || content.empty?
137
-
138
- protocol = nil
139
- column_widths = nil
140
- ports = []
141
- cache_name_line = nil
142
-
143
- content.each_line do |line|
144
- # find header, its delimiter
145
- if line =~ /TCP:|UDP:|SCTP:/
146
- # get protocol
147
- protocol = line.split(":")[0].chomp.strip.downcase
148
-
149
- # determine version tcp, tcp6, udp, udp6
150
- proto_version = line.split(":")[1].chomp.strip
151
- protocol += "6" if proto_version == "IPv6"
152
-
153
- # reset names cache
140
+ protocol = nil
154
141
  column_widths = nil
142
+ ports = []
155
143
  cache_name_line = nil
156
- names = nil
157
- # calulate width of a column based on the horizontal line
158
- elsif line =~ /^[- ]+$/
159
- column_widths = columns(line)
160
- # parse header values from line
161
- elsif column_widths.nil? && !line.nil?
162
- # we do not know the width at this point of time, therefore we need to cache
163
- cache_name_line = line
164
- # content line
165
- elsif !column_widths.nil? && !line.nil? && !line.chomp.empty?
166
- # default row
167
- port = split_columns(column_widths, line).to_a.map { |v| v.chomp.strip }
168
-
169
- # parse the header names
170
- # TODO: names should be optional
171
- names = split_columns(column_widths, cache_name_line).to_a.map { |v| v.chomp.strip.downcase.tr(" ", "-").gsub(/[^\w-]/, "_") }
172
- info = {
173
- "protocol" => protocol.downcase,
174
- }
175
144
 
176
- # generate hash for each line and use the names as keys
177
- names.each_index do |i|
178
- info[names[i]] = port[i] if i != 0
145
+ content.each_line do |line|
146
+ # find header, its delimiter
147
+ if line =~ /TCP:|UDP:|SCTP:/
148
+ # get protocol
149
+ protocol = line.split(":")[0].chomp.strip.downcase
150
+
151
+ # determine version tcp, tcp6, udp, udp6
152
+ proto_version = line.split(":")[1].chomp.strip
153
+ protocol += "6" if proto_version == "IPv6"
154
+
155
+ # reset names cache
156
+ column_widths = nil
157
+ cache_name_line = nil
158
+ names = nil
159
+ # calulate width of a column based on the horizontal line
160
+ elsif line =~ /^[- ]+$/
161
+ column_widths = columns(line)
162
+ # parse header values from line
163
+ elsif column_widths.nil? && !line.nil?
164
+ # we do not know the width at this point of time, therefore we need to cache
165
+ cache_name_line = line
166
+ # content line
167
+ elsif !column_widths.nil? && !line.nil? && !line.chomp.empty?
168
+ # default row
169
+ port = split_columns(column_widths, line).to_a.map { |v| v.chomp.strip }
170
+
171
+ # parse the header names
172
+ # TODO: names should be optional
173
+ names = split_columns(column_widths, cache_name_line).to_a.map { |v| v.chomp.strip.downcase.tr(" ", "-").gsub(/[^\w-]/, "_") }
174
+ info = {
175
+ "protocol" => protocol.downcase,
176
+ }
177
+
178
+ # generate hash for each line and use the names as keys
179
+ names.each_index do |i|
180
+ info[names[i]] = port[i] if i != 0
181
+ end
182
+
183
+ ports.push(info)
184
+ end
179
185
  end
180
-
181
- ports.push(info)
186
+ ports
182
187
  end
183
- end
184
- ports
185
- end
186
188
 
187
- private
189
+ private
188
190
 
189
- # takes a line like "-------------------- -------------------- ----------"
190
- # as input and calculates the length of each column
191
- def columns(line)
192
- # find all columns
193
- m = line.scan(/-+/)
194
- # calculate the length each column
195
- m.map { |x| x.length } # rubocop:disable Style/SymbolProc
196
- end
191
+ # takes a line like "-------------------- -------------------- ----------"
192
+ # as input and calculates the length of each column
193
+ def columns(line)
194
+ # find all columns
195
+ m = line.scan(/-+/)
196
+ # calculate the length each column
197
+ m.map { |x| x.length } # rubocop:disable Style/SymbolProc
198
+ end
197
199
 
198
- # takes a line and the width of the columns to extract the values
199
- def split_columns(columns, line)
200
- # generate regex based on columns
201
- sep = '\\s'
202
- length = columns.length
203
- arr = columns.map.with_index do |x, i|
204
- reg = "(.{#{x}})#{sep}" # add seperator between columns
205
- reg = "(.{,#{x}})#{sep}" if i == length - 2 # make the pre-last one optional
206
- reg = "(.{,#{x}})" if i == length - 1 # use , to say max value
207
- reg
200
+ # takes a line and the width of the columns to extract the values
201
+ def split_columns(columns, line)
202
+ # generate regex based on columns
203
+ sep = '\\s'
204
+ length = columns.length
205
+ arr = columns.map.with_index do |x, i|
206
+ reg = "(.{#{x}})#{sep}" # add seperator between columns
207
+ reg = "(.{,#{x}})#{sep}" if i == length - 2 # make the pre-last one optional
208
+ reg = "(.{,#{x}})" if i == length - 1 # use , to say max value
209
+ reg
210
+ end
211
+ # extracts the columns
212
+ line.match(Regexp.new(arr.join))
213
+ end
208
214
  end
209
- # extracts the columns
210
- line.match(Regexp.new(arr.join))
211
- end
212
- end
213
215
 
214
- # This parser for xinetd (extended Internet daemon) configuration files
215
- module XinetdParser
216
- def xinetd_include_dir(dir)
217
- return [] if dir.nil?
216
+ # This parser for xinetd (extended Internet daemon) configuration files
217
+ module XinetdParser
218
+ def xinetd_include_dir(dir)
219
+ return [] if dir.nil?
218
220
 
219
- unless inspec.file(dir).directory?
220
- raise Inspec::Exceptions::ResourceSkipped, "Can't find folder: #{dir}"
221
- end
221
+ unless inspec.file(dir).directory?
222
+ raise Inspec::Exceptions::ResourceSkipped, "Can't find folder: #{dir}"
223
+ end
222
224
 
223
- files = inspec.command("find #{dir} -type f").stdout.split("\n")
224
- files.map { |file| parse_xinetd(read_content(file)) }
225
- end
225
+ files = inspec.command("find #{dir} -type f").stdout.split("\n")
226
+ files.map { |file| parse_xinetd(read_content(file)) }
227
+ end
226
228
 
227
- def parse_xinetd(raw) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
228
- return {} if raw.nil?
229
-
230
- require "inspec/utils/simpleconfig"
231
-
232
- res = {}
233
- cur_group = nil
234
- simple_conf = []
235
- rest = raw + "\n"
236
- until rest.empty?
237
- # extract content line
238
- nl = rest.index("\n") || (rest.length - 1)
239
- comment = rest.index("#") || (rest.length - 1)
240
- dst_idx = comment < nl ? comment : nl
241
- inner_line = dst_idx == 0 ? "" : rest[0..dst_idx - 1].strip
242
- # update unparsed content
243
- rest = rest[nl + 1..-1]
244
- next if inner_line.empty?
245
-
246
- if inner_line == "}"
247
- if cur_group == "defaults"
248
- res[cur_group] = SimpleConfig.new(simple_conf.join("\n"))
249
- else
250
- res[cur_group] ||= []
251
- res[cur_group].push(SimpleConfig.new(simple_conf.join("\n")))
252
- end
229
+ def parse_xinetd(raw) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
230
+ return {} if raw.nil?
231
+
232
+ require "inspec/utils/simpleconfig"
233
+
234
+ res = {}
253
235
  cur_group = nil
254
- elsif rest.lstrip[0] == "{"
255
- cur_group = inner_line
256
236
  simple_conf = []
257
- rest = rest[rest.index("\n") + 1..-1]
258
- elsif cur_group.nil?
259
- # parse all included files
260
- others = xinetd_include_dir(inner_line[/includedir (.+)/, 1])
261
-
262
- # complex merging of included configurations, as multiple services
263
- # may be defined with the same name but different configuration
264
- others.each do |ores|
265
- ores.each do |k, v|
266
- res[k] ||= []
267
- res[k].concat(v)
237
+ rest = raw + "\n"
238
+ until rest.empty?
239
+ # extract content line
240
+ nl = rest.index("\n") || (rest.length - 1)
241
+ comment = rest.index("#") || (rest.length - 1)
242
+ dst_idx = comment < nl ? comment : nl
243
+ inner_line = dst_idx == 0 ? "" : rest[0..dst_idx - 1].strip
244
+ # update unparsed content
245
+ rest = rest[nl + 1..-1]
246
+ next if inner_line.empty?
247
+
248
+ if inner_line == "}"
249
+ if cur_group == "defaults"
250
+ res[cur_group] = SimpleConfig.new(simple_conf.join("\n"))
251
+ else
252
+ res[cur_group] ||= []
253
+ res[cur_group].push(SimpleConfig.new(simple_conf.join("\n")))
254
+ end
255
+ cur_group = nil
256
+ elsif rest.lstrip[0] == "{"
257
+ cur_group = inner_line
258
+ simple_conf = []
259
+ rest = rest[rest.index("\n") + 1..-1]
260
+ elsif cur_group.nil?
261
+ # parse all included files
262
+ others = xinetd_include_dir(inner_line[/includedir (.+)/, 1])
263
+
264
+ # complex merging of included configurations, as multiple services
265
+ # may be defined with the same name but different configuration
266
+ others.each do |ores|
267
+ ores.each do |k, v|
268
+ res[k] ||= []
269
+ res[k].concat(v)
270
+ end
271
+ end
272
+ else
273
+ simple_conf.push(inner_line)
268
274
  end
269
275
  end
270
- else
271
- simple_conf.push(inner_line)
276
+
277
+ res
272
278
  end
273
279
  end
274
-
275
- res
276
280
  end
277
281
  end