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