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
@@ -84,6 +84,13 @@ module Inspec::Resources
84
84
  file_permission_granted?('execute', by_usergroup, by_specific_user)
85
85
  end
86
86
 
87
+ def allowed?(permission, opts = {})
88
+ return false unless exist?
89
+ return skip_resource '`allowed?` is not supported on your OS yet.' if @perms_provider.nil?
90
+
91
+ file_permission_granted?(permission, opts[:by], opts[:by_user])
92
+ end
93
+
87
94
  def mounted?(expected_options = nil, identical = false)
88
95
  mounted = file.mounted
89
96
 
@@ -112,14 +119,20 @@ module Inspec::Resources
112
119
  (mode & 04000) > 0
113
120
  end
114
121
 
122
+ alias setuid? suid
123
+
115
124
  def sgid
116
125
  (mode & 02000) > 0
117
126
  end
118
127
 
128
+ alias setgid? sgid
129
+
119
130
  def sticky
120
131
  (mode & 01000) > 0
121
132
  end
122
133
 
134
+ alias sticky? sticky
135
+
123
136
  def to_s
124
137
  "File #{source_path}"
125
138
  end
@@ -200,18 +213,82 @@ module Inspec::Resources
200
213
  end
201
214
 
202
215
  def check_file_permission_by_user(access_type, user, path)
203
- access_rule = case access_type
204
- when 'read'
205
- '@(\'FullControl\', \'Modify\', \'ReadAndExecute\', \'Read\', \'ListDirectory\')'
206
- when 'write'
207
- '@(\'FullControl\', \'Modify\', \'Write\')'
208
- when 'execute'
209
- '@(\'FullControl\', \'Modify\', \'ReadAndExecute\', \'ExecuteFile\')'
210
- else
211
- raise 'Invalid access_type provided'
212
- end
216
+ access_rule = translate_perm_names(access_type)
217
+ access_rule = convert_to_powershell_array(access_rule)
218
+
213
219
  cmd = inspec.command("@(@((Get-Acl '#{path}').access | Where-Object {$_.AccessControlType -eq 'Allow' -and $_.IdentityReference -eq '#{user}' }) | Where-Object {($_.FileSystemRights.ToString().Split(',') | % {$_.trim()} | ? {#{access_rule} -contains $_}) -ne $null}) | measure | % { $_.Count }")
214
220
  cmd.stdout.chomp == '0' ? false : true
215
221
  end
222
+
223
+ private
224
+
225
+ def convert_to_powershell_array(arr)
226
+ if arr.empty?
227
+ '@()'
228
+ else
229
+ %{@('#{arr.join("', '")}')}
230
+ end
231
+ end
232
+
233
+ # Translates a developer-friendly string into a list of acceptable
234
+ # FileSystemRights that match it, because Windows has a fun heirarchy
235
+ # of permissions that are able to be noted in multiple ways.
236
+ #
237
+ # See also: https://www.codeproject.com/Reference/871338/AccessControl-FileSystemRights-Permissions-Table
238
+ def translate_perm_names(access_type)
239
+ names = translate_common_perms(access_type)
240
+ names ||= translate_granular_perms(access_type)
241
+ names ||= translate_uncommon_perms(access_type)
242
+ raise 'Invalid access_type provided' unless names
243
+ end
244
+
245
+ def translate_common_perms(access_type)
246
+ case access_type
247
+ when 'full-control'
248
+ %w{FullControl}
249
+ when 'modify'
250
+ translate_perm_names('full-control') + %w{Modify}
251
+ when 'read'
252
+ translate_perm_names('modify') + %w{ReadAndExecute Read}
253
+ when 'write'
254
+ translate_perm_names('modify') + %w{Write}
255
+ when 'execute'
256
+ translate_perm_names('modify') + %w{ReadAndExecute ExecuteFile Traverse}
257
+ when 'delete'
258
+ translate_perm_names('modify') + %w{Delete}
259
+ end
260
+ end
261
+
262
+ def translate_uncommon_perms(access_type)
263
+ case access_type
264
+ when 'delete-subdirectories-and-files'
265
+ translate_perm_names('full-control') + %w{DeleteSubdirectoriesAndFiles}
266
+ when 'change-permissions'
267
+ translate_perm_names('full-control') + %w{ChangePermissions}
268
+ when 'take-ownership'
269
+ translate_perm_names('full-control') + %w{TakeOwnership}
270
+ end
271
+ end
272
+
273
+ def translate_granular_perms(access_type)
274
+ case access_type
275
+ when 'write-data', 'create-files'
276
+ translate_perm_names('write') + %w{WriteData CreateFiles}
277
+ when 'append-data', 'create-directories'
278
+ translate_perm_names('write') + %w{CreateDirectories AppendData}
279
+ when 'write-extended-attributes'
280
+ translate_perm_names('write') + %w{WriteExtendedAttributes}
281
+ when 'write-attributes'
282
+ translate_perm_names('write') + %w{WriteAttributes}
283
+ when 'read-data', 'list-directory'
284
+ translate_perm_names('read') + %w{ReadData ListDirectory}
285
+ when 'read-attributes'
286
+ translate_perm_names('read') + %w{ReadAttributes}
287
+ when 'read-extended-attributes'
288
+ translate_perm_names('read') + %w{ReadExtendedAttributes}
289
+ when 'read-permissions'
290
+ translate_perm_names('read') + %w{ReadPermissions}
291
+ end
292
+ end
216
293
  end
