inspec 1.31.0 → 1.45.9

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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +2599 -1
  4. data/Gemfile +0 -1
  5. data/README.md +24 -34
  6. data/Rakefile +0 -31
  7. data/docs/dsl_inspec.md +10 -0
  8. data/docs/matchers.md +29 -0
  9. data/docs/plugin_kitchen_inspec.md +18 -24
  10. data/docs/profiles.md +101 -81
  11. data/docs/resources/aide_conf.md.erb +71 -0
  12. data/docs/resources/apache_conf.md.erb +19 -33
  13. data/docs/resources/apt.md.erb +22 -36
  14. data/docs/resources/audit_policy.md.erb +9 -24
  15. data/docs/resources/auditd.md.erb +78 -0
  16. data/docs/resources/auditd_conf.md.erb +20 -34
  17. data/docs/resources/auditd_rules.md.erb +8 -24
  18. data/docs/resources/bash.md.erb +4 -26
  19. data/docs/resources/bond.md.erb +25 -40
  20. data/docs/resources/bridge.md.erb +5 -25
  21. data/docs/resources/bsd_service.md.erb +5 -25
  22. data/docs/resources/command.md.erb +35 -50
  23. data/docs/resources/cpan.md.erb +62 -0
  24. data/docs/resources/cran.md.erb +54 -0
  25. data/docs/resources/crontab.md.erb +9 -23
  26. data/docs/resources/csv.md.erb +12 -27
  27. data/docs/resources/dh_params.md +1 -0
  28. data/docs/resources/directory.md.erb +5 -25
  29. data/docs/resources/docker.md.erb +61 -58
  30. data/docs/resources/docker_container.md.erb +23 -19
  31. data/docs/resources/docker_image.md.erb +20 -16
  32. data/docs/resources/elasticsearch.md.erb +245 -0
  33. data/docs/resources/etc_fstab.md.erb +120 -0
  34. data/docs/resources/etc_group.md.erb +29 -45
  35. data/docs/resources/etc_hosts.md.erb +68 -0
  36. data/docs/resources/etc_hosts_allow.md.erb +68 -0
  37. data/docs/resources/etc_hosts_deny.md.erb +68 -0
  38. data/docs/resources/file.md.erb +227 -216
  39. data/docs/resources/firewalld.md.erb +104 -0
  40. data/docs/resources/gem.md.erb +21 -35
  41. data/docs/resources/group.md.erb +16 -30
  42. data/docs/resources/grub_conf.md.erb +9 -24
  43. data/docs/resources/host.md.erb +32 -49
  44. data/docs/resources/http.md.erb +38 -44
  45. data/docs/resources/iis_app.md.erb +25 -35
  46. data/docs/resources/iis_site.md.erb +26 -40
  47. data/docs/resources/inetd_conf.md.erb +27 -42
  48. data/docs/resources/ini.md.erb +9 -23
  49. data/docs/resources/interface.md.erb +5 -25
  50. data/docs/resources/iptables.md.erb +15 -29
  51. data/docs/resources/json.md.erb +12 -27
  52. data/docs/resources/kernel_module.md.erb +47 -61
  53. data/docs/resources/kernel_parameter.md.erb +15 -29
  54. data/docs/resources/key_rsa.md.erb +3 -0
  55. data/docs/resources/launchd_service.md.erb +5 -25
  56. data/docs/resources/limits_conf.md.erb +15 -29
  57. data/docs/resources/login_def.md.erb +15 -30
  58. data/docs/resources/mount.md.erb +18 -33
  59. data/docs/resources/mssql_session.md.erb +9 -12
  60. data/docs/resources/mysql_conf.md.erb +28 -32
  61. data/docs/resources/mysql_session.md.erb +15 -29
  62. data/docs/resources/nginx.md.erb +78 -0
  63. data/docs/resources/nginx_conf.md.erb +127 -0
  64. data/docs/resources/npm.md.erb +19 -35
  65. data/docs/resources/ntp_conf.md.erb +20 -37
  66. data/docs/resources/oneget.md.erb +15 -30
  67. data/docs/resources/oracledb_session.md.erb +9 -11
  68. data/docs/resources/os.md.erb +29 -43
  69. data/docs/resources/os_env.md.erb +23 -44
  70. data/docs/resources/package.md.erb +34 -36
  71. data/docs/resources/parse_config.md.erb +5 -25
  72. data/docs/resources/parse_config_file.md.erb +31 -43
  73. data/docs/resources/{etc_passwd.md.erb → passwd.md.erb} +24 -39
  74. data/docs/resources/pip.md.erb +20 -35
  75. data/docs/resources/port.md.erb +43 -57
  76. data/docs/resources/postgres_conf.md.erb +17 -31
  77. data/docs/resources/postgres_hba_conf.md.erb +26 -38
  78. data/docs/resources/postgres_ident_conf.md.erb +25 -37
  79. data/docs/resources/postgres_session.md.erb +15 -29
  80. data/docs/resources/powershell.md.erb +27 -42
  81. data/docs/resources/processes.md.erb +17 -33
  82. data/docs/resources/rabbitmq_config.md.erb +9 -24
  83. data/docs/resources/registry_key.md.erb +27 -42
  84. data/docs/resources/runit_service.md.erb +5 -25
  85. data/docs/resources/security_policy.md.erb +12 -27
  86. data/docs/resources/service.md.erb +27 -42
  87. data/docs/resources/{etc_shadow.md.erb → shadow.md.erb} +41 -47
  88. data/docs/resources/ssh_config.md.erb +19 -34
  89. data/docs/resources/sshd_config.md.erb +19 -34
  90. data/docs/resources/ssl.md.erb +39 -54
  91. data/docs/resources/sys_info.md.erb +12 -26
  92. data/docs/resources/systemd_service.md.erb +5 -25
  93. data/docs/resources/sysv_service.md.erb +5 -25
  94. data/docs/resources/upstart_service.md.erb +5 -25
  95. data/docs/resources/user.md.erb +29 -44
  96. data/docs/resources/users.md.erb +12 -26
  97. data/docs/resources/vbscript.md.erb +9 -24
  98. data/docs/resources/virtualization.md.erb +8 -23
  99. data/docs/resources/windows_feature.md.erb +15 -30
  100. data/docs/resources/windows_hotfix.md.erb +50 -0
  101. data/docs/resources/windows_task.md.erb +12 -26
  102. data/docs/resources/wmi.md.erb +9 -24
  103. data/docs/resources/x509_certificate.md.erb +4 -0
  104. data/docs/resources/xinetd_conf.md.erb +65 -80
  105. data/docs/resources/xml.md.erb +61 -0
  106. data/docs/resources/yaml.md.erb +12 -27
  107. data/docs/resources/yum.md.erb +37 -51
  108. data/docs/resources/zfs_dataset.md.erb +15 -26
  109. data/docs/resources/zfs_pool.md.erb +9 -20
  110. data/docs/ruby_usage.md +0 -1
  111. data/examples/meta-profile/README.md +1 -1
  112. data/examples/profile-sensitive/README.md +29 -0
  113. data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -0
  114. data/examples/profile-sensitive/controls/sensitive.rb +9 -0
  115. data/examples/profile-sensitive/inspec.yml +8 -0
  116. data/inspec.gemspec +3 -3
  117. data/lib/bundles/inspec-artifact/cli.rb +5 -5
  118. data/lib/bundles/inspec-compliance/README.md +24 -22
  119. data/lib/bundles/inspec-compliance/api/login.rb +152 -0
  120. data/lib/bundles/inspec-compliance/api.rb +46 -16
  121. data/lib/bundles/inspec-compliance/cli.rb +58 -165
  122. data/lib/bundles/inspec-compliance/target.rb +2 -3
  123. data/lib/bundles/inspec-habitat/cli.rb +7 -2
  124. data/lib/bundles/inspec-habitat/profile.rb +44 -20
  125. data/lib/bundles/inspec-init/templates/profile/README.md +1 -1
  126. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +1 -1
  127. data/lib/bundles/inspec-supermarket/api.rb +1 -1
  128. data/lib/inspec/backend.rb +8 -0
  129. data/lib/inspec/cli.rb +1 -1
  130. data/lib/inspec/control_eval_context.rb +16 -6
  131. data/lib/inspec/dependencies/requirement.rb +22 -8
  132. data/lib/inspec/dependencies/resolver.rb +1 -1
  133. data/lib/inspec/exceptions.rb +4 -0
  134. data/lib/inspec/file_provider.rb +38 -9
  135. data/lib/inspec/metadata.rb +17 -3
  136. data/lib/inspec/objects/attribute.rb +16 -7
  137. data/lib/inspec/objects/control.rb +10 -2
  138. data/lib/inspec/plugins/resource.rb +34 -5
  139. data/lib/inspec/profile.rb +10 -1
  140. data/lib/inspec/profile_context.rb +3 -1
  141. data/lib/inspec/resource.rb +17 -5
  142. data/lib/inspec/rspec_json_formatter.rb +48 -14
  143. data/lib/inspec/rule.rb +4 -0
  144. data/lib/inspec/runner.rb +72 -30
  145. data/lib/inspec/shell.rb +24 -19
  146. data/lib/inspec/version.rb +1 -1
  147. data/lib/matchers/matchers.rb +40 -0
  148. data/lib/resources/aide_conf.rb +162 -0
  149. data/lib/resources/auditd.rb +231 -0
  150. data/lib/resources/auditd_rules.rb +3 -1
  151. data/lib/resources/command.rb +1 -1
  152. data/lib/resources/cpan.rb +60 -0
  153. data/lib/resources/cran.rb +66 -0
  154. data/lib/resources/docker.rb +8 -1
  155. data/lib/resources/docker_container.rb +19 -2
  156. data/lib/resources/elasticsearch.rb +172 -0
  157. data/lib/resources/etc_fstab.rb +102 -0
  158. data/lib/resources/etc_group.rb +1 -1
  159. data/lib/resources/etc_hosts.rb +81 -0
  160. data/lib/resources/etc_hosts_allow_deny.rb +122 -0
  161. data/lib/resources/file.rb +87 -10
  162. data/lib/resources/firewalld.rb +144 -0
  163. data/lib/resources/groups.rb +0 -1
  164. data/lib/resources/host.rb +30 -15
  165. data/lib/resources/http.rb +178 -22
  166. data/lib/resources/mssql_session.rb +3 -3
  167. data/lib/resources/mysql_conf.rb +10 -0
  168. data/lib/resources/mysql_session.rb +1 -1
  169. data/lib/resources/nginx.rb +97 -0
  170. data/lib/resources/nginx_conf.rb +134 -1
  171. data/lib/resources/oracledb_session.rb +9 -8
  172. data/lib/resources/os_env.rb +7 -4
  173. data/lib/resources/package.rb +14 -2
  174. data/lib/resources/packages.rb +1 -1
  175. data/lib/resources/pip.rb +28 -20
  176. data/lib/resources/port.rb +251 -3
  177. data/lib/resources/postgres.rb +1 -1
  178. data/lib/resources/postgres_session.rb +1 -1
  179. data/lib/resources/processes.rb +106 -20
  180. data/lib/resources/registry_key.rb +11 -2
  181. data/lib/resources/service.rb +5 -24
  182. data/lib/resources/ssh_conf.rb +1 -1
  183. data/lib/resources/ssl.rb +4 -3
  184. data/lib/resources/toml.rb +2 -2
  185. data/lib/resources/windows_hotfix.rb +35 -0
  186. data/lib/resources/xinetd.rb +4 -6
  187. data/lib/resources/xml.rb +27 -0
  188. data/lib/source_readers/inspec.rb +3 -3
  189. data/lib/utils/find_files.rb +2 -1
  190. data/lib/utils/nginx_parser.rb +10 -1
  191. data/lib/utils/object_traversal.rb +35 -10
  192. data/lib/utils/parser.rb +21 -3
  193. metadata +48 -18
  194. data/docs/cli.md +0 -392
