inspec-core 5.21.29 → 5.22.29

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f6f86730baadd988c363a823e5e46ba68c318e48b27bbaaba614ce7c33bb71c
4
- data.tar.gz: f02ca42e9d4e525b82e9cfa61a04b122dc3b09c1e3c96f2ec4440c3fb3053f25
3
+ metadata.gz: 31c8e898abb240d79f4c564ae182f620d291f0e895b1451185f891c0c03a4d3b
4
+ data.tar.gz: 513a01ebe59969076c1a9e2c210f41b1366dbb17603e6370bdcb88d162d0d06a
5
5
  SHA512:
6
- metadata.gz: 597fed943b00ed280a749f05c97d01e29b4d5a85034835e9f242990588b35b740bf56a886b65202777d68b79b5cbd1faf1a48a69c1a4f1f8a00a83ecdece3527
7
- data.tar.gz: 13aa012d46677b3cd31614dcc118c62b1aa1656f3007d6b105f7822a3c25a45b1b1284eef9fb9e761cf5a03cd8209ffe8518d54ed355c189a2f141788a000694
6
+ metadata.gz: af17576ae657e9ca435998a6a57655f0c78d48782e3c7b1f37f62be74d7f7c40192330c3bb34f81c84c1de5b60aea6694c40c8b9ccdedd7d00dfe756614edc00
7
+ data.tar.gz: dc279dd3ab134e92c7c318e859f0f4683347980eafaf80385330b3aeb762c9f72863cf66b2def9a2f61daabb378d3356b5110ac981d119e0e52eae009e785818
data/Gemfile CHANGED
@@ -25,16 +25,15 @@ end
25
25
  group :test do
26
26
  gem "chefstyle", "~> 2.2.2"
27
27
  gem "concurrent-ruby", "~> 1.0"
28
- gem "json_schemer", ">= 0.2.1", "< 0.2.19"
28
+ gem "json_schemer", ">= 0.2.1", "< 2.0.1"
29
29
  gem "m"
30
30
  gem "minitest-sprint", "~> 1.0"
31
31
  gem "minitest", "5.15.0"
32
- gem "mocha", "~> 1.1"
32
+ gem "mocha", "~> 2.1"
33
33
  gem "nokogiri", "~> 1.9"
34
34
  gem "pry-byebug"
35
35
  gem "pry", "~> 0.10"
36
36
  gem "rake", ">= 10"
37
- gem "ruby-progressbar", "~> 1.8"
38
37
  gem "simplecov", "~> 0.21"
39
38
  gem "simplecov_json_formatter"
40
39
  gem "webmock", "~> 3.0"
@@ -48,20 +47,3 @@ end
48
47
  group :deploy do
49
48
  gem "inquirer"
50
49
  end
51
-
52
- group :kitchen do
53
- gem "berkshelf"
54
-
55
- # Chef 18 requires ruby 3
56
- if Gem.ruby_version >= Gem::Version.new("3.0.0")
57
- gem "chef", ">= 17.0"
58
- else
59
- # Ruby 2.7 presumably - TODO remove this when 2.7 is sunsetted
60
- gem "chef", "~> 16.0"
61
- end
62
-
63
- gem "test-kitchen", ">= 2.8"
64
- gem "kitchen-inspec", ">= 2.0"
65
- gem "kitchen-dokken", ">= 2.11"
66
- gem "git"
67
- end
data/inspec-core.gemspec CHANGED
@@ -25,13 +25,15 @@ Gem::Specification.new do |spec|
25
25
  # Implementation dependencies
26
26
  spec.add_dependency "chef-telemetry", "~> 1.0", ">= 1.0.8" # 1.0.8+ removes the http dep
27
27
  spec.add_dependency "license-acceptance", ">= 0.2.13", "< 3.0"
28
- spec.add_dependency "thor", ">= 0.20", "< 2.0"
28
+ # TODO: We should remove the thor pinning in next upcoming releases currently it's breaking our unit test in cli_args_test for aliases due to
29
+ # recent changes made in thor library REF: https://github.com/rails/thor/releases/tag/v1.3.0 & https://github.com/rails/thor/pull/800
30
+ spec.add_dependency "thor", ">= 0.20", "< 1.3.0"
29
31
  spec.add_dependency "method_source", ">= 0.8", "< 2.0"
30
32
  spec.add_dependency "rubyzip", ">= 1.2.2", "< 3.0"
31
- spec.add_dependency "rspec", ">= 3.9", "<= 3.11"
33
+ spec.add_dependency "rspec", ">= 3.9", "<= 3.12"
32
34
  spec.add_dependency "rspec-its", "~> 1.2"
33
35
  spec.add_dependency "pry", "~> 0.13"
34
- spec.add_dependency "hashie", ">= 3.4", "< 5.0"
36
+ spec.add_dependency "hashie", ">= 3.4", "< 6.0"
35
37
  spec.add_dependency "mixlib-log", "~> 3.0"
36
38
  spec.add_dependency "sslshake", "~> 1.2"
37
39
  spec.add_dependency "parallel", "~> 1.9"
@@ -41,7 +43,7 @@ Gem::Specification.new do |spec|
41
43
  spec.add_dependency "tty-prompt", "~> 0.17"
42
44
  spec.add_dependency "tomlrb", ">= 1.2", "< 2.1"
43
45
  spec.add_dependency "addressable", "~> 2.4"
44
- spec.add_dependency "parslet", ">= 1.5", "< 2.0" # Pinned < 2.0, see #5389
46
+ spec.add_dependency "parslet", ">= 1.5", "< 3.0" # Pinned < 2.0, see #5389
45
47
  spec.add_dependency "semverse", "~> 3.0"
46
48
  spec.add_dependency "multipart-post", "~> 2.0"
47
49
 
@@ -100,11 +100,11 @@ module Inspec
100
100
  option :ssl, type: :boolean,
101
101
  desc: "Use SSL for transport layer encryption (WinRM)."
102
102
  option :ssl_peer_fingerprint, type: :string,
103
- desc: "Specify peer fingerprint for SSL authentication, used in lieu of certificates"
103
+ desc: "Specify SSL peer fingerprint in place of certificates for SSL authentication (WinRM)."
104
104
  option :self_signed, type: :boolean,
105
105
  desc: "Allow remote scans with self-signed certificates (WinRM)."
106
106
  option :ca_trust_file, type: :string,
107
- desc: "Specify CA trust file for SSL authentication"
107
+ desc: "Specify CA certificate required for SSL authentication (WinRM)."
108
108
  option :client_cert, type: :string,
109
109
  desc: "Specify client certificate for SSL authentication"
110
110
  option :client_key, type: :string,
@@ -121,32 +121,32 @@ module Inspec
121
121
  desc: "Read configuration from JSON file (`-` reads from stdin)."
122
122
  option :json_config, type: :string, hide: true
123
123
  option :proxy_command, type: :string,
124
- desc: "Specifies the command to use to connect to the server"
124
+ desc: "Specifies the command to use to connect to the server."
125
125
  option :bastion_host, type: :string,
126
- desc: "Specifies the bastion host if applicable"
126
+ desc: "Specifies the bastion host if applicable."
127
127
  option :bastion_user, type: :string,
128
- desc: "Specifies the bastion user if applicable"
128
+ desc: "Specifies the bastion user if applicable."
129
129
  option :bastion_port, type: :string,
130
- desc: "Specifies the bastion port if applicable"
130
+ desc: "Specifies the bastion port if applicable."
131
131
  option :insecure, type: :boolean, default: false,
132
- desc: "Disable SSL verification on select targets"
132
+ desc: "Disable SSL verification on select targets."
133
133
  option :target_id, type: :string,
