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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +22 -22
  3. data/etc/features.sig +6 -6
  4. data/etc/features.yaml +3 -0
  5. data/inspec-core.gemspec +10 -3
  6. data/lib/inspec/base_cli.rb +1 -1
  7. data/lib/inspec/cli.rb +1 -1
  8. data/lib/inspec/config.rb +9 -0
  9. data/lib/inspec/dependencies/dependency_set.rb +2 -2
  10. data/lib/inspec/dsl.rb +1 -1
  11. data/lib/inspec/feature/runner.rb +4 -1
  12. data/lib/inspec/feature.rb +8 -0
  13. data/lib/inspec/fetcher/url.rb +29 -7
  14. data/lib/inspec/iaf_file.rb +3 -2
  15. data/lib/inspec/input_registry.rb +5 -1
  16. data/lib/inspec/profile.rb +2 -2
  17. data/lib/inspec/reporters/cli.rb +1 -1
  18. data/lib/inspec/resources/nftables.rb +14 -1
  19. data/lib/inspec/resources/oracledb_session.rb +12 -3
  20. data/lib/inspec/resources/ssh_config.rb +100 -9
  21. data/lib/inspec/resources/ssh_key.rb +124 -0
  22. data/lib/inspec/resources/sshd_active_config.rb +2 -0
  23. data/lib/inspec/resources/sybase_session.rb +11 -2
  24. data/lib/inspec/resources/virtualization.rb +1 -1
  25. data/lib/inspec/resources.rb +1 -0
  26. data/lib/inspec/rule.rb +15 -10
  27. data/lib/inspec/runner.rb +10 -2
  28. data/lib/inspec/utils/profile_ast_helpers.rb +1 -2
  29. data/lib/inspec/utils/telemetry/base.rb +149 -0
  30. data/lib/inspec/utils/telemetry/http.rb +40 -0
  31. data/lib/inspec/utils/telemetry/null.rb +11 -0
  32. data/lib/inspec/utils/telemetry/run_context_probe.rb +13 -1
  33. data/lib/inspec/utils/telemetry.rb +74 -3
  34. data/lib/inspec/utils/waivers/csv_file_reader.rb +1 -1
  35. data/lib/inspec/utils/waivers/excel_file_reader.rb +1 -1
  36. data/lib/inspec/version.rb +1 -1
  37. data/lib/inspec.rb +0 -1
  38. data/lib/matchers/matchers.rb +3 -3
  39. data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +5 -0
  40. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +1 -0
  41. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +14 -6
  42. metadata +27 -11
  43. data/lib/inspec/utils/telemetry/collector.rb +0 -81
  44. data/lib/inspec/utils/telemetry/data_series.rb +0 -44
  45. data/lib/inspec/utils/telemetry/global_methods.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e2d81ddee4b440d4bcb0d00b586707400b189e833942dd65e677bd7f75df0a4
4
- data.tar.gz: 69905f313a3303d2e385fe603c6d808970aca2efc7d37498d74ca12727583608
3
+ metadata.gz: 935114b2bfd94c210bbaa34c7dcb77185f41d3fe40f2460f0cc202fc81bcf229
4
+ data.tar.gz: 1b32490bab8349155734d036c8690460436c13531318427a1a7cbbe4d1556479
5
5
  SHA512:
6
- metadata.gz: 754812b73f047d377cdcdfd1c409a15a916ae69d529bded4ace7993b638f1ab6783c7a4394107d538a2df5523887d782a251cfbbf7923a2084961372de1ab12d
7
- data.tar.gz: 2ccb38f332159f8af419ef0c343710271d32328198354327489d492ca66c7bad921a5271310f984e844f12eddfa0a70664024be9dac2bdabbf96a0974fc97c74
6
+ metadata.gz: ebc7bfa5348adaf7ef7f6fa066ced78691167acb491fa1d30dc70fba35125f7b44d9598be47b4868c280796e902e86ca8efcac1d4a71f294b5725b6f3c80a813
7
+ data.tar.gz: 426261a3570f1db8a1a99f0a07cd6f92eea754943c4d6e904b8a73f7e7dea49bc1758989d94047ca1689efa1c78d2149ca532c345e380c595e103e799f29c1d4
data/Gemfile CHANGED
@@ -1,11 +1,13 @@
1
+ # TODO: Commentine artifactory source block temporarily
2
+ # to addres JIRA #9390 (Chef InSpec Verify pipeline is failing due to checksum mismatch of mixlib-shellout gem)
1
3
  # For Chef internal builds, allows preview versions of gems if available.
