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 +4 -4
- data/Gemfile +2 -20
- data/inspec-core.gemspec +6 -4
- data/lib/inspec/base_cli.rb +17 -17
- data/lib/inspec/cli.rb +27 -25
- data/lib/inspec/config.rb +23 -10
- data/lib/inspec/dependencies/dependency_set.rb +2 -2
- data/lib/inspec/dsl.rb +4 -6
- data/lib/inspec/fetcher/git.rb +43 -28
- data/lib/inspec/formatters/base.rb +1 -1
- data/lib/inspec/profile.rb +25 -20
- data/lib/inspec/resources/host.rb +4 -16
- data/lib/inspec/resources/http.rb +3 -3
- data/lib/inspec/resources/mongodb_session.rb +5 -0
- data/lib/inspec/resources/nftables.rb +251 -0
- data/lib/inspec/resources/postgres_session.rb +2 -1
- data/lib/inspec/resources.rb +1 -0
- data/lib/inspec/rule.rb +5 -0
- data/lib/inspec/utils/simpleconfig.rb +10 -2
- data/lib/inspec/version.rb +1 -1
- data/lib/inspec/waiver_file_reader.rb +5 -3
- data/lib/matchers/matchers.rb +8 -1
- metadata +11 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31c8e898abb240d79f4c564ae182f620d291f0e895b1451185f891c0c03a4d3b
|
4
|
+
data.tar.gz: 513a01ebe59969076c1a9e2c210f41b1366dbb17603e6370bdcb88d162d0d06a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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", "~>
|
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
|
-
|
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.
|
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", "<
|
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", "<
|
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
|
|
data/lib/inspec/base_cli.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|
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", "
|
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
|
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", "
|
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
|
284
|
+
desc "exec LOCATIONS", "Run all test files at the specified locations."
|
279
285
|
long_desc <<~EOT
|
280
|
-
|
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
|
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
|
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", "
|
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", "
|
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
|
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(
|
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
|
-
#
|
480
|
-
options["
|
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
|
-
#
|
483
|
-
if options["
|
489
|
+
# Parse out reporter_cli_opts to proper report format
|
490
|
+
if options["reporter_cli_opts"].is_a?(Array)
|
484
491
|
reports = {}
|
485
|
-
options["
|
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
|
-
|
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}
|
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}
|
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
|
-
|
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
|
-
|
100
|
-
unless
|
101
|
-
profile_id_key = key.split("-#{
|
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
|
data/lib/inspec/fetcher/git.rb
CHANGED
@@ -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.
|
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
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
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
|
140
|
+
resolve_ref
|
129
141
|
end
|
130
142
|
end
|
131
143
|
|
132
|
-
def
|
133
|
-
command_string =
|
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
|
-
|
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
|
157
|
+
ref = parse_ls_remote(cmd.stdout, ref_name)
|
139
158
|
unless ref
|
140
|
-
raise
|
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
|
-
|
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
|
data/lib/inspec/profile.rb
CHANGED
@@ -383,24 +383,25 @@ module Inspec
|
|
383
383
|
@runner_context
|
384
384
|
end
|
385
385
|
|
386
|
-
|
386
|
+
# This collects the gem dependencies data from parent and its dependent profiles
|
387
|
+
def collect_gem_dependencies
|
387
388
|
gem_dependencies = []
|
388
|
-
|
389
|
-
|
390
|
-
|
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
|
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
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
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,
|
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,
|
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
|
-
|
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 =
|
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
|
326
|
-
|
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
|
344
|
-
|
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
|
-
|
307
|
+
request_header_array = []
|
308
308
|
request_headers.each do |k, v|
|
309
|
-
|
309
|
+
request_header_array << " '#{k}' = '#{v}'"
|
310
310
|
end
|
311
|
-
cmd << "-Headers @{#{
|
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
|
-
|
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?
|
data/lib/inspec/resources.rb
CHANGED
@@ -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
|
-
|
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]] =
|
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
|
data/lib/inspec/version.rb
CHANGED
@@ -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
|
-
|
23
|
-
|
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
|
|
data/lib/matchers/matchers.rb
CHANGED
@@ -148,7 +148,7 @@ RSpec::Matchers.define :be_resolvable do
|
|
148
148
|
end
|
149
149
|
end
|
150
150
|
|
151
|
-
# matcher for iptables and
|
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.
|
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-
|
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:
|
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:
|
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.
|
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.
|
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: '
|
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: '
|
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: '
|
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: '
|
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
|