inspec 2.1.21 → 2.1.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (502) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +101 -101
  3. data/CHANGELOG.md +3062 -3045
  4. data/Gemfile +56 -56
  5. data/LICENSE +14 -14
  6. data/MAINTAINERS.md +33 -33
  7. data/MAINTAINERS.toml +52 -52
  8. data/README.md +447 -447
  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 -100
  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_delivery_channel.md +79 -79
  36. data/docs/resources/aws_config_recorder.md.erb +71 -71
  37. data/docs/resources/aws_ec2_instance.md.erb +106 -106
  38. data/docs/resources/aws_iam_access_key.md.erb +123 -123
  39. data/docs/resources/aws_iam_access_keys.md.erb +198 -198
  40. data/docs/resources/aws_iam_group.md.erb +46 -46
  41. data/docs/resources/aws_iam_groups.md.erb +43 -43
  42. data/docs/resources/aws_iam_password_policy.md.erb +76 -76
  43. data/docs/resources/aws_iam_policies.md.erb +82 -82
  44. data/docs/resources/aws_iam_policy.md.erb +144 -144
  45. data/docs/resources/aws_iam_role.md.erb +63 -63
  46. data/docs/resources/aws_iam_root_user.md.erb +70 -58
  47. data/docs/resources/aws_iam_user.md.erb +64 -64
  48. data/docs/resources/aws_iam_users.md.erb +89 -89
  49. data/docs/resources/aws_kms_key.md.erb +171 -171
  50. data/docs/resources/aws_kms_keys.md.erb +84 -84
  51. data/docs/resources/aws_rds_instance.md.erb +60 -60
  52. data/docs/resources/aws_route_table.md.erb +47 -47
  53. data/docs/resources/aws_route_tables.md.erb +49 -0
  54. data/docs/resources/aws_s3_bucket.md.erb +134 -134
  55. data/docs/resources/aws_s3_bucket_object.md.erb +83 -83
  56. data/docs/resources/aws_s3_buckets.md.erb +53 -0
  57. data/docs/resources/aws_security_group.md.erb +151 -151
  58. data/docs/resources/aws_security_groups.md.erb +91 -91
  59. data/docs/resources/aws_sns_subscription.md.erb +124 -124
  60. data/docs/resources/aws_sns_topic.md.erb +63 -63
  61. data/docs/resources/aws_sns_topics.md.erb +52 -52
  62. data/docs/resources/aws_subnet.md.erb +134 -134
  63. data/docs/resources/aws_subnets.md.erb +126 -126
  64. data/docs/resources/aws_vpc.md.erb +120 -120
  65. data/docs/resources/aws_vpcs.md.erb +48 -48
  66. data/docs/resources/azure_generic_resource.md.erb +171 -171
  67. data/docs/resources/azure_resource_group.md.erb +284 -284
  68. data/docs/resources/azure_virtual_machine.md.erb +347 -347
  69. data/docs/resources/azure_virtual_machine_data_disk.md.erb +224 -224
  70. data/docs/resources/bash.md.erb +75 -75
  71. data/docs/resources/bond.md.erb +90 -90
  72. data/docs/resources/bridge.md.erb +57 -57
  73. data/docs/resources/bsd_service.md.erb +67 -67
  74. data/docs/resources/chocolatey_package.md.erb +58 -0
  75. data/docs/resources/command.md.erb +138 -138
  76. data/docs/resources/cpan.md.erb +79 -79
  77. data/docs/resources/cran.md.erb +64 -64
  78. data/docs/resources/crontab.md.erb +89 -89
  79. data/docs/resources/csv.md.erb +54 -54
  80. data/docs/resources/dh_params.md.erb +205 -205
  81. data/docs/resources/directory.md.erb +30 -30
  82. data/docs/resources/docker.md.erb +219 -219
  83. data/docs/resources/docker_container.md.erb +103 -103
  84. data/docs/resources/docker_image.md.erb +94 -94
  85. data/docs/resources/docker_service.md.erb +114 -114
  86. data/docs/resources/elasticsearch.md.erb +242 -242
  87. data/docs/resources/etc_fstab.md.erb +125 -125
  88. data/docs/resources/etc_group.md.erb +75 -75
  89. data/docs/resources/etc_hosts.md.erb +78 -78
  90. data/docs/resources/etc_hosts_allow.md.erb +74 -74
  91. data/docs/resources/etc_hosts_deny.md.erb +74 -74
  92. data/docs/resources/file.md.erb +526 -526
  93. data/docs/resources/filesystem.md.erb +41 -41
  94. data/docs/resources/firewalld.md.erb +107 -107
  95. data/docs/resources/gem.md.erb +79 -79
  96. data/docs/resources/group.md.erb +61 -61
  97. data/docs/resources/grub_conf.md.erb +101 -101
  98. data/docs/resources/host.md.erb +86 -86
  99. data/docs/resources/http.md.erb +196 -196
  100. data/docs/resources/iis_app.md.erb +122 -122
  101. data/docs/resources/iis_site.md.erb +135 -135
  102. data/docs/resources/inetd_conf.md.erb +94 -94
  103. data/docs/resources/ini.md.erb +76 -76
  104. data/docs/resources/interface.md.erb +58 -58
  105. data/docs/resources/iptables.md.erb +64 -64
  106. data/docs/resources/json.md.erb +63 -63
  107. data/docs/resources/kernel_module.md.erb +120 -120
  108. data/docs/resources/kernel_parameter.md.erb +53 -53
  109. data/docs/resources/key_rsa.md.erb +85 -85
  110. data/docs/resources/launchd_service.md.erb +57 -57
  111. data/docs/resources/limits_conf.md.erb +75 -75
  112. data/docs/resources/login_defs.md.erb +71 -71
  113. data/docs/resources/mount.md.erb +69 -69
  114. data/docs/resources/mssql_session.md.erb +60 -60
  115. data/docs/resources/mysql_conf.md.erb +99 -99
  116. data/docs/resources/mysql_session.md.erb +74 -74
  117. data/docs/resources/nginx.md.erb +79 -79
  118. data/docs/resources/nginx_conf.md.erb +138 -138
  119. data/docs/resources/npm.md.erb +60 -60
  120. data/docs/resources/ntp_conf.md.erb +60 -60
  121. data/docs/resources/oneget.md.erb +53 -53
  122. data/docs/resources/oracledb_session.md.erb +52 -52
  123. data/docs/resources/os.md.erb +141 -141
  124. data/docs/resources/os_env.md.erb +78 -78
  125. data/docs/resources/package.md.erb +120 -120
  126. data/docs/resources/packages.md.erb +67 -67
  127. data/docs/resources/parse_config.md.erb +103 -103
  128. data/docs/resources/parse_config_file.md.erb +138 -138
  129. data/docs/resources/passwd.md.erb +141 -141
  130. data/docs/resources/pip.md.erb +67 -67
  131. data/docs/resources/port.md.erb +137 -137
  132. data/docs/resources/postgres_conf.md.erb +79 -79
  133. data/docs/resources/postgres_hba_conf.md.erb +93 -93
  134. data/docs/resources/postgres_ident_conf.md.erb +76 -76
  135. data/docs/resources/postgres_session.md.erb +69 -69
  136. data/docs/resources/powershell.md.erb +102 -102
  137. data/docs/resources/processes.md.erb +109 -109
  138. data/docs/resources/rabbitmq_config.md.erb +41 -41
  139. data/docs/resources/registry_key.md.erb +158 -158
  140. data/docs/resources/runit_service.md.erb +57 -57
  141. data/docs/resources/security_policy.md.erb +47 -47
  142. data/docs/resources/service.md.erb +121 -121
  143. data/docs/resources/shadow.md.erb +146 -146
  144. data/docs/resources/ssh_config.md.erb +73 -73
  145. data/docs/resources/sshd_config.md.erb +83 -83
  146. data/docs/resources/ssl.md.erb +119 -119
  147. data/docs/resources/sys_info.md.erb +42 -42
  148. data/docs/resources/systemd_service.md.erb +57 -57
  149. data/docs/resources/sysv_service.md.erb +57 -57
  150. data/docs/resources/upstart_service.md.erb +57 -57
  151. data/docs/resources/user.md.erb +140 -140
  152. data/docs/resources/users.md.erb +127 -127
  153. data/docs/resources/vbscript.md.erb +55 -55
  154. data/docs/resources/virtualization.md.erb +57 -57
  155. data/docs/resources/windows_feature.md.erb +47 -47
  156. data/docs/resources/windows_hotfix.md.erb +53 -53
  157. data/docs/resources/windows_task.md.erb +95 -95
  158. data/docs/resources/wmi.md.erb +81 -81
  159. data/docs/resources/x509_certificate.md.erb +151 -151
  160. data/docs/resources/xinetd_conf.md.erb +156 -156
  161. data/docs/resources/xml.md.erb +85 -85
  162. data/docs/resources/yaml.md.erb +69 -69
  163. data/docs/resources/yum.md.erb +98 -98
  164. data/docs/resources/zfs_dataset.md.erb +53 -53
  165. data/docs/resources/zfs_pool.md.erb +47 -47
  166. data/docs/ruby_usage.md +203 -203
  167. data/docs/shared/matcher_be.md.erb +1 -1
  168. data/docs/shared/matcher_cmp.md.erb +43 -43
  169. data/docs/shared/matcher_eq.md.erb +3 -3
  170. data/docs/shared/matcher_include.md.erb +1 -1
  171. data/docs/shared/matcher_match.md.erb +1 -1
  172. data/docs/shell.md +217 -217
  173. data/examples/README.md +8 -8
  174. data/examples/inheritance/README.md +65 -65
  175. data/examples/inheritance/controls/example.rb +14 -14
  176. data/examples/inheritance/inspec.yml +15 -15
  177. data/examples/kitchen-ansible/.kitchen.yml +25 -25
  178. data/examples/kitchen-ansible/Gemfile +19 -19
  179. data/examples/kitchen-ansible/README.md +53 -53
  180. data/examples/kitchen-ansible/files/nginx.repo +6 -6
  181. data/examples/kitchen-ansible/tasks/main.yml +16 -16
  182. data/examples/kitchen-ansible/test/integration/default/default.yml +5 -5
  183. data/examples/kitchen-ansible/test/integration/default/web_spec.rb +28 -28
  184. data/examples/kitchen-chef/.kitchen.yml +20 -20
  185. data/examples/kitchen-chef/Berksfile +3 -3
  186. data/examples/kitchen-chef/Gemfile +19 -19
  187. data/examples/kitchen-chef/README.md +27 -27
  188. data/examples/kitchen-chef/metadata.rb +7 -7
  189. data/examples/kitchen-chef/recipes/default.rb +6 -6
  190. data/examples/kitchen-chef/recipes/nginx.rb +30 -30
  191. data/examples/kitchen-chef/test/integration/default/web_spec.rb +28 -28
  192. data/examples/kitchen-puppet/.kitchen.yml +22 -22
  193. data/examples/kitchen-puppet/Gemfile +20 -20
  194. data/examples/kitchen-puppet/Puppetfile +25 -25
  195. data/examples/kitchen-puppet/README.md +53 -53
  196. data/examples/kitchen-puppet/manifests/site.pp +33 -33
  197. data/examples/kitchen-puppet/metadata.json +11 -11
  198. data/examples/kitchen-puppet/test/integration/default/web_spec.rb +28 -28
  199. data/examples/meta-profile/README.md +37 -37
  200. data/examples/meta-profile/controls/example.rb +13 -13
  201. data/examples/meta-profile/inspec.yml +13 -13
  202. data/examples/profile-attribute.yml +2 -2
  203. data/examples/profile-attribute/README.md +14 -14
  204. data/examples/profile-attribute/controls/example.rb +11 -11
  205. data/examples/profile-attribute/inspec.yml +8 -8
  206. data/examples/profile-aws/controls/iam_password_policy_expiration.rb +8 -8
  207. data/examples/profile-aws/controls/iam_password_policy_max_age.rb +8 -8
  208. data/examples/profile-aws/controls/iam_root_user_mfa.rb +8 -8
  209. data/examples/profile-aws/controls/iam_users_access_key_age.rb +8 -8
  210. data/examples/profile-aws/controls/iam_users_console_users_mfa.rb +8 -8
  211. data/examples/profile-aws/inspec.yml +11 -11
  212. data/examples/profile-azure/controls/azure_resource_group_example.rb +24 -24
  213. data/examples/profile-azure/controls/azure_vm_example.rb +29 -29
  214. data/examples/profile-azure/inspec.yml +11 -11
  215. data/examples/profile-sensitive/README.md +29 -29
  216. data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -9
  217. data/examples/profile-sensitive/controls/sensitive.rb +9 -9
  218. data/examples/profile-sensitive/inspec.yml +8 -8
  219. data/examples/profile/README.md +48 -48
  220. data/examples/profile/controls/example.rb +23 -23
  221. data/examples/profile/controls/gordon.rb +36 -36
  222. data/examples/profile/controls/meta.rb +34 -34
  223. data/examples/profile/inspec.yml +10 -10
  224. data/examples/profile/libraries/gordon_config.rb +59 -53
  225. data/inspec.gemspec +47 -47
  226. data/lib/bundles/README.md +3 -3
  227. data/lib/bundles/inspec-artifact.rb +7 -7
  228. data/lib/bundles/inspec-artifact/README.md +1 -1
  229. data/lib/bundles/inspec-artifact/cli.rb +277 -277
  230. data/lib/bundles/inspec-compliance.rb +16 -16
  231. data/lib/bundles/inspec-compliance/.kitchen.yml +20 -20
  232. data/lib/bundles/inspec-compliance/README.md +185 -185
  233. data/lib/bundles/inspec-compliance/api.rb +316 -316
  234. data/lib/bundles/inspec-compliance/api/login.rb +152 -152
  235. data/lib/bundles/inspec-compliance/bootstrap.sh +41 -41
  236. data/lib/bundles/inspec-compliance/cli.rb +254 -254
  237. data/lib/bundles/inspec-compliance/configuration.rb +103 -103
  238. data/lib/bundles/inspec-compliance/http.rb +86 -86
  239. data/lib/bundles/inspec-compliance/support.rb +36 -36
  240. data/lib/bundles/inspec-compliance/target.rb +98 -98
  241. data/lib/bundles/inspec-compliance/test/integration/default/cli.rb +93 -93
  242. data/lib/bundles/inspec-habitat.rb +12 -12
  243. data/lib/bundles/inspec-habitat/cli.rb +36 -36
  244. data/lib/bundles/inspec-habitat/log.rb +10 -10
  245. data/lib/bundles/inspec-habitat/profile.rb +391 -391
  246. data/lib/bundles/inspec-init.rb +8 -8
  247. data/lib/bundles/inspec-init/README.md +31 -31
  248. data/lib/bundles/inspec-init/cli.rb +97 -97
  249. data/lib/bundles/inspec-init/templates/profile/README.md +3 -3
  250. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +19 -19
  251. data/lib/bundles/inspec-init/templates/profile/inspec.yml +8 -8
  252. data/lib/bundles/inspec-supermarket.rb +13 -13
  253. data/lib/bundles/inspec-supermarket/README.md +45 -45
  254. data/lib/bundles/inspec-supermarket/api.rb +84 -84
  255. data/lib/bundles/inspec-supermarket/cli.rb +73 -73
  256. data/lib/bundles/inspec-supermarket/target.rb +34 -34
  257. data/lib/fetchers/git.rb +163 -163
  258. data/lib/fetchers/local.rb +74 -74
  259. data/lib/fetchers/mock.rb +35 -35
  260. data/lib/fetchers/url.rb +204 -204
  261. data/lib/inspec.rb +24 -24
  262. data/lib/inspec/archive/tar.rb +29 -29
  263. data/lib/inspec/archive/zip.rb +19 -19
  264. data/lib/inspec/backend.rb +93 -93
  265. data/lib/inspec/base_cli.rb +363 -357
  266. data/lib/inspec/cached_fetcher.rb +66 -66
  267. data/lib/inspec/cli.rb +292 -292
  268. data/lib/inspec/completions/bash.sh.erb +45 -45
  269. data/lib/inspec/completions/fish.sh.erb +34 -34
  270. data/lib/inspec/completions/zsh.sh.erb +61 -61
  271. data/lib/inspec/control_eval_context.rb +179 -179
  272. data/lib/inspec/dependencies/cache.rb +72 -72
  273. data/lib/inspec/dependencies/dependency_set.rb +92 -92
  274. data/lib/inspec/dependencies/lockfile.rb +115 -115
  275. data/lib/inspec/dependencies/requirement.rb +123 -123
  276. data/lib/inspec/dependencies/resolver.rb +86 -86
  277. data/lib/inspec/describe.rb +27 -27
  278. data/lib/inspec/dsl.rb +66 -66
  279. data/lib/inspec/dsl_shared.rb +33 -33
  280. data/lib/inspec/env_printer.rb +157 -157
  281. data/lib/inspec/errors.rb +14 -13
  282. data/lib/inspec/exceptions.rb +12 -12
  283. data/lib/inspec/expect.rb +45 -45
  284. data/lib/inspec/fetcher.rb +45 -45
  285. data/lib/inspec/file_provider.rb +275 -275
  286. data/lib/inspec/formatters.rb +3 -3
  287. data/lib/inspec/formatters/base.rb +259 -250
  288. data/lib/inspec/formatters/json_rspec.rb +20 -20
  289. data/lib/inspec/formatters/show_progress.rb +12 -12
  290. data/lib/inspec/library_eval_context.rb +58 -58
  291. data/lib/inspec/log.rb +11 -11
  292. data/lib/inspec/metadata.rb +247 -247
  293. data/lib/inspec/method_source.rb +24 -24
  294. data/lib/inspec/objects.rb +14 -14
  295. data/lib/inspec/objects/attribute.rb +65 -65
  296. data/lib/inspec/objects/control.rb +61 -61
  297. data/lib/inspec/objects/describe.rb +92 -92
  298. data/lib/inspec/objects/each_loop.rb +36 -36
  299. data/lib/inspec/objects/list.rb +15 -15
  300. data/lib/inspec/objects/or_test.rb +40 -40
  301. data/lib/inspec/objects/ruby_helper.rb +15 -15
  302. data/lib/inspec/objects/tag.rb +27 -27
  303. data/lib/inspec/objects/test.rb +87 -87
  304. data/lib/inspec/objects/value.rb +27 -27
  305. data/lib/inspec/plugins.rb +60 -60
  306. data/lib/inspec/plugins/cli.rb +24 -24
  307. data/lib/inspec/plugins/fetcher.rb +86 -86
  308. data/lib/inspec/plugins/resource.rb +135 -135
  309. data/lib/inspec/plugins/secret.rb +15 -15
  310. data/lib/inspec/plugins/source_reader.rb +40 -40
  311. data/lib/inspec/polyfill.rb +12 -12
  312. data/lib/inspec/profile.rb +513 -513
  313. data/lib/inspec/profile_context.rb +208 -208
  314. data/lib/inspec/profile_vendor.rb +66 -66
  315. data/lib/inspec/reporters.rb +60 -54
  316. data/lib/inspec/reporters/automate.rb +76 -0
  317. data/lib/inspec/reporters/base.rb +25 -24
  318. data/lib/inspec/reporters/cli.rb +356 -356
  319. data/lib/inspec/reporters/json.rb +116 -116
  320. data/lib/inspec/reporters/json_min.rb +48 -48
  321. data/lib/inspec/reporters/junit.rb +77 -77
  322. data/lib/inspec/require_loader.rb +33 -33
  323. data/lib/inspec/resource.rb +187 -186
  324. data/lib/inspec/rule.rb +266 -266
  325. data/lib/inspec/runner.rb +345 -345
  326. data/lib/inspec/runner_mock.rb +41 -41
  327. data/lib/inspec/runner_rspec.rb +175 -175
  328. data/lib/inspec/runtime_profile.rb +26 -26
  329. data/lib/inspec/schema.rb +213 -213
  330. data/lib/inspec/secrets.rb +19 -19
  331. data/lib/inspec/secrets/yaml.rb +30 -30
  332. data/lib/inspec/shell.rb +220 -220
  333. data/lib/inspec/shell_detector.rb +90 -90
  334. data/lib/inspec/source_reader.rb +29 -29
  335. data/lib/inspec/version.rb +8 -8
  336. data/lib/matchers/matchers.rb +339 -339
  337. data/lib/resource_support/aws.rb +49 -47
  338. data/lib/resource_support/aws/aws_backend_base.rb +12 -12
  339. data/lib/resource_support/aws/aws_backend_factory_mixin.rb +12 -12
  340. data/lib/resource_support/aws/aws_plural_resource_mixin.rb +21 -21
  341. data/lib/resource_support/aws/aws_resource_mixin.rb +66 -66
  342. data/lib/resource_support/aws/aws_singular_resource_mixin.rb +24 -24
  343. data/lib/resources/aide_conf.rb +151 -151
  344. data/lib/resources/apache.rb +48 -48
  345. data/lib/resources/apache_conf.rb +149 -149
  346. data/lib/resources/apt.rb +149 -149
  347. data/lib/resources/audit_policy.rb +63 -63
  348. data/lib/resources/auditd.rb +231 -231
  349. data/lib/resources/auditd_conf.rb +46 -46
  350. data/lib/resources/aws/aws_cloudtrail_trail.rb +77 -77
  351. data/lib/resources/aws/aws_cloudtrail_trails.rb +47 -47
  352. data/lib/resources/aws/aws_cloudwatch_alarm.rb +62 -62
  353. data/lib/resources/aws/aws_cloudwatch_log_metric_filter.rb +100 -100
  354. data/lib/resources/aws/aws_config_delivery_channel.rb +76 -76
  355. data/lib/resources/aws/aws_config_recorder.rb +98 -98
  356. data/lib/resources/aws/aws_ec2_instance.rb +157 -157
  357. data/lib/resources/aws/aws_iam_access_key.rb +106 -106
  358. data/lib/resources/aws/aws_iam_access_keys.rb +149 -149
  359. data/lib/resources/aws/aws_iam_group.rb +56 -56
  360. data/lib/resources/aws/aws_iam_groups.rb +52 -52
  361. data/lib/resources/aws/aws_iam_password_policy.rb +116 -116
  362. data/lib/resources/aws/aws_iam_policies.rb +53 -53
  363. data/lib/resources/aws/aws_iam_policy.rb +125 -125
  364. data/lib/resources/aws/aws_iam_role.rb +51 -51
  365. data/lib/resources/aws/aws_iam_root_user.rb +78 -60
  366. data/lib/resources/aws/aws_iam_user.rb +111 -111
  367. data/lib/resources/aws/aws_iam_users.rb +108 -108
  368. data/lib/resources/aws/aws_kms_key.rb +96 -96
  369. data/lib/resources/aws/aws_kms_keys.rb +53 -53
  370. data/lib/resources/aws/aws_rds_instance.rb +71 -71
  371. data/lib/resources/aws/aws_route_table.rb +63 -63
  372. data/lib/resources/aws/aws_route_tables.rb +60 -0
  373. data/lib/resources/aws/aws_s3_bucket.rb +115 -115
  374. data/lib/resources/aws/aws_s3_bucket_object.rb +82 -82
  375. data/lib/resources/aws/aws_s3_buckets.rb +51 -0
  376. data/lib/resources/aws/aws_security_group.rb +93 -93
  377. data/lib/resources/aws/aws_security_groups.rb +68 -68
  378. data/lib/resources/aws/aws_sns_subscription.rb +78 -78
  379. data/lib/resources/aws/aws_sns_topic.rb +53 -53
  380. data/lib/resources/aws/aws_sns_topics.rb +56 -56
  381. data/lib/resources/aws/aws_subnet.rb +88 -88
  382. data/lib/resources/aws/aws_subnets.rb +53 -53
  383. data/lib/resources/aws/aws_vpc.rb +69 -69
  384. data/lib/resources/aws/aws_vpcs.rb +45 -45
  385. data/lib/resources/azure/azure_backend.rb +377 -377
  386. data/lib/resources/azure/azure_generic_resource.rb +59 -59
  387. data/lib/resources/azure/azure_resource_group.rb +152 -152
  388. data/lib/resources/azure/azure_virtual_machine.rb +264 -264
  389. data/lib/resources/azure/azure_virtual_machine_data_disk.rb +134 -134
  390. data/lib/resources/bash.rb +35 -35
  391. data/lib/resources/bond.rb +69 -69
  392. data/lib/resources/bridge.rb +122 -122
  393. data/lib/resources/chocolatey_package.rb +78 -0
  394. data/lib/resources/command.rb +73 -73
  395. data/lib/resources/cpan.rb +58 -58
  396. data/lib/resources/cran.rb +64 -64
  397. data/lib/resources/crontab.rb +169 -169
  398. data/lib/resources/csv.rb +56 -56
  399. data/lib/resources/dh_params.rb +77 -77
  400. data/lib/resources/directory.rb +25 -25
  401. data/lib/resources/docker.rb +236 -236
  402. data/lib/resources/docker_container.rb +89 -89
  403. data/lib/resources/docker_image.rb +83 -83
  404. data/lib/resources/docker_object.rb +57 -57
  405. data/lib/resources/docker_service.rb +90 -90
  406. data/lib/resources/elasticsearch.rb +169 -169
  407. data/lib/resources/etc_fstab.rb +94 -94
  408. data/lib/resources/etc_group.rb +152 -152
  409. data/lib/resources/etc_hosts.rb +66 -66
  410. data/lib/resources/etc_hosts_allow_deny.rb +112 -112
  411. data/lib/resources/file.rb +298 -298
  412. data/lib/resources/filesystem.rb +31 -31
  413. data/lib/resources/firewalld.rb +143 -143
  414. data/lib/resources/gem.rb +70 -70
  415. data/lib/resources/groups.rb +215 -215
  416. data/lib/resources/grub_conf.rb +227 -227
  417. data/lib/resources/host.rb +306 -306
  418. data/lib/resources/http.rb +253 -253
  419. data/lib/resources/iis_app.rb +101 -101
  420. data/lib/resources/iis_site.rb +148 -148
  421. data/lib/resources/inetd_conf.rb +54 -54
  422. data/lib/resources/ini.rb +29 -29
  423. data/lib/resources/interface.rb +129 -129
  424. data/lib/resources/iptables.rb +80 -80
  425. data/lib/resources/json.rb +107 -107
  426. data/lib/resources/kernel_module.rb +107 -107
  427. data/lib/resources/kernel_parameter.rb +58 -58
  428. data/lib/resources/key_rsa.rb +61 -61
  429. data/lib/resources/limits_conf.rb +46 -46
  430. data/lib/resources/login_def.rb +57 -57
  431. data/lib/resources/mount.rb +88 -88
  432. data/lib/resources/mssql_session.rb +101 -101
  433. data/lib/resources/mysql.rb +82 -81
  434. data/lib/resources/mysql_conf.rb +127 -127
  435. data/lib/resources/mysql_session.rb +85 -85
  436. data/lib/resources/nginx.rb +96 -96
  437. data/lib/resources/nginx_conf.rb +226 -226
  438. data/lib/resources/npm.rb +48 -48
  439. data/lib/resources/ntp_conf.rb +51 -51
  440. data/lib/resources/oneget.rb +71 -71
  441. data/lib/resources/oracledb_session.rb +139 -139
  442. data/lib/resources/os.rb +36 -36
  443. data/lib/resources/os_env.rb +75 -75
  444. data/lib/resources/package.rb +370 -370
  445. data/lib/resources/packages.rb +111 -111
  446. data/lib/resources/parse_config.rb +112 -112
  447. data/lib/resources/passwd.rb +76 -76
  448. data/lib/resources/pip.rb +130 -130
  449. data/lib/resources/platform.rb +109 -109
  450. data/lib/resources/port.rb +771 -771
  451. data/lib/resources/postgres.rb +131 -130
  452. data/lib/resources/postgres_conf.rb +114 -114
  453. data/lib/resources/postgres_hba_conf.rb +90 -90
  454. data/lib/resources/postgres_ident_conf.rb +79 -79
  455. data/lib/resources/postgres_session.rb +71 -71
  456. data/lib/resources/powershell.rb +66 -66
  457. data/lib/resources/processes.rb +204 -204
  458. data/lib/resources/rabbitmq_conf.rb +51 -51
  459. data/lib/resources/registry_key.rb +297 -297
  460. data/lib/resources/security_policy.rb +180 -180
  461. data/lib/resources/service.rb +794 -790
  462. data/lib/resources/shadow.rb +149 -149
  463. data/lib/resources/ssh_conf.rb +97 -97
  464. data/lib/resources/ssl.rb +99 -99
  465. data/lib/resources/sys_info.rb +28 -28
  466. data/lib/resources/toml.rb +32 -32
  467. data/lib/resources/users.rb +654 -654
  468. data/lib/resources/vbscript.rb +68 -68
  469. data/lib/resources/virtualization.rb +247 -247
  470. data/lib/resources/windows_feature.rb +84 -84
  471. data/lib/resources/windows_hotfix.rb +35 -35
  472. data/lib/resources/windows_task.rb +102 -102
  473. data/lib/resources/wmi.rb +110 -110
  474. data/lib/resources/x509_certificate.rb +137 -137
  475. data/lib/resources/xinetd.rb +106 -106
  476. data/lib/resources/xml.rb +46 -46
  477. data/lib/resources/yaml.rb +43 -43
  478. data/lib/resources/yum.rb +180 -180
  479. data/lib/resources/zfs_dataset.rb +60 -60
  480. data/lib/resources/zfs_pool.rb +49 -49
  481. data/lib/source_readers/flat.rb +39 -39
  482. data/lib/source_readers/inspec.rb +75 -75
  483. data/lib/utils/command_wrapper.rb +27 -27
  484. data/lib/utils/convert.rb +12 -12
  485. data/lib/utils/database_helpers.rb +77 -77
  486. data/lib/utils/erlang_parser.rb +192 -192
  487. data/lib/utils/file_reader.rb +25 -25
  488. data/lib/utils/filter.rb +273 -273
  489. data/lib/utils/filter_array.rb +27 -27
  490. data/lib/utils/find_files.rb +44 -44
  491. data/lib/utils/hash.rb +41 -41
  492. data/lib/utils/json_log.rb +18 -18
  493. data/lib/utils/latest_version.rb +22 -22
  494. data/lib/utils/modulator.rb +12 -12
  495. data/lib/utils/nginx_parser.rb +85 -85
  496. data/lib/utils/object_traversal.rb +49 -49
  497. data/lib/utils/parser.rb +274 -274
  498. data/lib/utils/plugin_registry.rb +93 -93
  499. data/lib/utils/simpleconfig.rb +120 -120
  500. data/lib/utils/spdx.rb +13 -13
  501. data/lib/utils/spdx.txt +343 -343
  502. metadata +9 -2