2
- if ENV["ARTIFACTORY_BASE_URL"]
3
- source ENV["ARTIFACTORY_BASE_URL"] + "/artifactory/api/gems/omnibus-gems-local/" do
4
- # TODO: either fully populate this list, or revert back to non-block format
5
- # to sweep all Chef gems from Artifactory.
6
- gem "chef-licensing"
7
- end
8
- end
4
+ # if ENV["ARTIFACTORY_BASE_URL"]
5
+ # source ENV["ARTIFACTORY_BASE_URL"] + "/artifactory/api/gems/omnibus-gems-local/" do
6
+ # # TODO: either fully populate this list, or revert back to non-block format
7
+ # # to sweep all Chef gems from Artifactory.
8
+ # gem "chef-licensing"
9
+ # end
10
+ # end
9
11
 
10
12
  source "https://rubygems.org"
11
13
 
@@ -18,7 +20,10 @@ gem "inspec", path: "."
18
20
  # in it in order to package the executable. Hence the odd backwards dependency.
19
21
  gem "inspec-bin", path: "./inspec-bin"
20
22
 
21
- gem "ffi", ">= 1.9.14", "!= 1.13.0", "!= 1.14.2"
23
+ # ffi version v1.17.0 is breaking verify pipeline as it requires
24
+ # rubygems version to be upgraded to >= 3.3.22 Ref:https://buildkite.com/chef/inspec-inspec-main-verify-private/builds/812#018fe177-2ccb-45ed-a25e-213c8a6453df/698-707
25
+
26
+ gem "ffi", ">= 1.15.5", "< 1.17.0"
22
27
 
23
28
  # inspec tests depend text output that changed in the 3.10 release
24
29
  # but our runtime dep is still 3.9+
@@ -32,25 +37,20 @@ group :omnibus do
32
37
  end
33
38
 
34
39
  group :test do
35
- gem "chefstyle", "~> 2.2.2"
36
- gem "concurrent-ruby", "~> 1.0"
37
- gem "json_schemer", ">= 0.2.1", "< 2.0.1"
40
+ gem "chefstyle"
41
+ gem "concurrent-ruby"
42
+ gem "json_schemer"
38
43
  gem "m"
39
44
  gem "minitest-sprint", "~> 1.0"
40
45
  gem "minitest", "5.15.0"
41
- gem "mocha", "~> 1.1"
42
- gem "nokogiri", "~> 1.9"
46
+ gem "mocha"
47
+ gem "nokogiri"
43
48
  gem "pry-byebug"
44
- gem "pry", "~> 0.10"
45
- gem "rake", ">= 10"
46
- gem "simplecov", "~> 0.21"
49
+ gem "pry"
50
+ gem "rake"
51
+ gem "simplecov"
47
52
  gem "simplecov_json_formatter"
48
- gem "webmock", "~> 3.0"
49
-
50
- if Gem.ruby_version >= Gem::Version.new("3.0.0")
51
- # html-proofer has a dep on io-event, which is ruby-3 only
52
- gem "html-proofer", "~> 3.19.4", platforms: :ruby # do not attempt to run proofer on windows. Pinned to 3.19.4 as test is breaking in updated versions.
53
- end
53
+ gem "webmock"
54
54
  end
55
55
 
56
56
  group :deploy do
