inspec 1.0.0 → 1.35.1

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 (286) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +933 -101
  3. data/Gemfile +8 -17
  4. data/MAINTAINERS.md +2 -1
  5. data/MAINTAINERS.toml +9 -4
  6. data/README.md +49 -28
  7. data/Rakefile +54 -39
  8. data/bin/inspec +1 -1
  9. data/docs/dsl_inspec.md +17 -4
  10. data/docs/dsl_resource.md +1 -1
  11. data/docs/habitat.md +192 -0
  12. data/docs/inspec_and_friends.md +4 -2
  13. data/docs/matchers.md +11 -0
  14. data/docs/migration.md +293 -0
  15. data/docs/profiles.md +124 -76
  16. data/docs/resources/aide_conf.md.erb +81 -0
  17. data/docs/resources/apache_conf.md.erb +10 -10
  18. data/docs/resources/apt.md.erb +13 -13
  19. data/docs/resources/audit_policy.md.erb +10 -10
  20. data/docs/resources/auditd_conf.md.erb +10 -10
  21. data/docs/resources/auditd_rules.md.erb +11 -11
  22. data/docs/resources/bash.md.erb +12 -12
  23. data/docs/resources/bond.md.erb +15 -15
  24. data/docs/resources/bridge.md.erb +11 -11
  25. data/docs/resources/bsd_service.md.erb +11 -11
  26. data/docs/resources/command.md.erb +23 -23
  27. data/docs/resources/crontab.md.erb +84 -0
  28. data/docs/resources/csv.md.erb +10 -10
  29. data/docs/resources/dh_params.md +191 -0
  30. data/docs/resources/directory.md.erb +8 -8
  31. data/docs/resources/docker.md.erb +160 -0
  32. data/docs/resources/docker_container.md.erb +89 -0
  33. data/docs/resources/docker_image.md.erb +86 -0
  34. data/docs/resources/etc_group.md.erb +16 -16
  35. data/docs/resources/etc_hosts.md.erb +62 -0
  36. data/docs/resources/file.md.erb +80 -58
  37. data/docs/resources/gem.md.erb +32 -13
  38. data/docs/resources/group.md.erb +12 -12
  39. data/docs/resources/grub_conf.md.erb +11 -11
  40. data/docs/resources/host.md.erb +27 -18
  41. data/docs/resources/http.md.erb +104 -0
  42. data/docs/resources/iis_app.md.erb +126 -0
  43. data/docs/resources/iis_site.md.erb +17 -17
  44. data/docs/resources/inetd_conf.md.erb +10 -10
  45. data/docs/resources/ini.md.erb +9 -9
  46. data/docs/resources/interface.md.erb +13 -13
  47. data/docs/resources/iptables.md.erb +21 -14
  48. data/docs/resources/json.md.erb +11 -11
  49. data/docs/resources/kernel_module.md.erb +73 -12
  50. data/docs/resources/kernel_parameter.md.erb +12 -12
  51. data/docs/resources/key_rsa.md.erb +70 -0
  52. data/docs/resources/launchd_service.md.erb +11 -11
  53. data/docs/resources/limits_conf.md.erb +10 -10
  54. data/docs/resources/login_def.md.erb +12 -12
  55. data/docs/resources/mount.md.erb +14 -14
  56. data/docs/resources/mssql_session.md.erb +62 -0
  57. data/docs/resources/mysql_conf.md.erb +14 -14
  58. data/docs/resources/mysql_session.md.erb +22 -10
  59. data/docs/resources/npm.md.erb +13 -13
  60. data/docs/resources/ntp_conf.md.erb +9 -9
  61. data/docs/resources/oneget.md.erb +11 -11
  62. data/docs/resources/oracledb_session.md.erb +53 -0
  63. data/docs/resources/os.md.erb +17 -17
  64. data/docs/resources/os_env.md.erb +12 -12
  65. data/docs/resources/package.md.erb +29 -16
  66. data/docs/resources/parse_config.md.erb +18 -18
  67. data/docs/resources/parse_config_file.md.erb +29 -23
  68. data/docs/resources/{etc_passwd.md.erb → passwd.md.erb} +17 -17
  69. data/docs/resources/pip.md.erb +15 -15
  70. data/docs/resources/port.md.erb +23 -23
  71. data/docs/resources/postgres_conf.md.erb +15 -13
  72. data/docs/resources/postgres_hba_conf.md.erb +104 -0
  73. data/docs/resources/postgres_ident_conf.md.erb +87 -0
  74. data/docs/resources/postgres_session.md.erb +28 -21
  75. data/docs/resources/powershell.md.erb +14 -14
  76. data/docs/resources/processes.md.erb +123 -0
  77. data/docs/resources/rabbitmq_config.md.erb +55 -0
  78. data/docs/resources/registry_key.md.erb +44 -21
  79. data/docs/resources/runit_service.md.erb +11 -11
  80. data/docs/resources/security_policy.md.erb +10 -10
  81. data/docs/resources/service.md.erb +18 -18
  82. data/docs/resources/{etc_shadow.md.erb → shadow.md.erb} +19 -19
  83. data/docs/resources/ssh_config.md.erb +13 -13
  84. data/docs/resources/sshd_config.md.erb +15 -15
  85. data/docs/resources/ssl.md.erb +13 -13
  86. data/docs/resources/sys_info.md.erb +10 -10
  87. data/docs/resources/systemd_service.md.erb +11 -11
  88. data/docs/resources/sysv_service.md.erb +11 -11
  89. data/docs/resources/upstart_service.md.erb +11 -11
  90. data/docs/resources/user.md.erb +20 -20
  91. data/docs/resources/users.md.erb +19 -19
  92. data/docs/resources/vbscript.md.erb +13 -13
  93. data/docs/resources/virtualization.md.erb +71 -0
  94. data/docs/resources/windows_feature.md.erb +10 -10
  95. data/docs/resources/windows_task.md.erb +103 -0
  96. data/docs/resources/wmi.md.erb +10 -10
  97. data/docs/resources/x509_certificate.md.erb +146 -0
  98. data/docs/resources/xinetd_conf.md.erb +17 -17
  99. data/docs/resources/xml.md.erb +75 -0
  100. data/docs/resources/yaml.md.erb +25 -11
  101. data/docs/resources/yum.md.erb +24 -16
  102. data/docs/resources/zfs_dataset.md.erb +63 -0
  103. data/docs/resources/zfs_pool.md.erb +57 -0
  104. data/docs/ruby_usage.md +50 -0
  105. data/docs/shared/matcher_cmp.md.erb +4 -6
  106. data/examples/inheritance/README.md +49 -3
  107. data/examples/inheritance/controls/example.rb +4 -1
  108. data/examples/inheritance/inspec.yml +3 -1
  109. data/examples/meta-profile/README.md +29 -3
  110. data/examples/meta-profile/controls/example.rb +10 -5
  111. data/examples/meta-profile/inspec.yml +4 -8
  112. data/examples/profile/README.md +1 -1
  113. data/examples/profile/controls/example.rb +0 -1
  114. data/examples/profile/controls/gordon.rb +0 -1
  115. data/examples/profile/inspec.yml +1 -1
  116. data/examples/profile-attribute/inspec.yml +1 -1
  117. data/examples/profile-sensitive/README.md +29 -0
  118. data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -0
  119. data/examples/profile-sensitive/controls/sensitive.rb +9 -0
  120. data/examples/profile-sensitive/inspec.yml +8 -0
  121. data/inspec.gemspec +10 -2
  122. data/lib/bundles/inspec-artifact/README.md +1 -0
  123. data/lib/bundles/inspec-artifact/cli.rb +284 -0
  124. data/lib/bundles/inspec-artifact.rb +7 -0
  125. data/lib/bundles/inspec-compliance/README.md +44 -15
  126. data/lib/bundles/inspec-compliance/api.rb +135 -29
  127. data/lib/bundles/inspec-compliance/cli.rb +128 -21
  128. data/lib/bundles/inspec-compliance/configuration.rb +8 -0
  129. data/lib/bundles/inspec-compliance/http.rb +26 -19
  130. data/lib/bundles/inspec-compliance/target.rb +50 -25
  131. data/lib/bundles/inspec-habitat/cli.rb +31 -0
  132. data/lib/bundles/inspec-habitat/log.rb +10 -0
  133. data/lib/bundles/inspec-habitat/profile.rb +372 -0
  134. data/lib/bundles/inspec-habitat.rb +12 -0
  135. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +0 -1
  136. data/lib/bundles/inspec-init/templates/profile/inspec.yml +1 -1
  137. data/lib/bundles/inspec-supermarket/api.rb +7 -5
  138. data/lib/bundles/inspec-supermarket/cli.rb +2 -2
  139. data/lib/fetchers/git.rb +4 -2
  140. data/lib/fetchers/local.rb +4 -1
  141. data/lib/fetchers/url.rb +64 -10
  142. data/lib/inspec/archive/tar.rb +1 -1
  143. data/lib/inspec/backend.rb +26 -3
  144. data/lib/inspec/base_cli.rb +44 -7
  145. data/lib/inspec/cached_fetcher.rb +2 -2
  146. data/lib/inspec/cli.rb +34 -12
  147. data/lib/inspec/completions/fish.sh.erb +34 -0
  148. data/lib/inspec/control_eval_context.rb +0 -2
  149. data/lib/inspec/dependencies/cache.rb +0 -1
  150. data/lib/inspec/dependencies/dependency_set.rb +2 -2
  151. data/lib/inspec/dependencies/lockfile.rb +13 -6
  152. data/lib/inspec/dependencies/requirement.rb +6 -6
  153. data/lib/inspec/dependencies/resolver.rb +4 -4
  154. data/lib/inspec/dsl.rb +3 -5
  155. data/lib/inspec/env_printer.rb +9 -1
  156. data/lib/inspec/exceptions.rb +8 -0
  157. data/lib/inspec/fetcher.rb +1 -1
  158. data/lib/inspec/file_provider.rb +47 -11
  159. data/lib/inspec/library_eval_context.rb +1 -1
  160. data/lib/inspec/metadata.rb +62 -18
  161. data/lib/inspec/method_source.rb +24 -0
  162. data/lib/inspec/objects/attribute.rb +21 -0
  163. data/lib/inspec/objects/control.rb +21 -3
  164. data/lib/inspec/objects/each_loop.rb +2 -1
  165. data/lib/inspec/objects/list.rb +1 -1
  166. data/lib/inspec/objects/or_test.rb +17 -2
  167. data/lib/inspec/objects/tag.rb +27 -0
  168. data/lib/inspec/objects/test.rb +9 -8
  169. data/lib/inspec/objects.rb +1 -0
  170. data/lib/inspec/plugins/fetcher.rb +4 -5
  171. data/lib/inspec/plugins/resource.rb +1 -2
  172. data/lib/inspec/plugins/source_reader.rb +3 -3
  173. data/lib/inspec/plugins.rb +1 -1
  174. data/lib/inspec/polyfill.rb +0 -1
  175. data/lib/inspec/profile.rb +92 -23
  176. data/lib/inspec/profile_context.rb +1 -1
  177. data/lib/inspec/profile_vendor.rb +66 -0
  178. data/lib/inspec/resource.rb +29 -3
  179. data/lib/inspec/rspec_json_formatter.rb +672 -285
  180. data/lib/inspec/rule.rb +12 -16
  181. data/lib/inspec/runner.rb +26 -15
  182. data/lib/inspec/runner_rspec.rb +1 -0
  183. data/lib/inspec/runtime_profile.rb +26 -0
  184. data/lib/inspec/schema.rb +185 -0
  185. data/lib/inspec/secrets/yaml.rb +8 -1
  186. data/lib/inspec/secrets.rb +1 -1
  187. data/lib/inspec/shell.rb +92 -22
  188. data/lib/inspec/source_reader.rb +1 -1
  189. data/lib/inspec/version.rb +1 -1
  190. data/lib/inspec.rb +1 -1
  191. data/lib/matchers/matchers.rb +67 -17
  192. data/lib/resources/aide_conf.rb +162 -0
  193. data/lib/resources/apache.rb +0 -1
  194. data/lib/resources/apache_conf.rb +3 -3
  195. data/lib/resources/apt.rb +2 -1
  196. data/lib/resources/audit_policy.rb +0 -1
  197. data/lib/resources/auditd_conf.rb +1 -2
  198. data/lib/resources/auditd_rules.rb +5 -5
  199. data/lib/resources/bond.rb +1 -1
  200. data/lib/resources/bridge.rb +1 -1
  201. data/lib/resources/command.rb +5 -3
  202. data/lib/resources/crontab.rb +102 -0
  203. data/lib/resources/csv.rb +18 -1
  204. data/lib/resources/dh_params.rb +84 -0
  205. data/lib/resources/directory.rb +7 -3
  206. data/lib/resources/docker.rb +198 -0
  207. data/lib/resources/docker_container.rb +114 -0
  208. data/lib/resources/docker_image.rb +96 -0
  209. data/lib/resources/etc_group.rb +2 -3
  210. data/lib/resources/etc_hosts.rb +81 -0
  211. data/lib/resources/file.rb +88 -30
  212. data/lib/resources/gem.rb +26 -6
  213. data/lib/resources/groups.rb +4 -5
  214. data/lib/resources/grub_conf.rb +3 -4
  215. data/lib/resources/host.rb +175 -35
  216. data/lib/resources/http.rb +73 -0
  217. data/lib/resources/iis_app.rb +103 -0
  218. data/lib/resources/iis_site.rb +23 -0
  219. data/lib/resources/inetd_conf.rb +9 -4
  220. data/lib/resources/interface.rb +4 -3
  221. data/lib/resources/json.rb +37 -14
  222. data/lib/resources/kernel_module.rb +77 -10
  223. data/lib/resources/kernel_parameter.rb +0 -1
  224. data/lib/resources/key_rsa.rb +67 -0
  225. data/lib/resources/limits_conf.rb +3 -4
  226. data/lib/resources/login_def.rb +2 -3
  227. data/lib/resources/mount.rb +34 -4
  228. data/lib/resources/mssql_session.rb +81 -12
  229. data/lib/resources/mysql.rb +0 -1
  230. data/lib/resources/mysql_conf.rb +1 -2
  231. data/lib/resources/mysql_session.rb +18 -7
  232. data/lib/resources/nginx_conf.rb +95 -0
  233. data/lib/resources/ntp_conf.rb +2 -3
  234. data/lib/resources/oracledb_session.rb +138 -0
  235. data/lib/resources/os_env.rb +7 -5
  236. data/lib/resources/package.rb +70 -13
  237. data/lib/resources/packages.rb +111 -0
  238. data/lib/resources/parse_config.rb +3 -4
  239. data/lib/resources/passwd.rb +4 -5
  240. data/lib/resources/pip.rb +29 -21
  241. data/lib/resources/port.rb +133 -14
  242. data/lib/resources/postgres.rb +53 -43
  243. data/lib/resources/postgres_conf.rb +40 -13
  244. data/lib/resources/postgres_hba_conf.rb +101 -0
  245. data/lib/resources/postgres_ident_conf.rb +79 -0
  246. data/lib/resources/postgres_session.rb +28 -20
  247. data/lib/resources/powershell.rb +2 -2
  248. data/lib/resources/processes.rb +61 -26
  249. data/lib/resources/rabbitmq_conf.rb +53 -0
  250. data/lib/resources/registry_key.rb +34 -11
  251. data/lib/resources/security_policy.rb +2 -2
  252. data/lib/resources/service.rb +17 -7
  253. data/lib/resources/ssh_conf.rb +17 -6
  254. data/lib/resources/ssl.rb +3 -4
  255. data/lib/resources/sys_info.rb +1 -1
  256. data/lib/resources/toml.rb +26 -0
  257. data/lib/resources/users.rb +41 -41
  258. data/lib/resources/virtualization.rb +252 -0
  259. data/lib/resources/windows_task.rb +106 -0
  260. data/lib/resources/x509_certificate.rb +143 -0
  261. data/lib/resources/xinetd.rb +18 -2
  262. data/lib/resources/xml.rb +27 -0
  263. data/lib/resources/yaml.rb +10 -2
  264. data/lib/resources/yum.rb +25 -7
  265. data/lib/resources/zfs_dataset.rb +60 -0
  266. data/lib/resources/zfs_pool.rb +49 -0
  267. data/lib/source_readers/flat.rb +2 -1
  268. data/lib/source_readers/inspec.rb +30 -11
  269. data/lib/utils/command_wrapper.rb +3 -3
  270. data/lib/utils/database_helpers.rb +77 -0
  271. data/lib/utils/erlang_parser.rb +192 -0
  272. data/lib/utils/filter.rb +2 -1
  273. data/lib/utils/filter_array.rb +0 -1
  274. data/lib/utils/find_files.rb +1 -2
  275. data/lib/utils/latest_version.rb +21 -0
  276. data/lib/utils/nginx_parser.rb +76 -0
  277. data/lib/utils/object_traversal.rb +5 -4
  278. data/lib/utils/parser.rb +23 -4
  279. data/lib/utils/plugin_registry.rb +3 -3
  280. data/lib/utils/simpleconfig.rb +25 -8
  281. data/lib/utils/spdx.rb +13 -0
  282. data/lib/utils/spdx.txt +344 -0
  283. metadata +168 -22
  284. data/docs/cli.md +0 -342
  285. data/docs/resources/process.md.erb +0 -73
  286. /data/docs/{plugin_kitchen_inspec.html.md → plugin_kitchen_inspec.md} +0 -0
