inspec 1.31.0 → 1.45.9

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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +2599 -1
  4. data/Gemfile +0 -1
  5. data/README.md +24 -34
  6. data/Rakefile +0 -31
  7. data/docs/dsl_inspec.md +10 -0
  8. data/docs/matchers.md +29 -0
  9. data/docs/plugin_kitchen_inspec.md +18 -24
  10. data/docs/profiles.md +101 -81
  11. data/docs/resources/aide_conf.md.erb +71 -0
  12. data/docs/resources/apache_conf.md.erb +19 -33
  13. data/docs/resources/apt.md.erb +22 -36
  14. data/docs/resources/audit_policy.md.erb +9 -24
  15. data/docs/resources/auditd.md.erb +78 -0
  16. data/docs/resources/auditd_conf.md.erb +20 -34
  17. data/docs/resources/auditd_rules.md.erb +8 -24
  18. data/docs/resources/bash.md.erb +4 -26
  19. data/docs/resources/bond.md.erb +25 -40
  20. data/docs/resources/bridge.md.erb +5 -25
  21. data/docs/resources/bsd_service.md.erb +5 -25
  22. data/docs/resources/command.md.erb +35 -50
  23. data/docs/resources/cpan.md.erb +62 -0
  24. data/docs/resources/cran.md.erb +54 -0
  25. data/docs/resources/crontab.md.erb +9 -23
  26. data/docs/resources/csv.md.erb +12 -27
  27. data/docs/resources/dh_params.md +1 -0
  28. data/docs/resources/directory.md.erb +5 -25
  29. data/docs/resources/docker.md.erb +61 -58
  30. data/docs/resources/docker_container.md.erb +23 -19
  31. data/docs/resources/docker_image.md.erb +20 -16
  32. data/docs/resources/elasticsearch.md.erb +245 -0
  33. data/docs/resources/etc_fstab.md.erb +120 -0
  34. data/docs/resources/etc_group.md.erb +29 -45
  35. data/docs/resources/etc_hosts.md.erb +68 -0
  36. data/docs/resources/etc_hosts_allow.md.erb +68 -0
  37. data/docs/resources/etc_hosts_deny.md.erb +68 -0
  38. data/docs/resources/file.md.erb +227 -216
  39. data/docs/resources/firewalld.md.erb +104 -0
  40. data/docs/resources/gem.md.erb +21 -35
  41. data/docs/resources/group.md.erb +16 -30
  42. data/docs/resources/grub_conf.md.erb +9 -24
  43. data/docs/resources/host.md.erb +32 -49
  44. data/docs/resources/http.md.erb +38 -44
  45. data/docs/resources/iis_app.md.erb +25 -35
  46. data/docs/resources/iis_site.md.erb +26 -40
  47. data/docs/resources/inetd_conf.md.erb +27 -42
  48. data/docs/resources/ini.md.erb +9 -23
  49. data/docs/resources/interface.md.erb +5 -25
  50. data/docs/resources/iptables.md.erb +15 -29
  51. data/docs/resources/json.md.erb +12 -27
  52. data/docs/resources/kernel_module.md.erb +47 -61
  53. data/docs/resources/kernel_parameter.md.erb +15 -29
  54. data/docs/resources/key_rsa.md.erb +3 -0
  55. data/docs/resources/launchd_service.md.erb +5 -25
  56. data/docs/resources/limits_conf.md.erb +15 -29
  57. data/docs/resources/login_def.md.erb +15 -30
  58. data/docs/resources/mount.md.erb +18 -33
  59. data/docs/resources/mssql_session.md.erb +9 -12
  60. data/docs/resources/mysql_conf.md.erb +28 -32
  61. data/docs/resources/mysql_session.md.erb +15 -29
  62. data/docs/resources/nginx.md.erb +78 -0
  63. data/docs/resources/nginx_conf.md.erb +127 -0
  64. data/docs/resources/npm.md.erb +19 -35
  65. data/docs/resources/ntp_conf.md.erb +20 -37
  66. data/docs/resources/oneget.md.erb +15 -30
  67. data/docs/resources/oracledb_session.md.erb +9 -11
  68. data/docs/resources/os.md.erb +29 -43
  69. data/docs/resources/os_env.md.erb +23 -44
  70. data/docs/resources/package.md.erb +34 -36
  71. data/docs/resources/parse_config.md.erb +5 -25
  72. data/docs/resources/parse_config_file.md.erb +31 -43
  73. data/docs/resources/{etc_passwd.md.erb → passwd.md.erb} +24 -39
  74. data/docs/resources/pip.md.erb +20 -35
  75. data/docs/resources/port.md.erb +43 -57
  76. data/docs/resources/postgres_conf.md.erb +17 -31
  77. data/docs/resources/postgres_hba_conf.md.erb +26 -38
  78. data/docs/resources/postgres_ident_conf.md.erb +25 -37
  79. data/docs/resources/postgres_session.md.erb +15 -29
  80. data/docs/resources/powershell.md.erb +27 -42
  81. data/docs/resources/processes.md.erb +17 -33
  82. data/docs/resources/rabbitmq_config.md.erb +9 -24
  83. data/docs/resources/registry_key.md.erb +27 -42
  84. data/docs/resources/runit_service.md.erb +5 -25
  85. data/docs/resources/security_policy.md.erb +12 -27
  86. data/docs/resources/service.md.erb +27 -42
  87. data/docs/resources/{etc_shadow.md.erb → shadow.md.erb} +41 -47
  88. data/docs/resources/ssh_config.md.erb +19 -34
  89. data/docs/resources/sshd_config.md.erb +19 -34
  90. data/docs/resources/ssl.md.erb +39 -54
  91. data/docs/resources/sys_info.md.erb +12 -26
  92. data/docs/resources/systemd_service.md.erb +5 -25
  93. data/docs/resources/sysv_service.md.erb +5 -25
  94. data/docs/resources/upstart_service.md.erb +5 -25
  95. data/docs/resources/user.md.erb +29 -44
  96. data/docs/resources/users.md.erb +12 -26
  97. data/docs/resources/vbscript.md.erb +9 -24
  98. data/docs/resources/virtualization.md.erb +8 -23
  99. data/docs/resources/windows_feature.md.erb +15 -30
  100. data/docs/resources/windows_hotfix.md.erb +50 -0
  101. data/docs/resources/windows_task.md.erb +12 -26
  102. data/docs/resources/wmi.md.erb +9 -24
  103. data/docs/resources/x509_certificate.md.erb +4 -0
  104. data/docs/resources/xinetd_conf.md.erb +65 -80
  105. data/docs/resources/xml.md.erb +61 -0
  106. data/docs/resources/yaml.md.erb +12 -27
  107. data/docs/resources/yum.md.erb +37 -51
  108. data/docs/resources/zfs_dataset.md.erb +15 -26
  109. data/docs/resources/zfs_pool.md.erb +9 -20
  110. data/docs/ruby_usage.md +0 -1
  111. data/examples/meta-profile/README.md +1 -1
  112. data/examples/profile-sensitive/README.md +29 -0
  113. data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -0
  114. data/examples/profile-sensitive/controls/sensitive.rb +9 -0
  115. data/examples/profile-sensitive/inspec.yml +8 -0
  116. data/inspec.gemspec +3 -3
  117. data/lib/bundles/inspec-artifact/cli.rb +5 -5
  118. data/lib/bundles/inspec-compliance/README.md +24 -22
  119. data/lib/bundles/inspec-compliance/api/login.rb +152 -0
  120. data/lib/bundles/inspec-compliance/api.rb +46 -16
  121. data/lib/bundles/inspec-compliance/cli.rb +58 -165
  122. data/lib/bundles/inspec-compliance/target.rb +2 -3
  123. data/lib/bundles/inspec-habitat/cli.rb +7 -2
  124. data/lib/bundles/inspec-habitat/profile.rb +44 -20
  125. data/lib/bundles/inspec-init/templates/profile/README.md +1 -1
  126. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +1 -1
  127. data/lib/bundles/inspec-supermarket/api.rb +1 -1
  128. data/lib/inspec/backend.rb +8 -0
  129. data/lib/inspec/cli.rb +1 -1
  130. data/lib/inspec/control_eval_context.rb +16 -6
  131. data/lib/inspec/dependencies/requirement.rb +22 -8
  132. data/lib/inspec/dependencies/resolver.rb +1 -1
  133. data/lib/inspec/exceptions.rb +4 -0
  134. data/lib/inspec/file_provider.rb +38 -9
  135. data/lib/inspec/metadata.rb +17 -3
  136. data/lib/inspec/objects/attribute.rb +16 -7
  137. data/lib/inspec/objects/control.rb +10 -2
  138. data/lib/inspec/plugins/resource.rb +34 -5
  139. data/lib/inspec/profile.rb +10 -1
  140. data/lib/inspec/profile_context.rb +3 -1
  141. data/lib/inspec/resource.rb +17 -5
  142. data/lib/inspec/rspec_json_formatter.rb +48 -14
  143. data/lib/inspec/rule.rb +4 -0
  144. data/lib/inspec/runner.rb +72 -30
  145. data/lib/inspec/shell.rb +24 -19
  146. data/lib/inspec/version.rb +1 -1
  147. data/lib/matchers/matchers.rb +40 -0
  148. data/lib/resources/aide_conf.rb +162 -0
  149. data/lib/resources/auditd.rb +231 -0
  150. data/lib/resources/auditd_rules.rb +3 -1
  151. data/lib/resources/command.rb +1 -1
  152. data/lib/resources/cpan.rb +60 -0
  153. data/lib/resources/cran.rb +66 -0
  154. data/lib/resources/docker.rb +8 -1
  155. data/lib/resources/docker_container.rb +19 -2
  156. data/lib/resources/elasticsearch.rb +172 -0
  157. data/lib/resources/etc_fstab.rb +102 -0
  158. data/lib/resources/etc_group.rb +1 -1
  159. data/lib/resources/etc_hosts.rb +81 -0
  160. data/lib/resources/etc_hosts_allow_deny.rb +122 -0
  161. data/lib/resources/file.rb +87 -10
  162. data/lib/resources/firewalld.rb +144 -0
  163. data/lib/resources/groups.rb +0 -1
  164. data/lib/resources/host.rb +30 -15
  165. data/lib/resources/http.rb +178 -22
  166. data/lib/resources/mssql_session.rb +3 -3
  167. data/lib/resources/mysql_conf.rb +10 -0
  168. data/lib/resources/mysql_session.rb +1 -1
  169. data/lib/resources/nginx.rb +97 -0
  170. data/lib/resources/nginx_conf.rb +134 -1
  171. data/lib/resources/oracledb_session.rb +9 -8
  172. data/lib/resources/os_env.rb +7 -4
  173. data/lib/resources/package.rb +14 -2
  174. data/lib/resources/packages.rb +1 -1
  175. data/lib/resources/pip.rb +28 -20
  176. data/lib/resources/port.rb +251 -3
  177. data/lib/resources/postgres.rb +1 -1
  178. data/lib/resources/postgres_session.rb +1 -1
  179. data/lib/resources/processes.rb +106 -20
  180. data/lib/resources/registry_key.rb +11 -2
  181. data/lib/resources/service.rb +5 -24
  182. data/lib/resources/ssh_conf.rb +1 -1
  183. data/lib/resources/ssl.rb +4 -3
  184. data/lib/resources/toml.rb +2 -2
  185. data/lib/resources/windows_hotfix.rb +35 -0
  186. data/lib/resources/xinetd.rb +4 -6
  187. data/lib/resources/xml.rb +27 -0
  188. data/lib/source_readers/inspec.rb +3 -3
  189. data/lib/utils/find_files.rb +2 -1
  190. data/lib/utils/nginx_parser.rb +10 -1
  191. data/lib/utils/object_traversal.rb +35 -10
  192. data/lib/utils/parser.rb +21 -3
  193. metadata +48 -18
  194. data/docs/cli.md +0 -392
