inspec-core 4.18.85 → 4.18.97

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 367bf0fb9467148e5434036ec14c7ec9b9b9e560504758e0c72493bacc73270a
4
- data.tar.gz: 352392686e13db1a8827f9d7aa29f3961c070fd0b66d7f8cdb7ab88513429f59
3
+ metadata.gz: 6b39b8f2921accd19eb170e079a3b85bfb72611d618d5bc0b0c705eb08000323
4
+ data.tar.gz: 2c6a90407e96b9038044f03884bc0b2c842f06ef410139f929d6149717a49ea4
5
5
  SHA512:
6
- metadata.gz: 8e237b06411e204f1a811c7cf5707ebed9082084f24e541fc94faea6e2e956ab671c2cdf1ab3ac2388e721129d151d06171791448501a71c760b426c2919567c
7
- data.tar.gz: 11bdb33d12ef3890934c17bb5855001de5d0bce259f484e80b15ed3fed2b69639d509baab1a7f4690778c882ea1a11a6b50244a881cb883853eb6bfa77ec0866
6
+ metadata.gz: da18afb78ba9601984a36e2f8557cde0c0c316083e99e6f3b86162c8130970cba53f0b04c896bb3b87d29ad6fe76a85fea46339ee4799d4c827eda587b880a35
7
+ data.tar.gz: 71473c95ca6a3b4c36e71edab3663157aa4590ac41dcf26148d6baad03655287ae689ae525fcaa0b56e55d5041b69a0deeeb7207ee7454554e205c30c7476dfe
data/Gemfile CHANGED
@@ -23,7 +23,7 @@ group :test do
23
23
  gem "minitest", "~> 5.5"
24
24
  gem "minitest-sprint", "~> 1.0"
25
25
  gem "rake", ">= 10"
26
- gem "simplecov", "~> 0.10"
26
+ gem "simplecov", ["~> 0.10", "<=0.18.2"]
27
27
  gem "concurrent-ruby", "~> 1.0"
28
28
  gem "mocha", "~> 1.1"
29
29
  gem "ruby-progressbar", "~> 1.8"
@@ -35,9 +35,10 @@ end
35
35
 
36
36
  group :integration do
37
37
  gem "berkshelf"
38
- gem "chef", "< 15"
39
38
  gem "test-kitchen"
40
39
  gem "kitchen-vagrant"
40
+ gem "chef", "< 15"
41
+ gem "chef-zero", "< 15"
41
42
  gem "kitchen-inspec"
42
43
  gem "kitchen-ec2"
43
44
  gem "kitchen-dokken"
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Chef InSpec: Inspect Your Infrastructure
2
2
 
3
3
  * **Project State: Active**
4
- * **Issues Response SLA: 3 business days**
5
- * **Pull Request Response SLA: 3 business days**
4
+ * **Issues Response SLA: 14 business days**
5
+ * **Pull Request Response SLA: 14 business days**
6
6
 
