inspec-core 5.24.7 → 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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/Chef-EULA +9 -0
  3. data/Gemfile +25 -36
  4. data/etc/features.sig +6 -0
  5. data/etc/features.yaml +94 -0
  6. data/inspec-core.gemspec +21 -19
  7. data/lib/inspec/archive/tar.rb +0 -1
  8. data/lib/inspec/backend.rb +2 -0
  9. data/lib/inspec/base_cli.rb +80 -14
  10. data/lib/inspec/cached_fetcher.rb +24 -3
  11. data/lib/inspec/cli.rb +292 -235
  12. data/lib/inspec/config.rb +24 -11
  13. data/lib/inspec/dependencies/cache.rb +33 -0
  14. data/lib/inspec/dependencies/dependency_set.rb +2 -2
  15. data/lib/inspec/dsl.rb +1 -1
  16. data/lib/inspec/enhanced_outcomes.rb +1 -0
  17. data/lib/inspec/errors.rb +5 -0
  18. data/lib/inspec/exceptions.rb +1 -0
  19. data/lib/inspec/feature/config.rb +75 -0
  20. data/lib/inspec/feature/runner.rb +26 -0
  21. data/lib/inspec/feature.rb +34 -0
  22. data/lib/inspec/fetcher/git.rb +6 -21
  23. data/lib/inspec/fetcher/url.rb +7 -29
  24. data/lib/inspec/file_provider.rb +0 -1
  25. data/lib/inspec/globals.rb +6 -0
  26. data/lib/inspec/input_registry.rb +1 -5
  27. data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +7 -0
  28. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +30 -2
  29. data/lib/inspec/profile.rb +49 -13
  30. data/lib/inspec/reporters/cli.rb +1 -1
  31. data/lib/inspec/reporters.rb +67 -54
  32. data/lib/inspec/resources/audit_policy.rb +2 -8
  33. data/lib/inspec/resources/groups.rb +0 -52
  34. data/lib/inspec/resources/mssql_session.rb +1 -13
  35. data/lib/inspec/resources/nftables.rb +1 -14
  36. data/lib/inspec/resources/oracledb_session.rb +11 -72
  37. data/lib/inspec/resources/postgres_session.rb +5 -9
  38. data/lib/inspec/resources/sybase_session.rb +2 -11
  39. data/lib/inspec/resources/virtualization.rb +1 -1
  40. data/lib/inspec/rule.rb +9 -14
  41. data/lib/inspec/run_data.rb +7 -5
  42. data/lib/inspec/runner.rb +35 -6
  43. data/lib/inspec/runner_rspec.rb +12 -9
  44. data/lib/inspec/secrets/yaml.rb +5 -1
  45. data/lib/inspec/shell.rb +10 -0
  46. data/lib/inspec/ui.rb +4 -0
  47. data/lib/inspec/utils/licensing_config.rb +9 -0
  48. data/lib/inspec/utils/profile_ast_helpers.rb +12 -39
  49. data/lib/inspec/utils/waivers/csv_file_reader.rb +1 -1
  50. data/lib/inspec/utils/waivers/excel_file_reader.rb +1 -1
  51. data/lib/inspec/version.rb +1 -1
  52. data/lib/inspec/waiver_file_reader.rb +18 -35
  53. data/lib/inspec.rb +2 -1
  54. data/lib/matchers/matchers.rb +3 -3
  55. data/lib/plugins/inspec-compliance/README.md +1 -11
  56. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -170
  57. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
  58. data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
  59. data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
  60. data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
  61. data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
  62. data/lib/plugins/inspec-license/README.md +16 -0
  63. data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
  64. data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
  65. data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
  66. data/lib/plugins/inspec-parallel/README.md +27 -0
  67. data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
  68. data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
  69. data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
  70. data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
  71. data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +265 -0
  72. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
  73. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
  74. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +124 -0
  75. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
  76. data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
  77. data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
  78. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +10 -11
  79. data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
  80. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
  81. data/lib/source_readers/inspec.rb +1 -1
  82. metadata +55 -47
