inspec 2.0.16 → 2.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (480) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +101 -101
  3. data/CHANGELOG.md +2949 -2944
  4. data/Gemfile +55 -55
  5. data/LICENSE +14 -14
  6. data/MAINTAINERS.md +31 -31
  7. data/MAINTAINERS.toml +47 -47
  8. data/README.md +438 -438
  9. data/Rakefile +284 -284
  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/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_ec2_instance.md.erb +106 -106
  36. data/docs/resources/aws_iam_access_key.md.erb +123 -123
  37. data/docs/resources/aws_iam_access_keys.md.erb +198 -198
  38. data/docs/resources/aws_iam_group.md.erb +46 -46
  39. data/docs/resources/aws_iam_groups.md.erb +43 -43
  40. data/docs/resources/aws_iam_password_policy.md.erb +76 -76
  41. data/docs/resources/aws_iam_policies.md.erb +82 -82
  42. data/docs/resources/aws_iam_policy.md.erb +146 -146
  43. data/docs/resources/aws_iam_role.md.erb +65 -65
  44. data/docs/resources/aws_iam_root_user.md.erb +58 -58
  45. data/docs/resources/aws_iam_user.md.erb +64 -64
  46. data/docs/resources/aws_iam_users.md.erb +89 -89
  47. data/docs/resources/aws_kms_keys.md.erb +84 -84
  48. data/docs/resources/aws_route_table.md.erb +47 -47
  49. data/docs/resources/aws_s3_bucket.md.erb +134 -134
  50. data/docs/resources/aws_security_group.md.erb +152 -152
  51. data/docs/resources/aws_security_groups.md.erb +92 -92
  52. data/docs/resources/aws_sns_topic.md.erb +62 -62
  53. data/docs/resources/aws_subnet.md.erb +133 -133
  54. data/docs/resources/aws_subnets.md.erb +126 -126
  55. data/docs/resources/aws_vpc.md.erb +120 -120
  56. data/docs/resources/aws_vpcs.md.erb +48 -48
  57. data/docs/resources/azure_generic_resource.md.erb +170 -139
  58. data/docs/resources/azure_resource_group.md.erb +284 -284
  59. data/docs/resources/azure_virtual_machine.md.erb +347 -314
  60. data/docs/resources/azure_virtual_machine_data_disk.md.erb +224 -182
  61. data/docs/resources/bash.md.erb +75 -75
  62. data/docs/resources/bond.md.erb +90 -90
  63. data/docs/resources/bridge.md.erb +57 -57
  64. data/docs/resources/bsd_service.md.erb +67 -67
  65. data/docs/resources/command.md.erb +138 -138
  66. data/docs/resources/cpan.md.erb +79 -79
  67. data/docs/resources/cran.md.erb +64 -64
  68. data/docs/resources/crontab.md.erb +88 -88
  69. data/docs/resources/csv.md.erb +54 -54
  70. data/docs/resources/dh_params.md.erb +217 -217
  71. data/docs/resources/directory.md.erb +30 -30
  72. data/docs/resources/docker.md.erb +164 -164
  73. data/docs/resources/docker_container.md.erb +104 -104
  74. data/docs/resources/docker_image.md.erb +94 -94
  75. data/docs/resources/docker_service.md.erb +114 -114
  76. data/docs/resources/elasticsearch.md.erb +242 -242
  77. data/docs/resources/etc_fstab.md.erb +125 -125
  78. data/docs/resources/etc_group.md.erb +75 -75
  79. data/docs/resources/etc_hosts.md.erb +78 -78
  80. data/docs/resources/etc_hosts_allow.md.erb +74 -74
  81. data/docs/resources/etc_hosts_deny.md.erb +74 -74
  82. data/docs/resources/file.md.erb +515 -515
  83. data/docs/resources/filesystem.md.erb +41 -41
  84. data/docs/resources/firewalld.md.erb +107 -107
  85. data/docs/resources/gem.md.erb +79 -79
  86. data/docs/resources/group.md.erb +61 -61
  87. data/docs/resources/grub_conf.md.erb +101 -101
  88. data/docs/resources/host.md.erb +78 -78
  89. data/docs/resources/http.md.erb +101 -101
  90. data/docs/resources/iis_app.md.erb +122 -122
  91. data/docs/resources/iis_site.md.erb +135 -135
  92. data/docs/resources/inetd_conf.md.erb +94 -94
  93. data/docs/resources/ini.md.erb +76 -76
  94. data/docs/resources/interface.md.erb +58 -58
  95. data/docs/resources/iptables.md.erb +64 -64
  96. data/docs/resources/json.md.erb +62 -62
  97. data/docs/resources/kernel_module.md.erb +107 -107
  98. data/docs/resources/kernel_parameter.md.erb +53 -53
  99. data/docs/resources/key_rsa.md.erb +85 -85
  100. data/docs/resources/launchd_service.md.erb +57 -57
  101. data/docs/resources/limits_conf.md.erb +75 -75
  102. data/docs/resources/login_def.md.erb +71 -71
  103. data/docs/resources/mount.md.erb +69 -69
  104. data/docs/resources/mssql_session.md.erb +60 -60
  105. data/docs/resources/mysql_conf.md.erb +99 -99
  106. data/docs/resources/mysql_session.md.erb +74 -74
  107. data/docs/resources/nginx.md.erb +79 -79
  108. data/docs/resources/nginx_conf.md.erb +128 -128
  109. data/docs/resources/npm.md.erb +60 -60
  110. data/docs/resources/ntp_conf.md.erb +60 -60
  111. data/docs/resources/oneget.md.erb +53 -53
  112. data/docs/resources/oracledb_session.md.erb +52 -52
  113. data/docs/resources/os.md.erb +141 -141
  114. data/docs/resources/os_env.md.erb +78 -78
  115. data/docs/resources/package.md.erb +120 -120
  116. data/docs/resources/packages.md.erb +67 -67
  117. data/docs/resources/parse_config.md.erb +103 -103
  118. data/docs/resources/parse_config_file.md.erb +138 -138
  119. data/docs/resources/passwd.md.erb +141 -141
  120. data/docs/resources/pip.md.erb +67 -67
  121. data/docs/resources/port.md.erb +137 -137
  122. data/docs/resources/postgres_conf.md.erb +79 -79
  123. data/docs/resources/postgres_hba_conf.md.erb +93 -93
  124. data/docs/resources/postgres_ident_conf.md.erb +76 -76
  125. data/docs/resources/postgres_session.md.erb +69 -69
  126. data/docs/resources/powershell.md.erb +102 -102
  127. data/docs/resources/processes.md.erb +109 -109
  128. data/docs/resources/rabbitmq_config.md.erb +41 -41
  129. data/docs/resources/registry_key.md.erb +158 -158
  130. data/docs/resources/runit_service.md.erb +57 -57
  131. data/docs/resources/security_policy.md.erb +47 -47
  132. data/docs/resources/service.md.erb +121 -121
  133. data/docs/resources/shadow.md.erb +144 -144
  134. data/docs/resources/ssh_config.md.erb +80 -80
  135. data/docs/resources/sshd_config.md.erb +83 -83
  136. data/docs/resources/ssl.md.erb +119 -119
  137. data/docs/resources/sys_info.md.erb +42 -42
  138. data/docs/resources/systemd_service.md.erb +57 -57
  139. data/docs/resources/sysv_service.md.erb +57 -57
  140. data/docs/resources/upstart_service.md.erb +57 -57
  141. data/docs/resources/user.md.erb +140 -140
  142. data/docs/resources/users.md.erb +127 -127
  143. data/docs/resources/vbscript.md.erb +55 -55
  144. data/docs/resources/virtualization.md.erb +57 -57
  145. data/docs/resources/windows_feature.md.erb +47 -47
  146. data/docs/resources/windows_hotfix.md.erb +53 -53
  147. data/docs/resources/windows_task.md.erb +95 -95
  148. data/docs/resources/wmi.md.erb +81 -81
  149. data/docs/resources/x509_certificate.md.erb +151 -151
  150. data/docs/resources/xinetd_conf.md.erb +156 -156
  151. data/docs/resources/xml.md.erb +85 -85
  152. data/docs/resources/yaml.md.erb +69 -69
  153. data/docs/resources/yum.md.erb +98 -98
  154. data/docs/resources/zfs_dataset.md.erb +53 -53
  155. data/docs/resources/zfs_pool.md.erb +47 -47
  156. data/docs/ruby_usage.md +203 -203
  157. data/docs/shared/matcher_be.md.erb +1 -1
  158. data/docs/shared/matcher_cmp.md.erb +43 -43
  159. data/docs/shared/matcher_eq.md.erb +3 -3
  160. data/docs/shared/matcher_include.md.erb +1 -1
  161. data/docs/shared/matcher_match.md.erb +1 -1
  162. data/docs/shell.md +172 -172
  163. data/examples/README.md +8 -8
  164. data/examples/inheritance/README.md +65 -65
  165. data/examples/inheritance/controls/example.rb +14 -14
  166. data/examples/inheritance/inspec.yml +15 -15
  167. data/examples/kitchen-ansible/.kitchen.yml +25 -25
  168. data/examples/kitchen-ansible/Gemfile +19 -19
  169. data/examples/kitchen-ansible/README.md +53 -53
  170. data/examples/kitchen-ansible/files/nginx.repo +6 -6
  171. data/examples/kitchen-ansible/tasks/main.yml +16 -16
  172. data/examples/kitchen-ansible/test/integration/default/default.yml +5 -5
  173. data/examples/kitchen-ansible/test/integration/default/web_spec.rb +28 -28
  174. data/examples/kitchen-chef/.kitchen.yml +20 -20
  175. data/examples/kitchen-chef/Berksfile +3 -3
  176. data/examples/kitchen-chef/Gemfile +19 -19
  177. data/examples/kitchen-chef/README.md +27 -27
  178. data/examples/kitchen-chef/metadata.rb +7 -7
  179. data/examples/kitchen-chef/recipes/default.rb +6 -6
  180. data/examples/kitchen-chef/recipes/nginx.rb +30 -30
  181. data/examples/kitchen-chef/test/integration/default/web_spec.rb +28 -28
  182. data/examples/kitchen-puppet/.kitchen.yml +22 -22
  183. data/examples/kitchen-puppet/Gemfile +20 -20
  184. data/examples/kitchen-puppet/Puppetfile +25 -25
  185. data/examples/kitchen-puppet/README.md +53 -53
  186. data/examples/kitchen-puppet/manifests/site.pp +33 -33
  187. data/examples/kitchen-puppet/metadata.json +11 -11
  188. data/examples/kitchen-puppet/test/integration/default/web_spec.rb +28 -28
  189. data/examples/meta-profile/README.md +37 -37
  190. data/examples/meta-profile/controls/example.rb +13 -13
  191. data/examples/meta-profile/inspec.yml +13 -13
  192. data/examples/profile-attribute.yml +2 -2
  193. data/examples/profile-attribute/README.md +14 -14
  194. data/examples/profile-attribute/controls/example.rb +11 -11
  195. data/examples/profile-attribute/inspec.yml +8 -8
  196. data/examples/profile-aws/controls/iam_password_policy_expiration.rb +8 -8
  197. data/examples/profile-aws/controls/iam_password_policy_max_age.rb +8 -8
  198. data/examples/profile-aws/controls/iam_root_user_mfa.rb +8 -8
  199. data/examples/profile-aws/controls/iam_users_access_key_age.rb +8 -8
  200. data/examples/profile-aws/controls/iam_users_console_users_mfa.rb +8 -8
  201. data/examples/profile-aws/inspec.yml +11 -11
  202. data/examples/profile-azure/controls/azure_resource_group_example.rb +24 -24
  203. data/examples/profile-azure/controls/azure_vm_example.rb +29 -29
  204. data/examples/profile-azure/inspec.yml +11 -11
  205. data/examples/profile-sensitive/README.md +29 -29
  206. data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -9
  207. data/examples/profile-sensitive/controls/sensitive.rb +9 -9
  208. data/examples/profile-sensitive/inspec.yml +8 -8
  209. data/examples/profile/README.md +48 -48
  210. data/examples/profile/controls/example.rb +23 -23
  211. data/examples/profile/controls/gordon.rb +36 -36
  212. data/examples/profile/controls/meta.rb +34 -34
  213. data/examples/profile/inspec.yml +10 -10
  214. data/examples/profile/libraries/gordon_config.rb +53 -53
  215. data/inspec.gemspec +47 -47
  216. data/lib/bundles/README.md +3 -3
  217. data/lib/bundles/inspec-artifact.rb +7 -7
  218. data/lib/bundles/inspec-artifact/README.md +1 -1
  219. data/lib/bundles/inspec-artifact/cli.rb +277 -277
  220. data/lib/bundles/inspec-compliance.rb +16 -16
  221. data/lib/bundles/inspec-compliance/.kitchen.yml +20 -20
  222. data/lib/bundles/inspec-compliance/README.md +185 -185
  223. data/lib/bundles/inspec-compliance/api.rb +316 -316
  224. data/lib/bundles/inspec-compliance/api/login.rb +152 -152
  225. data/lib/bundles/inspec-compliance/bootstrap.sh +41 -41
  226. data/lib/bundles/inspec-compliance/cli.rb +254 -254
  227. data/lib/bundles/inspec-compliance/configuration.rb +103 -103
  228. data/lib/bundles/inspec-compliance/http.rb +86 -86
  229. data/lib/bundles/inspec-compliance/support.rb +36 -36
  230. data/lib/bundles/inspec-compliance/target.rb +98 -98
  231. data/lib/bundles/inspec-compliance/test/integration/default/cli.rb +93 -93
  232. data/lib/bundles/inspec-habitat.rb +12 -12
  233. data/lib/bundles/inspec-habitat/cli.rb +36 -36
  234. data/lib/bundles/inspec-habitat/log.rb +10 -10
  235. data/lib/bundles/inspec-habitat/profile.rb +390 -390
  236. data/lib/bundles/inspec-init.rb +8 -8
  237. data/lib/bundles/inspec-init/README.md +31 -31
  238. data/lib/bundles/inspec-init/cli.rb +97 -97
  239. data/lib/bundles/inspec-init/templates/profile/README.md +3 -3
  240. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +19 -19
  241. data/lib/bundles/inspec-init/templates/profile/inspec.yml +8 -8
  242. data/lib/bundles/inspec-supermarket.rb +13 -13
  243. data/lib/bundles/inspec-supermarket/README.md +45 -45
  244. data/lib/bundles/inspec-supermarket/api.rb +84 -84
  245. data/lib/bundles/inspec-supermarket/cli.rb +73 -73
  246. data/lib/bundles/inspec-supermarket/target.rb +34 -34
  247. data/lib/fetchers/git.rb +163 -163
  248. data/lib/fetchers/local.rb +74 -74
  249. data/lib/fetchers/mock.rb +35 -35
  250. data/lib/fetchers/url.rb +204 -204
  251. data/lib/inspec.rb +24 -24
  252. data/lib/inspec/archive/tar.rb +29 -29
  253. data/lib/inspec/archive/zip.rb +19 -19
  254. data/lib/inspec/backend.rb +92 -92
  255. data/lib/inspec/base_cli.rb +350 -333
  256. data/lib/inspec/cached_fetcher.rb +66 -66
  257. data/lib/inspec/cli.rb +292 -302
  258. data/lib/inspec/completions/bash.sh.erb +45 -45
  259. data/lib/inspec/completions/fish.sh.erb +34 -34
  260. data/lib/inspec/completions/zsh.sh.erb +61 -61
  261. data/lib/inspec/control_eval_context.rb +179 -179
  262. data/lib/inspec/dependencies/cache.rb +72 -72
  263. data/lib/inspec/dependencies/dependency_set.rb +92 -92
  264. data/lib/inspec/dependencies/lockfile.rb +115 -115
  265. data/lib/inspec/dependencies/requirement.rb +123 -123
  266. data/lib/inspec/dependencies/resolver.rb +86 -86
  267. data/lib/inspec/describe.rb +27 -27
  268. data/lib/inspec/dsl.rb +66 -66
  269. data/lib/inspec/dsl_shared.rb +33 -33
  270. data/lib/inspec/env_printer.rb +157 -157
  271. data/lib/inspec/errors.rb +13 -13
  272. data/lib/inspec/exceptions.rb +12 -12
  273. data/lib/inspec/expect.rb +45 -45
  274. data/lib/inspec/fetcher.rb +45 -45
  275. data/lib/inspec/file_provider.rb +275 -275
  276. data/lib/inspec/formatters.rb +3 -3
  277. data/lib/inspec/formatters/base.rb +250 -250
  278. data/lib/inspec/formatters/json_rspec.rb +20 -20
  279. data/lib/inspec/formatters/show_progress.rb +12 -12
  280. data/lib/inspec/library_eval_context.rb +58 -58
  281. data/lib/inspec/log.rb +11 -11
  282. data/lib/inspec/metadata.rb +247 -247
  283. data/lib/inspec/method_source.rb +24 -24
  284. data/lib/inspec/objects.rb +14 -14
  285. data/lib/inspec/objects/attribute.rb +65 -65
  286. data/lib/inspec/objects/control.rb +61 -61
  287. data/lib/inspec/objects/describe.rb +92 -92
  288. data/lib/inspec/objects/each_loop.rb +36 -36
  289. data/lib/inspec/objects/list.rb +15 -15
  290. data/lib/inspec/objects/or_test.rb +40 -40
  291. data/lib/inspec/objects/ruby_helper.rb +15 -15
  292. data/lib/inspec/objects/tag.rb +27 -27
  293. data/lib/inspec/objects/test.rb +87 -87
  294. data/lib/inspec/objects/value.rb +27 -27
  295. data/lib/inspec/plugins.rb +60 -60
  296. data/lib/inspec/plugins/cli.rb +24 -24
  297. data/lib/inspec/plugins/fetcher.rb +86 -86
  298. data/lib/inspec/plugins/resource.rb +133 -133
  299. data/lib/inspec/plugins/secret.rb +15 -15
  300. data/lib/inspec/plugins/source_reader.rb +40 -40
  301. data/lib/inspec/polyfill.rb +12 -12
  302. data/lib/inspec/profile.rb +510 -510
  303. data/lib/inspec/profile_context.rb +207 -207
  304. data/lib/inspec/profile_vendor.rb +66 -66
  305. data/lib/inspec/reporters.rb +50 -50
  306. data/lib/inspec/reporters/base.rb +24 -24
  307. data/lib/inspec/reporters/cli.rb +356 -356
  308. data/lib/inspec/reporters/json.rb +116 -116
  309. data/lib/inspec/reporters/json_min.rb +48 -48
  310. data/lib/inspec/reporters/junit.rb +77 -77
  311. data/lib/inspec/require_loader.rb +33 -33
  312. data/lib/inspec/resource.rb +186 -186
  313. data/lib/inspec/rule.rb +266 -266
  314. data/lib/inspec/runner.rb +344 -344
  315. data/lib/inspec/runner_mock.rb +41 -41
  316. data/lib/inspec/runner_rspec.rb +174 -174
  317. data/lib/inspec/runtime_profile.rb +26 -26
  318. data/lib/inspec/schema.rb +213 -213
  319. data/lib/inspec/secrets.rb +19 -19
  320. data/lib/inspec/secrets/yaml.rb +30 -30
  321. data/lib/inspec/shell.rb +220 -223
  322. data/lib/inspec/shell_detector.rb +90 -90
  323. data/lib/inspec/source_reader.rb +29 -29
  324. data/lib/inspec/version.rb +8 -8
  325. data/lib/matchers/matchers.rb +339 -339
  326. data/lib/resource_support/aws.rb +40 -40
  327. data/lib/resource_support/aws/aws_backend_base.rb +12 -12
  328. data/lib/resource_support/aws/aws_backend_factory_mixin.rb +12 -12
  329. data/lib/resource_support/aws/aws_plural_resource_mixin.rb +21 -21
  330. data/lib/resource_support/aws/aws_resource_mixin.rb +66 -66
  331. data/lib/resource_support/aws/aws_singular_resource_mixin.rb +24 -24
  332. data/lib/resources/aide_conf.rb +160 -160
  333. data/lib/resources/apache.rb +48 -48
  334. data/lib/resources/apache_conf.rb +156 -156
  335. data/lib/resources/apt.rb +149 -149
  336. data/lib/resources/audit_policy.rb +63 -63
  337. data/lib/resources/auditd.rb +231 -231
  338. data/lib/resources/auditd_conf.rb +55 -55
  339. data/lib/resources/aws/aws_cloudtrail_trail.rb +77 -77
  340. data/lib/resources/aws/aws_cloudtrail_trails.rb +47 -47
  341. data/lib/resources/aws/aws_cloudwatch_alarm.rb +62 -62
  342. data/lib/resources/aws/aws_cloudwatch_log_metric_filter.rb +100 -100
  343. data/lib/resources/aws/aws_ec2_instance.rb +157 -157
  344. data/lib/resources/aws/aws_iam_access_key.rb +106 -106
  345. data/lib/resources/aws/aws_iam_access_keys.rb +144 -144
  346. data/lib/resources/aws/aws_iam_group.rb +56 -56
  347. data/lib/resources/aws/aws_iam_groups.rb +45 -45
  348. data/lib/resources/aws/aws_iam_password_policy.rb +116 -116
  349. data/lib/resources/aws/aws_iam_policies.rb +46 -46
  350. data/lib/resources/aws/aws_iam_policy.rb +119 -119
  351. data/lib/resources/aws/aws_iam_role.rb +51 -51
  352. data/lib/resources/aws/aws_iam_root_user.rb +60 -60
  353. data/lib/resources/aws/aws_iam_user.rb +111 -111
  354. data/lib/resources/aws/aws_iam_users.rb +96 -96
  355. data/lib/resources/aws/aws_kms_keys.rb +46 -46
  356. data/lib/resources/aws/aws_route_table.rb +61 -61
  357. data/lib/resources/aws/aws_s3_bucket.rb +115 -115
  358. data/lib/resources/aws/aws_security_group.rb +93 -93
  359. data/lib/resources/aws/aws_security_groups.rb +68 -68
  360. data/lib/resources/aws/aws_sns_topic.rb +53 -53
  361. data/lib/resources/aws/aws_subnet.rb +88 -88
  362. data/lib/resources/aws/aws_subnets.rb +53 -53
  363. data/lib/resources/aws/aws_vpc.rb +69 -69
  364. data/lib/resources/aws/aws_vpcs.rb +45 -45
  365. data/lib/resources/azure/azure_backend.rb +377 -377
  366. data/lib/resources/azure/azure_generic_resource.rb +59 -59
  367. data/lib/resources/azure/azure_resource_group.rb +152 -152
  368. data/lib/resources/azure/azure_virtual_machine.rb +264 -264
  369. data/lib/resources/azure/azure_virtual_machine_data_disk.rb +136 -136
  370. data/lib/resources/bash.rb +35 -35
  371. data/lib/resources/bond.rb +68 -68
  372. data/lib/resources/bridge.rb +122 -122
  373. data/lib/resources/command.rb +69 -69
  374. data/lib/resources/cpan.rb +58 -58
  375. data/lib/resources/cran.rb +64 -64
  376. data/lib/resources/crontab.rb +170 -170
  377. data/lib/resources/csv.rb +60 -60
  378. data/lib/resources/dh_params.rb +82 -82
  379. data/lib/resources/directory.rb +25 -25
  380. data/lib/resources/docker.rb +236 -236
  381. data/lib/resources/docker_container.rb +89 -89
  382. data/lib/resources/docker_image.rb +83 -83
  383. data/lib/resources/docker_object.rb +57 -57
  384. data/lib/resources/docker_service.rb +90 -90
  385. data/lib/resources/elasticsearch.rb +169 -169
  386. data/lib/resources/etc_fstab.rb +102 -102
  387. data/lib/resources/etc_group.rb +156 -156
  388. data/lib/resources/etc_hosts.rb +81 -81
  389. data/lib/resources/etc_hosts_allow_deny.rb +123 -123
  390. data/lib/resources/file.rb +298 -298
  391. data/lib/resources/filesystem.rb +31 -31
  392. data/lib/resources/firewalld.rb +144 -144
  393. data/lib/resources/gem.rb +70 -70
  394. data/lib/resources/groups.rb +215 -215
  395. data/lib/resources/grub_conf.rb +237 -237
  396. data/lib/resources/host.rb +300 -300
  397. data/lib/resources/http.rb +250 -250
  398. data/lib/resources/iis_app.rb +104 -104
  399. data/lib/resources/iis_site.rb +148 -148
  400. data/lib/resources/inetd_conf.rb +62 -62
  401. data/lib/resources/ini.rb +29 -29
  402. data/lib/resources/interface.rb +129 -129
  403. data/lib/resources/iptables.rb +69 -69
  404. data/lib/resources/json.rb +117 -117
  405. data/lib/resources/kernel_module.rb +107 -107
  406. data/lib/resources/kernel_parameter.rb +58 -58
  407. data/lib/resources/key_rsa.rb +67 -67
  408. data/lib/resources/limits_conf.rb +55 -55
  409. data/lib/resources/login_def.rb +66 -66
  410. data/lib/resources/mount.rb +88 -88
  411. data/lib/resources/mssql_session.rb +101 -101
  412. data/lib/resources/mysql.rb +81 -81
  413. data/lib/resources/mysql_conf.rb +134 -134
  414. data/lib/resources/mysql_session.rb +71 -71
  415. data/lib/resources/nginx.rb +96 -96
  416. data/lib/resources/nginx_conf.rb +227 -227
  417. data/lib/resources/npm.rb +48 -48
  418. data/lib/resources/ntp_conf.rb +58 -58
  419. data/lib/resources/oneget.rb +71 -71
  420. data/lib/resources/oracledb_session.rb +139 -139
  421. data/lib/resources/os.rb +36 -36
  422. data/lib/resources/os_env.rb +76 -76
  423. data/lib/resources/package.rb +363 -363
  424. data/lib/resources/packages.rb +111 -111
  425. data/lib/resources/parse_config.rb +116 -116
  426. data/lib/resources/passwd.rb +74 -74
  427. data/lib/resources/pip.rb +89 -89
  428. data/lib/resources/platform.rb +109 -109
  429. data/lib/resources/port.rb +771 -771
  430. data/lib/resources/postgres.rb +130 -130
  431. data/lib/resources/postgres_conf.rb +121 -121
  432. data/lib/resources/postgres_hba_conf.rb +100 -100
  433. data/lib/resources/postgres_ident_conf.rb +78 -78
  434. data/lib/resources/postgres_session.rb +71 -71
  435. data/lib/resources/powershell.rb +57 -57
  436. data/lib/resources/processes.rb +204 -204
  437. data/lib/resources/rabbitmq_conf.rb +52 -52
  438. data/lib/resources/registry_key.rb +296 -296
  439. data/lib/resources/security_policy.rb +180 -180
  440. data/lib/resources/service.rb +789 -789
  441. data/lib/resources/shadow.rb +140 -140
  442. data/lib/resources/ssh_conf.rb +102 -102
  443. data/lib/resources/ssl.rb +99 -99
  444. data/lib/resources/sys_info.rb +28 -28
  445. data/lib/resources/toml.rb +32 -32
  446. data/lib/resources/users.rb +654 -654
  447. data/lib/resources/vbscript.rb +69 -69
  448. data/lib/resources/virtualization.rb +251 -251
  449. data/lib/resources/windows_feature.rb +84 -84
  450. data/lib/resources/windows_hotfix.rb +35 -35
  451. data/lib/resources/windows_task.rb +105 -105
  452. data/lib/resources/wmi.rb +113 -113
  453. data/lib/resources/x509_certificate.rb +143 -143
  454. data/lib/resources/xinetd.rb +111 -111
  455. data/lib/resources/xml.rb +46 -46
  456. data/lib/resources/yaml.rb +47 -47
  457. data/lib/resources/yum.rb +180 -180
  458. data/lib/resources/zfs_dataset.rb +60 -60
  459. data/lib/resources/zfs_pool.rb +49 -49
  460. data/lib/source_readers/flat.rb +39 -39
  461. data/lib/source_readers/inspec.rb +75 -75
  462. data/lib/utils/command_wrapper.rb +27 -27
  463. data/lib/utils/convert.rb +12 -12
  464. data/lib/utils/database_helpers.rb +77 -77
  465. data/lib/utils/erlang_parser.rb +192 -192
  466. data/lib/utils/filter.rb +272 -272
  467. data/lib/utils/filter_array.rb +27 -27
  468. data/lib/utils/find_files.rb +44 -44
  469. data/lib/utils/hash.rb +41 -41
  470. data/lib/utils/json_log.rb +18 -18
  471. data/lib/utils/latest_version.rb +22 -22
  472. data/lib/utils/modulator.rb +12 -12
  473. data/lib/utils/nginx_parser.rb +85 -85
  474. data/lib/utils/object_traversal.rb +49 -49
  475. data/lib/utils/parser.rb +274 -274
  476. data/lib/utils/plugin_registry.rb +93 -93
  477. data/lib/utils/simpleconfig.rb +120 -120
  478. data/lib/utils/spdx.rb +13 -13
  479. data/lib/utils/spdx.txt +343 -343
  480. metadata +1 -1
@@ -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