@@ -71,7 +71,7 @@ module Inspec
71
71
  end
72
72
 
73
73
  if !dep.source_satisfies_spec?
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}"
74
+ raise Inspec::UnsatisfiedVersionSpecification, "The profile #{dep.name} from #{dep.resolved_source} has a version #{dep.source_version} which doesn't match #{dep.version_constraints}"
75
75
  end
76
76
 
77
77
  Inspec::Log.debug("Adding dependency #{dep.name} (#{dep.resolved_source})")
@@ -3,6 +3,10 @@
3
3
 
4
4
  module Inspec
5
5
  module Exceptions
6
+ class AttributesFileDoesNotExist < ArgumentError; end
7
+ class AttributesFileNotReadable < ArgumentError; end
8
+ class ResourceFailed < StandardError; end
9
+ class ResourceSkipped < StandardError; end
6
10
  class SecretsBackendNotFound < ArgumentError; end
7
11
  end
8
12
  end
@@ -72,7 +72,7 @@ module Inspec
72
72
  @files = if File.file?(path)
73
73
  [path]
74
74
  else
75
- Dir[File.join(path, '**', '*')]
75
+ Dir[File.join(Shellwords.shellescape(path), '**', '*')]
76
76
  end
77
77
  @path = path
78
78
  end
@@ -97,9 +97,10 @@ module Inspec
97
97
  @path = path
