inspec-core 6.6.0 → 6.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +22 -22
- data/etc/features.sig +6 -6
- data/etc/features.yaml +3 -0
- data/inspec-core.gemspec +10 -3
- data/lib/inspec/base_cli.rb +1 -1
- data/lib/inspec/cli.rb +1 -1
- data/lib/inspec/config.rb +9 -0
- data/lib/inspec/dependencies/dependency_set.rb +2 -2
- data/lib/inspec/dsl.rb +1 -1
- data/lib/inspec/feature/runner.rb +4 -1
- data/lib/inspec/feature.rb +8 -0
- data/lib/inspec/fetcher/url.rb +29 -7
- data/lib/inspec/iaf_file.rb +3 -2
- data/lib/inspec/input_registry.rb +5 -1
- data/lib/inspec/profile.rb +2 -2
- data/lib/inspec/reporters/cli.rb +1 -1
- data/lib/inspec/resources/nftables.rb +14 -1
- data/lib/inspec/resources/oracledb_session.rb +12 -3
- data/lib/inspec/resources/ssh_config.rb +100 -9
- data/lib/inspec/resources/ssh_key.rb +124 -0
- data/lib/inspec/resources/sshd_active_config.rb +2 -0
- data/lib/inspec/resources/sybase_session.rb +11 -2
- data/lib/inspec/resources/virtualization.rb +1 -1
- data/lib/inspec/resources.rb +1 -0
- data/lib/inspec/rule.rb +15 -10
- data/lib/inspec/runner.rb +10 -2
- data/lib/inspec/utils/profile_ast_helpers.rb +1 -2
- data/lib/inspec/utils/telemetry/base.rb +149 -0
- data/lib/inspec/utils/telemetry/http.rb +40 -0
- data/lib/inspec/utils/telemetry/null.rb +11 -0
- data/lib/inspec/utils/telemetry/run_context_probe.rb +13 -1
- data/lib/inspec/utils/telemetry.rb +74 -3
- data/lib/inspec/utils/waivers/csv_file_reader.rb +1 -1
- data/lib/inspec/utils/waivers/excel_file_reader.rb +1 -1
- data/lib/inspec/version.rb +1 -1
- data/lib/inspec.rb +0 -1
- data/lib/matchers/matchers.rb +3 -3
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +5 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +1 -0
- data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +14 -6
- metadata +27 -11
- data/lib/inspec/utils/telemetry/collector.rb +0 -81
- data/lib/inspec/utils/telemetry/data_series.rb +0 -44
- data/lib/inspec/utils/telemetry/global_methods.rb +0 -22
@@ -0,0 +1,124 @@
|
|
1
|
+
require "inspec/utils/file_reader"
|
2
|
+
require "net/ssh" unless defined?(Net::SSH)
|
3
|
+
|
4
|
+
# Change module if required
|
5
|
+
module Inspec::Resources
|
6
|
+
class SshKey < FileResource
|
7
|
+
# Every resource requires an internal name.
|
8
|
+
name "ssh_key"
|
9
|
+
|
10
|
+
# Restrict to only run on the below platforms (if none were given,
|
11
|
+
# all OS's and cloud API's supported)
|
12
|
+
supports platform: "unix"
|
13
|
+
supports platform: "windows"
|
14
|
+
|
15
|
+
desc "public/private SSH key pair test"
|
16
|
+
|
17
|
+
example <<~EXAMPLE
|
18
|
+
describe ssh_key('path: ~/.ssh/id_rsa') do
|
19
|
+
its('key_length') { should eq 4096 }
|
20
|
+
its('type') { should cmp /rsa/ }
|
21
|
+
it { should be_private }
|
22
|
+
end
|
23
|
+
EXAMPLE
|
24
|
+
|
25
|
+
include FileReader
|
26
|
+
|
27
|
+
def initialize(keypath, passphrase = nil)
|
28
|
+
skip_resource "The `ssh_key` resource is not yet available on your OS." unless inspec.os.unix? || inspec.os.windows?
|
29
|
+
@key_path = set_ssh_key_path(keypath)
|
30
|
+
@passphrase = passphrase
|
31
|
+
@key = read_ssh_key
|
32
|
+
super(@key_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def public?
|
36
|
+
return if @key.nil?
|
37
|
+
|
38
|
+
@key[:public]
|
39
|
+
end
|
40
|
+
|
41
|
+
def private?
|
42
|
+
return if @key.nil?
|
43
|
+
|
44
|
+
@key[:private]
|
45
|
+
end
|
46
|
+
|
47
|
+
def key_length
|
48
|
+
return if @key.nil?
|
49
|
+
|
50
|
+
@key[:key_length]
|
51
|
+
end
|
52
|
+
|
53
|
+
def type
|
54
|
+
return if @key.nil?
|
55
|
+
|
56
|
+
@key[:type]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Define a resource ID. This is used in reporting engines to uniquely identify the individual resource.
|
60
|
+
# This might be a file path, or a process ID, or a cloud instance ID. Only meaningful to the implementation.
|
61
|
+
# Must be a string. Defaults to the empty string if not implemented.
|
62
|
+
def resource_id
|
63
|
+
@key_path || "SSH key"
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
"ssh_key #{@key_path}"
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def set_ssh_key_path(keypath)
|
73
|
+
if File.exist?(keypath)
|
74
|
+
@key_path = keypath
|
75
|
+
elsif File.exist?(File.join("#{Dir.home}/.ssh/", keypath))
|
76
|
+
@key_path = File.join("#{Dir.home}/.ssh/", keypath)
|
77
|
+
else
|
78
|
+
raise Inspec::Exceptions::ResourceSkipped, "Can't find file: #{keypath}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def read_ssh_key
|
83
|
+
key_data = {}
|
84
|
+
key = nil
|
85
|
+
filecontent = read_file_content((@key_path), @passphrase)
|
86
|
+
raise Inspec::Exceptions::ResourceSkipped, "File is empty: #{@key_path}" if filecontent.split("\n").empty?
|
87
|
+
|
88
|
+
if filecontent.split("\n")[0].include?("PRIVATE")
|
89
|
+
# Net::SSH::KeyFactory does not have support to load private key for DSA
|
90
|
+
key = Net::SSH::KeyFactory.load_private_key(@key_path, @passphrase, false)
|
91
|
+
unless key.nil?
|
92
|
+
key_data[:private] = true
|
93
|
+
key_data[:public] = false
|
94
|
+
# The data send for ssh type is not in same format so it's good to match on the string
|
95
|
+
key_data[:type] = key.ssh_type
|
96
|
+
key_data[:key_length] = key_lengh(key)
|
97
|
+
end
|
98
|
+
else
|
99
|
+
key = Net::SSH::KeyFactory.load_public_key(@key_path)
|
100
|
+
unless key.nil?
|
101
|
+
key_data[:private] = false
|
102
|
+
key_data[:public] = true
|
103
|
+
# The data send for ssh type is not in same format so it's good to match on the string
|
104
|
+
key_data[:type] = key.ssh_type
|
105
|
+
key_data[:key_length] = key_lengh(key)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
key_data
|
110
|
+
rescue OpenSSL::PKey::PKeyError => e
|
111
|
+
raise Inspec::Exceptions::ResourceFailed, "#{e.message}"
|
112
|
+
end
|
113
|
+
|
114
|
+
def key_lengh(key)
|
115
|
+
if key.class.to_s == "OpenSSL::PKey::RSA"
|
116
|
+
key.public_key.n.num_bits
|
117
|
+
else
|
118
|
+
# Unable to get the key lenght data for other types of keys
|
119
|
+
# TODO: Need to check if there is any method that will get this info.
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -44,10 +44,19 @@ module Inspec::Resources
|
|
44
44
|
# try to get a temp path
|
45
45
|
sql_file_path = upload_sql_file(sql)
|
46
46
|
|
47
|
+
# TODO: Find if there is better way to get the current shell
|
48
|
+
current_shell = inspec.command("echo $SHELL")
|
49
|
+
|
50
|
+
res = current_shell.exit_status
|
51
|
+
|
47
52
|
# isql reuires that we have a matching locale set, but does not support C.UTF-8. en_US.UTF-8 is the least evil.
|
48
|
-
|
49
|
-
|
53
|
+
if res == 0 && ( current_shell.stdout&.include?("/csh") || current_shell.stdout&.include?("/tcsh") )
|
54
|
+
command = "source #{sybase_home}/SYBASE.csh; setenv LANG en_US.UTF-8; #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
|
55
|
+
else
|
56
|
+
command = "LANG=en_US.UTF-8 SYBASE=#{sybase_home} #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
|
57
|
+
end
|
50
58
|
|
59
|
+
isql_cmd = inspec.command(command)
|
51
60
|
# Check for isql errors
|
52
61
|
res = isql_cmd.exit_status
|
53
62
|
raise Inspec::Exceptions::ResourceFailed.new("isql exited with code #{res} and stderr '#{isql_cmd.stderr}', stdout '#{isql_cmd.stdout}'") unless res == 0
|
@@ -223,7 +223,7 @@ module Inspec::Resources
|
|
223
223
|
elsif cgroup_content =~ %r{^\d+:[^:]+:/(kubepods)/.+$}
|
224
224
|
@virtualization_data[:system] = $1
|
225
225
|
@virtualization_data[:role] = "guest"
|
226
|
-
elsif /container=podman/.match?(
|
226
|
+
elsif /container=podman/.match?(inspec.file("/proc/1/environ").content)
|
227
227
|
@virtualization_data[:system] = "podman"
|
228
228
|
@virtualization_data[:role] = "guest"
|
229
229
|
elsif lxc_version_exists? && cgroup_content =~ %r{\d:[^:]+:/$}
|
data/lib/inspec/resources.rb
CHANGED
@@ -110,6 +110,7 @@ require "inspec/resources/selinux"
|
|
110
110
|
require "inspec/resources/service"
|
111
111
|
require "inspec/resources/shadow"
|
112
112
|
require "inspec/resources/ssh_config"
|
113
|
+
require "inspec/resources/ssh_key"
|
113
114
|
require "inspec/resources/ssl"
|
114
115
|
require "inspec/resources/sys_info"
|
115
116
|
require "inspec/resources/toml"
|
data/lib/inspec/rule.rb
CHANGED
@@ -375,19 +375,24 @@ module Inspec
|
|
375
375
|
# only_if mechanism)
|
376
376
|
# Double underscore: not intended to be called as part of the DSL
|
377
377
|
def __apply_waivers
|
378
|
+
@__waiver_data = nil
|
378
379
|
control_id = @__rule_id # TODO: control ID slugging
|
379
|
-
waiver_files = Inspec::Config.cached.final_options["waiver_file"] if Inspec::Config.cached.respond_to?(:final_options)
|
380
|
-
|
381
|
-
waiver_data_by_profile = Inspec::WaiverFileReader.fetch_waivers_by_profile(__profile_id, waiver_files) unless waiver_files.nil?
|
382
380
|
|
383
|
-
|
381
|
+
waiver_files = Inspec::Config.cached.final_options["waiver_file"] if Inspec::Config.cached.respond_to?(:final_options)
|
382
|
+
if waiver_files.nil? || waiver_files.empty?
|
383
|
+
# Support for input registry is provided for backward compatibilty with compliance phase of chef-client
|
384
|
+
# Chef-client sends waiver information in inputs hash
|
385
|
+
input_registry = Inspec::InputRegistry.instance
|
386
|
+
waiver_data_via_input = input_registry.inputs_by_profile.dig(__profile_id, control_id)
|
387
|
+
return unless waiver_data_via_input && waiver_data_via_input.has_value? && waiver_data_via_input.value.is_a?(Hash)
|
388
|
+
|
389
|
+
@__waiver_data = waiver_data_via_input.value
|
390
|
+
else
|
391
|
+
waiver_data_by_profile = Inspec::WaiverFileReader.fetch_waivers_by_profile(__profile_id, waiver_files)
|
392
|
+
return unless waiver_data_by_profile && waiver_data_by_profile[control_id] && waiver_data_by_profile[control_id].is_a?(Hash)
|
384
393
|
|
385
|
-
|
386
|
-
|
387
|
-
# log of each "set" event so that when it is collapsed to a value,
|
388
|
-
# it can determine the correct (highest priority) value.
|
389
|
-
# Store in an instance variable for.. later reading???
|
390
|
-
@__waiver_data = waiver_data_by_profile[control_id]
|
394
|
+
@__waiver_data = waiver_data_by_profile[control_id]
|
395
|
+
end
|
391
396
|
|
392
397
|
__waiver_data["skipped_due_to_waiver"] = false
|
393
398
|
__waiver_data["message"] = ""
|
data/lib/inspec/runner.rb
CHANGED
@@ -12,6 +12,7 @@ require "inspec/dist"
|
|
12
12
|
require "inspec/reporters"
|
13
13
|
require "inspec/runner_rspec"
|
14
14
|
require "chef-licensing"
|
15
|
+
require "inspec/utils/telemetry"
|
15
16
|
# spec requirements
|
16
17
|
|
17
18
|
module Inspec
|
@@ -89,7 +90,12 @@ module Inspec
|
|
89
90
|
end
|
90
91
|
|
91
92
|
def configure_transport
|
92
|
-
|
93
|
+
backend = Inspec::Backend.create(@conf)
|
94
|
+
set_backend(backend)
|
95
|
+
end
|
96
|
+
|
97
|
+
def set_backend(new_backend)
|
98
|
+
@backend = new_backend
|
93
99
|
@test_collector.backend = @backend
|
94
100
|
end
|
95
101
|
|
@@ -145,7 +151,7 @@ module Inspec
|
|
145
151
|
get_check_example(m, a, b)
|
146
152
|
end.compact
|
147
153
|
|
148
|
-
examples.map { |example| total_checks += example.
|
154
|
+
examples.map { |example| total_checks += example.descendant_filtered_examples.count }
|
149
155
|
|
150
156
|
unless control_describe_checks.empty?
|
151
157
|
# controls with empty tests are avoided
|
@@ -174,6 +180,7 @@ module Inspec
|
|
174
180
|
}
|
175
181
|
|
176
182
|
Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
|
183
|
+
Inspec::Telemetry.run_starting(runner: self, conf: @conf)
|
177
184
|
load
|
178
185
|
run_tests(with)
|
179
186
|
rescue ChefLicensing::SoftwareNotEntitled
|
@@ -222,6 +229,7 @@ module Inspec
|
|
222
229
|
@run_data = @test_collector.run(with)
|
223
230
|
# dont output anything if we want a report
|
224
231
|
render_output(@run_data) unless @conf["report"]
|
232
|
+
Inspec::Telemetry.run_ending(runner: self, run_data: @run_data, conf: @conf)
|
225
233
|
@test_collector.exit_code
|
226
234
|
end
|
227
235
|
|
@@ -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
|
@@ -1,9 +1,21 @@
|
|
1
1
|
module Inspec
|
2
|
-
|
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 "
|
2
|
-
require "
|
3
|
-
|
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
|
@@ -19,7 +19,7 @@ module Waivers
|
|
19
19
|
row_hash.delete("control_id")
|
20
20
|
row_hash.delete_if { |k, v| k.nil? || v.nil? }
|
21
21
|
|
22
|
-
waiver_data_hash[control_id] = row_hash if control_id && !row_hash.
|
22
|
+
waiver_data_hash[control_id] = row_hash if control_id && !(row_hash.nil? || row_hash.empty?)
|
23
23
|
end
|
24
24
|
|
25
25
|
waiver_data_hash
|
@@ -25,7 +25,7 @@ module Waivers
|
|
25
25
|
row_hash.delete_if { |k, v| k.nil? || v.nil? }
|
26
26
|
end
|
27
27
|
|
28
|
-
waiver_data_hash[control_id] = row_hash if control_id && !row_hash.
|
28
|
+
waiver_data_hash[control_id] = row_hash if control_id && !(row_hash.nil? || row_hash.empty?)
|
29
29
|
end
|
30
30
|
waiver_data_hash
|
31
31
|
rescue Exception => e
|
data/lib/inspec/version.rb
CHANGED
data/lib/inspec.rb
CHANGED
data/lib/matchers/matchers.rb
CHANGED
@@ -299,9 +299,9 @@ RSpec::Matchers.define :cmp do |first_expected| # rubocop:disable Metrics/BlockL
|
|
299
299
|
end
|
300
300
|
end
|
301
301
|
|
302
|
-
def format_actual(actual)
|
302
|
+
def format_actual(actual, negate = false)
|
303
303
|
actual = "0%o" % actual if octal?(@expected) && !actual.nil?
|
304
|
-
"\n%s\n got: %s\n\n(compared using `cmp` matcher)\n" % [format_expectation(
|
304
|
+
"\n%s\n got: %s\n\n(compared using `cmp` matcher)\n" % [format_expectation(negate), actual]
|
305
305
|
end
|
306
306
|
|
307
307
|
def format_expectation(negate)
|
@@ -316,7 +316,7 @@ RSpec::Matchers.define :cmp do |first_expected| # rubocop:disable Metrics/BlockL
|
|
316
316
|
end
|
317
317
|
|
318
318
|
failure_message_when_negated do |actual|
|
319
|
-
format_actual actual
|
319
|
+
format_actual actual, true
|
320
320
|
end
|
321
321
|
|
322
322
|
description do
|
@@ -22,7 +22,10 @@ module InspecPlugins
|
|
22
22
|
|
23
23
|
def run
|
24
24
|
initiate_background_run if run_in_background # running a process as daemon changes parent process pid
|
25
|
+
original_stdout_stream = ChefLicensing::Config.output
|
25
26
|
until invocations.empty? && @child_tracker.empty?
|
27
|
+
# Changing output to STDERR to avoid the output interruption between runs
|
28
|
+
ChefLicensing::Config.output = STDERR
|
26
29
|
while should_start_more_jobs?
|
27
30
|
if Inspec.locally_windows?
|
28
31
|
spawn_another_process
|
@@ -35,6 +38,8 @@ module InspecPlugins
|
|
35
38
|
cleanup_child_processes
|
36
39
|
sleep 0.1
|
37
40
|
end
|
41
|
+
# Reset output to the original STDOUT stream as a safe measure.
|
42
|
+
ChefLicensing::Config.output = original_stdout_stream
|
38
43
|
|
39
44
|
# Requires renaming operations on windows only
|
40
45
|
# Do Rename and delete operations after all child processes have exited successfully
|