inspec-core 4.18.85 → 4.18.97

Sign up to get free protection for your applications and to get access to all the features.
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