217
294
  end
@@ -0,0 +1,144 @@
1
+ # encoding: utf-8
2
+ # author: Matthew
3
+
4
+ module Inspec::Resources
5
+ class FirewallD < Inspec.resource(1)
6
+ ###
7
+ # This recourse assumes that the file sudo vim /etc/polkit-1/rules.d/49-nopasswd_global.rules has been
8
+ # set to allow users in group "wheel" to perform any commands without authentication.
9
+ ###
10
+
11
+ name 'firewalld'
12
+ desc 'Use the firewalld resource to check and see if firewalld is configured to grand or deny access to specific hosts or services'
13
+ example "
14
+ describe firewalld do
15
+ it { should be_running }
16
+ its('default_zone') { should eq 'public' }
17
+ it { should have_service_enabled_in_zone('ssh', 'public') }
18
+ it { should have_rule_enabled('rule family=ipv4 source address=192.168.0.14 accept', 'public') }
19
+ end
20
+
21
+ describe firewalld.where { zone == 'public' } do
22
+ its('interfaces') { should cmp ['enp0s3', 'eno2'] }
23
+ its('sources') { should cmp ['ssh', 'icmp'] }
24
+ its('services') { should cmp ['192.168.1.0/24', '192.168.1.2'] }
25
+ end
26
+ "
27
+
28
+ attr_reader :params
29
+
30
+ filter = FilterTable.create
31
+ filter.add_accessor(:where)
32
+ .add_accessor(:entries)
33
+ .add(:zone, field: 'zone')
34
+ .add(:interfaces, field: 'interfaces')
35
+ .add(:sources, field: 'sources')
36
+ .add(:services, field: 'services')
37
+
38
+ filter.connect(self, :params)
39
+
40
+ def initialize
41
+ return skip_resource 'The `etc_hosts_deny` resource is not supported on your OS.' unless inspec.os.linux?
42
+ @params = parse_active_zones(active_zones)
43
+ end
44
+
45
+ def installed?
46
+ inspec.command('firewall-cmd').exist?
47
+ end
48
+
49
+ def has_zone?(query_zone)
50
+ return false unless installed?
51
+ result = firewalld_command('--get-zones').split(' ')
52
+ result.include?(query_zone)
53
+ end
54
+
55
+ def running?
56
+ return false unless installed?
57
+ result = firewalld_command('--state')
58
+ result =~ /^running/ ? true : false
59
+ end
60
+
61
+ def default_zone
62
+ # return: word associated with the name of the default zone
63
+ # example: 'public'
64
+ firewalld_command('--get-default-zone')
65
+ end
66
+
67
+ def has_service_enabled_in_zone?(query_service, query_zone = default_zone)
68
+ firewalld_command("--zone=#{query_zone} --query-service=#{query_service}") == 'yes'
69
+ end
70
+
71
+ def service_ports_enabled_in_zone(query_service, query_zone = default_zone)
72
+ # return: String of ports open
73
+ # example: ['22/tcp', '4722/tcp']
74
+ firewalld_command("--zone=#{query_zone} --service=#{query_service} --get-ports --permanent").split(' ')
75
+ end
76
+
77
+ def service_protocols_enabled_in_zone(query_service, query_zone = default_zone)
78
+ # return: String of protocoals open
79
+ # example: ['icmp', 'ipv4', 'igmp']
80
+ firewalld_command("--zone=#{query_zone} --service=#{query_service} --get-protocols --permanent").split(' ')
81
+ end
82
+
83
+ def has_port_enabled_in_zone?(query_port, query_zone = default_zone)
84
+ firewalld_command("--zone=#{query_zone} --query-port=#{query_port}") == 'yes'
85
+ end
86
+
87
+ def has_rule_enabled?(rule, query_zone = default_zone)
88
+ rule = 'rule ' + rule
89
+ firewalld_command("--zone=#{query_zone} --query-rich-rule=#{rule}") == 'yes'
90
+ end
91
+
92
+ private
93
+
94
+ def active_zones
95
+ # return syntax:
96
+ # [default-zone-name]
97
+ # interfaces: [open interfases]
98
+ #
99
+ # example:
100
+ # public
101
+ # interfaces: enp0s3
102
+ firewalld_command('--get-active-zones')
103
+ end
104
+
105
+ def parse_active_zones(content)
106
+ # Split by every second line, which contains the zone and the interfaces.
107
+ content = content.split(/\n/).each_slice(2).map { |slice| slice.join("\n") }
108
+ content.map do |line|
109
+ parse_line(line)
110
+ end.compact
111
+ end
112
+
113
+ def parse_line(line)
114
+ zone = line.split("\n")[0]
115
+ {
116
+ 'zone' => zone,
117
+ 'interfaces' => line.split(':')[1].split(' '),
118
+ 'services' => services_bound(zone),
119
+ 'sources' => sources_bound(zone),
120
+ }
121
+ end
122
+
123
+ def sources_bound(query_zone)
124
+ # result: a list containing either an ip address or ip address with a mask, or a ipset or an ipset with the ipset prefix.
125
+ # example: ['192.168.0.4', '192.168.0.0/16', '2111:DB28:ABC:12::', '2111:db89:ab3d:0112::0/64']
126
+ firewalld_command("--zone=#{query_zone} --list-sources").split(' ')
127
+ end
128
+
129
+ def services_bound(query_zone)
130
+ # result: a list of services bound to a zone.
131
+ # example: ['ssh', 'dhcpv6-client']
132
+ firewalld_command("--zone=#{query_zone} --list-services").split(' ')
133
+ end
134
+
135
+ def firewalld_command(command)
136
+ command = "firewall-cmd #{command}"
137
+ result = inspec.command(command)
138
+ if result.stderr != ''
139
+ return "Error on command #{command}: #{result.stderr}"
140
+ end
141
+ result.stdout.strip
142
+ end
143
+ end
144
+ end
@@ -90,7 +90,6 @@ module Inspec::Resources
90
90
 