@@ -59,9 +59,11 @@ module Inspec::Resources
59
59
  os = inspec.os
60
60
  if os.linux?
61
61
  LinuxPorts.new(inspec)
62
- elsif %w{darwin aix}.include?(os[:family])
62
+ elsif os.aix?
63
63
  # AIX: see http://www.ibm.com/developerworks/aix/library/au-lsof.html#resources
64
64
  # and https://www-01.ibm.com/marketing/iwm/iwm/web/reg/pick.do?source=aixbp
65
+ AixPorts.new(inspec)
66
+ elsif os.darwin?
65
67
  # Darwin: https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/lsof.8.html
66
68
  LsofPorts.new(inspec)
67
69
  elsif os.windows?
@@ -263,11 +265,150 @@ module Inspec::Resources
263
265
  end
264
266
  end
265
267
 
268
+ class AixPorts < PortsInfo
269
+ def info
270
+ ports_via_netstat || ports_via_lsof
271
+ end
272
+
273
+ def ports_via_lsof
274
+ return nil unless inspec.command('lsof').exist?
275
+ LsofPorts.new(inspec).info
276
+ end
277
+
278
+ def ports_via_netstat
279
+ return nil unless inspec.command('netstat').exist?
280
+
281
+ cmd = inspec.command('netstat -Aan | grep LISTEN')
282
+ return nil unless cmd.exit_status.to_i.zero?
283
+
284
+ ports = []
285
+ # parse all lines
286
+ cmd.stdout.each_line do |line|
287
+ port_info = parse_netstat_line(line)
288
+
289
+ # only push protocols we are interested in
290
+ next unless %w{tcp tcp6 udp udp6}.include?(port_info['protocol'])
291
+ ports.push(port_info)
292
+ end
293
+
294
+ ports
295
+ end
296
+
297
+ def parse_netstat_line(line)
298
+ # parse each line
299
+ # 1 - Socket, 2 - Proto, 3 - Receive-Q, 4 - Send-Q, 5 - Local address, 6 - Foreign Address, 7 - State
300
+ parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)?\s+(\S+)/.match(line)
301
+ return {} if parsed.nil?
302
+
303
+ # parse ip4 and ip6 addresses
304
+ protocol = parsed[2].downcase
305
+
306
+ # detect protocol if not provided
307
+ protocol += '6' if parsed[5].count(':') > 1 && %w{tcp udp}.include?(protocol)
308
+ protocol.chop! if %w{tcp4 upd4}.include?(protocol)
309
+
310
+ # extract host and port information
311
+ host, port = parse_net_address(parsed[5], protocol)
312
+ return {} if host.nil?
313
+
314
+ # extract PID
315
+ cmd = inspec.command("rmsock #{parsed[1]} tcpcb")
316
+ parsed_pid = /^The socket (\S+) is being held by proccess (\d+) \((\S+)\)/.match(cmd.stdout)
317
+ return {} if parsed_pid.nil?
318
+ process = parsed_pid[3]
319
+ pid = parsed_pid[2]
320
+ pid = pid.to_i if pid =~ /^\d+$/
321
+
322
+ {
323
+ 'port' => port,
324
+ 'address' => host,
325
+ 'protocol' => protocol,
326
+ 'process' => process,
327
+ 'pid' => pid,
328
+ }
329
+ end
330
+
331
+ def parse_net_address(net_addr, protocol)
332
+ # local/foreign addresses on AIX use a '.' to separate the addresss
333
+ # from the port
334
+ address, _sep, port = net_addr.rpartition('.')
335
+ if protocol.eql?('tcp6') || protocol.eql?('udp6')
336
+ ip6addr = address
337
+ # AIX uses the wildcard character for ipv6 addresses listening on
338
+ # all interfaces.
339
+ ip6addr = '::' if ip6addr =~ /^\*$/
340
+
341
+ # v6 addresses need to end in a double-colon when using
342
+ # shorthand notation. netstat ends with a single colon.
343
+ # IPAddr will fail to properly parse an address unless it
344
+ # uses a double-colon for short-hand notation.
345
+ ip6addr += ':' if ip6addr =~ /\w:$/
346
+
347
+ begin
348
+ ip_parser = IPAddr.new(ip6addr)
349
+ rescue IPAddr::InvalidAddressError
350
+ # This IP is not parsable. There appears to be a bug in netstat
351
+ # output that truncates link-local IP addresses:
352
+ # example: udp6 0 0 fe80::42:acff:fe11::123 :::* 0 54550 3335/ntpd
353
+ # actual link address: inet6 fe80::42:acff:fe11:5/64 scope link
354
+ #
355
+ # in this example, the "5" is truncated making the netstat output
356
+ # an invalid IP address.
357
+ return [nil, nil]
358
+ end
359
+
360
+ # Check to see if this is a IPv4 address in a tcp6/udp6 line.
361
+ # If so, don't put brackets around the IP or URI won't know how
362
+ # to properly handle it.
363
+ # example: f000000000000000 tcp6 0 0 127.0.0.1.8005 *.* LISTEN
364
+ if ip_parser.ipv4?
365
+ ip_addr = URI("addr://#{ip6addr}:#{port}")
366
+ host = ip_addr.host
367
+ else
368
+ ip_addr = URI("addr://[#{ip6addr}]:#{port}")
369
+ host = ip_addr.host[1..ip_addr.host.size-2]
370
+ end
371
+ else
372
+ ip4addr = address
373
+ # In AIX the wildcard character is used to match all interfaces
374
+ ip4addr = '0.0.0.0' if ip4addr =~ /^\*$/
375
+ ip_addr = URI("addr://#{ip4addr}:#{port}")
376
+ host = ip_addr.host
377
+ end
378
+
379
+ [host, port.to_i]
380
+ end
381
+ end
382
+
266
383
  # extract port information from netstat