98
98
  @contents = {}
99
99
  @files = []
100
- ::Zip::InputStream.open(@path) do |io|
100
+ walk_zip(@path) do |io|
101
101
  while (entry = io.get_next_entry)
102
- @files.push(entry.name.sub(%r{/+$}, ''))
102
+ name = entry.name.sub(%r{/+$}, '')
103
+ @files.push(name) unless name.empty?
103
104
  end
104
105
  end
105
106
  end
@@ -110,10 +111,14 @@ module Inspec
110
111
 
111
112
  private
112
113
 
114
+ def walk_zip(path, &callback)
115
+ ::Zip::InputStream.open(path, &callback)
116
+ end
117
+
113
118
  def read_from_zip(file)
114
119
  return nil unless @files.include?(file)
115
120
  res = nil
116
- ::Zip::InputStream.open(@path) do |io|
121
+ walk_zip(@path) do |io|
117
122
  while (entry = io.get_next_entry)
118
123
  next unless file == entry.name
119
124
  res = io.read
@@ -131,8 +136,17 @@ module Inspec
131
136
  @path = path
132
137
  @contents = {}
133
138
  @files = []
134
- Gem::Package::TarReader.new(Zlib::GzipReader.open(@path)) do |tar|
135
- @files = tar.map(&:full_name)
139
+ walk_tar(@path) do |tar|
140
+ @files = tar.find_all(&:file?)
141
+
142
+ # delete all entries with no name
143
+ @files = @files.find_all { |x| !x.full_name.empty? }
144
+
145
+ # delete all entries that have a PaxHeader
146
+ @files = @files.delete_if { |x| x.full_name.include?('PaxHeader/') }
147
+
148
+ # replace all items of the array simply with the relative filename of the file
149
+ @files.map! { |x| Pathname.new(x.full_name).relative_path_from(Pathname.new('.')).to_s }
136
150
  end