7
7
  For more information on project states and SLAs, see [this documentation](https://github.com/chef/chef-oss-practices/blob/master/repo-management/repo-states.md).
8
8
 
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency "chef-telemetry", "~> 1.0"
27
27
  spec.add_dependency "license-acceptance", ">= 0.2.13", "< 2.0"
28
28
  spec.add_dependency "thor", ">= 0.20", "< 2.0"
29
- spec.add_dependency "json-schema", "~> 2.8"
29
+ spec.add_dependency "json_schemer", "~> 0.2.1"
30
30
  spec.add_dependency "method_source", "~> 0.8"
31
31
  spec.add_dependency "rubyzip", "~> 1.2", ">= 1.2.2"
32
32
  spec.add_dependency "rspec", "~> 3.9"
@@ -377,12 +377,12 @@ class Inspec::InspecCLI < Inspec::BaseCLI
377
377
 
378
378
  desc "schema NAME", "print the JSON schema", hide: true
379
379
  def schema(name)
380
- require "inspec/schema"
380
+ require "inspec/schema/output_schema"
381
381
 
382
- puts Inspec::Schema.json(name)
382
+ puts Inspec::Schema::OutputSchema.json(name)
383
383
  rescue StandardError => e
384
384
  puts e
385
- puts "Valid schemas are #{Inspec::Schema.names.join(", ")}"
385
+ puts "Valid schemas are #{Inspec::Schema::OutputSchema.names.join(", ")}"
386
386
  end
387
387
 
388
388
  desc "version", "prints the version of this tool"
@@ -470,5 +470,5 @@ rescue Inspec::Plugin::V2::Exception => v2ex
470
470
  else
471
471
  Inspec::Log.error "Run again with --debug for a stacktrace."
472
472
  end
473
- ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
473
+ exit Inspec::UI::EXIT_PLUGIN_ERROR
474
474
  end
@@ -1,9 +1,15 @@
1
+ require_relative "utils/install_context"
2
+
1
3
  module Inspec
4
+
5
+ extend Inspec::InstallContextHelpers
6
+
2
7
  def self.config_dir
3
8
  ENV["INSPEC_CONFIG_DIR"] ? ENV["INSPEC_CONFIG_DIR"] : File.join(Dir.home, ".inspec")
4
9
  end
5
10
 
6
11
  def self.src_root
7
- File.expand_path(File.join(__FILE__, "..", "..", ".."))
12
+ @src_root ||= File.expand_path(File.join(__FILE__, "../../.."))
8
13
  end
14
+
9
15
  end
@@ -12,44 +12,42 @@ module Inspec
12
12
  # registry used by all dsl methods bound to the resource registry
13
13
  # passed into the #create constructor.
14
14
  #
15
+ #
15
16
  class LibraryEvalContext
16
- # rubocop:disable Naming/ConstantName
17
- Inspec = :nope! # see #initialize below
18
- # rubocop:enable Naming/ConstantName
19
-
20
- ##
21
- # Include a custom `require` method that gets used when this
22
- # context is used to eval source. See lib/inspec/dsl_shared.rb for
23
- # more details.
24
- include ::Inspec::DSL::RequireOverride
25
-
26
17
  def self.create(registry, require_loader)
27
- Class.new(LibraryEvalContext).new(registry, require_loader)
28
- end
29
-
30
- # Provide the local binding for this context which is
31
- # necessary for calls to `require` to create all dependent
32
- # objects in the correct context.
33
- attr_accessor :__inspec_binding
34
-
35
- def initialize(registry, require_loader)
36
- @require_loader = require_loader
37
- # rubocop:disable Style/RedundantSelf
38
- self.__inspec_binding = self.instance_eval { binding }
39
- # rubocop:enable Style/RedundantSelf
40
-
41
- @res_klass = Class.new ::Inspec::Resource
42
- @res_klass.__resource_registry = registry
43
-
44
- # NOTE: this *must* be a subclass of LibraryEvalContext to work
45
- self.class.const_set :Inspec, self # BYPASS! See resource below
46
- end
47
-
48
- # Fake for Inspec.resource in lib/inspec/resource.rb that provides
49
- # our own Resource subclass that has its own private __resource_registry
50
- def resource(version)
51
- ::Inspec.validate_resource_dsl_version!(version)
52
- @res_klass
18
+ c = Class.new(Inspec::Resource) do
19
+ define_singleton_method :__resource_registry do
20
+ registry
21
+ end
22
+ end
23
+
24
+ c2 = Class.new do
25
+ define_singleton_method :resource do |version|
26
+ Inspec.validate_resource_dsl_version!(version)
27
+ c
28
+ end
29
+ end
30
+
31
+ c3 = Class.new do
32
+ include Inspec::DSL::RequireOverride
33
+ def initialize(require_loader)
34
+ @require_loader = require_loader
35
+ @inspec_binding = nil
36
+ end
37
+
38
+ def __inspec_binding
39
+ @inspec_binding
40
+ end
41
+ end
42
+
43
+ c3.const_set(:Inspec, c2)
44
+ res = c3.new(require_loader)
45
+
46
+ # Provide the local binding for this context which is necessary for
47
+ # calls to `require` to create all dependent objects in the correct
48
+ # context.
49
+ res.instance_variable_set("@inspec_binding", res.instance_eval("binding"))
50
+ res
53
51
  end
54
52
  end
55
53
  end
@@ -32,6 +32,13 @@ module Inspec::Plugin::V2
32
32
 
33
33
  # Identify plugins that inspec is co-installed with
34
34
  detect_system_plugins unless options[:omit_sys_plugins]
35
+
36
+ # Train plugins are not true InSpec plugins; we need to decorate them a
37
+ # bit more to integrate them. Wait to do this until after we know if
38
+ # they are system or user.
39
+ registry.each do |plugin_name, status|
40
+ fixup_train_plugin_status(status) if train_plugin_name?(plugin_name)
41
+ end
35
42
  end
36
43
 
37
44
  def load_all
@@ -48,9 +55,12 @@ module Inspec::Plugin::V2
48
55
  begin
49
56
  # We could use require, but under testing, we need to repeatedly reload the same
50
57
  # plugin. However, gems only work with require (rubygems dooes not overload `load`)
51
- if plugin_details.installation_type == :user_gem
58
+ case plugin_details.installation_type
59
+ when :user_gem
52
60
  activate_managed_gems_for_plugin(plugin_name)
53
61
  require plugin_details.entry_point
62
+ when :system_gem
63
+ require plugin_details.entry_point
54
64
  else
55
65
  load_path = plugin_details.entry_point
56
66
  load_path += ".rb" unless plugin_details.entry_point.end_with?(".rb")
@@ -253,10 +263,6 @@ module Inspec::Plugin::V2
253
263
  status.entry_point = plugin_entry[:installation_path]
254
264
  end
255
265
 
256
- # Train plugins are not true InSpec plugins; we need to decorate them a
257
- # bit more to integrate them.
258
- fixup_train_plugin_status(status) if train_plugin_name?(plugin_entry[:name])
259
-
260
266
  registry[status.name] = status
261
267
  end
262
268
  end
@@ -265,6 +271,8 @@ module Inspec::Plugin::V2
265
271
  status.api_generation = :'train-1'
266
272
  if status.installation_type == :user_gem
267
273
  # Activate the gem. This allows train to 'require' the gem later.
274
+ # This is not required for system gems because rubygems already has
275
+ # activated the gemspecs.
268
276
  activate_managed_gems_for_plugin(status.entry_point)
269
277
  end
270
278
  end
@@ -187,8 +187,20 @@ module Inspec::Resources
187
187
  line.scan(/-S ([^ ]+)\s?/).flatten.first.split(",")
188
188
  end
189
189
 
190
+ # Processes the line and returns a pair of entries reflecting the 'action'
191
+ # and 'list' items.
192
+ #
193
+ # @return [Array[String,String]]
190
194
  def action_list_for(line)
191
- line.scan(/-a ([^,]+),([^ ]+)\s?/).flatten
195
+ action_list = line.scan(/-a ([^,]+),([^ ]+)\s?/).flatten
196
+
197
+ # Actions and lists can be in either order
198
+ valid_actions = %w{never always}
199
+
200
+ [
201
+ (action_list & valid_actions).first,
202
+ (action_list - valid_actions).first,
203
+ ]
192
204
  end
193
205
 
194
206
  def key_for(line)
@@ -1,7 +1,5 @@
1
1
  require "inspec/resources/command"
2
2
  require "inspec/utils/database_helpers"
3
- require "htmlentities"
4
- require "rexml/document"
5
3
  require "hashie/mash"
6
4
  require "csv"
7
5
 
@@ -48,18 +46,15 @@ module Inspec::Resources
48
46
  if @sqlcl_bin && inspec.command(@sqlcl_bin).exist?
49
47
  @bin = @sqlcl_bin
50
48
  format_options = "set sqlformat csv\nSET FEEDBACK OFF"
51
- parser = :parse_csv_result
52
49
  else
53
50
  @bin = "#{@sqlplus_bin} -S"
54
- format_options = "SET MARKUP HTML ON\nSET PAGESIZE 32000\nSET FEEDBACK OFF"
55
- parser = :parse_html_result
51
+ format_options = "SET MARKUP CSV ON\nSET PAGESIZE 32000\nSET FEEDBACK OFF"
56
52
  end
57
53
 
58
54
  command = command_builder(format_options, sql)
59
55
  inspec_cmd = inspec.command(command)
60
56
 
61
- DatabaseHelper::SQLQueryResult.new(inspec_cmd,
62
- send(parser, inspec_cmd.stdout))
57
+ DatabaseHelper::SQLQueryResult.new(inspec_cmd, parse_csv_result(inspec_cmd.stdout))
63
58
  end
64
59
 
65
60
  def to_s
@@ -86,7 +81,7 @@ module Inspec::Resources
86
81
  elsif @su_user.nil?
87
82
  %{#{sql_prefix}#{bin} "#{user}"/"#{password}"@#{host}:#{port}/#{@service} as #{@db_role}#{sql_postfix}}
88
83
  else
89
- %{su - #{@su_user} -c "env ORACLE_SID=#{@service} #{bin} / as #{@db_role}#{sql_postfix}"}
84
+ %{su - #{@su_user} -c "env ORACLE_SID=#{@service} #{@bin} / as #{@db_role}#{sql_postfix}"}
90
85
  end
91
86
  end
92
87
 
@@ -96,54 +91,9 @@ module Inspec::Resources
96
91
  end
97
92
 
98
93
  def parse_csv_result(stdout)
99
- output = stdout.delete(/\r/)
100
- table = CSV.parse(output, { headers: true })
101
-
102
- # convert to hash
103
- headers = table.headers
104
-
105
- results = table.map do |row|
106
- res = {}
107
- headers.each do |header|
108
- res[header.downcase] = row[header]
109
- end
110
- Hashie::Mash.new(res)
111
- end
112
- results
113
- end
114
-
115
- def parse_html_result(stdout)
116
- result = stdout
117
- # make oracle html valid html by removing the p tag, it does not include a closing tag
118
- result = result.gsub("<p>", "").gsub("</p>", "").gsub("<br>", "")
119
- doc = REXML::Document.new result
120
- table = doc.elements["table"]
121
- hash = []
122
- unless table.nil?
123
- rows = table.elements.to_a
124
- headers = rows[0].elements.to_a("th").map { |entry| entry.text.strip }
125
- rows.delete_at(0)
126
-
127
- # iterate over each row, first row is header
128
- hash = []
129
- if !rows.nil? && !rows.empty?
130
- hash = rows.map do |row|
131
- res = {}
132
- entries = row.elements.to_a("td")
133
- # ignore if we have empty entries, oracle is adding th rows in between
134
- return nil if entries.empty?
135
-
136
- headers.each_with_index do |header, index|
137
- # we need htmlentities since we do not have nokogiri
138
- coder = HTMLEntities.new
139
- val = coder.decode(entries[index].text).strip
140
- res[header.downcase] = val
141
- end
142
- Hashie::Mash.new(res)
143
- end.compact
144
- end
145
- end
146
- hash
94
+ output = stdout.sub(/\r/, "").strip
95
+ converter = ->(header) { header.downcase }
96
+ CSV.parse(output, headers: true, header_converters: converter).map { |row| Hashie::Mash.new(row.to_h) }
147
97
  end
148
98
  end
149
99
  end
@@ -0,0 +1,17 @@
1
+ # Schema Generation
2
+
3
+ These files handle the generation of JSON schema's for inspec output as yielded by `inspec schema <schema-name>`.
4
+ This schema is in the JSON Schema 4th revision spec.
5
+
6
+ ## Structure
7
+ Data/schema structures shared between all outputs are located in `primitives.rb`
8
+ The files `exec_json_min.rb`, `exec_json.rb`, and `profile_json.rb` contain JSON structures for their specific output types, respectively.
9
+ The output schema is "flat" in terms of schema structure, with each "type" defined in the JSONs `definitions` block and referred to via $ref.
10
+ The logic for this is handled via the `SchemaType` class in `primitives.rb`.
11
+ All relevant dependencies are included recursively via the `dependencies` property of that class, constructed using the `build_definitions` method in `output_schema.rb`.
12
+
13
+ ## Reasoning and validation
14
+ In general the first iteration of this updated schema was made by examining the output of all of the profiles in the tests directory.
15
+ All of the profiles generated by those tests successfully validate against this schema.
16
+ However, in light of potential missed properties that aren't covered by existing tests, the schema was left very non-strict.
17
+ More specifically, "additionalProperties" was left as True on all schema objects.
@@ -0,0 +1,128 @@
1
+ require "inspec/schema/primitives"
2
+
3
+ # These type occur only when running "inspec exec --reporter json <file>".
4
+
5
+ module Inspec
6
+ module Schema
7
+ module ExecJson
8
+ # Represents a label and description, to provide human-readable info about a control
9
+ CONTROL_DESCRIPTION = Primitives::SchemaType.new("Control Description", {
10
+ "type" => "object",
11
+ "additionalProperties" => true,
12
+ "required" => %w{label data},
13
+ "properties" => {
14
+ "label" => Primitives::STRING,
15
+ "data" => Primitives::STRING,
16
+ },
17
+ }, [])
18
+
19
+ # Lists the potential values for a control result
20
+ CONTROL_RESULT_STATUS = Primitives::SchemaType.new("Control Result Status", {
21
+ "type" => "string",
22
+ "enum" => %w{passed failed skipped error},
23
+ }, [])
24
+
25
+ # Represents the statistics/result of a control"s execution
26
+ CONTROL_RESULT = Primitives::SchemaType.new("Control Result", {
27
+ "type" => "object",
28
+ "additionalProperties" => true,
29
+ "required" => %w{code_desc run_time start_time},
30
+ "properties" => {
31
+ "status" => CONTROL_RESULT_STATUS.ref,
32
+ "code_desc" => Primitives::STRING,
33
+ "run_time" => Primitives::NUMBER,
34
+ "start_time" => Primitives::STRING,
35
+
36
+ # All optional
37
+ "resource" => Primitives::STRING,
38
+ "message" => Primitives::STRING,
39
+ "skip_message" => Primitives::STRING,
40
+ "exception" => Primitives::STRING,
41
+ "backtrace" => {
42
+ "anyOf" => [
43
+ Primitives.array(Primitives::STRING),
44
+ Primitives::NULL,
45
+ ],
46
+ },
47
+ },
48
+ }, [CONTROL_RESULT_STATUS])
49
+
50
+ # Represents a control produced
51
+ CONTROL = Primitives::SchemaType.new("Exec JSON Control", {
52
+ "type" => "object",
53
+ "additionalProperties" => true,
54
+ "required" => %w{id title desc impact refs tags code source_location results},
55
+ "properties" => {
56
+ "id" => Primitives.desc(Primitives::STRING, "The ID of this control"),
57
+ "title" => { "type" => %w{string null} }, # Nullable string
58
+ "desc" => { "type" => %w{string null} },
59
+ "descriptions" => Primitives.array(CONTROL_DESCRIPTION.ref),
60
+ "impact" => Primitives::IMPACT,
61
+ "refs" => Primitives.array(Primitives::REFERENCE.ref),
62
+ "tags" => Primitives::TAGS,
63
+ "code" => Primitives.desc(Primitives::STRING, "The raw source code of the control. Note that if this is an overlay control, it does not include the underlying source code"),
64
+ "source_location" => Primitives::SOURCE_LOCATION.ref,
65
+ "results" => Primitives.desc(Primitives.array(CONTROL_RESULT.ref), %q{
66
+ A list of all results of the controls describe blocks.
67
+
68
+ For instance, if in the controls code we had the following:
69
+ describe sshd_config do
70
+ its('Port') { should cmp 22 }
71
+ end
72
+ The result of this block as a ControlResult would be appended to the results list.
73
+ }),
74
+ },
75
+ }, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT])
76
+
77
+ # Based loosely on https://www.inspec.io/docs/reference/profiles/ as of July 3, 2019
78
+ # However, concessions were made to the reality of current reporters, specifically
79
+ # with how description is omitted and version/inspec_version aren't as advertised online
80
+ PROFILE = Primitives::SchemaType.new("Exec JSON Profile", {
81
+ "type" => "object",
82
+ "additionalProperties" => true,
83
+ "required" => %w{name sha256 supports attributes groups controls},
84
+ # Name is mandatory in inspec.yml.
85
+ # supports, controls, groups, and attributes are always present, even if empty
86
+ # sha256, status, skip_message
87
+ "properties" => {
88
+ # These are provided in inspec.yml
89
+ "name" => Primitives::STRING,
90
+ "title" => Primitives::STRING,
91
+ "maintainer" => Primitives::STRING,
92
+ "copyright" => Primitives::STRING,
93
+ "copyright_email" => Primitives::STRING,
94
+ "depends" => Primitives.array(Primitives::DEPENDENCY.ref),
95
+ "parent_profile" => Primitives::STRING,
96
+ "license" => Primitives::STRING,
97
+ "summary" => Primitives::STRING,
98
+ "version" => Primitives::STRING,
99
+ "supports" => Primitives.array(Primitives::SUPPORT.ref),
100
+ "description" => Primitives::STRING,
101
+ "inspec_version" => Primitives::STRING,
102
+
103
+ # These are generated at runtime, and all except skip_message are guaranteed
104
+ "sha256" => Primitives::STRING,
105
+ "status" => Primitives::STRING,
106
+ "skip_message" => Primitives::STRING, # If skipped, why
107
+ "controls" => Primitives.array(CONTROL.ref),
108
+ "groups" => Primitives.array(Primitives::CONTROL_GROUP.ref),
109
+ "attributes" => Primitives.array(Primitives::INPUT),
110
+ },
111
+ }, [CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::SUPPORT])
112
+
113
+ # Result of exec json. Top level value
114
+ # TODO: Include the format of top level controls. This was omitted for lack of sufficient examples
115
+ OUTPUT = Primitives::SchemaType.new("Exec JSON Output", {
116
+ "type" => "object",
117
+ "additionalProperties" => true,
118
+ "required" => %w{platform profiles statistics version},
119
+ "properties" => {
120
+ "platform" => Primitives::PLATFORM.ref,
121
+ "profiles" => Primitives.array(PROFILE.ref),
122
+ "statistics" => Primitives::STATISTICS.ref,
123
+ "version" => Primitives::STRING,
124
+ },
125
+ }, [Primitives::PLATFORM, PROFILE, Primitives::STATISTICS])
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,40 @@
1
+ require "inspec/schema/primitives"
2
+
3
+ # These type occur only when running "exec --reporter json-min <file>".
4
+
5
+ module Inspec
6
+ module Schema
7
+ module ExecJsonMin
8
+ # Represents a subset of the information about a control, designed for conciseness
9
+ CONTROL = Primitives::SchemaType.new("Exec JSON-MIN Control", {
10
+ "type" => "object",
11
+ "additionalProperties" => true,
12
+ "required" => %w{id profile_id profile_sha256 status code_desc},
13
+ "properties" => {
14
+ "id" => Primitives::STRING,
15
+ "profile_id" => { "type" => %w{string null} },
16
+ "profile_sha256" => Primitives::STRING,
17
+ "status" => Primitives::STRING,
18
+ "code_desc" => Primitives::STRING,
19
+ "skip_message" => Primitives::STRING,
20
+ "resource" => Primitives::STRING,
21
+ "message" => Primitives::STRING,
22
+ "exception" => Primitives::STRING,
23
+ "backtrace" => Primitives.array(Primitives::STRING),
24
+ },
25
+ }, [])
26
+
27
+ # Result of exec jsonmin. Top level value
28
+ OUTPUT = Primitives::SchemaType.new("Exec JSON-MIN output", {
29
+ "type" => "object",
30
+ "additionalProperties" => true,
31
+ "required" => %w{statistics controls version},
32
+ "properties" => {
33
+ "statistics" => Primitives::STATISTICS.ref,
34
+ "version" => Primitives::STRING,
35
+ "controls" => Primitives.array(CONTROL.ref),
36
+ },
37
+ }, [Primitives::STATISTICS, CONTROL])
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,53 @@
1
+ require "json"
2
+ require "inspec/schema/primitives"
3
+ require "inspec/schema/exec_json"
4
+ require "inspec/schema/exec_json_min"
5
+ require "inspec/schema/profile_json"
6
+
7
+ module Inspec
8
+ module Schema
9
+ module OutputSchema
10
+ # Build our definitions
11
+ def self.build_definitions(schema_type)
12
+ {
13
+ "definitions" => schema_type.all_depends.map { |t| [t.ref_name, t.body] }.to_h,
14
+ }
15
+ end
16
+
17
+ # Helper function to automatically bundle a type with its dependencies
18
+ def self.finalize(schema_type)
19
+ schema_type.body.merge(OutputSchema.build_definitions(schema_type))
20
+ end
21
+
22
+ # using a proc here so we can lazy load it when we need
23
+ PLATFORMS = lambda do
24
+ require "train"
25
+ Train.create("mock").connection
26
+ Train::Platforms.export
27
+ end
28
+
29
+ LIST = {
30
+ "profile-json" => OutputSchema.finalize(Schema::ProfileJson::PROFILE),
31
+ "exec-json" => OutputSchema.finalize(Schema::ExecJson::OUTPUT),
32
+ "exec-jsonmin" => OutputSchema.finalize(Schema::ExecJsonMin::OUTPUT),
33
+ "platforms" => PLATFORMS,
34
+ }.freeze
35
+
36
+ def self.names
37
+ LIST.keys
38
+ end
39
+
40
+ def self.json(name)
41
+ if !LIST.key?(name)
42
+ raise("Cannot find schema #{name.inspect}.")
43
+ elsif LIST[name].is_a?(Proc)
44
+ v = LIST[name].call
45
+ else
46
+ v = LIST[name]
47
+ end
48
+
49
+ JSON.dump(v)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,266 @@
1
+ require "set"
2
+
3
+ # These elements are shared between more than one output type
4
+
5
+ module Inspec
6
+ module Schema
7
+ module Primitives
8
+ ######################### Establish simple helpers for this schema ########################################
9
+ # Use this function to easily make described types
10
+ def self.desc(obj, description)
11
+ obj.merge({ "description" => description })
12
+ end
13
+
14
+ # Use this function to easily make an array of a type
15
+ def self.array(of_type)
16
+ {
17
+ "type" => "array",
18
+ "items" => of_type,
19
+ }
20
+ end
21
+
22
+ # This function performs some simple validation on schemas, to catch obvious missed requirements
23
+ def self.validate_schema(schema)
24
+ return if schema["type"] != "object"
25
+ raise "Objects in schema must have a \"required\" property, even if it is empty." unless schema.key?("required")
26
+
27
+ return if schema["required"].empty?
28
+ raise "An object with required properties must have a properties hash." unless schema.key?("properties")
29
+
30
+ return if Set.new(schema["required"]) <= Set.new(schema["properties"].keys)
31
+
32
+ raise "Not all required properties are present."
33
+ end
34
+
35
+ # Use this class to quickly add/use object types to/in a definition block
36
+ class SchemaType
37
+ attr_accessor :name, :depends
38
+ def initialize(name, body, dependencies)
39
+ # Validate the schema
40
+ Primitives.validate_schema(body)
41
+ # The title of the type
42
+ @name = name
43
+ # The body of the type
44
+ @body = body
45
+ # What SchemaType[]s it depends on. In essence, any thing that you .ref in the body
46
+ @depends = Set.new(dependencies)
47
+ end
48
+
49
+ # Produce this schema types generated body.
50
+ # Use to actually define the ref!
51
+ def body
52
+ @body.merge({
53
+ "title" => @name,
54
+ })
55
+ end
56
+
57
+ # Formats this to have a JSON pointer compatible title
58
+ def ref_name
59
+ @name.gsub(/\s+/, "_")
60
+ end
61
+
62
+ # Yields this type as a json schema ref
63
+ def ref
64
+ { "$ref" => "#/definitions/#{ref_name}" }
65
+ end
66
+
67
+ # Recursively acquire all depends for this schema. Return them sorted by name
68
+ def all_depends
69
+ result = @depends
70
+ # Fetch all from children
71
+ @depends.each do |nested_type|
72
+ # Yes, converting back to set here does some duplicate sorting.
73
+ # But here, performance really isn't our concern.
74
+ result += Set.new(nested_type.all_depends)
75
+ end
76
+ # Return the results as a sorted array
77
+ Array(result).sort_by(&:name)
78
+ end
79
+ end
80
+
81
+ ######################### Establish basic type shorthands for this schema ########################################
82
+ # These three are essentially primitives, used as shorthand
83
+ OBJECT = { "type" => "object", "additionalProperties" => true }.freeze
84
+ NUMBER = { "type" => "number" }.freeze
85
+ STRING = { "type" => "string" }.freeze
86
+ NULL = { "type" => "null" }.freeze
87
+
88
+ # We might eventually enforce string format stuff on this
89
+ URL = { "type" => "string" }.freeze
90
+
91
+ # A controls tags can have any number of properties, and any sorts of values
92
+ TAGS = { "type" => "object", "additionalProperties" => true }.freeze
93
+
94
+ # Attributes/Inputs specify the inputs to a profile.
95
+ INPUT = { "type" => "object", "additionalProperties" => true }.freeze
96
+
97
+ # Within a control file, impacts can be numeric 0-1 or string in [none,low,medium,high,critical]
98
+ # However, when they are output, the string types are automatically converted as follows:
99
+ # none -> 0 to 0.01
100
+ # low -> 0.01 to 0.4
101
+ # medium -> 0.4 to 0.7
102
+ # high -> 0.7 to 0.9
103
+ # Critical -> 0.9 to 1.0 (inclusive)
104
+ IMPACT = {
105
+ "type" => "number",
106
+ "minimum" => 0.0,
107
+ "maximum" => 1.0,
108
+ }.freeze
109
+
110
+ # A status for a control
111
+ STATUS = {
112
+ "type" => "string",
113
+ "enum" => %w{passed failed skipped error},
114
+ }.freeze
115
+
116
+ ######################### Establish inspec types common to several schemas helpers for this schema #######################################
117
+
118
+ # We use "title" to name the type.
119
+ # and "description" (via the describe function) to describe its particular usage
120
+ # Summary item containing run statistics about a subset of the controls
121
+ STATISTIC_ITEM = SchemaType.new("Statistic Block", {
122
+ "type" => "object",
123
+ "additionalProperties" => true,
124
+ "required" => ["total"],
125
+ "properties" => {
126
+ "total" => desc(NUMBER, "Total number of controls (in this category) for this inspec execution."),
127
+ },
128
+ }, [])
129
+
130
+ # Bundles several results statistics, representing distinct groups of controls
131
+ STATISTIC_GROUPING = SchemaType.new("Statistic Hash", {
132
+ "type" => "object",
133
+ "additionalProperties" => true,
134
+ "required" => [],
135
+ "properties" => {
136
+ "passed" => STATISTIC_ITEM.ref,
137
+ "skipped" => STATISTIC_ITEM.ref,
138
+ "failed" => STATISTIC_ITEM.ref,
139
+ },
140
+ }, [STATISTIC_ITEM])
141
+
142
+ # Contains statistics of an exec run.
143
+ STATISTICS = SchemaType.new("Statistics", {
144
+ "type" => "object",
145
+ "additionalProperties" => true,
146
+ "required" => ["duration"],
147
+ "properties" => {
148
+ "duration" => desc(NUMBER, "How long (in seconds) this inspec exec ran for."),
149
+ "controls" => desc(STATISTIC_GROUPING.ref, "Breakdowns of control statistics by result"),
150
+ },
151
+ }, [STATISTIC_GROUPING])
152
+
153
+ # Profile dependencies
154
+ DEPENDENCY = SchemaType.new("Dependency", {
155
+ "type" => "object",
156
+ "additionalProperties" => true, # Weird stuff shows up here sometimes
157
+ "required" => [], # Mysteriously even in a run profile we can have no status
158
+ "properties" => {
159
+ "name" => STRING,
160
+ "url" => URL,
161
+ "branch" => STRING,
162
+ "path" => STRING,
163
+ "skip_message" => STRING,
164
+ "status" => STRING,
165
+ "git" => URL,
166
+ "supermarket" => STRING,
167
+ "compliance" => STRING,
168
+ },
169
+ }, [])
170
+
171
+ # Represents the platform the test was run on
172
+ PLATFORM = SchemaType.new("Platform", {
173
+ "type" => "object",
174
+ "additionalProperties" => true,
175
+ "required" => %w{name release},
176
+ "properties" => {
177
+ "name" => desc(STRING, "The name of the platform this was run on."),
178
+ "release" => desc(STRING, "The version of the platform this was run on."),
179
+ "target_id" => STRING,
180
+ },
181
+ }, [])
182
+
183
+ # Explains what software ran the inspec profile/made this file. Typically inspec but could in theory be a different software
184
+ GENERATOR = SchemaType.new("Generator", {
185
+ "type" => "object",
186
+ "additionalProperties" => true,
187
+ "required" => %w{name version},
188
+ "properties" => {
189
+ "name" => desc(STRING, "The name of the software that generated this report."),
190
+ "version" => desc(STRING, "The version of the software that generated this report."),
191
+ },
192
+ }, [])
193
+
194
+ # Occurs from "exec --reporter json" and "inspec json"
195
+ # Denotes what file this control comes from, and where within
196
+ SOURCE_LOCATION = SchemaType.new("Source Location", {
197
+ "type" => "object",
198
+ "additionalProperties" => true,
199
+ "properties" => {
200
+ "ref" => desc(STRING, "Path to the file that this statement originates from"),
201
+ "line" => desc(NUMBER, "The line at which this statement is located in the file"),
202
+ },
203
+ "required" => %w{ref line},
204
+ }, [])
205
+
206
+ # References an external document
207
+ REFERENCE = SchemaType.new("Reference", {
208
+ # Needs at least one of title (ref), url, or uri.
209
+ "anyOf" => [
210
+ {
211
+ "type" => "object",
212
+ "required" => ["ref"],
213
+ "properties" => { "ref" => STRING },
214
+ },
215
+ {
216
+ "type" => "object",
217
+ "required" => ["url"],
218
+ "properties" => { "url" => STRING },
219
+ },
220
+ {
221
+ "type" => "object",
222
+ "required" => ["uri"],
223
+ "properties" => { "uri" => STRING },
224
+ },
225
+ # I am of the opinion that this is just an error in the codebase itself. See profiles/wrapper-override to uncover new mysteries!
226
+ {
227
+ "type" => "object",
228
+ "required" => ["ref"],
229
+ "properties" => { "ref" => array(OBJECT) },
230
+
231
+ },
232
+ ],
233
+ }, [])
234
+
235
+ # Represents a group of controls within a profile/.rb file
236
+ CONTROL_GROUP = SchemaType.new("Control Group", {
237
+ "type" => "object",
238
+ "additionalProperties" => true,
239
+ "required" => %w{id controls},
240
+ "properties" => {
241
+ "id" => desc(STRING, "The unique identifier of the group"),
242
+ "title" => desc({ type: %w{string null} }, "The name of the group"),
243
+ "controls" => desc(array(STRING), "The control IDs in this group"),
244
+ },
245
+ }, [])
246
+
247
+ # Occurs from "inspec exec --reporter json" and "inspec json"
248
+ # Represents a platfrom or group of platforms that this profile supports
249
+ SUPPORT = SchemaType.new("Supported Platform", {
250
+ "type" => "object",
251
+ "additionalProperties" => true, # NOTE: This should really be false, and inspec should validate profiles to ensure people don't make dumb mistakes like platform_family
252
+ "required" => [],
253
+ "properties" => {
254
+ "platform-family" => STRING,
255
+ "platform-name" => STRING,
256
+ "platform" => STRING,
257
+ "release" => STRING,
258
+ # os-* supports are being deprecated
259
+ "os-family" => STRING,
260
+ "os-name" => STRING,
261
+ },
262
+ }, [])
263
+
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,60 @@
1
+ require "inspec/schema/primitives"
2
+
3
+ # These type occur only when running "inspec json <file>".
4
+
5
+ module Inspec
6
+ module Schema
7
+ module ProfileJson
8
+ # Represents descriptions. Can have any string => string pairing
9
+ CONTROL_DESCRIPTIONS = Primitives::SchemaType.new("Profile JSON Control Descriptions", {
10
+ "type" => "object",
11
+ "aditionalProperties" => Primitives::STRING,
12
+ "required" => [],
13
+ }, [])
14
+
15
+ # Represents a control that hasn't been run
16
+ # Differs slightly from a normal control, in that it lacks results, and its descriptions are different
17
+ CONTROL = Primitives::SchemaType.new("Profile JSON Control", {
18
+ "type" => "object",
19
+ "additionalProperties" => true,
20
+ "required" => %w{id title desc impact tags code},
21
+ "properties" => {
22
+ "id" => Primitives.desc(Primitives::STRING, "The ID of this control"),
23
+ "title" => { "type" => %w{string null} },
24
+ "desc" => { "type" => %w{string null} },
25
+ "descriptions" => CONTROL_DESCRIPTIONS.ref,
26
+ "impact" => Primitives::IMPACT,
27
+ "refs" => Primitives.array(Primitives::REFERENCE.ref),
28
+ "tags" => Primitives::TAGS,
29
+ "code" => Primitives.desc(Primitives::STRING, "The raw source code of the control. Note that if this is an overlay control, it does not include the underlying source code"),
30
+ "source_location" => Primitives::SOURCE_LOCATION.ref,
31
+ },
32
+ }, [CONTROL_DESCRIPTIONS, Primitives::REFERENCE, Primitives::SOURCE_LOCATION])
33
+
34
+ # A profile that has not been run.
35
+ PROFILE = Primitives::SchemaType.new("Profile JSON Profile", {
36
+ "type" => "object",
37
+ "additionalProperties" => true, # Anything in the yaml will be put in here. LTTODO: Make this stricter!
38
+ "required" => %w{name supports controls groups sha256},
39
+ "properties" => {
40
+ "name" => Primitives::STRING,
41
+ "supports" => Primitives.array(Primitives::SUPPORT.ref),
42
+ "controls" => Primitives.array(CONTROL.ref),
43
+ "groups" => Primitives.array(Primitives::CONTROL_GROUP.ref),
44
+ "inputs" => Primitives.array(Primitives::INPUT),
45
+ "sha256" => Primitives::STRING,
46
+ "status" => Primitives::STRING,
47
+ "generator" => Primitives::GENERATOR.ref,
48
+ "version" => Primitives::STRING,
49
+
50
+ # Other properties possible in inspec docs, but that aren"t guaranteed
51
+ "title" => Primitives::STRING,
52
+ "maintainer" => Primitives::STRING,
53
+ "copyright" => Primitives::STRING,
54
+ "copyright_email" => Primitives::STRING,
55
+ "depends" => Primitives.array(Primitives::DEPENDENCY.ref), # Can have depends, but NOT a parentprofile
56
+ },
57
+ }, [Primitives::SUPPORT, CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::GENERATOR])
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,60 @@
1
+ module Inspec
2
+
3
+ # Heuristics to determine how InSpec was installed.
4
+ module InstallContextHelpers
5
+
6
+ def guess_install_context
7
+ # These all work by simple path recognition
8
+ return "chef-workstation" if chef_workstation_install?
9
+ return "omnibus" if omnibus_install?
10
+ return "chefdk" if chefdk_install?
11
+ return "habitat" if habitat_install?
12
+
13
+ # Order matters here - gem mode is easily mistaken for one of the above
14
+ return "docker" if docker_install?
15
+ return "rubygem" if rubygem_install?
16
+
17
+ return "source" if source_install?
18
+
19
+ "unknown"
20
+ end
21
+
22
+ private
23
+
24
+ def chef_workstation_install?
25
+ !!(src_root.start_with?("/opt/chef-workstation") || src_root.start_with?("C:/opscode/chef-workstation"))
26
+ end
27
+
28
+ def chefdk_install?
29
+ !!(src_root.start_with?("/opt/chef-dk") || src_root.start_with?("C:/opscode/chef-dk"))
30
+ end
31
+
32
+ def docker_install?
33
+ # Our docker image is based on alpine. This could be easily fooled.
34
+ !!(rubygem_install? && path_exist?("/etc/alpine-release")) && path_exist?("/.dockerenv")
35
+ end
36
+
37
+ def habitat_install?
38
+ !!src_root.match(%r{hab/pkgs/chef/inspec/\d+\.\d+\.\d+/\d{14}})
39
+ end
40
+
41
+ def omnibus_install?
42
+ !!(src_root.start_with?("/opt/inspec") || src_root.start_with?("C:/opscode/inspec"))
43
+ end
44
+
45
+ def rubygem_install?
46
+ !!src_root.match(%r{gems/inspec-\d+\.\d+\.\d+})
47
+ end
48
+
49
+ def source_install?
50
+ # These are a couple of examples of dirs removed during packaging
51
+ %w{habitat test}.all? do |devdir|
52
+ path_exist?("#{src_root}/#{devdir}")
53
+ end
54
+ end
55
+
56
+ def path_exist?(path)
57
+ File.exist? path
58
+ end
59
+ end
60
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "4.18.85".freeze
2
+ VERSION = "4.18.97".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.18.85
4
+ version: 4.18.97
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef InSpec Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-06 00:00:00.000000000 Z
11
+ date: 2020-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef-telemetry
@@ -65,19 +65,19 @@ dependencies:
65
65
  - !ruby/object:Gem::Version
66
66
  version: '2.0'
67
67
  - !ruby/object:Gem::Dependency
68
- name: json-schema
68
+ name: json_schemer
69
69
  requirement: !ruby/object:Gem::Requirement
70
70
  requirements:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
- version: '2.8'
73
+ version: 0.2.1
74
74
  type: :runtime
75
75
  prerelease: false
76
76
  version_requirements: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - "~>"
79
79
  - !ruby/object:Gem::Version
80
- version: '2.8'
80
+ version: 0.2.1
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: method_source
83
83
  requirement: !ruby/object:Gem::Requirement
@@ -612,6 +612,12 @@ files:
612
612
  - lib/inspec/runner_rspec.rb
613
613
  - lib/inspec/runtime_profile.rb
614
614
  - lib/inspec/schema.rb
615
+ - lib/inspec/schema/README.md
616
+ - lib/inspec/schema/exec_json.rb
617
+ - lib/inspec/schema/exec_json_min.rb
618
+ - lib/inspec/schema/output_schema.rb
619
+ - lib/inspec/schema/primitives.rb
620
+ - lib/inspec/schema/profile_json.rb
615
621
  - lib/inspec/secrets.rb
616
622
  - lib/inspec/secrets/yaml.rb
617
623
  - lib/inspec/shell.rb
@@ -634,6 +640,7 @@ files:
634
640
  - lib/inspec/utils/filter_array.rb
635
641
  - lib/inspec/utils/find_files.rb
636
642
  - lib/inspec/utils/hash.rb
643
+ - lib/inspec/utils/install_context.rb
637
644
  - lib/inspec/utils/json_log.rb
638
645
  - lib/inspec/utils/modulator.rb
639
646
  - lib/inspec/utils/nginx_parser.rb