267
- class LinuxPorts < PortsInfo
384
+ class LinuxPorts < PortsInfo # rubocop:disable Metrics/ClassLength
385
+ ALLOWED_PROTOCOLS = %w{tcp tcp6 udp udp6}.freeze
386
+
268
387
  def info
388
+ ports_via_ss || ports_via_netstat
389
+ end
390
+
391
+ def ports_via_ss
392
+ return nil unless inspec.command('ss').exist?
393
+
394
+ cmd = inspec.command('ss -tulpen')
395
+ return nil unless cmd.exit_status.to_i.zero?
396
+
397
+ ports = []
398
+
399
+ cmd.stdout.each_line do |line|
400
+ parsed_line = parse_ss_line(line)
401
+ ports << parsed_line unless parsed_line.nil?
402
+ end
403
+
404
+ ports
405
+ end
406
+
407
+ def ports_via_netstat
408
+ return nil unless inspec.command('netstat').exist?
409
+
269
410
  cmd = inspec.command('netstat -tulpen')
270
- return nil if cmd.exit_status.to_i != 0
411
+ return nil unless cmd.exit_status.to_i.zero?
271
412
 
272
413
  ports = []
273
414
  # parse all lines
@@ -362,6 +503,113 @@ module Inspec::Resources
362
503
  'pid' => pid,