91
91
  def initialize(groupname)
92
92
  @group = groupname
93
- @group = @group.downcase unless inspec.os.windows?
94
93
 
95
94
  # select group manager
96
95
  @group_provider = select_group_manager(inspec.os)
@@ -113,7 +113,10 @@ module Inspec::Resources
113
113
  end
114
114
 
115
115
  def to_s
116
- "Host #{hostname}"
116
+ resource_name = "Host #{hostname}"
117
+ resource_name += " port #{port} proto #{protocol}" if port
118
+
119
+ resource_name
117
120
  end
118
121
 
119
122
  private
@@ -148,17 +151,17 @@ module Inspec::Resources
148
151
  def resolve_with_dig(hostname)
149
152
  addresses = []
150
153
 
151
- # look for IPv6 addresses
152
- cmd = inspec.command("dig +short AAAA #{hostname}")
154
+ # look for IPv4 addresses
155
+ cmd = inspec.command("dig +short A #{hostname}")
153
156
  cmd.stdout.lines.each do |line|
154
- matched = line.chomp.match(Resolv::IPv6::Regex)
157
+ matched = line.chomp.match(Resolv::IPv4::Regex)
155
158
  addresses << matched.to_s unless matched.nil?
156
159
  end
157
160
 
158
- # look for IPv4 addresses
159
- cmd = inspec.command("dig +short A #{hostname}")
161
+ # look for IPv6 addresses
162
+ cmd = inspec.command("dig +short AAAA #{hostname}")
160
163
  cmd.stdout.lines.each do |line|
