inspec-core 4.18.51 → 4.18.85

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