inspec 1.51.6 → 1.51.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (404) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +101 -101
  3. data/CHANGELOG.md +2915 -2902
  4. data/Gemfile +53 -53
  5. data/LICENSE +14 -14
  6. data/MAINTAINERS.md +31 -31
  7. data/MAINTAINERS.toml +47 -47
  8. data/README.md +419 -419
  9. data/Rakefile +167 -167
  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 +165 -165
  19. data/docs/migration.md +293 -293
  20. data/docs/plugin_kitchen_inspec.md +49 -49
  21. data/docs/profiles.md +370 -370
  22. data/docs/resources/aide_conf.md.erb +78 -78
  23. data/docs/resources/apache.md.erb +66 -66
  24. data/docs/resources/apache_conf.md.erb +67 -67
  25. data/docs/resources/apt.md.erb +70 -70
  26. data/docs/resources/audit_policy.md.erb +46 -46
  27. data/docs/resources/auditd.md.erb +78 -78
  28. data/docs/resources/auditd_conf.md.erb +68 -68
  29. data/docs/resources/auditd_rules.md.erb +116 -116
  30. data/docs/resources/bash.md.erb +74 -74
  31. data/docs/resources/bond.md.erb +89 -89
  32. data/docs/resources/bridge.md.erb +54 -54
  33. data/docs/resources/bsd_service.md.erb +65 -65
  34. data/docs/resources/command.md.erb +137 -137
  35. data/docs/resources/cpan.md.erb +77 -77
  36. data/docs/resources/cran.md.erb +63 -63
  37. data/docs/resources/crontab.md.erb +87 -87
  38. data/docs/resources/csv.md.erb +53 -53
  39. data/docs/resources/dh_params.md.erb +216 -216
  40. data/docs/resources/directory.md.erb +28 -28
  41. data/docs/resources/docker.md.erb +163 -163
  42. data/docs/resources/docker_container.md.erb +99 -99
  43. data/docs/resources/docker_image.md.erb +93 -93
  44. data/docs/resources/docker_service.md.erb +113 -113
  45. data/docs/resources/elasticsearch.md.erb +230 -230
  46. data/docs/resources/etc_fstab.md.erb +124 -124
  47. data/docs/resources/etc_group.md.erb +74 -74
  48. data/docs/resources/etc_hosts.md.erb +75 -75
  49. data/docs/resources/etc_hosts_allow.md.erb +73 -73
  50. data/docs/resources/etc_hosts_deny.md.erb +73 -73
  51. data/docs/resources/file.md.erb +512 -512
  52. data/docs/resources/filesystem.md.erb +40 -40
  53. data/docs/resources/firewalld.md.erb +105 -105
  54. data/docs/resources/gem.md.erb +78 -78
  55. data/docs/resources/group.md.erb +60 -60
  56. data/docs/resources/grub_conf.md.erb +101 -100
  57. data/docs/resources/host.md.erb +77 -77
  58. data/docs/resources/http.md.erb +104 -98
  59. data/docs/resources/iis_app.md.erb +120 -116
  60. data/docs/resources/iis_site.md.erb +132 -128
  61. data/docs/resources/inetd_conf.md.erb +95 -84
  62. data/docs/resources/ini.md.erb +72 -69
  63. data/docs/resources/interface.md.erb +55 -46
  64. data/docs/resources/iptables.md.erb +63 -63
  65. data/docs/resources/json.md.erb +61 -61
  66. data/docs/resources/kernel_module.md.erb +106 -106
  67. data/docs/resources/kernel_parameter.md.erb +58 -58
  68. data/docs/resources/key_rsa.md.erb +73 -73
  69. data/docs/resources/launchd_service.md.erb +56 -56
  70. data/docs/resources/limits_conf.md.erb +66 -66
  71. data/docs/resources/login_def.md.erb +62 -62
  72. data/docs/resources/mount.md.erb +68 -68
  73. data/docs/resources/mssql_session.md.erb +59 -59
  74. data/docs/resources/mysql_conf.md.erb +98 -98
  75. data/docs/resources/mysql_session.md.erb +73 -73
  76. data/docs/resources/nginx.md.erb +78 -78
  77. data/docs/resources/nginx_conf.md.erb +127 -127
  78. data/docs/resources/npm.md.erb +59 -59
  79. data/docs/resources/ntp_conf.md.erb +59 -59
  80. data/docs/resources/oneget.md.erb +52 -52
  81. data/docs/resources/oracledb_session.md.erb +51 -51
  82. data/docs/resources/os.md.erb +140 -140
  83. data/docs/resources/os_env.md.erb +77 -77
  84. data/docs/resources/package.md.erb +119 -119
  85. data/docs/resources/packages.md.erb +66 -66
  86. data/docs/resources/parse_config.md.erb +102 -102
  87. data/docs/resources/parse_config_file.md.erb +137 -137
  88. data/docs/resources/passwd.md.erb +140 -140
  89. data/docs/resources/pip.md.erb +66 -66
  90. data/docs/resources/port.md.erb +136 -136
  91. data/docs/resources/postgres_conf.md.erb +78 -78
  92. data/docs/resources/postgres_hba_conf.md.erb +92 -92
  93. data/docs/resources/postgres_ident_conf.md.erb +75 -75
  94. data/docs/resources/postgres_session.md.erb +68 -68
  95. data/docs/resources/powershell.md.erb +101 -101
  96. data/docs/resources/processes.md.erb +107 -107
  97. data/docs/resources/rabbitmq_config.md.erb +40 -40
  98. data/docs/resources/registry_key.md.erb +157 -157
  99. data/docs/resources/runit_service.md.erb +56 -56
  100. data/docs/resources/security_policy.md.erb +46 -46
  101. data/docs/resources/service.md.erb +120 -120
  102. data/docs/resources/shadow.md.erb +143 -143
  103. data/docs/resources/ssh_config.md.erb +79 -79
  104. data/docs/resources/sshd_config.md.erb +82 -82
  105. data/docs/resources/ssl.md.erb +118 -118
  106. data/docs/resources/sys_info.md.erb +41 -41
  107. data/docs/resources/systemd_service.md.erb +56 -56
  108. data/docs/resources/sysv_service.md.erb +56 -56
  109. data/docs/resources/upstart_service.md.erb +56 -56
  110. data/docs/resources/user.md.erb +139 -139
  111. data/docs/resources/users.md.erb +126 -126
  112. data/docs/resources/vbscript.md.erb +54 -54
  113. data/docs/resources/virtualization.md.erb +56 -56
  114. data/docs/resources/windows_feature.md.erb +46 -46
  115. data/docs/resources/windows_hotfix.md.erb +52 -52
  116. data/docs/resources/windows_task.md.erb +89 -89
  117. data/docs/resources/wmi.md.erb +80 -80
  118. data/docs/resources/x509_certificate.md.erb +150 -150
  119. data/docs/resources/xinetd_conf.md.erb +155 -155
  120. data/docs/resources/xml.md.erb +84 -84
  121. data/docs/resources/yaml.md.erb +68 -68
  122. data/docs/resources/yum.md.erb +97 -97
  123. data/docs/resources/zfs_dataset.md.erb +52 -52
  124. data/docs/resources/zfs_pool.md.erb +46 -46
  125. data/docs/ruby_usage.md +203 -203
  126. data/docs/shared/matcher_be.md.erb +1 -1
  127. data/docs/shared/matcher_cmp.md.erb +43 -43
  128. data/docs/shared/matcher_eq.md.erb +3 -3
  129. data/docs/shared/matcher_include.md.erb +1 -1
  130. data/docs/shared/matcher_match.md.erb +1 -1
  131. data/docs/shell.md +172 -172
  132. data/examples/README.md +8 -8
  133. data/examples/inheritance/README.md +65 -65
  134. data/examples/inheritance/controls/example.rb +14 -14
  135. data/examples/inheritance/inspec.yml +15 -15
  136. data/examples/kitchen-ansible/.kitchen.yml +25 -25
  137. data/examples/kitchen-ansible/Gemfile +19 -19
  138. data/examples/kitchen-ansible/README.md +53 -53
  139. data/examples/kitchen-ansible/files/nginx.repo +6 -6
  140. data/examples/kitchen-ansible/tasks/main.yml +16 -16
  141. data/examples/kitchen-ansible/test/integration/default/default.yml +5 -5
  142. data/examples/kitchen-ansible/test/integration/default/web_spec.rb +28 -28
  143. data/examples/kitchen-chef/.kitchen.yml +20 -20
  144. data/examples/kitchen-chef/Berksfile +3 -3
  145. data/examples/kitchen-chef/Gemfile +19 -19
  146. data/examples/kitchen-chef/README.md +27 -27
  147. data/examples/kitchen-chef/metadata.rb +7 -7
  148. data/examples/kitchen-chef/recipes/default.rb +6 -6
  149. data/examples/kitchen-chef/recipes/nginx.rb +30 -30
  150. data/examples/kitchen-chef/test/integration/default/web_spec.rb +28 -28
  151. data/examples/kitchen-puppet/.kitchen.yml +22 -22
  152. data/examples/kitchen-puppet/Gemfile +20 -20
  153. data/examples/kitchen-puppet/Puppetfile +25 -25
  154. data/examples/kitchen-puppet/README.md +53 -53
  155. data/examples/kitchen-puppet/manifests/site.pp +33 -33
  156. data/examples/kitchen-puppet/metadata.json +11 -11
  157. data/examples/kitchen-puppet/test/integration/default/web_spec.rb +28 -28
  158. data/examples/meta-profile/README.md +37 -37
  159. data/examples/meta-profile/controls/example.rb +13 -13
  160. data/examples/meta-profile/inspec.yml +13 -13
  161. data/examples/profile-attribute.yml +2 -2
  162. data/examples/profile-attribute/README.md +14 -14
  163. data/examples/profile-attribute/controls/example.rb +11 -11
  164. data/examples/profile-attribute/inspec.yml +8 -8
  165. data/examples/profile-sensitive/README.md +29 -29
  166. data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -9
  167. data/examples/profile-sensitive/controls/sensitive.rb +9 -9
  168. data/examples/profile-sensitive/inspec.yml +8 -8
  169. data/examples/profile/README.md +48 -48
  170. data/examples/profile/controls/example.rb +23 -23
  171. data/examples/profile/controls/gordon.rb +36 -36
  172. data/examples/profile/controls/meta.rb +34 -34
  173. data/examples/profile/inspec.yml +10 -10
  174. data/examples/profile/libraries/gordon_config.rb +53 -53
  175. data/inspec.gemspec +47 -47
  176. data/lib/bundles/README.md +3 -3
  177. data/lib/bundles/inspec-artifact.rb +7 -7
  178. data/lib/bundles/inspec-artifact/README.md +1 -1
  179. data/lib/bundles/inspec-artifact/cli.rb +277 -277
  180. data/lib/bundles/inspec-compliance.rb +16 -16
  181. data/lib/bundles/inspec-compliance/.kitchen.yml +20 -20
  182. data/lib/bundles/inspec-compliance/README.md +185 -185
  183. data/lib/bundles/inspec-compliance/api.rb +316 -316
  184. data/lib/bundles/inspec-compliance/api/login.rb +152 -152
  185. data/lib/bundles/inspec-compliance/bootstrap.sh +41 -41
  186. data/lib/bundles/inspec-compliance/cli.rb +277 -277
  187. data/lib/bundles/inspec-compliance/configuration.rb +103 -103
  188. data/lib/bundles/inspec-compliance/http.rb +86 -86
  189. data/lib/bundles/inspec-compliance/support.rb +36 -36
  190. data/lib/bundles/inspec-compliance/target.rb +98 -98
  191. data/lib/bundles/inspec-compliance/test/integration/default/cli.rb +93 -93
  192. data/lib/bundles/inspec-habitat.rb +12 -12
  193. data/lib/bundles/inspec-habitat/cli.rb +36 -36
  194. data/lib/bundles/inspec-habitat/log.rb +10 -10
  195. data/lib/bundles/inspec-habitat/profile.rb +390 -390
  196. data/lib/bundles/inspec-init.rb +8 -8
  197. data/lib/bundles/inspec-init/README.md +31 -31
  198. data/lib/bundles/inspec-init/cli.rb +97 -97
  199. data/lib/bundles/inspec-init/templates/profile/README.md +3 -3
  200. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +19 -19
  201. data/lib/bundles/inspec-init/templates/profile/inspec.yml +8 -8
  202. data/lib/bundles/inspec-supermarket.rb +13 -13
  203. data/lib/bundles/inspec-supermarket/README.md +45 -45
  204. data/lib/bundles/inspec-supermarket/api.rb +84 -84
  205. data/lib/bundles/inspec-supermarket/cli.rb +65 -65
  206. data/lib/bundles/inspec-supermarket/target.rb +34 -34
  207. data/lib/fetchers/git.rb +163 -163
  208. data/lib/fetchers/local.rb +74 -74
  209. data/lib/fetchers/mock.rb +35 -35
  210. data/lib/fetchers/url.rb +204 -204
  211. data/lib/inspec.rb +24 -24
  212. data/lib/inspec/archive/tar.rb +29 -29
  213. data/lib/inspec/archive/zip.rb +19 -19
  214. data/lib/inspec/backend.rb +92 -92
  215. data/lib/inspec/base_cli.rb +324 -322
  216. data/lib/inspec/cached_fetcher.rb +66 -66
  217. data/lib/inspec/cli.rb +298 -298
  218. data/lib/inspec/completions/bash.sh.erb +45 -45
  219. data/lib/inspec/completions/fish.sh.erb +34 -34
  220. data/lib/inspec/completions/zsh.sh.erb +61 -61
  221. data/lib/inspec/control_eval_context.rb +179 -179
  222. data/lib/inspec/dependencies/cache.rb +72 -72
  223. data/lib/inspec/dependencies/dependency_set.rb +92 -92
  224. data/lib/inspec/dependencies/lockfile.rb +115 -115
  225. data/lib/inspec/dependencies/requirement.rb +123 -123
  226. data/lib/inspec/dependencies/resolver.rb +86 -86
  227. data/lib/inspec/describe.rb +27 -27
  228. data/lib/inspec/dsl.rb +66 -66
  229. data/lib/inspec/dsl_shared.rb +33 -33
  230. data/lib/inspec/env_printer.rb +157 -157
  231. data/lib/inspec/errors.rb +13 -13
  232. data/lib/inspec/exceptions.rb +12 -12
  233. data/lib/inspec/expect.rb +45 -45
  234. data/lib/inspec/fetcher.rb +45 -45
  235. data/lib/inspec/file_provider.rb +275 -275
  236. data/lib/inspec/formatters.rb +3 -3
  237. data/lib/inspec/formatters/base.rb +208 -208
  238. data/lib/inspec/formatters/json_rspec.rb +20 -20
  239. data/lib/inspec/formatters/show_progress.rb +12 -12
  240. data/lib/inspec/library_eval_context.rb +58 -58
  241. data/lib/inspec/log.rb +11 -11
  242. data/lib/inspec/metadata.rb +253 -253
  243. data/lib/inspec/method_source.rb +24 -24
  244. data/lib/inspec/objects.rb +14 -14
  245. data/lib/inspec/objects/attribute.rb +65 -65
  246. data/lib/inspec/objects/control.rb +61 -61
  247. data/lib/inspec/objects/describe.rb +92 -92
  248. data/lib/inspec/objects/each_loop.rb +36 -36
  249. data/lib/inspec/objects/list.rb +15 -15
  250. data/lib/inspec/objects/or_test.rb +40 -40
  251. data/lib/inspec/objects/ruby_helper.rb +15 -15
  252. data/lib/inspec/objects/tag.rb +27 -27
  253. data/lib/inspec/objects/test.rb +87 -87
  254. data/lib/inspec/objects/value.rb +27 -27
  255. data/lib/inspec/plugins.rb +60 -60
  256. data/lib/inspec/plugins/cli.rb +24 -24
  257. data/lib/inspec/plugins/fetcher.rb +86 -86
  258. data/lib/inspec/plugins/resource.rb +132 -132
  259. data/lib/inspec/plugins/secret.rb +15 -15
  260. data/lib/inspec/plugins/source_reader.rb +40 -40
  261. data/lib/inspec/polyfill.rb +12 -12
  262. data/lib/inspec/profile.rb +510 -510
  263. data/lib/inspec/profile_context.rb +207 -207
  264. data/lib/inspec/profile_vendor.rb +66 -66
  265. data/lib/inspec/reporters.rb +50 -33
  266. data/lib/inspec/reporters/base.rb +24 -23
  267. data/lib/inspec/reporters/cli.rb +395 -395
  268. data/lib/inspec/reporters/json.rb +134 -132
  269. data/lib/inspec/reporters/json_min.rb +48 -44
  270. data/lib/inspec/reporters/junit.rb +77 -77
  271. data/lib/inspec/require_loader.rb +33 -33
  272. data/lib/inspec/resource.rb +176 -176
  273. data/lib/inspec/rule.rb +266 -266
  274. data/lib/inspec/runner.rb +340 -337
  275. data/lib/inspec/runner_mock.rb +41 -41
  276. data/lib/inspec/runner_rspec.rb +163 -185
  277. data/lib/inspec/runtime_profile.rb +26 -26
  278. data/lib/inspec/schema.rb +186 -186
  279. data/lib/inspec/secrets.rb +19 -19
  280. data/lib/inspec/secrets/yaml.rb +30 -30
  281. data/lib/inspec/shell.rb +223 -223
  282. data/lib/inspec/shell_detector.rb +90 -90
  283. data/lib/inspec/source_reader.rb +29 -29
  284. data/lib/inspec/version.rb +8 -8
  285. data/lib/matchers/matchers.rb +397 -397
  286. data/lib/resources/aide_conf.rb +160 -160
  287. data/lib/resources/apache.rb +49 -49
  288. data/lib/resources/apache_conf.rb +158 -158
  289. data/lib/resources/apt.rb +150 -150
  290. data/lib/resources/audit_policy.rb +64 -64
  291. data/lib/resources/auditd.rb +233 -233
  292. data/lib/resources/auditd_conf.rb +56 -56
  293. data/lib/resources/auditd_rules.rb +205 -205
  294. data/lib/resources/bash.rb +36 -36
  295. data/lib/resources/bond.rb +69 -69
  296. data/lib/resources/bridge.rb +123 -123
  297. data/lib/resources/command.rb +69 -69
  298. data/lib/resources/cpan.rb +60 -60
  299. data/lib/resources/cran.rb +66 -66
  300. data/lib/resources/crontab.rb +169 -169
  301. data/lib/resources/csv.rb +58 -58
  302. data/lib/resources/dh_params.rb +83 -83
  303. data/lib/resources/directory.rb +25 -25
  304. data/lib/resources/docker.rb +239 -239
  305. data/lib/resources/docker_container.rb +92 -92
  306. data/lib/resources/docker_image.rb +86 -86
  307. data/lib/resources/docker_object.rb +57 -57
  308. data/lib/resources/docker_service.rb +94 -94
  309. data/lib/resources/elasticsearch.rb +168 -168
  310. data/lib/resources/etc_fstab.rb +102 -102
  311. data/lib/resources/etc_group.rb +157 -157
  312. data/lib/resources/etc_hosts.rb +81 -81
  313. data/lib/resources/etc_hosts_allow_deny.rb +122 -122
  314. data/lib/resources/file.rb +298 -298
  315. data/lib/resources/filesystem.rb +31 -31
  316. data/lib/resources/firewalld.rb +144 -144
  317. data/lib/resources/gem.rb +71 -71
  318. data/lib/resources/groups.rb +213 -213
  319. data/lib/resources/grub_conf.rb +237 -237
  320. data/lib/resources/host.rb +300 -300
  321. data/lib/resources/http.rb +252 -252
  322. data/lib/resources/iis_app.rb +103 -103
  323. data/lib/resources/iis_site.rb +147 -147
  324. data/lib/resources/inetd_conf.rb +63 -63
  325. data/lib/resources/ini.rb +29 -29
  326. data/lib/resources/interface.rb +130 -130
  327. data/lib/resources/iptables.rb +70 -70
  328. data/lib/resources/json.rb +115 -115
  329. data/lib/resources/kernel_module.rb +110 -110
  330. data/lib/resources/kernel_parameter.rb +58 -58
  331. data/lib/resources/key_rsa.rb +67 -67
  332. data/lib/resources/limits_conf.rb +56 -56
  333. data/lib/resources/login_def.rb +67 -67
  334. data/lib/resources/mount.rb +90 -90
  335. data/lib/resources/mssql_session.rb +103 -103
  336. data/lib/resources/mysql.rb +82 -82
  337. data/lib/resources/mysql_conf.rb +133 -133
  338. data/lib/resources/mysql_session.rb +72 -72
  339. data/lib/resources/nginx.rb +97 -97
  340. data/lib/resources/nginx_conf.rb +228 -228
  341. data/lib/resources/npm.rb +48 -48
  342. data/lib/resources/ntp_conf.rb +59 -59
  343. data/lib/resources/oneget.rb +72 -72
  344. data/lib/resources/oracledb_session.rb +140 -140
  345. data/lib/resources/os.rb +46 -46
  346. data/lib/resources/os_env.rb +76 -76
  347. data/lib/resources/package.rb +357 -357
  348. data/lib/resources/packages.rb +112 -112
  349. data/lib/resources/parse_config.rb +116 -116
  350. data/lib/resources/passwd.rb +96 -96
  351. data/lib/resources/pip.rb +89 -89
  352. data/lib/resources/platform.rb +112 -112
  353. data/lib/resources/port.rb +771 -771
  354. data/lib/resources/postgres.rb +132 -132
  355. data/lib/resources/postgres_conf.rb +122 -122
  356. data/lib/resources/postgres_hba_conf.rb +101 -101
  357. data/lib/resources/postgres_ident_conf.rb +79 -79
  358. data/lib/resources/postgres_session.rb +72 -72
  359. data/lib/resources/powershell.rb +58 -58
  360. data/lib/resources/processes.rb +204 -204
  361. data/lib/resources/rabbitmq_conf.rb +53 -53
  362. data/lib/resources/registry_key.rb +296 -296
  363. data/lib/resources/security_policy.rb +181 -181
  364. data/lib/resources/service.rb +784 -784
  365. data/lib/resources/shadow.rb +141 -141
  366. data/lib/resources/ssh_conf.rb +102 -102
  367. data/lib/resources/ssl.rb +99 -99
  368. data/lib/resources/sys_info.rb +26 -26
  369. data/lib/resources/toml.rb +32 -32
  370. data/lib/resources/users.rb +652 -652
  371. data/lib/resources/vbscript.rb +70 -70
  372. data/lib/resources/virtualization.rb +251 -251
  373. data/lib/resources/windows_feature.rb +85 -85
  374. data/lib/resources/windows_hotfix.rb +35 -35
  375. data/lib/resources/windows_task.rb +106 -106
  376. data/lib/resources/wmi.rb +114 -114
  377. data/lib/resources/x509_certificate.rb +143 -143
  378. data/lib/resources/xinetd.rb +112 -112
  379. data/lib/resources/xml.rb +45 -45
  380. data/lib/resources/yaml.rb +45 -45
  381. data/lib/resources/yum.rb +181 -181
  382. data/lib/resources/zfs_dataset.rb +60 -60
  383. data/lib/resources/zfs_pool.rb +49 -49
  384. data/lib/source_readers/flat.rb +39 -39
  385. data/lib/source_readers/inspec.rb +75 -75
  386. data/lib/utils/command_wrapper.rb +27 -27
  387. data/lib/utils/convert.rb +12 -12
  388. data/lib/utils/database_helpers.rb +77 -77
  389. data/lib/utils/erlang_parser.rb +192 -192
  390. data/lib/utils/filter.rb +272 -272
  391. data/lib/utils/filter_array.rb +27 -27
  392. data/lib/utils/find_files.rb +44 -44
  393. data/lib/utils/hash.rb +41 -41
  394. data/lib/utils/json_log.rb +18 -18
  395. data/lib/utils/latest_version.rb +22 -22
  396. data/lib/utils/modulator.rb +12 -12
  397. data/lib/utils/nginx_parser.rb +85 -85
  398. data/lib/utils/object_traversal.rb +49 -49
  399. data/lib/utils/parser.rb +274 -274
  400. data/lib/utils/plugin_registry.rb +93 -93
  401. data/lib/utils/simpleconfig.rb +132 -132
  402. data/lib/utils/spdx.rb +13 -13
  403. data/lib/utils/spdx.txt +343 -343
  404. metadata +2 -2