@@ -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
- config[:run_data] = run_data
13
- case name
14
- when "cli"
15
- reporter = Inspec::Reporters::CLI.new(config)
16
- when "json"
17
- reporter = Inspec::Reporters::Json.new(config)
18
- # This reporter is only used for Chef internal. We reserve the
19
- # right to introduce breaking changes to this reporter at any time.
20
- when "json-automate"
21
- reporter = Inspec::Reporters::JsonAutomate.new(config)
22
- when "automate"
23
- reporter = Inspec::Reporters::Automate.new(config)
24
- when "yaml"
25
- reporter = Inspec::Reporters::Yaml.new(config)
26
- else
27
- # If we made it here, it must be a plugin, and we know it exists (because we validated it in config.rb)
28
- activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
29
- activator.activate!
30
- reporter = activator.implementation_class.new(config)
31
- end
32
- reporter.enhanced_outcomes = enhanced_outcomes
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
- # optional send_report method on reporter
35
- return reporter.send_report if defined?(reporter.send_report)
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
- reporter.render
38
- output = reporter.rendered_output
43
+ # optional send_report method on reporter
44
+ return reporter.send_report if defined?(reporter.send_report)
39
45
 
40
- if config["file"]
41
- # create destination directory if it does not exist
42
- dirname = File.dirname(config["file"])
43
- FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
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
- File.write(config["file"], output)
46
- elsif config["stdout"] == true
47
- print output
48
- $stdout.flush
49
- end
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
- config[:run_data] = run_data
55
- case name
56
- when "json"
57
- reporter = Inspec::Reporters::Json.new(config)
58
- when "json-automate"
59
- reporter = Inspec::Reporters::JsonAutomate.new(config)
60
- when "yaml"
61
- reporter = Inspec::Reporters::Yaml.new(config)
62
- else
63
- # If we made it here, it might be a plugin
64
- begin
65
- activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
66
- activator.activate!
67
- reporter = activator.implementation_class.new(config)
68
- unless reporter.respond_to(:report?)
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
- reporter.report
89
+ reporter.report
90
+ }
78
91
  end
79
92
  end
@@ -39,17 +39,11 @@ module Inspec::Resources
39
39
  # expected result:
40
40
  # Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting
41
41
  # WIN-MB8NINQ388J,System,Kerberos Authentication Service,{0CCE9242-69AE-11D9-BED3-505054503030},No Auditing,
42
- auditpol_cmd = "Auditpol /get /subcategory:'#{key}' /r"
43
- result ||= inspec.command(auditpol_cmd)
44
-
45
- unless result.exit_status == 0
46
- error = result.stdout + "\n" + result.stderr
47
- raise Inspec::Exceptions::ResourceFailed, "Error while executing #{auditpol_cmd} command: #{error}"
48
- end
42
+ result ||= inspec.command("Auditpol /get /subcategory:'#{key}' /r").stdout
49
43
 
50
44
  # find line
51
45
  target = nil
52
- result.stdout.each_line do |s|
46
+ result.each_line do |s|
53
47
  target = s.strip if s =~ /\b.*#{key}.*\b/
54
48
  end
55
49
 
@@ -200,60 +200,8 @@ module Inspec::Resources
200
200
  # implements generic unix groups via /etc/group
201
201
  class UnixGroup < GroupInfo
202
202
  def groups
203
- get_group_info
204
- end
205
-
206
- private
207
-
208
- def get_group_info
209
- # First, try to fetch group info using getent
210
- group_info = fetch_group_info_using_getent
211
-
212
- return group_info unless group_info.empty?
213
-
214
- # If getent fails, fallback to reading group info from /etc/group using inspec.etc_group.entries
215
- Inspec::Log.debug("Falling back to reading group info from /etc/group as getent is unavailable or failed.")
216
203
  inspec.etc_group.entries
217
204
  end