data/etc/features.sig CHANGED
@@ -1,6 +1,6 @@
1
- LoJePRrMIqFz6d1uu5n3QBqQAPD8wLuLM8PfvdDerFjuX/TFJDFdwdcNZ8b8
2
- KBxFjR5qUTMZizjIUp5Jd6FFI4gSm0RIMKa4UeJCQQAWKJGo/tIbSKLPLWlV
3
- m1X1Z869AkvQSJxyaXvS2oKPck/znCbRKEDhuk2kqSyDJlC2BILTVa0sx3nd
4
- 4W2J2CwFBlqmYWI1FARkZCMGlfzkjcUqrVrCb3RcZ7bcEYOT5ebIm9zZlbuV
5
- n2Di29KFZhl8paEoGq3EYJvxEC7rVtLccei8UteNQcSOWihG61dtPGhHnpS+
6
- /7RNGjrS8s4i/dQHjZlZgV6guki6EqB+DIirVek9PQ==
1
+ nr7EKXZMiAwYI0Kon1ctCMkDulEkovRbT/FRezvP04yx8wVhJaSi7dMhL/mP
2
+ NvTzMOuT9G4R/QsP6VV7QKs4eBmAOPGrvgZgyfXDvfe1TPYcvpsVncSXm5rx
3
+ TO+g7i0XGz9s/FtvdzOpl2urhgOsQ35wk7IsNu9Ktij2HqZw7UmxMvtT954s
4
+ aQuW6eVvvM9n+bobEBVSErkhgvOvJ7jZyz5r0cv/uuhrayIC6V1qegod9QHa
5
+ uCdasmmEqglyNQYXIM7V7iNrnfuYB80or44Ewi640edHarSw8YU/Tul2Y2l/
6
+ DWeXRHsXxmuEL1wXA9ZIV6wqK0RsxaufwY6M7bqWSQ==
data/etc/features.yaml CHANGED
@@ -92,3 +92,6 @@
92
92
  inspec-audit-logging:
93
93
  description: Use audit logging.
94
94
  env_preview: true
95
+ inspec-telemetry-client:
96
+ description: Perform license usage telemetry.
97
+ env_preview: true
data/inspec-core.gemspec CHANGED
@@ -21,7 +21,7 @@ Source code obtained from the Chef GitHub repository is made available under Apa
21
21
  spec.license = "LicenseRef-Chef-EULA"
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.required_ruby_version = ">= 2.7"
24
+ spec.required_ruby_version = ">= 3.1.0"
25
25
 
26
26
  # the gemfile and gemspec are necessary for appbundler so don't remove it
27
27
  spec.files =
@@ -38,7 +38,7 @@ Source code obtained from the Chef GitHub repository is made available under Apa
38
38
  spec.add_dependency "thor", ">= 0.20", "< 1.3.0"
39
39
  spec.add_dependency "method_source", ">= 0.8", "< 2.0"
40
40
  spec.add_dependency "rubyzip", ">= 1.2.2", "< 3.0"
41
- spec.add_dependency "rspec", ">= 3.9", "<= 3.12"
41
+ spec.add_dependency "rspec", ">= 3.9", "<= 3.14"
42
42
  spec.add_dependency "rspec-its", "~> 1.2"
43
43
  spec.add_dependency "pry", "~> 0.13"
44
44
  spec.add_dependency "hashie", ">= 3.4", "< 6.0"
@@ -55,6 +55,13 @@ Source code obtained from the Chef GitHub repository is made available under Apa
55
55
  spec.add_dependency "semverse", "~> 3.0"
56
56
  spec.add_dependency "multipart-post", "~> 2.0"
57
57
 
58
+ # cookstyle support for inspec check
59
+ # This was initially included in 'inspec.gemspec' to keep 'chef-client' lightweight.
60
+ # However, it has been moved to 'inspec-core.gemspec' due to a dependency on the 'ast' gem,
61
+ # which was causing a LoadError ('cannot load such file -- ast') for users/applications using 'inspec-core'.
62
+ spec.add_dependency "cookstyle"
63
+
58
64
  spec.add_dependency "train-core", ">= 3.11.0"
59
- spec.add_dependency "chef-licensing", ">= 0.7.5"
65
+ # Minimum major version 1 is required for Chef licensing telemetry
66
+ spec.add_dependency "chef-licensing", ">= 1.0.2"
60
67
  end
@@ -47,7 +47,7 @@ module Inspec
47
47
  license_keys = ChefLicensing.fetch_and_persist
48
48
 
49
49
  # Only if EULA acceptance or license key args are present. And licenses are successfully persisted, do clean exit.
50
- if ARGV.select { |arg| !(arg.include? "--chef-license") }.empty? && !license_keys.blank?
50
+ if ARGV.select { |arg| !(arg.include? "--chef-license") }.empty? && !(license_keys.nil? || license_keys.empty?)
51
51
  Inspec::UI.new.exit
52
52
  end
53
53
  end
