inspec-core 5.22.50 → 6.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/Chef-EULA +9 -0
  3. data/Gemfile +14 -4
  4. data/etc/features.sig +6 -0
  5. data/etc/features.yaml +97 -0
  6. data/inspec-core.gemspec +17 -7
  7. data/lib/inspec/backend.rb +2 -0
  8. data/lib/inspec/base_cli.rb +80 -4
  9. data/lib/inspec/cached_fetcher.rb +24 -3
  10. data/lib/inspec/cli.rb +293 -236
  11. data/lib/inspec/config.rb +24 -2
  12. data/lib/inspec/dependencies/cache.rb +33 -0
  13. data/lib/inspec/enhanced_outcomes.rb +1 -0
  14. data/lib/inspec/errors.rb +5 -0
  15. data/lib/inspec/exceptions.rb +2 -0
  16. data/lib/inspec/feature/config.rb +75 -0
  17. data/lib/inspec/feature/runner.rb +29 -0
  18. data/lib/inspec/feature.rb +42 -0
  19. data/lib/inspec/fetcher/git.rb +5 -0
  20. data/lib/inspec/fetcher/url.rb +24 -4
  21. data/lib/inspec/globals.rb +6 -0
  22. data/lib/inspec/iaf_file.rb +3 -2
  23. data/lib/inspec/input_registry.rb +5 -1
  24. data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +7 -0
  25. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +30 -2
  26. data/lib/inspec/profile.rb +44 -1
  27. data/lib/inspec/reporters.rb +67 -54
  28. data/lib/inspec/resources/nftables.rb +14 -1
  29. data/lib/inspec/resources/oracledb_session.rb +12 -3
  30. data/lib/inspec/resources/ssh_config.rb +100 -9
  31. data/lib/inspec/resources/ssh_key.rb +124 -0
  32. data/lib/inspec/resources/sshd_active_config.rb +2 -0
  33. data/lib/inspec/resources/sybase_session.rb +11 -2
  34. data/lib/inspec/resources.rb +1 -0
  35. data/lib/inspec/rule.rb +6 -6
  36. data/lib/inspec/run_data.rb +7 -5
  37. data/lib/inspec/runner.rb +43 -6
  38. data/lib/inspec/runner_rspec.rb +12 -9
  39. data/lib/inspec/secrets/yaml.rb +9 -3
  40. data/lib/inspec/shell.rb +10 -0
  41. data/lib/inspec/ui.rb +4 -0
  42. data/lib/inspec/utils/licensing_config.rb +9 -0
  43. data/lib/inspec/utils/telemetry/base.rb +149 -0
  44. data/lib/inspec/utils/telemetry/http.rb +40 -0
  45. data/lib/inspec/utils/telemetry/null.rb +11 -0
  46. data/lib/inspec/utils/telemetry/run_context_probe.rb +13 -1
  47. data/lib/inspec/utils/telemetry.rb +74 -3
  48. data/lib/inspec/version.rb +1 -1
  49. data/lib/inspec/waiver_file_reader.rb +68 -27
  50. data/lib/inspec.rb +2 -2
  51. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -168
  52. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
  53. data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
  54. data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
  55. data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
  56. data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
  57. data/lib/plugins/inspec-license/README.md +16 -0
  58. data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
  59. data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
  60. data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
  61. data/lib/plugins/inspec-parallel/README.md +27 -0
  62. data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
  63. data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
  64. data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
  65. data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
  66. data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +270 -0
  67. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
  68. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
  69. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +125 -0
  70. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
  71. data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
  72. data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
  73. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +20 -8
  74. data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
  75. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
  76. metadata +61 -19
  77. data/lib/inspec/utils/telemetry/collector.rb +0 -81
  78. data/lib/inspec/utils/telemetry/data_series.rb +0 -44
  79. data/lib/inspec/utils/telemetry/global_methods.rb +0 -22
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+ require "chef-licensing"
3
+ require "securerandom" unless defined?(SecureRandom)
4
+ require "digest" unless defined?(Digest)
5
+ require_relative "../../dist"
6
+ module Inspec
7
+ class Telemetry
8
+ class Base
9
+ VERSION = 2.0
10
+ TYPE = "job"
11
+ JOB_TYPE = "InSpec"
12
+
13
+ attr_accessor :scratch
14
+
15
+ def fetch_license_ids
16
+ Inspec::Log.debug "Fetching license IDs for telemetry"
17
+ @license_keys ||= ChefLicensing.license_keys
18
+ end
19
+
20
+ def create_wrapper
21
+ Inspec::Log.debug "Initialising wrapper for telemetry"
22
+ {
23
+ version: VERSION,
24
+ createdTimeUTC: Time.now.getutc.iso8601,
25
+ environment: Inspec::Telemetry::RunContextProbe.guess_run_context,
26
+ licenseIds: fetch_license_ids,
27
+ source: "#{Inspec::Dist::EXEC_NAME}:#{Inspec::VERSION}",
28
+ type: TYPE,
29
+ }
30
+ end
31
+
32
+ def note_feature_usage(feature_name)
33
+ @scratch ||= {}
34
+ @scratch[:features] ||= []
35
+ @scratch[:features] << feature_name
36
+ end
37
+
38
+ def run_starting(_opts = {})
39
+ Inspec::Log.debug "Initiating telemetry for InSpec"
40
+ @scratch ||= {}
41
+ @scratch[:features] ||= []
42
+ @scratch[:run_start_time] = Time.now.getutc.iso8601
43
+ end
44
+
45
+ def run_ending(opts)
46
+ note_per_run_features(opts)
47
+
48
+ payload = create_wrapper
49
+
50
+ train_platform = opts[:runner].backend.backend.platform
51
+ payload[:platform] = train_platform.name
52
+
53
+ payload[:jobs] = [{
54
+ type: JOB_TYPE,
55
+
56
+ # Target platform info
57
+ environment: {
58
+ host: obscure(URI(opts[:runner].backend.backend.uri).host) || "unknown",
59
+ os: train_platform.name,
60
+ version: train_platform.release,
61
+ architecture: train_platform.arch || "",
62
+ id: train_platform.uuid,
63
+ },
64
+
65
+ runtime: Inspec::VERSION,
66
+ content: [], # one content == one profile
67
+ steps: [], # one step == one control
68
+ }]
69
+
70
+ opts[:run_data][:profiles].each do |profile|
71
+ payload[:jobs][0][:content] << {
72
+ name: obscure(profile[:name]),
73
+ version: profile[:version],
74
+ sha256: profile[:sha256],
75
+ maintainer: profile[:maintainer] || "",
76
+ type: "profile",
77
+ }
78
+
79
+ profile[:controls].each do |control|
80
+ payload[:jobs][0][:steps] << {
81
+ id: obscure(control[:id]),
82
+ name: "inspec-control",
83
+ description: control[:desc] || "",
84
+ target: {
85
+ mode: opts[:runner].backend.backend.backend_type,
86
+ id: opts[:runner].backend.backend.platform.uuid,
87
+ },
88
+ resources: [],
89
+ features: [],
90
+ tags: format_control_tags(control[:tags]),
91
+ }
92
+
93
+ control[:results]&.each do |resource_block|
94
+ payload[:jobs][0][:steps].last[:resources] << {
95
+ type: "inspec-resource",
96
+ name: resource_block[:resource_class],
97
+ id: obscure(resource_block[:resource_title].respond_to?(:resource_id) ? resource_block[:resource_title].resource_id : nil) || "unknown",
98
+ }
99
+ end
100
+
101
+ # Per-control features.
102
+ payload[:jobs][0][:steps].last[:features] = scratch[:features].dup
103
+ end
104
+ end
105
+
106
+ Inspec::Log.debug "Final data for telemetry upload -> #{payload}"
107
+ Inspec::Log.debug "Finishing telemetry for InSpec"
108
+ # Return payload object for testing
109
+ payload
110
+ end
111
+
112
+ def format_control_tags(tags)
113
+ tags_list = []
114
+ tags.each do |key, value|
115
+ tags_list << { name: key.to_s, value: (value || "").to_s }
116
+ end
117
+ tags_list
118
+ end
119
+
120
+ # Hash text if non-nil
121
+ def obscure(cleartext)
122
+ return nil if cleartext.nil?
123
+ return nil if cleartext.empty?
124
+
125
+ Digest::SHA2.new(256).hexdigest(cleartext)
126
+ end
127
+
128
+ def note_per_run_features(opts)
129
+ note_all_invoked_features
130
+ note_gem_dependency_usage(opts)
131
+ end
132
+
133
+ def note_all_invoked_features
134
+ Inspec::Feature.list_all_invoked_features.each do |feature|
135
+ Inspec::Telemetry.note_feature_usage(feature.to_s)
136
+ end
137
+ end
138
+
139
+ def note_gem_dependency_usage(opts)
140
+ unless opts[:runner].target_profiles.map do |tp|
141
+ tp.metadata.gem_dependencies + \
142
+ tp.locked_dependencies.list.map { |_k, v| v.profile.metadata.gem_dependencies }.flatten
143
+ end.flatten.empty?
144
+ Inspec::Telemetry.note_feature_usage("inspec-gem-deps-in-profiles")
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require_relative "base"
3
+ require "faraday" unless defined?(Faraday)
4
+ require "inspec/utils/licensing_config"
5
+ module Inspec
6
+ class Telemetry
7
+ class HTTP < Base
8
+ TELEMETRY_JOBS_PATH = "v1/job"
9
+ TELEMETRY_URL = if ChefLicensing::Config.license_server_url&.match?("acceptance")
10
+ ENV["CHEF_TELEMETRY_URL"]
11
+ else
12
+ "https://services.chef.io/telemetry/"
13
+ end
14
+ def run_ending(opts)
15
+ payload = super
16
+ response = connection.post(TELEMETRY_JOBS_PATH) do |req|
17
+ req.body = payload.to_json
18
+ end
19
+ if response.success?
20
+ Inspec::Log.debug "HTTP connection with Telemetry Client successful."
21
+ Inspec::Log.debug "HTTP response from Telemetry Client -> #{response.to_hash}"
22
+ true
23
+ else
24
+ Inspec::Log.debug "HTTP connection with Telemetry Client faced an error."
25
+ Inspec::Log.debug "HTTP error -> #{response.to_hash[:body]["error"]}" if response.to_hash[:body] && response.to_hash[:body]["error"]
26
+ false
27
+ end
28
+ rescue Faraday::ConnectionFailed
29
+ Inspec::Log.debug "HTTP connection failure with telemetry url -> #{TELEMETRY_URL}"
30
+ end
31
+
32
+ def connection
33
+ Faraday.new(url: TELEMETRY_URL) do |config|
34
+ config.request :json
35
+ config.response :json
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ require_relative "base"
3
+ module Inspec
4
+ class Telemetry
5
+ class Null < Base
6
+ def run_starting(_opts); end
7
+ def run_ending(_opts); end
8
+ end
9
+ end
10
+ end
11
+
@@ -1,9 +1,21 @@
1
1
  module Inspec
