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
data/lib/resources/ssl.rb CHANGED
@@ -1,99 +1,99 @@
1
- # encoding: utf-8
2
- # copyright: 2015, Chef Software Inc.
3
-
4
- require 'sslshake'
5
- require 'utils/filter'
6
- require 'uri'
7
- require 'parallel'
8
-
9
- # Custom resource based on the InSpec resource DSL
10
- class SSL < Inspec.resource(1)
11
- name 'ssl'
12
- supports platform: 'unix'
13
- supports platform: 'windows'
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
+
4
+ require 'sslshake'
5
+ require 'utils/filter'
6
+ require 'uri'
7
+ require 'parallel'
8
+
9
+ # Custom resource based on the InSpec resource DSL
10
+ class SSL < Inspec.resource(1)
11
+ name 'ssl'
12
+ supports platform: 'unix'
13
+ supports platform: 'windows'
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,28 +1,28 @@
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
- supports platform: 'unix'
7
- supports platform: 'windows'
8
-
9
- desc 'Use the user InSpec system resource to test for operating system properties.'
10
- example "
11
- describe sys_info do
12
- its('hostname') { should eq 'example.com' }
13
- end
14
- "
15
-
16
- # returns the hostname of the local system
17
- def hostname
18
- os = inspec.os
19
- if os.linux? || os.darwin?
20
- inspec.command('hostname').stdout.chomp
21
- elsif os.windows?
22
- inspec.powershell('$env:computername').stdout.chomp
23
- else
24
- skip_resource 'The `sys_info.hostname` resource is not supported on your OS yet.'
25
- end
26
- end
27
- end
28
- 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
+ supports platform: 'unix'
7
+ supports platform: 'windows'
8
+
9
+ desc 'Use the user InSpec system resource to test for operating system properties.'
10
+ example "
11
+ describe sys_info do
12
+ its('hostname') { should eq 'example.com' }
13
+ end
14
+ "
15
+
16
+ # returns the hostname of the local system
17
+ def hostname
18
+ os = inspec.os
19
+ if os.linux? || os.darwin?
20
+ inspec.command('hostname').stdout.chomp
21
+ elsif os.windows?
22
+ inspec.powershell('$env:computername').stdout.chomp
23
+ else
24
+ skip_resource 'The `sys_info.hostname` resource is not supported on your OS yet.'
25
+ end
26
+ end
27
+ end
28
+ 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,654 +1,654 @@
1
- # encoding: utf-8
2
-
3
- require 'utils/parser'
4
- require 'utils/convert'
5
- require 'utils/filter'
6
-
7
- module Inspec::Resources
8
- # This file contains two resources, the `user` and `users` resource.
9
- # The `user` resource is optimized for requests that verify specific users
10
- # that you know upfront for testing. If you need to query all users or search
11
- # specific users with certain properties, use the `users` resource.
12
- module UserManagementSelector
13
- # select user provider based on the operating system
14
- # returns nil, if no user manager was found for the operating system
15
- def select_user_manager(os)
16
- if os.linux?
17
- LinuxUser.new(inspec)
18
- elsif os.windows?
19
- WindowsUser.new(inspec)
20
- elsif ['darwin'].include?(os[:family])
21
- DarwinUser.new(inspec)
22
- elsif ['freebsd'].include?(os[:family])
23
- FreeBSDUser.new(inspec)
24
- elsif ['aix'].include?(os[:family])
25
- AixUser.new(inspec)
26
- elsif os.solaris?
27
- SolarisUser.new(inspec)
28
- elsif ['hpux'].include?(os[:family])
29
- HpuxUser.new(inspec)
30
- end
31
- end
32
- end
33
-
34
- # The InSpec users resources looksup all local users available on a system.
35
- # TODO: the current version of the users resource will use eg. /etc/passwd
36
- # on Linux to parse all usernames. Therefore the resource may not return
37
- # users managed on other systems like LDAP/ActiveDirectory. Please open
38
- # a feature request at https://github.com/chef/inspec if you need that
39
- # functionality
40
- #
41
- # This resource allows complex filter mechanisms
42
- #
43
- # describe users.where(uid: 0).entries do
44
- # it { should eq ['root'] }
45
- # its('uids') { should eq [1234] }
46
- # its('gids') { should eq [1234] }
47
- # end
48
- #
49
- # describe users.where { uid =~ /S\-1\-5\-21\-\d+\-\d+\-\d+\-500/ } do
50
- # it { should exist }
51
- # end
52
- class Users < Inspec.resource(1)
53
- include UserManagementSelector
54
-
55
- name 'users'
56
- supports platform: 'unix'
57
- supports platform: 'windows'
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
- supports platform: 'unix'
144
- supports platform: 'windows'
145
- 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.'
146
- example "
147
- describe user('root') do
148
- it { should exist }
149
- its('uid') { should eq 1234 }
150
- its('gid') { should eq 1234 }
151
- end
152
- "
153
- def initialize(username = nil)
154
- @username = username
155
- # select user provider
156
- @user_provider = select_user_manager(inspec.os)
157
- return skip_resource 'The `user` resource is not supported on your OS yet.' if @user_provider.nil?
158
- end
159
-
160
- def exists?
161
- !identity.nil? && !identity[:username].nil?
162
- end
163
-
164
- def disabled?
165
- identity[:disabled] == true unless identity.nil?
166
- end
167
-
168
- def enabled?
169
- identity[:disabled] == false unless identity.nil?
170
- end
171
-
172
- def username
173
- identity[:username] unless identity.nil?
174
- end
175
-
176
- def uid
177
- identity[:uid] unless identity.nil?
178
- end
179
-
180
- def gid
181
- identity[:gid] unless identity.nil?
182
- end
183
-
184
- def groupname
185
- identity[:groupname] unless identity.nil?
186
- end
187
- alias group groupname
188
-
189
- def groups
190
- identity[:groups] unless identity.nil?
191
- end
192
-
193
- def home
194
- meta_info[:home] unless meta_info.nil?
195
- end
196
-
197
- def shell
198
- meta_info[:shell] unless meta_info.nil?
199
- end
200
-
201
- # returns the minimum days between password changes
202
- def mindays
203
- credentials[:mindays] unless credentials.nil?
204
- end
205
-
206
- # returns the maximum days between password changes
207
- def maxdays
208
- credentials[:maxdays] unless credentials.nil?
209
- end
210
-
211
- # returns the days for password change warning
212
- def warndays
213
- credentials[:warndays] unless credentials.nil?
214
- end
215
-
216
- # implement 'mindays' method to be compatible with serverspec
217
- def minimum_days_between_password_change
218
- deprecated('minimum_days_between_password_change', "Please use: its('mindays')")
219
- mindays
220
- end
221
-
222
- # implement 'maxdays' method to be compatible with serverspec
223
- def maximum_days_between_password_change
224
- deprecated('maximum_days_between_password_change', "Please use: its('maxdays')")
225
- maxdays
226
- end
227
-
228
- # implements rspec has matcher, to be compatible with serverspec
229
- # @see: https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/has.rb
230
- def has_uid?(compare_uid)
231
- deprecated('has_uid?')
232
- uid == compare_uid
233
- end
234
-
235
- def has_home_directory?(compare_home)
236
- deprecated('has_home_directory?', "Please use: its('home')")
237
- home == compare_home
238
- end
239
-
240
- def has_login_shell?(compare_shell)
241
- deprecated('has_login_shell?', "Please use: its('shell')")
242
- shell == compare_shell
243
- end
244
-
245
- def has_authorized_key?(_compare_key)
246
- deprecated('has_authorized_key?')
247
- raise NotImplementedError
248
- end
249
-
250
- def deprecated(name, alternative = nil)
251
- warn "[DEPRECATION] #{name} is deprecated. #{alternative}"
252
- end
253
-
254
- def to_s
255
- "User #{@username}"
256
- end
257
-
258
- private
259
-
260
- # returns the iden
261
- def identity
262
- return @id_cache if defined?(@id_cache)
263
- @id_cache = @user_provider.identity(@username) if !@user_provider.nil?
264
- end
265
-
266
- def meta_info
267
- return @meta_cache if defined?(@meta_cache)
268
- @meta_cache = @user_provider.meta_info(@username) if !@user_provider.nil?
269
- end
270
-
271
- def credentials
272
- return @cred_cache if defined?(@cred_cache)
273
- @cred_cache = @user_provider.credentials(@username) if !@user_provider.nil?
274
- end
275
- end
276
-
277
- # This is an abstract class that every user provoider has to implement.
278
- # A user provider implements a system abstracts and helps the InSpec resource
279
- # hand-over system specific behavior to those providers
280
- class UserInfo
281
- include Converter
282
-
283
- attr_reader :inspec
284
- def initialize(inspec)
285
- @inspec = inspec
286
- end
287
-
288
- # returns a hash with user-specific values:
289
- # {
290
- # uid: '',
291
- # user: '',
292
- # gid: '',
293
- # group: '',
294
- # groups: '',
295
- # }
296
- def identity(_username)
297
- raise 'user provider must implement the `identity` method'
298
- end
299
-
300
- # returns optional information about a user, eg shell
301
- def meta_info(_username)
302
- nil
303
- end
304
-
305
- # returns a hash with meta-data about user credentials
306
- # {
307
- # mindays: 1,
308
- # maxdays: 1,
309
- # warndays: 1,
310
- # }
311
- # this method is optional and may not be implemented by each provider
312
- def credentials(_username)
313
- nil
314
- end
315
-
316
- # returns an array with users
317
- def list_users
318
- raise 'user provider must implement the `list_users` method'
319
- end
320
-
321
- # retuns all aspects of the user as one hash
322
- def user_details(username)
323
- item = {}
324
- id = identity(username)
325
- item.merge!(id) unless id.nil?
326
- meta = meta_info(username)
327
- item.merge!(meta) unless meta.nil?
328
- cred = credentials(username)
329
- item.merge!(cred) unless cred.nil?
330
- item
331
- end
332
-
333
- # returns the full information list for a user
334
- def collect_user_details
335
- list_users.map { |username|
336
- user_details(username.chomp)
337
- }
338
- end
339
- end
340
-
341
- # implements generic unix id handling
342
- class UnixUser < UserInfo
343
- attr_reader :inspec, :id_cmd, :list_users_cmd
344
- def initialize(inspec)
345
- @inspec = inspec
346
- @id_cmd ||= 'id'
347
- @list_users_cmd ||= 'cut -d: -f1 /etc/passwd | grep -v "^#"'
348
- super
349
- end
350
-
351
- # returns a list of all local users on a system
352
- def list_users
353
- cmd = inspec.command(list_users_cmd)
354
- return [] if cmd.exit_status != 0
355
- cmd.stdout.chomp.lines
356
- end
357
-
358
- # parse one id entry like '0(wheel)''
359
- def parse_value(line)
360
- SimpleConfig.new(
361
- line,
362
- line_separator: ',',
363
- assignment_regex: /^\s*([^\(]*?)\s*\(\s*(.*?)\)*$/,
364
- group_re: nil,
365
- multiple_values: false,
366
- ).params
367
- end
368
-
369
- # extracts the identity
370
- def identity(username)
371
- cmd = inspec.command("#{id_cmd} #{username}")
372
- return nil if cmd.exit_status != 0
373
-
374
- # parse words
375
- params = SimpleConfig.new(
376
- parse_id_entries(cmd.stdout.chomp),
377
- assignment_regex: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
378
- group_re: nil,
379
- multiple_values: false,
380
- ).params
381
-
382
- {
383
- uid: convert_to_i(parse_value(params['uid']).keys[0]),
384
- username: parse_value(params['uid']).values[0],
385
- gid: convert_to_i(parse_value(params['gid']).keys[0]),
386
- groupname: parse_value(params['gid']).values[0],
387
- groups: parse_value(params['groups']).values,
388
- }
389
- end
390
-
391
- # splits the results of id into seperate lines
392
- def parse_id_entries(raw)
393
- data = []
394
- until (index = raw.index(/\)\s{1}/)).nil?
395
- data.push(raw[0, index+1]) # inclue closing )
396
- raw = raw[index+2, raw.length-index-2]
397
- end
398
- data.push(raw) if !raw.nil?
399
- data.join("\n")
400
- end
401
- end
402
-
403
- class LinuxUser < UnixUser
404
- include PasswdParser
405
- include CommentParser
406
-
407
- def meta_info(username)
408
- cmd = inspec.command("getent passwd #{username}")
409
- return nil if cmd.exit_status != 0
410
- # returns: root:x:0:0:root:/root:/bin/bash
411
- passwd = parse_passwd_line(cmd.stdout.chomp)
412
- {
413
- home: passwd['home'],
414
- shell: passwd['shell'],
415
- }
416
- end
417
-
418
- def credentials(username)
419
- cmd = inspec.command("chage -l #{username}")
420
- return nil if cmd.exit_status != 0
421
-
422
- params = SimpleConfig.new(
423
- cmd.stdout.chomp,
424
- assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
425
- group_re: nil,
426
- multiple_values: false,
427
- ).params
428
-
429
- {
430
- mindays: convert_to_i(params['Minimum number of days between password change']),
431
- maxdays: convert_to_i(params['Maximum number of days between password change']),
432
- warndays: convert_to_i(params['Number of days of warning before password expires']),
433
- }
434
- end
435
- end
436
-
437
- class SolarisUser < LinuxUser
438
- def initialize(inspec)
439
- @inspec = inspec
440
- @id_cmd ||= 'id -a'
441
- super
442
- end
443
- end
444
-
445
- class AixUser < UnixUser
446
- def identity(username)
447
- id = super(username)
448
- return nil if id.nil?
449
- # AIX 'id' command doesn't include the primary group in the supplementary
450
- # yet it can be somewhere in the supplementary list if someone added root
451
- # to a groups list in /etc/group
452
- # we rearrange to expected list if that is the case
453
- if id[:groups].first != id[:group]
454
- id[:groups].reject! { |i| i == id[:group] } if id[:groups].include?(id[:group])
455
- id[:groups].unshift(id[:group])
456
- end
457
-
458
- id
459
- end
460
-
461
- def meta_info(username)
462
- lsuser = inspec.command("lsuser -C -a home shell #{username}")
463
- return nil if lsuser.exit_status != 0
464
-
465
- user = lsuser.stdout.chomp.split("\n").last.split(':')
466
- {
467
- home: user[1],
468
- shell: user[2],
469
- }
470
- end
471
-
472
- def credentials(username)
473
- cmd = inspec.command(
474
- "lssec -c -f /etc/security/user -s #{username} -a minage -a maxage -a pwdwarntime",
475
- )
476
- return nil if cmd.exit_status != 0
477
-
478
- user_sec = cmd.stdout.chomp.split("\n").last.split(':')
479
-
480
- {
481
- mindays: user_sec[1].to_i * 7,
482
- maxdays: user_sec[2].to_i * 7,
483
- warndays: user_sec[3].to_i,
484
- }
485
- end
486
- end
487
-
488
- class HpuxUser < UnixUser
489
- def meta_info(username)
490
- hpuxuser = inspec.command("logins -x -l #{username}")
491
- return nil if hpuxuser.exit_status != 0
492
- user = hpuxuser.stdout.chomp.split(' ')
493
- {
494
- home: user[4],
495
- shell: user[5],
496
- }
497
- end
498
- end
499
-
500
- # we do not use 'finger' for MacOS, because it is harder to parse data with it
501
- # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/fingerd.8.html
502
- # instead we use 'dscl' to request user data
503
- # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
504
- # @see http://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user
505
- class DarwinUser < UnixUser
506
- def initialize(inspec)
507
- @list_users_cmd ||= 'dscl . list /Users'
508
- super
509
- end
510
-
511
- def meta_info(username)
512
- cmd = inspec.command("dscl -q . -read /Users/#{username} NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell")
513
- return nil if cmd.exit_status != 0
514
-
515
- params = SimpleConfig.new(
516
- cmd.stdout.chomp,
517
- assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
518
- group_re: nil,
519
- multiple_values: false,
520
- ).params
521
-
522
- {
523
- home: params['NFSHomeDirectory'],
524
- shell: params['UserShell'],
525
- }
526
- end
527
- end
528
-
529
- # FreeBSD recommends to use the 'pw' command for user management
530
- # @see: https://www.freebsd.org/doc/handbook/users-synopsis.html
531
- # @see: https://www.freebsd.org/cgi/man.cgi?pw(8)
532
- # It offers the following commands:
533
- # - adduser(8) The recommended command-line application for adding new users.
534
- # - rmuser(8) The recommended command-line application for removing users.
535
- # - chpass(1) A flexible tool for changing user database information.
536
- # - passwd(1) The command-line tool to change user passwords.
537
- class FreeBSDUser < UnixUser
538
- include PasswdParser
539
-
540
- def meta_info(username)
541
- cmd = inspec.command("pw usershow #{username} -7")
542
- return nil if cmd.exit_status != 0
543
- # returns: root:*:0:0:Charlie &:/root:/bin/csh
544
- passwd = parse_passwd_line(cmd.stdout.chomp)
545
- {
546
- home: passwd['home'],
547
- shell: passwd['shell'],
548
- }
549
- end
550
- end
551
-
552
- # This optimization was inspired by
553
- # @see https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx
554
- # Alternative solutions are WMI Win32_UserAccount
555
- # @see https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
556
- # @see https://msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx
557
- class WindowsUser < UserInfo
558
- def parse_windows_account(username)
559
- account = username.split('\\')
560
- name = account.pop
561
- domain = account.pop if !account.empty?
562
- [name, domain]
563
- end
564
-
565
- def identity(username)
566
- # TODO: we look for local users only at this point
567
- name, _domain = parse_windows_account(username)
568
- return if collect_user_details.nil?
569
- res = collect_user_details.select { |user| user[:username] == name }
570
- res[0] if !res.empty?
571
- end
572
-
573
- def list_users
574
- collect_user_details.map { |user| user[:username] }
575
- end
576
-
577
- # https://msdn.microsoft.com/en-us/library/aa746340(v=vs.85).aspx
578
- def collect_user_details # rubocop:disable Metrics/MethodLength
579
- return @users_cache if defined?(@users_cache)
580
- script = <<~EOH
581
- Function ConvertTo-SID { Param([byte[]]$BinarySID)
582
- (New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value
583
- }
584
-
585
- Function Convert-UserFlag { Param ($UserFlag)
586
- $List = @()
587
- Switch ($UserFlag) {
588
- ($UserFlag -BOR 0x0001) { $List += 'SCRIPT' }
589
- ($UserFlag -BOR 0x0002) { $List += 'ACCOUNTDISABLE' }
590
- ($UserFlag -BOR 0x0008) { $List += 'HOMEDIR_REQUIRED' }
591
- ($UserFlag -BOR 0x0010) { $List += 'LOCKOUT' }
592
- ($UserFlag -BOR 0x0020) { $List += 'PASSWD_NOTREQD' }
593
- ($UserFlag -BOR 0x0040) { $List += 'PASSWD_CANT_CHANGE' }
594
- ($UserFlag -BOR 0x0080) { $List += 'ENCRYPTED_TEXT_PWD_ALLOWED' }
595
- ($UserFlag -BOR 0x0100) { $List += 'TEMP_DUPLICATE_ACCOUNT' }
596
- ($UserFlag -BOR 0x0200) { $List += 'NORMAL_ACCOUNT' }
597
- ($UserFlag -BOR 0x0800) { $List += 'INTERDOMAIN_TRUST_ACCOUNT' }
598
- ($UserFlag -BOR 0x1000) { $List += 'WORKSTATION_TRUST_ACCOUNT' }
599
- ($UserFlag -BOR 0x2000) { $List += 'SERVER_TRUST_ACCOUNT' }
600
- ($UserFlag -BOR 0x10000) { $List += 'DONT_EXPIRE_PASSWORD' }
601
- ($UserFlag -BOR 0x20000) { $List += 'MNS_LOGON_ACCOUNT' }
602
- ($UserFlag -BOR 0x40000) { $List += 'SMARTCARD_REQUIRED' }
603
- ($UserFlag -BOR 0x80000) { $List += 'TRUSTED_FOR_DELEGATION' }
604
- ($UserFlag -BOR 0x100000) { $List += 'NOT_DELEGATED' }
605
- ($UserFlag -BOR 0x200000) { $List += 'USE_DES_KEY_ONLY' }
606
- ($UserFlag -BOR 0x400000) { $List += 'DONT_REQ_PREAUTH' }
607
- ($UserFlag -BOR 0x800000) { $List += 'PASSWORD_EXPIRED' }
608
- ($UserFlag -BOR 0x1000000) { $List += 'TRUSTED_TO_AUTH_FOR_DELEGATION' }
609
- ($UserFlag -BOR 0x04000000) { $List += 'PARTIAL_SECRETS_ACCOUNT' }
610
- }
611
- $List
612
- }
613
-
614
- $Computername = $Env:Computername
615
- $adsi = [ADSI]"WinNT://$Computername"
616
- $adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
617
- New-Object PSObject -property @{
618
- uid = ConvertTo-SID -BinarySID $_.ObjectSID[0]
619
- username = $_.Name[0]
620
- description = $_.Description[0]
621
- disabled = $_.AccountDisabled[0]
622
- userflags = Convert-UserFlag -UserFlag $_.UserFlags[0]
623
- passwordage = [math]::Round($_.PasswordAge[0]/86400)
624
- minpasswordlength = $_.MinPasswordLength[0]
625
- mindays = [math]::Round($_.MinPasswordAge[0]/86400)
626
- maxdays = [math]::Round($_.MaxPasswordAge[0]/86400)
627
- warndays = $null
628
- badpasswordattempts = $_.BadPasswordAttempts[0]
629
- maxbadpasswords = $_.MaxBadPasswordsAllowed[0]
630
- gid = $null
631
- group = $null
632
- groups = @($_.Groups() | Foreach-Object { $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null) })
633
- home = $_.HomeDirectory[0]
634
- shell = $null
635
- domain = $Computername
636
- }
637
- } | ConvertTo-Json
638
- EOH
639
- cmd = inspec.powershell(script)
640
- # cannot rely on exit code for now, successful command returns exit code 1
641
- # return nil if cmd.exit_status != 0, try to parse json
642
- begin
643
- users = JSON.parse(cmd.stdout)
644
- rescue JSON::ParserError => _e
645
- return nil
646
- end
647
-
648
- # ensure we have an array of groups
649
- users = [users] if !users.is_a?(Array)
650
- # convert keys to symbols
651
- @users_cache = users.map { |user| user.each_with_object({}) { |(k, v), h| h[k.to_sym] = v } }
652
- end
653
- end
654
- end
1
+ # encoding: utf-8
2
+
3
+ require 'utils/parser'
4
+ require 'utils/convert'
5
+ require 'utils/filter'
6
+
7
+ module Inspec::Resources
8
+ # This file contains two resources, the `user` and `users` resource.
9
+ # The `user` resource is optimized for requests that verify specific users
10
+ # that you know upfront for testing. If you need to query all users or search
11
+ # specific users with certain properties, use the `users` resource.
12
+ module UserManagementSelector
13
+ # select user provider based on the operating system
14
+ # returns nil, if no user manager was found for the operating system
15
+ def select_user_manager(os)
16
+ if os.linux?
17
+ LinuxUser.new(inspec)
18
+ elsif os.windows?
19
+ WindowsUser.new(inspec)
20
+ elsif ['darwin'].include?(os[:family])
21
+ DarwinUser.new(inspec)
22
+ elsif ['freebsd'].include?(os[:family])
23
+ FreeBSDUser.new(inspec)
24
+ elsif ['aix'].include?(os[:family])
25
+ AixUser.new(inspec)
26
+ elsif os.solaris?
27
+ SolarisUser.new(inspec)
28
+ elsif ['hpux'].include?(os[:family])
29
+ HpuxUser.new(inspec)
30
+ end
31
+ end
32
+ end
33
+
34
+ # The InSpec users resources looksup all local users available on a system.
35
+ # TODO: the current version of the users resource will use eg. /etc/passwd
36
+ # on Linux to parse all usernames. Therefore the resource may not return
37
+ # users managed on other systems like LDAP/ActiveDirectory. Please open
38
+ # a feature request at https://github.com/chef/inspec if you need that
39
+ # functionality
40
+ #
41
+ # This resource allows complex filter mechanisms
42
+ #
43
+ # describe users.where(uid: 0).entries do
44
+ # it { should eq ['root'] }
45
+ # its('uids') { should eq [1234] }
46
+ # its('gids') { should eq [1234] }
47
+ # end
48
+ #
49
+ # describe users.where { uid =~ /S\-1\-5\-21\-\d+\-\d+\-\d+\-500/ } do
50
+ # it { should exist }
51
+ # end
52
+ class Users < Inspec.resource(1)
53
+ include UserManagementSelector
54
+
55
+ name 'users'
56
+ supports platform: 'unix'
57
+ supports platform: 'windows'
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
+ supports platform: 'unix'
144
+ supports platform: 'windows'
145
+ 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.'
146
+ example "
147
+ describe user('root') do
148
+ it { should exist }
149
+ its('uid') { should eq 1234 }
150
+ its('gid') { should eq 1234 }
151
+ end
152
+ "
153
+ def initialize(username = nil)
154
+ @username = username
155
+ # select user provider
156
+ @user_provider = select_user_manager(inspec.os)
157
+ return skip_resource 'The `user` resource is not supported on your OS yet.' if @user_provider.nil?
158
+ end
159
+
160
+ def exists?
161
+ !identity.nil? && !identity[:username].nil?
162
+ end
163
+
164
+ def disabled?
165
+ identity[:disabled] == true unless identity.nil?
166
+ end
167
+
168
+ def enabled?
169
+ identity[:disabled] == false unless identity.nil?
170
+ end
171
+
172
+ def username
173
+ identity[:username] unless identity.nil?
174
+ end
175
+
176
+ def uid
177
+ identity[:uid] unless identity.nil?
178
+ end
179
+
180
+ def gid
181
+ identity[:gid] unless identity.nil?
182
+ end
183
+
184
+ def groupname
185
+ identity[:groupname] unless identity.nil?
186
+ end
187
+ alias group groupname
188
+
189
+ def groups
190
+ identity[:groups] unless identity.nil?
191
+ end
192
+
193
+ def home
194
+ meta_info[:home] unless meta_info.nil?
195
+ end
196
+
197
+ def shell
198
+ meta_info[:shell] unless meta_info.nil?
199
+ end
200
+
201
+ # returns the minimum days between password changes
202
+ def mindays
203
+ credentials[:mindays] unless credentials.nil?
204
+ end
205
+
206
+ # returns the maximum days between password changes
207
+ def maxdays
208
+ credentials[:maxdays] unless credentials.nil?
209
+ end
210
+
211
+ # returns the days for password change warning
212
+ def warndays
213
+ credentials[:warndays] unless credentials.nil?
214
+ end
215
+
216
+ # implement 'mindays' method to be compatible with serverspec
217
+ def minimum_days_between_password_change
218
+ deprecated('minimum_days_between_password_change', "Please use: its('mindays')")
219
+ mindays
220
+ end
221
+
222
+ # implement 'maxdays' method to be compatible with serverspec
223
+ def maximum_days_between_password_change
224
+ deprecated('maximum_days_between_password_change', "Please use: its('maxdays')")
225
+ maxdays
226
+ end
227
+
228
+ # implements rspec has matcher, to be compatible with serverspec
229
+ # @see: https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/has.rb
230
+ def has_uid?(compare_uid)
231
+ deprecated('has_uid?')
232
+ uid == compare_uid
233
+ end
234
+
235
+ def has_home_directory?(compare_home)
236
+ deprecated('has_home_directory?', "Please use: its('home')")
237
+ home == compare_home
238
+ end
239
+
240
+ def has_login_shell?(compare_shell)
241
+ deprecated('has_login_shell?', "Please use: its('shell')")
242
+ shell == compare_shell
243
+ end
244
+
245
+ def has_authorized_key?(_compare_key)
246
+ deprecated('has_authorized_key?')
247
+ raise NotImplementedError
248
+ end
249
+
250
+ def deprecated(name, alternative = nil)
251
+ warn "[DEPRECATION] #{name} is deprecated. #{alternative}"
252
+ end
253
+
254
+ def to_s
255
+ "User #{@username}"
256
+ end
257
+
258
+ private
259
+
260
+ # returns the iden
261
+ def identity
262
+ return @id_cache if defined?(@id_cache)
263
+ @id_cache = @user_provider.identity(@username) if !@user_provider.nil?
264
+ end
265
+
266
+ def meta_info
267
+ return @meta_cache if defined?(@meta_cache)
268
+ @meta_cache = @user_provider.meta_info(@username) if !@user_provider.nil?
269
+ end
270
+
271
+ def credentials
272
+ return @cred_cache if defined?(@cred_cache)
273
+ @cred_cache = @user_provider.credentials(@username) if !@user_provider.nil?
274
+ end
275
+ end
276
+
277
+ # This is an abstract class that every user provoider has to implement.
278
+ # A user provider implements a system abstracts and helps the InSpec resource
279
+ # hand-over system specific behavior to those providers
280
+ class UserInfo
281
+ include Converter
282
+
283
+ attr_reader :inspec
284
+ def initialize(inspec)
285
+ @inspec = inspec
286
+ end
287
+
288
+ # returns a hash with user-specific values:
289
+ # {
290
+ # uid: '',
291
+ # user: '',
292
+ # gid: '',
293
+ # group: '',
294
+ # groups: '',
295
+ # }
296
+ def identity(_username)
297
+ raise 'user provider must implement the `identity` method'
298
+ end
299
+
300
+ # returns optional information about a user, eg shell
301
+ def meta_info(_username)
302
+ nil
303
+ end
304
+
305
+ # returns a hash with meta-data about user credentials
306
+ # {
307
+ # mindays: 1,
308
+ # maxdays: 1,
309
+ # warndays: 1,
310
+ # }
311
+ # this method is optional and may not be implemented by each provider
312
+ def credentials(_username)
313
+ nil
314
+ end
315
+
316
+ # returns an array with users
317
+ def list_users
318
+ raise 'user provider must implement the `list_users` method'
319
+ end
320
+
321
+ # retuns all aspects of the user as one hash
322
+ def user_details(username)
323
+ item = {}
324
+ id = identity(username)
325
+ item.merge!(id) unless id.nil?
326
+ meta = meta_info(username)
327
+ item.merge!(meta) unless meta.nil?
328
+ cred = credentials(username)
329
+ item.merge!(cred) unless cred.nil?
330
+ item
331
+ end
332
+
333
+ # returns the full information list for a user
334
+ def collect_user_details
335
+ list_users.map { |username|
336
+ user_details(username.chomp)
337
+ }
338
+ end
339
+ end
340
+
341
+ # implements generic unix id handling
342
+ class UnixUser < UserInfo
343
+ attr_reader :inspec, :id_cmd, :list_users_cmd
344
+ def initialize(inspec)
345
+ @inspec = inspec
346
+ @id_cmd ||= 'id'
347
+ @list_users_cmd ||= 'cut -d: -f1 /etc/passwd | grep -v "^#"'
348
+ super
349
+ end
350
+
351
+ # returns a list of all local users on a system
352
+ def list_users
353
+ cmd = inspec.command(list_users_cmd)
354
+ return [] if cmd.exit_status != 0
355
+ cmd.stdout.chomp.lines
356
+ end
357
+
358
+ # parse one id entry like '0(wheel)''
359
+ def parse_value(line)
360
+ SimpleConfig.new(
361
+ line,
362
+ line_separator: ',',
363
+ assignment_regex: /^\s*([^\(]*?)\s*\(\s*(.*?)\)*$/,
364
+ group_re: nil,
365
+ multiple_values: false,
366
+ ).params
367
+ end
368
+
369
+ # extracts the identity
370
+ def identity(username)
371
+ cmd = inspec.command("#{id_cmd} #{username}")
372
+ return nil if cmd.exit_status != 0
373
+
374
+ # parse words
375
+ params = SimpleConfig.new(
376
+ parse_id_entries(cmd.stdout.chomp),
377
+ assignment_regex: /^\s*([^=]*?)\s*=\s*(.*?)\s*$/,
378
+ group_re: nil,
379
+ multiple_values: false,
380
+ ).params
381
+
382
+ {
383
+ uid: convert_to_i(parse_value(params['uid']).keys[0]),
384
+ username: parse_value(params['uid']).values[0],
385
+ gid: convert_to_i(parse_value(params['gid']).keys[0]),
386
+ groupname: parse_value(params['gid']).values[0],
387
+ groups: parse_value(params['groups']).values,
388
+ }
389
+ end
390
+
391
+ # splits the results of id into seperate lines
392
+ def parse_id_entries(raw)
393
+ data = []
394
+ until (index = raw.index(/\)\s{1}/)).nil?
395
+ data.push(raw[0, index+1]) # inclue closing )
396
+ raw = raw[index+2, raw.length-index-2]
397
+ end
398
+ data.push(raw) if !raw.nil?
399
+ data.join("\n")
400
+ end
401
+ end
402
+
403
+ class LinuxUser < UnixUser
404
+ include PasswdParser
405
+ include CommentParser
406
+
407
+ def meta_info(username)
408
+ cmd = inspec.command("getent passwd #{username}")
409
+ return nil if cmd.exit_status != 0
410
+ # returns: root:x:0:0:root:/root:/bin/bash
411
+ passwd = parse_passwd_line(cmd.stdout.chomp)
412
+ {
413
+ home: passwd['home'],
414
+ shell: passwd['shell'],
415
+ }
416
+ end
417
+
418
+ def credentials(username)
419
+ cmd = inspec.command("chage -l #{username}")
420
+ return nil if cmd.exit_status != 0
421
+
422
+ params = SimpleConfig.new(
423
+ cmd.stdout.chomp,
424
+ assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
425
+ group_re: nil,
426
+ multiple_values: false,
427
+ ).params
428
+
429
+ {
430
+ mindays: convert_to_i(params['Minimum number of days between password change']),
431
+ maxdays: convert_to_i(params['Maximum number of days between password change']),
432
+ warndays: convert_to_i(params['Number of days of warning before password expires']),
433
+ }
434
+ end
435
+ end
436
+
437
+ class SolarisUser < LinuxUser
438
+ def initialize(inspec)
439
+ @inspec = inspec
440
+ @id_cmd ||= 'id -a'
441
+ super
442
+ end
443
+ end
444
+
445
+ class AixUser < UnixUser
446
+ def identity(username)
447
+ id = super(username)
448
+ return nil if id.nil?
449
+ # AIX 'id' command doesn't include the primary group in the supplementary
450
+ # yet it can be somewhere in the supplementary list if someone added root
451
+ # to a groups list in /etc/group
452
+ # we rearrange to expected list if that is the case
453
+ if id[:groups].first != id[:group]
454
+ id[:groups].reject! { |i| i == id[:group] } if id[:groups].include?(id[:group])
455
+ id[:groups].unshift(id[:group])
456
+ end
457
+
458
+ id
459
+ end
460
+
461
+ def meta_info(username)
462
+ lsuser = inspec.command("lsuser -C -a home shell #{username}")
463
+ return nil if lsuser.exit_status != 0
464
+
465
+ user = lsuser.stdout.chomp.split("\n").last.split(':')
466
+ {
467
+ home: user[1],
468
+ shell: user[2],
469
+ }
470
+ end
471
+
472
+ def credentials(username)
473
+ cmd = inspec.command(
474
+ "lssec -c -f /etc/security/user -s #{username} -a minage -a maxage -a pwdwarntime",
475
+ )
476
+ return nil if cmd.exit_status != 0
477
+
478
+ user_sec = cmd.stdout.chomp.split("\n").last.split(':')
479
+
480
+ {
481
+ mindays: user_sec[1].to_i * 7,
482
+ maxdays: user_sec[2].to_i * 7,
483
+ warndays: user_sec[3].to_i,
484
+ }
485
+ end
486
+ end
487
+
488
+ class HpuxUser < UnixUser
489
+ def meta_info(username)
490
+ hpuxuser = inspec.command("logins -x -l #{username}")
491
+ return nil if hpuxuser.exit_status != 0
492
+ user = hpuxuser.stdout.chomp.split(' ')
493
+ {
494
+ home: user[4],
495
+ shell: user[5],
496
+ }
497
+ end
498
+ end
499
+
500
+ # we do not use 'finger' for MacOS, because it is harder to parse data with it
501
+ # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/fingerd.8.html
502
+ # instead we use 'dscl' to request user data
503
+ # @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dscl.1.html
504
+ # @see http://superuser.com/questions/592921/mac-osx-users-vs-dscl-command-to-list-user
505
+ class DarwinUser < UnixUser
506
+ def initialize(inspec)
507
+ @list_users_cmd ||= 'dscl . list /Users'
508
+ super
509
+ end
510
+
511
+ def meta_info(username)
512
+ cmd = inspec.command("dscl -q . -read /Users/#{username} NFSHomeDirectory PrimaryGroupID RecordName UniqueID UserShell")
513
+ return nil if cmd.exit_status != 0
514
+
515
+ params = SimpleConfig.new(
516
+ cmd.stdout.chomp,
517
+ assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
518
+ group_re: nil,
519
+ multiple_values: false,
520
+ ).params
521
+
522
+ {
523
+ home: params['NFSHomeDirectory'],
524
+ shell: params['UserShell'],
525
+ }
526
+ end
527
+ end
528
+
529
+ # FreeBSD recommends to use the 'pw' command for user management
530
+ # @see: https://www.freebsd.org/doc/handbook/users-synopsis.html
531
+ # @see: https://www.freebsd.org/cgi/man.cgi?pw(8)
532
+ # It offers the following commands:
533
+ # - adduser(8) The recommended command-line application for adding new users.
534
+ # - rmuser(8) The recommended command-line application for removing users.
535
+ # - chpass(1) A flexible tool for changing user database information.
536
+ # - passwd(1) The command-line tool to change user passwords.
537
+ class FreeBSDUser < UnixUser
538
+ include PasswdParser
539
+
540
+ def meta_info(username)
541
+ cmd = inspec.command("pw usershow #{username} -7")
542
+ return nil if cmd.exit_status != 0
543
+ # returns: root:*:0:0:Charlie &:/root:/bin/csh
544
+ passwd = parse_passwd_line(cmd.stdout.chomp)
545
+ {
546
+ home: passwd['home'],
547
+ shell: passwd['shell'],
548
+ }
549
+ end
550
+ end
551
+
552
+ # This optimization was inspired by
553
+ # @see https://mcpmag.com/articles/2015/04/15/reporting-on-local-accounts.aspx
554
+ # Alternative solutions are WMI Win32_UserAccount
555
+ # @see https://msdn.microsoft.com/en-us/library/aa394507(v=vs.85).aspx
556
+ # @see https://msdn.microsoft.com/en-us/library/aa394153(v=vs.85).aspx
557
+ class WindowsUser < UserInfo
558
+ def parse_windows_account(username)
559
+ account = username.split('\\')
560
+ name = account.pop
561
+ domain = account.pop if !account.empty?
562
+ [name, domain]
563
+ end
564
+
565
+ def identity(username)
566
+ # TODO: we look for local users only at this point
567
+ name, _domain = parse_windows_account(username)
568
+ return if collect_user_details.nil?
569
+ res = collect_user_details.select { |user| user[:username] == name }
570
+ res[0] if !res.empty?
571
+ end
572
+
573
+ def list_users
574
+ collect_user_details.map { |user| user[:username] }
575
+ end
576
+
577
+ # https://msdn.microsoft.com/en-us/library/aa746340(v=vs.85).aspx
578
+ def collect_user_details # rubocop:disable Metrics/MethodLength
579
+ return @users_cache if defined?(@users_cache)
580
+ script = <<~EOH
581
+ Function ConvertTo-SID { Param([byte[]]$BinarySID)
582
+ (New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value
583
+ }
584
+
585
+ Function Convert-UserFlag { Param ($UserFlag)
586
+ $List = @()
587
+ Switch ($UserFlag) {
588
+ ($UserFlag -BOR 0x0001) { $List += 'SCRIPT' }
589
+ ($UserFlag -BOR 0x0002) { $List += 'ACCOUNTDISABLE' }
590
+ ($UserFlag -BOR 0x0008) { $List += 'HOMEDIR_REQUIRED' }
591
+ ($UserFlag -BOR 0x0010) { $List += 'LOCKOUT' }
592
+ ($UserFlag -BOR 0x0020) { $List += 'PASSWD_NOTREQD' }
593
+ ($UserFlag -BOR 0x0040) { $List += 'PASSWD_CANT_CHANGE' }
594
+ ($UserFlag -BOR 0x0080) { $List += 'ENCRYPTED_TEXT_PWD_ALLOWED' }
595
+ ($UserFlag -BOR 0x0100) { $List += 'TEMP_DUPLICATE_ACCOUNT' }
596
+ ($UserFlag -BOR 0x0200) { $List += 'NORMAL_ACCOUNT' }
597
+ ($UserFlag -BOR 0x0800) { $List += 'INTERDOMAIN_TRUST_ACCOUNT' }
598
+ ($UserFlag -BOR 0x1000) { $List += 'WORKSTATION_TRUST_ACCOUNT' }
599
+ ($UserFlag -BOR 0x2000) { $List += 'SERVER_TRUST_ACCOUNT' }
600
+ ($UserFlag -BOR 0x10000) { $List += 'DONT_EXPIRE_PASSWORD' }
601
+ ($UserFlag -BOR 0x20000) { $List += 'MNS_LOGON_ACCOUNT' }
602
+ ($UserFlag -BOR 0x40000) { $List += 'SMARTCARD_REQUIRED' }
603
+ ($UserFlag -BOR 0x80000) { $List += 'TRUSTED_FOR_DELEGATION' }
604
+ ($UserFlag -BOR 0x100000) { $List += 'NOT_DELEGATED' }
605
+ ($UserFlag -BOR 0x200000) { $List += 'USE_DES_KEY_ONLY' }
606
+ ($UserFlag -BOR 0x400000) { $List += 'DONT_REQ_PREAUTH' }
607
+ ($UserFlag -BOR 0x800000) { $List += 'PASSWORD_EXPIRED' }
608
+ ($UserFlag -BOR 0x1000000) { $List += 'TRUSTED_TO_AUTH_FOR_DELEGATION' }
609
+ ($UserFlag -BOR 0x04000000) { $List += 'PARTIAL_SECRETS_ACCOUNT' }
610
+ }
611
+ $List
612
+ }
613
+
614
+ $Computername = $Env:Computername
615
+ $adsi = [ADSI]"WinNT://$Computername"
616
+ $adsi.Children | where {$_.SchemaClassName -eq 'user'} | ForEach {
617
+ New-Object PSObject -property @{
618
+ uid = ConvertTo-SID -BinarySID $_.ObjectSID[0]
619
+ username = $_.Name[0]
620
+ description = $_.Description[0]
621
+ disabled = $_.AccountDisabled[0]
622
+ userflags = Convert-UserFlag -UserFlag $_.UserFlags[0]
623
+ passwordage = [math]::Round($_.PasswordAge[0]/86400)
624
+ minpasswordlength = $_.MinPasswordLength[0]
625
+ mindays = [math]::Round($_.MinPasswordAge[0]/86400)
626
+ maxdays = [math]::Round($_.MaxPasswordAge[0]/86400)
627
+ warndays = $null
628
+ badpasswordattempts = $_.BadPasswordAttempts[0]
629
+ maxbadpasswords = $_.MaxBadPasswordsAllowed[0]
630
+ gid = $null
631
+ group = $null
632
+ groups = @($_.Groups() | Foreach-Object { $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null) })
633
+ home = $_.HomeDirectory[0]
634
+ shell = $null
635
+ domain = $Computername
636
+ }
637
+ } | ConvertTo-Json
638
+ EOH
639
+ cmd = inspec.powershell(script)
640
+ # cannot rely on exit code for now, successful command returns exit code 1
641
+ # return nil if cmd.exit_status != 0, try to parse json
642
+ begin
643
+ users = JSON.parse(cmd.stdout)
644
+ rescue JSON::ParserError => _e
645
+ return nil
646
+ end
647
+
648
+ # ensure we have an array of groups
649
+ users = [users] if !users.is_a?(Array)
650
+ # convert keys to symbols
651
+ @users_cache = users.map { |user| user.each_with_object({}) { |(k, v), h| h[k.to_sym] = v } }
652
+ end
653
+ end
654
+ end