137
151
  end
138
152
 
@@ -142,13 +156,17 @@ module Inspec
142
156
 
143
157
  private
144
158
 
159
+ def walk_tar(path, &callback)
160
+ Gem::Package::TarReader.new(Zlib::GzipReader.open(path), &callback)
161
+ end
162
+
145
163
  def read_from_tar(file)
146
164
  return nil unless @files.include?(file)
147
165
  res = nil
148
166
  # NB `TarReader` includes `Enumerable` beginning with Ruby 2.x
149
- Gem::Package::TarReader.new(Zlib::GzipReader.open(@path)) do |tar|
167
+ walk_tar(@path) do |tar|
150
168
  tar.each do |entry|
151
- next unless entry.file? && file == entry.full_name
169
+ next unless entry.file? && [file, "./#{file}"].include?(entry.full_name)
152
170
  res = entry.read
153
171
  break
154
172
  end
@@ -173,8 +191,19 @@ module Inspec
173
191
  if @prefix.nil?
174
192
  raise "Could not determine path prefix for #{parent}"
175
193
  end
176
- @files = parent.files.find_all { |x| x.start_with?(prefix) && x != prefix }
194
+
195
+ # select all files that begin with the prefix, and strip off the prefix from the file.
196
+ #
197
+ # strip off any leading top-level relative path (./) which is common in
198
+ # PAX-formatted tar files. Do not do any translation of the path if the
199
+ # path is an absolute path.
200
+ @files = parent.files
201
+ .find_all { |x| x.start_with?(prefix) && x != prefix }
177
202
  .map { |x| x[prefix.length..-1] }
203
+ .map do |x|
204
+ path = Pathname.new(x)
205
+ path.absolute? ? path.to_s : path.relative_path_from(Pathname.new('.')).to_s
206
+ end
178
207
  end
179
208
 
180
209
  def abs_path(file)
@@ -11,6 +11,9 @@ require 'utils/spdx'
11
11
 
12
12
  module Inspec
13
13
  # Extract metadata.rb information
14
+ # A Metadata object may be created and finalized with invalid data.
15
+ # This allows the check CLI command to analyse the issues.
16
+ # Use valid? to determine if the metadata is coherent.
14
17
  class Metadata # rubocop:disable Metrics/ClassLength
15
18
  attr_reader :ref
16
19
  attr_accessor :params, :content
@@ -112,6 +115,12 @@ module Inspec
112
115
  errors.push("Missing profile #{field} in #{ref}")
113
116
  end
114
117
 
118
+ if params[:name] =~ %r{[\/\\]}
119
+ warnings.push("Your profile name (#{params[:name]}) contains a slash " \
120
+ 'which will not be permitted in InSpec 2.0. Please change your profile ' \
121
+ 'name in the `inspec.yml` file.')
122
+ end
123
+
115
124
  # if version is set, ensure it is correct
116
125
  if !params[:version].nil? && !valid_version?(params[:version])
117
126
  errors.push('Version needs to be in SemVer format')
@@ -180,7 +189,7 @@ module Inspec
180
189
  logger.warn(
181
190
  "Do not use deprecated `supports: #{x}` syntax. Instead use:\n"\
182
191
  "supports:\n - os-family: #{x}\n\n")
183
- { :'os-family' => x }
192
+ { :'os-family' => x } # rubocop:disable Style/HashSyntax
184
193
  end
185
194
  end
186
195
 