161
- matched = line.chomp.match(Resolv::IPv4::Regex)
164
+ matched = line.chomp.match(Resolv::IPv6::Regex)
162
165
  addresses << matched.to_s unless matched.nil?
163
166
  end
164
167
 
@@ -166,13 +169,25 @@ module Inspec::Resources
166
169
  end
167
170
 
168
171
  def resolve_with_getent(hostname)
169
- # TODO: we rely on getent hosts for now, but it prefers to return IPv6, only then IPv4
170
- cmd = inspec.command("getent hosts #{hostname}")
171
- return nil if cmd.exit_status.to_i != 0
172
+ cmd = inspec.command("getent ahosts #{hostname}")
173
+ return nil unless cmd.exit_status.to_i.zero?
174
+
175
+ # getent ahosts output is formatted like so:
176
+ # $ getent ahosts www.google.com
177
+ # 172.217.8.4 STREAM www.google.com
178
+ # 172.217.8.4 DGRAM
179
+ # 172.217.8.4 RAW
180
+ # 2607:f8b0:4004:803::2004 STREAM
181
+ # 2607:f8b0:4004:803::2004 DGRAM
182
+ # 2607:f8b0:4004:803::2004 RAW
183
+ addresses = []
184
+ cmd.stdout.lines.each do |line|
185
+ ip, = line.split(/\s+/, 2)
186
+ next unless ip.match(Resolv::IPv4::Regex) || ip.match(Resolv::IPv6::Regex)
187
+ addresses << ip unless addresses.include?(ip)
188
+ end
172
189
 
173
- # extract ip adress
174
- resolve = /^\s*(?<ip>\S+)\s+(.*)\s*$/.match(cmd.stdout.chomp)
175
- [resolve[1]] if resolve
190
+ addresses
176
191
  end
177
192
  end
178
193
 
@@ -210,7 +225,7 @@ module Inspec::Resources
210
225
  def missing_requirements(protocol)
211
226
  missing = []
212
227
 
213
- if protocol == 'tcp' && (!inspec.command('nc').exist? || !inspec.command('ncat').exist?)
228
+ if protocol == 'tcp' && (!inspec.command('nc').exist? && !inspec.command('ncat').exist?)
214
229
  missing << 'netcat must be installed'
215
230
  end
216
231
 
@@ -245,7 +260,7 @@ module Inspec::Resources
245
260
  end
246
261
 
247
262
  def resolve(hostname)
248
- inspec.command('dig').exist? ? resolve_with_dig(hostname) : resolve_with_getent(hostname)
263
+ resolve_with_getent(hostname)
249
264
  end
250
265
  end
251
266
 
@@ -22,50 +22,206 @@ module Inspec::Resources
22
22
  its('Content-Length') { should cmp 258 }
23
23
  its('Content-Type') { should cmp 'text/html; charset=UTF-8' }
24
24
  end
25
+
26
+ # properly execute the HTTP call on the scanned machine instead of the
27
+ # machine executing InSpec. This will be the default behavior in InSpec 2.0.
28
+ describe http('http://localhost:8080', enable_remote_worker: true) do
29
+ its('body') { should cmp 'local web server on target machine' }
30
+ end
25
31
  "
26
32
 
27
33
  def initialize(url, opts = {})
28
34
  @url = url
29
- @method = opts.fetch(:method, 'GET')
30
- @params = opts.fetch(:params, nil)
31
- @auth = opts.fetch(:auth, {})
32
- @headers = opts.fetch(:headers, {})
33
- @data = opts.fetch(:data, nil)
34
- @open_timeout = opts.fetch(:open_timeout, 60)
35
- @read_timeout = opts.fetch(:read_timeout, 60)
36
- @ssl_verify = opts.fetch(:ssl_verify, true)
35
+ @opts = opts
36
+
37
+ if use_remote_worker?
38
+ return skip_resource 'curl is not available on the target machine' unless inspec.command('curl').exist?
39
+ @worker = Worker::Remote.new(inspec, http_method, url, opts)
40
+ else
41
+ @worker = Worker::Local.new(http_method, url, opts)
42
+ end
37
43
  end