134
- desc: "Provide a ID which will be included on reports - deprecated"
134
+ desc: "Provide an ID which will be included on reports - deprecated"
135
135
  option :winrm_shell_type, type: :string, default: "powershell",
136
- desc: "Specify a shell type for winrm (eg. 'elevated' or 'powershell')"
136
+ desc: "Specify which shell type to use (powershell, elevated, or cmd), which defaults to powershell (WinRM)."
137
137
  option :docker_url, type: :string,
138
- desc: "Provides path to Docker API endpoint (Docker)"
138
+ desc: "Provides path to Docker API endpoint (Docker). Defaults to unix:///var/run/docker.sock on Unix systems and tcp://localhost:2375 on Windows."
139
139
  option :ssh_config_file, type: :array,
140
- desc: "A list of paths to the ssh config file, e.g ~/.ssh/config or /etc/ssh/ssh_config"
140
+ desc: "A list of paths to the ssh config file, e.g ~/.ssh/config or /etc/ssh/ssh_config."
141
141
  option :podman_url, type: :string,
142
- desc: "Provides path to Podman API endpoint"
142
+ desc: "Provides the path to the Podman API endpoint. Defaults to unix:///run/user/$UID/podman/podman.sock for rootless container, unix:///run/podman/podman.sock for rootful container (for this you need to execute inspec as root user)."
143
143
  end
144
144
 
145
145
  def self.profile_options
146
146
  option :profiles_path, type: :string,
147
147
  desc: "Folder which contains referenced profiles."
148
148
  option :vendor_cache, type: :string,
149
- desc: "Use the given path for caching dependencies. (default: ~/.inspec/cache)"
149
+ desc: "Use the given path for caching dependencies, (default: ~/.inspec/cache)."
150
150
  option :auto_install_gems, type: :boolean, default: false,
151
151
  desc: "Auto installs gem dependencies of the profile or resource pack."
152
152
  end
@@ -174,7 +174,7 @@ module Inspec
174
174
  option :input, type: :array, banner: "name1=value1 name2=value2",
175
175
  desc: "Specify one or more inputs directly on the command line, as --input NAME=VALUE. Accepts single-quoted YAML and JSON structures."
176
176
  option :input_file, type: :array,
177
- desc: "Load one or more input files, a YAML file with values for the profile to use"
177
+ desc: "Load one or more input files, a YAML file with values for the profile to use."
178
178
  option :waiver_file, type: :array,
179
179
  desc: "Load one or more waiver files."
180
180
  option :attrs, type: :array,
@@ -182,14 +182,14 @@ module Inspec
182
182
  option :create_lockfile, type: :boolean,
183
183
  desc: "Write out a lockfile based on this execution (unless one already exists)"
184
184
  option :backend_cache, type: :boolean,
185
- desc: "Allow caching for backend command output. (default: true)"
185
+ desc: "Allow caching for backend command output. (default: true)."
186
186
  option :show_progress, type: :boolean,
187
187
  desc: "Show progress while executing tests."
188
188
  option :distinct_exit, type: :boolean, default: true,
189
189
  desc: "Exit with code 101 if any tests fail, and 100 if any are skipped (default). If disabled, exit 0 on skips and 1 for failures."
190
190
  option :silence_deprecations, type: :array,
191
191
  banner: "[all]|[GROUP GROUP...]",
192
- desc: "Suppress deprecation warnings. See install_dir/etc/deprecations.json for list of GROUPs or use 'all'."
192
+ desc: "Suppress deprecation warnings. See install_dir/etc/deprecations.json for a list of GROUPs or use 'all'."
193
193
  option :diff, type: :boolean, default: true,
194
194
  desc: "Use --no-diff to suppress 'diff' output of failed textual test results."
195
195
  option :sort_results_by, type: :string, default: "file", banner: "--sort-results-by=none|control|file|random",
@@ -197,7 +197,7 @@ module Inspec
197
197
  option :filter_empty_profiles, type: :boolean, default: false,
198
198
  desc: "Filter empty profiles (profiles without controls) from the report."
199
199
  option :filter_waived_controls, type: :boolean,
200
- desc: "Do not execute waived controls in InSpec at all. Must use with --waiver-file. Ignores `run` setting of waiver file."
200
+ desc: "Do not execute waived controls in InSpec at all. Must use with --waiver-file. Ignores the `run` setting of the waiver file."
201
201
  option :retain_waiver_data, type: :boolean,
202
202
  desc: "EXPERIMENTAL: Only works in conjunction with --filter-waived-controls, retains waiver data about controls that were skipped"
203
203
  option :command_timeout, type: :numeric,
data/lib/inspec/cli.rb CHANGED
@@ -61,9 +61,9 @@ class Inspec::InspecCLI < Inspec::BaseCLI
61
61
  require "license_acceptance/cli_flags/thor"
62
62
  include LicenseAcceptance::CLIFlags::Thor
63
63
 
64
- desc "json PATH", "read all tests in PATH and generate a JSON summary"
64
+ desc "json PATH", "read all tests in the PATH and generate a JSON summary."
65
65
  option :output, aliases: :o, type: :string,
66
- desc: "Save the created profile to a path"
66
+ desc: "Save the created profile to a path."
67
67
  option :controls, type: :array,
68
68
  desc: "A list of controls to include. Ignore all other tests."
69
69
  option :tags, type: :array,
@@ -81,7 +81,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
81
81
  option :format, type: :string,
82
82
  desc: "The output format to use: json, raw, yaml. If valid format is not provided then it will use the default for the given 'what'."
83
83
  option :output, aliases: :o, type: :string,
84
- desc: "Save the created output to a path"
84
+ desc: "Save the created output to a path."
85
85
  option :controls, type: :array,
86
86
  desc: "For --what=profile, a list of controls to include. Ignore all other tests."
87
87
  option :tags, type: :array,
@@ -145,9 +145,11 @@ class Inspec::InspecCLI < Inspec::BaseCLI
145
145
  pretty_handle_exception(e)
146
146
  end
147
147
 
148
- desc "check PATH", "verify all tests at the specified PATH"
148
+ desc "check PATH", "Verify the metadata in the `inspec.yml` file,\
149
+ verify that control blocks have the correct fields (title, description, impact),\
150
+ and define that all controls have visible tests and the controls are not using deprecated InSpec DSL code"
149
151
  option :format, type: :string,
150
- desc: "The output format to use doc (default), json. If valid format is not provided then it will use the default."
152
+ desc: "The output format to use. Valid values: `json` and `doc`. Default value: `doc`."
151
153
  option :with_cookstyle, type: :boolean,
152
154
  desc: "Enable or disable cookstyle checks.", default: false
153
155
  profile_options
@@ -228,10 +230,10 @@ class Inspec::InspecCLI < Inspec::BaseCLI
228
230
  vendor_deps(path, o)
229
231
  end
230
232
 
231
- desc "archive PATH", "archive a profile to tar.gz (default) or zip"
233
+ desc "archive PATH", "Archive a profile to a tar file (default) or zip file."
232
234
  profile_options
233
235
  option :output, aliases: :o, type: :string,
234
- desc: "Save the archive to a path"
236
+ desc: "Save the archive to a path."
235
237
  option :zip, type: :boolean, default: false,
236
238
  desc: "Generates a zip archive."
237
239
  option :tar, type: :boolean, default: false,
@@ -242,6 +244,10 @@ class Inspec::InspecCLI < Inspec::BaseCLI
242
244
  desc: "Fallback to using local archives if fetching fails."
243
245
  option :ignore_errors, type: :boolean, default: false,
