inspec-core 7.0.95 → 7.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e160d7044fa022329cb438d770054bb63107b59f219030464c709f6b1dcda93
4
- data.tar.gz: 1dc6673dc552f75cb9b8671b0a466feb2c818f069112fac69aa63b1de568ef54
3
+ metadata.gz: d615f8a9108fd5631217d136cb8b29e8d1c44f6b049a53c2618907df84876fa8
4
+ data.tar.gz: 7745773bd58acc11ac0d8acc9cf7c7e3d1bcb945556a6686514d441a6b8375e0
5
5
  SHA512:
6
- metadata.gz: a389860f421bec680c4db4462fc4cf5765073661cc1154f5fe57e823f7b4f33b10d1ecd22fb286d3a50cd467170645d460ba80b96515331bd2aec9eaa80d8579
7
- data.tar.gz: 6133800736ed63493d37bca5b3727740b1a1ab7eadb9919ffc8b728f021604a9dd7bf1d1b388169566bc4e484b3fb2fc26845e05769f1dc2afa26f530fddf7a0
6
+ metadata.gz: 4aad4bdd8864cb072120a838b7e342d4d667f24d30cb1a160627f125d6ab9968888f6dc30b0391fed5241afc81c75c4d3bea5868fa6d8f0b26e2847c899d1885
7
+ data.tar.gz: a2f4f3c01f19816fd77c65db3ac2a737d23093d71dac78ee82bbede8b39816f18efc6f94a5f7ae718533f065f9b87e9f577eeb42545f4b3fc5f75e4880ef8f98
data/Gemfile CHANGED
@@ -43,11 +43,14 @@ group :test do
43
43
  gem "m"
44
44
  gem "minitest-sprint", "~> 1.0"
45
45
  gem "minitest"
46
+ # Ruby 3.4+ extracts minitest-mock to a separate gem (bundled gem)
47
+ # Adding unconditionally as it's compatible with all Ruby versions
48
+ gem "minitest-mock"
46
49
  gem "mocha"
47
50
  gem "nokogiri"
48
51
  gem "pry-byebug"
49
52
  gem "pry"
50
- gem "rake"
53
+ gem "rake", ">= 12.3.3"
51
54
  gem "simplecov"
52
55
  gem "simplecov_json_formatter"
53
56
  gem "webmock"
data/inspec-core.gemspec CHANGED
@@ -50,7 +50,7 @@ Source code obtained from the Chef GitHub repository is made available under Apa
50
50
  spec.add_dependency "tty-table", "~> 0.10"
51
51
  spec.add_dependency "tty-prompt", "~> 0.17"
52
52
  spec.add_dependency "tomlrb", ">= 1.3", "< 2.1"
53
- spec.add_dependency "addressable", "~> 2.4"
53
+ spec.add_dependency "addressable", "~> 2.9"
54
54
  spec.add_dependency "parslet", ">= 1.5", "< 3.0" # Pinned < 2.0, see #5389
55
55
  spec.add_dependency "semverse", "~> 3.0"
56
56
  spec.add_dependency "multipart-post", "~> 2.0"
@@ -68,7 +68,7 @@ Source code obtained from the Chef GitHub repository is made available under Apa
68
68
  # which was causing a LoadError ('cannot load such file -- ast') for users/applications using 'inspec-core'.
69
69
  spec.add_dependency "cookstyle"
70
70
 
71
- spec.add_dependency "train-core", "~> 3.13", ">= 3.13.4"
71
+ spec.add_dependency "train-core", "~> 3.16", ">= 3.16.1"
72
72
  # Minimum major version 1 is required for Chef licensing telemetry
73
73
  spec.add_dependency "chef-licensing", ">= 1.2.0"
74
74
  end
@@ -45,7 +45,7 @@ module Inspec
45
45
 
46
46
  def self.fetch_validation_key_from_github(keyname)
47
47
  URI.open("https://raw.githubusercontent.com/inspec/inspec/main/etc/keys/#{keyname}.pem.pub") do |r|