data/lib/inspec/cli.rb CHANGED
@@ -57,7 +57,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
57
57
  desc: "Disable loading all plugins that the user installed."
58
58
 
59
59
  class_option :enable_telemetry, type: :boolean,
60
- desc: "Allow or disable telemetry", default: false
60
+ desc: "Allow or disable telemetry", default: true
61
61
 
62
62
  require "license_acceptance/cli_flags/thor"
63
63
  include LicenseAcceptance::CLIFlags::Thor
data/lib/inspec/config.rb CHANGED
@@ -470,6 +470,15 @@ module Inspec
470
470
  # Reporter options may be defined top-level.
471
471
  options.merge!(config_file_reporter_options)
472
472
 
473
+ # when sent reporter from compliance-mode (via chef-client), the reporter is a symbol
474
+ if @cli_opts.key?(:reporter) && @cli_opts["reporter"].nil?
475
+ @cli_opts["reporter"] = @cli_opts[:reporter]
476
+ @cli_opts.delete(:reporter)
477
+ elsif @cli_opts.key?(:reporter) && @cli_opts.key?("reporter") && @cli_opts["reporter"].is_a?(Array)
478
+ # combine reporter and "reporter" options into "reporter" option
479
+ @cli_opts["reporter"] = @cli_opts[:reporter] + @cli_opts["reporter"]
480
+ end
481
+
473
482
  if @cli_opts["reporter"]
474
483
  # Add reporter_cli_opts in options to capture reporter cli opts separately
475
484
  options.merge!({ "reporter_cli_opts" => @cli_opts["reporter"] })
@@ -26,7 +26,7 @@ module Inspec
26
26
  dep_list = {}
27
27
  dependencies.each do |d|
28
28
  # if depedent profile does not have a source version then only name is used in dependency hash
29
- key_name = (d.source_version.blank? ? "#{d.name}" : "#{d.name}-#{d.source_version}") rescue "#{d.name}"
29
+ key_name = ((d.source_version.nil? || d.source_version.empty?) ? "#{d.name}" : "#{d.name}-#{d.source_version}") rescue "#{d.name}"
30
30
  dep_list[key_name] = d
31
31
  end
32
32
  new(cwd, cache, dep_list, backend)
@@ -42,7 +42,7 @@ module Inspec
42
42
  dep_list = {}
43
43
  dep_tree.each do |d|
44
44
  # if depedent profile does not have a source version then only name is used in dependency hash
45
- key_name = (d.source_version.blank? ? "#{d.name}" : "#{d.name}-#{d.source_version}") rescue "#{d.name}"
45
+ key_name = ((d.source_version.nil? || d.source_version.empty?) ? "#{d.name}" : "#{d.name}-#{d.source_version}") rescue "#{d.name}"
46
46
  dep_list[key_name] = d
47
47
  dep_list.merge!(flatten_dep_tree(d.dependencies))
48
48
  end
data/lib/inspec/dsl.rb CHANGED
@@ -95,7 +95,7 @@ module Inspec::DSL
95
95
  # 1. Fetching VERSION from a profile dependency name which is in a format NAME-VERSION.
96
96
  # 2. Matching original profile dependency name with profile name used with include or require control DSL.
97
97
  source_version = value.source_version
98
- unless source_version.blank?
98
+ unless source_version.nil? || source_version.empty?
99
99
  profile_id_key = key.split("-#{source_version}")[0]
100
100
  new_profile_id = key if profile_id_key == profile_id
101
101
  end
@@ -11,7 +11,10 @@ module Inspec
11
11
  # Validate that the feature is recognized
12
12
  feature = config[feature_name]
13
13
  unless feature
14
- logger.warn "Unrecognized feature name '#{feature_name}'"
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)
15
18
  end
16
19
 
17
20
  # If the feature is not recognized
@@ -8,11 +8,15 @@ module Inspec
8
8
 
9
9
  class Feature
10
10
  attr_reader :name, :description, :env_preview
11
+
12
+ @@features_invoked = []
13
+
11
14
  def initialize(feature_name, feature_yaml_opts)
12
15
  @name = feature_name
13
16
  feature_yaml_opts ||= {}
14
17
  @description = feature_yaml_opts["description"]
15
18
  @env_preview = feature_yaml_opts["env_preview"]