@@ -194,7 +203,7 @@ module Inspec
194
203
  logger.warn(
195
204
  "Do not use deprecated `supports: #{x}` syntax. Instead use:\n"\
196
205
  "supports:\n - os-family: #{x}\n\n")
197
- [{ :'os-family' => x }]
206
+ [{ :'os-family' => x }] # rubocop:disable Style/HashSyntax
198
207
  end
199
208
  end
200
209
 
@@ -212,7 +221,12 @@ module Inspec
212
221
  return unless metadata.params[:title].nil?
213
222
 
214
223
  # 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?
224
+ # Crudely slug the target to not contain slashes, to avoid breaking
225
+ # unit tests that look for warning sequences
226
+ unless original_target.to_s.empty?
227
+ metadata.params[:title] = "tests from #{original_target}"
228
+ metadata.params[:name] = metadata.params[:title].gsub(%r{[\\\/]}, '.')
229
+ end
216
230
  end
217
231
 
218
232
  def self.finalize(metadata, profile_id, options, logger = nil)
@@ -3,22 +3,31 @@
3
3
  module Inspec
4
4
  class Attribute
5
5
  attr_accessor :name
6
- def initialize(name, options)
6
+ attr_writer :value
7
+
8
+ DEFAULT_ATTRIBUTE = Class.new do
9
+ def method_missing(*_)
10
+ self
11
+ end
12
+
13
+ def respond_to_missing?(_, _)
14
+ true
15
+ end
16
+ end
17
+
18
+ def initialize(name, options = {})
7
19
  @name = name
8
20
  @opts = options
9
21
  @value = nil
10
22
  end
11
23
 
12
24
  # implicit call is done by inspec to determine the value of an attribute
13
- def value(newvalue = nil)
14
- unless newvalue.nil?
15
- @value = newvalue
16
- end
17
- @value || default
25
+ def value
26
+ @value.nil? ? default : @value
18
27
  end
19
28
 
20
29
  def default
21
- @opts[:default]
30
+ @opts[:default] || DEFAULT_ATTRIBUTE.new
22
31
  end
23
32
 
24
33
  def title
@@ -2,10 +2,11 @@
2
2
 
3
3
  module Inspec
4
4
  class Control
5
- attr_accessor :id, :title, :desc, :impact, :tests, :tags
5
+ attr_accessor :id, :title, :desc, :impact, :tests, :tags, :refs
6
6
  def initialize
7
7
  @tests = []
8
8
  @tags = []
9
+ @refs = []
9
10
  end
10
11
 
11
12
  def add_test(t)
@@ -20,12 +21,13 @@ module Inspec
20
21
  { id: id, title: title, desc: desc, impact: impact, tests: tests.map(&:to_hash), tags: tags.map(&:to_hash) }
21
22
  end
22
23
 
23
- def to_ruby
24
+ def to_ruby # rubocop:disable Metrics/AbcSize
24
25
  res = ["control #{id.inspect} do"]
25
26
  res.push " title #{title.inspect}" unless title.to_s.empty?
26
27
  res.push " desc #{prettyprint_text(desc, 2)}" unless desc.to_s.empty?
27
28
  res.push " impact #{impact}" unless impact.nil?
28
29
  tags.each { |t| res.push(indent(t.to_ruby, 2)) }
30
+ refs.each { |t| res.push(" ref #{print_ref(t)}") }
29
31
  tests.each { |t| res.push(indent(t.to_ruby, 2)) }
30
32
  res.push 'end'
31
33
  res.join("\n")
@@ -33,6 +35,12 @@ module Inspec
33
35
 
34
36
  private
35
37
 
38
+ def print_ref(x)
39
+ return x.inspect if x.is_a?(String)
40
+ raise "Cannot process the ref: #{x}" unless x.is_a?(Hash)
41
+ '('+x.inspect+')'
42
+ end
43
+
36
44
  # Pretty-print a text block of InSpec code
37
45
  #
38
46
  # @param s [String] should not be empty
@@ -37,14 +37,26 @@ module Inspec
37
37
  Inspec::Resource.registry
38
38
  end
39
39
 
40
- def __register(name, obj)
40
+ def __register(name, obj) # rubocop:disable Metrics/MethodLength
41
41
  cl = Class.new(obj) do
42
+ attr_reader :resource_exception_message
43
+
42
44
  def initialize(backend, name, *args)
45
+ @resource_skipped = false
46
+ @resource_failed = false
47
+
43
48
  # attach the backend to this instance
44
49
  @__backend_runner__ = backend
45
50
  @__resource_name__ = name
51
+
46
52
  # call the resource initializer