@@ -9,16 +9,13 @@ module Compliance
9
9
  # implements a simple http abstraction on top of Net::HTTP
10
10
  class HTTP
11
11
  # generic get requires
12
- def self.get(url, token, insecure, basic_auth = false)
13
- uri = URI.parse(url)
12
+ def self.get(url, headers = nil, insecure)
13
+ uri = _parse_url(url)
14
14
  req = Net::HTTP::Get.new(uri.path)
15
-
16
- return send_request(uri, req, insecure) if token.nil?
17
-
18
- if basic_auth
19
- req.basic_auth(token, '')
20
- else
21
- req['Authorization'] = "Bearer #{token}"
15
+ if !headers.nil?
16
+ headers.each do |key, value|
17
+ req.add_field(key, value)
18
+ end
22
19
  end
23
20
  send_request(uri, req, insecure)
24
21
  end
@@ -26,7 +23,7 @@ module Compliance
26
23
  # generic post request
27
24
  def self.post(url, token, insecure, basic_auth = false)
28
25
  # form request
29
- uri = URI.parse(url)
26
+ uri = _parse_url(url)
30
27
  req = Net::HTTP::Post.new(uri.path)
31
28
  if basic_auth
32
29
  req.basic_auth token, ''
@@ -39,9 +36,9 @@ module Compliance
39
36
  end