19
+ @@features_invoked << feature_name
16
20
  end
17
21
 
18
22
  def previewable?
@@ -30,5 +34,9 @@ module Inspec
30
34
  env_preview_feature_name = name.to_s.split("inspec-")[-1]
31
35
  ENV["CHEF_PREVIEW_#{env_preview_feature_name.gsub("-", "_").upcase}"]
32
36
  end
37
+
38
+ def self.list_all_invoked_features
39
+ @@features_invoked.uniq
40
+ end
33
41
  end
34
42
  end
@@ -93,7 +93,7 @@ module Inspec::Fetcher
93
93
  end
94
94
 
95
95
  if transformed_target
96
- Inspec::Log.warn("URL target #{target} transformed to #{transformed_target}. Consider using the git fetcher")
96
+ Inspec::Log.debug("URL target #{target} transformed to #{transformed_target}. Consider using the git fetcher")
97
97
  transformed_target
98
98
  else
99
99
  target
@@ -133,12 +133,14 @@ module Inspec::Fetcher
133
133
  class << self
134
134
  def default_ref(match_data, repo_url)
135
135
  remote_url = "#{repo_url}/#{match_data[:user]}/#{match_data[:repo]}.git"
136
- command_string = "git remote show #{remote_url}"
136
+ command_string = "git ls-remote #{remote_url} HEAD"
137
137
  cmd = shellout(command_string)
138
138
  unless cmd.exitstatus == 0
139
139
  raise(Inspec::FetcherFailure, "Profile git dependency failed with default reference - #{remote_url} - error running '#{command_string}': #{cmd.stderr}")
140
140
  else
141
- ref = cmd.stdout.lines.detect { |l| l.include? "HEAD branch:" }&.split(":")&.last&.strip
141
+ # cmd.stdout of "git ls-remote #{remote_url} HEAD" looks like this:
142
+ # "457d14843ab7c1c3740169eb47cf129a6f417964\tHEAD\n"
143
+ ref = cmd.stdout.split("\t").first
142
144
  unless ref
143
145
  raise(Inspec::FetcherFailure, "Profile git dependency failed with default reference - #{remote_url} - error running '#{command_string}': NULL reference")
144
146
  end
@@ -246,10 +248,30 @@ module Inspec::Fetcher
246
248
  @temp_archive_path = archive.path
247
249
  end
248
250
 
249
- def open(target, opts) # overridden so we can control who we're talking to
250
- URI.open(target, opts)
251
- rescue NoMethodError # TODO: remove when we drop ruby 2.4
252
- super(target, opts) # Kernel#open
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
253
275
  end
254
276
 
255
277
  def open_via_uri(target)
@@ -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
- [".", File.join(Inspec.config_dir, "keys")].each do |path|
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
- when /^true|false$/i
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
@@ -291,7 +291,7 @@ module Inspec
291
291
  ## Find the waivers file
292
292
  # - TODO: cli_opts and instance_variable_get could be exposed
293
293
  waiver_paths = cfg.instance_variable_get(:@cli_opts)["waiver_file"]
294
- if waiver_paths.blank?
294
+ if waiver_paths.nil? || waiver_paths.empty?
295
295
  Inspec::Log.error "Must use --waiver-file with --filter-waived-controls"
296
296
  Inspec::UI.new.exit(:usage_error)
297
297
  end
@@ -319,7 +319,7 @@ module Inspec
319
319
  # be processed and rendered
320
320
  tests.each do |control_filename, source_code|