244
246
  desc: "Ignore profile warnings."
247
+ option :check, type: :boolean, default: false,
248
+ desc: "Run profile check before archiving."
249
+ option :export, type: :boolean, default: false,
250
+ desc: "Export the profile to inspec.json and include in archive"
245
251
  def archive(path, log_level = nil)
246
252
  o = config
247
253
  diagnose(o)
@@ -262,7 +268,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
262
268
  o[:logger].warn "Archiving a profile that contains gem dependencies, but InSpec cannot package gems with the profile! Please archive your ~/.inspec/gems directory separately."
263
269
  end
264
270
 
265
- result = profile.check
271
+ result = profile.check if o[:check]
266
272
 
267
273
  if result && !o[:ignore_errors] == false
268
274
  o[:logger].info "Profile check failed. Please fix the profile before generating an archive."
@@ -275,14 +281,10 @@ class Inspec::InspecCLI < Inspec::BaseCLI
275
281
  pretty_handle_exception(e)
276
282
  end
277
283
 
278
- desc "exec LOCATIONS", "Run all tests at LOCATIONS."
284
+ desc "exec LOCATIONS", "Run all test files at the specified locations."
279
285
  long_desc <<~EOT
280
- Run all test files at the specified LOCATIONS.
281
-
282
- Loads the given profile(s) and fetches their dependencies if needed. Then
283
- connects to the target and executes any controls contained in the profiles.
284
- One or more reporters are used to generate output.
285
-
286
+ The subcommand loads the given profiles, fetches their dependencies if needed, then connects to the target and executes any controls in the profiles.
287
+ One or more reporters are used to generate the output.
286
288
  ```
287
289
  Exit codes:
288
290
  0 Normal exit, all tests passed
@@ -296,7 +298,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
296
298
 
297
299
  Below are some examples of using `exec` with different test LOCATIONS:
298
300
 
299
- Automate:
301
+ Chef Automate:
300
302
  ```
301
303
  #{Inspec::Dist::EXEC_NAME} automate login
302
304
  #{Inspec::Dist::EXEC_NAME} exec compliance://username/linux-baseline
@@ -306,7 +308,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
306
308
  #{Inspec::Dist::EXEC_NAME} compliance login
307
309
  ```
308
310
 
309
- Supermarket:
311
+ Chef Supermarket:
310
312
  ```
311
313
  #{Inspec::Dist::EXEC_NAME} exec supermarket://username/linux-baseline
312
314
  ```
@@ -343,12 +345,12 @@ class Inspec::InspecCLI < Inspec::BaseCLI
343
345
  #{Inspec::Dist::EXEC_NAME} exec https://github.com/dev-sec/linux-baseline.git
344
346
  ```
345
347
 
346
- Web hosted fileshare (also supports .zip):
348
+ Web hosted file (also supports .zip):
347
349
  ```
348
350
  #{Inspec::Dist::EXEC_NAME} exec https://webserver/linux-baseline.tar.gz
349
351
  ```
350
352
 
351
- Web hosted fileshare with basic authentication (supports .zip):
353
+ Web hosted file with basic authentication (supports .zip):
352
354
  ```
353
355
  #{Inspec::Dist::EXEC_NAME} exec https://username:password@webserver/linux-baseline.tar.gz
354
356
  ```
@@ -371,7 +373,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
371
373
  pretty_handle_exception(e)
372
374
  end
373
375
 
374
- desc "detect", "detect the target OS"
376
+ desc "detect", "detects the target OS."
375
377
  target_options
376
378
  option :format, type: :string
377
379
  def detect
@@ -396,7 +398,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
396
398
  pretty_handle_exception(e)
397
399
  end
398
400
 
399
- desc "shell", "open an interactive debugging shell"
401
+ desc "shell", "open an interactive debugging shell."
400
402
  target_options
401
403
  option :command, aliases: :c,
402
404
  desc: "A single command string to run instead of launching the shell"
@@ -455,7 +457,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
455
457
  pretty_handle_exception(e)
456
458
  end
457
459
 
458
- desc "env", "Output shell-appropriate completion configuration"
460
+ desc "env", "Outputs shell-appropriate completion configuration."
459
461
  def env(shell = nil)
460
462
  p = Inspec::EnvPrinter.new(self.class, shell)
461
463
  p.print_and_exit!
@@ -481,7 +483,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
481
483
  puts Inspec::Telemetry::RunContextProbe.guess_run_context
482
484
  end
483
485
 
484
- desc "version", "prints the version of this tool"
486
+ desc "version", "prints the version of this tool."
485
487
  option :format, type: :string
486
488
  def version
487
489
  if config["format"] == "json"
@@ -495,7 +497,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
495
497
 
496
498
  desc "clear_cache", "clears the InSpec cache. Useful for debugging."
497
499
  option :vendor_cache, type: :string,
498
- desc: "Use the given path for caching dependencies. (default: ~/.inspec/cache)"
500
+ desc: "Use the given path for caching dependencies, (default: `~/.inspec/cache`)."
499
501
  def clear_cache
500
502
  o = config
501
503
  configure_logger(o)
@@ -516,7 +518,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
516
518
  end
517
519
 
518
520
  def run_command(opts)
519
- runner = Inspec::Runner.new(Inspec::Config.new(opts))
521
+ runner = Inspec::Runner.new(opts)
520
522
  res = runner.eval_with_virtual_profile(opts[:command])
521
523
  runner.load
522
524
 
data/lib/inspec/config.rb CHANGED
@@ -448,6 +448,13 @@ module Inspec
448
448
  # Reporter options may be defined top-level.
449
449
  options.merge!(config_file_reporter_options)
450
450
 
451
+ if @cli_opts["reporter"]
452
+ # Add reporter_cli_opts in options to capture reporter cli opts separately
453
+ options.merge!({ "reporter_cli_opts" => @cli_opts["reporter"] })
454
+ # Delete reporter from cli_opts to avoid direct merging of reporter info of cli and config
455
+ @cli_opts.delete("reporter")
456
+ end
457
+
451
458
  # Highest precedence: merge in any options defined via the CLI
452
459
  options.merge!(@cli_opts)
453
460
 
@@ -476,13 +483,13 @@ module Inspec
476
483
  end
477
484
 
478
485
  def finalize_parse_reporters(options) # rubocop:disable Metrics/AbcSize
479
- # default to cli report for ad-hoc runners
480
- options["reporter"] = ["cli"] if options["reporter"].nil?
486
+ # Default to cli report for ad-hoc runners
487
+ options["reporter_cli_opts"] = ["cli"] if (options["reporter"].nil? || options["reporter"].empty?) && options["reporter_cli_opts"].nil?
481
488
 
482
- # parse out cli to proper report format
483
- if options["reporter"].is_a?(Array)
489
+ # Parse out reporter_cli_opts to proper report format
490
+ if options["reporter_cli_opts"].is_a?(Array)
484
491
  reports = {}
485
- options["reporter"].each do |report|
492
+ options["reporter_cli_opts"].each do |report|
486
493
  reporter_name, destination = report.split(":", 2)
487
494
  if destination.nil? || destination.strip == "-"
488
495
  reports[reporter_name] = { "stdout" => true }
@@ -494,7 +501,12 @@ module Inspec
494
501
  reports[reporter_name]["target_id"] = options["target_id"] if options["target_id"]
495
502
  end
496
503
  end
497
- options["reporter"] = reports
504
+
505
+ if options["reporter"].nil? || options["reporter"].empty?
506
+ options["reporter"] = reports
507
+ else
508
+ options["reporter"].merge!(reports)
509
+ end
498
510
  end
