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
@@ -26,7 +26,7 @@ module Inspec
26
26
  def self.resolve(dependencies, cache, working_dir, backend)
27
27
  reqs = dependencies.map do |dep|
28
28
  req = Inspec::Requirement.from_metadata(dep, cache, cwd: working_dir, backend: backend)
29
- req || fail("Cannot initialize dependency: #{req}")
29
+ req || raise("Cannot initialize dependency: #{req}")
30
30
  end
31
31
  new.resolve(reqs)
32
32
  end
@@ -40,7 +40,7 @@ module Inspec
40
40
  else
41
41
  "the dependency information for #{path_string.split(' ').last}"
42
42
  end
43
- fail Inspec::DuplicateDep, "The dependency #{dep.name} is listed twice in #{problem_cookbook}"
43
+ raise Inspec::DuplicateDep, "The dependency #{dep.name} is listed twice in #{problem_cookbook}"
44
44
  else
45
45
  seen_items_local << dep.name
46
46
  end
@@ -65,13 +65,13 @@ module Inspec
65
65
  end
66
66
 
67
67
  if new_seen_items.key?(dep.resolved_source)
68
- fail Inspec::CyclicDependencyError, "Dependency #{dep} would cause a dependency cycle (#{new_path_string})"
68
+ raise Inspec::CyclicDependencyError, "Dependency #{dep} would cause a dependency cycle (#{new_path_string})"
69
69
  else
70
70
  new_seen_items[dep.resolved_source] = true
71
71
  end
72
72
 
73
73
  if !dep.source_satisfies_spec?
74
- fail Inspec::UnsatisfiedVersionSpecification, "The profile #{dep.name} from #{dep.resolved_source} has a version #{dep.source_version} which doesn't match #{dep.required_version}"
74
+ raise Inspec::UnsatisfiedVersionSpecification, "The profile #{dep.name} from #{dep.resolved_source} has a version #{dep.source_version} which doesn't match #{dep.required_version}"
75
75
  end
76
76
 
77
77
  Inspec::Log.debug("Adding dependency #{dep.name} (#{dep.resolved_source})")
data/lib/inspec/dsl.rb CHANGED
@@ -19,7 +19,7 @@ module Inspec::DSL
19
19
  alias include_rules include_controls
20
20
 
21
21
  def require_resource(options = {})
22
- fail 'You must specify a specific resource name when calling require_resource()' if options[:resource].nil?
22
+ raise 'You must specify a specific resource name when calling require_resource()' if options[:resource].nil?
23
23
 
24
24
  from_profile = options[:profile] || profile_name
25
25
  target_name = options[:as] || options[:resource]
@@ -33,12 +33,10 @@ module Inspec::DSL
33
33
 
34
34
  dep_entry = dependencies.list[profile_id]
35
35
  if dep_entry.nil?
36
- fail <<EOF
37
- Cannot load #{profile_id} since it is not listed as a dependency
38
- of #{bind_context.profile_name}.
36
+ raise <<EOF
37
+ Cannot load #{profile_id} since it is not listed as a dependency of #{bind_context.profile_name}.
39
38
 
40
39
  Dependencies available from this context are:
41
-
42
40
  #{dependencies.list.keys.join("\n ")}
43
41
  EOF
44
42
  end
@@ -5,6 +5,14 @@ require 'shellwords'
5
5
 
6
6
  module Inspec
7
7
  class EnvPrinter
8
+ attr_reader :shell
9
+
10
+ EVAL_COMMANDS = {
11
+ 'bash' => 'eval \"$(inspec env bash)\"',
12
+ 'fish' => 'inspec env fish > ~/.config/fish/completions/inspec.fish',
13
+ 'zsh' => 'eval \"$(inspec env zsh)\"',
14
+ }.freeze
15
+
8
16
  def initialize(command_class, shell = nil)
9
17
  if !shell
10
18
  @detected = true
@@ -56,7 +64,7 @@ module Inspec
56
64
  puts <<EOF
57
65
  # To use this, eval it in your shell
58
66
  #
59
- # eval "$(inspec env #{@shell})"
67
+ # #{EVAL_COMMANDS[shell]}
60
68
  #
61
69
  #