48
- puts "Fetching validation key '#{keyname}' from github"
48
+ Inspec::Log.debug "Fetching validation key '#{keyname}' from github"
49
49
  dir = File.join(Inspec.config_dir, "keys")
50
50
  FileUtils.mkdir_p dir
51
51
  key_file = File.join(dir, "#{keyname}.pem.pub")
@@ -55,6 +55,11 @@ module Inspec::Reporters
55
55
  def extract_resource_id(r)
56
56
  # According to the RunData API, this is supposed to be an anonymous
57
57
  # class that represents a resource, with embedded instance methods....
58
+ # Prefer resource object if present and exposes resource_id
59
+ resource_candidate = r[:resource]
60
+ return resource_candidate.resource_id if resource_candidate.respond_to?(:resource_id)
61
+
62
+ # Fall back to resource_title
58
63
  resource_obj = r[:resource_title]
59
64
  return resource_obj.resource_id if resource_obj.respond_to?(:resource_id)
60
65
 
@@ -62,8 +67,13 @@ module Inspec::Reporters
62
67
  if resource_obj.is_a?(String)
63
68
  orig_str = resource_obj
64
69
  # Try to trim off the resource class - eg "File /some/path" => "/some/path"
65
- trimmed_str = orig_str.sub(/^#{r[:resource_class]}/i, "").strip
66
- trimmed_str.empty? ? orig_str : trimmed_str
70
+ resource_class = r[:resource_class].to_s
71
+ trimmed_str = orig_str.sub(/^#{Regexp.escape(resource_class)}/i, "").strip
72
+
73
+ # Cap the resource_id to a reasonable length to avoid bloating reports
74
+ max_length = 256
75
+ candidate = trimmed_str.empty? ? orig_str : trimmed_str
76
+ candidate.length > max_length ? candidate[0, max_length] : candidate
67
77
  else
68
78
  # Boo, InSpec is crazy, and we don't know what it possibly could be.
69
79
  # Failsafe for resource_id is empty string.
@@ -30,9 +30,15 @@ module Inspec::Resources
30
30
  its('value') { should_not be_empty }
31
31
  its('value') { should cmp == 1 }
32
32
  end
33
+
34
+ # Trust the SQL Server TLS certificate when using sqlcmd
35
+ sql_tls = mssql_session(user: 'myuser', password: 'mypassword', trust_server_certificate: true)
36
+ describe sql_tls.query(\"SELECT SERVERPROPERTY('ProductVersion') as \\\"version\\\";\").row(0).column('version') do
37
+ its('value') { should_not be_empty }
38
+ end
33
39
  EXAMPLE
34
40
 
35
- attr_reader :user, :password, :host, :port, :instance, :local_mode, :db_name
41
+ attr_reader :user, :password, :host, :port, :instance, :local_mode, :db_name, :trust_server_certificate
36
42
  def initialize(opts = {})
37
43
  @user = opts[:user]
38
44
  @password = opts[:password] || opts[:pass]
@@ -46,6 +52,7 @@ module Inspec::Resources
46
52
  end
47
53
  @instance = opts[:instance]
48
54
  @db_name = opts[:db_name]
55
+ @trust_server_certificate = !!opts[:trust_server_certificate] # rubocop:disable Style/DoubleNegation
49
56
 
50
57
  # check if sqlcmd is available
51
58
  raise Inspec::Exceptions::ResourceSkipped, "sqlcmd is missing" unless inspec.command("sqlcmd").exist?
@@ -57,6 +64,7 @@ module Inspec::Resources
57
64
  escaped_query = q.gsub(/\\/, "\\\\").gsub(/"/, '""').gsub(/\$/, '\\$')
58
65
  # surpress 'x rows affected' in SQLCMD with 'set nocount on;'
59
66
  cmd_string = "sqlcmd -Q \"set nocount on; #{escaped_query}\" -W -w 1024 -s ','"
67
+ cmd_string += " -C" if trust_server_certificate?
60
68
  cmd_string += " -U '#{@user}' -P '#{@password}'" unless @user.nil? || @password.nil?
61
69
  cmd_string += " -d '#{@db_name}'" unless @db_name.nil?
62
70
  unless local_mode?
@@ -94,6 +102,10 @@ module Inspec::Resources
94
102
  !!@local_mode # rubocop:disable Style/DoubleNegation
95
103
  end
96
104
 
105
+ def trust_server_certificate?
106
+ @trust_server_certificate
107
+ end
108
+
97
109
  def test_connection
98
110
  !query("select getdate()").empty?
99
111
  end
@@ -13,14 +13,29 @@ module Inspec::Resources
13
13
  supports platform: "windows"
14
14
  desc "Use the oracledb_session InSpec resource to test commands against an Oracle database"
15
15
  example <<~EXAMPLE
16
+ # Using password
16
17
  sql = oracledb_session(user: 'my_user', pass: 'password')
17
18
  describe sql.query(\"SELECT UPPER(VALUE) AS VALUE FROM V$PARAMETER WHERE UPPER(NAME)='AUDIT_SYS_OPERATIONS'\").row(0).column('value') do
18
19
  its('value') { should eq 'TRUE' }
19
20
  end
21
+
22
+ # CHEF-28019: Using TNS alias (recommended for TCPS/SSL connections)
23
+ sql = oracledb_session(
24
+ user: 'my_user',
25
+ password: 'password',
26
+ tns_alias: 'MYDB_TCPS',
27
+ env: {
28
+ 'TNS_ADMIN' => '/path/to/tnsnames',
29
+ 'LD_LIBRARY_PATH' => '/opt/oracle/instantclient'
30
+ }
31
+ )
32
+ describe sql.query('SELECT * FROM dual').row(0).column('dummy') do
33
+ its('value') { should eq 'X' }
34
+ end
20
35
  EXAMPLE
21
36
 
22
37
  attr_reader :bin, :db_role, :host, :password, :port, :service,
23
- :su_user, :user
38
+ :su_user, :user, :tns_alias, :env_vars
24
39
 
25
40
  def initialize(opts = {})
26
41
  @user = opts[:user]
@@ -37,6 +52,11 @@ module Inspec::Resources
37
52
  @db_role = opts[:as_db_role]
38
53
  @sqlcl_bin = opts[:sqlcl_bin] || nil
39
54
  @sqlplus_bin = opts[:sqlplus_bin] || "sqlplus"
55
+
56
+ # CHEF-28019: Support for TNS alias and environment variables
57
+ @tns_alias = opts[:tns_alias]
58
+ @env_vars = opts[:env] || {}
59
+
40
60
  skip_resource "Option 'as_os_user' not available in Windows" if inspec.os.windows? && su_user
41
61
  fail_resource "Can't run Oracle checks without authentication" unless su_user || (user || password)
42
62
  end
@@ -77,8 +97,10 @@ module Inspec::Resources
77
97
  end
78
98
 
79
99
  def resource_id
80
- if @user
81
- "#{@host}-#{@port}-#{@user}"
100
+ if @tns_alias && !@tns_alias.empty?
101
+ "#{@tns_alias}-#{@user}" # e.g., "XEPDB1_TCPS-USER"
102
+ elsif @user
103
+ "#{@host}-#{@port}-#{@user}" # e.g., "localhost-1521-USER"
82
104
  elsif @su_user
83
105
  "#{@host}-#{@port}-#{@su_user}"
84
106
  else
@@ -88,10 +110,9 @@ module Inspec::Resources
88
110
 
89
111
  private
90
112
 
91
- # 3 commands
92
- # regular user password
93
- # using a db_role
94
- # su, using a db_role
113
+ # CHEF-28019: Build command with support for TNS alias and environment variables
114
+ # Existing behavior: regular user/password, using db_role, or su with db_role
115
+ # Added New behavior: TNS alias connections with optional env vars
95
116
  def command_builder(format_options, query)
96
117
  if @db_role.nil? || @su_user.nil?
97
118
  verified_query = verify_query(query)
@@ -116,7 +137,11 @@ module Inspec::Resources
116
137
  sql_postfix = %{ <<'EOC'\n#{format_options}\n#{verified_query}\nEXIT\n'EOC'} if shell_is_csh
117
138
  end
118
139
 
119
- if @db_role.nil?
140
+ # CHEF-28019: New path for TNS alias connections
141
+ if @tns_alias && !@tns_alias.to_s.empty?
142
+ build_tns_command(format_options, verified_query, oracle_echo_str)
143
+ # Original paths preserved
144
+ elsif @db_role.nil?
120
145
  %{#{oracle_echo_str}#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service}#{sql_postfix}}
121
146
  elsif @su_user.nil?
122
147
  %{#{oracle_echo_str}#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service} as #{@db_role}#{sql_postfix}}
@@ -153,5 +178,35 @@ module Inspec::Resources
153
178
  Hashie::Mash.new([revised_row].to_h)
154
179
  end
155
180
  end
181
+
182
+ # CHEF-28019: Build TNS alias command with environment variables
183
+ def build_tns_command(format_options, verified_query, oracle_echo_str)
184
+ env_prefix = build_env_prefix
185
+ connect_string = build_connect_string
186
+ heredoc_content = "connect #{connect_string}\n#{format_options}\n#{verified_query}\nEXIT"
187
+
188
+ if @su_user
189
+ cmd = %{su - #{@su_user} -c "#{oracle_echo_str} #{env_prefix} #{@bin} -s /nolog <<'INSPECSQL'\n#{heredoc_content}\nINSPECSQL"}
190
+ else
191
+ cmd = %{#{oracle_echo_str}#{bin} -s /nolog <<'INSPECSQL'\n#{heredoc_content}\nINSPECSQL}
192
+ cmd = "#{env_prefix} #{cmd}" unless env_prefix.empty?
193
+ end
194
+
195
+ cmd
196
+ end
197
+
198
+ # CHEF-28019: Build Oracle connect string for TNS alias
199
+ def build_connect_string
200
+ connect_str = "#{@user}/#{@password}@#{@tns_alias}"
201
+ connect_str += " as #{@db_role}" if @db_role && !@su_user
202
+ connect_str
203
+ end
204
+
205
+ # CHEF-28019: Build environment variable prefix
206
+ def build_env_prefix
207
+ return "" if @env_vars.nil? || @env_vars.empty?
208
+
209
+ @env_vars.map { |k, v| "#{k}='#{v}'" }.join(" ")
210
+ end
156
211
  end
157
212
  end
data/lib/inspec/rule.rb CHANGED
@@ -48,12 +48,23 @@ module Inspec
48
48
  return unless block_given?
49
49
 
50
50
  begin
51
- instance_eval(&block)
52
-
53
- # By applying waivers *after* the instance eval, we assure that
54
- # waivers have higher precedence than only_if.
51
+ # Pre-check: apply waivers before evaluating the control block.
52
+ # If the control is waived with run: false, skip the block entirely
53
+ # to avoid eager resource evaluation (e.g., `command('find /').stdout`
54
+ # executing expensive commands for waived controls).
55
55
  __apply_waivers
56
56
 
57
+ unless @__skip_rule[:result] && @__skip_rule[:type] == :waiver
58
+ instance_eval(&block)
59
+
60
+ # Re-apply waivers after instance eval. This is a no-op in practice:
61
+ # run:false waivers are already handled by the pre-check above (the
62
+ # unless guard prevents instance_eval from running at all), and
63
+ # run:true / no-run-key waivers do not set a skip flag. Kept for
64
+ # defensive correctness in case waiver state changes during eval.
65
+ __apply_waivers
66
+ end
67
+
57
68
  rescue SystemStackError, StandardError => e
58
69
  # We've encountered an exception while trying to eval the code inside the
59
70
  # control block. We need to prevent the exception from bubbling up, and
@@ -6,7 +6,6 @@ module Inspec
6
6
  def guess_install_context
7
7
  # These all work by simple path recognition
8
8
  return "chef-workstation" if chef_workstation_install?
9
- return "omnibus" if omnibus_install?
10
9
  return "chefdk" if chefdk_install?
11
10
  return "habitat" if habitat_install?
12
11
 
@@ -19,8 +18,6 @@ module Inspec
19
18
  "unknown"
20
19
  end
21
20
 
22
- private
23
-
24
21
  def chef_workstation_install?
25
22
  !!(src_root.start_with?("/opt/chef-workstation") || src_root.start_with?("C:/opscode/chef-workstation"))
26
23
  end
@@ -38,10 +35,6 @@ module Inspec
38
35
  !!src_root.match(%r{hab/pkgs/chef/inspec/\d+\.\d+\.\d+/\d{14}})
39
36
  end
40
37
 
41
- def omnibus_install?
42
- !!(src_root.start_with?("/opt/inspec") || src_root.start_with?("C:/opscode/inspec"))
43
- end
44
-
45
38
  def rubygem_install?
46
39
  !!src_root.match(%r{gems/inspec-\d+\.\d+\.\d+})
47
40
  end
@@ -24,6 +24,37 @@ module Inspec
24
24
  @memo = memo
25
25
  end
26
26
 
27
+ def extract_node_value(node)
28
+ case node.class.to_s
29
+ when "RuboCop::AST::HashNode"
30
+ # Handle hash nodes
31
+ values = {}
32
+ node.children.each do |pair_node|
33
+ values.merge!(pair_node.key.value => extract_node_value(pair_node.value))
34
+ end
35
+ values
36
+ when "RuboCop::AST::ArrayNode"
37
+ # Handle array nodes
38
+ node.children.map { |element| extract_node_value(element) }
39
+ else
40
+ # Handle simple nodes (strings, numbers, symbols, booleans, nil, etc.)
41
+ if node.respond_to?(:type)
42
+ case node.type
43
+ when :true
44
+ true
45
+ when :false
46
+ false
47
+ when :nil
48
+ nil
49
+ else
50
+ node.respond_to?(:value) ? node.value : node
51
+ end
52
+ else
53
+ node.respond_to?(:value) ? node.value : node
54
+ end
55
+ end
56
+ end
57
+
27
58
  def collect_input(input_children)
28
59
  input_name = input_children.children[2].value
29
60
 
@@ -39,15 +70,9 @@ module Inspec
39
70
  if VALID_INPUT_OPTIONS.include?(child_node.key.value)
40
71
  if child_node.value.class == RuboCop::AST::Node && REQUIRED_VALUES_MAP.key?(child_node.value.type)
41
72
  opts.merge!(child_node.key.value => REQUIRED_VALUES_MAP[child_node.value.type])
42
- elsif child_node.value.class == RuboCop::AST::HashNode
43
- # Here value will be a hash
44
- values = {}
45
- child_node.value.children.each do |grand_child_node|
46
- values.merge!(grand_child_node.key.value => grand_child_node.value.value)
47
- end
48
- opts.merge!(child_node.key.value => values)
49
73
  else
50
- opts.merge!(child_node.key.value => child_node.value.value)
74
+ # Use the helper method to recursively extract values from any node type
75
+ opts.merge!(child_node.key.value => extract_node_value(child_node.value))
51
76
  end
52
77
  end
53
78
  end
@@ -313,8 +338,11 @@ module Inspec
313
338
  collectors.push InputCollectorWithinControlBlock.new(@memo)
314
339
  collectors.push TestsCollector.new(control_data) if include_tests
315
340
 
316
- begin_block.each_node do |node_within_control|
317
- collectors.each { |collector| collector.process(node_within_control) }
341
+ # Handle empty control blocks (e.g., control "id" do end)
342
+ if begin_block
343
+ begin_block.each_node do |node_within_control|
344
+ collectors.each { |collector| collector.process(node_within_control) }
345
+ end
318
346
  end
319
347
 
320
348
  memo[:controls].push control_data
@@ -48,7 +48,7 @@ module Inspec
48
48
  payload = create_wrapper
49
49
 
50
50
  train_platform = opts[:runner].backend.backend.platform
51
- payload[:platform] = train_platform.name
51
+ payload[:platform] = safe_platform_field(train_platform, :name)
52
52
 
53
53
  payload[:jobs] = [{
54
54
  type: JOB_TYPE,
@@ -56,10 +56,10 @@ module Inspec
56
56
  # Target platform info
57
57
  environment: {
58
58
  host: obscure(URI(opts[:runner].backend.backend.uri).host) || "unknown",
59
- os: train_platform.name,
60
- version: train_platform.release,
61
- architecture: train_platform.arch || "",
62
- id: train_platform.uuid,
59
+ os: safe_platform_field(train_platform, :name) || "unknown",
60
+ version: safe_platform_field(train_platform, :release) || "unknown",
61
+ architecture: safe_platform_field(train_platform, :arch) || "unknown",
62
+ id: safe_platform_field(train_platform, :uuid),
63
63
  },
64
64
 
65
65
  runtime: Inspec::VERSION,
@@ -125,6 +125,14 @@ module Inspec
125
125
  Digest::SHA2.new(256).hexdigest(cleartext)
126
126
  end
127
127
 
128
+ # Safely access platform fields that may not exist
129
+ def safe_platform_field(platform, field)
130
+ return nil if platform.nil?
131
+ return nil unless platform.respond_to?(field)
132
+
133
+ platform.send(field)
134
+ end
135
+
128
136
  def note_per_run_features(opts)
129
137
  note_all_invoked_features
130
138
  note_gem_dependency_usage(opts)
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "7.0.95".freeze
2
+ VERSION = "7.1.7".freeze
3
3
  end
@@ -57,7 +57,7 @@ module InspecPlugins
57
57
  path = profile.root_path
58
58
  logger.debug("Setting up #{path} for Habitat...")
59
59
 
60
- plan_file = File.join(path, "habitat", "plan.sh")
60
+ plan_file = File.join(path, "habitat", "x86_64-linux", "plan.sh")
61
61
  logger.info("Generating Habitat plan at #{plan_file}...")
62
62
  vars = {
63
63
  profile: profile,
@@ -426,7 +426,7 @@ module InspecPlugins
426
426
  "at https://github.com/inspec/inspec/issues/new")
427
427
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
428
428
  rescue Inspec::Plugin::V2::InstallError => e
429
- # This change is required for Ruby 3.3 upgrade
429
+ # This change is compatible with various versions of Ruby, including Ruby 3.3
430
430
  # Using Inspec::Log::level breaks with error `undefined method nil` in Ruby log library
431
431
  Inspec::Log.debug e.backtrace
432
432
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.95
4
+ version: 7.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef InSpec Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-16 00:00:00.000000000 Z
11
+ date: 2026-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef-telemetry
@@ -314,14 +314,14 @@ dependencies:
314
314
  requirements:
315
315
  - - "~>"
316
316
  - !ruby/object:Gem::Version
317
- version: '2.4'
317
+ version: '2.9'
318
318
  type: :runtime
319
319
  prerelease: false
320
320
  version_requirements: !ruby/object:Gem::Requirement
321
321
  requirements:
322
322
  - - "~>"
323
323
  - !ruby/object:Gem::Version
324
- version: '2.4'
324
+ version: '2.9'
325
325
  - !ruby/object:Gem::Dependency
326
326
  name: parslet
327
327
  requirement: !ruby/object:Gem::Requirement
@@ -438,20 +438,20 @@ dependencies:
438
438
  requirements:
439
439
  - - "~>"
440
440
  - !ruby/object:Gem::Version
441
- version: '3.13'
441
+ version: '3.16'
442
442
  - - ">="
443
443
  - !ruby/object:Gem::Version
444
- version: 3.13.4
444
+ version: 3.16.1
445
445
  type: :runtime
446
446
  prerelease: false
447
447
  version_requirements: !ruby/object:Gem::Requirement
448
448
  requirements:
449
449
  - - "~>"
450
450
  - !ruby/object:Gem::Version
451
- version: '3.13'
451
+ version: '3.16'
452
452
  - - ">="
453
453
  - !ruby/object:Gem::Version
454
- version: 3.13.4
454
+ version: 3.16.1
455
455
  - !ruby/object:Gem::Dependency
456
456
  name: chef-licensing
457
457
  requirement: !ruby/object:Gem::Requirement