inspec-core 5.22.50 → 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/Chef-EULA +9 -0
- data/Gemfile +14 -4
- data/etc/features.sig +6 -0
- data/etc/features.yaml +97 -0
- data/inspec-core.gemspec +17 -7
- data/lib/inspec/backend.rb +2 -0
- data/lib/inspec/base_cli.rb +80 -4
- data/lib/inspec/cached_fetcher.rb +24 -3
- data/lib/inspec/cli.rb +293 -236
- data/lib/inspec/config.rb +24 -2
- data/lib/inspec/dependencies/cache.rb +33 -0
- data/lib/inspec/enhanced_outcomes.rb +1 -0
- data/lib/inspec/errors.rb +5 -0
- data/lib/inspec/exceptions.rb +2 -0
- data/lib/inspec/feature/config.rb +75 -0
- data/lib/inspec/feature/runner.rb +29 -0
- data/lib/inspec/feature.rb +42 -0
- data/lib/inspec/fetcher/git.rb +5 -0
- data/lib/inspec/fetcher/url.rb +24 -4
- data/lib/inspec/globals.rb +6 -0
- data/lib/inspec/iaf_file.rb +3 -2
- data/lib/inspec/input_registry.rb +5 -1
- data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +7 -0
- data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +30 -2
- data/lib/inspec/profile.rb +44 -1
- data/lib/inspec/reporters.rb +67 -54
- 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.rb +1 -0
- data/lib/inspec/rule.rb +6 -6
- data/lib/inspec/run_data.rb +7 -5
- data/lib/inspec/runner.rb +43 -6
- data/lib/inspec/runner_rspec.rb +12 -9
- data/lib/inspec/secrets/yaml.rb +9 -3
- data/lib/inspec/shell.rb +10 -0
- data/lib/inspec/ui.rb +4 -0
- data/lib/inspec/utils/licensing_config.rb +9 -0
- 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/version.rb +1 -1
- data/lib/inspec/waiver_file_reader.rb +68 -27
- data/lib/inspec.rb +2 -2
- data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -168
- data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
- data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
- data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
- data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
- data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
- data/lib/plugins/inspec-license/README.md +16 -0
- data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
- data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
- data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
- data/lib/plugins/inspec-parallel/README.md +27 -0
- data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +270 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +125 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
- data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +20 -8
- data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
- metadata +61 -19
- 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
data/lib/inspec/config.rb
CHANGED
@@ -7,6 +7,7 @@ require "forwardable" unless defined?(Forwardable)
|
|
7
7
|
require "thor" unless defined?(Thor)
|
8
8
|
require "base64" unless defined?(Base64)
|
9
9
|
require "inspec/plugin/v2/filter"
|
10
|
+
require "inspec/feature"
|
10
11
|
|
11
12
|
module Inspec
|
12
13
|
class Config
|
@@ -25,6 +26,12 @@ module Inspec
|
|
25
26
|
shell_command
|
26
27
|
}.freeze
|
27
28
|
|
29
|
+
AUDIT_LOG_OPTIONS = %w{
|
30
|
+
audit_log_location
|
31
|
+
enable_audit_log
|
32
|
+
audit_log_app_name
|
33
|
+
}.freeze
|
34
|
+
|
28
35
|
KNOWN_VERSIONS = [
|
29
36
|
"1.1",
|
30
37
|
"1.2",
|
@@ -80,6 +87,10 @@ module Inspec
|
|
80
87
|
puts
|
81
88
|
end
|
82
89
|
|
90
|
+
def allow_unsigned_profiles?
|
91
|
+
self["allow_unsigned_profiles"] || ENV["CHEF_ALLOW_UNSIGNED_PROFILES"]
|
92
|
+
end
|
93
|
+
|
83
94
|
# return all telemetry options from config
|
84
95
|
# @return [Hash]
|
85
96
|
def telemetry_options
|
@@ -109,11 +120,14 @@ module Inspec
|
|
109
120
|
def unpack_train_credentials
|
110
121
|
# Internally, use indifferent access while we build the creds
|
111
122
|
credentials = Thor::CoreExt::HashWithIndifferentAccess.new({})
|
112
|
-
|
113
123
|
# Helper methods prefixed with _utc_ (Unpack Train Credentials)
|
114
124
|
|
115
125
|
credentials.merge!(_utc_generic_credentials)
|
116
126
|
|
127
|
+
# Only runs this block when preview flag CHEF_PREVIEW_AUDIT_LOGGING is set
|
128
|
+
Inspec.with_feature("inspec-audit-logging") {
|
129
|
+
credentials.merge!(_utc_merge_audit_log_options)
|
130
|
+
}
|
117
131
|
_utc_determine_backend(credentials)
|
118
132
|
transport_name = credentials[:backend].to_s
|
119
133
|
|
@@ -154,13 +168,21 @@ module Inspec
|
|
154
168
|
|
155
169
|
private
|
156
170
|
|
171
|
+
def _utc_merge_audit_log_options
|
172
|
+
@final_options.select { |option, _value| AUDIT_LOG_OPTIONS.include?(option) }
|
173
|
+
end
|
174
|
+
|
157
175
|
def _utc_merge_transport_options(credentials, transport_name)
|
158
176
|
# Ask Train for the names of the transport options
|
159
177
|
transport_options = Train.options(transport_name).keys.map(&:to_s)
|
160
178
|
|
161
179
|
# If there are any options with those (unprefixed) names, merge them in.
|
180
|
+
# e.g., 'host'
|
162
181
|
unprefixed_transport_options = final_options.select do |option_name, _value|
|
163
|
-
|
182
|
+
# We currently want this option only to be set if CHEF_PREVIEW_AUDIT_LOGGING is set
|
183
|
+
# and we already merging the audit_log_options within _utc_merge_audit_log_options method which only invoke if feature flag is on
|
184
|
+
# we don't want audit log option to get set from here again so this is a safe check and can be removed when we remove preview feature flag.
|
185
|
+
transport_options.include? option_name unless option_name.match?("enable_audit_log")
|
164
186
|
end
|
165
187
|
credentials.merge!(unprefixed_transport_options)
|
166
188
|
|
@@ -70,5 +70,38 @@ module Inspec
|
|
70
70
|
def base_path_for(cache_key)
|
71
71
|
File.join(@path, cache_key)
|
72
72
|
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# For given cache key, return true if the
|
76
|
+
# cache path is locked
|
77
|
+
def locked?(key)
|
78
|
+
locked = false
|
79
|
+
path = base_path_for(key)
|
80
|
+
# For archive there is no need to lock the directory so we skip those and return false for archive formatted cache
|
81
|
+
if File.directory?(path)
|
82
|
+
locked = File.exist?("#{path}/.lock")
|
83
|
+
end
|
84
|
+
locked
|
85
|
+
end
|
86
|
+
|
87
|
+
def lock(cache_path)
|
88
|
+
lock_file_path = File.join(cache_path, ".lock")
|
89
|
+
begin
|
90
|
+
FileUtils.mkdir_p(cache_path)
|
91
|
+
Inspec::Log.debug("Locking cache ..... #{cache_path}")
|
92
|
+
FileUtils.touch(lock_file_path)
|
93
|
+
rescue Errno::EACCES
|
94
|
+
raise "Permission denied while creating cache lock #{cache_path}/.lock."
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def unlock(cache_path)
|
99
|
+
Inspec::Log.debug("Unlocking cache..... #{cache_path}")
|
100
|
+
begin
|
101
|
+
FileUtils.rm("#{cache_path}/.lock") if File.exist?("#{cache_path}/.lock")
|
102
|
+
rescue Errno::EACCES
|
103
|
+
raise "Permission denied while removing cache lock #{cache_path}/.lock"
|
104
|
+
end
|
105
|
+
end
|
73
106
|
end
|
74
107
|
end
|
data/lib/inspec/errors.rb
CHANGED
data/lib/inspec/exceptions.rb
CHANGED
@@ -12,5 +12,7 @@ module Inspec
|
|
12
12
|
class ProfileSigningKeyNotFound < ArgumentError; end
|
13
13
|
class WaiversFileNotReadable < ArgumentError; end
|
14
14
|
class WaiversFileDoesNotExist < ArgumentError; end
|
15
|
+
class WaiversFileInvalidFormatting < ArgumentError; end
|
16
|
+
class InvalidAuditLogOption < ArgumentError; end
|
15
17
|
end
|
16
18
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "inspec/iaf_file" # Uses some of the same encryption routines
|
2
|
+
|
3
|
+
module Inspec
|
4
|
+
class Feature
|
5
|
+
class Config
|
6
|
+
|
7
|
+
VERIFICATION_KEY_NAME = "progress-2022-05-04".freeze
|
8
|
+
|
9
|
+
attr_reader :cfg_data, :valid
|
10
|
+
|
11
|
+
def initialize(conf_path = nil)
|
12
|
+
# If conf path is nil, read from source installation
|
13
|
+
conf_path ||= File.join(Inspec.src_root, "etc", "features.yaml")
|
14
|
+
|
15
|
+
# Verify path and sig file exists or else throw exception
|
16
|
+
sig_path = conf_path.sub(/\.yaml/, ".sig")
|
17
|
+
[conf_path, sig_path].each do |file|
|
18
|
+
raise Inspec::FeatureConfigMissingError.new("No such file #{file}") unless File.exist?(file)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Verify sig matches contents
|
22
|
+
validation_key_path = Inspec::IafFile.find_validation_key(VERIFICATION_KEY_NAME)
|
23
|
+
verification_key = Inspec::IafFile::KEY_ALG.new File.read validation_key_path
|
24
|
+
signature = Base64.decode64 File.read sig_path
|
25
|
+
digest = Inspec::IafFile::ARTIFACT_DIGEST.new
|
26
|
+
unless verification_key.verify digest, signature, File.read(conf_path)
|
27
|
+
# If not load default empty config and raise exception
|
28
|
+
@cfg_data = load_error_data
|
29
|
+
raise Inspec::FeatureConfigTamperedError.new("Feature yaml file does not match signature - tampered?")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Read YAML data from path
|
33
|
+
@cfg_data = YAML.load_file(conf_path)
|
34
|
+
@features_by_name = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_each_feature
|
38
|
+
cfg_data["features"].each do |feature_name, raw_info|
|
39
|
+
feat = @features_by_name[feature_name] ||= Inspec::Feature.new(feature_name.to_sym, raw_info)
|
40
|
+
yield(feat)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def [](feature_name)
|
45
|
+
raw_info = cfg_data["features"][feature_name]
|
46
|
+
return nil unless raw_info
|
47
|
+
|
48
|
+
@features_by_name[feature_name] ||= Inspec::Feature.new(feature_name.to_sym, raw_info)
|
49
|
+
end
|
50
|
+
|
51
|
+
def feature_name?(query)
|
52
|
+
cfg_data["features"].key?(query.to_s)
|
53
|
+
end
|
54
|
+
|
55
|
+
def features
|
56
|
+
@features ||= load_features
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def load_features
|
62
|
+
feats = []
|
63
|
+
with_each_feature { |f| feats << f }
|
64
|
+
feats
|
65
|
+
end
|
66
|
+
|
67
|
+
# Default data for when the config is in an error state.
|
68
|
+
def load_error_data
|
69
|
+
{
|
70
|
+
"features": {},
|
71
|
+
}
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Inspec
|
2
|
+
class Feature
|
3
|
+
class Runner
|
4
|
+
def self.with_feature(feature_name, opts = {}, &feature_implementation)
|
5
|
+
config = opts[:config] || Inspec::Feature::Config.new
|
6
|
+
logger = opts[:logger] || Inspec::Log
|
7
|
+
|
8
|
+
# Emit log message saying we're running a feature
|
9
|
+
logger.debug("Prepping to run feature '#{feature_name}'")
|
10
|
+
|
11
|
+
# Validate that the feature is recognized
|
12
|
+
feature = config[feature_name]
|
13
|
+
unless feature
|
14
|
+
# Avoid warning for custom plugins
|
15
|
+
user_plugins = Inspec::Plugin::V2::Registry.instance.plugin_statuses.select { |status| status.installation_type == :user_gem }
|
16
|
+
user_plugin_names = user_plugins.collect { |a| a.name.to_s }
|
17
|
+
logger.warn "Unrecognized feature name '#{feature_name}'" unless user_plugin_names.include?(feature_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
# If the feature is not recognized
|
21
|
+
# If the feature has no env_preview flag set in config
|
22
|
+
# If the feature is previewable
|
23
|
+
if feature.nil? || feature&.no_preview? || feature&.previewable?
|
24
|
+
yield feature_implementation
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative "feature/config"
|
2
|
+
require_relative "feature/runner"
|
3
|
+
|
4
|
+
module Inspec
|
5
|
+
def self.with_feature(feature_name, opts = {}, &feature_implementation)
|
6
|
+
Inspec::Feature::Runner.with_feature(feature_name, opts, &feature_implementation)
|
7
|
+
end
|
8
|
+
|
9
|
+
class Feature
|
10
|
+
attr_reader :name, :description, :env_preview
|
11
|
+
|
12
|
+
@@features_invoked = []
|
13
|
+
|
14
|
+
def initialize(feature_name, feature_yaml_opts)
|
15
|
+
@name = feature_name
|
16
|
+
feature_yaml_opts ||= {}
|
17
|
+
@description = feature_yaml_opts["description"]
|
18
|
+
@env_preview = feature_yaml_opts["env_preview"]
|
19
|
+
@@features_invoked << feature_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def previewable?
|
23
|
+
# If the feature is previewable in config (features.yaml) & has an environment value set to use previewed feature
|
24
|
+
!!env_preview && !env_preview_value.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def no_preview?
|
28
|
+
env_preview.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def env_preview_value
|
32
|
+
# Examples: If feature name is "inspec-test-feature"
|
33
|
+
# ENV used for this feature preview would be CHEF_PREVIEW_TEST_FEATURE
|
34
|
+
env_preview_feature_name = name.to_s.split("inspec-")[-1]
|
35
|
+
ENV["CHEF_PREVIEW_#{env_preview_feature_name.gsub("-", "_").upcase}"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.list_all_invoked_features
|
39
|
+
@@features_invoked.uniq
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/inspec/fetcher/git.rb
CHANGED
@@ -127,6 +127,11 @@ module Inspec::Fetcher
|
|
127
127
|
%i{branch tag ref}.map { |opt_name| update_ivar_from_opt(opt_name, opts) }.any?
|
128
128
|
end
|
129
129
|
|
130
|
+
# Git fetcher is sensitive to cache contention so it needs cache locking mechanism.
|
131
|
+
def requires_locking?
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
130
135
|
private
|
131
136
|
|
132
137
|
def resolved_ref
|
data/lib/inspec/fetcher/url.rb
CHANGED
@@ -248,10 +248,30 @@ module Inspec::Fetcher
|
|
248
248
|
@temp_archive_path = archive.path
|
249
249
|
end
|
250
250
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
251
|
+
# Opens a URI or local file specified by `target` with options `opts`.
|
252
|
+
# If `target` is a valid URI (http://, https://, ftp://), opens it using URI.open.
|
253
|
+
# If `target` is a local file path, opens it using File.open.
|
254
|
+
# Raises ArgumentError for invalid `target` that is neither a valid URI nor a local file path.
|
255
|
+
# Logs or handles exceptions gracefully using `pretty_handle_exception`.
|
256
|
+
def open(target, opts)
|
257
|
+
if valid_uri?(target)
|
258
|
+
URI(target).open(opts) # Open URI if it's a valid HTTP, HTTPS, or FTP URI
|
259
|
+
elsif File.file?(target)
|
260
|
+
File.open(target, opts) # Open local file if it exists
|
261
|
+
else
|
262
|
+
raise ArgumentError, "Invalid target: #{target}. Must be a valid URI or a local file path."
|
263
|
+
end
|
264
|
+
rescue StandardError => e
|
265
|
+
raise Inspec::FetcherFailure, "Profile URL dependency #{target} could not be fetched: #{e.message}"
|
266
|
+
end
|
267
|
+
|
268
|
+
# Checks if the given `target` string is a valid URI by attempting to parse it.
|
269
|
+
# Returns true if `target` is a valid HTTP, HTTPS, or FTP URI; false otherwise.
|
270
|
+
def valid_uri?(target)
|
271
|
+
uri = URI.parse(target)
|
272
|
+
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) || uri.is_a?(URI::FTP)
|
273
|
+
rescue URI::InvalidURIError
|
274
|
+
false
|
255
275
|
end
|
256
276
|
|
257
277
|
def open_via_uri(target)
|
data/lib/inspec/globals.rb
CHANGED
@@ -23,4 +23,10 @@ module Inspec
|
|
23
23
|
require "rbconfig" unless defined?(RbConfig)
|
24
24
|
RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
|
25
25
|
end
|
26
|
+
|
27
|
+
def self.log_dir
|
28
|
+
log_dir_path = "#{config_dir}/logs"
|
29
|
+
FileUtils.mkdir_p(log_dir_path) unless File.directory?(log_dir_path)
|
30
|
+
log_dir_path
|
31
|
+
end
|
26
32
|
end
|
data/lib/inspec/iaf_file.rb
CHANGED
@@ -34,8 +34,9 @@ module Inspec
|
|
34
34
|
raise Inspec::Exceptions::ProfileValidationKeyNotFound.new("Validation key #{keyname} not found")
|
35
35
|
end
|
36
36
|
|
37
|
-
def self.find_signing_key(keyname)
|
38
|
-
|
37
|
+
def self.find_signing_key(keyname, config_dir = nil)
|
38
|
+
config_dir ||= Inspec.config_dir
|
39
|
+
[".", File.join(config_dir, "keys")].each do |path|
|
39
40
|
filename = File.join(path, "#{keyname}.pem.key")
|
40
41
|
return filename if File.exist?(filename)
|
41
42
|
end
|
@@ -189,7 +189,11 @@ module Inspec
|
|
189
189
|
def parse_cli_input_value(input_name, given_value)
|
190
190
|
value = given_value.chomp(",") # Trim trailing comma if any
|
191
191
|
case value
|
192
|
-
|
192
|
+
# Changed regex to use \A and \z instead of ^ and $ for stricter start and end of string matching.
|
193
|
+
# This prevents potential bypass issues with multi-line input and ensures the entire string
|
194
|
+
# is exactly "true" or "false", enhancing security when dealing with untrusted input.
|
195
|
+
# Issue detected here: https://github.com/inspec/inspec/security/code-scanning/41
|
196
|
+
when /\A(true|false)\z/i
|
193
197
|
value = !!(value =~ /true/i)
|
194
198
|
when /^-?\d+$/
|
195
199
|
value = value.to_i
|
@@ -105,6 +105,13 @@ module Inspec
|
|
105
105
|
file_provider = Inspec::FileProvider.for_path(archive_path)
|
106
106
|
file_provider.relative_provider
|
107
107
|
end
|
108
|
+
|
109
|
+
# Returns false by default
|
110
|
+
# This is used to regulate cache contention.
|
111
|
+
# Fetchers that are sensitive to cache contention should return true.
|
112
|
+
def requires_locking?
|
113
|
+
false
|
114
|
+
end
|
108
115
|
end
|
109
116
|
end
|
110
117
|
end
|
@@ -12,6 +12,7 @@ module Inspec::Plugin::V2::PluginType
|
|
12
12
|
@control_checks_count_map = {}
|
13
13
|
@controls_count = nil
|
14
14
|
@notifications = {}
|
15
|
+
@enhanced_outcome_control_wise = {}
|
15
16
|
end
|
16
17
|
|
17
18
|
private
|
@@ -29,16 +30,43 @@ module Inspec::Plugin::V2::PluginType
|
|
29
30
|
|
30
31
|
# method to identify when the control ended running
|
31
32
|
# this will be useful in executing operations on control's level end
|
32
|
-
def control_ended?(control_id)
|
33
|
+
def control_ended?(notification, control_id)
|
33
34
|
set_control_checks_count_map_value
|
34
35
|
unless @control_checks_count_map[control_id].nil?
|
35
36
|
@control_checks_count_map[control_id] -= 1
|
36
|
-
@control_checks_count_map[control_id] == 0
|
37
|
+
control_ended = @control_checks_count_map[control_id] == 0
|
38
|
+
# after a control has ended it checks for certain operations, like enhanced outcomes
|
39
|
+
run_control_operations(notification, control_id) if control_ended
|
40
|
+
control_ended
|
37
41
|
else
|
38
42
|
false
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
46
|
+
def run_control_operations(notification, control_id)
|
47
|
+
check_for_enhanced_outcomes(notification, control_id)
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_for_enhanced_outcomes(notification, control_id)
|
51
|
+
if enhanced_outcomes
|
52
|
+
control_outcome = add_enhanced_outcomes(control_id)
|
53
|
+
@enhanced_outcome_control_wise[control_id] = control_outcome
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def format_message(indicator, control_id, title, full_description)
|
58
|
+
message_to_format = ""
|
59
|
+
message_to_format += "#{indicator} "
|
60
|
+
message_to_format += "#{control_id.to_s.strip.dup.force_encoding(Encoding::UTF_8)} "
|
61
|
+
message_to_format += "#{title.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " if title
|
62
|
+
message_to_format += "#{full_description.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " unless title
|
63
|
+
message_to_format
|
64
|
+
end
|
65
|
+
|
66
|
+
def control_outcome(control_id)
|
67
|
+
@enhanced_outcome_control_wise[control_id]
|
68
|
+
end
|
69
|
+
|
42
70
|
# method to identify total no. of controls
|
43
71
|
def controls_count
|
44
72
|
@controls_count ||= RSpec.configuration.formatters.grep(Inspec::Formatters::Base).first.get_controls_count
|
data/lib/inspec/profile.rb
CHANGED
@@ -16,6 +16,7 @@ require "inspec/utils/json_profile_summary"
|
|
16
16
|
require "inspec/dependency_loader"
|
17
17
|
require "inspec/dependency_installer"
|
18
18
|
require "inspec/utils/profile_ast_helpers"
|
19
|
+
require "plugins/inspec-sign/lib/inspec-sign/base"
|
19
20
|
|
20
21
|
module Inspec
|
21
22
|
class Profile
|
@@ -82,7 +83,7 @@ module Inspec
|
|
82
83
|
end
|
83
84
|
|
84
85
|
attr_reader :source_reader, :backend, :runner_context, :check_mode
|
85
|
-
attr_accessor :parent_profile, :profile_id, :profile_name
|
86
|
+
attr_accessor :parent_profile, :profile_id, :profile_name, :target
|
86
87
|
def_delegator :@source_reader, :tests
|
87
88
|
def_delegator :@source_reader, :libraries
|
88
89
|
def_delegator :@source_reader, :metadata
|
@@ -181,6 +182,26 @@ module Inspec
|
|
181
182
|
@state == :failed
|
182
183
|
end
|
183
184
|
|
185
|
+
def verify_if_signed
|
186
|
+
if signed?
|
187
|
+
verify_signed_profile
|
188
|
+
true
|
189
|
+
else
|
190
|
+
false
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def signed?
|
195
|
+
# Signed profiles have .iaf extension
|
196
|
+
(@source_reader&.target&.parent&.class == Inspec::IafProvider)
|
197
|
+
end
|
198
|
+
|
199
|
+
def verify_signed_profile
|
200
|
+
# Kitchen inspec send target profile in Hash format in some scenarios. For example: While using local profile with kitchen, {:path => "path/to/kitchen/lcoal-profile"}
|
201
|
+
target_profile = target.is_a?(Hash) ? target.values[0] : target
|
202
|
+
InspecPlugins::Sign::Base.profile_verify(target_profile, true) if target_profile
|
203
|
+
end
|
204
|
+
|
184
205
|
#
|
185
206
|
# Is this profile is supported on the current platform of the
|
186
207
|
# backend machine and the current inspec version.
|
@@ -215,8 +236,30 @@ module Inspec
|
|
215
236
|
@params ||= load_params
|
216
237
|
end
|
217
238
|
|
239
|
+
def virtual_profile?
|
240
|
+
# A virtual profile is for virtual profile evaluation
|
241
|
+
# Used by shell & inspec detect command.
|
242
|
+
(name == "inspec-shell") && (files&.length == 1) && (files[0] == "inspec.yml")
|
243
|
+
end
|
244
|
+
|
218
245
|
def collect_tests
|
219
246
|
unless @tests_collected || failed?
|
247
|
+
|
248
|
+
# This is that one common place in InSpec engine which is used to collect tests of InSpec profile
|
249
|
+
# One common place used by most of the CLI commands using profile, like exec, export etc
|
250
|
+
# Checking for profile signature in parent profile only
|
251
|
+
# Child profiles of a signed profile are extracted to cache dir
|
252
|
+
# Hence they are not in .iaf format
|
253
|
+
# Only runs this block when preview flag CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING is set
|
254
|
+
Inspec.with_feature("inspec-mandatory-profile-signing") {
|
255
|
+
if !parent_profile && !virtual_profile?
|
256
|
+
cfg = Inspec::Config.cached
|
257
|
+
if cfg.is_a?(Inspec::Config) && !cfg.allow_unsigned_profiles?
|
258
|
+
raise Inspec::ProfileSignatureRequired, "Signature required for profile: #{name}. Please provide a signed profile. Or set CHEF_ALLOW_UNSIGNED_PROFILES in the environment. Or use `--allow-unsigned-profiles` flag with InSpec CLI." unless verify_if_signed
|
259
|
+
end
|
260
|
+
end
|
261
|
+
}
|
262
|
+
|
220
263
|
return unless supports_platform?
|
221
264
|
|
222
265
|
locked_dependencies.each(&:collect_tests)
|
data/lib/inspec/reporters.rb
CHANGED
@@ -4,76 +4,89 @@ require "inspec/reporters/json"
|
|
4
4
|
require "inspec/reporters/json_automate"
|
5
5
|
require "inspec/reporters/automate"
|
6
6
|
require "inspec/reporters/yaml"
|
7
|
+
require "inspec/feature"
|
7
8
|
|
8
9
|
module Inspec::Reporters
|
9
10
|
# rubocop:disable Metrics/CyclomaticComplexity
|
10
11
|
def self.render(reporter, run_data, enhanced_outcomes = false)
|
11
12
|
name, config = reporter.dup
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
13
|
+
Inspec.with_feature("inspec-reporter-#{name}") {
|
14
|
+
config[:run_data] = run_data
|
15
|
+
case name
|
16
|
+
when "cli"
|
17
|
+
reporter = Inspec::Reporters::CLI.new(config)
|
18
|
+
when "json"
|
19
|
+
reporter = Inspec::Reporters::Json.new(config)
|
20
|
+
# This reporter is only used for Chef internal. We reserve the
|
21
|
+
# right to introduce breaking changes to this reporter at any time.
|
22
|
+
when "json-automate"
|
23
|
+
reporter = Inspec::Reporters::JsonAutomate.new(config)
|
24
|
+
when "automate"
|
25
|
+
reporter = Inspec::Reporters::Automate.new(config)
|
26
|
+
when "yaml"
|
27
|
+
reporter = Inspec::Reporters::Yaml.new(config)
|
28
|
+
else
|
29
|
+
# If we made it here, it must be a plugin, and we know it exists (because we validated it in config.rb)
|
30
|
+
activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
|
31
|
+
activator.activate!
|
32
|
+
reporter = activator.implementation_class.new(config)
|
33
|
+
end
|
33
34
|
|
34
|
-
|
35
|
-
|
35
|
+
if enhanced_outcomes
|
36
|
+
Inspec.with_feature("inspec-enhanced-outcomes") {
|
37
|
+
reporter.enhanced_outcomes = enhanced_outcomes
|
38
|
+
}
|
39
|
+
else
|
40
|
+
reporter.enhanced_outcomes = enhanced_outcomes
|
41
|
+
end
|
36
42
|
|
37
|
-
|
38
|
-
|
43
|
+
# optional send_report method on reporter
|
44
|
+
return reporter.send_report if defined?(reporter.send_report)
|
39
45
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
46
|
+
reporter.render
|
47
|
+
output = reporter.rendered_output
|
48
|
+
config_file = config["file"]
|
49
|
+
if config_file
|
50
|
+
config_file.gsub!("CHILD_PID", Process.pid.to_s)
|
51
|
+
# create destination directory if it does not exist
|
52
|
+
dirname = File.dirname(config_file)
|
53
|
+
FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
|
44
54
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
55
|
+
File.write(config_file, output)
|
56
|
+
elsif config["stdout"] == true
|
57
|
+
print output
|
58
|
+
$stdout.flush
|
59
|
+
end
|
60
|
+
}
|
50
61
|
end
|
51
62
|
|
52
63
|
def self.report(reporter, run_data)
|
53
64
|
name, config = reporter.dup
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
Inspec.with_feature("inspec-reporter-#{name}") {
|
66
|
+
config[:run_data] = run_data
|
67
|
+
case name
|
68
|
+
when "json"
|
69
|
+
reporter = Inspec::Reporters::Json.new(config)
|
70
|
+
when "json-automate"
|
71
|
+
reporter = Inspec::Reporters::JsonAutomate.new(config)
|
72
|
+
when "yaml"
|
73
|
+
reporter = Inspec::Reporters::Yaml.new(config)
|
74
|
+
else
|
75
|
+
# If we made it here, it might be a plugin
|
76
|
+
begin
|
77
|
+
activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
|
78
|
+
activator.activate!
|
79
|
+
reporter = activator.implementation_class.new(config)
|
80
|
+
unless reporter.respond_to(:report?)
|
81
|
+
return run_data
|
82
|
+
end
|
83
|
+
rescue Inspec::Plugin::V2::LoadError
|
84
|
+
# Must not have been a plugin - just return the run_data
|
69
85
|
return run_data
|
70
86
|
end
|
71
|
-
rescue Inspec::Plugin::V2::LoadError
|
72
|
-
# Must not have been a plugin - just return the run_data
|
73
|
-
return run_data
|
74
87
|
end
|
75
|
-
end
|
76
88
|
|
77
|
-
|
89
|
+
reporter.report
|
90
|
+
}
|
78
91
|
end
|
79
92
|
end
|