218
-
219
- # Fetches group information using the getent utility
220
- def fetch_group_info_using_getent
221
- # Find getent utility on the system
222
- bin = find_getent_utility
223
-
224
- # If getent is available, fetch group info
225
- return [] unless bin
226
-
227
- cmd = inspec.command("#{bin} group")
228
- return parse_group_info(cmd) if cmd.exit_status.to_i == 0
229
-
230
- # If getent fails, log the error and return an empty array
231
- Inspec::Log.debug("Failed to execute #{bin} group: #{cmd.stderr}.")
232
- []
233
- end
234
-
235
- # Parses group info from the command output
236
- def parse_group_info(cmd)
237
- cmd.stdout.strip.split("\n").map do |line|
238
- name, password, gid, members = line.split(":")
239
- {
240
- "name" => name,
241
- "password" => password,
242
- "gid" => gid.to_i,
243
- "members" => members,
244
- }
245
- end
246
- end
247
-
248
- # Checks if getent exists on the system
249
- def find_getent_utility
250
- %w{/usr/bin/getent /bin/getent getent}.each do |cmd|
251
- return cmd if inspec.command(cmd).exist?
252
- end
253
- # Log debug information if getent is not found
254
- Inspec::Log.debug("Could not find `getent` on your system.")
255
- nil # Return nil if getent is not found
256
- end
257
205
  end
258
206
 
259
207
  # OSX uses opendirectory for groups, so `/etc/group` may not be fully accurate
@@ -30,15 +30,9 @@ 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
39
33
  EXAMPLE
40
34
 
41
- attr_reader :user, :password, :host, :port, :instance, :local_mode, :db_name, :trust_server_certificate
35
+ attr_reader :user, :password, :host, :port, :instance, :local_mode, :db_name
42
36
  def initialize(opts = {})
43
37
  @user = opts[:user]
44
38
  @password = opts[:password] || opts[:pass]
@@ -52,7 +46,6 @@ module Inspec::Resources
52
46
  end
53
47
  @instance = opts[:instance]
54
48
  @db_name = opts[:db_name]
55
- @trust_server_certificate = !!opts[:trust_server_certificate] # rubocop:disable Style/DoubleNegation
56
49
 
57
50
  # check if sqlcmd is available
58
51
  raise Inspec::Exceptions::ResourceSkipped, "sqlcmd is missing" unless inspec.command("sqlcmd").exist?
@@ -64,7 +57,6 @@ module Inspec::Resources
64
57
  escaped_query = q.gsub(/\\/, "\\\\").gsub(/"/, '""').gsub(/\$/, '\\$')
65
58
  # surpress 'x rows affected' in SQLCMD with 'set nocount on;'
66
59
  cmd_string = "sqlcmd -Q \"set nocount on; #{escaped_query}\" -W -w 1024 -s ','"
67
- cmd_string += " -C" if trust_server_certificate?
68
60
  cmd_string += " -U '#{@user}' -P '#{@password}'" unless @user.nil? || @password.nil?
69
61
  cmd_string += " -d '#{@db_name}'" unless @db_name.nil?
70
62
  unless local_mode?
@@ -102,10 +94,6 @@ module Inspec::Resources
102
94
  !!@local_mode # rubocop:disable Style/DoubleNegation
103
95
  end
104
96
 
105
- def trust_server_certificate?
106
- @trust_server_certificate
107
- end
108
-
109
97
  def test_connection
110
98
  !query("select getdate()").empty?
111
99
  end
@@ -135,20 +135,7 @@ module Inspec::Resources
135
135
  cmd = inspec.command(nftables_cmd)
136
136
  return [] if cmd.exit_status.to_i != 0
137
137
 
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')
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)
152
139
  end
153
140
 
154
141
  def retrieve_chain_rules
@@ -13,29 +13,14 @@ 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
17
16
  sql = oracledb_session(user: 'my_user', pass: 'password')