2
- module Telemetry
2
+ class Telemetry
3
3
  # Guesses the run context of InSpec - how were we invoked?
4
4
  # All stack values here are determined experimentally
5
5
 
6
6
  class RunContextProbe
7
+ # Guess if we are running under Automate
8
+ def self.under_automate?
9
+ # Currently assume we are under automate if we have an automate-based reporter
10
+ Inspec::Config.cached[:reporter]
11
+ .keys
12
+ .map(&:to_s)
13
+ .any? { |n| n =~ /automate/ }
14
+ end
15
+
16
+ # Guess, using stack introspection, if we were called under
17
+ # test-kitchen, cli, audit-cookbook, or otherwise.
18
+ # TODO add compliance-phase of chef-infra
7
19
  def self.guess_run_context(stack = nil)
8
20
  stack ||= caller_locations
9
21
  return "test-kitchen" if kitchen?(stack)
@@ -1,3 +1,74 @@
1
- require "inspec/utils/telemetry/collector"
2
- require "inspec/utils/telemetry/data_series"
3
- require "inspec/utils/telemetry/global_methods"
1
+ require "time" unless defined?(Time.zone_offset)
2
+ require "chef-licensing"
3
+ require_relative "telemetry/null"
4
+ require_relative "telemetry/http"
5
+ require_relative "telemetry/run_context_probe"
6
+
7
+ module Inspec
8
+ class Telemetry
9
+
10
+ @@instance = nil
11
+ @@config = nil
12
+
13
+ def self.instance
14
+ @@instance ||= determine_backend_class.new
15
+ end
16
+
17
+ def self.determine_backend_class
18
+ # Don't perform telemetry action for other InSpec distros
19
+ # Don't perform telemetry action if running under Automate - Automate does LDC tracking for us
20
+ # Don't perform telemetry action if license is a commercial license
21
+
22
+ if Inspec::Dist::EXEC_NAME != "inspec" ||
23
+ Inspec::Telemetry::RunContextProbe.under_automate? ||
24
+ license&.license_type&.downcase == "commercial"
25
+
26
+ Inspec::Log.debug "Determined telemetry operation is not applicable and hence aborting it."
27
+ return Inspec::Telemetry::Null
28
+ end
29
+
30
+ if Inspec::Dist::EXEC_NAME == "inspec" && telemetry_disabled?
31
+ # Issue a warning if an InSpec user is explicitly trying to opt out of telemetry using cli option
32
+ Inspec::Log.warn "Telemetry opt-out is not permissible."
33
+ end
34
+
35
+ Inspec::Log.debug "Determined HTTP instance for telemetry"
36
+
37
+ Inspec::Telemetry::HTTP
38
+ end
39
+
40
+ def self.license
41
+ Inspec::Log.debug "Fetching license context for telemetry check"
42
+ @license = ChefLicensing.license_context
43
+ end
44
+
45
+ ######
46
+ # These class methods make it convenient to call from anywhere within the InSpec codebase.
47
+ ######
48
+ def self.run_starting(opts)
49
+ @@config ||= opts[:conf]
50
+ instance.run_starting(opts)
51
+ rescue StandardError => e
52
+ Inspec::Log.debug "Encountered error in Telemetry start run call -> #{e.message}"
53
+ end
54
+
55
+ def self.run_ending(opts)
56
+ @@config ||= opts[:conf]
57
+ instance.run_ending(opts)
58
+ rescue StandardError => e
59
+ Inspec::Log.debug "Encountered error in Telemetry end run call -> #{e.message}"
60
+ end
61
+
62
+ def self.note_feature_usage(feature_name)
63
+ instance.note_feature_usage(feature_name)
64
+ end
65
+
66
+ def self.config
67
+ @@config
68
+ end
69
+
70
+ def self.telemetry_disabled?
71
+ config.telemetry_options["enable_telemetry"].nil? ? false : !config.telemetry_options["enable_telemetry"]
72
+ end
73
+ end
74
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "5.22.50".freeze
2
+ VERSION = "6.8.1".freeze
3
3
  end