47
- super(*args)
53
+ begin
54
+ super(*args)
55
+ rescue Inspec::Exceptions::ResourceSkipped => e
56
+ skip_resource(e.message)
57
+ rescue Inspec::Exceptions::ResourceFailed => e
58
+ fail_resource(e.message)
59
+ end
48
60
  end
49
61
 
50
62
  def self.desc(description = nil)
@@ -57,12 +69,29 @@ module Inspec
57
69
  @example = example
58
70
  end
59
71
 
72
+ def skip_resource(message)
73
+ @resource_skipped = true
74
+ @resource_exception_message = message
75
+ end
76
+
77
+ def resource_skipped?
78
+ @resource_skipped
79
+ end
80
+
60
81
  def resource_skipped
61
- @resource_skipped if defined?(@resource_skipped)
82
+ warn('[DEPRECATION] Use `resource_exception_message` for the resource skipped message. This method will be removed in InSpec 2.0.')
83
+ # Returning `nil` here to match previous behavior
84
+ return nil if @resource_skipped == false
85
+ @resource_exception_message
62
86
  end
63
87
 
64
- def skip_resource(message)
65
- @resource_skipped = message
88
+ def fail_resource(message)
89
+ @resource_failed = true
90
+ @resource_exception_message = message
91
+ end
92
+
93
+ def resource_failed?
94
+ @resource_failed
66
95
  end
67
96
 
68
97
  def inspec
@@ -93,12 +93,21 @@ module Inspec
93
93
  @writable = options[:writable] || false
94
94
  @profile_id = options[:id]
95
95
  @cache = options[:cache] || Cache.new
96
- @backend = options[:backend] || Inspec::Backend.create(options.select { |k, _| k != 'target' })
97
96
  @attr_values = options[:attributes]
98
97
  @tests_collected = false
99
98
  @libraries_loaded = false
100
99
  Metadata.finalize(@source_reader.metadata, @profile_id, options)
101
100
 
101
+ # if a backend has already been created, clone it so each profile has its own unique backend object
102
+ # otherwise, create a new backend object
103
+ #
104
+ # This is necessary since we store the RuntimeProfile on the backend object. If a user runs `inspec exec`
105
+ # with multiple profiles, only the RuntimeProfile for the last-loaded profile will be available if
106
+ # we share the backend between profiles.
107
+ #
108
+ # This will cause issues if a profile attempts to load a file via `inspec.profile.file`
109
+ train_options = options.select { |k, _| k != 'target' } # See https://github.com/chef/inspec/pull/1646
110
+ @backend = options[:backend].nil? ? Inspec::Backend.create(train_options) : options[:backend].dup
102
111
  @runtime_profile = RuntimeProfile.new(self)
103
112
  @backend.profile = @runtime_profile
104
113
 
@@ -127,6 +127,8 @@ module Inspec
127
127
  end
128
128
 
129
129
  def load_control_file(*args)
130
+ # Set `skip_file` to `false` between file loads to prevent skips from spanning multiple control files
131
+ control_eval_context.skip_file = false
130
132
  load_with_context(control_eval_context, *args)
131
133
  end
132
134
  alias load load_control_file
@@ -177,7 +179,7 @@ module Inspec
177
179
  # we need to return an attribute object, to allow dermination of default values
178
180
  attr = Attribute.new(name, options)
179
181
  # read value from given gived values
180
- attr.value(@conf['attributes'][attr.name]) unless @conf['attributes'].nil?
182
+ attr.value = @conf['attributes'][attr.name] unless @conf['attributes'].nil?
181
183
  @attributes.push(attr)
182
184
  attr.value
183
185
  end
@@ -72,24 +72,33 @@ module Inspec
72
72
  end
73
73
  end
74
74
 
75
+ require 'resources/aide_conf'
75
76
  require 'resources/apache'
76
77
  require 'resources/apache_conf'
77
78
  require 'resources/apt'
78
79
  require 'resources/audit_policy'
80
+ require 'resources/auditd'
79
81
  require 'resources/auditd_conf'
80
82
  require 'resources/auditd_rules'
81
83
  require 'resources/bash'
82
84
  require 'resources/bond'
83
85
  require 'resources/bridge'
84
86
  require 'resources/command'
87
+ require 'resources/cran'
88
+ require 'resources/cpan'
85
89
  require 'resources/crontab'
86
90
  require 'resources/dh_params'
87
91
  require 'resources/directory'
88
92
  require 'resources/docker'
89
- require 'resources/docker_image'
90
93
  require 'resources/docker_container'
94
+ require 'resources/docker_image'
95
+ require 'resources/elasticsearch'
96
+ require 'resources/etc_fstab'
91
97
  require 'resources/etc_group'
98
+ require 'resources/etc_hosts_allow_deny'
99
+ require 'resources/etc_hosts'
92
100
  require 'resources/file'
