inspec 2.0.32 → 2.0.45

Sign up to get free protection for your applications and to get access to all the features.
Files changed (482) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +101 -101
  3. data/CHANGELOG.md +2991 -2970
  4. data/Gemfile +55 -55
  5. data/LICENSE +14 -14
  6. data/MAINTAINERS.md +33 -33
  7. data/MAINTAINERS.toml +52 -52
  8. data/README.md +446 -437
  9. data/Rakefile +322 -322
  10. data/bin/inspec +12 -12
  11. data/docs/.gitignore +2 -2
  12. data/docs/README.md +40 -40
  13. data/docs/dsl_inspec.md +258 -258
  14. data/docs/dsl_resource.md +93 -93
  15. data/docs/glossary.md +99 -99
  16. data/docs/habitat.md +191 -191
  17. data/docs/inspec_and_friends.md +107 -107
  18. data/docs/matchers.md +169 -168
  19. data/docs/migration.md +293 -293
  20. data/docs/platforms.md +118 -118
  21. data/docs/plugin_kitchen_inspec.md +49 -49
  22. data/docs/profiles.md +370 -370
  23. data/docs/reporters.md +105 -105
  24. data/docs/resources/aide_conf.md.erb +75 -75
  25. data/docs/resources/apache.md.erb +67 -67
  26. data/docs/resources/apache_conf.md.erb +68 -68
  27. data/docs/resources/apt.md.erb +71 -71
  28. data/docs/resources/audit_policy.md.erb +47 -47
  29. data/docs/resources/auditd.md.erb +79 -79
  30. data/docs/resources/auditd_conf.md.erb +68 -68
  31. data/docs/resources/aws_cloudtrail_trail.md.erb +140 -140
  32. data/docs/resources/aws_cloudtrail_trails.md.erb +81 -81
  33. data/docs/resources/aws_cloudwatch_alarm.md.erb +86 -86
  34. data/docs/resources/aws_cloudwatch_log_metric_filter.md.erb +151 -151
  35. data/docs/resources/aws_config_recorder.md.erb +71 -71
  36. data/docs/resources/aws_ec2_instance.md.erb +106 -106
  37. data/docs/resources/aws_iam_access_key.md.erb +123 -123
  38. data/docs/resources/aws_iam_access_keys.md.erb +198 -198
  39. data/docs/resources/aws_iam_group.md.erb +46 -46
  40. data/docs/resources/aws_iam_groups.md.erb +43 -43
  41. data/docs/resources/aws_iam_password_policy.md.erb +76 -76
  42. data/docs/resources/aws_iam_policies.md.erb +82 -82
  43. data/docs/resources/aws_iam_policy.md.erb +144 -144
  44. data/docs/resources/aws_iam_role.md.erb +63 -63
  45. data/docs/resources/aws_iam_root_user.md.erb +58 -58
  46. data/docs/resources/aws_iam_user.md.erb +64 -64
  47. data/docs/resources/aws_iam_users.md.erb +89 -89
  48. data/docs/resources/aws_kms_keys.md.erb +84 -84
  49. data/docs/resources/aws_route_table.md.erb +47 -47
  50. data/docs/resources/aws_s3_bucket.md.erb +134 -134
  51. data/docs/resources/aws_security_group.md.erb +151 -151
  52. data/docs/resources/aws_security_groups.md.erb +91 -91
  53. data/docs/resources/aws_sns_topic.md.erb +63 -63
  54. data/docs/resources/aws_subnet.md.erb +133 -133
  55. data/docs/resources/aws_subnets.md.erb +126 -126
  56. data/docs/resources/aws_vpc.md.erb +120 -120
  57. data/docs/resources/aws_vpcs.md.erb +48 -48
  58. data/docs/resources/azure_generic_resource.md.erb +170 -170
  59. data/docs/resources/azure_resource_group.md.erb +284 -284
  60. data/docs/resources/azure_virtual_machine.md.erb +347 -347
  61. data/docs/resources/azure_virtual_machine_data_disk.md.erb +224 -224
  62. data/docs/resources/bash.md.erb +75 -75
  63. data/docs/resources/bond.md.erb +90 -90
  64. data/docs/resources/bridge.md.erb +57 -57
  65. data/docs/resources/bsd_service.md.erb +67 -67
  66. data/docs/resources/command.md.erb +138 -138
  67. data/docs/resources/cpan.md.erb +79 -79
  68. data/docs/resources/cran.md.erb +64 -64
  69. data/docs/resources/crontab.md.erb +89 -89
  70. data/docs/resources/csv.md.erb +54 -54
  71. data/docs/resources/dh_params.md.erb +205 -205
  72. data/docs/resources/directory.md.erb +30 -30
  73. data/docs/resources/docker.md.erb +219 -219
  74. data/docs/resources/docker_container.md.erb +104 -104
  75. data/docs/resources/docker_image.md.erb +94 -94
  76. data/docs/resources/docker_service.md.erb +114 -114
  77. data/docs/resources/elasticsearch.md.erb +242 -242
  78. data/docs/resources/etc_fstab.md.erb +125 -125
  79. data/docs/resources/etc_group.md.erb +75 -75
  80. data/docs/resources/etc_hosts.md.erb +78 -78
  81. data/docs/resources/etc_hosts_allow.md.erb +74 -74
  82. data/docs/resources/etc_hosts_deny.md.erb +74 -74
  83. data/docs/resources/file.md.erb +526 -515
  84. data/docs/resources/filesystem.md.erb +41 -41
  85. data/docs/resources/firewalld.md.erb +107 -107
  86. data/docs/resources/gem.md.erb +79 -79
  87. data/docs/resources/group.md.erb +61 -61
  88. data/docs/resources/grub_conf.md.erb +101 -101
  89. data/docs/resources/host.md.erb +86 -86
  90. data/docs/resources/http.md.erb +196 -196
  91. data/docs/resources/iis_app.md.erb +122 -122
  92. data/docs/resources/iis_site.md.erb +135 -135
  93. data/docs/resources/inetd_conf.md.erb +94 -94
  94. data/docs/resources/ini.md.erb +76 -76
  95. data/docs/resources/interface.md.erb +58 -58
  96. data/docs/resources/iptables.md.erb +64 -64
  97. data/docs/resources/json.md.erb +63 -63
  98. data/docs/resources/kernel_module.md.erb +120 -120
  99. data/docs/resources/kernel_parameter.md.erb +53 -53
  100. data/docs/resources/key_rsa.md.erb +85 -85
  101. data/docs/resources/launchd_service.md.erb +57 -57
  102. data/docs/resources/limits_conf.md.erb +75 -75
  103. data/docs/resources/login_def.md.erb +71 -71
  104. data/docs/resources/mount.md.erb +69 -69
  105. data/docs/resources/mssql_session.md.erb +60 -60
  106. data/docs/resources/mysql_conf.md.erb +99 -99
  107. data/docs/resources/mysql_session.md.erb +74 -74
  108. data/docs/resources/nginx.md.erb +79 -79
  109. data/docs/resources/nginx_conf.md.erb +128 -128
  110. data/docs/resources/npm.md.erb +60 -60
  111. data/docs/resources/ntp_conf.md.erb +60 -60
  112. data/docs/resources/oneget.md.erb +53 -53
  113. data/docs/resources/oracledb_session.md.erb +52 -52
  114. data/docs/resources/os.md.erb +141 -141
  115. data/docs/resources/os_env.md.erb +78 -78
  116. data/docs/resources/package.md.erb +120 -120
  117. data/docs/resources/packages.md.erb +67 -67
  118. data/docs/resources/parse_config.md.erb +103 -103
  119. data/docs/resources/parse_config_file.md.erb +138 -138
  120. data/docs/resources/passwd.md.erb +141 -141
  121. data/docs/resources/pip.md.erb +67 -67
  122. data/docs/resources/port.md.erb +137 -137
  123. data/docs/resources/postgres_conf.md.erb +79 -79
  124. data/docs/resources/postgres_hba_conf.md.erb +93 -93
  125. data/docs/resources/postgres_ident_conf.md.erb +76 -76
  126. data/docs/resources/postgres_session.md.erb +69 -69
  127. data/docs/resources/powershell.md.erb +102 -102
  128. data/docs/resources/processes.md.erb +109 -109
  129. data/docs/resources/rabbitmq_config.md.erb +41 -41
  130. data/docs/resources/registry_key.md.erb +158 -158
  131. data/docs/resources/runit_service.md.erb +57 -57
  132. data/docs/resources/security_policy.md.erb +47 -47
  133. data/docs/resources/service.md.erb +121 -121
  134. data/docs/resources/shadow.md.erb +146 -144
  135. data/docs/resources/ssh_config.md.erb +80 -80
  136. data/docs/resources/sshd_config.md.erb +83 -83
  137. data/docs/resources/ssl.md.erb +119 -119
  138. data/docs/resources/sys_info.md.erb +42 -42
  139. data/docs/resources/systemd_service.md.erb +57 -57
  140. data/docs/resources/sysv_service.md.erb +57 -57
  141. data/docs/resources/upstart_service.md.erb +57 -57
  142. data/docs/resources/user.md.erb +140 -140
  143. data/docs/resources/users.md.erb +127 -127
  144. data/docs/resources/vbscript.md.erb +55 -55
  145. data/docs/resources/virtualization.md.erb +57 -57
  146. data/docs/resources/windows_feature.md.erb +47 -47
  147. data/docs/resources/windows_hotfix.md.erb +53 -53
  148. data/docs/resources/windows_task.md.erb +95 -95
  149. data/docs/resources/wmi.md.erb +81 -81
  150. data/docs/resources/x509_certificate.md.erb +151 -151
  151. data/docs/resources/xinetd_conf.md.erb +156 -156
  152. data/docs/resources/xml.md.erb +85 -85
  153. data/docs/resources/yaml.md.erb +69 -69
  154. data/docs/resources/yum.md.erb +98 -98
  155. data/docs/resources/zfs_dataset.md.erb +53 -53
  156. data/docs/resources/zfs_pool.md.erb +47 -47
  157. data/docs/ruby_usage.md +203 -203
  158. data/docs/shared/matcher_be.md.erb +1 -1
  159. data/docs/shared/matcher_cmp.md.erb +43 -43
  160. data/docs/shared/matcher_eq.md.erb +3 -3
  161. data/docs/shared/matcher_include.md.erb +1 -1
  162. data/docs/shared/matcher_match.md.erb +1 -1
  163. data/docs/shell.md +215 -215
  164. data/examples/README.md +8 -8
  165. data/examples/inheritance/README.md +65 -65
  166. data/examples/inheritance/controls/example.rb +14 -14
  167. data/examples/inheritance/inspec.yml +15 -15
  168. data/examples/kitchen-ansible/.kitchen.yml +25 -25
  169. data/examples/kitchen-ansible/Gemfile +19 -19
  170. data/examples/kitchen-ansible/README.md +53 -53
  171. data/examples/kitchen-ansible/files/nginx.repo +6 -6
  172. data/examples/kitchen-ansible/tasks/main.yml +16 -16
  173. data/examples/kitchen-ansible/test/integration/default/default.yml +5 -5
  174. data/examples/kitchen-ansible/test/integration/default/web_spec.rb +28 -28
  175. data/examples/kitchen-chef/.kitchen.yml +20 -20
  176. data/examples/kitchen-chef/Berksfile +3 -3
  177. data/examples/kitchen-chef/Gemfile +19 -19
  178. data/examples/kitchen-chef/README.md +27 -27
  179. data/examples/kitchen-chef/metadata.rb +7 -7
  180. data/examples/kitchen-chef/recipes/default.rb +6 -6
  181. data/examples/kitchen-chef/recipes/nginx.rb +30 -30
  182. data/examples/kitchen-chef/test/integration/default/web_spec.rb +28 -28
  183. data/examples/kitchen-puppet/.kitchen.yml +22 -22
  184. data/examples/kitchen-puppet/Gemfile +20 -20
  185. data/examples/kitchen-puppet/Puppetfile +25 -25
  186. data/examples/kitchen-puppet/README.md +53 -53
  187. data/examples/kitchen-puppet/manifests/site.pp +33 -33
  188. data/examples/kitchen-puppet/metadata.json +11 -11
  189. data/examples/kitchen-puppet/test/integration/default/web_spec.rb +28 -28
  190. data/examples/meta-profile/README.md +37 -37
  191. data/examples/meta-profile/controls/example.rb +13 -13
  192. data/examples/meta-profile/inspec.yml +13 -13
  193. data/examples/profile-attribute.yml +2 -2
  194. data/examples/profile-attribute/README.md +14 -14
  195. data/examples/profile-attribute/controls/example.rb +11 -11
  196. data/examples/profile-attribute/inspec.yml +8 -8
  197. data/examples/profile-aws/controls/iam_password_policy_expiration.rb +8 -8
  198. data/examples/profile-aws/controls/iam_password_policy_max_age.rb +8 -8
  199. data/examples/profile-aws/controls/iam_root_user_mfa.rb +8 -8
  200. data/examples/profile-aws/controls/iam_users_access_key_age.rb +8 -8
  201. data/examples/profile-aws/controls/iam_users_console_users_mfa.rb +8 -8
  202. data/examples/profile-aws/inspec.yml +11 -11
  203. data/examples/profile-azure/controls/azure_resource_group_example.rb +24 -24
  204. data/examples/profile-azure/controls/azure_vm_example.rb +29 -29
  205. data/examples/profile-azure/inspec.yml +11 -11
  206. data/examples/profile-sensitive/README.md +29 -29
  207. data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -9
  208. data/examples/profile-sensitive/controls/sensitive.rb +9 -9
  209. data/examples/profile-sensitive/inspec.yml +8 -8
  210. data/examples/profile/README.md +48 -48
  211. data/examples/profile/controls/example.rb +23 -23
  212. data/examples/profile/controls/gordon.rb +36 -36
  213. data/examples/profile/controls/meta.rb +34 -34
  214. data/examples/profile/inspec.yml +10 -10
  215. data/examples/profile/libraries/gordon_config.rb +53 -53
  216. data/inspec.gemspec +47 -47
  217. data/lib/bundles/README.md +3 -3
  218. data/lib/bundles/inspec-artifact.rb +7 -7
  219. data/lib/bundles/inspec-artifact/README.md +1 -1
  220. data/lib/bundles/inspec-artifact/cli.rb +277 -277
  221. data/lib/bundles/inspec-compliance.rb +16 -16
  222. data/lib/bundles/inspec-compliance/.kitchen.yml +20 -20
  223. data/lib/bundles/inspec-compliance/README.md +185 -185
  224. data/lib/bundles/inspec-compliance/api.rb +316 -316
  225. data/lib/bundles/inspec-compliance/api/login.rb +152 -152
  226. data/lib/bundles/inspec-compliance/bootstrap.sh +41 -41
  227. data/lib/bundles/inspec-compliance/cli.rb +254 -254
  228. data/lib/bundles/inspec-compliance/configuration.rb +103 -103
  229. data/lib/bundles/inspec-compliance/http.rb +86 -86
  230. data/lib/bundles/inspec-compliance/support.rb +36 -36
  231. data/lib/bundles/inspec-compliance/target.rb +98 -98
  232. data/lib/bundles/inspec-compliance/test/integration/default/cli.rb +93 -93
  233. data/lib/bundles/inspec-habitat.rb +12 -12
  234. data/lib/bundles/inspec-habitat/cli.rb +36 -36
  235. data/lib/bundles/inspec-habitat/log.rb +10 -10
  236. data/lib/bundles/inspec-habitat/profile.rb +390 -390
  237. data/lib/bundles/inspec-init.rb +8 -8
  238. data/lib/bundles/inspec-init/README.md +31 -31
  239. data/lib/bundles/inspec-init/cli.rb +97 -97
  240. data/lib/bundles/inspec-init/templates/profile/README.md +3 -3
  241. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +19 -19
  242. data/lib/bundles/inspec-init/templates/profile/inspec.yml +8 -8
  243. data/lib/bundles/inspec-supermarket.rb +13 -13
  244. data/lib/bundles/inspec-supermarket/README.md +45 -45
  245. data/lib/bundles/inspec-supermarket/api.rb +84 -84
  246. data/lib/bundles/inspec-supermarket/cli.rb +73 -73
  247. data/lib/bundles/inspec-supermarket/target.rb +34 -34
  248. data/lib/fetchers/git.rb +163 -163
  249. data/lib/fetchers/local.rb +74 -74
  250. data/lib/fetchers/mock.rb +35 -35
  251. data/lib/fetchers/url.rb +204 -204
  252. data/lib/inspec.rb +24 -24
  253. data/lib/inspec/archive/tar.rb +29 -29
  254. data/lib/inspec/archive/zip.rb +19 -19
  255. data/lib/inspec/backend.rb +92 -92
  256. data/lib/inspec/base_cli.rb +355 -350
  257. data/lib/inspec/cached_fetcher.rb +66 -66
  258. data/lib/inspec/cli.rb +292 -292
  259. data/lib/inspec/completions/bash.sh.erb +45 -45
  260. data/lib/inspec/completions/fish.sh.erb +34 -34
  261. data/lib/inspec/completions/zsh.sh.erb +61 -61
  262. data/lib/inspec/control_eval_context.rb +179 -179
  263. data/lib/inspec/dependencies/cache.rb +72 -72
  264. data/lib/inspec/dependencies/dependency_set.rb +92 -92
  265. data/lib/inspec/dependencies/lockfile.rb +115 -115
  266. data/lib/inspec/dependencies/requirement.rb +123 -123
  267. data/lib/inspec/dependencies/resolver.rb +86 -86
  268. data/lib/inspec/describe.rb +27 -27
  269. data/lib/inspec/dsl.rb +66 -66
  270. data/lib/inspec/dsl_shared.rb +33 -33
  271. data/lib/inspec/env_printer.rb +157 -157
  272. data/lib/inspec/errors.rb +13 -13
  273. data/lib/inspec/exceptions.rb +12 -12
  274. data/lib/inspec/expect.rb +45 -45
  275. data/lib/inspec/fetcher.rb +45 -45
  276. data/lib/inspec/file_provider.rb +275 -275
  277. data/lib/inspec/formatters.rb +3 -3
  278. data/lib/inspec/formatters/base.rb +250 -250
  279. data/lib/inspec/formatters/json_rspec.rb +20 -20
  280. data/lib/inspec/formatters/show_progress.rb +12 -12
  281. data/lib/inspec/library_eval_context.rb +58 -58
  282. data/lib/inspec/log.rb +11 -11
  283. data/lib/inspec/metadata.rb +247 -247
  284. data/lib/inspec/method_source.rb +24 -24
  285. data/lib/inspec/objects.rb +14 -14
  286. data/lib/inspec/objects/attribute.rb +65 -65
  287. data/lib/inspec/objects/control.rb +61 -61
  288. data/lib/inspec/objects/describe.rb +92 -92
  289. data/lib/inspec/objects/each_loop.rb +36 -36
  290. data/lib/inspec/objects/list.rb +15 -15
  291. data/lib/inspec/objects/or_test.rb +40 -40
  292. data/lib/inspec/objects/ruby_helper.rb +15 -15
  293. data/lib/inspec/objects/tag.rb +27 -27
  294. data/lib/inspec/objects/test.rb +87 -87
  295. data/lib/inspec/objects/value.rb +27 -27
  296. data/lib/inspec/plugins.rb +60 -60
  297. data/lib/inspec/plugins/cli.rb +24 -24
  298. data/lib/inspec/plugins/fetcher.rb +86 -86
  299. data/lib/inspec/plugins/resource.rb +135 -135
  300. data/lib/inspec/plugins/secret.rb +15 -15
  301. data/lib/inspec/plugins/source_reader.rb +40 -40
  302. data/lib/inspec/polyfill.rb +12 -12
  303. data/lib/inspec/profile.rb +510 -510
  304. data/lib/inspec/profile_context.rb +207 -207
  305. data/lib/inspec/profile_vendor.rb +66 -66
  306. data/lib/inspec/reporters.rb +54 -50
  307. data/lib/inspec/reporters/base.rb +24 -24
  308. data/lib/inspec/reporters/cli.rb +356 -356
  309. data/lib/inspec/reporters/json.rb +116 -116
  310. data/lib/inspec/reporters/json_min.rb +48 -48
  311. data/lib/inspec/reporters/junit.rb +77 -77
  312. data/lib/inspec/require_loader.rb +33 -33
  313. data/lib/inspec/resource.rb +186 -186
  314. data/lib/inspec/rule.rb +266 -266
  315. data/lib/inspec/runner.rb +345 -345
  316. data/lib/inspec/runner_mock.rb +41 -41
  317. data/lib/inspec/runner_rspec.rb +175 -175
  318. data/lib/inspec/runtime_profile.rb +26 -26
  319. data/lib/inspec/schema.rb +213 -213
  320. data/lib/inspec/secrets.rb +19 -19
  321. data/lib/inspec/secrets/yaml.rb +30 -30
  322. data/lib/inspec/shell.rb +220 -220
  323. data/lib/inspec/shell_detector.rb +90 -90
  324. data/lib/inspec/source_reader.rb +29 -29
  325. data/lib/inspec/version.rb +8 -8
  326. data/lib/matchers/matchers.rb +339 -339
  327. data/lib/resource_support/aws.rb +41 -41
  328. data/lib/resource_support/aws/aws_backend_base.rb +12 -12
  329. data/lib/resource_support/aws/aws_backend_factory_mixin.rb +12 -12
  330. data/lib/resource_support/aws/aws_plural_resource_mixin.rb +21 -21
  331. data/lib/resource_support/aws/aws_resource_mixin.rb +66 -66
  332. data/lib/resource_support/aws/aws_singular_resource_mixin.rb +24 -24
  333. data/lib/resources/aide_conf.rb +159 -160
  334. data/lib/resources/apache.rb +48 -48
  335. data/lib/resources/apache_conf.rb +156 -156
  336. data/lib/resources/apt.rb +149 -149
  337. data/lib/resources/audit_policy.rb +63 -63
  338. data/lib/resources/auditd.rb +231 -231
  339. data/lib/resources/auditd_conf.rb +55 -55
  340. data/lib/resources/aws/aws_cloudtrail_trail.rb +77 -77
  341. data/lib/resources/aws/aws_cloudtrail_trails.rb +47 -47
  342. data/lib/resources/aws/aws_cloudwatch_alarm.rb +62 -62
  343. data/lib/resources/aws/aws_cloudwatch_log_metric_filter.rb +100 -100
  344. data/lib/resources/aws/aws_config_recorder.rb +98 -98
  345. data/lib/resources/aws/aws_ec2_instance.rb +157 -157
  346. data/lib/resources/aws/aws_iam_access_key.rb +106 -106
  347. data/lib/resources/aws/aws_iam_access_keys.rb +149 -144
  348. data/lib/resources/aws/aws_iam_group.rb +56 -56
  349. data/lib/resources/aws/aws_iam_groups.rb +52 -45
  350. data/lib/resources/aws/aws_iam_password_policy.rb +116 -116
  351. data/lib/resources/aws/aws_iam_policies.rb +53 -46
  352. data/lib/resources/aws/aws_iam_policy.rb +125 -119
  353. data/lib/resources/aws/aws_iam_role.rb +51 -51
  354. data/lib/resources/aws/aws_iam_root_user.rb +60 -60
  355. data/lib/resources/aws/aws_iam_user.rb +111 -111
  356. data/lib/resources/aws/aws_iam_users.rb +108 -96
  357. data/lib/resources/aws/aws_kms_keys.rb +53 -46
  358. data/lib/resources/aws/aws_route_table.rb +61 -61
  359. data/lib/resources/aws/aws_s3_bucket.rb +115 -115
  360. data/lib/resources/aws/aws_security_group.rb +93 -93
  361. data/lib/resources/aws/aws_security_groups.rb +68 -68
  362. data/lib/resources/aws/aws_sns_topic.rb +53 -53
  363. data/lib/resources/aws/aws_subnet.rb +88 -88
  364. data/lib/resources/aws/aws_subnets.rb +53 -53
  365. data/lib/resources/aws/aws_vpc.rb +69 -69
  366. data/lib/resources/aws/aws_vpcs.rb +45 -45
  367. data/lib/resources/azure/azure_backend.rb +377 -377
  368. data/lib/resources/azure/azure_generic_resource.rb +59 -59
  369. data/lib/resources/azure/azure_resource_group.rb +152 -152
  370. data/lib/resources/azure/azure_virtual_machine.rb +264 -264
  371. data/lib/resources/azure/azure_virtual_machine_data_disk.rb +136 -136
  372. data/lib/resources/bash.rb +35 -35
  373. data/lib/resources/bond.rb +68 -68
  374. data/lib/resources/bridge.rb +122 -122
  375. data/lib/resources/command.rb +73 -69
  376. data/lib/resources/cpan.rb +58 -58
  377. data/lib/resources/cran.rb +64 -64
  378. data/lib/resources/crontab.rb +169 -170
  379. data/lib/resources/csv.rb +60 -60
  380. data/lib/resources/dh_params.rb +82 -82
  381. data/lib/resources/directory.rb +25 -25
  382. data/lib/resources/docker.rb +236 -236
  383. data/lib/resources/docker_container.rb +89 -89
  384. data/lib/resources/docker_image.rb +83 -83
  385. data/lib/resources/docker_object.rb +57 -57
  386. data/lib/resources/docker_service.rb +90 -90
  387. data/lib/resources/elasticsearch.rb +169 -169
  388. data/lib/resources/etc_fstab.rb +101 -102
  389. data/lib/resources/etc_group.rb +152 -156
  390. data/lib/resources/etc_hosts.rb +82 -81
  391. data/lib/resources/etc_hosts_allow_deny.rb +122 -123
  392. data/lib/resources/file.rb +298 -298
  393. data/lib/resources/filesystem.rb +31 -31
  394. data/lib/resources/firewalld.rb +143 -144
  395. data/lib/resources/gem.rb +70 -70
  396. data/lib/resources/groups.rb +215 -215
  397. data/lib/resources/grub_conf.rb +237 -237
  398. data/lib/resources/host.rb +306 -300
  399. data/lib/resources/http.rb +251 -250
  400. data/lib/resources/iis_app.rb +101 -104
  401. data/lib/resources/iis_site.rb +148 -148
  402. data/lib/resources/inetd_conf.rb +62 -62
  403. data/lib/resources/ini.rb +29 -29
  404. data/lib/resources/interface.rb +129 -129
  405. data/lib/resources/iptables.rb +80 -69
  406. data/lib/resources/json.rb +117 -117
  407. data/lib/resources/kernel_module.rb +107 -107
  408. data/lib/resources/kernel_parameter.rb +58 -58
  409. data/lib/resources/key_rsa.rb +67 -67
  410. data/lib/resources/limits_conf.rb +55 -55
  411. data/lib/resources/login_def.rb +66 -66
  412. data/lib/resources/mount.rb +88 -88
  413. data/lib/resources/mssql_session.rb +101 -101
  414. data/lib/resources/mysql.rb +81 -81
  415. data/lib/resources/mysql_conf.rb +134 -134
  416. data/lib/resources/mysql_session.rb +71 -71
  417. data/lib/resources/nginx.rb +96 -96
  418. data/lib/resources/nginx_conf.rb +227 -227
  419. data/lib/resources/npm.rb +48 -48
  420. data/lib/resources/ntp_conf.rb +58 -58
  421. data/lib/resources/oneget.rb +71 -71
  422. data/lib/resources/oracledb_session.rb +139 -139
  423. data/lib/resources/os.rb +36 -36
  424. data/lib/resources/os_env.rb +76 -76
  425. data/lib/resources/package.rb +370 -370
  426. data/lib/resources/packages.rb +111 -111
  427. data/lib/resources/parse_config.rb +116 -116
  428. data/lib/resources/passwd.rb +74 -74
  429. data/lib/resources/pip.rb +89 -89
  430. data/lib/resources/platform.rb +109 -109
  431. data/lib/resources/port.rb +771 -771
  432. data/lib/resources/postgres.rb +130 -130
  433. data/lib/resources/postgres_conf.rb +121 -121
  434. data/lib/resources/postgres_hba_conf.rb +99 -100
  435. data/lib/resources/postgres_ident_conf.rb +76 -78
  436. data/lib/resources/postgres_session.rb +71 -71
  437. data/lib/resources/powershell.rb +53 -57
  438. data/lib/resources/processes.rb +204 -204
  439. data/lib/resources/rabbitmq_conf.rb +52 -52
  440. data/lib/resources/registry_key.rb +296 -296
  441. data/lib/resources/security_policy.rb +180 -180
  442. data/lib/resources/service.rb +789 -789
  443. data/lib/resources/shadow.rb +146 -140
  444. data/lib/resources/ssh_conf.rb +102 -102
  445. data/lib/resources/ssl.rb +99 -99
  446. data/lib/resources/sys_info.rb +28 -28
  447. data/lib/resources/toml.rb +32 -32
  448. data/lib/resources/users.rb +654 -654
  449. data/lib/resources/vbscript.rb +68 -69
  450. data/lib/resources/virtualization.rb +247 -247
  451. data/lib/resources/windows_feature.rb +84 -84
  452. data/lib/resources/windows_hotfix.rb +35 -35
  453. data/lib/resources/windows_task.rb +102 -105
  454. data/lib/resources/wmi.rb +110 -113
  455. data/lib/resources/x509_certificate.rb +143 -143
  456. data/lib/resources/xinetd.rb +111 -111
  457. data/lib/resources/xml.rb +46 -46
  458. data/lib/resources/yaml.rb +47 -47
  459. data/lib/resources/yum.rb +180 -180
  460. data/lib/resources/zfs_dataset.rb +60 -60
  461. data/lib/resources/zfs_pool.rb +49 -49
  462. data/lib/source_readers/flat.rb +39 -39
  463. data/lib/source_readers/inspec.rb +75 -75
  464. data/lib/utils/command_wrapper.rb +27 -27
  465. data/lib/utils/convert.rb +12 -12
  466. data/lib/utils/database_helpers.rb +77 -77
  467. data/lib/utils/erlang_parser.rb +192 -192
  468. data/lib/utils/filter.rb +272 -272
  469. data/lib/utils/filter_array.rb +27 -27
  470. data/lib/utils/find_files.rb +44 -44
  471. data/lib/utils/hash.rb +41 -41
  472. data/lib/utils/json_log.rb +18 -18
  473. data/lib/utils/latest_version.rb +22 -22
  474. data/lib/utils/modulator.rb +12 -12
  475. data/lib/utils/nginx_parser.rb +85 -85
  476. data/lib/utils/object_traversal.rb +49 -49
  477. data/lib/utils/parser.rb +274 -274
  478. data/lib/utils/plugin_registry.rb +93 -93
  479. data/lib/utils/simpleconfig.rb +120 -120
  480. data/lib/utils/spdx.rb +13 -13
  481. data/lib/utils/spdx.txt +343 -343
  482. metadata +2 -2