62
70
  EOF
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+ # copyright: 2017, Chef Software Inc.
3
+
4
+ module Inspec
5
+ module Exceptions
6
+ class SecretsBackendNotFound < ArgumentError; end
7
+ end
8
+ end
@@ -34,7 +34,7 @@ module Inspec
34
34
 
35
35
  def self.fetcher(version)
36
36
  if version != 1
37
- fail 'Only fetcher version 1 is supported!'
37
+ raise 'Only fetcher version 1 is supported!'
38
38
  end
39
39
  Inspec::Plugins::Fetcher
40
40
  end
@@ -17,19 +17,36 @@ module Inspec
17
17
  elsif File.exist?(path)
18
18
  DirProvider.new(path)
19
19
  else
20
- fail "No file provider for the provided path: #{path}"
20
+ raise "No file provider for the provided path: #{path}"
21
21
  end
22
22
  end
23
23
 
24
24
  def initialize(_path)
25
25
  end
26
26
 
27
+ # List all files that are offered.
28
+ #
29
+ # @return [Array[String]] list of file paths that are included
30
+ def files
31
+ raise "Fetcher #{self} does not implement `files()`. This is required."
32
+ end
33
+
34
+ # Read the contents of a file. This will typically refer to a text
35
+ # file reading a string.
36
+ #
37
+ # @param _file [String] path of the file to be read
38
+ # @return [String] contents of the file described
27
39
  def read(_file)
28
- fail "#{self} does not implement `read(...)`. This is required."
40
+ raise "#{self} does not implement `read(...)`. This is required."
29
41
  end
30
42
 
31
- def files
32
- fail "Fetcher #{self} does not implement `files()`. This is required."
43
+ # Provide a method for reading binary contents from a file.
44
+ # It will default to #read if not defined. For most streams that implement
45
+ # it, it will be the same. For some special cases, it will add change the
46
+ # way in which encoding of the returned data structure is handled.
47
+ # Does not work with alias nor alias_method.
48
+ def binread(file)
49
+ read(file)
33
50
  end
34
51
 
35
52
  def relative_provider
@@ -65,6 +82,12 @@ module Inspec
65
82
  return nil unless File.file?(file)
66
83
  File.read(file)
67
84
  end
85
+
86
+ def binread(file)
87
+ return nil unless files.include?(file)
88
+ return nil unless File.file?(file)
89
+ File.binread(file)
90
+ end
68
91
  end
69
92
 
70
93
  class ZipProvider < FileProvider
@@ -74,9 +97,10 @@ module Inspec
74
97
  @path = path
75
98
  @contents = {}
76
99
  @files = []
77
- ::Zip::InputStream.open(@path) do |io|
100
+ walk_zip(@path) do |io|
78
101
  while (entry = io.get_next_entry)
79
- @files.push(entry.name.sub(%r{/+$}, ''))
102
+ name = entry.name.sub(%r{/+$}, '')
103
+ @files.push(name) unless name.empty?
80
104
  end
81
105
  end
82
106
  end
@@ -87,10 +111,14 @@ module Inspec
87
111
 
88
112
  private
89
113
 
114
+ def walk_zip(path, &callback)
115
+ ::Zip::InputStream.open(path, &callback)
116
+ end
117
+
90
118
  def read_from_zip(file)
91
119
  return nil unless @files.include?(file)
92
120
  res = nil
93
- ::Zip::InputStream.open(@path) do |io|
121
+ walk_zip(@path) do |io|
94
122
  while (entry = io.get_next_entry)
95
123
  next unless file == entry.name
96
124
  res = io.read
@@ -108,8 +136,8 @@ module Inspec
108
136
  @path = path
109
137
  @contents = {}
110
138
  @files = []
111
- Gem::Package::TarReader.new(Zlib::GzipReader.open(@path)) do |tar|
112
- @files = tar.map(&:full_name)
139
+ walk_tar(@path) do |tar|
140
+ @files = tar.map(&:full_name).find_all { |x| !x.empty? }
113
141
  end
114
142
  end
115
143
 
@@ -119,11 +147,15 @@ module Inspec
119
147
 
120
148
  private
121
149
 
150
+ def walk_tar(path, &callback)
151
+ Gem::Package::TarReader.new(Zlib::GzipReader.open(path), &callback)
152
+ end
153
+
122
154
  def read_from_tar(file)
123
155
  return nil unless @files.include?(file)