101
+ require 'resources/firewalld'
93
102
  require 'resources/gem'
94
103
  require 'resources/groups'
95
104
  require 'resources/grub_conf'
@@ -111,6 +120,7 @@ require 'resources/mssql_session'
111
120
  require 'resources/mysql'
112
121
  require 'resources/mysql_conf'
113
122
  require 'resources/mysql_session'
123
+ require 'resources/nginx'
114
124
  require 'resources/nginx_conf'
115
125
  require 'resources/npm'
116
126
  require 'resources/ntp_conf'
@@ -122,12 +132,12 @@ require 'resources/package'
122
132
  require 'resources/packages'
123
133
  require 'resources/parse_config'
124
134
  require 'resources/passwd'
125
- require 'resources/postgres_hba_conf'
126
- require 'resources/postgres_ident_conf'
127
135
  require 'resources/pip'
128
136
  require 'resources/port'
129
137
  require 'resources/postgres'
130
138
  require 'resources/postgres_conf'
139
+ require 'resources/postgres_hba_conf'
140
+ require 'resources/postgres_ident_conf'
131
141
  require 'resources/postgres_session'
132
142
  require 'resources/powershell'
133
143
  require 'resources/processes'
@@ -136,18 +146,19 @@ require 'resources/registry_key'
136
146
  require 'resources/security_policy'
137
147
  require 'resources/service'
138
148
  require 'resources/shadow'
139
- require 'resources/ssl'
140
149
  require 'resources/ssh_conf'
150
+ require 'resources/ssl'
141
151
  require 'resources/sys_info'
142
152
  require 'resources/toml'
143
153
  require 'resources/users'
144
154
  require 'resources/vbscript'
145
155
  require 'resources/virtualization'
146
156
  require 'resources/windows_feature'
157
+ require 'resources/windows_hotfix'
147
158
  require 'resources/windows_task'
148
- require 'resources/xinetd'
149
159
  require 'resources/wmi'
150
160
  require 'resources/x509_certificate'
161
+ require 'resources/xinetd'
151
162
  require 'resources/yum'
152
163
  require 'resources/zfs_dataset'
153
164
  require 'resources/zfs_pool'
@@ -157,3 +168,4 @@ require 'resources/json'
157
168
  require 'resources/yaml'
158
169
  require 'resources/csv'
159
170
  require 'resources/ini'
171
+ require 'resources/xml'
@@ -52,11 +52,20 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
52
52
  format_example(example).tap do |hash|
53
53
  e = example.exception
54
54
  next unless e
55
- hash[:message] = exception_message(e)
55
+
56
+ if example.metadata[:sensitive]
57
+ hash[:message] = '*** sensitive output suppressed ***'
58
+ else
59
+ hash[:message] = exception_message(e)
60
+ end
56
61
 
57
62
  next if e.is_a? RSpec::Expectations::ExpectationNotMetError
58
63
  hash[:exception] = e.class.name
59
64
  hash[:backtrace] = e.backtrace
65
+
66
+ # if the exception indicates the resource author wants to skip the test,
67
+ # we update the test status here.
68
+ hash[:status] = 'skipped' if e.is_a?(Inspec::Exceptions::ResourceSkipped)
60
69
  end
61
70
  end
62
71
  end
@@ -341,7 +350,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
341
350
  # This method is called through the RSpec Formatter interface for every
342
351
  # example found in the test suite.
343
352
  #
344
- # Within #format_example we are getting and example and:
353
+ # Within #format_example we are getting an example and:
345
354
  # * if this is an example, within a control, within a profile then we want
346
355
  # to display the profile header, display the control, and then display
347
356
  # the example.
@@ -566,7 +575,8 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
566
575
  end
567
576
 
568
577
  def print_result(result)
569
- test_status = result[:status_type]
578
+ test_skipped = result[:status] == 'skipped'
579
+ test_status = test_skipped ? 'skipped' : result[:status_type]
570
580
  indicator = INDICATORS[result[:status]]
571
581
  indicator = INDICATORS['empty'] if indicator.nil?
572
582
  if result[:message]
@@ -613,10 +623,18 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
613
623
  summary = profile_summary
614
624
  return unless summary['total'] > 0
615
625
 