38
44
 
39
45
  def status
40
- response.status
46
+ @worker.status
47
+ end
48
+
49
+ def headers
50
+ Hashie::Mash.new(@worker.response_headers)
41
51
  end
42
52
 
43
53
  def body
44
- response.body
54
+ @worker.body
45
55
  end
46
56
 
47
- def headers
48
- Hashie::Mash.new(response.headers.to_h)
57
+ def http_method
58
+ @opts.fetch(:method, 'GET')
49
59
  end
50
60
 
51
61
  def to_s
52
- "http #{@method} on #{@url}"
62
+ "http #{http_method} on #{@url}"
53
63
  end
54
64
 
55
65
  private
56
66
 
57
- def response
58
- conn = Faraday.new url: @url, headers: @headers, params: @params, ssl: { verify: @ssl_verify }
67
+ def use_remote_worker?
68
+ return false if inspec.local_transport?
69
+ return true if @opts[:enable_remote_worker]
70
+
71
+ warn "[DEPRECATION] #{self} will execute locally instead of the target machine. To execute remotely, add `enable_remote_worker: true`."
72
+ warn '[DEPRECATION] `enable_remote_worker: true` will be the default behavior in InSpec 2.0.'
73
+ false
74
+ end
75
+
76
+ class Worker
77
+ class Base
78
+ attr_reader :http_method, :opts, :url
79
+
80
+ def initialize(http_method, url, opts)
81
+ @http_method = http_method
82
+ @url = url
83
+ @opts = opts
84
+ end
85
+
86
+ private
87
+
88
+ def params
89
+ opts.fetch(:params, nil)
90
+ end
91
+
92
+ def username
93
+ opts.fetch(:auth, {})[:user]
94
+ end
95
+
96
+ def password
97
+ opts.fetch(:auth, {})[:pass]
98
+ end
99
+
100
+ def request_headers
101
+ opts.fetch(:headers, {})
102
+ end
103
+
104
+ def request_body
105
+ opts[:data]
106
+ end
107
+
108
+ def open_timeout
109
+ opts.fetch(:open_timeout, 60)
110
+ end
111
+
112
+ def read_timeout
113
+ opts.fetch(:read_timeout, 60)
114
+ end
115
+
116
+ def ssl_verify?
117
+ opts.fetch(:ssl_verify, true)
118
+ end
119
+ end
120
+
121
+ class Local < Base
122
+ def status
123
+ response.status
124
+ end
125
+
126
+ def body
127
+ response.body
128
+ end
129
+
130
+ def response_headers
131
+ response.headers.to_h
132
+ end
133
+
134
+ private
135
+
136
+ def response
137
+ return @response if @response
138
+ conn = Faraday.new url: url, headers: request_headers, params: params, ssl: { verify: ssl_verify? }
139
+
140
+ # set basic authentication
141
+ conn.basic_auth username, password unless username.nil? || password.nil?
142
+
143
+ # set default timeout
144
+ conn.options.timeout = read_timeout # open/read timeout in seconds
145
+ conn.options.open_timeout = open_timeout # connection open timeout in seconds
146
+
147
+ @response = conn.send(http_method.downcase) do |req|
148
+ req.body = request_body
149
+ end
150
+ end
151
+ end
152
+
153
+ class Remote < Base
154
+ attr_reader :inspec
155
+
156
+ def initialize(inspec, http_method, url, opts)
157
+ @inspec = inspec
158
+ super(http_method, url, opts)
159
+ end
160
+
161
+ def status
162
+ run_curl
163
+ @status
164
+ end
165
+
166
+ def body
167
+ run_curl
168
+ @body&.strip
169
+ end
170
+
171
+ def response_headers
172
+ run_curl
173
+ @response_headers
174
+ end
175
+
176
+ private
177
+
178
+ def run_curl
179
+ return if @ran_curl
180
+
181
+ response = inspec.command(curl_command).stdout
182
+ @ran_curl = true
183
+ return if response.nil?
184
+
185
+ # strip any carriage returns to normalize output
186
+ response.delete!("\r")
187
+
188
+ # split the prelude (status line and headers) and the body
189
+ prelude, @body = response.split("\n\n", 2)
190
+ prelude = prelude.lines
191
+
192
+ # grab the status off of the first line of the prelude
193
+ status_line = prelude.shift
194
+ @status = status_line.split(' ', 3)[1].to_i
195
+
196
+ # parse the rest of the prelude which will be all the HTTP headers
197
+ @response_headers = {}
198
+ prelude.each do |line|
199
+ line.strip!
200
+ key, value = line.split(':', 2)
201
+ @response_headers[key] = value.strip
202
+ end
203
+ end
204
+
205
+ def curl_command # rubocop:disable Metrics/AbcSize
206
+ cmd = ["curl -i -X #{http_method}"]
207
+ cmd << "--connect-timeout #{open_timeout}"
208
+ cmd << "--max-time #{open_timeout+read_timeout}"
209
+ cmd << "--user \'#{username}:#{password}\'" unless username.nil? || password.nil?
210
+ cmd << '--insecure' unless ssl_verify?
211
+ cmd << "--data #{Shellwords.shellescape(request_body)}" unless request_body.nil?
59
212
 