18
17
  describe sql.query(\"SELECT UPPER(VALUE) AS VALUE FROM V$PARAMETER WHERE UPPER(NAME)='AUDIT_SYS_OPERATIONS'\").row(0).column('value') do
19
18
  its('value') { should eq 'TRUE' }
20
19
  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
35
20
  EXAMPLE
36
21
 
37
22
  attr_reader :bin, :db_role, :host, :password, :port, :service,
38
- :su_user, :user, :tns_alias, :env_vars
23
+ :su_user, :user
39
24
 
40
25
  def initialize(opts = {})
41
26
  @user = opts[:user]
@@ -52,11 +37,6 @@ module Inspec::Resources
52
37
  @db_role = opts[:as_db_role]
53
38
  @sqlcl_bin = opts[:sqlcl_bin] || nil
54
39
  @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
-
60
40
  skip_resource "Option 'as_os_user' not available in Windows" if inspec.os.windows? && su_user
61
41
  fail_resource "Can't run Oracle checks without authentication" unless su_user || (user || password)
62
42
  end
@@ -77,7 +57,7 @@ module Inspec::Resources
77
57
  inspec_cmd = inspec.command(command)
78
58
  out = inspec_cmd.stdout + "\n" + inspec_cmd.stderr
79
59
 
80
- if inspec_cmd.exit_status != 0 || out.downcase =~ /^error.*/
60
+ if inspec_cmd.exit_status != 0 || !inspec_cmd.stderr.empty? || out.downcase =~ /^error.*/
81
61
  raise Inspec::Exceptions::ResourceFailed, "Oracle query with errors: #{out}"
82
62
  else
83
63
  begin
@@ -97,10 +77,8 @@ module Inspec::Resources
97
77
  end
98
78
 
99
79
  def resource_id
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"
80
+ if @user
81
+ "#{@host}-#{@port}-#{@user}"
104
82
  elsif @su_user
105
83
  "#{@host}-#{@port}-#{@su_user}"
106
84
  else
@@ -110,14 +88,16 @@ module Inspec::Resources
110
88
 
111
89
  private
112
90
 
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
91
+ # 3 commands
92
+ # regular user password
93
+ # using a db_role
94
+ # su, using a db_role
116
95
  def command_builder(format_options, query)
117
96
  if @db_role.nil? || @su_user.nil?
118
97
  verified_query = verify_query(query)
119
98
  else
120
- escaped_query = escape_query(query)
99
+ escaped_query = query.gsub(/\\\\/, "\\").gsub(/"/, '\\"')
100
+ escaped_query = escaped_query.gsub("$", '\\$') unless escaped_query.include? "\\$"
121
101
  verified_query = verify_query(escaped_query)
122
102
  end
123
103
 
@@ -137,11 +117,7 @@ module Inspec::Resources
137
117
  sql_postfix = %{ <<'EOC'\n#{format_options}\n#{verified_query}\nEXIT\n'EOC'} if shell_is_csh
138
118
  end
139
119
 
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
+ if @db_role.nil?
145
121
  %{#{oracle_echo_str}#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service}#{sql_postfix}}
146
122
  elsif @su_user.nil?
147
123
  %{#{oracle_echo_str}#{sql_prefix}#{bin} #{user}/#{password}@#{host}:#{port}/#{@service} as #{@db_role}#{sql_postfix}}
@@ -158,17 +134,10 @@ module Inspec::Resources
158
134
  query
159
135
  end
160
136
 
161
- def escape_query(query)
162
- escaped_query = query.gsub(/\\\\/, "\\").gsub(/"/, '\\"')
163
- escaped_query = escaped_query.gsub("$", '\\$') unless escaped_query.include? "\\$"
164
- escaped_query
165
- end
166
-
167
137
  def parse_csv_result(stdout)
168
138
  output = stdout.split("oracle_query_string")[-1]
169
139
  # comma_query_sub replaces the csv delimiter "," in the output.
170
140
  # Handles CSV parsing of data like this (DROP,3) etc
171
-
172
141
  output = output.sub(/\r/, "").strip.gsub(",", "comma_query_sub")
173
142
  converter = ->(header) { header.downcase }
174
143
  CSV.parse(output, headers: true, header_converters: converter).map do |row|
@@ -178,35 +147,5 @@ module Inspec::Resources
178
147
  Hashie::Mash.new([revised_row].to_h)
179
148
  end
180
149
  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
211
150
  end
212
151
  end
@@ -1,7 +1,7 @@
1
1
  # copyright: 2015, Vulcano Security GmbH
2
2
 
3
3
  require "shellwords" unless defined?(Shellwords)
4
- require "cgi" unless defined?(CGI)
4
+
5
5
  module Inspec::Resources
6
6
  class Lines
7
7
  attr_reader :output, :exit_status
@@ -55,7 +55,7 @@ module Inspec::Resources
55
55
  psql_cmd = create_psql_cmd(query, db)
56
56
  cmd = inspec.command(psql_cmd, redact_regex: %r{(:\/\/[a-z]*:).*(@)})
57
57
  out = cmd.stdout + "\n" + cmd.stderr
58
- if cmd.exit_status != 0 && ( out =~ /could not connect to/ || out =~ /password authentication failed/ ) && (out.downcase =~ /error:/ || out.downcase =~ /fatal:/)
58
+ if cmd.exit_status != 0 && ( out =~ /could not connect to/ || out =~ /password authentication failed/ ) && out.downcase =~ /error:/
59
59
  raise Inspec::Exceptions::ResourceFailed, "PostgreSQL connection error: #{out}"
60
60
  elsif cmd.exit_status != 0 && out.downcase =~ /error:/
61
61
  Lines.new(out, "PostgreSQL query with error: #{query}", cmd.exit_status)
@@ -74,10 +74,6 @@ module Inspec::Resources
74
74
  Shellwords.escape(query)
75
75
  end
76
76
 
77
- def encoded_password(password)
78
- CGI.escape(password)
79
- end
80
-
81
77
  def create_psql_cmd(query, db = [])
82
78
  dbs = db.map { |x| "#{x}" }.join(" ")
83
79
 
@@ -86,14 +82,14 @@ module Inspec::Resources
86
82
  # Socket connection only enabled for non-windows platforms
87
83
  # Windows does not support unix domain sockets
88
84
  option_port = @port.nil? ? "" : "-p #{@port}" # add explicit port if specified
89
- "psql -d postgresql://#{@user}:#{encoded_password(@pass)}@/#{dbs}?host=#{@socket_path} #{option_port} -A -t -w -c #{escaped_query(query)}"
85
+ "psql -d postgresql://#{@user}:#{@pass}@/#{dbs}?host=#{@socket_path} #{option_port} -A -t -w -c #{escaped_query(query)}"
90
86
  else
91
87
  # Host in connection string establishes tcp/ip connection
92
88
  if inspec.os.windows?
93
89
  warn "Socket based connection not supported in windows, connecting using host" if @socket_path
94
- "psql -d postgresql://#{@user}:#{encoded_password(@pass)}@#{@host}:#{@port}/#{dbs} -A -t -w -c \"#{query}\""
90
+ "psql -d postgresql://#{@user}:#{@pass}@#{@host}:#{@port}/#{dbs} -A -t -w -c \"#{query}\""
95
91
  else
96
- "psql -d postgresql://#{@user}:#{encoded_password(@pass)}@#{@host}:#{@port}/#{dbs} -A -t -w -c #{escaped_query(query)}"
92
+ "psql -d postgresql://#{@user}:#{@pass}@#{@host}:#{@port}/#{dbs} -A -t -w -c #{escaped_query(query)}"
97
93
  end
98
94
  end
99
95
  end
@@ -44,19 +44,10 @@ module Inspec::Resources
44
44
  # try to get a temp path
45
45
  sql_file_path = upload_sql_file(sql)
46
46
 
47
- # TODO: Find if there is better way to get the current shell
48
- current_shell = inspec.command("echo $SHELL")
49
-
50
- res = current_shell.exit_status
51
-
52
47
  # isql reuires that we have a matching locale set, but does not support C.UTF-8. en_US.UTF-8 is the least evil.
53
- if res == 0 && ( current_shell.stdout&.include?("/csh") || current_shell.stdout&.include?("/tcsh") )
54
- command = "source #{sybase_home}/SYBASE.csh; setenv LANG en_US.UTF-8; #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
55
- else
56
- command = "LANG=en_US.UTF-8 SYBASE=#{sybase_home} #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
57
- end
58
-
48
+ command = "LANG=en_US.UTF-8 SYBASE=#{sybase_home} #{bin} -s\"#{col_sep}\" -w80000 -S #{server} -U #{username} -D #{database} -P \"#{password}\" < #{sql_file_path}"
59
49
  isql_cmd = inspec.command(command)
50
+
60
51
  # Check for isql errors
61
52
  res = isql_cmd.exit_status
62
53
  raise Inspec::Exceptions::ResourceFailed.new("isql exited with code #{res} and stderr '#{isql_cmd.stderr}', stdout '#{isql_cmd.stdout}'") unless res == 0
@@ -223,7 +223,7 @@ module Inspec::Resources
223
223
  elsif cgroup_content =~ %r{^\d+:[^:]+:/(kubepods)/.+$}
224
224
  @virtualization_data[:system] = $1
225
225
  @virtualization_data[:role] = "guest"
226
- elsif /container=podman/.match?(inspec.file("/proc/1/environ").content)
226
+ elsif /container=podman/.match?(file_read("/proc/1/environ"))
227
227
  @virtualization_data[:system] = "podman"
228
228
  @virtualization_data[:role] = "guest"
229
229
  elsif lxc_version_exists? && cgroup_content =~ %r{\d:[^:]+:/$}
data/lib/inspec/rule.rb CHANGED
@@ -375,24 +375,19 @@ module Inspec
375
375
  # only_if mechanism)
376
376
  # Double underscore: not intended to be called as part of the DSL
377
377
  def __apply_waivers
378
- @__waiver_data = nil
379
378
  control_id = @__rule_id # TODO: control ID slugging
380
-
381
379
  waiver_files = Inspec::Config.cached.final_options["waiver_file"] if Inspec::Config.cached.respond_to?(:final_options)
382
- unless waiver_files.nil? || waiver_files.empty?
383
- waiver_data_by_profile = Inspec::WaiverFileReader.fetch_waivers_by_profile(__profile_id, waiver_files)
384
- return unless waiver_data_by_profile && waiver_data_by_profile[control_id] && waiver_data_by_profile[control_id].is_a?(Hash)
385
380
 
386
- @__waiver_data = waiver_data_by_profile[control_id]
387
- else
388
- # Support for input registry is provided for backward compatibilty with compliance phase of chef-client
389
- # Chef-client sends waiver information in inputs hash
390
- input_registry = Inspec::InputRegistry.instance
391
- waiver_data_via_input = input_registry.inputs_by_profile.dig(__profile_id, control_id)
392
- return unless waiver_data_via_input && waiver_data_via_input.has_value? && waiver_data_via_input.value.is_a?(Hash)
381
+ waiver_data_by_profile = Inspec::WaiverFileReader.fetch_waivers_by_profile(__profile_id, waiver_files) unless waiver_files.nil?
393
382
 
394
- @__waiver_data = waiver_data_via_input.value
395
- end
383
+ return unless waiver_data_by_profile && waiver_data_by_profile[control_id] && waiver_data_by_profile[control_id].is_a?(Hash)
384
+
385
+ # An InSpec Input is a datastructure that tracks a profile parameter
386
+ # over time. Its value can be set by many sources, and it keeps a
387
+ # log of each "set" event so that when it is collapsed to a value,
388
+ # it can determine the correct (highest priority) value.
389
+ # Store in an instance variable for.. later reading???
390
+ @__waiver_data = waiver_data_by_profile[control_id]
396
391
 
397
392
  __waiver_data["skipped_due_to_waiver"] = false
398
393
  __waiver_data["message"] = ""
@@ -27,11 +27,13 @@ module Inspec
27
27
  ) do
28
28
  include HashLikeStruct
29
29
  def initialize(raw_run_data)
30
- self.controls = raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
31
- self.profiles = raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
32
- self.statistics = Inspec::RunData::Statistics.new(raw_run_data[:statistics])
33
- self.platform = Inspec::RunData::Platform.new(raw_run_data[:platform])
34
- self.version = raw_run_data[:version]
30
+ @raw_run_data = raw_run_data
31
+
32
+ self.controls = @raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
33
+ self.profiles = @raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
34
+ self.statistics = Inspec::RunData::Statistics.new(@raw_run_data[:statistics])
35
+ self.platform = Inspec::RunData::Platform.new(@raw_run_data[:platform])
36
+ self.version = @raw_run_data[:version]
35
37
  end
36
38
  end
37
39
 
data/lib/inspec/runner.rb CHANGED
@@ -11,6 +11,7 @@ require "inspec/dependencies/cache"
11
11
  require "inspec/dist"
12
12
  require "inspec/reporters"
13
13
  require "inspec/runner_rspec"
14
+ require "chef-licensing"
14
15
  # spec requirements
15
16
 
16
17
  module Inspec
@@ -60,11 +61,13 @@ module Inspec
60
61
  end
61
62
 
62
63
  if @conf[:waiver_file]
63
- @conf[:waiver_file].each do |file|
64
- unless File.file?(file)
65
- raise Inspec::Exceptions::WaiversFileDoesNotExist, "Waiver file #{file} does not exist."
64
+ Inspec.with_feature("inspec-waivers") {
65
+ @conf[:waiver_file].each do |file|
66
+ unless File.file?(file)
67
+ raise Inspec::Exceptions::WaiversFileDoesNotExist, "Waiver file #{file} does not exist."
68
+ end
66
69
  end
67
- end
70
+ }
68
71
  end
69
72
 
70
73
  # About reading inputs:
@@ -142,7 +145,7 @@ module Inspec
142
145
  get_check_example(m, a, b)
143
146
  end.compact
144
147
 
145
- examples.map { |example| total_checks += example.descendant_filtered_examples.count }
148
+ examples.map { |example| total_checks += example.examples.count }
146
149
 
147
150
  unless control_describe_checks.empty?
148
151
  # controls with empty tests are avoided
@@ -159,16 +162,42 @@ module Inspec
159
162
  end
160
163
 
161
164
  def run(with = nil)
165
+ ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
166
+
167
+ # Validate if profiles are signed and verified
168
+ # Additional check is required to provide error message in case of inspec exec command (exec command can use multiple profiles as well)
169
+ # Only runs this block when preview flag CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING is set
170
+ Inspec.with_feature("inspec-mandatory-profile-signing") {
171
+ unless @conf.allow_unsigned_profiles?
172
+ verify_target_profiles_if_signed(@target_profiles)
173
+ end
174
+ }
175
+
162
176
  Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
163
177
  load
164
178
  run_tests(with)
179
+ rescue ChefLicensing::SoftwareNotEntitled
180
+ Inspec::Log.error "License is not entitled to use InSpec."
181
+ Inspec::UI.new.exit(:license_not_entitled)
182
+ rescue ChefLicensing::Error => e
183
+ Inspec::Log.error e.message
184
+ Inspec::UI.new.exit(:usage_error)
185
+ end
186
+
187
+ def verify_target_profiles_if_signed(target_profiles)
188
+ unsigned_profiles = []
189
+ target_profiles.each do |profile|
190
+ unsigned_profiles << profile.name unless profile.verify_if_signed
191
+ end
192
+ raise Inspec::ProfileSignatureRequired, "Signature required for profile/s: #{unsigned_profiles.join(", ")}. Please provide a signed profile. Or set CHEF_ALLOW_UNSIGNED_PROFILES in the environment. Or use `--allow-unsigned-profiles` flag with InSpec CLI. " unless unsigned_profiles.empty?
165
193
  end
166
194
 
167
195
  def render_output(run_data)
168
196
  return if @conf["reporter"].nil?
169
197
 
170
198
  @conf["reporter"].each do |reporter|
171
- result = Inspec::Reporters.render(reporter, run_data, @conf["enhanced_outcomes"])
199
+ enhanced_outcome_flag = @conf["enhanced_outcomes"]
200
+ result = Inspec::Reporters.render(reporter, run_data, enhanced_outcome_flag)
172
201
  raise Inspec::ReporterError, "Error generating reporter '#{reporter[0]}'" if result == false
173
202
  end
174
203
  end