626
+ success_str = summary['passed'] == 1 ? '1 successful control' : "#{summary['passed']} successful controls"
627
+ failed_str = summary['failed']['total'] == 1 ? '1 control failure' : "#{summary['failed']['total']} control failures"
628
+ skipped_str = summary['skipped'] == 1 ? '1 control skipped' : "#{summary['skipped']} controls skipped"
629
+
630
+ success_color = summary['passed'] > 0 ? 'passed' : 'no_color'
631
+ failed_color = summary['failed']['total'] > 0 ? 'failed' : 'no_color'
632
+ skipped_color = summary['skipped'] > 0 ? 'skipped' : 'no_color'
633
+
616
634
  s = format('Profile Summary: %s, %s, %s',
617
- format_with_color('passed', "#{summary['passed']} successful"),
618
- format_with_color('failed', "#{summary['failed']['total']} failures"),
619
- format_with_color('skipped', "#{summary['skipped']} skipped"),
635
+ format_with_color(success_color, success_str),
636
+ format_with_color(failed_color, failed_str),
637
+ format_with_color(skipped_color, skipped_str),
620
638
  )
621
639
  output.puts(s) if summary['total'] > 0
622
640
  end
@@ -624,10 +642,16 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
624
642
  def print_tests_summary
625
643
  summary = tests_summary
626
644
 
645
+ failed_str = summary['failed'] == 1 ? '1 failure' : "#{summary['failed']} failures"
646
+
647
+ success_color = summary['passed'] > 0 ? 'passed' : 'no_color'
648
+ failed_color = summary['failed'] > 0 ? 'failed' : 'no_color'
649
+ skipped_color = summary['skipped'] > 0 ? 'skipped' : 'no_color'
650
+
627
651
  s = format('Test Summary: %s, %s, %s',
628
- format_with_color('passed', "#{summary['passed']} successful"),
629
- format_with_color('failed', "#{summary['failed']} failures"),
630
- format_with_color('skipped', "#{summary['skipped']} skipped"),
652
+ format_with_color(success_color, "#{summary['passed']} successful"),
653
+ format_with_color(failed_color, failed_str),
654
+ format_with_color(skipped_color, "#{summary['skipped']} skipped"),
631
655
  )
632
656
 
633
657
  output.puts(s)
@@ -802,7 +826,9 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
802
826
  end
803
827
 
804
828
  def update_summary(example)
805
- example_status = STATUS_TYPES[example[:status_type]]
829
+ test_skipped = example[:status] == 'skipped'
830
+ status_type = test_skipped ? 'skipped' : example[:status_type]
831
+ example_status = STATUS_TYPES[status_type]
806
832
  @summary_status = example_status if example_status > @summary_status
807
833
  fails.push(example) if example_status > 0
808
834
  passes.push(example) if example_status == STATUS_TYPES['passed']
@@ -855,8 +881,9 @@ class InspecRspecJUnit < InspecRspecJson
855
881
  private
856
882
 
857
883
  def build_profile_xml(profile)
884
+ profile_name = profile[:name]
858
885
  profile_xml = REXML::Element.new('testsuite')
859
- profile_xml.add_attribute('name', profile[:name])
886
+ profile_xml.add_attribute('name', profile_name)
860
887
  profile_xml.add_attribute('tests', count_profile_tests(profile))
861
888
  profile_xml.add_attribute('failed', count_profile_failed_tests(profile))
862
889
 
@@ -864,17 +891,24 @@ class InspecRspecJUnit < InspecRspecJson
864
891
  next if control[:results].nil?
865
892
 
866
893
  control[:results].each do |result|
867
- profile_xml.add(build_result_xml(control, result))
894
+ profile_xml.add(build_result_xml(profile_name, control, result))
868
895
  end
869
896
  end
870
897
 
871
898
  profile_xml
872
899
  end
873
900
 
874
- def build_result_xml(control, result)
901
+ def build_result_xml(profile_name, control, result)
875
902
  result_xml = REXML::Element.new('testcase')
876
903
  result_xml.add_attribute('name', result[:code_desc])
877
- result_xml.add_attribute('class', control[:title].nil? ? 'Anonymous' : control[:id])
904
+ # if there is no control title, we are likely receiving test results from a
905
+ # "naked" test (a test not located within a control block). Therefore, rather
906
+ # than outputting the auto-generated ID, i.e.
907
+ #
908
+ # "(generated from test_spec.rb:1 de0ce10e4bbbd4d0ff7a65f4234de8c1)")
909
+ #
910
+ # ... we'll output "Anonymous" instead.
911
+ result_xml.add_attribute('classname', control[:title].nil? ? "#{profile_name}.Anonymous" : "#{profile_name}.#{control[:id]}")
878
912
  result_xml.add_attribute('time', result[:run_time])
879
913
 
880
914
  if result[:status] == 'failed'
data/lib/inspec/rule.rb CHANGED
@@ -94,6 +94,10 @@ module Inspec
94
94
  @tags
95
95
  end
96
96
 
97
+ def source_file
98
+ @__file
99
+ end
100
+
97
101
  # Skip all checks if only_if is false
98
102
  #
99
103
  # @param [Type] &block returns true if tests are added, false otherwise