499
511
 
500
512
  # add in stdout if not specified
@@ -507,6 +519,10 @@ module Inspec
507
519
  end
508
520
 
509
521
  validate_reporters!(options["reporter"])
522
+
523
+ # Delete reporter_cli_opts after graceful merging of cli and config reporters
524
+ options.delete("reporter_cli_opts")
525
+
510
526
  options
511
527
  end
512
528
 
@@ -548,15 +564,12 @@ module Inspec
548
564
  class Defaults
549
565
  DEFAULTS = {
550
566
  exec: {
551
- "reporter" => ["cli"],
552
567
  "show_progress" => false,
553
568
  "color" => true,
554
569
  "create_lockfile" => true,
555
570
  "backend_cache" => true,
556
571
  },
557
- shell: {
558
- "reporter" => ["cli"],
559
- },
572
+ shell: {},
560
573
  }.freeze
561
574
 
562
575
  def self.for_command(command_name)
@@ -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 ? "#{d.name}-#{d.source_version}" : "#{d.name}") rescue "#{d.name}"
29
+ key_name = (d.source_version.blank? ? "#{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 ? "#{d.name}-#{d.source_version}" : "#{d.name}") rescue d.name
45
+ key_name = (d.source_version.blank? ? "#{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
@@ -91,14 +91,12 @@ module Inspec::DSL
91
91
  if profile_version
92
92
  new_profile_id = "#{profile_id}-#{profile_version}"
93
93
  else
94
- # This scary regex is used to match version following semantic Versioning (SemVer). Thanks to https://ihateregex.io/expr/semver/
95
- regex_for_semver = /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?/
96
- dependencies.list.keys.each do |key|
94
+ dependencies.list.each do |key, value|
97
95
  # 1. Fetching VERSION from a profile dependency name which is in a format NAME-VERSION.
98
96
  # 2. Matching original profile dependency name with profile name used with include or require control DSL.
99
- fetching_semver = key.match(regex_for_semver).to_s
100
- unless fetching_semver.nil? || fetching_semver.empty?
101
- profile_id_key = key.split("-#{fetching_semver}")[0]
97
+ source_version = value.source_version
98
+ unless source_version.blank?
99
+ profile_id_key = key.split("-#{source_version}")[0]
102
100
  new_profile_id = key if profile_id_key == profile_id
103
101
  end
104
102
  end
@@ -41,6 +41,7 @@ module Inspec::Fetcher
41
41
  @ref = opts[:ref]
42
42
  @remote_url = expand_local_path(remote_url)
43
43
  @repo_directory = nil
44
+ @resolved_ref = nil
44
45
  @relative_path = opts[:relative_path] if opts[:relative_path] && !opts[:relative_path].empty?
45
46
  end
46
47
 
@@ -70,7 +71,7 @@ module Inspec::Fetcher
70
71
  if @relative_path
71
72
  perform_relative_path_fetch(destination_path, working_dir)
72
73
  else
73
- Inspec::Log.debug("Checkout of #{resolved_ref} successful. " \
74
+ Inspec::Log.debug("Checkout of #{resolved_ref.nil? ? @remote_url : resolved_ref} successful. " \
74
75
  "Moving checkout to #{destination_path}")
75
76
  FileUtils.cp_r(working_dir + "/.", destination_path)
76
77
  end
@@ -80,14 +81,14 @@ module Inspec::Fetcher
80
81
  end
81
82
 
82
83
  def perform_relative_path_fetch(destination_path, working_dir)
83
- Inspec::Log.debug("Checkout of #{resolved_ref} successful. " \
84
+ Inspec::Log.debug("Checkout of #{resolved_ref.nil? ? @remote_url : resolved_ref} successful. " \
84
85
  "Moving #{@relative_path} to #{destination_path}")
85
86
  unless File.exist?("#{working_dir}/#{@relative_path}")
86
87
  # Cleanup the destination path - otherwise we'll have an empty dir
87
88
  # in the cache, which is enough to confuse the cache reader
88
89
  # This is a courtesy, assuming we're writing to the cache; if we're
89
90
  # vendoring to something more complex, don't bother.
90
- FileUtils.rmdir(destination_path) if Dir.empty?(destination_path)
91
+ FileUtils.rm_r(destination_path) if Dir.exist?(destination_path)
91
92
 
92
93
  raise Inspec::FetcherFailure, "Cannot find relative path '#{@relative_path}' " \
93
94
  "within profile in git repo specified by '#{@remote_url}'"
@@ -96,9 +97,16 @@ module Inspec::Fetcher
96
97
  end
97
98
 
98
99
  def cache_key
99
- return resolved_ref unless @relative_path
100
-
101
- OpenSSL::Digest.hexdigest("SHA256", resolved_ref + @relative_path)
100
+ cache_key = if @relative_path && !resolved_ref.nil?
101
+ OpenSSL::Digest.hexdigest("SHA256", resolved_ref + @relative_path)
102
+ elsif @relative_path && resolved_ref.nil?
103
+ OpenSSL::Digest.hexdigest("SHA256", @remote_url + @relative_path)
104
+ elsif resolved_ref.nil?
105
+ OpenSSL::Digest.hexdigest("SHA256", @remote_url)
106
+ else
107
+ resolved_ref
108
+ end
109
+ cache_key
102
110
  end
103
111
 
104
112
  def archive_path
@@ -106,7 +114,11 @@ module Inspec::Fetcher
106
114
  end
107
115
 
108
116
  def resolved_source
109
- source = { git: @remote_url, ref: resolved_ref }
117
+ if resolved_ref.nil?
118
+ source = { git: @remote_url }
119
+ else
120
+ source = { git: @remote_url, ref: resolved_ref }
121
+ end
110
122
  source[:relative_path] = @relative_path if @relative_path
111
123
  source
112
124
  end
@@ -125,33 +137,27 @@ module Inspec::Fetcher
125
137
  elsif @tag
126
138
  resolve_ref(@tag)
127
139
  else
128
- resolve_ref(default_ref)
140
+ resolve_ref
129
141
  end
130
142
  end
131
143
 
132
- def default_ref
133
- command_string = "git remote show #{@remote_url}"
144
+ def resolve_ref(ref_name = nil)
145
+ command_string = if ref_name.nil?
146
+ # Running git ls-remote command helps to raise error if git URL is invalid and avoids cache_key creation
147
+ "git ls-remote \"#{@remote_url}\""
148
+ else
149
+ "git ls-remote \"#{@remote_url}\" \"#{ref_name}*\""
150
+ end
134
151
  cmd = shellout(command_string)
135
- unless cmd.exitstatus == 0
136
- raise(Inspec::FetcherFailure, "Profile git dependency failed with default reference - #{@remote_url} - error running '#{command_string}': #{cmd.stderr}")
152
+ raise(Inspec::FetcherFailure, "Profile git dependency failed for #{@remote_url} - error running '#{command_string}': #{cmd.stderr}") unless cmd.exitstatus == 0
153
+
154
+ if ref_name.nil?
155
+ ref = nil
137
156
  else
138
- ref = cmd.stdout.lines.detect { |l| l.include? "HEAD branch:" }&.split(":")&.last&.strip
157
+ ref = parse_ls_remote(cmd.stdout, ref_name)
139
158
  unless ref
140
- raise(Inspec::FetcherFailure, "Profile git dependency failed with default reference - #{@remote_url} - error running '#{command_string}': NULL reference")
159
+ raise Inspec::FetcherFailure, "Profile git dependency failed - unable to resolve #{ref_name} to a specific git commit for #{@remote_url}"
141
160
  end
142
-
143
- ref
144
- end
145
- end
146
-
147
- def resolve_ref(ref_name)
148
- command_string = "git ls-remote \"#{@remote_url}\" \"#{ref_name}*\""
149
- cmd = shellout(command_string)
150
- raise(Inspec::FetcherFailure, "Profile git dependency failed for #{@remote_url} - error running '#{command_string}': #{cmd.stderr}") unless cmd.exitstatus == 0
151
-
152
- ref = parse_ls_remote(cmd.stdout, ref_name)
153
- unless ref
154
- raise Inspec::FetcherFailure, "Profile git dependency failed - unable to resolve #{ref_name} to a specific git commit for #{@remote_url}"
155
161
  end
156
162
 
157
163
  ref
@@ -200,7 +206,14 @@ module Inspec::Fetcher
200
206
 
201
207
  def checkout(dir = @repo_directory)
202
208
  clone(dir)
203
- git_cmd("checkout #{resolved_ref}", dir)
209
+ # In case of branch, tag or git reference is not provided by User the resolved_ref will always be nil
210
+ # and will always checkout the default HEAD branch, else it will checkout specific branch, tag or git reference.
211
+ if resolved_ref.nil?
212
+ git_cmd("checkout", dir)
213
+ else
214
+ git_cmd("checkout #{resolved_ref}", dir)
215
+ end
216
+
204
217
  @repo_directory
205
218
  end
206
219
 
@@ -208,6 +221,8 @@ module Inspec::Fetcher
208
221
  cmd = shellout("git #{cmd}", cwd: dir)
209
222
  cmd.error!
210
223
  cmd.status
224
+ rescue Mixlib::ShellOut::ShellCommandFailed => e
225
+ raise Inspec::FetcherFailure, "Error while running git command. #{e.message} "
211
226
  rescue Errno::ENOENT
212
227
  raise Inspec::FetcherFailure, "Profile git dependency failed for #{@remote_url} - to use git sources, you must have git installed."
213
228
  end
@@ -160,7 +160,7 @@ module Inspec::Formatters
160
160
  end
161
161
 
162
162
  # added this additionally because stats summary is also used for determining exit code in runner rspec
163
- skipped += 1 if control[:results].any? { |r| r[:status] == "skipped" }
163
+ skipped += 1 if control[:results] && (control[:results].any? { |r| r[:status] == "skipped" })
164
164
 
165
165
  end
166
166
  total = error + not_applicable + not_reviewed + failed + passed
@@ -383,24 +383,25 @@ module Inspec
383
383
  @runner_context
384
384
  end
385
385
 
386
- def collect_gem_dependencies(profile_context)
386
+ # This collects the gem dependencies data from parent and its dependent profiles
387
+ def collect_gem_dependencies
387
388
  gem_dependencies = []
388
- all_profiles = []
389
- profile_context.dependencies.list.values.each do |requirement|
390
- all_profiles << requirement.profile
391
- end
392
- all_profiles << self
393
- all_profiles.each do |profile|
389
+ # This collects the dependent profiles gem dependencies if any
390
+ locked_dependencies.dep_list.each do |_name, dep|
391
+ profile = dep.profile
394
392
  gem_dependencies << profile.metadata.gem_dependencies unless profile.metadata.gem_dependencies.empty?
395
393
  end
394
+ # Appends the parent profile gem dependencies which are available through metadata
395
+ gem_dependencies << metadata.gem_dependencies unless metadata.gem_dependencies.empty?
396
396
  gem_dependencies.flatten.uniq
397
397
  end
398
398
 
399
399
  # Loads the required gems specified in the Profile's metadata file from default inspec gems path i.e. ~/.inspec/gems
400
400
  # else installs and loads them.
401
401
  def load_gem_dependencies
402
- gem_dependencies = collect_gem_dependencies(load_libraries)
402
+ gem_dependencies = collect_gem_dependencies
403
403
  gem_dependencies.each do |gem_data|
404
+
404
405
  dependency_loader = DependencyLoader.new
405
406
  if dependency_loader.gem_version_installed?(gem_data[:name], gem_data[:version]) ||
406
407
  dependency_loader.gem_installed?(gem_data[:name])
@@ -681,7 +682,6 @@ module Inspec
681
682
  end
682
683
 
683
684
  # generates a archive of a folder profile
684
- # assumes that the profile was checked before
685
685
  def archive(opts)
686
686
  # check if file exists otherwise overwrite the archive
687
687
  dst = archive_name(opts)
@@ -698,31 +698,34 @@ module Inspec
698
698
  # TODO ignore all .files, but add the files to debug output
699
699
 
700
700
  # Generate temporary inspec.json for archive
701
- Inspec::Utils::JsonProfileSummary.produce_json(
702
- info: info,
703
- write_path: "#{root_path}inspec.json",
704
- suppress_output: true
705
- )
701
+ if opts[:export]
702
+ Inspec::Utils::JsonProfileSummary.produce_json(
703
+ info: info, # TODO: conditionalize and call info_from_parse
704
+ write_path: "#{root_path}inspec.json",
705
+ suppress_output: true
706
+ )
707
+ end
706
708
 
707
709
  # display all files that will be part of the archive
708
710
  @logger.debug "Add the following files to archive:"
709
711
  files.each { |f| @logger.debug " " + f }
710
- @logger.debug " inspec.json"
712
+ @logger.debug " inspec.json" if opts[:export]
711
713
 
714
+ archive_files = opts[:export] ? files.push("inspec.json") : files
712
715
  if opts[:zip]
713
716
  # generate zip archive
714
717
  require "inspec/archive/zip"
715
718
  zag = Inspec::Archive::ZipArchiveGenerator.new
716
- zag.archive(root_path, files.push("inspec.json"), dst)
719
+ zag.archive(root_path, archive_files, dst)
717
720
  else
718
721
  # generate tar archive
719
722
  require "inspec/archive/tar"
720
723
  tag = Inspec::Archive::TarArchiveGenerator.new
721
- tag.archive(root_path, files.push("inspec.json"), dst)
724
+ tag.archive(root_path, archive_files, dst)
722
725
  end
723
726
 
724
727
  # Cleanup
725
- FileUtils.rm_f("#{root_path}inspec.json")
728
+ FileUtils.rm_f("#{root_path}inspec.json") if opts[:export]
726
729
 
727
730
  @logger.info "Finished archive generation."
728
731
  true
@@ -828,10 +831,12 @@ module Inspec
828
831
  return Pathname.new(name)
829
832
  end
830
833
 
831
- name = params[:name] ||
834
+ # Using metadata to fetch basic info of name and version
835
+ metadata = @source_reader.metadata.params
836
+ name = metadata[:name] ||
832
837
  raise("Cannot create an archive without a profile name! Please "\
833
838
  "specify the name in metadata or use --output to create the archive.")
834
- version = params[:version] ||
839
+ version = metadata[:version] ||
835
840
  raise("Cannot create an archive without a profile version! Please "\
836
841
  "specify the version in metadata or use --output to create the archive.")
837
842
  ext = opts[:zip] ? "zip" : "tar.gz"
@@ -319,15 +319,9 @@ module Inspec::Resources
319
319
  return nil
320
320
  end
321
321
 
322
- resolve_ipv4 = resolve_ipv4.inject(:merge) if resolve_ipv4.is_a?(Array)
323
-
324
322
  # Append the ipv4 addresses
325
- resolve_ipv4.each_value do |ip|
326
- matched = ip.to_s.chomp.match(Resolv::IPv4::Regex)
327
- next if matched.nil? || addresses.include?(matched.to_s)
328
-
329
- addresses << matched.to_s
330
- end
323
+ resolve_ipv4 = [resolve_ipv4] unless resolve_ipv4.is_a?(Array)
324
+ resolve_ipv4.each { |entry| addresses << entry["IPAddress"] }
331
325
 
332
326
  # -Type AAAA is the DNS query for IPv6 server Address.
333
327
  cmd = inspec.command("Resolve-DnsName –Type AAAA #{hostname} | ConvertTo-Json")
@@ -337,15 +331,9 @@ module Inspec::Resources
337
331
  return nil
338
332
  end
339
333
 
340
- resolve_ipv6 = resolve_ipv6.inject(:merge) if resolve_ipv6.is_a?(Array)
341
-
342
334
  # Append the ipv6 addresses
343
- resolve_ipv6.each_value do |ip|
344
- matched = ip.to_s.chomp.match(Resolv::IPv6::Regex)
345
- next if matched.nil? || addresses.include?(matched.to_s)
346
-
347
- addresses << matched.to_s
348
- end
335
+ resolve_ipv6 = [resolve_ipv6] unless resolve_ipv6.is_a?(Array)
336
+ resolve_ipv6.each { |entry| addresses << entry["IPAddress"] }
349
337
 
350
338
  addresses
351
339
  end
@@ -304,11 +304,11 @@ module Inspec::Resources
304
304
  # Insecure not supported simply https://stackoverflow.com/questions/11696944/powershell-v3-invoke-webrequest-https-error
305
305
  cmd << "-MaximumRedirection #{max_redirects}" unless max_redirects.nil?
306
306
  request_headers["Authorization"] = """ '\"Basic ' + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(\"#{username}:#{password}\")) +'\"' """ unless username.nil? || password.nil?
307
- request_header_string = nil
307
+ request_header_array = []
308
308
  request_headers.each do |k, v|
309
- request_header_string << " #{k} = #{v}"
309
+ request_header_array << " '#{k}' = '#{v}'"
310
310
  end
311
- cmd << "-Headers @{#{request_header_string.join(";")}}" unless request_header_string.nil?
311
+ cmd << "-Headers @{#{request_header_array.join(";")}}" unless request_header_array.empty?
312
312
  if params.nil?
313
313
  cmd << "'#{url}'"
314
314
  else
@@ -84,6 +84,11 @@ module Inspec::Resources
84
84
  options[:ssl_cert] = @ssl_cert unless @ssl_cert.nil?
85
85
  options[:ssl_ca_cert] = @ssl_ca_cert unless @ssl_ca_cert.nil?
86
86
 
87
+ # Setting the logger level to INFO as mongo gem version 2.13.2 is using DEBUG as the log level Ref: https://github.com/mongodb/mongo-ruby-driver/blob/v2.13.2/lib/mongo/logger.rb#L79
88
+ # Latest version of the mongo gem don't have this issue as it set to INFO level Ref: https://github.com/mongodb/mongo-ruby-driver/blob/master/lib/mongo/logger.rb#L82
89
+ # We pinned the version to 2.13.2 as the latest version of the mongo gem has broken symlink https://jira.mongodb.org/browse/RUBY-2546 which causes omnibus build failure.
90
+ # Once we get the latest version working we can remove logger level set here.
91
+ Mongo::Logger.logger.level = Logger::INFO
87
92
  @client = Mongo::Client.new([ "#{host}:#{port}" ], options)
88
93
 
89
94
  rescue => e
@@ -0,0 +1,251 @@
1
+ require "inspec/resources/command"
2
+ require "json" unless defined?(JSON)
3
+
4
+ # @see https://wiki.nftables.org/
5
+ # @see https://www.netfilter.org/projects/nftables/manpage.html
6
+
7
+ # rubocop:disable Style/ClassVars
8
+
9
+ module Inspec::Resources
10
+ class NfTables < Inspec.resource(1)
11
+ name "nftables"
12
+ supports platform: "linux"
13
+ desc "Use the nftables InSpec audit resource to test rules and sets that are defined in nftables, which maintains tables of IP packet filtering rules. There may be more than one table. Each table contains one (or more) chains. A chain is a list of rules that match packets. When the rule matches, the rule defines what target to assign to the packet."
14
+ example <<~EXAMPLE
15
+ describe nftables(family:'inet', table:'filter', chain: 'INPUT') do
16
+ its('type') { should eq 'filter' }
17
+ its('hook') { should eq 'input' }
18
+ its('prio') { should eq 0 } # filter
19
+ its('policy') { should eq 'drop' }
20
+ it { should have_rule('tcp dport { 22, 80, 443 } accept') }
21
+ end
22
+ describe nftables(family: 'inet', table: 'filter', set: 'OPEN_PORTS') do
23
+ its('type') { should eq 'ipv4_addr . inet_proto . inet_service' }
24
+ its('flags') { should include 'interval' }
25
+ it { should have_element('1.1.1.1 . tcp . 25-27') }
26
+ end
27
+ EXAMPLE
28
+
29
+ @@bin = nil
30
+ @@nft_params = {}
31
+ @@nft_params["json"] = ""
32
+ @@nft_params["stateless"] = ""
33
+ @@nft_params["num"] = ""
34
+
35
+ def initialize(params = {})
36
+ @family = params[:family] || nil
37
+ @table = params[:table] || nil
38
+ @chain = params[:chain] || nil
39
+ @set = params[:set] || nil
40
+ @ignore_comments = params[:ignore_comments] || false
41
+ unless @@bin
42
+ @@bin = find_nftables_or_error
43
+ end
44
+
45
+ # Some old versions of `nft` do not support JSON output or stateless modifier
46
+ res = inspec.command("#{@@bin} --version").stdout
47
+ version = Gem::Version.new(/^nftables v(\S+) .*/.match(res)[1])
48
+ case
49
+ when version < Gem::Version.new("0.8.0")
50
+ @@nft_params["num"] = "-nn"
51
+ when version < Gem::Version.new("0.9.0")
52
+ @@nft_params["stateless"] = "-s"
53
+ @@nft_params["num"] = "-nn"
54
+ when version < Gem::Version.new("0.9.3")
55
+ @@nft_params["json"] = "-j"
56
+ @@nft_params["stateless"] = "-s"
57
+ @@nft_params["num"] = "-nn"
58
+ when version >= Gem::Version.new("0.9.3")
59
+ @@nft_params["json"] = "-j"
60
+ @@nft_params["stateless"] = "-s"
61
+ @@nft_params["num"] = "-y"
62
+ ## --terse
63
+ end
64
+
65
+ # family and table attributes are mandatory
66
+ fail_resource "nftables family and table are mandatory." if @family.nil? || @family.empty? || @table.nil? || @table.empty?
67
+ # chain name or set name has to be specified and are mutually exclusive
68
+ fail_resource "You must specify either a chain or a set name." if (@chain.nil? || @chain.empty?) && (@set.nil? || @set.empty?)
69
+ fail_resource "You must specify either a chain or a set name, not both." if !(@chain.nil? || @chain.empty?) && !(@set.nil? || @set.empty?)
70
+
71
+ # we're done if we are on linux
72
+ return if inspec.os.linux?
73
+
74
+ # ensures, all calls are aborted for non-supported os
75
+ @nftables_cache = {}
76
+ skip_resource "The `nftables` resource is not supported on your OS yet."
77
+ end
78
+
79
+ # Let's have a generic method to retrieve attributes for chains and sets
80
+ def _get_attr(name)
81
+ # Some attributes are valid for chains only, for sets only or for both
82
+ valid = {
83
+ "chains" => %w{hook policy prio type},
84
+ "sets" => %w{flags size type},
85
+ }
86
+
87
+ target_obj = @set.nil? ? "chains" : "sets"
88
+
89
+ if valid[target_obj].include?(name)
90
+ attrs = @set.nil? ? retrieve_chain_attrs : retrieve_set_attrs
91
+ else
92
+ raise Inspec::Exceptions::ResourceSkipped, "`#{name}` attribute is not valid for #{target_obj}"
93
+ end
94
+ # flags attribute is an array, if not retrieved ensure we return an empty array
95
+ # otherwise return an empty string
96
+ default = name == "flags" ? [] : ""
97
+ val = attrs.key?(name) ? attrs[name] : default
98
+ # When set type is has multiple data types it's retrieved as an array, make humans life easier
99
+ # by returning a string representation
100
+ if name == "type" && target_obj == "sets" && val.is_a?(Array)
101
+ return val.join(" . ")
102
+ end
103
+
104
+ val
105
+ end
106
+
107
+ # Create a method for each attribute
108
+ %i{flags hook policy prio size type}.each do |attr_method|
109
+ define_method attr_method do
110
+ _get_attr(attr_method.to_s)
111
+ end
112
+ end
113
+
114
+ def has_rule?(rule = nil, _family = nil, _table = nil, _chain = nil)
115
+ # checks if the rule is part of the chain
116
+ # for now, we expect an exact match
117
+ retrieve_chain_rules.any? { |line| line.casecmp(rule) == 0 }
118
+ end
119
+
120
+ def has_element?(element = nil, _family = nil, _table = nil, _chain = nil)
121
+ # checks if the element is part of the set
122
+ # for now, we expect an exact match
123
+ retrieve_set_elements.any? { |line| line.casecmp(element) == 0 }
124
+ end
125
+
126
+ def retrieve_set_elements
127
+ idx = "set_#{@family}_#{@table}_#{@set}"
128
+ return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
129
+
130
+ @nftables_cache = {} unless defined?(@nftables_cache)
131
+
132
+ elem_cmd = "list set #{@family} #{@table} #{@set}"
133
+ nftables_cmd = format("%s %s %s", @@bin, @@nft_params["stateless"], elem_cmd).strip
134
+
135
+ cmd = inspec.command(nftables_cmd)
136
+ return [] if cmd.exit_status.to_i != 0
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)
139
+ end
140
+
141
+ def retrieve_chain_rules
142
+ idx = "rule_#{@family}_#{@table}_#{@chain}"
143
+ return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
144
+
145
+ @nftables_cache = {} unless defined?(@nftables_cache)
146
+
147
+ # construct nftables command to read all rules of the given chain
148
+ chain_cmd = "list chain #{@family} #{@table} #{@chain}"
149
+ nftables_cmd = format("%s %s %s %s", @@bin, @@nft_params["stateless"], @@nft_params["num"], chain_cmd).strip
150
+
151
+ cmd = inspec.command(nftables_cmd)
152
+ return [] if cmd.exit_status.to_i != 0
153
+
154
+ rules = cmd.stdout.gsub("\t", "").split("\n").reject { |line| line =~ /^(table|chain)/ || line =~ /^}$/ }
155
+
156
+ if @ignore_comments
157
+ # split rules, returns array or rules without any comment
158
+ @nftables_cache[idx] = remove_comments_from_rules(rules)
159
+ else
160
+ # split rules, returns array or rules
161
+ @nftables_cache[idx] = rules.map(&:strip)
162
+ end
163
+ end
164
+
165
+ def retrieve_chain_attrs
166
+ idx = "chain_attrs_#{@family}_#{@table}_#{@chain}"
167
+ return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
168
+
169
+ @nftables_cache = {} unless defined?(@nftables_cache)
170
+
171
+ chain_cmd = "list chain #{@family} #{@table} #{@chain}"
172
+ nftables_cmd = format("%s %s %s %s", @@bin, @@nft_params["stateless"], @@nft_params["json"], chain_cmd).strip
173
+
174
+ cmd = inspec.command(nftables_cmd)
175
+ return {} if cmd.exit_status.to_i != 0
176
+
177
+ if @@nft_params["json"].empty?
178
+ res = cmd.stdout.gsub("\t", "").split("\n").select { |line| line =~ /^type/ }[0]
179
+ parsed = /type (\S+) hook (\S+) priority (\S+); policy (\S+);/.match(res)
180
+ @nftables_cache[idx] = { "type" => parsed[1], "hook" => parsed[2], "prio" => parsed[3].to_i, "policy" => parsed[4] }
181
+ else
182
+ @nftables_cache[idx] = JSON.parse(cmd.stdout)["nftables"].select { |line| line.key?("chain") }[0]["chain"]
183
+ end
184
+ end
185
+
186
+ def retrieve_set_attrs
187
+ idx = "set_attrs_#{@family}_#{@table}_#{@chain}"
188
+ return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
189
+
190
+ @nftables_cache = {} unless defined?(@nftables_cache)
191
+
192
+ chain_cmd = "list set #{@family} #{@table} #{@set}"
193
+ nftables_cmd = format("%s %s %s %s", @@bin, @@nft_params["stateless"], @@nft_params["json"], chain_cmd).strip
194
+
195
+ cmd = inspec.command(nftables_cmd)
196
+ return {} if cmd.exit_status.to_i != 0
197
+
198
+ if @@nft_params["json"].empty?
199
+ type = ""
200
+ size = 0
201
+ flags = []
202
+ res = cmd.stdout.gsub("\t", "").split("\n").select { |line| line =~ /^(type|size|flags)/ }
203
+ res.each do |line|
204
+ parsed = /^type (.*)/.match(line)
205
+ if parsed
206
+ type = parsed[1]
207
+ end
208
+ parsed = /^flags (.*)/.match(line)
209
+ if parsed
210
+ flags = parsed[1].split(",")
211
+ end
212
+ parsed = /^size (.*)/.match(line)
213
+ if parsed
214
+ size = parsed[1].to_i
215
+ end
216
+ end
217
+ @nftables_cache[idx] = { "type" => type, "size" => size, "flags" => flags }
218
+ else
219
+ @nftables_cache[idx] = JSON.parse(cmd.stdout)["nftables"].select { |line| line.key?("set") }[0]["set"]
220
+ end
221
+ end
222
+
223
+ def resource_id
224
+ to_s || "nftables"
225
+ end
226
+
227
+ def to_s
228
+ format("nftables (%s %s %s %s)", @family && "family: #{@family}", @table && "table: #{@table}", @chain && "chain: #{@chain}", @set && "set: #{@set}").strip
229
+ end
230
+
231
+ private
232
+
233
+ def remove_comments_from_rules(rules)
234
+ rules.each do |rule|
235
+ next if rule.nil?
236
+
237
+ rule.gsub!(/ comment "([^"]*)"/, "")
238
+ rule.strip
239
+ end
240
+ rules
241
+ end
242
+
243
+ def find_nftables_or_error
244
+ %w{/usr/sbin/nft /sbin/nft nft}.each do |cmd|
245
+ return cmd if inspec.command(cmd).exist?
246
+ end
247
+
248
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `nft`"
249
+ end
250
+ end
251
+ end
@@ -81,7 +81,8 @@ module Inspec::Resources
81
81
  # Socket path and empty host in the connection string establishes socket connection
82
82
  # Socket connection only enabled for non-windows platforms
83
83
  # Windows does not support unix domain sockets
84
- "psql -d postgresql://#{@user}:#{@pass}@/#{dbs}?host=#{@socket_path} -A -t -w -c #{escaped_query(query)}"
84
+ option_port = @port.nil? ? "" : "-p #{@port}" # add explicit port if specified
85
+ "psql -d postgresql://#{@user}:#{@pass}@/#{dbs}?host=#{@socket_path} #{option_port} -A -t -w -c #{escaped_query(query)}"
85
86
  else
86
87
  # Host in connection string establishes tcp/ip connection
87
88
  if inspec.os.windows?
@@ -73,6 +73,7 @@ require "inspec/resources/mssql_sys_conf"
73
73
  require "inspec/resources/mysql"
74
74
  require "inspec/resources/mysql_conf"
75
75
  require "inspec/resources/mysql_session"
76
+ require "inspec/resources/nftables"
76
77
  require "inspec/resources/nginx"
77
78
  require "inspec/resources/nginx_conf"
78
79
  require "inspec/resources/npm"
data/lib/inspec/rule.rb CHANGED
@@ -63,6 +63,11 @@ module Inspec
63
63
  # Rubocop thinks we are raising an exception - we're actually calling RSpec's fail()
64
64
  its(location) { fail e.message } # rubocop: disable Style/SignalException
65
65
  end
66
+
67
+ # instance_eval evaluates the describe block and raise errors if at the resource level any execution is failed
68
+ # Waived controls expect not to raise any controls and get skipped if run is false so __apply_waivers needs to be called here too
69
+ # so that waived control are actually gets waived.
70
+ __apply_waivers
66
71
  end
67
72
  end
68
73
 
@@ -57,11 +57,18 @@ class SimpleConfig
57
57
  m = opts[:assignment_regex].match(line)
58
58
  return nil if m.nil?
59
59
 
60
+ values = parse_values(m, opts[:key_values])
61
+
60
62
  if opts[:multiple_values]
61
63
  @vals[m[1]] ||= []
62
- @vals[m[1]].push(parse_values(m, opts[:key_values]))
64
+ if opts[:multiple_value_regex] # can be used only if multiple values is set as true
65
+ value_to_array = values.split(opts[:multiple_value_regex])
66
+ @vals[m[1]].concat(value_to_array)
67
+ else
68
+ @vals[m[1]].push(values)
69
+ end
63
70
  else
64
- @vals[m[1]] = parse_values(m, opts[:key_values])
71
+ @vals[m[1]] = values
65
72
  end
66
73
  end
67
74
 
@@ -116,6 +123,7 @@ class SimpleConfig
116
123
  key_values: 1, # default for key=value, may require for 'key val1 val2 val3'
117
124
  standalone_comments: false,
118
125
  multiple_values: false,
126
+ multiple_value_regex: nil,
119
127
  }