@@ -15,49 +15,90 @@ module Inspec
15
15
  output = {}
16
16
 
17
17
  files.each do |file_path|
18
- file_extension = File.extname(file_path)
19
- data = nil
20
- if [".yaml", ".yml"].include? file_extension
21
- data = Secrets::YAML.resolve(file_path)
22
- unless data.nil?
23
- data = data.inputs
24
- validate_json_yaml(data)
25
- end
26
- elsif file_extension == ".csv"
27
- data = Waivers::CSVFileReader.resolve(file_path)
28
- headers = Waivers::CSVFileReader.headers
29
- validate_headers(headers)
30
- elsif file_extension == ".json"
31
- data = Waivers::JSONFileReader.resolve(file_path)
32
- validate_json_yaml(data) unless data.nil?
33
- end
18
+ data = read_from_file(file_path)
34
19
  output.merge!(data) if !data.nil? && data.is_a?(Hash)
35
20
 
36
21
  if data.nil?
37
22
  raise Inspec::Exceptions::WaiversFileNotReadable,
38
- "Cannot find parser for waivers file '#{file_path}'. " \
23
+ "Cannot find parser for waivers file." \
39
24
  "Check to make sure file has the appropriate extension."
40
25
  end
26
+ rescue Inspec::Exceptions::WaiversFileNotReadable, Inspec::Exceptions::WaiversFileInvalidFormatting => e
27
+ Inspec::Log.error "Error reading waivers file #{file_path}. #{e.message}"
28
+ Inspec::UI.new.exit(:usage_error)
41
29
  end
