inspec 2.1.21 → 2.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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