120
128
  end
121
129
  end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "5.21.29".freeze
2
+ VERSION = "5.22.29".freeze
3
3
  end
@@ -19,15 +19,17 @@ module Inspec
19
19
  data = nil
20
20
  if [".yaml", ".yml"].include? file_extension
21
21
  data = Secrets::YAML.resolve(file_path)
22
- data = data.inputs unless data.nil?
23
- validate_json_yaml(data)
22
+ unless data.nil?
23
+ data = data.inputs
24
+ validate_json_yaml(data)
25
+ end
24
26
  elsif file_extension == ".csv"
25
27
  data = Waivers::CSVFileReader.resolve(file_path)
26
28
  headers = Waivers::CSVFileReader.headers
27
29
  validate_headers(headers)
28
30
  elsif file_extension == ".json"
29
31
  data = Waivers::JSONFileReader.resolve(file_path)
30
- validate_json_yaml(data)
32
+ validate_json_yaml(data) unless data.nil?
31
33
  end
32
34
  output.merge!(data) if !data.nil? && data.is_a?(Hash)
33
35
 
@@ -148,7 +148,7 @@ RSpec::Matchers.define :be_resolvable do
148
148
  end
149
149
  end
150
150
 
151
- # matcher for iptables and ip6tables
151
+ # matcher for iptables, ip6tables and nftables
152
152
  RSpec::Matchers.define :have_rule do |rule|