42
30
 
43
31
  @waivers_data[profile_id] = output
44
32
  end
45
33
 
46
- def self.validate_headers(headers, json_yaml = false)
47
- required_fields = json_yaml ? %w{justification} : %w{control_id justification}
48
- all_fields = %w{control_id justification expiration_date run}
34
+ def self.read_from_file(file_path)
35
+ data = nil
36
+ file_extension = File.extname(file_path)
37
+ if [".yaml", ".yml"].include? file_extension
38
+ data = Secrets::YAML.resolve(file_path)
39
+ data = data.inputs unless data.nil?
40
+ validate_json_yaml(data)
41
+ elsif file_extension == ".csv"
42
+ data = Waivers::CSVFileReader.resolve(file_path)
43
+ headers = Waivers::CSVFileReader.headers
44
+ validate_csv_headers(headers)
45
+ elsif file_extension == ".json"
46
+ data = Waivers::JSONFileReader.resolve(file_path)
47
+ validate_json_yaml(data) unless data.nil?
48
+ end
49
+ data
50
+ end
51
+
52
+ def self.all_fields
53
+ %w{control_id justification expiration_date run}
54
+ end
55
+
56
+ def self.validate_csv_headers(headers)
57
+ invalid_headers_info = fetch_invalid_headers_info(headers)
58
+ # Warn if blank column found in csv file
59
+ Inspec::Log.warn "Invalid column headers: Column can't be nil" if invalid_headers_info[:blank_column]
60
+ # Warn if extra header found in csv file
61
+ Inspec::Log.warn "Extra header/s #{invalid_headers_info[:extra_headers]}" unless invalid_headers_info[:extra_headers].empty?
62
+ unless invalid_headers_info[:missing_required_fields].empty?
63
+ raise Inspec::Exceptions::WaiversFileInvalidFormatting,
64
+ "Missing required header/s #{invalid_headers_info[:missing_required_fields]}. Fix headers in file to proceed."
65
+ end
66
+ end
49
67
 