60
- # set basic authentication
61
- conn.basic_auth @auth[:user], @auth[:pass] unless @auth.empty?
213
+ request_headers.each do |k, v|
214
+ cmd << "-H '#{k}: #{v}'"
215
+ end
62
216
 
63
- # set default timeout
64
- conn.options.timeout = @read_timeout # open/read timeout in seconds
65
- conn.options.open_timeout = @open_timeout # connection open timeout in seconds
217
+ if params.nil?
218
+ cmd << "'#{url}'"
219
+ else
220
+ cmd << "'#{url}?#{params.map { |e| e.join('=') }.join('&')}'"
221
+ end
66
222
 
67
- @response = conn.send(@method.downcase) do |req|
68
- req.body = @data
223
+ cmd.join(' ')
224
+ end
69
225
  end
70
226
  end
71
227
  end
@@ -51,11 +51,11 @@ module Inspec::Resources
51
51
  escaped_query = q.gsub(/\\/, '\\\\').gsub(/"/, '\\"').gsub(/\$/, '\\$')
52
52
  # surpress 'x rows affected' in SQLCMD with 'set nocount on;'
53
53
  cmd_string = "sqlcmd -Q \"set nocount on; #{escaped_query}\" -W -w 1024 -s ','"
54
- cmd_string += " -U #{@user} -P '#{@password}'" unless @user.nil? || @password.nil?
54
+ cmd_string += " -U '#{@user}' -P '#{@password}'" unless @user.nil? || @password.nil?
55
55
  if @instance.nil?
56
- cmd_string += " -S #{@host}"
56
+ cmd_string += " -S '#{@host}'"
57
57
  else
58
- cmd_string += " -S #{@host}\\#{@instance}"
58
+ cmd_string += " -S '#{@host}\\#{@instance}'"
59
59
  end
60
60
  cmd = inspec.command(cmd_string)
61
61
  out = cmd.stdout + "\n" + cmd.stderr
@@ -33,6 +33,16 @@ module Inspec::Resources
33
33
  describe mysql_conf('path') do
34
34
  its('setting') { should eq 'value' }
35
35
  end
36
+
37
+ # Test a parameter set within the [mysqld] section
38
+ describe mysql_conf do
39
+ its('mysqld.port') { should cmp 3306 }
40
+ end
41
+
42
+ # Test a parameter set within the [mariadb] section using array notation
43
+ describe mysql_conf do
44
+ its(['mariadb', 'max-connections']) { should_not be_nil }
45
+ end
36
46
  "
37
47
 
38
48
  include FindFiles
@@ -38,7 +38,7 @@ module Inspec::Resources
38
38
  command += " -h #{@host}"
39
39
  end
40
40
  command += " --port #{@port}" unless @port.nil?
41
- command += " #{db} -s -S #{@socket} -e \"#{escaped_query}\""
41
+ command += " #{db} -s -e \"#{escaped_query}\""
42
42
 
43
43
  cmd = inspec.command(command)
44
44
  out = cmd.stdout + "\n" + cmd.stderr