@@ -1,771 +1,771 @@
1
- # encoding: utf-8
2
-
3
- require 'utils/parser'
4
- require 'utils/filter'
5
- require 'ipaddr'
6
-
7
- # TODO: currently we return local ip only
8
- # TODO: improve handling of same port on multiple interfaces
9
- module Inspec::Resources
10
- class Port < Inspec.resource(1)
11
- name 'port'
12
- supports platform: 'unix'
13
- supports platform: 'windows'
14
- desc "Use the port InSpec audit resource to test basic port properties, such as port, process, if it's listening."
15
- example "
16
- describe port(80) do
17
- it { should be_listening }
18
- its('protocols') {should eq ['tcp']}
19
- its('addresses') {should eq ['127.0.0.1']}
20
- end
21
-
22
- describe port.where { protocol =~ /tcp/ && port > 80 } do
23
- it { should_not be_listening }
24
- end
25
- "
26
-
27
- def initialize(*args)
28
- args.unshift(nil) if args.length <= 1 # add the ip address to the front
29
- @ip = args[0]
30
- @port = if args[1].nil?
31
- nil
32
- else
33
- args[1].to_i
34
- end
35
-
36
- @cache = nil
37
- @port_manager = port_manager_for_os
38
- return skip_resource 'The `port` resource is not supported on your OS yet.' if @port_manager.nil?
39
- end
40
-
41
- filter = FilterTable.create
42
- filter.add_accessor(:where)
43
- .add_accessor(:entries)
44
- .add(:ports, field: 'port', style: :simple)
45
- .add(:addresses, field: 'address', style: :simple)
46
- .add(:protocols, field: 'protocol', style: :simple)
47
- .add(:processes, field: 'process', style: :simple)
48
- .add(:pids, field: 'pid', style: :simple)
49
- .add(:listening?) { |x| !x.entries.empty? }
50
- filter.connect(self, :info)
51
-
52
- def to_s
53
- "Port #{@port}"
54
- end
55
-
56
- private
57
-
58
- def port_manager_for_os
59
- os = inspec.os
60
- if os.linux?
61
- LinuxPorts.new(inspec)
62
- elsif os.aix?
63
- # AIX: see http://www.ibm.com/developerworks/aix/library/au-lsof.html#resources
64
- # and https://www-01.ibm.com/marketing/iwm/iwm/web/reg/pick.do?source=aixbp
65
- AixPorts.new(inspec)
66
- elsif os.darwin?
67
- # Darwin: https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/lsof.8.html
68
- LsofPorts.new(inspec)
69
- elsif os.windows?
70
- WindowsPorts.new(inspec)
71
- elsif ['freebsd'].include?(os[:family])
72
- FreeBsdPorts.new(inspec)
73
- elsif os.solaris?
74
- SolarisPorts.new(inspec)
75
- elsif os.hpux?
76
- HpuxPorts.new(inspec)
77
- end
78
- end
79
-
80
- def info
81
- return @cache if !@cache.nil?
82
- # abort if os detection has not worked
83
- return @cache = [] if @port_manager.nil?
84
- # query ports
85
- cache = @port_manager.info || []
86
- cache.select! { |x| x['port'] == @port } unless @port.nil?
87
- cache.select! { |x| x['address'] == @ip } unless @ip.nil?
88
- @cache = cache
89
- end
90
- end
91
-
92
- # implements an info method and returns all ip adresses and protocols for
93
- # each port
94
- # [{
95
- # 'port' => 22,
96
- # 'address' => '0.0.0.0'
97
- # 'protocol' => 'tcp'
98
- # },
99
- # {
100
- # 'port' => 22,
101
- # 'address' => '::'
102
- # 'protocol' => 'tcp6'
103
- # }]
104
- class PortsInfo
105
- attr_reader :inspec
106
- def initialize(inspec)
107
- @inspec = inspec
108
- end
109
- end
110
-
111
- # TODO: Add UDP infromation Get-NetUDPEndpoint
112
- # TODO: currently Windows only supports tcp ports
113
- # TODO: Get-NetTCPConnection does not return PIDs
114
- # TODO: double-check output with 'netstat -ano'
115
- # @see https://connect.microsoft.com/PowerShell/feedback/details/1349420/get-nettcpconnection-does-not-show-processid
116
- class WindowsPorts < PortsInfo
117
- def info
118
- netstat_info || powershell_info
119
- end
120
-
121
- private
122
-
123
- def powershell_info
124
- cmd = inspec.command('Get-NetTCPConnection -state Listen | Select-Object -Property State, Caption, Description, LocalAddress, LocalPort, RemoteAddress, RemotePort, DisplayName, Status | ConvertTo-Json')
125
- return nil if cmd.exit_status != 0
126
-
127
- entries = JSON.parse(cmd.stdout)
128
- return nil if entries.nil?
129
-
130
- entries.map { |x|
131
- {
132
- 'port' => x['LocalPort'],
133
- 'address' => x['LocalAddress'],
134
- 'protocol' => 'tcp',
135
- }
136
- }
137
- rescue JSON::ParserError => _e
138
- return nil
139
- end
140
-
141
- def netstat_info
142
- # retrieve processes grepping by LISTENING state with 0 lines before and 1 after to catch the process name
143
- # also UDP ports have nothing in the State column
144
- cmd = inspec.command('netstat -anbo | Select-String -CaseSensitive -pattern "^\s+UDP|\s+LISTENING\s+\d+$" -context 0,1')
145
- return nil if cmd.exit_status != 0
146
- lines = cmd.stdout.scan(/^>\s*(tcp\S*|udp\S*)\s+(\S+):(\d+)\s+(\S+)\s+(\S*)\s+(\d+)\s+(.+)/i)
147
- lines.map do |line|
148
- pid = line[5].to_i
149
- process = line[6].delete('[').delete(']').strip
150
- process = 'System' if process == 'Can not obtain ownership information' && pid == 4
151
- {
152
- 'port' => line[2].to_i,
153
- 'address' => line[1].delete('[').delete(']'),
154
- 'protocol' => line[0].downcase,
155
- 'pid' => pid,
156
- 'process' => process,
157
- }
158
- end
159
- end
160
- end
161
-
162
- # extracts udp and tcp ports from the lsof command
163
- class LsofPorts < PortsInfo
164
- attr_reader :lsof
165
-
166
- def initialize(inspec, lsofpath = nil)
167
- @lsof = lsofpath || 'lsof'
168
- super(inspec)
169
- end
170
-
171
- def info
172
- ports = []
173
-
174
- # check that lsof is available, otherwise fail
175
- raise 'Please ensure `lsof` is available on the machine.' if !inspec.command(@lsof.to_s).exist?
176
-
177
- # -F p=pid, c=command, P=protocol name, t=type, n=internet addresses
178
- # see 'OUTPUT FOR OTHER PROGRAMS' in LSOF(8)
179
- lsof_cmd = inspec.command("#{@lsof} -nP -i -FpctPn")
180
- return nil if lsof_cmd.exit_status.to_i != 0
181
-
182
- # map to desired return struct
183
- lsof_parser(lsof_cmd).each do |process, port_ids|
184
- pid, cmd = process.split(':')
185
- port_ids.each do |port_str|
186
- # should not break on ipv6 addresses
187
- ipv, proto, port, host = port_str.split(':', 4)
188
- ports.push({ 'port' => port.to_i,
189
- 'address' => host,
190
- 'protocol' => ipv == 'ipv6' ? proto + '6' : proto,
191
- 'process' => cmd,
192
- 'pid' => pid.to_i })
193
- end
194
- end
195
-
196
- ports
197
- end
198
-
199
- # rubocop:disable Metrics/CyclomaticComplexity
200
- # rubocop:disable Metrics/AbcSize
201
- def lsof_parser(lsof_cmd)
202
- procs = {}
203
- # build this with formatted output (-F) from lsof
204
- # procs = {
205
- # '123:sshd' => [
206
- # 'ipv4:tcp:22:127.0.0.1',
207
- # 'ipv6:tcp:22:::1',
208
- # 'ipv4:tcp:*',
209
- # 'ipv6:tcp:*',
210
- # ],
211
- # '456:ntpd' => [
212
- # 'ipv4:udp:123:*',
213
- # 'ipv6:udp:123:*',
214
- # ]
215
- # }
216
- proc_id = port_id = nil
217
- lsof_cmd.stdout.each_line do |line|
218
- line.chomp!
219
- key = line.slice!(0)
220
- case key
221
- when 'p'
222
- proc_id = line
223
- port_id = nil
224
- when 'c'
225
- proc_id += ':' + line
226
- when 't'
227
- port_id = line.downcase
228
- when 'P'
229
- port_id += ':' + line.downcase
230
- when 'n'
231
- src, dst = line.split('->')
232
-
233
- # skip active comm streams
234
- next if dst
235
-
236
- host, port = /^(\S+):(\d+|\*)$/.match(src)[1, 2]
237
-
238
- # skip channels from port 0 - what does this mean?
239
- next if port == '*'
240
-
241
- # create new array stub if !exist?
242
- procs[proc_id] = [] unless procs.key?(proc_id)
243
-
244
- # change address '*' to zero
245
- host = port_id =~ /^ipv6:/ ? '[::]' : '0.0.0.0' if host == '*'
246
- # entrust URI to scrub the host and port
247
- begin
248
- uri = URI("addr://#{host}:#{port}")
249
- uri.host && uri.port
250
- rescue => e
251
- warn "could not parse URI 'addr://#{host}:#{port}' - #{e}"
252
- next
253
- end
254
-
255
- # e.g. 'ipv4:tcp:22:127.0.0.1'
256
- # strip ipv6 squares for inspec
257
- port_id += ':' + port + ':' + host.gsub(/^\[|\]$/, '')
258
-
259
- # lsof will give us another port unless it's done
260
- procs[proc_id] << port_id
261
- end
262
- end
263
-
264
- procs
265
- end
266
- end
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
-
383
- # extract port information from netstat
384
- class LinuxPorts < PortsInfo
385
- ALLOWED_PROTOCOLS = %w{tcp tcp6 udp udp6}.freeze
386
-
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
-
410
- cmd = inspec.command('netstat -tulpen')
411
- return nil unless cmd.exit_status.to_i.zero?
412
-
413
- ports = []
414
- # parse all lines
415
- cmd.stdout.each_line do |line|
416
- port_info = parse_netstat_line(line)
417
-
418
- # only push protocols we are interested in
419
- next unless %w{tcp tcp6 udp udp6}.include?(port_info['protocol'])
420
- ports.push(port_info)
421
- end
422
- ports
423
- end
424
-
425
- def parse_net_address(net_addr, protocol)
426
- if protocol.eql?('tcp6') || protocol.eql?('udp6')
427
- # prep for URI parsing, parse ip6 port
428
- ip6 = /^(\S+):(\d+)$/.match(net_addr)
429
- ip6addr = ip6[1]
430
- ip6addr = '::' if ip6addr =~ /^:::$/
431
-
432
- # v6 addresses need to end in a double-colon when using
433
- # shorthand notation. netstat ends with a single colon.
434
- # IPAddr will fail to properly parse an address unless it
435
- # uses a double-colon for short-hand notation.
436
- ip6addr += ':' if ip6addr =~ /\w:$/
437
-
438
- begin
439
- ip_parser = IPAddr.new(ip6addr)
440
- rescue IPAddr::InvalidAddressError
441
- # This IP is not parsable. There appears to be a bug in netstat
442
- # output that truncates link-local IP addresses:
443
- # example: udp6 0 0 fe80::42:acff:fe11::123 :::* 0 54550 3335/ntpd
444
- # actual link address: inet6 fe80::42:acff:fe11:5/64 scope link
445
- #
446
- # in this example, the "5" is truncated making the netstat output
447
- # an invalid IP address.
448
- return [nil, nil]
449
- end
450
-
451
- # Check to see if this is a IPv4 address in a tcp6/udp6 line.
452
- # If so, don't put brackets around the IP or URI won't know how
453
- # to properly handle it.
454
- # example: tcp6 0 0 127.0.0.1:8005 :::* LISTEN
455
- if ip_parser.ipv4?
456
- ip_addr = URI("addr://#{ip6addr}:#{ip6[2]}")
457
- host = ip_addr.host
458
- else
459
- ip_addr = URI("addr://[#{ip6addr}]:#{ip6[2]}")
460
- # strip []
461
- host = ip_addr.host[1..ip_addr.host.size-2]
462
- end
463
- else
464
- ip_addr = URI('addr://'+net_addr)
465
- host = ip_addr.host
466
- end
467
-
468
- port = ip_addr.port
469
-
470
- [host, port]
471
- rescue URI::InvalidURIError => e
472
- warn "Could not parse #{net_addr}, #{e}"
473
- nil
474
- end
475
-
476
- def parse_netstat_line(line)
477
- # parse each line
478
- # 1 - Proto, 2 - Recv-Q, 3 - Send-Q, 4 - Local Address, 5 - Foreign Address, 6 - State, 7 - Inode, 8 - PID/Program name
479
- parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)?\s+(\S+)\s+(\S+)\s+(\S+)/.match(line)
480
- return {} if parsed.nil? || line.match(/^proto/i)
481
-
482
- # parse ip4 and ip6 addresses
483
- protocol = parsed[1].downcase
484
-
485
- # detect protocol if not provided
486
- protocol += '6' if parsed[4].count(':') > 1 && %w{tcp udp}.include?(protocol)
487
-
488
- # extract host and port information
489
- host, port = parse_net_address(parsed[4], protocol)
490
- return {} if host.nil?
491
-
492
- # extract PID
493
- process = parsed[9].split('/')
494
- pid = process[0]
495
- pid = pid.to_i if pid =~ /^\d+$/
496
- process = process[1]
497
-
498
- {
499
- 'port' => port,
500
- 'address' => host,
501
- 'protocol' => protocol,
502
- 'process' => process,
503
- 'pid' => pid,
504
- }
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
613
- end
614
-
615
- # extracts information from sockstat
616
- class FreeBsdPorts < PortsInfo
617
- def info
618
- cmd = inspec.command('sockstat -46l')
619
- return nil if cmd.exit_status.to_i != 0
620
-
621
- ports = []
622
- # split on each newline
623
- cmd.stdout.each_line do |line|
624
- port_info = parse_sockstat_line(line)
625
-
626
- # push data, if not headerfile
627
- next unless %w{tcp tcp6 udp udp6}.include?(port_info['protocol'])
628
- ports.push(port_info)
629
- end
630
- ports
631
- end
632
-
633
- def parse_net_address(net_addr, protocol)
634
- case protocol
635
- when 'tcp4', 'udp4', 'tcp', 'udp'
636
- # replace * with 0.0.0.0
637
- net_addr = net_addr.gsub(/^\*:/, '0.0.0.0:') if net_addr =~ /^*:(\d+)$/
638
- ip_addr = URI('addr://'+net_addr)
639
- host = ip_addr.host
640
- port = ip_addr.port
641
- when 'tcp6', 'udp6'
642
- return [] if net_addr == '*:*' # abort for now
643
- # replace * with 0:0:0:0:0:0:0:0
644
- net_addr = net_addr.gsub(/^\*:/, '0:0:0:0:0:0:0:0:') if net_addr =~ /^*:(\d+)$/
645
- # extract port
646
- ip6 = /^(\S+):(\d+)$/.match(net_addr)
647
- ip6addr = ip6[1]
648
- ip_addr = URI("addr://[#{ip6addr}]:#{ip6[2]}")
649
- # replace []
650
- host = ip_addr.host[1..ip_addr.host.size-2]
651
- port = ip_addr.port
652
- end
653
- [host, port]
654
- rescue URI::InvalidURIError => e
655
- warn "Could not parse #{net_addr}, #{e}"
656
- nil
657
- end
658
-
659
- def parse_sockstat_line(line)
660
- # 1 - USER, 2 - COMMAND, 3 - PID, 4 - FD 5 - PROTO, 6 - LOCAL ADDRESS, 7 - FOREIGN ADDRESS
661
- parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
662
- return {} if parsed.nil?
663
-
664
- # extract ip information
665
- protocol = parsed[5].downcase
666
- host, port = parse_net_address(parsed[6], protocol)
667
- return {} if host.nil? or port.nil?
668
-
669
- # extract process
670
- process = parsed[2]
671
-
672
- # extract PID
673
- pid = parsed[3]
674
- pid = pid.to_i if pid =~ /^\d+$/
675
-
676
- # map tcp4 and udp4
677
- protocol = 'tcp' if protocol.eql?('tcp4')
678
- protocol = 'udp' if protocol.eql?('udp4')
679
-
680
- {
681
- 'port' => port,
682
- 'address' => host,
683
- 'protocol' => protocol,
684
- 'process' => process,
685
- 'pid' => pid,
686
- }
687
- end
688
- end
689
-
690
- class SolarisPorts < FreeBsdPorts
691
- include SolarisNetstatParser
692
-
693
- def info
694
- # extract all port info
695
- cmd = inspec.command('netstat -an -f inet -f inet6')
696
- return nil if cmd.exit_status.to_i != 0
697
-
698
- # parse the content
699
- netstat_ports = parse_netstat(cmd.stdout)
700
-
701
- # filter all ports, where we `listen`
702
- listen = netstat_ports.select { |val|
703
- !val['state'].nil? && 'listen'.casecmp(val['state']) == 0
704
- }
705
-
706
- # map the data
707
- ports = listen.map { |val|
708
- protocol = val['protocol']
709
- local_addr = val['local-address']
710
-
711
- # solaris uses 127.0.0.1.57455 instead 127.0.0.1:57455, lets convert the
712
- # the last . to :
713
- local_addr[local_addr.rindex('.')] = ':'
714
- host, port = parse_net_address(local_addr, protocol)
715
- if host.nil?
716
- nil
717
- else
718
- {
719
- 'port' => port,
720
- 'address' => host,
721
- 'protocol' => protocol,
722
- }
723
- end
724
- }
725
- ports.compact
726
- end
727
- end
728
-
729
- # extracts information from netstat for hpux
730
- class HpuxPorts < FreeBsdPorts
731
- def info
732
- ## Can't use 'netstat -an -f inet -f inet6' as the latter -f option overrides the former one and return only inet ports
733
- cmd1 = inspec.command('netstat -an -f inet')
734
- return nil if cmd1.exit_status.to_i != 0
735
- cmd2 = inspec.command('netstat -an -f inet6')
736
- return nil if cmd2.exit_status.to_i != 0
737
- cmd = cmd1.stdout + cmd2.stdout
738
- ports = []
739
- # parse all lines
740
- cmd.each_line do |line|
741
- port_info = parse_netstat_line(line)
742
- next unless %w{tcp tcp6 udp udp6}.include?(port_info['protocol'])
743
- ports.push(port_info)
744
- end
745
- # select all ports, where we `listen`
746
- ports.select { |val| val if 'listen'.casecmp(val['state']) == 0 }
747
- end
748
-
749
- def parse_netstat_line(line)
750
- # parse each line
751
- # 1 - Proto, 2 - Recv-Q, 3 - Send-Q, 4 - Local Address, 5 - Foreign Address, 6 - (state)
752
- parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)?/.match(line)
753
-
754
- return {} if parsed.nil? || line.match(/^proto/i) || line.match(/^active/i)
755
- protocol = parsed[1].downcase
756
- state = parsed[6].nil?? ' ' : parsed[6].downcase
757
- local_addr = parsed[4]
758
- local_addr[local_addr.rindex('.')] = ':'
759
- # extract host and port information
760
- host, port = parse_net_address(local_addr, protocol)
761
- return {} if host.nil?
762
- # map data
763
- {
764
- 'port' => port,
765
- 'address' => host,
766
- 'protocol' => protocol,
767
- 'state' => state,
768
- }
769
- end
770
- end
771
- end
1
+ # encoding: utf-8
2
+
3
+ require 'utils/parser'
4
+ require 'utils/filter'
5
+ require 'ipaddr'
6
+
7
+ # TODO: currently we return local ip only
8
+ # TODO: improve handling of same port on multiple interfaces
9
+ module Inspec::Resources
10
+ class Port < Inspec.resource(1)
11
+ name 'port'
12
+ supports platform: 'unix'
13
+ supports platform: 'windows'
14
+ desc "Use the port InSpec audit resource to test basic port properties, such as port, process, if it's listening."
15
+ example "
16
+ describe port(80) do
17
+ it { should be_listening }
18
+ its('protocols') {should eq ['tcp']}
19
+ its('addresses') {should eq ['127.0.0.1']}
20
+ end
21
+
22
+ describe port.where { protocol =~ /tcp/ && port > 80 } do
23
+ it { should_not be_listening }
24
+ end
25
+ "
26
+
27
+ def initialize(*args)
28
+ args.unshift(nil) if args.length <= 1 # add the ip address to the front
29
+ @ip = args[0]
30
+ @port = if args[1].nil?
31
+ nil
32
+ else
33
+ args[1].to_i
34
+ end
35
+
36
+ @cache = nil
37
+ @port_manager = port_manager_for_os
38
+ return skip_resource 'The `port` resource is not supported on your OS yet.' if @port_manager.nil?
39
+ end
40
+
41
+ filter = FilterTable.create
42
+ filter.add_accessor(:where)
43
+ .add_accessor(:entries)
44
+ .add(:ports, field: 'port', style: :simple)
45
+ .add(:addresses, field: 'address', style: :simple)
46
+ .add(:protocols, field: 'protocol', style: :simple)
47
+ .add(:processes, field: 'process', style: :simple)
48
+ .add(:pids, field: 'pid', style: :simple)
49
+ .add(:listening?) { |x| !x.entries.empty? }
50
+ filter.connect(self, :info)
51
+
52
+ def to_s
53
+ "Port #{@port}"
54
+ end
55
+
56
+ private
57
+
58
+ def port_manager_for_os
59
+ os = inspec.os
60
+ if os.linux?
61
+ LinuxPorts.new(inspec)
62
+ elsif os.aix?
63
+ # AIX: see http://www.ibm.com/developerworks/aix/library/au-lsof.html#resources
64
+ # and https://www-01.ibm.com/marketing/iwm/iwm/web/reg/pick.do?source=aixbp
65
+ AixPorts.new(inspec)
66
+ elsif os.darwin?
67
+ # Darwin: https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/lsof.8.html
68
+ LsofPorts.new(inspec)
69
+ elsif os.windows?
70
+ WindowsPorts.new(inspec)
71
+ elsif ['freebsd'].include?(os[:family])
72
+ FreeBsdPorts.new(inspec)
73
+ elsif os.solaris?
74
+ SolarisPorts.new(inspec)
75
+ elsif os.hpux?
76
+ HpuxPorts.new(inspec)
77
+ end
78
+ end
79
+
80
+ def info
81
+ return @cache if !@cache.nil?
82
+ # abort if os detection has not worked
83
+ return @cache = [] if @port_manager.nil?
84
+ # query ports
85
+ cache = @port_manager.info || []
86
+ cache.select! { |x| x['port'] == @port } unless @port.nil?
87
+ cache.select! { |x| x['address'] == @ip } unless @ip.nil?
88
+ @cache = cache
89
+ end
90
+ end
91
+
92
+ # implements an info method and returns all ip adresses and protocols for
93
+ # each port
94
+ # [{
95
+ # 'port' => 22,
96
+ # 'address' => '0.0.0.0'
97
+ # 'protocol' => 'tcp'
98
+ # },
99
+ # {
100
+ # 'port' => 22,
101
+ # 'address' => '::'
102
+ # 'protocol' => 'tcp6'
103
+ # }]
104
+ class PortsInfo
105
+ attr_reader :inspec
106
+ def initialize(inspec)
107
+ @inspec = inspec
108
+ end
109
+ end
110
+
111
+ # TODO: Add UDP infromation Get-NetUDPEndpoint
112
+ # TODO: currently Windows only supports tcp ports
113
+ # TODO: Get-NetTCPConnection does not return PIDs
114
+ # TODO: double-check output with 'netstat -ano'
115
+ # @see https://connect.microsoft.com/PowerShell/feedback/details/1349420/get-nettcpconnection-does-not-show-processid
116
+ class WindowsPorts < PortsInfo
117
+ def info
118
+ netstat_info || powershell_info
119
+ end
120
+
121
+ private
122
+
123
+ def powershell_info
124
+ cmd = inspec.command('Get-NetTCPConnection -state Listen | Select-Object -Property State, Caption, Description, LocalAddress, LocalPort, RemoteAddress, RemotePort, DisplayName, Status | ConvertTo-Json')
125
+ return nil if cmd.exit_status != 0
126
+
127
+ entries = JSON.parse(cmd.stdout)
128
+ return nil if entries.nil?
129
+
130
+ entries.map { |x|
131
+ {
132
+ 'port' => x['LocalPort'],
133
+ 'address' => x['LocalAddress'],
134
+ 'protocol' => 'tcp',
135
+ }
136
+ }
137
+ rescue JSON::ParserError => _e
138
+ return nil
139
+ end
140
+
141
+ def netstat_info
142
+ # retrieve processes grepping by LISTENING state with 0 lines before and 1 after to catch the process name
143
+ # also UDP ports have nothing in the State column
144
+ cmd = inspec.command('netstat -anbo | Select-String -CaseSensitive -pattern "^\s+UDP|\s+LISTENING\s+\d+$" -context 0,1')
145
+ return nil if cmd.exit_status != 0
146
+ lines = cmd.stdout.scan(/^>\s*(tcp\S*|udp\S*)\s+(\S+):(\d+)\s+(\S+)\s+(\S*)\s+(\d+)\s+(.+)/i)
147
+ lines.map do |line|
148
+ pid = line[5].to_i
149
+ process = line[6].delete('[').delete(']').strip
150
+ process = 'System' if process == 'Can not obtain ownership information' && pid == 4
151
+ {
152
+ 'port' => line[2].to_i,
153
+ 'address' => line[1].delete('[').delete(']'),
154
+ 'protocol' => line[0].downcase,
155
+ 'pid' => pid,
156
+ 'process' => process,
157
+ }
158
+ end
159
+ end
160
+ end
161
+
162
+ # extracts udp and tcp ports from the lsof command
163
+ class LsofPorts < PortsInfo
164
+ attr_reader :lsof
165
+
166
+ def initialize(inspec, lsofpath = nil)
167
+ @lsof = lsofpath || 'lsof'
168
+ super(inspec)
169
+ end
170
+
171
+ def info
172
+ ports = []
173
+
174
+ # check that lsof is available, otherwise fail
175
+ raise 'Please ensure `lsof` is available on the machine.' if !inspec.command(@lsof.to_s).exist?
176
+
177
+ # -F p=pid, c=command, P=protocol name, t=type, n=internet addresses
178
+ # see 'OUTPUT FOR OTHER PROGRAMS' in LSOF(8)
179
+ lsof_cmd = inspec.command("#{@lsof} -nP -i -FpctPn")
180
+ return nil if lsof_cmd.exit_status.to_i != 0
181
+
182
+ # map to desired return struct
183
+ lsof_parser(lsof_cmd).each do |process, port_ids|
184
+ pid, cmd = process.split(':')
185
+ port_ids.each do |port_str|
186
+ # should not break on ipv6 addresses
187
+ ipv, proto, port, host = port_str.split(':', 4)
188
+ ports.push({ 'port' => port.to_i,
189
+ 'address' => host,
190
+ 'protocol' => ipv == 'ipv6' ? proto + '6' : proto,
191
+ 'process' => cmd,
192
+ 'pid' => pid.to_i })
193
+ end
194
+ end
195
+
196
+ ports
197
+ end
198
+
199
+ # rubocop:disable Metrics/CyclomaticComplexity
200
+ # rubocop:disable Metrics/AbcSize
201
+ def lsof_parser(lsof_cmd)
202
+ procs = {}
203
+ # build this with formatted output (-F) from lsof
204
+ # procs = {
205
+ # '123:sshd' => [
206
+ # 'ipv4:tcp:22:127.0.0.1',
207
+ # 'ipv6:tcp:22:::1',
208
+ # 'ipv4:tcp:*',
209
+ # 'ipv6:tcp:*',
210
+ # ],
211
+ # '456:ntpd' => [
212
+ # 'ipv4:udp:123:*',
213
+ # 'ipv6:udp:123:*',
214
+ # ]
215
+ # }
216
+ proc_id = port_id = nil
217
+ lsof_cmd.stdout.each_line do |line|
218
+ line.chomp!
219
+ key = line.slice!(0)
220
+ case key
221
+ when 'p'
222
+ proc_id = line
223
+ port_id = nil
224
+ when 'c'
225
+ proc_id += ':' + line
226
+ when 't'
227
+ port_id = line.downcase
228
+ when 'P'
229
+ port_id += ':' + line.downcase
230
+ when 'n'
231
+ src, dst = line.split('->')
232
+
233
+ # skip active comm streams
234
+ next if dst
235
+
236
+ host, port = /^(\S+):(\d+|\*)$/.match(src)[1, 2]
237
+
238
+ # skip channels from port 0 - what does this mean?
239
+ next if port == '*'
240
+
241
+ # create new array stub if !exist?
242
+ procs[proc_id] = [] unless procs.key?(proc_id)
243
+
244
+ # change address '*' to zero
245
+ host = port_id =~ /^ipv6:/ ? '[::]' : '0.0.0.0' if host == '*'
246
+ # entrust URI to scrub the host and port
247
+ begin
248
+ uri = URI("addr://#{host}:#{port}")
249
+ uri.host && uri.port
250
+ rescue => e
251
+ warn "could not parse URI 'addr://#{host}:#{port}' - #{e}"
252
+ next
253
+ end
254
+
255
+ # e.g. 'ipv4:tcp:22:127.0.0.1'
256
+ # strip ipv6 squares for inspec
257
+ port_id += ':' + port + ':' + host.gsub(/^\[|\]$/, '')
258
+
259
+ # lsof will give us another port unless it's done
260
+ procs[proc_id] << port_id
261
+ end
262
+ end
263
+
264
+ procs
265
+ end
266
+ end
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
+
383
+ # extract port information from netstat
384
+ class LinuxPorts < PortsInfo
385
+ ALLOWED_PROTOCOLS = %w{tcp tcp6 udp udp6}.freeze
386
+
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
+
410
+ cmd = inspec.command('netstat -tulpen')
411
+ return nil unless cmd.exit_status.to_i.zero?
412
+
413
+ ports = []
414
+ # parse all lines
415
+ cmd.stdout.each_line do |line|
416
+ port_info = parse_netstat_line(line)
417
+
418
+ # only push protocols we are interested in
419
+ next unless %w{tcp tcp6 udp udp6}.include?(port_info['protocol'])
420
+ ports.push(port_info)
421
+ end
422
+ ports
423
+ end
424
+
425
+ def parse_net_address(net_addr, protocol)
426
+ if protocol.eql?('tcp6') || protocol.eql?('udp6')
427
+ # prep for URI parsing, parse ip6 port
428
+ ip6 = /^(\S+):(\d+)$/.match(net_addr)
429
+ ip6addr = ip6[1]
430
+ ip6addr = '::' if ip6addr =~ /^:::$/
431
+
432
+ # v6 addresses need to end in a double-colon when using
433
+ # shorthand notation. netstat ends with a single colon.
434
+ # IPAddr will fail to properly parse an address unless it
435
+ # uses a double-colon for short-hand notation.
436
+ ip6addr += ':' if ip6addr =~ /\w:$/
437
+
438
+ begin
439
+ ip_parser = IPAddr.new(ip6addr)
440
+ rescue IPAddr::InvalidAddressError
441
+ # This IP is not parsable. There appears to be a bug in netstat
442
+ # output that truncates link-local IP addresses:
443
+ # example: udp6 0 0 fe80::42:acff:fe11::123 :::* 0 54550 3335/ntpd
444
+ # actual link address: inet6 fe80::42:acff:fe11:5/64 scope link
445
+ #
446
+ # in this example, the "5" is truncated making the netstat output
447
+ # an invalid IP address.
448
+ return [nil, nil]
449
+ end
450
+
451
+ # Check to see if this is a IPv4 address in a tcp6/udp6 line.
452
+ # If so, don't put brackets around the IP or URI won't know how
453
+ # to properly handle it.
454
+ # example: tcp6 0 0 127.0.0.1:8005 :::* LISTEN
455
+ if ip_parser.ipv4?
456
+ ip_addr = URI("addr://#{ip6addr}:#{ip6[2]}")
457
+ host = ip_addr.host
458
+ else
459
+ ip_addr = URI("addr://[#{ip6addr}]:#{ip6[2]}")
460
+ # strip []
461
+ host = ip_addr.host[1..ip_addr.host.size-2]
462
+ end
463
+ else
464
+ ip_addr = URI('addr://'+net_addr)
465
+ host = ip_addr.host
466
+ end
467
+
468
+ port = ip_addr.port
469
+
470
+ [host, port]
471
+ rescue URI::InvalidURIError => e
472
+ warn "Could not parse #{net_addr}, #{e}"
473
+ nil
474
+ end
475
+
476
+ def parse_netstat_line(line)
477
+ # parse each line
478
+ # 1 - Proto, 2 - Recv-Q, 3 - Send-Q, 4 - Local Address, 5 - Foreign Address, 6 - State, 7 - Inode, 8 - PID/Program name
479
+ parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)?\s+(\S+)\s+(\S+)\s+(\S+)/.match(line)
480
+ return {} if parsed.nil? || line.match(/^proto/i)
481
+
482
+ # parse ip4 and ip6 addresses
483
+ protocol = parsed[1].downcase
484
+
485
+ # detect protocol if not provided
486
+ protocol += '6' if parsed[4].count(':') > 1 && %w{tcp udp}.include?(protocol)
487
+
488
+ # extract host and port information
489
+ host, port = parse_net_address(parsed[4], protocol)
490
+ return {} if host.nil?
491
+
492
+ # extract PID
493
+ process = parsed[9].split('/')
494
+ pid = process[0]
495
+ pid = pid.to_i if pid =~ /^\d+$/
496
+ process = process[1]
497
+
498
+ {
499
+ 'port' => port,
500
+ 'address' => host,
501
+ 'protocol' => protocol,
502
+ 'process' => process,
503
+ 'pid' => pid,
504
+ }
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
613
+ end
614
+
615
+ # extracts information from sockstat
616
+ class FreeBsdPorts < PortsInfo
617
+ def info
618
+ cmd = inspec.command('sockstat -46l')
619
+ return nil if cmd.exit_status.to_i != 0
620
+
621
+ ports = []
622
+ # split on each newline
623
+ cmd.stdout.each_line do |line|
624
+ port_info = parse_sockstat_line(line)
625
+
626
+ # push data, if not headerfile
627
+ next unless %w{tcp tcp6 udp udp6}.include?(port_info['protocol'])
628
+ ports.push(port_info)
629
+ end
630
+ ports
631
+ end
632
+
633
+ def parse_net_address(net_addr, protocol)
634
+ case protocol
635
+ when 'tcp4', 'udp4', 'tcp', 'udp'
636
+ # replace * with 0.0.0.0
637
+ net_addr = net_addr.gsub(/^\*:/, '0.0.0.0:') if net_addr =~ /^*:(\d+)$/
638
+ ip_addr = URI('addr://'+net_addr)
639
+ host = ip_addr.host
640
+ port = ip_addr.port
641
+ when 'tcp6', 'udp6'
642
+ return [] if net_addr == '*:*' # abort for now
643
+ # replace * with 0:0:0:0:0:0:0:0
644
+ net_addr = net_addr.gsub(/^\*:/, '0:0:0:0:0:0:0:0:') if net_addr =~ /^*:(\d+)$/
645
+ # extract port
646
+ ip6 = /^(\S+):(\d+)$/.match(net_addr)
647
+ ip6addr = ip6[1]
648
+ ip_addr = URI("addr://[#{ip6addr}]:#{ip6[2]}")
649
+ # replace []
650
+ host = ip_addr.host[1..ip_addr.host.size-2]
651
+ port = ip_addr.port
652
+ end
653
+ [host, port]
654
+ rescue URI::InvalidURIError => e
655
+ warn "Could not parse #{net_addr}, #{e}"
656
+ nil
657
+ end
658
+
659
+ def parse_sockstat_line(line)
660
+ # 1 - USER, 2 - COMMAND, 3 - PID, 4 - FD 5 - PROTO, 6 - LOCAL ADDRESS, 7 - FOREIGN ADDRESS
661
+ parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/.match(line)
662
+ return {} if parsed.nil?
663
+
664
+ # extract ip information
665
+ protocol = parsed[5].downcase
666
+ host, port = parse_net_address(parsed[6], protocol)
667
+ return {} if host.nil? or port.nil?
668
+
669
+ # extract process
670
+ process = parsed[2]
671
+
672
+ # extract PID
673
+ pid = parsed[3]
674
+ pid = pid.to_i if pid =~ /^\d+$/
675
+
676
+ # map tcp4 and udp4
677
+ protocol = 'tcp' if protocol.eql?('tcp4')
678
+ protocol = 'udp' if protocol.eql?('udp4')
679
+
680
+ {
681
+ 'port' => port,
682
+ 'address' => host,
683
+ 'protocol' => protocol,
684
+ 'process' => process,
685
+ 'pid' => pid,
686
+ }
687
+ end
688
+ end
689
+
690
+ class SolarisPorts < FreeBsdPorts
691
+ include SolarisNetstatParser
692
+
693
+ def info
694
+ # extract all port info
695
+ cmd = inspec.command('netstat -an -f inet -f inet6')
696
+ return nil if cmd.exit_status.to_i != 0
697
+
698
+ # parse the content
699
+ netstat_ports = parse_netstat(cmd.stdout)
700
+
701
+ # filter all ports, where we `listen`
702
+ listen = netstat_ports.select { |val|
703
+ !val['state'].nil? && 'listen'.casecmp(val['state']) == 0
704
+ }
705
+
706
+ # map the data
707
+ ports = listen.map { |val|
708
+ protocol = val['protocol']
709
+ local_addr = val['local-address']
710
+
711
+ # solaris uses 127.0.0.1.57455 instead 127.0.0.1:57455, lets convert the
712
+ # the last . to :
713
+ local_addr[local_addr.rindex('.')] = ':'
714
+ host, port = parse_net_address(local_addr, protocol)
715
+ if host.nil?
716
+ nil
717
+ else
718
+ {
719
+ 'port' => port,
720
+ 'address' => host,
721
+ 'protocol' => protocol,
722
+ }
723
+ end
724
+ }
725
+ ports.compact
726
+ end
727
+ end
728
+
729
+ # extracts information from netstat for hpux
730
+ class HpuxPorts < FreeBsdPorts
731
+ def info
732
+ ## Can't use 'netstat -an -f inet -f inet6' as the latter -f option overrides the former one and return only inet ports
733
+ cmd1 = inspec.command('netstat -an -f inet')
734
+ return nil if cmd1.exit_status.to_i != 0
735
+ cmd2 = inspec.command('netstat -an -f inet6')
736
+ return nil if cmd2.exit_status.to_i != 0
737
+ cmd = cmd1.stdout + cmd2.stdout
738
+ ports = []
739
+ # parse all lines
740
+ cmd.each_line do |line|
741
+ port_info = parse_netstat_line(line)
742
+ next unless %w{tcp tcp6 udp udp6}.include?(port_info['protocol'])
743
+ ports.push(port_info)
744
+ end
745
+ # select all ports, where we `listen`
746
+ ports.select { |val| val if 'listen'.casecmp(val['state']) == 0 }
747
+ end
748
+
749
+ def parse_netstat_line(line)
750
+ # parse each line
751
+ # 1 - Proto, 2 - Recv-Q, 3 - Send-Q, 4 - Local Address, 5 - Foreign Address, 6 - (state)
752
+ parsed = /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)?/.match(line)
753
+
754
+ return {} if parsed.nil? || line.match(/^proto/i) || line.match(/^active/i)
755
+ protocol = parsed[1].downcase
756
+ state = parsed[6].nil?? ' ' : parsed[6].downcase
757
+ local_addr = parsed[4]
758
+ local_addr[local_addr.rindex('.')] = ':'
759
+ # extract host and port information
760
+ host, port = parse_net_address(local_addr, protocol)
761
+ return {} if host.nil?
762
+ # map data
763
+ {
764
+ 'port' => port,
765
+ 'address' => host,
766
+ 'protocol' => protocol,
767
+ 'state' => state,
768
+ }
769
+ end
770
+ end
771
+ end