321
321
  cleared_tests = source_code.scan(/control\s+['"].+?['"].+?(?=(?:control\s+['"].+?['"])|\z)/m).collect do |element|
322
- next if element.blank?
322
+ next if element.nil? || element.empty?
323
323
 
324
324
  if element&.match?(waived_control_id_regex)
325
325
  splitlines = element.split("\n")
@@ -317,7 +317,7 @@ module Inspec::Reporters
317
317
  not_applicable = 0
318
318
 
319
319
  all_unique_controls.each do |control|
320
- next if control[:status].blank?
320
+ next if control[:status].nil? || control[:status].empty?
321
321
 
322
322
  if control[:status] == "failed"
323
323
  failed += 1
@@ -135,7 +135,20 @@ module Inspec::Resources
135
135
  cmd = inspec.command(nftables_cmd)
136
136
  return [] if cmd.exit_status.to_i != 0
137
137
 
138
- @nftables_cache[idx] = cmd.stdout.gsub("\t", "").split("\n").reject { |line| line =~ /^(table|set|type|size|flags|typeof|auto-merge)/ || line =~ /^}$/ }.map { |line| line.sub("elements = {", "").sub("}", "").split(",") }.flatten.map(&:strip)
138
+ # https://github.com/inspec/inspec/security/code-scanning/10
139
+ # Update @nftables_cache with sanitized command output
140
+ @nftables_cache[idx] = cmd.stdout.gsub("\t", "").split("\n")
141
+ .reject { |line| line =~ /^(table|set|type|size|flags|typeof|auto-merge)/ || line =~ /^}$/ } # Reject lines that match certain patterns
142
+ .map { |line| line.gsub("elements = {", "").gsub("}", "").split(",") } # Use gsub to replace all occurrences of specified strings
143
+ .flatten # Flatten the array of arrays into a single array
144
+ .map(&:strip) # Remove leading and trailing whitespace from each element
145
+ .map { |element| sanitize_input(element) } # Sanitize each element to prevent injection attacks
146
+ end
147
+
148
+ # Method to sanitize input
149
+ def sanitize_input(input)
150
+ # Replace potentially dangerous characters with their escaped counterparts
151
+ input.gsub(/([\\'";])/, '\\\\\1')
139
152
  end
140
153
 
141
154
  def retrieve_chain_rules
@@ -96,8 +96,7 @@ module Inspec::Resources
96
96
  if @db_role.nil? || @su_user.nil?
97
97
  verified_query = verify_query(query)
98
98
  else
99
- escaped_query = query.gsub(/\\\\/, "\\").gsub(/"/, '\\"')
100
- escaped_query = escaped_query.gsub("$", '\\$') unless escaped_query.include? "\\$"
99
+ escaped_query = escape_query(query)
101
100
  verified_query = verify_query(escaped_query)
102
101
  end
103
102
 
@@ -134,11 +133,21 @@ module Inspec::Resources
134
133
  query
135
134
  end
136
135
 
136
+ def escape_query(query)
137
+ # https://github.com/inspec/inspec/security/code-scanning/7
138
+ # https://github.com/inspec/inspec/security/code-scanning/8
139
+ escaped_query = query.gsub(/["\\]/) { |match| match == '"' ? '\\"' : "\\\\" } # Escape backslashes and double quotes
140
+ escaped_query.gsub!("$", '\\$') unless escaped_query.include? "\\$" # Escape dollar signs, but only if not already escaped
141
+ escaped_query
142
+ end
143
+
137
144
  def parse_csv_result(stdout)
138
145
  output = stdout.split("oracle_query_string")[-1]
139
146
  # comma_query_sub replaces the csv delimiter "," in the output.
140
147
  # Handles CSV parsing of data like this (DROP,3) etc
141
- output = output.sub(/\r/, "").strip.gsub(",", "comma_query_sub")
148
+ # Replace all occurrences of the target pattern using gsub instead of sub
149
+ # Issue detected: https://github.com/inspec/inspec/security/code-scanning/9
150
+ output = output.gsub(/\r/, "").strip.gsub(",", "comma_query_sub")
142
151
  converter = ->(header) { header.downcase }
143
152
  CSV.parse(output, headers: true, header_converters: converter).map do |row|
144
153
  next if row.entries.flatten.empty?
@@ -38,16 +38,13 @@ module Inspec::Resources
38
38
 
39
39
  def convert_hash(hash)
40
40
  new_hash = {}
41
- hash.each do |k, v|
42
- new_hash[k.downcase] ||= v
43
- end
41
+ hash.each { |k, v| new_hash[k.downcase] ||= v }
44
42
  new_hash
45
43
  end
46
44
 
47
45
  def method_missing(name)
48
46
  param = read_params[name.to_s.downcase]
49
47
  return nil if param.nil?
50
- # extract first value if we have only one value in array
51
48
  return param[0] if param.length == 1
52
49
 
53
50
  param
@@ -73,11 +70,12 @@ module Inspec::Resources
73
70
  return @params if defined?(@params)
74
71
  return @params = {} if read_content.nil?
75
72
 
76
- conf = SimpleConfig.new(
77
- read_content,
78
- assignment_regex: /^\s*(\S+?)\s+(.*?)\s*$/,
79
- multiple_values: true
80
- )
73
+ conf =
74
+ SimpleConfig.new(
75
+ read_content,
76
+ assignment_regex: /^\s*(\S+?)\s+(.*?)\s*$/,
77
+ multiple_values: true
78
+ )
81
79
  @params = convert_hash(conf.params)
82
80
  end
83
81
 
@@ -121,4 +119,97 @@ module Inspec::Resources
121
119
  "/etc/ssh/#{type}"
122
120
  end
123
121
  end
122
+
123
+ class SshdActiveConfig < SshdConfig
124
+ name "sshd_active_config"
125
+ supports platform: "unix"
126
+ supports platform: "windows"
127
+ desc "Use the sshd_active_config InSpec audit resource to test configuration data for the Open SSH daemon located at /etc/ssh/sshd_config on Linux and UNIX platforms. sshd---the Open SSH daemon---listens on dedicated ports, starts a daemon for each incoming connection, and then handles encryption, authentication, key exchanges, command execution, and data exchanges."
128
+ example <<~EXAMPLE
129
+ describe sshd_active_config do
130
+ its('Protocol') { should eq '2' }
131
+ end
132
+ EXAMPLE
133
+
134
+ attr_reader :active_path
135
+
136
+ def initialize
137
+ @active_path = dynamic_sshd_config_path
138
+ super(@active_path)
139
+ end
140
+
141
+ def to_s
142
+ "SSHD Active Configuration (active path: #{@conf_path})"
143
+ end
144
+
145
+ private
146
+
147
+ def ssh_config_file(type)
148
+ if inspec.os.windows?
149
+ programdata = inspec.os_env("programdata").content
150
+ return "#{programdata}\\ssh\\#{type}"
151
+ end
152
+
153
+ "/etc/ssh/#{type}"
154
+ end
155
+
156
+ def dynamic_sshd_config_path
157
+ if inspec.os.windows?
158
+ script = <<-EOH
159
+ $sshdPath = (Get-Command sshd.exe).Source
160
+ if ($sshdPath -ne $null) {
161
+ Write-Output $sshdPath
162
+ } else {
163
+ Write-Error "sshd.exe not found"
164
+ }
165
+ EOH
166
+ sshd_path_result = inspec.powershell(script).stdout.strip
167
+ sshd_path = "\"#{sshd_path_result}\""
168
+ if !sshd_path_result.empty? && sshd_path_result != "sshd.exe not found"
169
+ command_output = inspec.command("sudo #{sshd_path} -dd 2>&1").stdout
170
+ dynamic_path =
171
+ command_output
172
+ .lines
173
+ .find { |line| line.include?("filename") }
174
+ &.split("filename")
175
+ &.last
176
+ &.strip
177
+ env_var_name = dynamic_path.match(/__(.*?)__/)[1]
178
+ if env_var_name?
179
+ dynamic_path =
180
+ dynamic_path.gsub(
181
+ /__#{env_var_name}__/,
182
+ inspec.os_env(env_var_name).content
183
+ )
184
+ end
185
+ else
186
+ Inspec::Log.error("sshd.exe not found using PowerShell script block.")
187
+ return nil
188
+ end
189
+ elsif inspec.os.unix?
190
+ sshd_path = "/usr/sbin/sshd"
191
+ command_output = inspec.command("sudo #{sshd_path} -dd 2>&1").stdout
192
+ dynamic_path =
193
+ command_output
194
+ .lines
195
+ .find { |line| line.include?("filename") }
196
+ &.split("filename")
197
+ &.last
198
+ &.strip
199
+ else
200
+ Inspec::Log.error(
201
+ "Unable to determine sshd configuration path on Windows using -T flag."
202
+ )
203
+ return nil
204
+ end
205
+
206
+ if dynamic_path.nil? || dynamic_path.empty?
207
+ Inspec::Log.warn(
208
+ "No active SSHD configuration found. Using default configuration."
209
+ )
210
+ return ssh_config_file("sshd_config")
211
+ end
212
+ dynamic_path
213
+ end
214
+ end
124
215
  end