363
504
  }
364
505
  end
506
+
507
+ def tokenize_ss_line(line)
508
+ # iproute-2.6.32-54.el6 output:
509
+ # Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port
510
+ # udp UNCONN 0 0 *:111 *:* users:(("rpcbind",1123,6)) ino=8680 sk=ffff8801390cf7c0
511
+ # tcp LISTEN 0 128 *:22 *:* users:(("sshd",3965,3)) ino:11604 sk:ffff88013a3b5800
512
+ #
513
+ # iproute-2.6.32-20.el6 output:
514
+ # Netid Recv-Q Send-Q Local Address:Port Peer Address:Port
515
+ # udp 0 0 *:111 *:* users:(("rpcbind",1123,6)) ino=8680 sk=ffff8801390cf7c0
516
+ # tcp 0 128 *:22 *:* users:(("sshd",3965,3)) ino:11604 sk:ffff88013a3b5800
517
+ tokens = line.split(/\s+/, 7)
518
+ if tokens[1] =~ /^\d+$/ # iproute-2.6.32-20
519
+ {
520
+ netid: tokens[0],
521
+ local_addr: tokens[3],
522
+ process_info: tokens[5],
523
+ }
524
+ else # iproute-2.6.32-54
525
+ {
526
+ netid: tokens[0],
527
+ local_addr: tokens[4],
528
+ process_info: tokens[6],
529
+ }
530
+ end
531
+ end
532
+
533
+ def parse_ss_line(line)
534
+ # parsed = line.split(/\s+/, 7)
535
+ parsed = tokenize_ss_line(line)
536
+
537
+ # ss only returns "tcp" and "udp" as the protocol. However, netstat would return
538
+ # "tcp6" and "udp6" as necessary. In order to maintain backward compatibility, we
539
+ # will manually modify the protocol value if the line we're parsing is an IPv6
540
+ # entry.
541
+ process_info = parsed[:process_info]
542
+ protocol = parsed[:netid]
543
+ protocol += '6' if process_info.include?('v6only:1')
544
+ return nil unless ALLOWED_PROTOCOLS.include?(protocol)
545
+
546
+ # parse the Local Address:Port
547
+ # examples:
548
+ # *:22
549
+ # :::22
550
+ # 10.0.2.15:1234
551
+ # ::ffff:10.0.2.15:9300
552
+ # fe80::a00:27ff:fe32:ed09%enp0s3:9200
553
+ parsed_net_address = parsed[:local_addr].match(/(\S+):(\*|\d+)$/)
554
+ return nil if parsed_net_address.nil?
555
+ host = parsed_net_address[1]
556
+ port = parsed_net_address[2]
557
+ return nil if host.nil? && port.nil?
558
+
559
+ # For backward compatibility with the netstat output, ensure the
560
+ # port is stored as an integer
561
+ port = port.to_i
562
+
563
+ # for those "v4-but-listed-in-v6" entries, strip off the
564
+ # leading IPv6 value at the beginning
565
+ # example: ::ffff:10.0.2.15:9200
566
+ host.delete!('::ffff:') if host.start_with?('::ffff:')
567
+
568
+ # if there's an interface name in the local address, which is common for
569
+ # IPv6 listeners, strip that out too.
570
+ # example: fe80::a00:27ff:fe32:ed09%enp0s3
571
+ host = host.split('%').first
572
+
573
+ # if host is "*", replace with "0.0.0.0" to maintain backward compatibility with
574
+ # the netstat-provided data
575
+ host = '0.0.0.0' if host == '*'
576
+
577
+ # in case process list parsing is not successfull
578
+ process = nil
579
+ pid = nil
580
+
581
+ # parse process and pid from the process list
582
+ #
583
+ # remove the "users:((" and "))" parts
584
+ # input: users:((\"nginx\",pid=583,fd=8),(\"nginx\",pid=582,fd=8),(\"nginx\",pid=580,fd=8),(\"nginx\",pid=579,fd=8))
585
+ # res: \"nginx\",pid=583,fd=8),(\"nginx\",pid=582,fd=8),(\"nginx\",pid=580,fd=8),(\"nginx\",pid=579,fd=8
586
+ process_list_match = parsed[:process_info].match(/users:\(\((.+)\)\)/)
587
+ if process_list_match
588
+ # list entires are seperated by "," the braces can also be removed
589
+ # input: \"nginx\",pid=583,fd=8),(\"nginx\",pid=582,fd=8),(\"nginx\",pid=580,fd=8),(\"nginx\",pid=579,fd=8
590
+ # res: ["\"nginx\",pid=583,fd=8", "\"nginx\",pid=582,fd=8", "\"nginx\",pid=580,fd=8", "\"nginx\",pid=579,fd=8"]
591
+ process_list = process_list_match[1].split('),(')
592
+ # To stay backwards compatible with netstat we need to select
593
+ # the last element in the resulting array.
594
+ # res: "\"nginx\",pid=579,fd=8"
595
+
596
+ # parse the process name from the process list
597
+ process_match = process_list.last.match(/^\"(\S+)\"/)
598
+ process = process_match.nil? ? nil : process_match[1]
599
+
600
+ # parse the PID from the process list
601
+ pid_match = process_list.last.match(/pid=(\d+)/)
602
+ pid = pid_match.nil? ? nil : pid_match[1].to_i
603
+ end
604
+
605
+ {
606
+ 'port' => port,
607
+ 'address' => host,
608
+ 'protocol' => protocol,
609
+ 'process' => process,
610
+ 'pid' => pid,
611
+ }
612
+ end
365
613
  end
366
614
 
367
615
  # extracts information from sockstat
@@ -86,7 +86,7 @@ module Inspec::Resources
86
86
  if data_dir_loc.nil?
87
87
  warn 'Unable to find the PostgreSQL data_dir in expected location(s), please
88
88
  execute "psql -t -A -p <port> -h <host> -c "show hba_file";" as the PostgreSQL
89
- DBA to find the non-starndard data_dir location.'
89
+ DBA to find the non-standard data_dir location.'
90
90
  end
91
91
  data_dir_loc
92
92
  end
@@ -52,7 +52,7 @@ module Inspec::Resources
52
52
  cmd = inspec.command(psql_cmd)
53
53
  out = cmd.stdout + "\n" + cmd.stderr
54
54
  if cmd.exit_status != 0 || out =~ /could not connect to .*/ || out.downcase =~ /^error:.*/
55
- skip_resource "Can't read run query #{query.inspect} on postgres_session: #{out}"
55
+ Lines.new(out, "PostgreSQL query with errors: #{query}")
56
56
  else
57
57
  Lines.new(cmd.stdout.strip, "PostgreSQL query: #{query}")
58
58
  end
@@ -4,9 +4,10 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  require 'utils/filter'
7
+ require 'ostruct'
7
8
 
8
9
  module Inspec::Resources
9
- class Processes < Inspec.resource(1)
10
+ class Processes < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
10
11
  name 'processes'
11
12
  desc 'Use the processes InSpec audit resource to test properties for programs that are running on the system.'
12
13
  example "
@@ -15,12 +16,18 @@ module Inspec::Resources
15
16
  its('users') { should eq ['mysql'] }
16
17
  its('states') { should include 'S' }
17
18
  end
19
+
18
20
  describe processes(/.+/).where { label != 'unconfined' && pid < 1000 } do
19
21
  its('users') { should cmp [] }
20
22
  end
23
+
24
+ # work with all processes
25
+ describe processes do
26
+ its('entries.length') { should be <= 100 }
27
+ end
21
28
  "
22
29
 
23
- def initialize(grep)
30
+ def initialize(grep = /.*/)
24
31
  @grep = grep
25
32
  # turn into a regexp if it isn't one yet
26
33
  if grep.class == String
@@ -80,39 +87,118 @@ module Inspec::Resources
80
87
  os = inspec.os
81
88
 
82
89
  if os.linux?
83
- command = 'ps axo label,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user:32,command'
84
- regex = /^([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(\w{3} \d{2}|\d{2}:\d{2}:\d{2})\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
90
+ command, regex, field_map = ps_configuration_for_linux
85
91
  elsif os.windows?
86
92
  command = '$Proc = Get-Process -IncludeUserName | Where-Object {$_.Path -ne $null } | Select-Object PriorityClass,Id,CPU,PM,VirtualMemorySize,NPM,SessionId,Responding,StartTime,TotalProcessorTime,UserName,Path | ConvertTo-Csv -NoTypeInformation;$Proc.Replace("""","").Replace("`r`n","`n")'
87
93
  # Wanted to use /(?:^|,)([^,]*)/; works on rubular.com not sure why here?
88
94
  regex = /^(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+)$/
95
+ field_map = {
96
+ pid: 2,
97
+ cpu: 3,
98
+ mem: 4,
99
+ vsz: 5,
100
+ rss: 6,
101
+ tty: 7,
102
+ stat: 8,
103
+ start: 9,
104
+ time: 10,
105
+ user: 11,
106
+ command: 12,
107
+ }
89
108
  else
90
109
  command = 'ps axo pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user,command'
91
110
  regex = /^\s*([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
111
+ field_map = {
112
+ pid: 1,
113
+ cpu: 2,
114
+ mem: 3,
115
+ vsz: 4,
116
+ rss: 5,
117
+ tty: 6,
118
+ stat: 7,
119
+ start: 8,
120
+ time: 9,
121
+ user: 10,
122
+ command: 11,
123
+ }
92
124
  end
93
- build_process_list(command, regex, os)
125
+ build_process_list(command, regex, field_map)
94
126
  end
95
127
 
96
- Process = Struct.new(:label, :pid,
97
- :cpu, :mem, :vsz,
98
- :rss, :tty, :stat,
99
- :start, :time, :user, :command)
128
+ def ps_configuration_for_linux
129
+ if busybox_ps?
130
+ command = 'ps -o pid,vsz,rss,tty,stat,time,ruser,args'
131
+ regex = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/
132
+ field_map = {
133
+ pid: 1,
134
+ vsz: 2,
135
+ rss: 3,
136
+ tty: 4,
137
+ stat: 5,
138
+ time: 6,
139
+ user: 7,
140
+ command: 8,
141
+ }
142
+ else
143
+ command = 'ps axo label,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user:32,command'
144
+ regex = /^(.+?)\s+(\d+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(\w{3} \d{2}|\d{2}:\d{2}:\d{2})\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
145
+ field_map = {
146
+ label: 1,
147
+ pid: 2,
148
+ cpu: 3,
149
+ mem: 4,
150
+ vsz: 5,
151
+ rss: 6,
152
+ tty: 7,
153
+ stat: 8,
154
+ start: 9,
155
+ time: 10,
156
+ user: 11,
157
+ command: 12,
158
+ }
159
+ end
160
+
161
+ [command, regex, field_map]
162
+ end
163
+
164
+ def busybox_ps?
165
+ @busybox_ps ||= inspec.command('ps --help').stderr.include?('BusyBox')
166
+ end
100
167
 
101
- def build_process_list(command, regex, os)
168
+ def build_process_list(command, regex, field_map)
102
169
  cmd = inspec.command(command)
103
170
  all = cmd.stdout.split("\n")[1..-1]
104
171
  return [] if all.nil?
105
- lines = all.map do |line|
106
- line.match(regex)
172
+
173
+ # map all the process lines into match objects, fetch the available fields,
174
+ # and then build an OpenStruct of the process data for each process
175
+ all.map do |line|
176
+ line = line.match(regex)
177
+
178
+ # skip this line if we couldn't match the regular expression
179
+ next if line.nil?
180
+
181
+ # skip this entry if there's no command for this line
182
+ next if line[field_map[:command]].nil?
183
+
184
+ # build a hash of process data that we'll turn into a struct for FilterTable
185
+ process_data = {}
186
+ [:label, :pid, :cpu, :mem, :vsz, :rss, :tty, :stat, :start, :time, :user, :command].each do |param|
187
+ # not all operating systems support all fields, so skip the field if we don't have it
188
+ process_data[param] = line[field_map[param]] if field_map.key?(param)
189
+ end
190
+
191
+ # ensure pid, vsz, and rss are integers for backward compatibility
192
+ [:pid, :vsz, :rss].each do |int_param|
193
+ process_data[int_param] = process_data[int_param].to_i if process_data.key?(int_param)
194
+ end
195
+
196
+ # strip any newlines off the command
197
+ process_data[:command].strip!
198
+
199
+ # return an OpenStruct of the process for future use by FilterTable
200
+ OpenStruct.new(process_data)
107
201
  end.compact
108
- lines.map do |m|
109
- a = m.to_a[1..-1] # grab all matching groups
110
- a.unshift(nil) unless os.linux? || os.windows?
111
- a[1] = a[1].to_i
112
- a[4] = a[4].to_i
113
- a[5] = a[5].to_i
114
- Process.new(*a)
115
- end
116
202
  end
117
203
  end
118
204
  end
@@ -106,10 +106,19 @@ module Inspec::Resources
106
106
  end
107
107
 
108
108
  # returns nil, if not existant or value
109
- def method_missing(meth)
109
+ def method_missing(*keys)
110
+ # allow the use of array syntax in an `its` block so that users
111
+ # can use it to query for keys with . characters in them
112
+ if keys.is_a?(Array)
113
+ keys.shift if keys[0] == :[]
114
+ key = keys.first
115
+ else
116
+ key = keys
117
+ end
118
+
110
119
  # get data
111
120
  val = registry_key(@options[:path])
112
- registry_property_value(val, meth)
121
+ registry_property_value(val, key)
113
122
  end
114
123
 
115
124
  def to_s
@@ -349,40 +349,21 @@ module Inspec::Resources
349
349
  description: nil,
350
350
  installed: true,
351
351
  running: running,
352
- enabled: info_enabled(status, service_name),
352
+ enabled: info_enabled(service_name),
353
353
  type: 'upstart',
354
354
  }
355
355
  end
356
356
 
357
357
  private
358
358
 
359
- def info_enabled(status, service_name)
359
+ def info_enabled(service_name)
360
360
  # check if a service is enabled
361
- # http://upstart.ubuntu.com/cookbook/#determine-if-a-job-is-disabled
362
- # $ initctl show-config $job | grep -q "^ start on" && echo enabled || echo disabled
363
- # Ubuntu 10.04 show-config is not supported
364
- # @see http://manpages.ubuntu.com/manpages/maverick/man8/initctl.8.html
365
- support_for_show_config = Gem::Version.new('1.3')
366
-
367
- if version >= support_for_show_config
368
- config = inspec.command("#{service_ctl} show-config #{service_name}").stdout
369
- else # use config file as fallback
370
- config = inspec.file("/etc/init/#{service_name}.conf").content
371
- end
361
+ config = inspec.file("/etc/init/#{service_name}.conf").content
372
362
 
373
363
  # disregard if the config does not exist
374
364
  return nil if config.nil?
375
- enabled = !config[/^\s*start on/].nil?
376
-
377
- # implement fallback for Ubuntu 10.04
378
- if inspec.os[:name] == 'ubuntu' &&
379
- inspec.os[:release].to_f >= 10.04 &&
380
- inspec.os[:release].to_f < 12.04 &&
381
- status.exit_status == 0
382
- enabled = true
383
- end
384
365
 
385
- enabled
366
+ !config.match(/^\s*start on/).nil?
386
367
  end
387
368
 
388
369
  def version
@@ -416,7 +397,7 @@ module Inspec::Resources
416
397
  # read all enabled services from runlevel
417
398
  # on rhel via: 'chkconfig --list', is not installed by default
418
399
  # bash: for i in `find /etc/rc*.d -name S*`; do basename $i | sed -r 's/^S[0-9]+//'; done | sort | uniq
419
- enabled_services_cmd = inspec.command('find /etc/rc*.d /etc/init.d/rc*.d -name S*').stdout
400
+ enabled_services_cmd = inspec.command('find /etc/rc*.d /etc/init.d/rc*.d -name "S*"').stdout
420
401
  service_line = %r{rc(?<runlevel>[0-6])\.d/S[^/]*?#{Regexp.escape service_name}$}
421
402
  all_services = enabled_services_cmd.split("\n").map { |line|
422
403
  service_line.match(line)
@@ -63,7 +63,7 @@ module Inspec::Resources
63
63
  end
64
64
 
65
65
  @content = file.content
66
- if @content.empty? && !file.empty?
66
+ if @content.nil? || (@content.empty? && !file.size.zero?)
67
67
  return skip_resource "Can't read file \"#{@conf_path}\""
68
68
  end
69
69
 
data/lib/resources/ssl.rb CHANGED
@@ -50,8 +50,6 @@ class SSL < Inspec.resource(1)
50
50
  @host = inspec.backend.hostname
51
51
  elsif inspec.backend.class.to_s == 'Train::Transports::Local::Connection'
52
52
  @host = 'localhost'
53
- else
54
- raise 'Cannot determine host for SSL test. Please specify it or use a different target.'
55
53
  end
56
54
  end
57
55
  @port = opts[:port] || 443
@@ -60,11 +58,14 @@ class SSL < Inspec.resource(1)
60
58
  end
61
59
 
62
60
  filter = FilterTable.create
61
+ filter.add(:enabled?) do |x|
62
+ raise 'Cannot determine host for SSL test. Please specify it or use a different target.' if x.resource.host.nil?
63
+ x.handshake.values.any? { |i| i['success'] }
64
+ end
63
65
  filter.add_accessor(:where)
64
66
  .add_accessor(:entries)
65
67
  .add(:ciphers, field: 'cipher')
66
68
  .add(:protocols, field: 'protocol')
67
- .add(:enabled?) { |x| x.handshake.values.any? { |i| i['success'] } }
68
69
  .add(:handshake) { |x|
69
70
  groups = x.entries.group_by(&:protocol)
70
71
  res = Parallel.map(groups, in_threads: 8) do |proto, e|
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
  # author: Nolan Davidson
3
3
 
4
- require 'toml'
4
+ require 'tomlrb'
5
5
 
6
6
  module Inspec::Resources
7
7
  class TomlConfig < JsonConfig
@@ -16,7 +16,7 @@ module Inspec::Resources
16
16
  "
17
17
 
18
18
  def parse(content)
19
- TOML::Parser.new(content).parsed
19
+ Tomlrb.parse(content)
20
20
  end
21
21
 
22
22
  def to_s
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ # author: Matt Ray
3
+
4
+ module Inspec::Resources
5
+ class WindowsHotfix < Inspec.resource(1)
6
+ name 'windows_hotfix'
7
+ desc 'Use the windows_hotfix InSpec audit resource to test if the hotfix has been installed on the Windows system.'
8
+ example "
9
+ describe windows_hotfix('KB4012212') do
10
+ it { should be_installed }
11
+ end
12
+ "
13
+
14
+ attr_accessor :content
15
+
16
+ def initialize(hotfix_id = nil)
17
+ @id = hotfix_id.upcase
18
+ @content = nil
19
+ os = inspec.os
20
+ return skip_resource 'The `windows_hotfix` resource is not a feature of your OS.' unless os.windows?
21
+ query = "get-hotfix -id #{@id}"
22
+ cmd = inspec.powershell(query)
23
+ @content = cmd.stdout
24
+ end
25
+
26
+ def to_s
27
+ "Windows Hotfix #{@id}"
28
+ end
29
+
30
+ def installed?
31
+ return false if @content.nil?
32
+ @content.include?(@id)
33
+ end
34
+ end
35
+ end
@@ -53,15 +53,14 @@ module Inspec::Resources
53
53
  return @contents[path] if @contents.key?(path)
54
54
  file = inspec.file(path)
55
55
  if !file.file?
56
- return skip_resource "Can't find file \"#{path}\""
56
+ raise Inspec::Exceptions::ResourceSkipped, "Can't find file: #{path}"
57
57
  end
58
58
 
59
- @contents[path] = file.content
60
- if @contents[path].nil? || @contents[path].empty?
61
- return skip_resource "Can't read file \"#{path}\""
59
+ if file.content.nil? || file.content.empty?
60
+ raise Inspec::Exceptions::ResourceSkipped, "Can't read file: #{path}"
62
61
  end
63
62
 
64
- @contents[path]
63
+ @contents[path] = file.content
65
64
  end
66
65
 
67
66
  def read_params
@@ -69,7 +68,6 @@ module Inspec::Resources
69
68
  flat_params = parse_xinetd(read_content)
70
69
  # we need to map service data in order to use it with filtertable
71
70
  params = { 'services' => {} }
72
-
73
71
  # map services that were defined and map it to the service hash
74
72
  flat_params.each do |k, v|
75
73
  name = k[/^service (.+)$/, 1]