124
156
  res = nil
125
157
  # NB `TarReader` includes `Enumerable` beginning with Ruby 2.x
126
- Gem::Package::TarReader.new(Zlib::GzipReader.open(@path)) do |tar|
158
+ walk_tar(@path) do |tar|
127
159
  tar.each do |entry|
128
160
  next unless entry.file? && file == entry.full_name
129
161
  res = entry.read
@@ -148,7 +180,7 @@ module Inspec
148
180
  @parent = parent_provider
149
181
  @prefix = get_prefix(parent.files)
150
182
  if @prefix.nil?
151
- fail "Could not determine path prefix for #{parent}"
183
+ raise "Could not determine path prefix for #{parent}"
152
184
  end
153
185
  @files = parent.files.find_all { |x| x.start_with?(prefix) && x != prefix }
154
186
  .map { |x| x[prefix.length..-1] }
@@ -163,6 +195,10 @@ module Inspec
163
195
  parent.read(abs_path(file))
164
196
  end
165
197
 
198
+ def binread(file)
199
+ parent.binread(abs_path(file))
200
+ end
201
+
166
202
  private
167
203
 
168
204
  def get_prefix(fs)
@@ -35,7 +35,7 @@ module Inspec
35
35
 
36
36
  c3 = Class.new do
37
37
  include Inspec::DSL::RequireOverride
38
- def initialize(require_loader) # rubocop:disable Lint/NestedMethodDefinition
38
+ def initialize(require_loader)
39
39
  @require_loader = require_loader
40
40
  end
41
41
  end
@@ -1,20 +1,23 @@
1
1
  # encoding: utf-8
2
- # Copyright 2015 Dominik Richter. All rights reserved.
2
+ # Copyright 2015 Dominik Richter
3
3
  # author: Dominik Richter
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  require 'logger'
7
7
  require 'rubygems/version'
8
8
  require 'rubygems/requirement'
9
+ require 'semverse'
10
+ require 'utils/spdx'
9
11
 
10
12
  module Inspec
11
13
  # Extract metadata.rb information
12
14
  class Metadata # rubocop:disable Metrics/ClassLength
13
15
  attr_reader :ref
14
- attr_accessor :params
16
+ attr_accessor :params, :content
15
17
  def initialize(ref, logger = nil)
16
18
  @ref = ref
17
19
  @logger = logger || Logger.new(nil)
20
+ @content = ''
18
21
  @params = {}
19
22
  @missing_methods = []
20
23
  end
@@ -68,6 +71,7 @@ module Inspec
68
71
  os.method(family_check).call
69
72
  )
70
73
 
74
+ # ensure we do have a string if we have a non-nil value eg. 16.06
71
75
  release_ok = release.nil? || os[:release] == release
72
76
 
73
77
  # we want to make sure that all matchers are true
@@ -99,7 +103,7 @@ module Inspec
99
103
  end
100
104
 
101
105
  # return all warn and errors
102
- def valid
106
+ def valid # rubocop:disable Metrics/AbcSize
103
107
  errors = []
104
108
  warnings = []
105
109
 
@@ -107,11 +111,22 @@ module Inspec
107
111
  next unless params[field.to_sym].nil?
108
112
  errors.push("Missing profile #{field} in #{ref}")
109
113
  end
110
- %w{ title summary maintainer copyright }.each do |field|
114
+
115
+ # if version is set, ensure it is correct
116
+ if !params[:version].nil? && !valid_version?(params[:version])
117
+ errors.push('Version needs to be in SemVer format')
118
+ end
119
+
120
+ %w{ title summary maintainer copyright license }.each do |field|
111
121
  next unless params[field.to_sym].nil?
112
122
  warnings.push("Missing profile #{field} in #{ref}")
113
123
  end
114
124
 
125
+ # if version is set, ensure it is in SPDX format
126
+ if !params[:license].nil? && !Spdx.valid_license?(params[:license])
127
+ warnings.push("License '#{params[:license]}' needs to be in SPDX format. See https://spdx.org/licenses/.")
128
+ end
129
+
115
130
  [errors, warnings]
116
131
  end
117
132
 
@@ -121,6 +136,13 @@ module Inspec
121
136
  errors.empty? && unsupported.empty?
122
137
  end
123
138
 