40
37
 
41
38
  # post a file
42
- def self.post_file(url, token, file_path, insecure, basic_auth = false)
43
- uri = URI.parse(url)
44
- fail "Unable to parse URL: #{url}" if uri.nil? || uri.host.nil?
39
+ def self.post_file(url, headers, file_path, insecure)
40
+ uri = _parse_url(url)
41
+ raise "Unable to parse URL: #{url}" if uri.nil? || uri.host.nil?
45
42
  http = Net::HTTP.new(uri.host, uri.port)
46
43
 
47
44
  # set connection flags
@@ -49,10 +46,8 @@ module Compliance
49
46
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
50
47
 
51
48
  req = Net::HTTP::Post.new(uri.path)
52
- if basic_auth
53
- req.basic_auth token, ''
54
- else
55
- req['Authorization'] = "Bearer #{token}"
49
+ headers.each do |key, value|
50
+ req.add_field(key, value)
56
51
  end
57
52
 
58
53
  req.body_stream=File.open(file_path, 'rb')
@@ -72,11 +67,23 @@ module Compliance
72
67
  }
73
68
  opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE if insecure
74
69
 
75
- fail "Unable to parse URI: #{uri}" if uri.nil? || uri.host.nil?
76
- res = Net::HTTP.start(uri.host, uri.port, opts) {|http|
70
+ raise "Unable to parse URI: #{uri}" if uri.nil? || uri.host.nil?
71
+ res = Net::HTTP.start(uri.host, uri.port, opts) { |http|
77
72
  http.request(req)
78
73
  }
79
74
  res
75
+
76
+ rescue OpenSSL::SSL::SSLError => e
77
+ raise e unless e.message.include? 'certificate verify failed'
78
+
79
+ puts "Error: Failed to connect to #{uri}."
80
+ puts 'If the server uses a self-signed certificate, please re-run the login command with the --insecure option.'
81
+ exit 1
82
+ end
83
+
84
+ def self._parse_url(url)
85
+ url = "https://#{url}" if URI.parse(url).scheme.nil?
86
+ URI.parse(url)
80
87
  end
81
88
  end
82
89
  end
@@ -13,8 +13,7 @@ module Compliance
13
13
  class Fetcher < Fetchers::Url
14
14
  name 'compliance'
15
15
  priority 500
16
-
17
- def self.resolve(target)
16
+ def self.resolve(target) # rubocop:disable PerceivedComplexity, Metrics/CyclomaticComplexity
18
17
  uri = if target.is_a?(String) && URI(target).scheme == 'compliance'
19
18
  URI(target)
20
19
  elsif target.respond_to?(:key?) && target.key?(:compliance)
@@ -23,41 +22,54 @@ module Compliance
23
22
 
24
23
  return nil if uri.nil?
25
24
 
26
- # check if we have a compliance token
27
- config = Compliance::Configuration.new
28
- if config['token'].nil?
29
- fail Inspec::FetcherFailure, <<EOF
25
+ # we have detailed information available in our lockfile, no need to ask the server
26
+ if target.respond_to?(:key?) && target.key?(:url)
27
+ profile_fetch_url = target[:url]
28
+ config = {}
29
+ else
30
+ # check if we have a compliance token
31
+ config = Compliance::Configuration.new
32
+ if config['token'].nil? && config['refresh_token'].nil?
33
+ if config['server_type'] == 'automate'
34
+ server = 'automate'
35
+ msg = 'inspec compliance login_automate https://your_automate_server --user USER --ent ENT --dctoken DCTOKEN or --usertoken USERTOKEN'
36
+ else
37
+ server = 'compliance'
38
+ msg = "inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE' "
39
+ end
40
+ raise Inspec::FetcherFailure, <<EOF
30
41
 
31
- Cannot fetch #{uri} because your compliance token has not been
42
+ Cannot fetch #{uri} because your #{server} token has not been
32
43
  configured.
33
44
 
34
45
  Please login using
35
46
 
36
- inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE'
47
+ #{msg}
37
48
  EOF
38
- end
49
+ end
39
50
 
40
- # verifies that the target e.g base/ssh exists
41
- profile = uri.host + uri.path
42
- if !Compliance::API.exist?(config, profile)
43
- fail Inpsec::FetcherFailure, "The compliance profile #{profile} was not found on the configured compliance server"
51
+ # verifies that the target e.g base/ssh exists
52
+ profile = Compliance::API.sanitize_profile_name(uri)
53
+ if !Compliance::API.exist?(config, profile)
54
+ raise Inspec::FetcherFailure, "The compliance profile #{profile} was not found on the configured compliance server"
55
+ end
56
+ profile_fetch_url = Compliance::API.target_url(config, profile)
44
57
  end
45
- new(target_url(profile, config), config)
58
+ # We need to pass the token to the fetcher
59
+ config['token'] = Compliance::API.get_token(config)
60
+ new(profile_fetch_url, config)
46
61
  rescue URI::Error => _e
47
62
  nil
48
63
  end
49
64
 
50
- def self.target_url(profile, config)
51
- owner, id = profile.split('/')
52
- "#{config['server']}/owners/#{owner}/compliance/#{id}/tar"
53
- end
54
-
55
- #
56
65
  # We want to save compliance: in the lockfile rather than url: to
57
- # make sure we go back through the ComplianceAPI handling.
58
- #
66
+ # make sure we go back through the Compliance API handling.
59
67
  def resolved_source
60
- { compliance: supermarket_profile_name }
68
+ @resolved_source ||= {
69
+ compliance: compliance_profile_name,
70
+ url: @target,
71
+ sha256: sha256,
72
+ }
61
73
  end
62
74
 
63
75
  def to_s
@@ -66,8 +78,21 @@ EOF
66
78
 
67
79
  private
68
80
 
69
- def supermarket_profile_name
70
- m = %r{^#{@config['server']}/owners/(?<owner>[^/]+)/compliance/(?<id>[^/]+)/tar$}.match(@target)
81
+ # determine the owner_id and the profile name from the url
82
+ def compliance_profile_name
83
+ m = if Compliance::API.is_automate_server_pre_080?(@config)
84
+ %r{^#{@config['server']}/(?<owner>[^/]+)/(?<id>[^/]+)/tar$}
85
+ elsif Compliance::API.is_automate_server_080_and_later?(@config)
86
+ %r{^#{@config['server']}/profiles/(?<owner>[^/]+)/(?<id>[^/]+)/tar$}
87
+ else
88
+ %r{^#{@config['server']}/owners/(?<owner>[^/]+)/compliance/(?<id>[^/]+)/tar$}
89
+ end.match(@target)
90
+
91
+ raise 'Unable to determine compliance profile name. This can be caused by ' \
92
+ 'an incorrect server in your configuration. Try to login to compliance ' \
93
+ 'via the `inspec compliance login` or `inspec compliance login_automate` ' \
94
+ 'commands.' if m.nil?
95
+
71
96
  "#{m[:owner]}/#{m[:id]}"
72
97
  end
73
98
  end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ # author: Adam Leff
3
+
4
+ require 'thor'
5
+
6
+ module Habitat
7
+ class HabitatProfileCLI < Thor
8
+ namespace 'habitat profile'
9
+
10
+ desc 'create PATH', 'Create a Habitat artifact for the profile found at PATH'
11
+ option :output_dir, type: :string, required: false,
12
+ desc: 'Directory in which to save the generated Habitat artifact. Default: current directory'
13
+ def create(path)
14
+ Habitat::Profile.create(path, options)
15
+ end
16
+
17
+ desc 'upload PATH', 'Create a Habitat artifact for the profile found at PATH, and upload it to a Habitat Depot'
18
+ def upload(path)
19
+ Habitat::Profile.upload(path, options)
20
+ end
21
+ end
22
+
23
+ class HabitatCLI < Inspec::BaseCLI
24
+ namespace 'habitat'
25
+
26
+ desc 'profile', 'Manage InSpec profiles as Habitat artifacts'
27
+ subcommand 'profile', HabitatProfileCLI
28
+ end
29
+
30
+ Inspec::Plugins::CLI.add_subcommand(HabitatCLI, 'habitat', 'habitat SUBCOMMAND ...', 'Commands for InSpec + Habitat Integration', {})
31
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+ # author: Adam Leff
3
+
4
+ require 'mixlib/log'
5
+
6
+ module Habitat
7
+ class Log
8
+ extend Mixlib::Log
9
+ end
10
+ end
@@ -0,0 +1,372 @@
1
+ # encoding: utf-8
2
+ # author: Adam Leff
3
+
4
+ require 'inspec/profile_vendor'
5
+ require 'mixlib/shellout'
6
+ require 'toml'
7
+
8
+ module Habitat
9
+ class Profile # rubocop:disable Metrics/ClassLength
10
+ attr_reader :options, :path, :profile
11
+
12
+ def self.create(path, options = {})
13
+ creator = new(path, options)
14
+ hart_file = creator.create
15
+ creator.copy(hart_file)
16
+ ensure
17
+ creator.delete_work_dir
18
+ end
19
+
20
+ def self.upload(path, options = {})
21
+ uploader = new(path, options)
22
+ uploader.upload
23
+ ensure
24
+ uploader.delete_work_dir
25
+ end
26
+
27
+ def initialize(path, options = {})
28
+ @path = path
29
+ @options = options
30
+
31
+ log_level = options.fetch('log_level', 'info')
32
+ Habitat::Log.level(log_level.to_sym)
33
+ end
34
+
35
+ def create
36
+ Habitat::Log.info("Creating a Habitat artifact for profile: #{path}")
37
+
38
+ validate_habitat_installed
39
+ validate_habitat_origin
40
+ create_profile_object
41
+ verify_profile
42
+ vendor_profile_dependencies
43
+ copy_profile_to_work_dir
44
+ create_plan
45
+ create_run_hook
46
+ create_settings_file
47
+ create_default_config
48
+
49
+ # returns the path to the .hart file in the work directory
50
+ build_hart
51
+ rescue => e
52
+ Habitat::Log.debug(e.backtrace.join("\n"))
53
+ exit_with_error(
54
+ 'Unable to generate Habitat artifact.',
55
+ "#{e.class} -- #{e.message}",
56
+ )
57
+ end
58
+
59
+ def copy(hart_file)
60
+ validate_output_dir
61
+
62
+ Habitat::Log.info("Copying artifact to #{output_dir}...")
63
+ copy_hart(hart_file)
64
+ end
65
+
66
+ def upload
67
+ validate_habitat_auth_token
68
+ hart_file = create
69
+ upload_hart(hart_file)
70
+ rescue => e
71
+ Habitat::Log.debug(e.backtrace.join("\n"))
72
+ exit_with_error(
73
+ 'Unable to upload Habitat artifact.',
74
+ "#{e.class} -- #{e.message}",
75
+ )
76
+ end
77
+
78
+ def delete_work_dir
79
+ Habitat::Log.debug("Deleting work directory #{work_dir}")
80
+ FileUtils.rm_rf(work_dir) if Dir.exist?(work_dir)
81
+ end
82
+
83
+ private
84
+
85
+ def create_profile_object
86
+ @profile = Inspec::Profile.for_target(
87
+ path,
88
+ backend: Inspec::Backend.create(target: 'mock://'),
89
+ )
90
+ end
91
+
92
+ def verify_profile
93
+ Habitat::Log.info('Checking to see if the profile is valid...')
94
+
95
+ unless profile.check[:summary][:valid]
96
+ exit_with_error('Profile check failed. Please fix the profile before creating a Habitat artifact.')
97
+ end
98
+
99
+ Habitat::Log.info('Profile is valid.')
100
+ end
101
+
102
+ def vendor_profile_dependencies
103
+ profile_vendor = Inspec::ProfileVendor.new(path)
104
+ if profile_vendor.lockfile.exist? && profile_vendor.cache_path.exist?
105
+ Habitat::Log.info("Profile's dependencies are already vendored, skipping vendor process.")
106
+ else
107
+ Habitat::Log.info("Vendoring the profile's dependencies...")
108
+ profile_vendor.vendor!
109
+
110
+ Habitat::Log.info('Ensuring all vendored content has read permissions...')
111
+ profile_vendor.make_readable
112
+
113
+ # refresh the profile object since the profile now has new files
114
+ create_profile_object
115
+ end
116
+ end
117
+
118
+ def validate_habitat_installed
119
+ Habitat::Log.info('Checking to see if Habitat is installed...')
120
+ cmd = Mixlib::ShellOut.new('hab --version')
121
+ cmd.run_command
122
+ if cmd.error?
123
+ exit_with_error('Unable to run Habitat commands.', cmd.stderr)
124
+ end
125
+ end
126
+
127
+ def validate_habitat_origin
128
+ if habitat_origin.nil?
129
+ exit_with_error(
130
+ 'Unable to determine Habitat origin name.',
131
+ 'Run `hab setup` or set the HAB_ORIGIN environment variable.',
132
+ )
133
+ end
134
+ end
135
+
136
+ def validate_habitat_auth_token
137
+ if habitat_auth_token.nil?
138
+ exit_with_error(
139
+ 'Unable to determine Habitat auth token for publishing.',
140
+ 'Run `hab setup` or set the HAB_AUTH_TOKEN environment variable.',
141
+ )
142
+ end
143
+ end
144
+
145
+ def validate_output_dir
146
+ exit_with_error("Output directory #{output_dir} is not a directory or does not exist.") unless
147
+ File.directory?(output_dir)
148
+ end
149
+
150
+ def work_dir
151
+ return @work_dir if @work_dir
152
+
153
+ @work_dir ||= Dir.mktmpdir('inspec-habitat-exporter')
154
+ Dir.mkdir(File.join(@work_dir, 'src'))
155
+ Dir.mkdir(File.join(@work_dir, 'habitat'))
156
+ Dir.mkdir(File.join(@work_dir, 'habitat', 'config'))
157
+ Dir.mkdir(File.join(@work_dir, 'habitat', 'hooks'))
158
+ Habitat::Log.debug("Generated work directory #{@work_dir}")
159
+
160
+ @work_dir
161
+ end
162
+
163
+ def copy_profile_to_work_dir
164
+ Habitat::Log.info('Copying profile contents to the work directory...')
165
+ profile.files.each do |f|
166
+ src = File.join(profile.root_path, f)
167
+ dst = File.join(work_dir, 'src', f)
168
+ if File.directory?(f)
169
+ Habitat::Log.debug("Creating directory #{dst}")
170
+ FileUtils.mkdir_p(dst)
171
+ else
172
+ Habitat::Log.debug("Copying file #{src} to #{dst}")
173
+ FileUtils.cp_r(src, dst)
174
+ end
175
+ end
176
+ end
177
+
178
+ def create_plan
179
+ plan_file = File.join(work_dir, 'habitat', 'plan.sh')
180
+ Habitat::Log.info("Generating Habitat plan at #{plan_file}...")
181
+ File.write(plan_file, plan_contents)
182
+ end
183
+
184
+ def create_run_hook
185
+ run_hook_file = File.join(work_dir, 'habitat', 'hooks', 'run')
186
+ Habitat::Log.info("Generating a Habitat run hook at #{run_hook_file}...")
187
+ File.write(run_hook_file, run_hook_contents)
188
+ end
189
+
190
+ def create_settings_file
191
+ settings_file = File.join(work_dir, 'habitat', 'config', 'settings.sh')
192
+ Habitat::Log.info("Generating a settings file at #{settings_file}...")
193
+ File.write(settings_file, "SLEEP_TIME={{cfg.sleep_time}}\n")
194
+ end
195
+
196
+ def create_default_config
197
+ default_toml = File.join(work_dir, 'habitat', 'default.toml')
198
+ Habitat::Log.info("Generating Habitat's default.toml configuration...")
199
+ File.write(default_toml, 'sleep_time = 300')
200
+ end
201
+
202
+ def build_hart
203
+ Habitat::Log.info('Building our Habitat artifact...')
204
+
205
+ env = {
206
+ 'TERM' => 'vt100',
207
+ 'HAB_ORIGIN' => habitat_origin,
208
+ 'HAB_NONINTERACTIVE' => 'true',
209
+ }
210
+
211
+ env['RUST_LOG'] = 'debug' if Habitat::Log.level == :debug
212
+
213
+ # TODO: Would love to use Mixlib::ShellOut here, but it doesn't
214
+ # seem to preserve the STDIN tty, and docker gets angry.
215
+ Dir.chdir(work_dir) do
216
+ unless system(env, 'hab pkg build .')
217
+ exit_with_error('Unable to build the Habitat artifact.')
218
+ end
219
+ end
220
+
221
+ hart_files = Dir.glob(File.join(work_dir, 'results', '*.hart'))
222
+
223
+ if hart_files.length > 1
224
+ exit_with_error('More than one Habitat artifact was created which was not expected.')
225
+ elsif hart_files.empty?
226
+ exit_with_error('No Habitat artifact was created.')
227
+ end
228
+
229
+ hart_files.first
230
+ end
231
+
232
+ def copy_hart(working_dir_hart)
233
+ hart_basename = File.basename(working_dir_hart)
234
+ dst = File.join(output_dir, hart_basename)
235
+ FileUtils.cp(working_dir_hart, dst)
236
+
237
+ dst
238
+ end
239
+
240
+ def upload_hart(hart_file)
241
+ Habitat::Log.info('Uploading the Habitat artifact to our Depot...')
242
+
243
+ env = {
244
+ 'TERM' => 'vt100',
245
+ 'HAB_AUTH_TOKEN' => habitat_auth_token,
246
+ 'HAB_NONINTERACTIVE' => 'true',
247
+ }
248
+
249
+ env['HAB_DEPOT_URL'] = ENV['HAB_DEPOT_URL'] if ENV['HAB_DEPOT_URL']
250
+
251
+ cmd = Mixlib::ShellOut.new("hab pkg upload #{hart_file}", env: env)
252
+ cmd.run_command
253
+ if cmd.error?
254
+ exit_with_error(
255
+ 'Unable to upload Habitat artifact to the Depot.',
256
+ cmd.stdout,
257
+ cmd.stderr,
258
+ )
259
+ end
260
+
261
+ Habitat::Log.info('Upload complete!')
262
+ end
263
+
264
+ def habitat_origin
265
+ ENV['HAB_ORIGIN'] || habitat_cli_config['origin']
266
+ end
267
+
268
+ def habitat_auth_token
269
+ ENV['HAB_AUTH_TOKEN'] || habitat_cli_config['auth_token']
270
+ end
271
+
272
+ def habitat_cli_config
273
+ return @cli_config if @cli_config
274
+
275
+ config_file = File.join(ENV['HOME'], '.hab', 'etc', 'cli.toml')
276
+ return {} unless File.exist?(config_file)
277
+
278
+ @cli_config = TOML.load_file(config_file)
279
+ end
280
+
281
+ def output_dir
282
+ options[:output_dir] || Dir.pwd
283
+ end
284
+
285
+ def exit_with_error(*errors)
286
+ errors.each do |error_msg|
287
+ Habitat::Log.error(error_msg)
288
+ end
289
+
290
+ exit 1
291
+ end
292
+
293
+ def package_name
294
+ "inspec-profile-#{profile.name}"
295
+ end
296
+
297
+ def plan_contents
298
+ plan = <<-EOL
299
+ pkg_name=#{package_name}
300
+ pkg_version=#{profile.version}
301
+ pkg_origin=#{habitat_origin}
302
+ pkg_deps=(chef/inspec core/ruby core/hab)
303
+ pkg_svc_user=root
304
+ EOL
305
+
306
+ plan += "pkg_license='#{profile.metadata.params[:license]}'\n\n" if profile.metadata.params[:license]
307
+
308
+ plan += <<-EOL
309
+
310
+ do_build() {
311
+ cp -vr $PLAN_CONTEXT/../src/* $HAB_CACHE_SRC_PATH/$pkg_dirname
312
+ }
313
+
314
+ do_install() {
315
+ local profile_contents
316
+ local excludes
317
+ profile_contents=($(ls))
318
+ excludes=(habitat results *.hart)
319
+
320
+ for item in ${excludes[@]}; do
321
+ profile_contents=(${profile_contents[@]/$item/})
322
+ done
323
+
324
+ cp -r ${profile_contents[@]} ${pkg_prefix}/dist/
325
+ }
326
+ EOL
327
+
328
+ plan
329
+ end
330
+
331
+ def run_hook_contents
332
+ <<-EOL
333
+ #!/bin/sh
334
+
335
+ # redirect stderr to stdout
336
+ # ultimately, we'd like to log this somewhere useful, but due to
337
+ # https://github.com/habitat-sh/habitat/issues/2395, we need to
338
+ # avoid doing that for now.
339
+ exec 2>&1
340
+
341
+ # InSpec will try to create a .cache directory in the user's home directory
342
+ # so this needs to be someplace writeable by the hab user
343
+ export HOME={{pkg.svc_var_path}}
344
+
345
+ PROFILE_IDENT="{{pkg.origin}}/{{pkg.name}}"
346
+ RESULTS_DIR="{{pkg.svc_var_path}}/inspec_results"
347
+ RESULTS_FILE="${RESULTS_DIR}/{{pkg.name}}.json"
348
+
349
+ # Create a directory for inspec formatter output
350
+ mkdir -p {{pkg.svc_var_path}}/inspec_results
351
+
352
+ while true; do
353
+ echo "Executing InSpec for ${PROFILE_IDENT}"
354
+ inspec exec {{pkg.path}}/dist --format=json > ${RESULTS_FILE}
355
+
356
+ if [ $? -eq 0 ]; then
357
+ echo "InSpec run completed successfully."
358
+ else
359
+ echo "InSpec run did not complete successfully. If you do not see any errors above,"
360
+ echo "control failures were detected. Check the InSpec results here for details:"
361
+ echo ${RESULTS_FILE}
362
+ echo "Otherwise, troubleshoot any errors shown above."
363
+ fi
364
+
365
+ source {{pkg.svc_config_path}}/settings.sh
366
+ echo "sleeping for ${SLEEP_TIME} seconds"
367
+ sleep ${SLEEP_TIME}
368
+ done
369
+ EOL
370
+ end
371
+ end
372
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ # author: Adam Leff
3
+
4
+ libdir = File.dirname(__FILE__)
5
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
6
+
7
+ module Habitat
8
+ autoload :Log, 'inspec-habitat/log'
9
+ autoload :Profile, 'inspec-habitat/profile'
10
+ end
11
+
12
+ require 'inspec-habitat/cli'
@@ -1,6 +1,5 @@
1
1
  # encoding: utf-8
2
2
  # copyright: 2015, The Authors
3
- # license: All rights reserved
4
3
 
5
4
  title 'sample section'
6
5
 
@@ -3,6 +3,6 @@ title: InSpec Profile
3
3
  maintainer: The Authors
4
4
  copyright: The Authors
5
5
  copyright_email: you@example.com
6
- license: All Rights Reserved
6
+ license: Apache-2.0
7
7
  summary: An InSpec Compliance Profile
8
8
  version: 0.1.0
@@ -4,6 +4,7 @@
4
4
  # author: Dominik Richter
5
5
 
6
6
  require 'net/http'
7
+ require 'addressable/uri'
7
8
 
8
9
  module Supermarket
9
10
  class API
@@ -12,7 +13,7 @@ module Supermarket
12
13
  # displays a list of profiles
13
14
  def self.profiles(supermarket_url = SUPERMARKET_URL)
14
15
  url = "#{supermarket_url}/api/v1/tools-search"
15
- _success, data = get(url, { q: 'compliance_profile' })
16
+ _success, data = get(url, { type: 'compliance_profile', items: 100 })
16
17
  if !data.nil?
17
18
  profiles = JSON.parse(data)
18
19
  profiles['items'].map { |x|
@@ -26,9 +27,10 @@ module Supermarket
26
27
  end
27
28
 
28
29
  def self.profile_name(profile)
29
- uri = URI(profile)
30
+ # We use Addressable::URI here because URI has a bug in Ruby 2.1.x where it doesn't allow underscore in host
31
+ uri = Addressable::URI.parse profile
30
32
  [uri.host, uri.path[1..-1]]
31
- rescue URI::Error => _e
33
+ rescue
32
34
  nil
33
35
  end
34
36
 
@@ -50,8 +52,8 @@ module Supermarket
50
52
  supermarket_tool['tool_owner'] == tool_owner && supermarket_tool['tool'] == tool
51
53
  end
52
54
 
53
- def self.find(profile, supermarket_url)
54
- profiles = Supermarket::API.profiles(supermarket_url=SUPERMARKET_URL)
55
+ def self.find(profile, supermarket_url = SUPERMARKET_URL)
56
+ profiles = Supermarket::API.profiles(supermarket_url)
55
57
  if !profiles.empty?
56
58
  index = profiles.index { |t| same?(profile, t, supermarket_url) }
57
59
  # return profile or nil
@@ -22,7 +22,7 @@ module Supermarket
22
22
 
23
23
  headline('Available profiles:')
24
24
  supermarket_profiles.each { |p|
25
- li("#{p['tool_owner']}/#{p['slug']}")
25
+ li("#{p['tool_name']} #{mark_text(p['tool_owner'] + '/' + p['slug'])}")
26
26
  }
27
27
  end
28
28
 
@@ -45,7 +45,7 @@ module Supermarket
45
45
  "#{p['tool_owner']}/#{p['slug']}" == profile
46
46
  }
47
47
 
48
- if found.length == 0
48
+ if found.empty?
49
49
  puts "#{mark_text(profile)} is not available on Supermarket"
50
50
  return
51
51
  end