50
- Inspec::Log.warn "Missing column headers: #{(required_fields - headers)}" unless (required_fields - headers).empty?
51
- Inspec::Log.warn "Invalid column header: Column can't be nil" if headers.include? nil
52
- Inspec::Log.warn "Extra column headers: #{(headers - all_fields)}" unless (headers - all_fields).empty?
68
+ def self.fetch_invalid_headers_info(headers, json_yaml = false)
69
+ required_fields = json_yaml ? %w{justification} : %w{control_id justification}
70
+ data = {}
71
+ data[:missing_required_fields] = []
72
+ # Finds missing required fields
73
+ unless (required_fields - headers).empty?
74
+ data[:missing_required_fields] = required_fields - headers
75
+ end
76
+ # If column with no header found set the blank_column flag. Only applicable for csv
77
+ data[:blank_column] = headers.include?(nil) ? true : false
78
+ # Find extra headers/parameters
79
+ data[:extra_headers] = (headers - all_fields)
80
+ data
53
81
  end
54
82
 
55
83
  def self.validate_json_yaml(data)
56
- headers = []
57
- data.each_value do |value|
58
- headers.push value.keys
84
+ missing_required_field = false
85
+ data.each do |key, value|
86
+ # In case of yaml or json we need to validate headers/parametes for each value
87
+ invalid_headers_info = fetch_invalid_headers_info(value.keys, true)
88
+ # WARN in case of extra parameters found in each waived control
89
+ Inspec::Log.warn "Control ID #{key}: extra parameter/s #{invalid_headers_info[:extra_headers]}" unless invalid_headers_info[:extra_headers].empty?
90
+ unless invalid_headers_info[:missing_required_fields].empty?
91
+ missing_required_field = true
92
+ # Log error for each waived control
93
+ Inspec::Log.error "Control ID #{key}: missing required parameter/s #{invalid_headers_info[:missing_required_fields]}"
94
+ end
95
+ end
96
+
97
+ # Raise error if any of the waived control has missing required filed
98
+ if missing_required_field
99
+ raise Inspec::Exceptions::WaiversFileInvalidFormatting,
100
+ "Missing required parameter [justification]. Fix parameters in file to proceed."
59
101
  end
60
- validate_headers(headers.flatten.uniq, true)
61
102
  end
62
103
  end
63
104
  end
data/lib/inspec.rb CHANGED
@@ -4,6 +4,7 @@ libdir = File.dirname(__FILE__)
4
4
  $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
5
5
 
6
6
  require "inspec/version"
7
+ require "inspec/utils/licensing_config"
7
8
  require "inspec/exceptions"
8
9
  require "inspec/utils/deprecation"
9
10
  require "inspec/profile"
@@ -18,7 +19,6 @@ require "inspec/rspec_extensions"
18
19
  require "inspec/globals"
19
20
  require "inspec/impact"
20
21
  require "inspec/utils/telemetry"
21
- require "inspec/utils/telemetry/global_methods"
22
22
 
23
23
  require "inspec/plugin/v2"
24
24
  require "inspec/plugin/v1"
@@ -30,4 +30,4 @@ require "inspec/source_reader"
30
30
  require "inspec/resource"
31
31
 
32
32
  require "inspec/dependency_loader"
33
- require "inspec/dependency_installer"
33
+ require "inspec/dependency_installer"