153
153
  match do |tables|
154
154
  tables.has_rule?(rule)
@@ -163,6 +163,13 @@ RSpec::Matchers.define :have_rule do |rule|
163
163
  end
164
164
  end
165
165
 
166
+ # matcher for nftables sets
167
+ RSpec::Matchers.define :have_element do |elem|
168
+ match do |sets|
169
+ sets.has_element?(elem)
170
+ end
171
+ end
172
+
166
173
  # `be_in` matcher
167
174
  # You can use it in the following cases:
168
175
  # - check if an item or array is included in a given array
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: 5.21.29
4
+ version: 5.22.29
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: 2023-01-24 00:00:00.000000000 Z
11
+ date: 2023-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef-telemetry
@@ -59,7 +59,7 @@ dependencies:
59
59
  version: '0.20'
60
60
  - - "<"
61
61
  - !ruby/object:Gem::Version
62
- version: '2.0'
62
+ version: 1.3.0
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
@@ -69,7 +69,7 @@ dependencies:
69
69
  version: '0.20'
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
- version: '2.0'
72
+ version: 1.3.0
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: method_source
75
75
  requirement: !ruby/object:Gem::Requirement
@@ -119,7 +119,7 @@ dependencies:
119
119
  version: '3.9'
120
120
  - - "<="
