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.
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