@@ -1,99 +1,99 @@
1
- # encoding: utf-8
2
- # copyright: 2015, Chef Software Inc.
3
- # author: Dominik Richter
4
- # author: Christoph Hartmann
5
-
6
- require 'sslshake'
7
- require 'utils/filter'
8
- require 'uri'
9
- require 'parallel'
10
-
11
- # Custom resource based on the InSpec resource DSL
12
- class SSL < Inspec.resource(1)
13
- name 'ssl'
14
-
15
- desc "
16
- SSL test resource
17
- "
18
-
19
- example "
20
- describe ssl(port: 443) do
21
- it { should be_enabled }
22
- end
23
-
24
- # protocols: ssl2, ssl3, tls1.0, tls1.1, tls1.2
25
- describe ssl(port: 443).protocols('ssl2') do
26
- it { should_not be_enabled }
27
- end
28
-
29
- # any ciphers, filter by name or regex
30
- describe ssl(port: 443).ciphers(/rc4/i) do
31
- it { should_not be_enabled }
32
- end
33
- "
34
-
35
- VERSIONS = [
36
- 'ssl2',
37
- 'ssl3',
38
- 'tls1.0',
39
- 'tls1.1',
40
- 'tls1.2',
41
- ].freeze
42
-
43
- attr_reader :host, :port, :timeout, :retries
44
-
45
- def initialize(opts = {})
46
- @host = opts[:host]
47
- if @host.nil?
48
- # Transports like SSH and WinRM will provide a hostname
49
- if inspec.backend.respond_to?('hostname')
50
- @host = inspec.backend.hostname
51
- elsif inspec.backend.class.to_s == 'Train::Transports::Local::Connection'
52
- @host = 'localhost'
53
- end
54
- end
55
- @port = opts[:port] || 443
56
- @timeout = opts[:timeout]
57
- @retries = opts[:retries]
58
- end
59
-
60
- filter = FilterTable.create
61
- filter.add(:enabled?) do |x|
62
- raise 'Cannot determine host for SSL test. Please specify it or use a different target.' if x.resource.host.nil?
63
- x.handshake.values.any? { |i| i['success'] }
64
- end
65
- filter.add_accessor(:where)
66
- .add_accessor(:entries)
67
- .add(:ciphers, field: 'cipher')
68
- .add(:protocols, field: 'protocol')
69
- .add(:handshake) { |x|
70
- groups = x.entries.group_by(&:protocol)
71
- res = Parallel.map(groups, in_threads: 8) do |proto, e|
72
- [proto, SSLShake.hello(x.resource.host, port: x.resource.port,
73
- protocol: proto, ciphers: e.map(&:cipher),
74
- timeout: x.resource.timeout, retries: x.resource.retries, servername: x.resource.host)]
75
- end
76
- Hash[res]
77
- }
78
- .connect(self, :scan_config)
79
-
80
- def to_s
81
- "SSL/TLS on #{@host}:#{@port}"
82
- end
83
-
84
- private
85
-
86
- def scan_config
87
- [
88
- { 'protocol' => 'ssl2', 'ciphers' => SSLShake::SSLv2::CIPHERS.keys },
89
- { 'protocol' => 'ssl3', 'ciphers' => SSLShake::TLS::SSL3_CIPHERS.keys },
90
- { 'protocol' => 'tls1.0', 'ciphers' => SSLShake::TLS::TLS10_CIPHERS.keys },
91
- { 'protocol' => 'tls1.1', 'ciphers' => SSLShake::TLS::TLS10_CIPHERS.keys },
92
- { 'protocol' => 'tls1.2', 'ciphers' => SSLShake::TLS::TLS_CIPHERS.keys },
93
- ].map do |line|
94
- line['ciphers'].map do |cipher|
95
- { 'protocol' => line['protocol'], 'cipher' => cipher }
96
- end
97
- end.flatten
98
- end
99
- end
1
+ # encoding: utf-8
2
+ # copyright: 2015, Chef Software Inc.
3
+ # author: Dominik Richter
4
+ # author: Christoph Hartmann
5
+
6
+ require 'sslshake'
7
+ require 'utils/filter'
8
+ require 'uri'
9
+ require 'parallel'
10
+
11
+ # Custom resource based on the InSpec resource DSL
12
+ class SSL < Inspec.resource(1)
13
+ name 'ssl'
14
+
15
+ desc "
16
+ SSL test resource
17
+ "
18
+
19
+ example "
20
+ describe ssl(port: 443) do
21
+ it { should be_enabled }
22
+ end
23
+
24
+ # protocols: ssl2, ssl3, tls1.0, tls1.1, tls1.2
25
+ describe ssl(port: 443).protocols('ssl2') do
26
+ it { should_not be_enabled }
27
+ end
28
+
29
+ # any ciphers, filter by name or regex
30
+ describe ssl(port: 443).ciphers(/rc4/i) do
31
+ it { should_not be_enabled }
32
+ end
33
+ "
34
+
35
+ VERSIONS = [
36
+ 'ssl2',
37
+ 'ssl3',
38
+ 'tls1.0',
39
+ 'tls1.1',
40
+ 'tls1.2',
41
+ ].freeze
42
+
43
+ attr_reader :host, :port, :timeout, :retries
44
+
45
+ def initialize(opts = {})
46
+ @host = opts[:host]
47
+ if @host.nil?
48
+ # Transports like SSH and WinRM will provide a hostname
49
+ if inspec.backend.respond_to?('hostname')
50
+ @host = inspec.backend.hostname
51
+ elsif inspec.backend.class.to_s == 'Train::Transports::Local::Connection'
52
+ @host = 'localhost'
53
+ end
54
+ end
55
+ @port = opts[:port] || 443
56
+ @timeout = opts[:timeout]
57
+ @retries = opts[:retries]
58
+ end
59
+
60
+ filter = FilterTable.create
61
+ filter.add(:enabled?) do |x|
62
+ raise 'Cannot determine host for SSL test. Please specify it or use a different target.' if x.resource.host.nil?
63
+ x.handshake.values.any? { |i| i['success'] }
64
+ end
65
+ filter.add_accessor(:where)
66
+ .add_accessor(:entries)
67
+ .add(:ciphers, field: 'cipher')
68
+ .add(:protocols, field: 'protocol')
69
+ .add(:handshake) { |x|
70
+ groups = x.entries.group_by(&:protocol)
71
+ res = Parallel.map(groups, in_threads: 8) do |proto, e|
72
+ [proto, SSLShake.hello(x.resource.host, port: x.resource.port,
73
+ protocol: proto, ciphers: e.map(&:cipher),
74
+ timeout: x.resource.timeout, retries: x.resource.retries, servername: x.resource.host)]
75
+ end
76
+ Hash[res]
77
+ }
78
+ .connect(self, :scan_config)
79
+
80
+ def to_s
81
+ "SSL/TLS on #{@host}:#{@port}"
82
+ end
83
+
84
+ private
85
+
86
+ def scan_config
87
+ [
88
+ { 'protocol' => 'ssl2', 'ciphers' => SSLShake::SSLv2::CIPHERS.keys },
89
+ { 'protocol' => 'ssl3', 'ciphers' => SSLShake::TLS::SSL3_CIPHERS.keys },
90
+ { 'protocol' => 'tls1.0', 'ciphers' => SSLShake::TLS::TLS10_CIPHERS.keys },
91
+ { 'protocol' => 'tls1.1', 'ciphers' => SSLShake::TLS::TLS10_CIPHERS.keys },
92
+ { 'protocol' => 'tls1.2', 'ciphers' => SSLShake::TLS::TLS_CIPHERS.keys },
93
+ ].map do |line|
94
+ line['ciphers'].map do |cipher|
95
+ { 'protocol' => line['protocol'], 'cipher' => cipher }
96
+ end
97
+ end.flatten
98
+ end
99
+ end
@@ -1,26 +1,26 @@
1
- # encoding: utf-8
2
- module Inspec::Resources
3
- # this resource returns additional system informatio
4
- class System < Inspec.resource(1)
5
- name 'sys_info'
6
-
7
- desc 'Use the user InSpec system resource to test for operating system properties.'
8
- example "
9
- describe sys_info do
10
- its('hostname') { should eq 'example.com' }
11
- end
12
- "
13
-
14
- # returns the hostname of the local system
15
- def hostname
16
- os = inspec.os
17
- if os.linux? || os.darwin?
18
- inspec.command('hostname').stdout.chomp
19
- elsif os.windows?
20
- inspec.powershell('$env:computername').stdout.chomp
21
- else
22
- skip_resource 'The `sys_info.hostname` resource is not supported on your OS yet.'
23
- end
24
- end
25
- end
26
- end
1
+ # encoding: utf-8
2
+ module Inspec::Resources
3
+ # this resource returns additional system informatio
4
+ class System < Inspec.resource(1)
5
+ name 'sys_info'
6
+
7
+ desc 'Use the user InSpec system resource to test for operating system properties.'
8
+ example "
9
+ describe sys_info do
10
+ its('hostname') { should eq 'example.com' }
11
+ end
12
+ "
13
+
14
+ # returns the hostname of the local system
15
+ def hostname
16
+ os = inspec.os
17
+ if os.linux? || os.darwin?
18
+ inspec.command('hostname').stdout.chomp
19
+ elsif os.windows?
20
+ inspec.powershell('$env:computername').stdout.chomp
21
+ else
22
+ skip_resource 'The `sys_info.hostname` resource is not supported on your OS yet.'
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,32 +1,32 @@
1
- # encoding: utf-8
2
- # author: Nolan Davidson
3
-
4
- require 'tomlrb'
5
-
6
- module Inspec::Resources
7
- class TomlConfig < JsonConfig
8
- name 'toml'
9
- desc 'Use the toml InSpec resource to test configuration data in a TOML file'
10
- example "
11
- describe toml('default.toml') do
12
- its('key') { should eq('value') }
13
- its (['arr', 1]) { should eq 2 }
14
- its (['mytable', 'key1']) { should eq 'value1' }
15
- end
16
- "
17
-
18
- def parse(content)
19
- Tomlrb.parse(content)
20
- rescue => e
21
- raise Inspec::Exceptions::ResourceFailed, "Unable to parse TOML: #{e.message}"
22
- end
23
-
24
- private
25
-
26
- # used by JsonConfig to build up a full to_s method
27
- # based on whether a file path, content, or command was supplied.
28
- def resource_base_name
29
- 'TOML'
30
- end
31
- end
32
- end
1
+ # encoding: utf-8
2
+ # author: Nolan Davidson
3
+
4
+ require 'tomlrb'
5
+
6
+ module Inspec::Resources
7
+ class TomlConfig < JsonConfig
8
+ name 'toml'
9
+ desc 'Use the toml InSpec resource to test configuration data in a TOML file'
10
+ example "
11
+ describe toml('default.toml') do
12
+ its('key') { should eq('value') }
13
+ its (['arr', 1]) { should eq 2 }
14
+ its (['mytable', 'key1']) { should eq 'value1' }
15
+ end
16
+ "
17
+
18
+ def parse(content)
19
+ Tomlrb.parse(content)
20
+ rescue => e
21
+ raise Inspec::Exceptions::ResourceFailed, "Unable to parse TOML: #{e.message}"
22
+ end
23
+
24
+ private
25
+
26
+ # used by JsonConfig to build up a full to_s method
27
+ # based on whether a file path, content, or command was supplied.
28
+ def resource_base_name
29
+ 'TOML'
30
+ end
31
+ end
32
+ end
@@ -1,652 +1,652 @@
1
- # encoding: utf-8
2
- # author: Christoph Hartmann
3
- # author: Dominik Richter
4
-
5
- require 'utils/parser'
6
- require 'utils/convert'
7
- require 'utils/filter'
8
-
9
- module Inspec::Resources
10
- # This file contains two resources, the `user` and `users` resource.
11
- # The `user` resource is optimized for requests that verify specific users
12
- # that you know upfront for testing. If you need to query all users or search
13
- # specific users with certain properties, use the `users` resource.
14
- module UserManagementSelector
15
- # select user provider based on the operating system
16
- # returns nil, if no user manager was found for the operating system
17
- def select_user_manager(os)
18
- if os.linux?
19
- LinuxUser.new(inspec)
20
- elsif os.windows?
21
- WindowsUser.new(inspec)
22
- elsif ['darwin'].include?(os[:family])
23
- DarwinUser.new(inspec)
24
- elsif ['freebsd'].include?(os[:family])
25
- FreeBSDUser.new(inspec)
26
- elsif ['aix'].include?(os[:family])
27
- AixUser.new(inspec)
28
- elsif os.solaris?
29
- SolarisUser.new(inspec)
30
- elsif ['hpux'].include?(os[:family])
31
- HpuxUser.new(inspec)
32
- end
33
- end
34
- end
35
-
36
- # The InSpec users resources looksup all local users available on a system.
37
- # TODO: the current version of the users resource will use eg. /etc/passwd
38
- # on Linux to parse all usernames. Therefore the resource may not return
39
- # users managed on other systems like LDAP/ActiveDirectory. Please open
40
- # a feature request at https://github.com/chef/inspec if you need that
41
- # functionality
42
- #
43
- # This resource allows complex filter mechanisms
44
- #
45
- # describe users.where(uid: 0).entries do
46
- # it { should eq ['root'] }
47
- # its('uids') { should eq [1234] }
48
- # its('gids') { should eq [1234] }
49
- # end
50
- #
51
- # describe users.where { uid =~ /S\-1\-5\-21\-\d+\-\d+\-\d+\-500/ } do
52
- # it { should exist }
53
- # end
54
- class Users < Inspec.resource(1)
55
- include UserManagementSelector
56
-
57
- name 'users'
58
- desc 'Use the users InSpec audit resource to test local user profiles. Users can be filtered by groups to which they belong, the frequency of required password changes, the directory paths to home and shell.'
59
- example "
60
- describe users.where { uid == 0 }.entries do
61
- it { should eq ['root'] }
62
- its('uids') { should eq [1234] }
63
- its('gids') { should eq [1234] }
64
- end
65
- "
66
- def initialize
67
- # select user provider
68
- @user_provider = select_user_manager(inspec.os)
69
- return skip_resource 'The `users` resource is not supported on your OS yet.' if @user_provider.nil?
70
- end
71
-
72
- filter = FilterTable.create
73
- filter.add_accessor(:where)
74
- .add_accessor(:entries)
75
- .add(:usernames, field: :username)
76
- .add(:uids, field: :uid)
77
- .add(:gids, field: :gid)
78
- .add(:groupnames, field: :groupname)
79
- .add(:groups, field: :groups)
80
- .add(:homes, field: :home)
81
- .add(:shells, field: :shell)
82
- .add(:mindays, field: :mindays)
83
- .add(:maxdays, field: :maxdays)
84
- .add(:warndays, field: :warndays)
85
- .add(:disabled, field: :disabled)
86
- .add(:exists?) { |x| !x.entries.empty? }
87
- .add(:disabled?) { |x| x.where { disabled == false }.entries.empty? }
88
- .add(:enabled?) { |x| x.where { disabled == true }.entries.empty? }
89
- filter.connect(self, :collect_user_details)
90
-
91
- def to_s
92
- 'Users'
93
- end
94
-
95
- private
96
-
97
- # method to get all available users
98
- def list_users
99
- @username_cache ||= @user_provider.list_users unless @user_provider.nil?
100
- end
101
-
102
- # collects information about every user
103
- def collect_user_details
104
- @users_cache ||= @user_provider.collect_user_details unless @user_provider.nil?
105
- end
106
- end
107
-
108
- # The `user` resource handles the special case where only one resource is required
109
- #
110
- # describe user('root') do
111
- # it { should exist }
112
- # its('uid') { should eq 0 }
113
- # its('gid') { should eq 0 }
114
- # its('group') { should eq 'root' }
115
- # its('groups') { should eq ['root', 'wheel']}
116
- # its('home') { should eq '/root' }
117
- # its('shell') { should eq '/bin/bash' }
118
- # its('mindays') { should eq 0 }
119
- # its('maxdays') { should eq 99 }
120
- # its('warndays') { should eq 5 }
121
- # end
122
- #
123
- # The following Serverspec matchers are deprecated in favor for direct value access
124
- #
125
- # describe user('root') do
126
- # it { should belong_to_group 'root' }
127
- # it { should have_uid 0 }
128
- # it { should have_home_directory '/root' }
129
- # it { should have_login_shell '/bin/bash' }
130
- # its('minimum_days_between_password_change') { should eq 0 }
131
- # its('maximum_days_between_password_change') { should eq 99 }
132
- # end
133
- #
134
- # ServerSpec tests that are not supported:
135
- #
136
- # describe user('root') do
137
- # it { should have_authorized_key 'ssh-rsa ADg54...3434 user@example.local' }
138
- # its(:encrypted_password) { should eq 1234 }
139
- # end
140
- class User < Inspec.resource(1)
141
- include UserManagementSelector
142
- name 'user'
143
- desc 'Use the user InSpec audit resource to test user profiles, including the groups to which they belong, the frequency of required password changes, the directory paths to home and shell.'
144
- example "
145
- describe user('root') do
146
- it { should exist }
147
- its('uid') { should eq 1234 }
148
- its('gid') { should eq 1234 }
149
- end
150
- "
151
- def initialize(username = nil)
152
- @username = username
153
- # select user provider
154
- @user_provider = select_user_manager(inspec.os)
155
- return skip_resource 'The `user` resource is not supported on your OS yet.' if @user_provider.nil?
156
- end
157
-
158
- def exists?
159
- !identity.nil? && !identity[:username].nil?
160
- end
161
-
162
- def disabled?
163
- identity[:disabled] == true unless identity.nil?
164
- end
165
-
166
- def enabled?
167
- identity[:disabled] == false unless identity.nil?
168
- end
169
-
170
- def username
171
- identity[:username] unless identity.nil?
172
- end
173
-
174
- def uid
175
- identity[:uid] unless identity.nil?
176
- end
177
-
178
- def gid
179
- identity[:gid] unless identity.nil?
180
- end
181
-
182
- def groupname
183
- identity[:groupname] unless identity.nil?
184
- end
185
- alias group groupname
186
-
187
- def groups
188
- identity[:groups] unless identity.nil?
189
- end
190
-
191
- def home
192
- meta_info[:home] unless meta_info.nil?
193
- end
194
-
195
- def shell
196
- meta_info[:shell] unless meta_info.nil?
197
- end
198
-
199
- # returns the minimum days between password changes
200
- def mindays
201
- credentials[:mindays] unless credentials.nil?
202
- end
203
-
204
- # returns the maximum days between password changes
205
- def maxdays
206
- credentials[:maxdays] unless credentials.nil?
207
- end
208
-
209
- # returns the days for password change warning
210
- def warndays
211
- credentials[:warndays] unless credentials.nil?
212
- end
213
-
214
- # implement 'mindays' method to be compatible with serverspec
215
- def minimum_days_between_password_change
216
- deprecated('minimum_days_between_password_change', "Please use: its('mindays')")
217
- mindays
218
- end
219
-
220
- # implement 'maxdays' method to be compatible with serverspec
221
- def maximum_days_between_password_change
222
- deprecated('maximum_days_between_password_change', "Please use: its('maxdays')")
223
- maxdays
224
- end
225
-
226
- # implements rspec has matcher, to be compatible with serverspec
227
- # @see: https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/has.rb
228
- def has_uid?(compare_uid)
229
- deprecated('has_uid?')
230
- uid == compare_uid
231
- end
232
-
233
- def has_home_directory?(compare_home)
234
- deprecated('has_home_directory?', "Please use: its('home')")
235
- home == compare_home
236
- end
237
-
238
- def has_login_shell?(compare_shell)
239
- deprecated('has_login_shell?', "Please use: its('shell')")
240
- shell == compare_shell
241
- end
242
-
243
- def has_authorized_key?(_compare_key)
244
- deprecated('has_authorized_key?')
245
- raise NotImplementedError
246
- end
247
-
248
- def deprecated(name, alternative = nil)
249
- warn "[DEPRECATION] #{name} is deprecated. #{alternative}"
250
- end
251
-
252
- def to_s
253
- "User #{@username}"
254
- end
255
-
256
- private
257
-
258
- # returns the iden
259
- def identity
260
- return @id_cache if defined?(@id_cache)
261
- @id_cache = @user_provider.identity(@username) if !@user_provider.nil?
262
- end
263
-
264
- def meta_info
265
- return @meta_cache if defined?(@meta_cache)
266
- @meta_cache = @user_provider.meta_info(@username) if !@user_provider.nil?
267
- end
268
-
269
- def credentials
270
- return @cred_cache if defined?(@cred_cache)
271
- @cred_cache = @user_provider.credentials(@username) if !@user_provider.nil?
272
- end
273
- end
274
-
275
- # This is an abstract class that every user provoider has to implement.
276
- # A user provider implements a system abstracts and helps the InSpec resource
277
- # hand-over system specific behavior to those providers
278
- class UserInfo
279
- include Converter
280
-
281
- attr_reader :inspec
282
- def initialize(inspec)
283
- @inspec = inspec
284
- end
285
-
286
- # returns a hash with user-specific values:
287
- # {
288
- # uid: '',
289
- # user: '',
290
- # gid: '',
291
- # group: '',
292
- # groups: '',
293
- # }
294
- def identity(_username)
295
- raise 'user provider must implement the `identity` method'
296
- end
297
-
298
- # returns optional information about a user, eg shell
299
- def meta_info(_username)
300
- nil
301
- end
302
-
303
- # returns a hash with meta-data about user credentials
304
- # {
305
- # mindays: 1,
306
- # maxdays: 1,
307
- # warndays: 1,
308
- # }
309
- # this method is optional and may not be implemented by each provider
310
- def credentials(_username)
311
- nil
312
- end
313
-
314
- # returns an array with users
315
- def list_users
316
- raise 'user provider must implement the `list_users` method'
317
- end
318
-
319
- # retuns all aspects of the user as one hash
320
- def user_details(username)
321
- item = {}
322
- id = identity(username)
323
- item.merge!(id) unless id.nil?
324
- meta = meta_info(username)
325
- item.merge!(meta) unless meta.nil?
326
- cred = credentials(username)
327
- item.merge!(cred) unless cred.nil?
328
- item
329
- end
330
-
331
- # returns the full information list for a user
332
- def collect_user_details
333
- list_users.map { |username|
334
- user_details(username.chomp)
335
- }
336
- end
337
- end
338
-
339
- # implements generic unix id handling
340
- class UnixUser < UserInfo
341
- attr_reader :inspec, :id_cmd, :list_users_cmd
342
- def initialize(inspec)
343
- @inspec = inspec
344
- @id_cmd ||= 'id'
345
- @list_users_cmd ||= 'cut -d: -f1 /etc/passwd | grep -v "^#"'
346
- super
347
- end
348
-
349
- # returns a list of all local users on a system
350
- def list_users
351
- cmd = inspec.command(list_users_cmd)
352
- return [] if cmd.exit_status != 0
353
- cmd.stdout.chomp.lines
354
- end
355
-
356
- # parse one id entry like '0(wheel)''
357
- def parse_value(line)
358
- SimpleConfig.new(
359
- line,
360
- line_separator: ',',
361
- assignment_regex: /^\s*([^\(]*?)\s*\(\s*(.*?)\)*$/,
362
- group_re: nil,
363
- multiple_values: false,
364
- ).params
365
- end
366
-
367
- # extracts the identity
368
- def identity(username)
369
- cmd = inspec.command("#{id_cmd} #{username}")
370
- return nil if cmd.exit_status != 0
371
-
372
- # parse words
373
- params = SimpleConfig.new(
374
- parse_id_entries(cmd.stdout.chomp),
375
- assignment_regex: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
376
- group_re: nil,
377
- multiple_values: false,
378
- ).params
379
-
380
- {
381
- uid: convert_to_i(parse_value(params['uid']).keys[0]),
382
- username: parse_value(params['uid']).values[0],
383
- gid: convert_to_i(parse_value(params['gid']).keys[0]),
384
- groupname: parse_value(params['gid']).values[0],
385
- groups: parse_value(params['groups']).values,
386
- }
387
- end
388
-
389
- # splits the results of id into seperate lines
390
- def parse_id_entries(raw)
391
- data = []
392
- until (index = raw.index(/\)\s{1}/)).nil?
393
- data.push(raw[0, index+1]) # inclue closing )
394
- raw = raw[index+2, raw.length-index-2]
395
- end
396
- data.push(raw) if !raw.nil?
397
- data.join("\n")
398
- end
399
- end
400
-
401
- class LinuxUser < UnixUser
402
- include PasswdParser
403
- include CommentParser
404
-
405
- def meta_info(username)
406
- cmd = inspec.command("getent passwd #{username}")
407
- return nil if cmd.exit_status != 0
408
- # returns: root:x:0:0:root:/root:/bin/bash
409
- passwd = parse_passwd_line(cmd.stdout.chomp)
410
- {
411
- home: passwd['home'],
412
- shell: passwd['shell'],
413
- }
414
- end
415
-
416
- def credentials(username)
417
- cmd = inspec.command("chage -l #{username}")
418
- return nil if cmd.exit_status != 0
419
-
420
- params = SimpleConfig.new(
421
- cmd.stdout.chomp,
422
- assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
423
- group_re: nil,
424
- multiple_values: false,
425
- ).params
426
-
427
- {
428
- mindays: convert_to_i(params['Minimum number of days between password change']),
429
- maxdays: convert_to_i(params['Maximum number of days between password change']),
430
- warndays: convert_to_i(params['Number of days of warning before password expires']),
431
- }
432
- end
433
- end
434
-
435
- class SolarisUser < LinuxUser
436
- def initialize(inspec)
437
- @inspec = inspec
438
- @id_cmd ||= 'id -a'
439
- super
440
- end
441
- end
442
-
443
- class AixUser < UnixUser
444
- def identity(username)
445
- id = super(username)
446
- return nil if id.nil?
447
- # AIX 'id' command doesn't include the primary group in the supplementary
448
- # yet it can be somewhere in the supplementary list if someone added root
449
- # to a groups list in /etc/group
450
- # we rearrange to expected list if that is the case
451
- if id[:groups].first != id[:group]
452
- id[:groups].reject! { |i| i == id[:group] } if id[:groups].include?(id[:group])
453
- id[:groups].unshift(id[:group])
454
- end
455
-
456
- id
457
- end
458
-
459
- def meta_info(username)
460
- lsuser = inspec.command("lsuser -C -a home shell #{username}")
461
- return nil if lsuser.exit_status != 0
462
-
463
- user = lsuser.stdout.chomp.split("\n").last.split(':')
464
- {
465
- home: user[1],
466
- shell: user[2],
467
- }
468
- end
469
-
470
- def credentials(username)
471
- cmd = inspec.command(
472
- "lssec -c -f /etc/security/user -s #{username} -a minage -a maxage -a pwdwarntime",
473
- )
474
- return nil if cmd.exit_status != 0
475
-
476
- user_sec = cmd.stdout.chomp.split("\n").last.split(':')
477
-
478
- {
479
- mindays: user_sec[1].to_i * 7,
480
- maxdays: user_sec[2].to_i * 7,
481
- warndays: user_sec[3].to_i,
482
- }
483
- end
484
- end
485
-
486
- class HpuxUser < UnixUser
487
- def meta_info(username)
488
- hpuxuser = inspec.command("logins -x -l #{username}")
489
- return nil if hpuxuser.exit_status != 0
490
- user = hpuxuser.stdout.chomp.split(' ')
491
- {
492
- home: user[4],
493
- shell: user[5],
494
- }
495
- end
496
- end
497
-
498
- # we do not use 'finger' for MacOS, because it is harder to parse data with it
499
- # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/fingerd.8.html
500
- # instead we use 'dscl' to request user data
501
- # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
502
- # @see http://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user
503
- class DarwinUser < UnixUser
504
- def initialize(inspec)
505
- @list_users_cmd ||= 'dscl . list /Users'
506
- super
507
- end
508
-
509
- def meta_info(username)
510
- cmd = inspec.command("dscl -q . -read /Users/#{username} NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell")
511
- return nil if cmd.exit_status != 0
512
-
513
- params = SimpleConfig.new(
514
- cmd.stdout.chomp,
515
- assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
516
- group_re: nil,
517
- multiple_values: false,
518
- ).params
519
-
520
- {
521
- home: params['NFSHomeDirectory'],
522
- shell: params['UserShell'],
523
- }
524
- end
525
- end
526
-
527
- # FreeBSD recommends to use the 'pw' command for user management
528
- # @see: https://www.freebsd.org/doc/handbook/users-synopsis.html
529
- # @see: https://www.freebsd.org/cgi/man.cgi?pw(8)
530
- # It offers the following commands:
531
- # - adduser(8) The recommended command-line application for adding new users.
532
- # - rmuser(8) The recommended command-line application for removing users.
533
- # - chpass(1) A flexible tool for changing user database information.
534
- # - passwd(1) The command-line tool to change user passwords.
535
- class FreeBSDUser < UnixUser
536
- include PasswdParser
537
-
538
- def meta_info(username)
539
- cmd = inspec.command("pw usershow #{username} -7")
540
- return nil if cmd.exit_status != 0
541
- # returns: root:*:0:0:Charlie &:/root:/bin/csh
542
- passwd = parse_passwd_line(cmd.stdout.chomp)
543
- {
544
- home: passwd['home'],
545
- shell: passwd['shell'],
546
- }
547
- end
548
- end
549
-
550
- # This optimization was inspired by
551
- # @see https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx
552
- # Alternative solutions are WMI Win32_UserAccount
553
- # @see https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
554
- # @see https://msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx
555
- class WindowsUser < UserInfo
556
- def parse_windows_account(username)
557
- account = username.split('\\')
558
- name = account.pop
559
- domain = account.pop if !account.empty?
560
- [name, domain]
561
- end
562
-
563
- def identity(username)
564
- # TODO: we look for local users only at this point
565
- name, _domain = parse_windows_account(username)
566
- return if collect_user_details.nil?
567
- res = collect_user_details.select { |user| user[:username] == name }
568
- res[0] if !res.empty?
569
- end
570
-
571
- def list_users
572
- collect_user_details.map { |user| user[:username] }
573
- end
574
-
575
- # https://msdn.microsoft.com/en-us/library/aa746340(v=vs.85).aspx
576
- def collect_user_details # rubocop:disable Metrics/MethodLength
577
- return @users_cache if defined?(@users_cache)
578
- script = <<~EOH
579
- Function ConvertTo-SID { Param([byte[]]$BinarySID)
580
- (New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value
581
- }
582
-
583
- Function Convert-UserFlag { Param ($UserFlag)
584
- $List = @()
585
- Switch ($UserFlag) {
586
- ($UserFlag -BOR 0x0001) { $List += 'SCRIPT' }
587
- ($UserFlag -BOR 0x0002) { $List += 'ACCOUNTDISABLE' }
588
- ($UserFlag -BOR 0x0008) { $List += 'HOMEDIR_REQUIRED' }
589
- ($UserFlag -BOR 0x0010) { $List += 'LOCKOUT' }
590
- ($UserFlag -BOR 0x0020) { $List += 'PASSWD_NOTREQD' }
591
- ($UserFlag -BOR 0x0040) { $List += 'PASSWD_CANT_CHANGE' }
592
- ($UserFlag -BOR 0x0080) { $List += 'ENCRYPTED_TEXT_PWD_ALLOWED' }
593
- ($UserFlag -BOR 0x0100) { $List += 'TEMP_DUPLICATE_ACCOUNT' }
594
- ($UserFlag -BOR 0x0200) { $List += 'NORMAL_ACCOUNT' }
595
- ($UserFlag -BOR 0x0800) { $List += 'INTERDOMAIN_TRUST_ACCOUNT' }
596
- ($UserFlag -BOR 0x1000) { $List += 'WORKSTATION_TRUST_ACCOUNT' }
597
- ($UserFlag -BOR 0x2000) { $List += 'SERVER_TRUST_ACCOUNT' }
598
- ($UserFlag -BOR 0x10000) { $List += 'DONT_EXPIRE_PASSWORD' }
599
- ($UserFlag -BOR 0x20000) { $List += 'MNS_LOGON_ACCOUNT' }
600
- ($UserFlag -BOR 0x40000) { $List += 'SMARTCARD_REQUIRED' }
601
- ($UserFlag -BOR 0x80000) { $List += 'TRUSTED_FOR_DELEGATION' }
602
- ($UserFlag -BOR 0x100000) { $List += 'NOT_DELEGATED' }
603
- ($UserFlag -BOR 0x200000) { $List += 'USE_DES_KEY_ONLY' }
604
- ($UserFlag -BOR 0x400000) { $List += 'DONT_REQ_PREAUTH' }
605
- ($UserFlag -BOR 0x800000) { $List += 'PASSWORD_EXPIRED' }
606
- ($UserFlag -BOR 0x1000000) { $List += 'TRUSTED_TO_AUTH_FOR_DELEGATION' }
607
- ($UserFlag -BOR 0x04000000) { $List += 'PARTIAL_SECRETS_ACCOUNT' }
608
- }
609
- $List
610
- }
611
-
612
- $Computername = $Env:Computername
613
- $adsi = [ADSI]"WinNT://$Computername"
614
- $adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
615
- New-Object PSObject -property @{
616
- uid = ConvertTo-SID -BinarySID $_.ObjectSID[0]
617
- username = $_.Name[0]
618
- description = $_.Description[0]
619
- disabled = $_.AccountDisabled[0]
620
- userflags = Convert-UserFlag -UserFlag $_.UserFlags[0]
621
- passwordage = [math]::Round($_.PasswordAge[0]/86400)
622
- minpasswordlength = $_.MinPasswordLength[0]
623
- mindays = [math]::Round($_.MinPasswordAge[0]/86400)
624
- maxdays = [math]::Round($_.MaxPasswordAge[0]/86400)
625
- warndays = $null
626
- badpasswordattempts = $_.BadPasswordAttempts[0]
627
- maxbadpasswords = $_.MaxBadPasswordsAllowed[0]
628
- gid = $null
629
- group = $null
630
- groups = @($_.Groups() | Foreach-Object { $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null) })
631
- home = $_.HomeDirectory[0]
632
- shell = $null
633
- domain = $Computername
634
- }
635
- } | ConvertTo-Json
636
- EOH
637
- cmd = inspec.powershell(script)
638
- # cannot rely on exit code for now, successful command returns exit code 1
639
- # return nil if cmd.exit_status != 0, try to parse json
640
- begin
641
- users = JSON.parse(cmd.stdout)
642
- rescue JSON::ParserError => _e
643
- return nil
644
- end
645
-
646
- # ensure we have an array of groups
647
- users = [users] if !users.is_a?(Array)
648
- # convert keys to symbols
649
- @users_cache = users.map { |user| user.each_with_object({}) { |(k, v), h| h[k.to_sym] = v } }
650
- end
651
- end
652
- end
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+
5
+ require 'utils/parser'
6
+ require 'utils/convert'
7
+ require 'utils/filter'
8
+
9
+ module Inspec::Resources
10
+ # This file contains two resources, the `user` and `users` resource.
11
+ # The `user` resource is optimized for requests that verify specific users
12
+ # that you know upfront for testing. If you need to query all users or search
13
+ # specific users with certain properties, use the `users` resource.
14
+ module UserManagementSelector
15
+ # select user provider based on the operating system
16
+ # returns nil, if no user manager was found for the operating system
17
+ def select_user_manager(os)
18
+ if os.linux?
19
+ LinuxUser.new(inspec)
20
+ elsif os.windows?
21
+ WindowsUser.new(inspec)
22
+ elsif ['darwin'].include?(os[:family])
23
+ DarwinUser.new(inspec)
24
+ elsif ['freebsd'].include?(os[:family])
25
+ FreeBSDUser.new(inspec)
26
+ elsif ['aix'].include?(os[:family])
27
+ AixUser.new(inspec)
28
+ elsif os.solaris?
29
+ SolarisUser.new(inspec)
30
+ elsif ['hpux'].include?(os[:family])
31
+ HpuxUser.new(inspec)
32
+ end
33
+ end
34
+ end
35
+
36
+ # The InSpec users resources looksup all local users available on a system.
37
+ # TODO: the current version of the users resource will use eg. /etc/passwd
38
+ # on Linux to parse all usernames. Therefore the resource may not return
39
+ # users managed on other systems like LDAP/ActiveDirectory. Please open
40
+ # a feature request at https://github.com/chef/inspec if you need that
41
+ # functionality
42
+ #
43
+ # This resource allows complex filter mechanisms
44
+ #
45
+ # describe users.where(uid: 0).entries do
46
+ # it { should eq ['root'] }
47
+ # its('uids') { should eq [1234] }
48
+ # its('gids') { should eq [1234] }
49
+ # end
50
+ #
51
+ # describe users.where { uid =~ /S\-1\-5\-21\-\d+\-\d+\-\d+\-500/ } do
52
+ # it { should exist }
53
+ # end
54
+ class Users < Inspec.resource(1)
55
+ include UserManagementSelector
56
+
57
+ name 'users'
58
+ desc 'Use the users InSpec audit resource to test local user profiles. Users can be filtered by groups to which they belong, the frequency of required password changes, the directory paths to home and shell.'
59
+ example "
60
+ describe users.where { uid == 0 }.entries do
61
+ it { should eq ['root'] }
62
+ its('uids') { should eq [1234] }
63
+ its('gids') { should eq [1234] }
64
+ end
65
+ "
66
+ def initialize
67
+ # select user provider
68
+ @user_provider = select_user_manager(inspec.os)
69
+ return skip_resource 'The `users` resource is not supported on your OS yet.' if @user_provider.nil?
70
+ end
71
+
72
+ filter = FilterTable.create
73
+ filter.add_accessor(:where)
74
+ .add_accessor(:entries)
75
+ .add(:usernames, field: :username)
76
+ .add(:uids, field: :uid)
77
+ .add(:gids, field: :gid)
78
+ .add(:groupnames, field: :groupname)
79
+ .add(:groups, field: :groups)
80
+ .add(:homes, field: :home)
81
+ .add(:shells, field: :shell)
82
+ .add(:mindays, field: :mindays)
83
+ .add(:maxdays, field: :maxdays)
84
+ .add(:warndays, field: :warndays)
85
+ .add(:disabled, field: :disabled)
86
+ .add(:exists?) { |x| !x.entries.empty? }
87
+ .add(:disabled?) { |x| x.where { disabled == false }.entries.empty? }
88
+ .add(:enabled?) { |x| x.where { disabled == true }.entries.empty? }
89
+ filter.connect(self, :collect_user_details)
90
+
91
+ def to_s
92
+ 'Users'
93
+ end
94
+
95
+ private
96
+
97
+ # method to get all available users
98
+ def list_users
99
+ @username_cache ||= @user_provider.list_users unless @user_provider.nil?
100
+ end
101
+
102
+ # collects information about every user
103
+ def collect_user_details
104
+ @users_cache ||= @user_provider.collect_user_details unless @user_provider.nil?
105
+ end
106
+ end
107
+
108
+ # The `user` resource handles the special case where only one resource is required
109
+ #
110
+ # describe user('root') do
111
+ # it { should exist }
112
+ # its('uid') { should eq 0 }
113
+ # its('gid') { should eq 0 }
114
+ # its('group') { should eq 'root' }
115
+ # its('groups') { should eq ['root', 'wheel']}
116
+ # its('home') { should eq '/root' }
117
+ # its('shell') { should eq '/bin/bash' }
118
+ # its('mindays') { should eq 0 }
119
+ # its('maxdays') { should eq 99 }
120
+ # its('warndays') { should eq 5 }
121
+ # end
122
+ #
123
+ # The following Serverspec matchers are deprecated in favor for direct value access
124
+ #
125
+ # describe user('root') do
126
+ # it { should belong_to_group 'root' }
127
+ # it { should have_uid 0 }
128
+ # it { should have_home_directory '/root' }
129
+ # it { should have_login_shell '/bin/bash' }
130
+ # its('minimum_days_between_password_change') { should eq 0 }
131
+ # its('maximum_days_between_password_change') { should eq 99 }
132
+ # end
133
+ #
134
+ # ServerSpec tests that are not supported:
135
+ #
136
+ # describe user('root') do
137
+ # it { should have_authorized_key 'ssh-rsa ADg54...3434 user@example.local' }
138
+ # its(:encrypted_password) { should eq 1234 }
139
+ # end
140
+ class User < Inspec.resource(1)
141
+ include UserManagementSelector
142
+ name 'user'
143
+ desc 'Use the user InSpec audit resource to test user profiles, including the groups to which they belong, the frequency of required password changes, the directory paths to home and shell.'
144
+ example "
145
+ describe user('root') do
146
+ it { should exist }
147
+ its('uid') { should eq 1234 }
148
+ its('gid') { should eq 1234 }
149
+ end
150
+ "
151
+ def initialize(username = nil)
152
+ @username = username
153
+ # select user provider
154
+ @user_provider = select_user_manager(inspec.os)
155
+ return skip_resource 'The `user` resource is not supported on your OS yet.' if @user_provider.nil?
156
+ end
157
+
158
+ def exists?
159
+ !identity.nil? && !identity[:username].nil?
160
+ end
161
+
162
+ def disabled?
163
+ identity[:disabled] == true unless identity.nil?
164
+ end
165
+
166
+ def enabled?
167
+ identity[:disabled] == false unless identity.nil?
168
+ end
169
+
170
+ def username
171
+ identity[:username] unless identity.nil?
172
+ end
173
+
174
+ def uid
175
+ identity[:uid] unless identity.nil?
176
+ end
177
+
178
+ def gid
179
+ identity[:gid] unless identity.nil?
180
+ end
181
+
182
+ def groupname
183
+ identity[:groupname] unless identity.nil?
184
+ end
185
+ alias group groupname
186
+
187
+ def groups
188
+ identity[:groups] unless identity.nil?
189
+ end
190
+
191
+ def home
192
+ meta_info[:home] unless meta_info.nil?
193
+ end
194
+
195
+ def shell
196
+ meta_info[:shell] unless meta_info.nil?
197
+ end
198
+
199
+ # returns the minimum days between password changes
200
+ def mindays
201
+ credentials[:mindays] unless credentials.nil?
202
+ end
203
+
204
+ # returns the maximum days between password changes
205
+ def maxdays
206
+ credentials[:maxdays] unless credentials.nil?
207
+ end
208
+
209
+ # returns the days for password change warning
210
+ def warndays
211
+ credentials[:warndays] unless credentials.nil?
212
+ end
213
+
214
+ # implement 'mindays' method to be compatible with serverspec
215
+ def minimum_days_between_password_change
216
+ deprecated('minimum_days_between_password_change', "Please use: its('mindays')")
217
+ mindays
218
+ end
219
+
220
+ # implement 'maxdays' method to be compatible with serverspec
221
+ def maximum_days_between_password_change
222
+ deprecated('maximum_days_between_password_change', "Please use: its('maxdays')")
223
+ maxdays
224
+ end
225
+
226
+ # implements rspec has matcher, to be compatible with serverspec
227
+ # @see: https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/has.rb
228
+ def has_uid?(compare_uid)
229
+ deprecated('has_uid?')
230
+ uid == compare_uid
231
+ end
232
+
233
+ def has_home_directory?(compare_home)
234
+ deprecated('has_home_directory?', "Please use: its('home')")
235
+ home == compare_home
236
+ end
237
+
238
+ def has_login_shell?(compare_shell)
239
+ deprecated('has_login_shell?', "Please use: its('shell')")
240
+ shell == compare_shell
241
+ end
242
+
243
+ def has_authorized_key?(_compare_key)
244
+ deprecated('has_authorized_key?')
245
+ raise NotImplementedError
246
+ end
247
+
248
+ def deprecated(name, alternative = nil)
249
+ warn "[DEPRECATION] #{name} is deprecated. #{alternative}"
250
+ end
251
+
252
+ def to_s
253
+ "User #{@username}"
254
+ end
255
+
256
+ private
257
+
258
+ # returns the iden
259
+ def identity
260
+ return @id_cache if defined?(@id_cache)
261
+ @id_cache = @user_provider.identity(@username) if !@user_provider.nil?
262
+ end
263
+
264
+ def meta_info
265
+ return @meta_cache if defined?(@meta_cache)
266
+ @meta_cache = @user_provider.meta_info(@username) if !@user_provider.nil?
267
+ end
268
+
269
+ def credentials
270
+ return @cred_cache if defined?(@cred_cache)
271
+ @cred_cache = @user_provider.credentials(@username) if !@user_provider.nil?
272
+ end
273
+ end
274
+
275
+ # This is an abstract class that every user provoider has to implement.
276
+ # A user provider implements a system abstracts and helps the InSpec resource
277
+ # hand-over system specific behavior to those providers
278
+ class UserInfo
279
+ include Converter
280
+
281
+ attr_reader :inspec
282
+ def initialize(inspec)
283
+ @inspec = inspec
284
+ end
285
+
286
+ # returns a hash with user-specific values:
287
+ # {
288
+ # uid: '',
289
+ # user: '',
290
+ # gid: '',
291
+ # group: '',
292
+ # groups: '',
293
+ # }
294
+ def identity(_username)
295
+ raise 'user provider must implement the `identity` method'
296
+ end
297
+
298
+ # returns optional information about a user, eg shell
299
+ def meta_info(_username)
300
+ nil
301
+ end
302
+
303
+ # returns a hash with meta-data about user credentials
304
+ # {
305
+ # mindays: 1,
306
+ # maxdays: 1,
307
+ # warndays: 1,
308
+ # }
309
+ # this method is optional and may not be implemented by each provider
310
+ def credentials(_username)
311
+ nil
312
+ end
313
+
314
+ # returns an array with users
315
+ def list_users
316
+ raise 'user provider must implement the `list_users` method'
317
+ end
318
+
319
+ # retuns all aspects of the user as one hash
320
+ def user_details(username)
321
+ item = {}
322
+ id = identity(username)
323
+ item.merge!(id) unless id.nil?
324
+ meta = meta_info(username)
325
+ item.merge!(meta) unless meta.nil?
326
+ cred = credentials(username)
327
+ item.merge!(cred) unless cred.nil?
328
+ item
329
+ end
330
+
331
+ # returns the full information list for a user
332
+ def collect_user_details
333
+ list_users.map { |username|
334
+ user_details(username.chomp)
335
+ }
336
+ end
337
+ end
338
+
339
+ # implements generic unix id handling
340
+ class UnixUser < UserInfo
341
+ attr_reader :inspec, :id_cmd, :list_users_cmd
342
+ def initialize(inspec)
343
+ @inspec = inspec
344
+ @id_cmd ||= 'id'
345
+ @list_users_cmd ||= 'cut -d: -f1 /etc/passwd | grep -v "^#"'
346
+ super
347
+ end
348
+
349
+ # returns a list of all local users on a system
350
+ def list_users
351
+ cmd = inspec.command(list_users_cmd)
352
+ return [] if cmd.exit_status != 0
353
+ cmd.stdout.chomp.lines
354
+ end
355
+
356
+ # parse one id entry like '0(wheel)''
357
+ def parse_value(line)
358
+ SimpleConfig.new(
359
+ line,
360
+ line_separator: ',',
361
+ assignment_regex: /^\s*([^\(]*?)\s*\(\s*(.*?)\)*$/,
362
+ group_re: nil,
363
+ multiple_values: false,
364
+ ).params
365
+ end
366
+
367
+ # extracts the identity
368
+ def identity(username)
369
+ cmd = inspec.command("#{id_cmd} #{username}")
370
+ return nil if cmd.exit_status != 0
371
+
372
+ # parse words
373
+ params = SimpleConfig.new(
374
+ parse_id_entries(cmd.stdout.chomp),
375
+ assignment_regex: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
376
+ group_re: nil,
377
+ multiple_values: false,
378
+ ).params
379
+
380
+ {
381
+ uid: convert_to_i(parse_value(params['uid']).keys[0]),
382
+ username: parse_value(params['uid']).values[0],
383
+ gid: convert_to_i(parse_value(params['gid']).keys[0]),
384
+ groupname: parse_value(params['gid']).values[0],
385
+ groups: parse_value(params['groups']).values,
386
+ }
387
+ end
388
+
389
+ # splits the results of id into seperate lines
390
+ def parse_id_entries(raw)
391
+ data = []
392
+ until (index = raw.index(/\)\s{1}/)).nil?
393
+ data.push(raw[0, index+1]) # inclue closing )
394
+ raw = raw[index+2, raw.length-index-2]
395
+ end
396
+ data.push(raw) if !raw.nil?
397
+ data.join("\n")
398
+ end
399
+ end
400
+
401
+ class LinuxUser < UnixUser
402
+ include PasswdParser
403
+ include CommentParser
404
+
405
+ def meta_info(username)
406
+ cmd = inspec.command("getent passwd #{username}")
407
+ return nil if cmd.exit_status != 0
408
+ # returns: root:x:0:0:root:/root:/bin/bash
409
+ passwd = parse_passwd_line(cmd.stdout.chomp)
410
+ {
411
+ home: passwd['home'],
412
+ shell: passwd['shell'],
413
+ }
414
+ end
415
+
416
+ def credentials(username)
417
+ cmd = inspec.command("chage -l #{username}")
418
+ return nil if cmd.exit_status != 0
419
+
420
+ params = SimpleConfig.new(
421
+ cmd.stdout.chomp,
422
+ assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
423
+ group_re: nil,
424
+ multiple_values: false,
425
+ ).params
426
+
427
+ {
428
+ mindays: convert_to_i(params['Minimum number of days between password change']),
429
+ maxdays: convert_to_i(params['Maximum number of days between password change']),
430
+ warndays: convert_to_i(params['Number of days of warning before password expires']),
431
+ }
432
+ end
433
+ end
434
+
435
+ class SolarisUser < LinuxUser
436
+ def initialize(inspec)
437
+ @inspec = inspec
438
+ @id_cmd ||= 'id -a'
439
+ super
440
+ end
441
+ end
442
+
443
+ class AixUser < UnixUser
444
+ def identity(username)
445
+ id = super(username)
446
+ return nil if id.nil?
447
+ # AIX 'id' command doesn't include the primary group in the supplementary
448
+ # yet it can be somewhere in the supplementary list if someone added root
449
+ # to a groups list in /etc/group
450
+ # we rearrange to expected list if that is the case
451
+ if id[:groups].first != id[:group]
452
+ id[:groups].reject! { |i| i == id[:group] } if id[:groups].include?(id[:group])
453
+ id[:groups].unshift(id[:group])
454
+ end
455
+
456
+ id
457
+ end
458
+
459
+ def meta_info(username)
460
+ lsuser = inspec.command("lsuser -C -a home shell #{username}")
461
+ return nil if lsuser.exit_status != 0
462
+
463
+ user = lsuser.stdout.chomp.split("\n").last.split(':')
464
+ {
465
+ home: user[1],
466
+ shell: user[2],
467
+ }
468
+ end
469
+
470
+ def credentials(username)
471
+ cmd = inspec.command(
472
+ "lssec -c -f /etc/security/user -s #{username} -a minage -a maxage -a pwdwarntime",
473
+ )
474
+ return nil if cmd.exit_status != 0
475
+
476
+ user_sec = cmd.stdout.chomp.split("\n").last.split(':')
477
+
478
+ {
479
+ mindays: user_sec[1].to_i * 7,
480
+ maxdays: user_sec[2].to_i * 7,
481
+ warndays: user_sec[3].to_i,
482
+ }
483
+ end
484
+ end
485
+
486
+ class HpuxUser < UnixUser
487
+ def meta_info(username)
488
+ hpuxuser = inspec.command("logins -x -l #{username}")
489
+ return nil if hpuxuser.exit_status != 0
490
+ user = hpuxuser.stdout.chomp.split(' ')
491
+ {
492
+ home: user[4],
493
+ shell: user[5],
494
+ }
495
+ end
496
+ end
497
+
498
+ # we do not use 'finger' for MacOS, because it is harder to parse data with it
499
+ # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/fingerd.8.html
500
+ # instead we use 'dscl' to request user data
501
+ # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
502
+ # @see http://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user
503
+ class DarwinUser < UnixUser
504
+ def initialize(inspec)
505
+ @list_users_cmd ||= 'dscl . list /Users'
506
+ super
507
+ end
508
+
509
+ def meta_info(username)
510
+ cmd = inspec.command("dscl -q . -read /Users/#{username} NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell")
511
+ return nil if cmd.exit_status != 0
512
+
513
+ params = SimpleConfig.new(
514
+ cmd.stdout.chomp,
515
+ assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
516
+ group_re: nil,
517
+ multiple_values: false,
518
+ ).params
519
+
520
+ {
521
+ home: params['NFSHomeDirectory'],
522
+ shell: params['UserShell'],
523
+ }
524
+ end
525
+ end
526
+
527
+ # FreeBSD recommends to use the 'pw' command for user management
528
+ # @see: https://www.freebsd.org/doc/handbook/users-synopsis.html
529
+ # @see: https://www.freebsd.org/cgi/man.cgi?pw(8)
530
+ # It offers the following commands:
531
+ # - adduser(8) The recommended command-line application for adding new users.
532
+ # - rmuser(8) The recommended command-line application for removing users.
533
+ # - chpass(1) A flexible tool for changing user database information.
534
+ # - passwd(1) The command-line tool to change user passwords.
535
+ class FreeBSDUser < UnixUser
536
+ include PasswdParser
537
+
538
+ def meta_info(username)
539
+ cmd = inspec.command("pw usershow #{username} -7")
540
+ return nil if cmd.exit_status != 0
541
+ # returns: root:*:0:0:Charlie &:/root:/bin/csh
542
+ passwd = parse_passwd_line(cmd.stdout.chomp)
543
+ {
544
+ home: passwd['home'],
545
+ shell: passwd['shell'],
546
+ }
547
+ end
548
+ end
549
+
550
+ # This optimization was inspired by
551
+ # @see https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx
552
+ # Alternative solutions are WMI Win32_UserAccount
553
+ # @see https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
554
+ # @see https://msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx
555
+ class WindowsUser < UserInfo
556
+ def parse_windows_account(username)
557
+ account = username.split('\\')
558
+ name = account.pop
559
+ domain = account.pop if !account.empty?
560
+ [name, domain]
561
+ end
562
+
563
+ def identity(username)
564
+ # TODO: we look for local users only at this point
565
+ name, _domain = parse_windows_account(username)
566
+ return if collect_user_details.nil?
567
+ res = collect_user_details.select { |user| user[:username] == name }
568
+ res[0] if !res.empty?
569
+ end
570
+
571
+ def list_users
572
+ collect_user_details.map { |user| user[:username] }
573
+ end
574
+
575
+ # https://msdn.microsoft.com/en-us/library/aa746340(v=vs.85).aspx
576
+ def collect_user_details # rubocop:disable Metrics/MethodLength
577
+ return @users_cache if defined?(@users_cache)
578
+ script = <<~EOH
579
+ Function ConvertTo-SID { Param([byte[]]$BinarySID)
580
+ (New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value
581
+ }
582
+
583
+ Function Convert-UserFlag { Param ($UserFlag)
584
+ $List = @()
585
+ Switch ($UserFlag) {
586
+ ($UserFlag -BOR 0x0001) { $List += 'SCRIPT' }
587
+ ($UserFlag -BOR 0x0002) { $List += 'ACCOUNTDISABLE' }
588
+ ($UserFlag -BOR 0x0008) { $List += 'HOMEDIR_REQUIRED' }
589
+ ($UserFlag -BOR 0x0010) { $List += 'LOCKOUT' }
590
+ ($UserFlag -BOR 0x0020) { $List += 'PASSWD_NOTREQD' }
591
+ ($UserFlag -BOR 0x0040) { $List += 'PASSWD_CANT_CHANGE' }
592
+ ($UserFlag -BOR 0x0080) { $List += 'ENCRYPTED_TEXT_PWD_ALLOWED' }
593
+ ($UserFlag -BOR 0x0100) { $List += 'TEMP_DUPLICATE_ACCOUNT' }
594
+ ($UserFlag -BOR 0x0200) { $List += 'NORMAL_ACCOUNT' }
595
+ ($UserFlag -BOR 0x0800) { $List += 'INTERDOMAIN_TRUST_ACCOUNT' }
596
+ ($UserFlag -BOR 0x1000) { $List += 'WORKSTATION_TRUST_ACCOUNT' }
597
+ ($UserFlag -BOR 0x2000) { $List += 'SERVER_TRUST_ACCOUNT' }
598
+ ($UserFlag -BOR 0x10000) { $List += 'DONT_EXPIRE_PASSWORD' }
599
+ ($UserFlag -BOR 0x20000) { $List += 'MNS_LOGON_ACCOUNT' }
600
+ ($UserFlag -BOR 0x40000) { $List += 'SMARTCARD_REQUIRED' }
601
+ ($UserFlag -BOR 0x80000) { $List += 'TRUSTED_FOR_DELEGATION' }
602
+ ($UserFlag -BOR 0x100000) { $List += 'NOT_DELEGATED' }
603
+ ($UserFlag -BOR 0x200000) { $List += 'USE_DES_KEY_ONLY' }
604
+ ($UserFlag -BOR 0x400000) { $List += 'DONT_REQ_PREAUTH' }
605
+ ($UserFlag -BOR 0x800000) { $List += 'PASSWORD_EXPIRED' }
606
+ ($UserFlag -BOR 0x1000000) { $List += 'TRUSTED_TO_AUTH_FOR_DELEGATION' }
607
+ ($UserFlag -BOR 0x04000000) { $List += 'PARTIAL_SECRETS_ACCOUNT' }
608
+ }
609
+ $List
610
+ }
611
+
612
+ $Computername = $Env:Computername
613
+ $adsi = [ADSI]"WinNT://$Computername"
614
+ $adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
615
+ New-Object PSObject -property @{
616
+ uid = ConvertTo-SID -BinarySID $_.ObjectSID[0]
617
+ username = $_.Name[0]
618
+ description = $_.Description[0]
619
+ disabled = $_.AccountDisabled[0]
620
+ userflags = Convert-UserFlag -UserFlag $_.UserFlags[0]
621
+ passwordage = [math]::Round($_.PasswordAge[0]/86400)
622
+ minpasswordlength = $_.MinPasswordLength[0]
623
+ mindays = [math]::Round($_.MinPasswordAge[0]/86400)
624
+ maxdays = [math]::Round($_.MaxPasswordAge[0]/86400)
625
+ warndays = $null
626
+ badpasswordattempts = $_.BadPasswordAttempts[0]
627
+ maxbadpasswords = $_.MaxBadPasswordsAllowed[0]
628
+ gid = $null
629
+ group = $null
630
+ groups = @($_.Groups() | Foreach-Object { $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null) })
631
+ home = $_.HomeDirectory[0]
632
+ shell = $null
633
+ domain = $Computername
634
+ }
635
+ } | ConvertTo-Json
636
+ EOH
637
+ cmd = inspec.powershell(script)
638
+ # cannot rely on exit code for now, successful command returns exit code 1
639
+ # return nil if cmd.exit_status != 0, try to parse json
640
+ begin
641
+ users = JSON.parse(cmd.stdout)
642
+ rescue JSON::ParserError => _e
643
+ return nil
644
+ end
645
+
646
+ # ensure we have an array of groups
647
+ users = [users] if !users.is_a?(Array)
648
+ # convert keys to symbols
649
+ @users_cache = users.map { |user| user.each_with_object({}) { |(k, v), h| h[k.to_sym] = v } }
650
+ end
651
+ end
652
+ end