139
+ def valid_version?(value)
140
+ Semverse::Version.new(value)
141
+ true
142
+ rescue Semverse::InvalidVersionFormat
143
+ false
144
+ end
145
+
124
146
  def method_missing(sth, *args)
125
147
  @logger.warn "#{ref} doesn't support: #{sth} #{args}"
126
148
  @missing_methods.push(sth)
@@ -143,7 +165,9 @@ module Inspec
143
165
 
144
166
  def self.finalize_supports_elem(elem, logger)
145
167
  case x = elem
146
- when Hash then x
168
+ when Hash
169
+ x[:release] = x[:release].to_s if x[:release]
170
+ x
147
171
  when Array
148
172
  logger.warn(
149
173
  'Failed to read supports entry that is an array. Please use '\
@@ -162,7 +186,7 @@ module Inspec
162
186
 
163
187
  def self.finalize_supports(supports, logger)
164
188
  case x = supports
165
- when Hash then [x]
189
+ when Hash then [finalize_supports_elem(x, logger)]
166
190
  when Array then x.map { |e| finalize_supports_elem(e, logger) }.compact
167
191
  when nil then []
168
192
  else
@@ -174,37 +198,57 @@ module Inspec
174
198
  end
175
199
  end
176
200
 
177
- def self.finalize(metadata, profile_id, logger = nil)
201
+ def self.finalize_name(metadata, profile_id, original_target)
202
+ # profile_id always overwrites whatever already exists as the name
203
+ unless profile_id.to_s.empty?
204
+ metadata.params[:name] = profile_id.to_s
205
+ return
206
+ end
207
+
208
+ # don't overwrite an existing name
209
+ return unless metadata.params[:name].nil?
210
+
211
+ # if there's a title, there is no need to set a name too
212
+ return unless metadata.params[:title].nil?
213
+
214
+ # create a new name based on the original target if it exists
215
+ metadata.params[:name] = "tests from #{original_target}" unless original_target.to_s.empty?
216
+ end
217
+
218
+ def self.finalize(metadata, profile_id, options, logger = nil)
178
219
  return nil if metadata.nil?
179
220
  param = metadata.params || {}
180
- param['name'] = profile_id.to_s unless profile_id.to_s.empty?
221
+ options ||= {}
181
222
  param['version'] = param['version'].to_s unless param['version'].nil?
182
223
  metadata.params = symbolize_keys(param)
183
224
  metadata.params[:supports] = finalize_supports(metadata.params[:supports], logger)
225
+ finalize_name(metadata, profile_id, options[:target])
184
226
 
185
227
  metadata
186
228
  end
187
229
 
188
- def self.from_yaml(ref, contents, profile_id, logger = nil)
230
+ def self.from_yaml(ref, content, profile_id, logger = nil)
189
231
  res = Metadata.new(ref, logger)
190
- res.params = YAML.load(contents)
191
- finalize(res, profile_id, logger)
232
+ res.params = YAML.load(content)
233
+ res.content = content
234
+ finalize(res, profile_id, {}, logger)
192
235
  end
193
236
 
194
- def self.from_ruby(ref, contents, profile_id, logger = nil)
237
+ def self.from_ruby(ref, content, profile_id, logger = nil)
195
238
  res = Metadata.new(ref, logger)
196
- res.instance_eval(contents, ref, 1)
197
- finalize(res, profile_id, logger)
239
+ res.instance_eval(content, ref, 1)
240
+ res.content = content
241
+ finalize(res, profile_id, {}, logger)
198
242
  end
199
243
 
200
- def self.from_ref(ref, contents, profile_id, logger = nil)
244
+ def self.from_ref(ref, content, profile_id, logger = nil)
201
245
  # NOTE there doesn't have to exist an actual file, it may come from an
202
- # archive (i.e., contents)
246
+ # archive (i.e., content)
203
247
  case File.basename(ref)
204
248
  when 'inspec.yml'
205
- from_yaml(ref, contents, profile_id, logger)
249
+ from_yaml(ref, content, profile_id, logger)
206
250
  when 'metadata.rb'
207
- from_ruby(ref, contents, profile_id, logger)
251
+ from_ruby(ref, content, profile_id, logger)
208
252
  else
209
253
  logger ||= Logger.new(nil)
210
254
  logger.error "Don't know how to handle metadata in #{ref}"
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ module Inspec
6
+ module MethodSource
7
+ def self.code_at(location, source_reader)
8
+ # TODO: logger for these cases
9
+ return '' if location.nil? || location[:ref].nil? || location[:line].nil?
10
+ return '' unless source_reader && source_reader.target
11
+
12
+ # TODO: Non-controls still need more detection
13
+ ref = location[:ref]
14
+ ref = ref.sub(source_reader.target.prefix, '')
15
+ src = source_reader.tests[ref]
16
+ return '' if src.nil?
17
+
18
+ ::MethodSource.expression_at(src.lines, location[:line]).force_encoding('utf-8')
19
+ rescue SyntaxError => e
20
+ raise ::MethodSource::SourceNotFoundError,
21
+ "Could not parse source at #{location[:ref]}:#{location[:line]}: #{e.message}"
22
+ end
23
+ end
24
+ end
@@ -21,6 +21,18 @@ module Inspec
21
21
  @opts[:default]
22
22
  end
23
23
 
24
+ def title
25
+ @opts[:title]
26
+ end
27
+
28
+ def description
29
+ @opts[:description]
30
+ end
31
+
32
+ def ruby_var_identifier
33
+ 'attr_' + @name.downcase.strip.gsub(/\s+/, '-').gsub(/[^\w-]/, '')
34
+ end
35
+
24
36
  def to_hash
25
37
  {
26
38
  name: @name,
@@ -28,6 +40,15 @@ module Inspec
28
40
  }
29
41
  end
30
42
 
43
+ def to_ruby
44
+ res = ["#{ruby_var_identifier} = attribute('#{@name}',{"]
45
+ res.push " title: '#{title}'," unless title.to_s.empty?
46
+ res.push " default: '#{default}'," unless default.to_s.empty?
47
+ res.push " description: '#{description}'," unless description.to_s.empty?
48
+ res.push '})'
49
+ res.join("\n")
50
+ end
51
+
31
52
  def to_s
32
53
  "Attribute #{@name} with #{@value}"
33
54
  end
@@ -2,24 +2,30 @@
2
2
 
3
3
  module Inspec
4
4
  class Control
5
- attr_accessor :id, :title, :desc, :impact, :tests
5
+ attr_accessor :id, :title, :desc, :impact, :tests, :tags
6
6
  def initialize
7
7
  @tests = []
8
+ @tags = []
8
9
  end
9
10
 
10
11
  def add_test(t)
11
12
  @tests.push(t)
12
13
  end
13
14
 
15
+ def add_tag(t)
16
+ @tags.push(t)
17
+ end
18
+
14
19
  def to_hash
15
- { id: id, title: title, desc: desc, impact: impact, tests: tests.map(&:to_hash) }
20
+ { id: id, title: title, desc: desc, impact: impact, tests: tests.map(&:to_hash), tags: tags.map(&:to_hash) }
16
21
  end
17
22
 
18
23
  def to_ruby
19
24
  res = ["control #{id.inspect} do"]
20
25
  res.push " title #{title.inspect}" unless title.to_s.empty?
21
- res.push " desc #{desc.inspect}" unless desc.to_s.empty?
26
+ res.push " desc #{prettyprint_text(desc, 2)}" unless desc.to_s.empty?
22
27
  res.push " impact #{impact}" unless impact.nil?
28
+ tags.each { |t| res.push(indent(t.to_ruby, 2)) }
23
29
  tests.each { |t| res.push(indent(t.to_ruby, 2)) }
24
30
  res.push 'end'
25
31
  res.join("\n")
@@ -27,6 +33,18 @@ module Inspec
27
33
 
28
34
  private
29
35
 
36
+ # Pretty-print a text block of InSpec code
37
+ #
38
+ # @param s [String] should not be empty
39
+ # @param depth [Int] indentation length for multiline text blocks
40
+ # @return [String] pretty-printed textblock
41
+ def prettyprint_text(s, depth)
42
+ txt = s.to_s.inspect.gsub('\n', "\n")
43
+ return txt if !txt.include?("\n")
44
+ middle = indent(txt[1..-2], depth+2)
45
+ txt[0] + "\n" + middle + "\n" + ' '*depth + txt[-1]
46
+ end
47
+
30
48
  def indent(txt, d)
31
49
  dt = ' '*d
32
50
  dt + txt.gsub("\n", "\n"+dt)
@@ -2,7 +2,8 @@
2
2
 
3
3
  module Inspec
4
4
  class EachLoop < List
5
- attr_reader :tests, :variables
5
+ attr_reader :variables
6
+ attr_accessor :tests
6
7
  def initialize
7
8
  super
8
9
  @tests = []
@@ -3,7 +3,7 @@
3
3
  module Inspec
4
4
  class List < Value
5
5
  def map
6
- fail 'Inspec::List.map needs to be called with a block' unless block_given?
6
+ raise 'Inspec::List.map needs to be called with a block' unless block_given?
7
7
  t = List.new
8
8
  t.qualifier = [['x']]
9
9
  yield(t)
@@ -5,15 +5,30 @@ module Inspec
5
5
  attr_reader :tests
6
6
  def initialize(tests)
7
7
  @tests = tests
8
+ @negated = false
8
9
  end
9
10
 
10
11
  def skip
11
12
  nil
12
13
  end
13
14
 
15
+ def negate!
16
+ @negated = !@negated
17
+ end
18
+
14
19
  def to_ruby
15
- all_tests = @tests.map(&:to_ruby).join("\n").gsub("\n", "\n ")
16
- format("describe.one do\n %s\nend", all_tests)
20
+ if @negated
21
+ # We don't use the describe.one wrapper when negated because:
22
+ # !(test1 || test2) same as (!test1 && !test2) where && is implicit in inspec
23
+ all_tests = @tests.map { |test|
24
+ test.negate!
25
+ test
26
+ }.map(&:to_ruby).join("\n")
27
+ return all_tests
28
+ else
29
+ all_tests = @tests.map(&:to_ruby).join("\n").gsub("\n", "\n ")
30
+ return format("describe.one do\n %s\nend", all_tests)
31
+ end
17
32
  end
18
33
 
19
34
  def to_hash
@@ -0,0 +1,27 @@
1
+ # encoding:utf-8
2
+
3
+ module Inspec
4
+ class Tag
5
+ attr_accessor :key, :value
6
+
7
+ def initialize(key, value)
8
+ @key = key
9
+ @value = value
10
+ end
11
+
12
+ def to_hash
13
+ {
14
+ name: key,
15
+ value: value,
16
+ }
17
+ end
18
+
19
+ def to_ruby
20
+ "tag #{key.inspect}: #{value.inspect}"
21
+ end
22
+
23
+ def to_s
24
+ "Tag #{key} with #{value}"
25
+ end
26
+ end
27
+ end
@@ -53,7 +53,7 @@ module Inspec
53
53
  # this will go in its()
54
54
  xres = last_call
55
55
  else
56
- res += '.' + ruby_qualifier(last)
56
+ res += '.' + ruby_qualifier(last) unless last_call.empty?
57
57
  end
58
58
  end
59
59
 
@@ -66,13 +66,14 @@ module Inspec
66
66
  res, xtra = describe_chain
67
67
  itsy = xtra.nil? ? 'it' : 'its(' + xtra.to_s.inspect + ')'
68
68
  naughty = @negated ? '_not' : ''
69
- xpect = defined?(@expectation) ? expectation.inspect : ''
70
- if matcher == 'match'
71
- # without this, xpect values like / \/zones\// will not be parsed properly
72
- xpect = "(#{xpect})"
73
- elsif xpect != ''
74
- xpect = ' ' + xpect
75
- end
69
+ xpect = if !defined?(@expectation)
70
+ ''
71
+ elsif @expectation.class == Regexp
72
+ # without this, xpect values like / \/zones\// will not be parsed properly
73
+ "(#{@expectation.inspect})"
74
+ elsif xpect != ''
75
+ ' ' + expectation.inspect
76
+ end
76
77
  format("%sdescribe %s do\n %s { should%s %s%s }\nend",
77
78
  vars, res, itsy, naughty, matcher, xpect)
78
79
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Inspec
4
4
  autoload :Attribute, 'inspec/objects/attribute'
5
+ autoload :Tag, 'inspec/objects/tag'
5
6
  autoload :Control, 'inspec/objects/control'
6
7
  autoload :EachLoop, 'inspec/objects/each_loop'
7
8
  autoload :List, 'inspec/objects/list'