@@ -1,15 +1,15 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'utils/plugin_registry'
6
-
7
- module Inspec
8
- module Plugins
9
- class Secret < PluginRegistry::Plugin
10
- def self.plugin_registry
11
- Inspec::SecretsBackend
12
- end
13
- end
14
- end
15
- end
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ require 'utils/plugin_registry'
6
+
7
+ module Inspec
8
+ module Plugins
9
+ class Secret < PluginRegistry::Plugin
10
+ def self.plugin_registry
11
+ Inspec::SecretsBackend
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,40 +1,40 @@
1
- # encoding: utf-8
2
- # author: Dominik Richter
3
- # author: Christoph Hartmann
4
-
5
- require 'utils/plugin_registry'
6
-
7
- module Inspec
8
- module Plugins
9
- class SourceReader < PluginRegistry::Plugin
10
- def self.plugin_registry
11
- Inspec::SourceReader
12
- end
13
-
14
- # Retrieve this profile's metadata.
15
- #
16
- # @return [Inspec::Metadata] profile metadata
17
- def metadata
18
- raise "SourceReader #{self} does not implement `metadata()`. This method is required"
19
- end
20
-
21
- # Retrieve this profile's tests
22
- #
23
- # "tests" here refers to a test file. Individual controls and anonymous
24
- # tests are later extracted from the raw contents of a test file. The map
25
- # her simply maps from a test file name to the file contents.
26
- #
27
- # @return [Hash] Collection with references pointing to test contents
28
- def tests
29
- raise "SourceReader #{self} does not implement `tests()`. This method is required"
30
- end
31
-
32
- # Retrieve this profile's libraries
33
- #
34
- # @return [Hash] Collection with references pointing to library contents
35
- def libraries
36
- raise "SourceReader #{self} does not implement `libraries()`. This method is required"
37
- end
38
- end
39
- end
40
- end
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ require 'utils/plugin_registry'
6
+
7
+ module Inspec
8
+ module Plugins
9
+ class SourceReader < PluginRegistry::Plugin
10
+ def self.plugin_registry
11
+ Inspec::SourceReader
12
+ end
13
+
14
+ # Retrieve this profile's metadata.
15
+ #
16
+ # @return [Inspec::Metadata] profile metadata
17
+ def metadata
18
+ raise "SourceReader #{self} does not implement `metadata()`. This method is required"
19
+ end
20
+
21
+ # Retrieve this profile's tests
22
+ #
23
+ # "tests" here refers to a test file. Individual controls and anonymous
24
+ # tests are later extracted from the raw contents of a test file. The map
25
+ # her simply maps from a test file name to the file contents.
26
+ #
27
+ # @return [Hash] Collection with references pointing to test contents
28
+ def tests
29
+ raise "SourceReader #{self} does not implement `tests()`. This method is required"
30
+ end
31
+
32
+ # Retrieve this profile's libraries
33
+ #
34
+ # @return [Hash] Collection with references pointing to library contents
35
+ def libraries
36
+ raise "SourceReader #{self} does not implement `libraries()`. This method is required"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,12 +1,12 @@
1
- # encoding: utf-8
2
- # copyright: 2016, Chef Software Inc.
3
- # author: Dominik Richter
4
- # author: Christoph Hartmann
5
-
6
- class Struct
7
- unless instance_methods.include? :to_h
8
- def to_h
9
- Hash[each_pair.to_a]
10
- end
11
- end
12
- end
1
+ # encoding: utf-8
2
+ # copyright: 2016, Chef Software Inc.
3
+ # author: Dominik Richter
4
+ # author: Christoph Hartmann
5
+
6
+ class Struct
7
+ unless instance_methods.include? :to_h
8
+ def to_h
9
+ Hash[each_pair.to_a]
10
+ end
11
+ end
12
+ end
@@ -1,513 +1,513 @@
1
- # encoding: utf-8
2
- # Copyright 2015 Dominik Richter
3
- # author: Dominik Richter
4
- # author: Christoph Hartmann
5
-
6
- require 'forwardable'
7
- require 'openssl'
8
- require 'inspec/polyfill'
9
- require 'inspec/cached_fetcher'
10
- require 'inspec/file_provider'
11
- require 'inspec/source_reader'
12
- require 'inspec/metadata'
13
- require 'inspec/backend'
14
- require 'inspec/rule'
15
- require 'inspec/log'
16
- require 'inspec/profile_context'
17
- require 'inspec/runtime_profile'
18
- require 'inspec/method_source'
19
- require 'inspec/dependencies/cache'
20
- require 'inspec/dependencies/lockfile'
21
- require 'inspec/dependencies/dependency_set'
22
-
23
- module Inspec
24
- class Profile
25
- extend Forwardable
26
-
27
- def self.resolve_target(target, cache)
28
- Inspec::Log.debug "Resolve #{target} into cache #{cache.path}"
29
- Inspec::CachedFetcher.new(target, cache)
30
- end
31
-
32
- # Check if the profile contains a vendored cache, move content into global cache
33
- # TODO: use relative file provider
34
- # TODO: use source reader for Cache as well
35
- def self.copy_deps_into_cache(file_provider, opts)
36
- # filter content
37
- cache = file_provider.files.find_all do |entry|
38
- entry.start_with?('vendor')
39
- end
40
- content = Hash[cache.map { |x| [x, file_provider.binread(x)] }]
41
- keys = content.keys
42
- keys.each do |key|
43
- next if content[key].nil?
44
- # remove prefix
45
- rel = Pathname.new(key).relative_path_from(Pathname.new('vendor')).to_s
46
- tar = Pathname.new(opts[:vendor_cache].path).join(rel)
47
-
48
- FileUtils.mkdir_p tar.dirname.to_s
49
- Inspec::Log.debug "Copy #{tar} to cache directory"
50
- File.binwrite(tar.to_s, content[key])
51
- end
52
- end
53
-
54
- def self.for_path(path, opts)
55
- file_provider = FileProvider.for_path(path)
56
- rp = file_provider.relative_provider
57
-
58
- # copy embedded dependecies into global cache
59
- copy_deps_into_cache(rp, opts) unless opts[:vendor_cache].nil?
60
-
61
- reader = Inspec::SourceReader.resolve(rp)
62
- if reader.nil?
63
- raise("Don't understand inspec profile in #{path}, it " \
64
- "doesn't look like a supported profile structure.")
65
- end
66
- new(reader, opts)
67
- end
68
-
69
- def self.for_fetcher(fetcher, opts)
70
- opts[:vendor_cache] = opts[:vendor_cache] || Cache.new
71
- path, writable = fetcher.fetch
72
- for_path(path, opts.merge(target: fetcher.target, writable: writable))
73
- end
74
-
75
- def self.for_target(target, opts = {})
76
- opts[:vendor_cache] = opts[:vendor_cache] || Cache.new
77
- fetcher = resolve_target(target, opts[:vendor_cache])
78
- for_fetcher(fetcher, opts)
79
- end
80
-
81
- attr_reader :source_reader, :backend, :runner_context, :check_mode
82
- def_delegator :@source_reader, :tests
83
- def_delegator :@source_reader, :libraries
84
- def_delegator :@source_reader, :metadata
85
-
86
- # rubocop:disable Metrics/AbcSize
87
- def initialize(source_reader, options = {})
88
- @source_reader = source_reader
89
- @target = options[:target]
90
- @logger = options[:logger] || Logger.new(nil)
91
- @locked_dependencies = options[:dependencies]
92
- @controls = options[:controls] || []
93
- @writable = options[:writable] || false
94
- @profile_id = options[:id]
95
- @cache = options[:vendor_cache] || Cache.new
96
- @attr_values = options[:attributes]
97
- @tests_collected = false
98
- @libraries_loaded = false
99
- @check_mode = options[:check_mode] || false
100
- Metadata.finalize(@source_reader.metadata, @profile_id, options)
101
-
102
- # if a backend has already been created, clone it so each profile has its own unique backend object
103
- # otherwise, create a new backend object
104
- #
105
- # This is necessary since we store the RuntimeProfile on the backend object. If a user runs `inspec exec`
106
- # with multiple profiles, only the RuntimeProfile for the last-loaded profile will be available if
107
- # we share the backend between profiles.
108
- #
109
- # This will cause issues if a profile attempts to load a file via `inspec.profile.file`
110
- train_options = options.reject { |k, _| k == 'target' } # See https://github.com/chef/inspec/pull/1646
111
- @backend = options[:backend].nil? ? Inspec::Backend.create(train_options) : options[:backend].dup
112
- @runtime_profile = RuntimeProfile.new(self)
113
- @backend.profile = @runtime_profile
114
-
115
- @runner_context =
116
- options[:profile_context] ||
117
- Inspec::ProfileContext.for_profile(self, @backend, @attr_values)
118
-
119
- @supports_platform = metadata.supports_platform?(@backend)
120
- @supports_runtime = metadata.supports_runtime?
121
- end
122
-
123
- def name
124
- metadata.params[:name]
125
- end
126
-
127
- def version
128
- metadata.params[:version]
129
- end
130
-
131
- def writable?
132
- @writable
133
- end
134
-
135
- #
136
- # Is this profile is supported on the current platform of the
137
- # backend machine and the current inspec version.
138
- #
139
- # @returns [TrueClass, FalseClass]
140
- #
141
- def supported?
142
- supports_platform? && supports_runtime?
143
- end
144
-
145
- def supports_platform?
146
- if @supports_platform.nil?
147
- @supports_platform = metadata.supports_platform?(@backend)
148
- end
149
- @supports_platform
150
- end
151
-
152
- def supports_runtime?
153
- if @supports_runtime.nil?
154
- @supports_runtime = metadata.supports_runtime?
155
- end
156
- @supports_runtime
157
- end
158
-
159
- def params
160
- @params ||= load_params
161
- end
162
-
163
- def collect_tests(include_list = @controls)
164
- if !@tests_collected
165
- locked_dependencies.each(&:collect_tests)
166
-
167
- tests.each do |path, content|
168
- next if content.nil? || content.empty?
169
- abs_path = source_reader.target.abs_path(path)
170
- @runner_context.load_control_file(content, abs_path, nil)
171
- end
172
- @tests_collected = true
173
- end
174
- filter_controls(@runner_context.all_rules, include_list)
175
- end
176
-
177
- def filter_controls(controls_array, include_list)
178
- return controls_array if include_list.nil? || include_list.empty?
179
- controls_array.select do |c|
180
- id = ::Inspec::Rule.rule_id(c)
181
- include_list.include?(id)
182
- end
183
- end
184
-
185
- def load_libraries
186
- return @runner_context if @libraries_loaded
187
-
188
- locked_dependencies.each do |d|
189
- c = d.load_libraries
190
- @runner_context.add_resources(c)
191
- end
192
-
193
- libs = libraries.map do |path, content|
194
- [content, path]
195
- end
196
-
197
- @runner_context.load_libraries(libs)
198
- @libraries_loaded = true
199
- @runner_context
200
- end
201
-
202
- def to_s
203
- "Inspec::Profile<#{name}>"
204
- end
205
-
206
- # return info using uncached params
207
- def info!
208
- info(load_params.dup)
209
- end
210
-
211
- def info(res = params.dup)
212
- # add information about the controls
213
- res[:controls] = res[:controls].map do |id, rule|
214
- next if id.to_s.empty?
215
- data = rule.dup
216
- data.delete(:checks)
217
- data[:impact] ||= 0.5
218
- data[:impact] = 1.0 if data[:impact] > 1.0
219
- data[:impact] = 0.0 if data[:impact] < 0.0
220
- data[:id] = id
221
- data
222
- end.compact
223
-
224
- # resolve hash structure in groups
225
- res[:groups] = res[:groups].map do |id, group|
226
- group[:id] = id
227
- group
228
- end
229
-
230
- # add information about the required attributes
231
- res[:attributes] = res[:attributes].map(&:to_hash) unless res[:attributes].nil? || res[:attributes].empty?
232
- res[:sha256] = sha256
233
- res
234
- end
235
-
236
- # Check if the profile is internally well-structured. The logger will be
237
- # used to print information on errors and warnings which are found.
238
- #
239
- # @return [Boolean] true if no errors were found, false otherwise
240
- def check # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
241
- # initial values for response object
242
- result = {
243
- summary: {
244
- valid: false,
245
- timestamp: Time.now.iso8601,
246
- location: @target,
247
- profile: nil,
248
- controls: 0,
249
- },
250
- errors: [],
251
- warnings: [],
252
- }
253
-
254
- entry = lambda { |file, line, column, control, msg|
255
- {
256
- file: file,
257
- line: line,
258
- column: column,
259
- control_id: control,
260
- msg: msg,
261
- }
262
- }
263
-
264
- warn = lambda { |file, line, column, control, msg|
265
- @logger.warn(msg)
266
- result[:warnings].push(entry.call(file, line, column, control, msg))
267
- }
268
-
269
- error = lambda { |file, line, column, control, msg|
270
- @logger.error(msg)
271
- result[:errors].push(entry.call(file, line, column, control, msg))
272
- }
273
-
274
- @logger.info "Checking profile in #{@target}"
275
- meta_path = @source_reader.target.abs_path(@source_reader.metadata.ref)
276
- if meta_path =~ /metadata\.rb$/
277
- warn.call(@target, 0, 0, nil, 'The use of `metadata.rb` is deprecated. Use `inspec.yml`.')
278
- end
279
-
280
- # verify metadata
281
- m_errors, m_warnings = metadata.valid
282
- m_errors.each { |msg| error.call(meta_path, 0, 0, nil, msg) }
283
- m_warnings.each { |msg| warn.call(meta_path, 0, 0, nil, msg) }
284
- m_unsupported = metadata.unsupported
285
- m_unsupported.each { |u| warn.call(meta_path, 0, 0, nil, "doesn't support: #{u}") }
286
- @logger.info 'Metadata OK.' if m_errors.empty? && m_unsupported.empty?
287
-
288
- # extract profile name
289
- result[:summary][:profile] = metadata.params[:name]
290
-
291
- # check if the profile is using the old test directory instead of the
292
- # new controls directory
293
- if @source_reader.tests.keys.any? { |x| x =~ %r{^test/$} }
294
- warn.call(@target, 0, 0, nil, 'Profile uses deprecated `test` directory, rename it to `controls`.')
295
- end
296
-
297
- count = controls_count
298
- result[:summary][:controls] = count
299
- if count == 0
300
- warn.call(nil, nil, nil, nil, 'No controls or tests were defined.')
301
- else
302
- @logger.info("Found #{count} controls.")
303
- end
304
-
305
- # iterate over hash of groups
306
- params[:controls].each { |id, control|
307
- sfile = control[:source_location][:ref]
308
- sline = control[:source_location][:line]
309
- error.call(sfile, sline, nil, id, 'Avoid controls with empty IDs') if id.nil? or id.empty?
310
- next if id.start_with? '(generated '
311
- warn.call(sfile, sline, nil, id, "Control #{id} has no title") if control[:title].to_s.empty?
312
- warn.call(sfile, sline, nil, id, "Control #{id} has no description") if control[:desc].to_s.empty?
313
- warn.call(sfile, sline, nil, id, "Control #{id} has impact > 1.0") if control[:impact].to_f > 1.0
314
- warn.call(sfile, sline, nil, id, "Control #{id} has impact < 0.0") if control[:impact].to_f < 0.0
315
- warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? or control[:checks].empty?
316
- }
317
-
318
- # profile is valid if we could not find any error
319
- result[:summary][:valid] = result[:errors].empty?
320
-
321
- @logger.info 'Control definitions OK.' if result[:warnings].empty?
322
- result
323
- end
324
-
325
- def controls_count
326
- params[:controls].values.length
327
- end
328
-
329
- # generates a archive of a folder profile
330
- # assumes that the profile was checked before
331
- def archive(opts)
332
- # check if file exists otherwise overwrite the archive
333
- dst = archive_name(opts)
334
- if dst.exist? && !opts[:overwrite]
335
- @logger.info "Archive #{dst} exists already. Use --overwrite."
336
- return false
337
- end
338
-
339
- # remove existing archive
340
- File.delete(dst) if dst.exist?
341
- @logger.info "Generate archive #{dst}."
342
-
343
- # filter files that should not be part of the profile
344
- # TODO ignore all .files, but add the files to debug output
345
-
346
- # display all files that will be part of the archive
347
- @logger.debug 'Add the following files to archive:'
348
- files.each { |f| @logger.debug ' ' + f }
349
-
350
- if opts[:zip]
351
- # generate zip archive
352
- require 'inspec/archive/zip'
353
- zag = Inspec::Archive::ZipArchiveGenerator.new
354
- zag.archive(root_path, files, dst)
355
- else
356
- # generate tar archive
357
- require 'inspec/archive/tar'
358
- tag = Inspec::Archive::TarArchiveGenerator.new
359
- tag.archive(root_path, files, dst)
360
- end
361
-
362
- @logger.info 'Finished archive generation.'
363
- true
364
- end
365
-
366
- def locked_dependencies
367
- @locked_dependencies ||= load_dependencies
368
- end
369
-
370
- def lockfile_exists?
371
- @source_reader.target.files.include?('inspec.lock')
372
- end
373
-
374
- def lockfile_path
375
- File.join(cwd, 'inspec.lock')
376
- end
377
-
378
- def root_path
379
- @source_reader.target.prefix
380
- end
381
-
382
- def files
383
- @source_reader.target.files
384
- end
385
-
386
- #
387
- # TODO(ssd): Relative path handling really needs to be carefully
388
- # thought through, especially with respect to relative paths in
389
- # tarballs.
390
- #
391
- def cwd
392
- @target.is_a?(String) && File.directory?(@target) ? @target : './'
393
- end
394
-
395
- def lockfile
396
- @lockfile ||= if lockfile_exists?
397
- Inspec::Lockfile.from_content(@source_reader.target.read('inspec.lock'))
398
- else
399
- generate_lockfile
400
- end
401
- end
402
-
403
- #
404
- # Generate an in-memory lockfile. This won't render the lock file
405
- # to disk, it must be explicitly written to disk by the caller.
406
- #
407
- # @param vendor_path [String] Path to the on-disk vendor dir
408
- # @return [Inspec::Lockfile]
409
- #
410
- def generate_lockfile
411
- res = Inspec::DependencySet.new(cwd, @cache, nil, @backend)
412
- res.vendor(metadata.dependencies)
413
- Inspec::Lockfile.from_dependency_set(res)
414
- end
415
-
416
- def load_dependencies
417
- Inspec::DependencySet.from_lockfile(lockfile, cwd, @cache, @backend, { attributes: @attr_values })
418
- end
419
-
420
- # Calculate this profile's SHA256 checksum. Includes metadata, dependencies,
421
- # libraries, data files, and controls.
422
- #
423
- # @return [Type] description of returned object
424
- def sha256
425
- # get all dependency checksums
426
- deps = Hash[locked_dependencies.list.map { |k, v| [k, v.profile.sha256] }]
427
-
428
- res = OpenSSL::Digest::SHA256.new
429
- files = source_reader.tests.to_a + source_reader.libraries.to_a +
430
- source_reader.data_files.to_a +
431
- [['inspec.yml', source_reader.metadata.content]] +
432
- [['inspec.lock.deps', YAML.dump(deps)]]
433
-
434
- files.sort_by { |a| a[0] }
435
- .map { |f| res << f[0] << "\0" << f[1] << "\0" }
436
-
437
- res.digest.unpack('H*')[0]
438
- end
439
-
440
- private
441
-
442
- # Create an archive name for this profile and an additional options
443
- # configuration. Either use :output or generate the name from metadata.
444
- #
445
- # @param [Hash] configuration options
446
- # @return [Pathname] path for the archive
447
- def archive_name(opts)
448
- if (name = opts[:output])
449
- return Pathname.new(name)
450
- end
451
-
452
- name = params[:name] ||
453
- raise('Cannot create an archive without a profile name! Please '\
454
- 'specify the name in metadata or use --output to create the archive.')
455
- version = params[:version] ||
456
- raise('Cannot create an archive without a profile version! Please '\
457
- 'specify the version in metadata or use --output to create the archive.')
458
- ext = opts[:zip] ? 'zip' : 'tar.gz'
459
- slug = name.downcase.strip.tr(' ', '-').gsub(/[^\w-]/, '_')
460
- Pathname.new(Dir.pwd).join("#{slug}-#{version}.#{ext}")
461
- end
462
-
463
- def load_params
464
- params = @source_reader.metadata.params
465
- params[:name] = @profile_id unless @profile_id.nil?
466
- load_checks_params(params)
467
- @profile_id ||= params[:name]
468
- params
469
- end
470
-
471
- def load_checks_params(params)
472
- load_libraries
473
- tests = collect_tests
474
- params[:controls] = controls = {}
475
- params[:groups] = groups = {}
476
- prefix = @source_reader.target.prefix || ''
477
- tests.each do |rule|
478
- next if rule.nil?
479
- f = load_rule_filepath(prefix, rule)
480
- load_rule(rule, f, controls, groups)
481
- end
482
- params[:attributes] = @runner_context.attributes
483
- params
484
- end
485
-
486
- def load_rule_filepath(prefix, rule)
487
- file = rule.instance_variable_get(:@__file)
488
- file = file[prefix.length..-1] if file.start_with?(prefix)
489
- file
490
- end
491
-
492
- def load_rule(rule, file, controls, groups)
493
- id = Inspec::Rule.rule_id(rule)
494
- location = rule.instance_variable_get(:@__source_location)
495
- controls[id] = {
496
- title: rule.title,
497
- desc: rule.desc,
498
- impact: rule.impact,
499
- refs: rule.ref,
500
- tags: rule.tag,
501
- checks: Inspec::Rule.checks(rule),
502
- code: Inspec::MethodSource.code_at(location, source_reader),
503
- source_location: location,
504
- }
505
-
506
- groups[file] ||= {
507
- title: rule.instance_variable_get(:@__group_title),
508
- controls: [],
509
- }
510
- groups[file][:controls].push(id)
511
- end
512
- end
513
- end
1
+ # encoding: utf-8
2
+ # Copyright 2015 Dominik Richter
3
+ # author: Dominik Richter
4
+ # author: Christoph Hartmann
5
+
6
+ require 'forwardable'
7
+ require 'openssl'
8
+ require 'inspec/polyfill'
9
+ require 'inspec/cached_fetcher'
10
+ require 'inspec/file_provider'
11
+ require 'inspec/source_reader'
12
+ require 'inspec/metadata'
13
+ require 'inspec/backend'
14
+ require 'inspec/rule'
15
+ require 'inspec/log'
16
+ require 'inspec/profile_context'
17
+ require 'inspec/runtime_profile'
18
+ require 'inspec/method_source'
19
+ require 'inspec/dependencies/cache'
20
+ require 'inspec/dependencies/lockfile'
21
+ require 'inspec/dependencies/dependency_set'
22
+
23
+ module Inspec
24
+ class Profile
25
+ extend Forwardable
26
+
27
+ def self.resolve_target(target, cache)
28
+ Inspec::Log.debug "Resolve #{target} into cache #{cache.path}"
29
+ Inspec::CachedFetcher.new(target, cache)
30
+ end
31
+
32
+ # Check if the profile contains a vendored cache, move content into global cache
33
+ # TODO: use relative file provider
34
+ # TODO: use source reader for Cache as well
35
+ def self.copy_deps_into_cache(file_provider, opts)
36
+ # filter content
37
+ cache = file_provider.files.find_all do |entry|
38
+ entry.start_with?('vendor')
39
+ end
40
+ content = Hash[cache.map { |x| [x, file_provider.binread(x)] }]
41
+ keys = content.keys
42
+ keys.each do |key|
43
+ next if content[key].nil?
44
+ # remove prefix
45
+ rel = Pathname.new(key).relative_path_from(Pathname.new('vendor')).to_s
46
+ tar = Pathname.new(opts[:vendor_cache].path).join(rel)
47
+
48
+ FileUtils.mkdir_p tar.dirname.to_s
49
+ Inspec::Log.debug "Copy #{tar} to cache directory"
50
+ File.binwrite(tar.to_s, content[key])
51
+ end
52
+ end
53
+
54
+ def self.for_path(path, opts)
55
+ file_provider = FileProvider.for_path(path)
56
+ rp = file_provider.relative_provider
57
+
58
+ # copy embedded dependecies into global cache
59
+ copy_deps_into_cache(rp, opts) unless opts[:vendor_cache].nil?
60
+
61
+ reader = Inspec::SourceReader.resolve(rp)
62
+ if reader.nil?
63
+ raise("Don't understand inspec profile in #{path}, it " \
64
+ "doesn't look like a supported profile structure.")
65
+ end
66
+ new(reader, opts)
67
+ end
68
+
69
+ def self.for_fetcher(fetcher, opts)
70
+ opts[:vendor_cache] = opts[:vendor_cache] || Cache.new
71
+ path, writable = fetcher.fetch
72
+ for_path(path, opts.merge(target: fetcher.target, writable: writable))
73
+ end
74
+
75
+ def self.for_target(target, opts = {})
76
+ opts[:vendor_cache] = opts[:vendor_cache] || Cache.new
77
+ fetcher = resolve_target(target, opts[:vendor_cache])
78
+ for_fetcher(fetcher, opts)
79
+ end
80
+
81
+ attr_reader :source_reader, :backend, :runner_context, :check_mode
82
+ def_delegator :@source_reader, :tests
83
+ def_delegator :@source_reader, :libraries
84
+ def_delegator :@source_reader, :metadata
85
+
86
+ # rubocop:disable Metrics/AbcSize
87
+ def initialize(source_reader, options = {})
88
+ @source_reader = source_reader
89
+ @target = options[:target]
90
+ @logger = options[:logger] || Logger.new(nil)
91
+ @locked_dependencies = options[:dependencies]
92
+ @controls = options[:controls] || []
93
+ @writable = options[:writable] || false
94
+ @profile_id = options[:id]
95
+ @cache = options[:vendor_cache] || Cache.new
96
+ @attr_values = options[:attributes]
97
+ @tests_collected = false
98
+ @libraries_loaded = false
99
+ @check_mode = options[:check_mode] || false
100
+ Metadata.finalize(@source_reader.metadata, @profile_id, options)
101
+
102
+ # if a backend has already been created, clone it so each profile has its own unique backend object
103
+ # otherwise, create a new backend object
104
+ #
105
+ # This is necessary since we store the RuntimeProfile on the backend object. If a user runs `inspec exec`
106
+ # with multiple profiles, only the RuntimeProfile for the last-loaded profile will be available if
107
+ # we share the backend between profiles.
108
+ #
109
+ # This will cause issues if a profile attempts to load a file via `inspec.profile.file`
110
+ train_options = options.reject { |k, _| k == 'target' } # See https://github.com/chef/inspec/pull/1646
111
+ @backend = options[:backend].nil? ? Inspec::Backend.create(train_options) : options[:backend].dup
112
+ @runtime_profile = RuntimeProfile.new(self)
113
+ @backend.profile = @runtime_profile
114
+
115
+ @runner_context =
116
+ options[:profile_context] ||
117
+ Inspec::ProfileContext.for_profile(self, @backend, @attr_values)
118
+
119
+ @supports_platform = metadata.supports_platform?(@backend)
120
+ @supports_runtime = metadata.supports_runtime?
121
+ end
122
+
123
+ def name
124
+ metadata.params[:name]
125
+ end
126
+
127
+ def version
128
+ metadata.params[:version]
129
+ end
130
+
131
+ def writable?
132
+ @writable
133
+ end
134
+
135
+ #
136
+ # Is this profile is supported on the current platform of the
137
+ # backend machine and the current inspec version.
138
+ #
139
+ # @returns [TrueClass, FalseClass]
140
+ #
141
+ def supported?
142
+ supports_platform? && supports_runtime?
143
+ end
144
+
145
+ def supports_platform?
146
+ if @supports_platform.nil?
147
+ @supports_platform = metadata.supports_platform?(@backend)
148
+ end
149
+ @supports_platform
150
+ end
151
+
152
+ def supports_runtime?
153
+ if @supports_runtime.nil?
154
+ @supports_runtime = metadata.supports_runtime?
155
+ end
156
+ @supports_runtime
157
+ end
158
+
159
+ def params
160
+ @params ||= load_params
161
+ end
162
+
163
+ def collect_tests(include_list = @controls)
164
+ if !@tests_collected
165
+ locked_dependencies.each(&:collect_tests)
166
+
167
+ tests.each do |path, content|
168
+ next if content.nil? || content.empty?
169
+ abs_path = source_reader.target.abs_path(path)
170
+ @runner_context.load_control_file(content, abs_path, nil)
171
+ end
172
+ @tests_collected = true
173
+ end
174
+ filter_controls(@runner_context.all_rules, include_list)
175
+ end
176
+
177
+ def filter_controls(controls_array, include_list)
178
+ return controls_array if include_list.nil? || include_list.empty?
179
+ controls_array.select do |c|
180
+ id = ::Inspec::Rule.rule_id(c)
181
+ include_list.include?(id)
182
+ end
183
+ end
184
+
185
+ def load_libraries
186
+ return @runner_context if @libraries_loaded
187
+
188
+ locked_dependencies.each do |d|
189
+ c = d.load_libraries
190
+ @runner_context.add_resources(c)
191
+ end
192
+
193
+ libs = libraries.map do |path, content|
194
+ [content, path]
195
+ end
196
+
197
+ @runner_context.load_libraries(libs)
198
+ @libraries_loaded = true
199
+ @runner_context
200
+ end
201
+
202
+ def to_s
203
+ "Inspec::Profile<#{name}>"
204
+ end
205
+
206
+ # return info using uncached params
207
+ def info!
208
+ info(load_params.dup)
209
+ end
210
+
211
+ def info(res = params.dup)
212
+ # add information about the controls
213
+ res[:controls] = res[:controls].map do |id, rule|
214
+ next if id.to_s.empty?
215
+ data = rule.dup
216
+ data.delete(:checks)
217
+ data[:impact] ||= 0.5
218
+ data[:impact] = 1.0 if data[:impact] > 1.0
219
+ data[:impact] = 0.0 if data[:impact] < 0.0
220
+ data[:id] = id
221
+ data
222
+ end.compact
223
+
224
+ # resolve hash structure in groups
225
+ res[:groups] = res[:groups].map do |id, group|
226
+ group[:id] = id
227
+ group
228
+ end
229
+
230
+ # add information about the required attributes
231
+ res[:attributes] = res[:attributes].map(&:to_hash) unless res[:attributes].nil? || res[:attributes].empty?
232
+ res[:sha256] = sha256
233
+ res
234
+ end
235
+
236
+ # Check if the profile is internally well-structured. The logger will be
237
+ # used to print information on errors and warnings which are found.
238
+ #
239
+ # @return [Boolean] true if no errors were found, false otherwise
240
+ def check # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
241
+ # initial values for response object
242
+ result = {
243
+ summary: {
244
+ valid: false,
245
+ timestamp: Time.now.iso8601,
246
+ location: @target,
247
+ profile: nil,
248
+ controls: 0,
249
+ },
250
+ errors: [],
251
+ warnings: [],
252
+ }
253
+
254
+ entry = lambda { |file, line, column, control, msg|
255
+ {
256
+ file: file,
257
+ line: line,
258
+ column: column,
259
+ control_id: control,
260
+ msg: msg,
261
+ }
262
+ }
263
+
264
+ warn = lambda { |file, line, column, control, msg|
265
+ @logger.warn(msg)
266
+ result[:warnings].push(entry.call(file, line, column, control, msg))
267
+ }
268
+
269
+ error = lambda { |file, line, column, control, msg|
270
+ @logger.error(msg)
271
+ result[:errors].push(entry.call(file, line, column, control, msg))
272
+ }
273
+
274
+ @logger.info "Checking profile in #{@target}"
275
+ meta_path = @source_reader.target.abs_path(@source_reader.metadata.ref)
276
+ if meta_path =~ /metadata\.rb$/
277
+ warn.call(@target, 0, 0, nil, 'The use of `metadata.rb` is deprecated. Use `inspec.yml`.')
278
+ end
279
+
280
+ # verify metadata
281
+ m_errors, m_warnings = metadata.valid
282
+ m_errors.each { |msg| error.call(meta_path, 0, 0, nil, msg) }
283
+ m_warnings.each { |msg| warn.call(meta_path, 0, 0, nil, msg) }
284
+ m_unsupported = metadata.unsupported
285
+ m_unsupported.each { |u| warn.call(meta_path, 0, 0, nil, "doesn't support: #{u}") }
286
+ @logger.info 'Metadata OK.' if m_errors.empty? && m_unsupported.empty?
287
+
288
+ # extract profile name
289
+ result[:summary][:profile] = metadata.params[:name]
290
+
291
+ # check if the profile is using the old test directory instead of the
292
+ # new controls directory
293
+ if @source_reader.tests.keys.any? { |x| x =~ %r{^test/$} }
294
+ warn.call(@target, 0, 0, nil, 'Profile uses deprecated `test` directory, rename it to `controls`.')
295
+ end
296
+
297
+ count = controls_count
298
+ result[:summary][:controls] = count
299
+ if count == 0
300
+ warn.call(nil, nil, nil, nil, 'No controls or tests were defined.')
301
+ else
302
+ @logger.info("Found #{count} controls.")
303
+ end
304
+
305
+ # iterate over hash of groups
306
+ params[:controls].each { |id, control|
307
+ sfile = control[:source_location][:ref]
308
+ sline = control[:source_location][:line]
309
+ error.call(sfile, sline, nil, id, 'Avoid controls with empty IDs') if id.nil? or id.empty?
310
+ next if id.start_with? '(generated '
311
+ warn.call(sfile, sline, nil, id, "Control #{id} has no title") if control[:title].to_s.empty?
312
+ warn.call(sfile, sline, nil, id, "Control #{id} has no description") if control[:desc].to_s.empty?
313
+ warn.call(sfile, sline, nil, id, "Control #{id} has impact > 1.0") if control[:impact].to_f > 1.0
314
+ warn.call(sfile, sline, nil, id, "Control #{id} has impact < 0.0") if control[:impact].to_f < 0.0
315
+ warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? or control[:checks].empty?
316
+ }
317
+
318
+ # profile is valid if we could not find any error
319
+ result[:summary][:valid] = result[:errors].empty?
320
+
321
+ @logger.info 'Control definitions OK.' if result[:warnings].empty?
322
+ result
323
+ end
324
+
325
+ def controls_count
326
+ params[:controls].values.length
327
+ end
328
+
329
+ # generates a archive of a folder profile
330
+ # assumes that the profile was checked before
331
+ def archive(opts)
332
+ # check if file exists otherwise overwrite the archive
333
+ dst = archive_name(opts)
334
+ if dst.exist? && !opts[:overwrite]
335
+ @logger.info "Archive #{dst} exists already. Use --overwrite."
336
+ return false
337
+ end
338
+
339
+ # remove existing archive
340
+ File.delete(dst) if dst.exist?
341
+ @logger.info "Generate archive #{dst}."
342
+
343
+ # filter files that should not be part of the profile
344
+ # TODO ignore all .files, but add the files to debug output
345
+
346
+ # display all files that will be part of the archive
347
+ @logger.debug 'Add the following files to archive:'
348
+ files.each { |f| @logger.debug ' ' + f }
349
+
350
+ if opts[:zip]
351
+ # generate zip archive
352
+ require 'inspec/archive/zip'
353
+ zag = Inspec::Archive::ZipArchiveGenerator.new
354
+ zag.archive(root_path, files, dst)
355
+ else
356
+ # generate tar archive
357
+ require 'inspec/archive/tar'
358
+ tag = Inspec::Archive::TarArchiveGenerator.new
359
+ tag.archive(root_path, files, dst)
360
+ end
361
+
362
+ @logger.info 'Finished archive generation.'
363
+ true
364
+ end
365
+
366
+ def locked_dependencies
367
+ @locked_dependencies ||= load_dependencies
368
+ end
369
+
370
+ def lockfile_exists?
371
+ @source_reader.target.files.include?('inspec.lock')
372
+ end
373
+
374
+ def lockfile_path
375
+ File.join(cwd, 'inspec.lock')
376
+ end
377
+
378
+ def root_path
379
+ @source_reader.target.prefix
380
+ end
381
+
382
+ def files
383
+ @source_reader.target.files
384
+ end
385
+
386
+ #
387
+ # TODO(ssd): Relative path handling really needs to be carefully
388
+ # thought through, especially with respect to relative paths in
389
+ # tarballs.
390
+ #
391
+ def cwd
392
+ @target.is_a?(String) && File.directory?(@target) ? @target : './'
393
+ end
394
+
395
+ def lockfile
396
+ @lockfile ||= if lockfile_exists?
397
+ Inspec::Lockfile.from_content(@source_reader.target.read('inspec.lock'))
398
+ else
399
+ generate_lockfile
400
+ end
401
+ end
402
+
403
+ #
404
+ # Generate an in-memory lockfile. This won't render the lock file
405
+ # to disk, it must be explicitly written to disk by the caller.
406
+ #
407
+ # @param vendor_path [String] Path to the on-disk vendor dir
408
+ # @return [Inspec::Lockfile]
409
+ #
410
+ def generate_lockfile
411
+ res = Inspec::DependencySet.new(cwd, @cache, nil, @backend)
412
+ res.vendor(metadata.dependencies)
413
+ Inspec::Lockfile.from_dependency_set(res)
414
+ end
415
+
416
+ def load_dependencies
417
+ Inspec::DependencySet.from_lockfile(lockfile, cwd, @cache, @backend, { attributes: @attr_values })
418
+ end
419
+
420
+ # Calculate this profile's SHA256 checksum. Includes metadata, dependencies,
421
+ # libraries, data files, and controls.
422
+ #
423
+ # @return [Type] description of returned object
424
+ def sha256
425
+ # get all dependency checksums
426
+ deps = Hash[locked_dependencies.list.map { |k, v| [k, v.profile.sha256] }]
427
+
428
+ res = OpenSSL::Digest::SHA256.new
429
+ files = source_reader.tests.to_a + source_reader.libraries.to_a +
430
+ source_reader.data_files.to_a +
431
+ [['inspec.yml', source_reader.metadata.content]] +
432
+ [['inspec.lock.deps', YAML.dump(deps)]]
433
+
434
+ files.sort_by { |a| a[0] }
435
+ .map { |f| res << f[0] << "\0" << f[1] << "\0" }
436
+
437
+ res.digest.unpack('H*')[0]
438
+ end
439
+
440
+ private
441
+
442
+ # Create an archive name for this profile and an additional options
443
+ # configuration. Either use :output or generate the name from metadata.
444
+ #
445
+ # @param [Hash] configuration options
446
+ # @return [Pathname] path for the archive
447
+ def archive_name(opts)
448
+ if (name = opts[:output])
449
+ return Pathname.new(name)
450
+ end
451
+
452
+ name = params[:name] ||
453
+ raise('Cannot create an archive without a profile name! Please '\
454
+ 'specify the name in metadata or use --output to create the archive.')
455
+ version = params[:version] ||
456
+ raise('Cannot create an archive without a profile version! Please '\
457
+ 'specify the version in metadata or use --output to create the archive.')
458
+ ext = opts[:zip] ? 'zip' : 'tar.gz'
459
+ slug = name.downcase.strip.tr(' ', '-').gsub(/[^\w-]/, '_')
460
+ Pathname.new(Dir.pwd).join("#{slug}-#{version}.#{ext}")
461
+ end
462
+
463
+ def load_params
464
+ params = @source_reader.metadata.params
465
+ params[:name] = @profile_id unless @profile_id.nil?
466
+ load_checks_params(params)
467
+ @profile_id ||= params[:name]
468
+ params
469
+ end
470
+
471
+ def load_checks_params(params)
472
+ load_libraries
473
+ tests = collect_tests
474
+ params[:controls] = controls = {}
475
+ params[:groups] = groups = {}
476
+ prefix = @source_reader.target.prefix || ''
477
+ tests.each do |rule|
478
+ next if rule.nil?
479
+ f = load_rule_filepath(prefix, rule)
480
+ load_rule(rule, f, controls, groups)
481
+ end
482
+ params[:attributes] = @runner_context.attributes
483
+ params
484
+ end
485
+
486
+ def load_rule_filepath(prefix, rule)
487
+ file = rule.instance_variable_get(:@__file)
488
+ file = file[prefix.length..-1] if file.start_with?(prefix)
489
+ file
490
+ end
491
+
492
+ def load_rule(rule, file, controls, groups)
493
+ id = Inspec::Rule.rule_id(rule)
494
+ location = rule.instance_variable_get(:@__source_location)
495
+ controls[id] = {
496
+ title: rule.title,
497
+ desc: rule.desc,
498
+ impact: rule.impact,
499
+ refs: rule.ref,
500
+ tags: rule.tag,
501
+ checks: Inspec::Rule.checks(rule),
502
+ code: Inspec::MethodSource.code_at(location, source_reader),
503
+ source_location: location,
504
+ }
505
+
506
+ groups[file] ||= {
507
+ title: rule.instance_variable_get(:@__group_title),
508
+ controls: [],
509
+ }
510
+ groups[file][:controls].push(id)
511
+ end
512
+ end
513
+ end