inspec 2.1.0 → 2.1.10

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