121
121
  - !ruby/object:Gem::Version
122
- version: '3.11'
122
+ version: '3.12'
123
123
  type: :runtime
124
124
  prerelease: false
125
125
  version_requirements: !ruby/object:Gem::Requirement
@@ -129,7 +129,7 @@ dependencies:
129
129
  version: '3.9'
130
130
  - - "<="
131
131
  - !ruby/object:Gem::Version
132
- version: '3.11'
132
+ version: '3.12'
133
133
  - !ruby/object:Gem::Dependency
134
134
  name: rspec-its
135
135
  requirement: !ruby/object:Gem::Requirement
@@ -167,7 +167,7 @@ dependencies:
167
167
  version: '3.4'
168
168
  - - "<"
169
169
  - !ruby/object:Gem::Version
170
- version: '5.0'
170
+ version: '6.0'
171
171
  type: :runtime
172
172
  prerelease: false
173
173
  version_requirements: !ruby/object:Gem::Requirement
@@ -177,7 +177,7 @@ dependencies:
177
177
  version: '3.4'
178
178
  - - "<"
179
179
  - !ruby/object:Gem::Version
180
- version: '5.0'
180
+ version: '6.0'
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: mixlib-log
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -325,7 +325,7 @@ dependencies:
325
325
  version: '1.5'
326
326
  - - "<"
327
327
  - !ruby/object:Gem::Version
328
- version: '2.0'
328
+ version: '3.0'
329
329
  type: :runtime
330
330
  prerelease: false
331
331
  version_requirements: !ruby/object:Gem::Requirement
@@ -335,7 +335,7 @@ dependencies:
335
335
  version: '1.5'
336
336
  - - "<"
337
337
  - !ruby/object:Gem::Version
338
- version: '2.0'
338
+ version: '3.0'
339
339
  - !ruby/object:Gem::Dependency
340
340
  name: semverse
341
341
  requirement: !ruby/object:Gem::Requirement
@@ -584,6 +584,7 @@ files:
584
584
  - lib/inspec/resources/mysql.rb
585
585
  - lib/inspec/resources/mysql_conf.rb
586
586
  - lib/inspec/resources/mysql_session.rb
587
+ - lib/inspec/resources/nftables.rb
587
588
  - lib/inspec/resources/nginx.rb
588
589
  - lib/inspec/resources/nginx_conf.rb
589
590
  - lib/inspec/resources/noop.rb