inspec-core 5.22.29 → 6.6.0

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/Chef-EULA +9 -0
  3. data/Gemfile +10 -1
  4. data/etc/features.sig +6 -0
  5. data/etc/features.yaml +94 -0
  6. data/inspec-core.gemspec +14 -5
  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 +300 -230
  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 +26 -0
  18. data/lib/inspec/feature.rb +34 -0
  19. data/lib/inspec/fetcher/git.rb +5 -0
  20. data/lib/inspec/globals.rb +6 -0
  21. data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +7 -0
  22. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +30 -2
  23. data/lib/inspec/profile.rb +373 -12
  24. data/lib/inspec/reporters/cli.rb +1 -1
  25. data/lib/inspec/reporters.rb +67 -54
  26. data/lib/inspec/resources/security_policy.rb +7 -2
  27. data/lib/inspec/run_data.rb +7 -5
  28. data/lib/inspec/runner.rb +34 -5
  29. data/lib/inspec/runner_rspec.rb +12 -9
  30. data/lib/inspec/secrets/yaml.rb +9 -3
  31. data/lib/inspec/shell.rb +10 -0
  32. data/lib/inspec/ui.rb +4 -0
  33. data/lib/inspec/utils/licensing_config.rb +9 -0
  34. data/lib/inspec/utils/profile_ast_helpers.rb +372 -0
  35. data/lib/inspec/version.rb +1 -1
  36. data/lib/inspec/waiver_file_reader.rb +68 -27
  37. data/lib/inspec.rb +2 -1
  38. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -168
  39. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
  40. data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
  41. data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
  42. data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
  43. data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
  44. data/lib/plugins/inspec-license/README.md +16 -0
  45. data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
  46. data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
  47. data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
  48. data/lib/plugins/inspec-parallel/README.md +27 -0
  49. data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
  50. data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
  51. data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
  52. data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
  53. data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +265 -0
  54. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
  55. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
  56. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +124 -0
  57. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
  58. data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
  59. data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
  60. data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +7 -6
  61. data/lib/plugins/inspec-reporter-html2/templates/default.js +6 -6
  62. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +6 -2
  63. data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
  64. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
  65. metadata +54 -13
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
- transport_options.include? option_name # e.g., 'host'
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
@@ -15,5 +15,6 @@ module Inspec
15
15
  "passed"
16
16
  end
17
17
  end
18
+
18
19
  end
19
20
  end
data/lib/inspec/errors.rb CHANGED
@@ -24,4 +24,9 @@ module Inspec
24
24
  end
25
25
 
26
26
  class InvalidProfileSignature < Error; end
27
+
28
+ class FeatureConfigMissingError < Error; end
29
+ class FeatureConfigTamperedError < Error; end
30
+
31
+ class ProfileSignatureRequired < Error; end
27
32
  end
@@ -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,26 @@
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
+ logger.warn "Unrecognized feature name '#{feature_name}'"
15
+ end
16
+
17
+ # If the feature is not recognized
18
+ # If the feature has no env_preview flag set in config
19
+ # If the feature is previewable
20
+ if feature.nil? || feature&.no_preview? || feature&.previewable?
21
+ yield feature_implementation
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,34 @@
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
+ def initialize(feature_name, feature_yaml_opts)
12
+ @name = feature_name
13
+ feature_yaml_opts ||= {}
14
+ @description = feature_yaml_opts["description"]
15
+ @env_preview = feature_yaml_opts["env_preview"]
16
+ end
17
+
18
+ def previewable?
19
+ # If the feature is previewable in config (features.yaml) & has an environment value set to use previewed feature
20
+ !!env_preview && !env_preview_value.nil?
21
+ end
22
+
23
+ def no_preview?
24
+ env_preview.nil?
25
+ end
26
+
27
+ def env_preview_value
28
+ # Examples: If feature name is "inspec-test-feature"
29
+ # ENV used for this feature preview would be CHEF_PREVIEW_TEST_FEATURE
30
+ env_preview_feature_name = name.to_s.split("inspec-")[-1]
31
+ ENV["CHEF_PREVIEW_#{env_preview_feature_name.gsub("-", "_").upcase}"]
32
+ end
33
+ end
34
+ end
@@ -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
@@ -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
@@ -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