diff --git a/CHANGELOG.md b/CHANGELOG.md index a6db848e5..0f42f2662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,37 +1,59 @@ # Change Log - -## [v5.18.3](https://github.com/inspec/inspec/tree/v5.18.3) (2022-06-10) + +## [v5.20.1](https://github.com/inspec/inspec/tree/v5.20.1) (2022-08-04) #### Merged Pull Requests -- Dk/matchers rewrite [#6007](https://github.com/inspec/inspec/pull/6007) ([dkumaras](https://github.com/dkumaras)) +- Fix the dependabot adding ffi (1.15.5-x64-unknown) to omnibus bump [#6213](https://github.com/inspec/inspec/pull/6213) ([Vasu1105](https://github.com/Vasu1105)) - -### Changes since 5.17.4 release + +### Changes since 5.18.14 release #### Merged Pull Requests -- Dk/matchers rewrite [#6007](https://github.com/inspec/inspec/pull/6007) ([dkumaras](https://github.com/dkumaras)) -- Fixed Lint/DuplicateMethods: Method Inspec::Resources::Service#resource_id is defined at both [#6132](https://github.com/inspec/inspec/pull/6132) ([Vasu1105](https://github.com/Vasu1105)) -- CFINSPEC-291: Fix `processes` resource to consider processes without `path` on Windows [#6100](https://github.com/inspec/inspec/pull/6100) ([ahasunos](https://github.com/ahasunos)) -- CFINSPEC-167: Profile Signing Rollup [#5995](https://github.com/inspec/inspec/pull/5995) ([Vasu1105](https://github.com/Vasu1105)) -- Bump berkshelf from 8.0.0 to 8.0.2 in /omnibus [#6114](https://github.com/inspec/inspec/pull/6114) ([dependabot[bot]](https://github.com/dependabot[bot])) -- CFINSPEC-262 - Handle resource_id in error situation [#6119](https://github.com/inspec/inspec/pull/6119) ([Vasu1105](https://github.com/Vasu1105)) -- Handle resource_id in error situations [#6118](https://github.com/inspec/inspec/pull/6118) ([ahasunos](https://github.com/ahasunos)) -- CFINSPEC-273 Adds resource_id group 12 [#6112](https://github.com/inspec/inspec/pull/6112) ([Vasu1105](https://github.com/Vasu1105)) -- CFINSPEC-270 Adds resource_id group9 [#6111](https://github.com/inspec/inspec/pull/6111) ([Vasu1105](https://github.com/Vasu1105)) -- CFINSPEC-265 Group 4 - Added resource_id in resources [#6109](https://github.com/inspec/inspec/pull/6109) ([Nik08](https://github.com/Nik08)) -- CFINSPEC-269 Adds resource_id group 8 [#6107](https://github.com/inspec/inspec/pull/6107) ([Vasu1105](https://github.com/Vasu1105)) -- CFINSPEC-268 Adds resource_id group 7 [#6105](https://github.com/inspec/inspec/pull/6105) ([Vasu1105](https://github.com/Vasu1105)) -- Fix the key duplication error warning in the mock_loader.rb [#6120](https://github.com/inspec/inspec/pull/6120) ([Vasu1105](https://github.com/Vasu1105)) -- CFINSPEC-266: resource_ids group 5 [#6103](https://github.com/inspec/inspec/pull/6103) ([ahasunos](https://github.com/ahasunos)) -- CFINSPEC-262 Adds resource_id group 1 [#6102](https://github.com/inspec/inspec/pull/6102) ([Vasu1105](https://github.com/Vasu1105)) -- CFINSPEC-267: resource_ids group 6 [#6101](https://github.com/inspec/inspec/pull/6101) ([ahasunos](https://github.com/ahasunos)) -- CFINSPEC-95: Enhance `x509_certificate` resource [#6041](https://github.com/inspec/inspec/pull/6041) ([ahasunos](https://github.com/ahasunos)) -- Bump rack from 2.2.3 to 2.2.3.1 in /omnibus [#6098](https://github.com/inspec/inspec/pull/6098) ([dependabot[bot]](https://github.com/dependabot[bot])) +- Fix the dependabot adding ffi (1.15.5-x64-unknown) to omnibus bump [#6213](https://github.com/inspec/inspec/pull/6213) ([Vasu1105](https://github.com/Vasu1105)) +- Adds podman resources. [#6183](https://github.com/inspec/inspec/pull/6183) ([Vasu1105](https://github.com/Vasu1105)) +- CFINSPEC-237 Added enhanced_outcomes option [#6145](https://github.com/inspec/inspec/pull/6145) ([Nik08](https://github.com/Nik08)) +- CFINSPEC-400 Fix for verify pipeline failure [#6218](https://github.com/inspec/inspec/pull/6218) ([Vasu1105](https://github.com/Vasu1105)) +- Docs spellcheck [#6214](https://github.com/inspec/inspec/pull/6214) ([IanMadd](https://github.com/IanMadd)) +- Trivial README change to trigger new omnibus build [#6203](https://github.com/inspec/inspec/pull/6203) ([clintoncwolfe](https://github.com/clintoncwolfe)) +## [v5.18.14](https://github.com/inspec/inspec/tree/v5.18.14) (2022-07-13) + +#### Merged Pull Requests +- Bump rack from 2.2.3 to 2.2.3.1 in /omnibus [#6098](https://github.com/inspec/inspec/pull/6098) ([dependabot[bot]](https://github.com/dependabot[bot])) +- CFINSPEC-95: Enhance `x509_certificate` resource [#6041](https://github.com/inspec/inspec/pull/6041) ([ahasunos](https://github.com/ahasunos)) +- CFINSPEC-267: resource_ids group 6 [#6101](https://github.com/inspec/inspec/pull/6101) ([ahasunos](https://github.com/ahasunos)) +- CFINSPEC-262 Adds resource_id group 1 [#6102](https://github.com/inspec/inspec/pull/6102) ([Vasu1105](https://github.com/Vasu1105)) +- CFINSPEC-266: resource_ids group 5 [#6103](https://github.com/inspec/inspec/pull/6103) ([ahasunos](https://github.com/ahasunos)) +- Fix the key duplication error warning in the mock_loader.rb [#6120](https://github.com/inspec/inspec/pull/6120) ([Vasu1105](https://github.com/Vasu1105)) +- CFINSPEC-268 Adds resource_id group 7 [#6105](https://github.com/inspec/inspec/pull/6105) ([Vasu1105](https://github.com/Vasu1105)) +- CFINSPEC-269 Adds resource_id group 8 [#6107](https://github.com/inspec/inspec/pull/6107) ([Vasu1105](https://github.com/Vasu1105)) +- CFINSPEC-265 Group 4 - Added resource_id in resources [#6109](https://github.com/inspec/inspec/pull/6109) ([Nik08](https://github.com/Nik08)) +- CFINSPEC-270 Adds resource_id group9 [#6111](https://github.com/inspec/inspec/pull/6111) ([Vasu1105](https://github.com/Vasu1105)) +- CFINSPEC-273 Adds resource_id group 12 [#6112](https://github.com/inspec/inspec/pull/6112) ([Vasu1105](https://github.com/Vasu1105)) +- Handle resource_id in error situations [#6118](https://github.com/inspec/inspec/pull/6118) ([ahasunos](https://github.com/ahasunos)) +- CFINSPEC-262 - Handle resource_id in error situation [#6119](https://github.com/inspec/inspec/pull/6119) ([Vasu1105](https://github.com/Vasu1105)) +- Bump berkshelf from 8.0.0 to 8.0.2 in /omnibus [#6114](https://github.com/inspec/inspec/pull/6114) ([dependabot[bot]](https://github.com/dependabot[bot])) +- CFINSPEC-167: Profile Signing Rollup [#5995](https://github.com/inspec/inspec/pull/5995) ([Vasu1105](https://github.com/Vasu1105)) +- CFINSPEC-291: Fix `processes` resource to consider processes without `path` on Windows [#6100](https://github.com/inspec/inspec/pull/6100) ([ahasunos](https://github.com/ahasunos)) +- Fixed Lint/DuplicateMethods: Method Inspec::Resources::Service#resource_id is defined at both [#6132](https://github.com/inspec/inspec/pull/6132) ([Vasu1105](https://github.com/Vasu1105)) +- Dk/matchers rewrite [#6007](https://github.com/inspec/inspec/pull/6007) ([dkumaras](https://github.com/dkumaras)) +- Add inspec-6 branch as release branch [#6136](https://github.com/inspec/inspec/pull/6136) ([clintoncwolfe](https://github.com/clintoncwolfe)) +- add ruby test 3.1 in verify pipeline [#5892](https://github.com/inspec/inspec/pull/5892) ([jayashrig158](https://github.com/jayashrig158)) +- Updated plugins doc with send_report functionality [#6144](https://github.com/inspec/inspec/pull/6144) ([Nik08](https://github.com/Nik08)) +- Bump octokit from 4.23.0 to 4.25.0 in /omnibus [#6146](https://github.com/inspec/inspec/pull/6146) ([dependabot[bot]](https://github.com/dependabot[bot])) +- Fixes for Buildkite Issues [#6161](https://github.com/inspec/inspec/pull/6161) ([Nik08](https://github.com/Nik08)) +- CFINSPEC-238 Enhanced Outcomes Design Doc [#6152](https://github.com/inspec/inspec/pull/6152) ([clintoncwolfe](https://github.com/clintoncwolfe)) +- Add k8s section to resources index page [#6167](https://github.com/inspec/inspec/pull/6167) ([IanMadd](https://github.com/IanMadd)) +- Windows fix for dependent profiles [#6173](https://github.com/inspec/inspec/pull/6173) ([Nik08](https://github.com/Nik08)) +- Bump omnibus-software from `a9b13a0` to `7bb8c7b` in /omnibus [#6191](https://github.com/inspec/inspec/pull/6191) ([dependabot[bot]](https://github.com/dependabot[bot])) +- Trial - Update the omnibus/Gemfile.lock (can be the reason for omnibus build failure) [#6195](https://github.com/inspec/inspec/pull/6195) ([Vasu1105](https://github.com/Vasu1105)) +- CFINSPEC-239 Attestations Design Doc [#6188](https://github.com/inspec/inspec/pull/6188) ([clintoncwolfe](https://github.com/clintoncwolfe)) + + ## [v5.17.4](https://github.com/inspec/inspec/tree/v5.17.4) (2022-05-25) #### Merged Pull Requests @@ -52,7 +74,6 @@ - fixing bad markdown syntax [#6066](https://github.com/inspec/inspec/pull/6066) ([replicajune](https://github.com/replicajune)) - Add vale config to docs in inspec repository [#6065](https://github.com/inspec/inspec/pull/6065) ([IanMadd](https://github.com/IanMadd)) - Remove Hugo version from Netlify config [#6075](https://github.com/inspec/inspec/pull/6075) ([IanMadd](https://github.com/IanMadd)) - ## [v5.14.0](https://github.com/inspec/inspec/tree/v5.14.0) (2022-04-21) diff --git a/Dockerfile b/Dockerfile index afef388bf..9d8a3a9ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:18.04 LABEL maintainer="Chef Software, Inc. " -ARG VERSION=5.17.4 +ARG VERSION=5.18.14 ARG CHANNEL=stable ENV PATH=/opt/inspec/bin:/opt/inspec/embedded/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin diff --git a/Gemfile b/Gemfile index a1bb0b915..de753e71b 100644 --- a/Gemfile +++ b/Gemfile @@ -25,7 +25,7 @@ end group :test do gem "chefstyle", "~> 2.0.3" gem "concurrent-ruby", "~> 1.0" - gem "html-proofer", platforms: :ruby # do not attempt to run proofer on windows + gem "html-proofer", "~> 3.19.4", platforms: :ruby # do not attempt to run proofer on windows. Pinned to 3.19.4 as test is breaking in updated versions. gem "json_schemer", ">= 0.2.1", "< 0.2.19" gem "m" gem "minitest-sprint", "~> 1.0" diff --git a/README.md b/README.md index dde89040c..a3b5242af 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ inspec exec test.rb -t docker://container_id Chef InSpec requires Ruby ( >= 2.7 ). -Note: Versions of Chef InSpec 4.0 and later require accepting the EULA to use. Please visit the [license acceptance page](https://docs.chef.io/chef_license_accept.html) on the Chef docs site for more information. +All currently supported versions of Chef InSpec (4.0 and later) require accepting the EULA to use. Please visit the [license acceptance page](https://docs.chef.io/chef_license_accept.html) on the Chef docs site for more information. ### Install as package diff --git a/dev-docs/attestations.md b/dev-docs/attestations.md new file mode 100644 index 000000000..214b48056 --- /dev/null +++ b/dev-docs/attestations.md @@ -0,0 +1,82 @@ +# Attestations + +## Use Cases + +As a compliance officer, +I want to mark skipped controls as manually passed or failed +so that I can manually complete the profile. + +As a compliance officer, +I want to set an expiration date and a justification for my attestations +so that I can control their application. + +As a compliance officer, +I want flexibility in the file format accepted by the attestations system (XLSX, YAML, CSV, JSON), +so that I can use a familiar file format. + +When used with Enhanced Outcomes, this becomes handling `Not Reviewed` controls. + +## Mechanism + +### CLI option desirable + +`inspec exec profilename --attestation-file file.???` + +The new option is named like `--waiver-file` - singular, with `-file`. You may provide multiple arguments for the option. + +The file can be any of the following formats: `YAML`, `XLSX`, `CSV`, or `JSON`. + +#### YAML and JSON + +An array of Hashes. + +#### XLSX and CSV + +XLSX is the first sheet in the file. + +Both formats assume a header row. + +### Fields in the file + +#### control_id + +_Required_. Matches control ID of the control. + +#### justification + +_Required_. Free text field, used as an explanation for the control when displayed. + +#### evidence_url + +_Optional_. URL to some evidence, determined by the user, supports the justification. + +#### expiration_date + +_Optional_. If present, the attestation expires at the end of the date given. + +#### status + +_Optional_. + +Default `passed`. If the attestation should indicate that the control is a failure, set this to `failed`. + +### Implementation + +When running, at the **RunData** stage, attestations are handled by the following process: + +1. Locate matching controls by matching the control ID. + +2. Inject an artificial test result into the control. Use the attestation justification as the result message. + +3. If the attestation is expired, set the new test result to Skip. + +4. If the attestation is not expired, set the new test result to the status given on the attestation data (default pass). + +5. Record a copy of the attestation data structure in the Control RunData structure. + +### Compatibility + +To support backward compatibility with existing MITRE work, support will be added (but not otherwise documented) for the following fields: + +* explanation - the equivalent of justification +* updated (Date) and frequency (string enum) - together, the equivalent of the expiration date. diff --git a/dev-docs/enhanced-outcomes.md b/dev-docs/enhanced-outcomes.md new file mode 100644 index 000000000..00a681dd0 --- /dev/null +++ b/dev-docs/enhanced-outcomes.md @@ -0,0 +1,54 @@ +# Enhanced Outcomes + +Enhanced Outcomes refers to the addition of new control outcomes to the InSpec vocabulary. + +## Test Outcomes vs. Control Outcomes + +It is essential to understand that Enhanced Outcomes refers to new **control outcomes**, the results of running a control. + +The results of running a test, a `describe block` are determined by RSpec and are limited to `Pass`, `Fail`, and `Skip`. + +Test outcomes are much more difficult to extend than control outcomes. + +## New Control Outcomes + +Enhanced Outcomes adds three new control outcomes to the existing `Pass`, `Fail`, and `Skip` outcomes. + +### Error + +In the first iteration of Enhanced Outcomes, the Error outcome is detected: + + * if the message of any test includes the text "Control source error" OR + * the result of any test includes a backtrace + +Then the entire control should be marked `Error`. + +Additional means of detecting error may be developed in the future. + +Error's abbreviation is `ERR`. Error's UI color assignment is `Indigo`. + +### Not Applicable + +If the control is not in `Error` and the impact of the control is `0.0`, then the control's outcome is `Not Applicable`. + +Not Applicable's abbreviation is `N/A`. Not Applicable's UI color assignment is `Sky Blue`. + +### Not Reviewed + +If the control is not in `Error` or `Not Applicable`, and all test results are `Skipped`, then the control outcome is `Not Reviewed`. + +Not Reviewed replaces `Skipped` as a control outcome. + +Not Reviewed's abbreviation is `N/R`. Not Reviewed's UI color assignment is `Amber`. + +## The `--enhanced-outcomes` option + +A new CLI option will be introduced for `inspec exec`, `inspec shell`, and `inspec schema` that controls the Enhanced Outcomes functionality. + +### InSpec 5 + +In InSpec 5.x, a user must request the enhanced outcomes functionality explicitly by adding the `--enhanced-outcomes` option. + +### InSpec 6 + +In InSpec 6.x, --enhanced-outcomes will default to `true`. A user may request disabling the enhanced outcomes functionality by adding the `--no-enhanced-outcomes` option. diff --git a/dev-docs/plugins.md b/dev-docs/plugins.md index 5f80f8be9..0a0d7dfa2 100644 --- a/dev-docs/plugins.md +++ b/dev-docs/plugins.md @@ -493,6 +493,24 @@ v0.1.0 - Initial version v0.2.0 - added `run_data.profiles[0].inputs[0].options.sensitive` v0.3.0 - added resource_name && params +#### Implement send_report + +The primary responsibilty of this function is to implement a logic for sending reporter output through email invocations or making API calls. When this is defined in a reporter, rendering of output is skipped. + +```ruby +module InspecPlugins::Sweeten + class Reporter < Inspec.plugin(2, :reporter) + def send_report + # logic for sending reporter output using email invocations or API calls. + end + + def render + # this will be skipped, will only run send_report + end + end +end +``` + ## Implementing Streaming Reporter Plugins Streaming Reporter plugins offer the opportunity to customize or create a plugin which operates real-time as the Chef Inspec tests runs. Streaming reporters perform streaming using RSpec custom formatters. diff --git a/docs-chef-io/content/inspec/cli.md b/docs-chef-io/content/inspec/cli.md index 0b9c5efff..5202b202b 100644 --- a/docs-chef-io/content/inspec/cli.md +++ b/docs-chef-io/content/inspec/cli.md @@ -62,7 +62,7 @@ inspec automate SUBCOMMAND ## check -Verify the metadata in the `inspec.yml` file, verify that control blocks have the correct fields (title, description, impact) defined that all controls have visible tests, and the controls are not using deprecated InSpec DSL code. +Verify the metadata in the `inspec.yml` file, verify that control blocks have the correct fields (title, description, impact), and define that all controls have visible tests and the controls are not using deprecated InSpec DSL code. ### Syntax @@ -118,9 +118,9 @@ This subcommand has the following additional options: * `--client-key-pass=CLIENT_CERT_PASSWORD` Specify client certificate password, if required for SSL authentication (WinRM). * `--config=CONFIG` - Read configuration from JSON file (`-` reads from stdin). + Read configuration from the JSON file (`-` reads from stdin). * `--docker-url` - Provides path to Docker API endpoint (Docker). + Provides a path to the Docker API endpoint (Docker). * `--enable-password=ENABLE_PASSWORD` Password for enable mode on Cisco IOS devices. * `--format=FORMAT` @@ -152,7 +152,7 @@ This subcommand has the following additional options: * `--ssl`, `--no-ssl` Use SSL for transport layer encryption (WinRM). * `--ssl-peer-fingerprint` - Specify ssl peer fingerprint in lieu of certificates, for SSL authentication (WinRM). + Specify SSL peer fingerprint in place of certificates for SSL authentication (WinRM). * `--sudo`, `--no-sudo` Run scans with sudo. Only activates on Unix and non-root user. * `--sudo-command=SUDO_COMMAND` @@ -174,7 +174,7 @@ This subcommand has the following additional options: * `--winrm-transport=WINRM_TRANSPORT` Specify which transport to use, defaults to negotiate (WinRM). * `--winrm-shell-type=WINRM_SHELL_TYPE` - Specify which shell type to use (powershell,elevated or cmd), defaults to powershell (WinRM). + Specify which shell type to use (powershell, elevated, or cmd), which defaults to powershell (WinRM). ## env @@ -192,7 +192,7 @@ inspec env Run all test files at the specified locations. -The subcommand loads the given profiles, fetches their dependencies if needed, then connects to the target and executes any controls contained in the profiles. One or more reporters are used to generate the output. +The subcommand loads the given profiles, fetches their dependencies if needed, then connects to the target and executes any controls in the profiles. One or more reporters are used to generate the output. ```ruby exit codes: @@ -314,13 +314,13 @@ This subcommand has the following additional options: * `--command-timeout=SECONDS` Maximum seconds to allow a command to run. * `--config=CONFIG` - Read configuration from JSON file (`-` reads from stdin). + Read configuration from the JSON file (`-` reads from stdin). * `--controls=one two three` - A list of control names to run, or a list of /regexes/ to match against control names. Ignore all other tests. + A list of control names to run or a list of /regexes/ to match against control names. Ignore all other tests. * `--create-lockfile`, `--no-create-lockfile` Write out a lockfile based on this execution (unless one already exists). * `--distinct-exit`, `--no-distinct-exit` - 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. + 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. * `--docker-url` Provides path to Docker API endpoint (Docker). Defaults to unix:///var/run/docker.sock on Unix systems and tcp://localhost:2375 on Windows. * `--enable-password=ENABLE_PASSWORD` @@ -328,7 +328,7 @@ This subcommand has the following additional options: * `--filter-empty-profiles`, `--no-filter-empty-profiles` Filter empty profiles (profiles without controls) from the report. * `--filter-waived-controls` - Do not execute waived controls in InSpec at all. Must use with --waiver-file. Ignores `run` setting of waiver file. + Do not execute waived controls in InSpec at all. Must use with --waiver-file. Ignores the `run` setting of the waiver file. * `--host=HOST` Specify a remote host which is tested. * `--input=name1=value1 name2=value2` @@ -352,13 +352,13 @@ This subcommand has the following additional options: * `--proxy-command=PROXY_COMMAND` Specifies the command to use to connect to the server. * `--reporter=one two:/output/file/path` - Enable one or more output reporters: cli, documentation, html, progress, progress-bar, json, json-min, json-rspec, junit, yaml. + Enable one or more output reporters: cli, documentation, html2, progress, progress-bar, json, json-min, json-rspec, junit2, yaml. * `--reporter-backtrace-inclusion`, `--no-reporter-backtrace-inclusion` Include a code backtrace in report data (default: true). * `--reporter-include-source` Include full source code of controls in the CLI report. * `--reporter-message-truncation=REPORTER_MESSAGE_TRUNCATION` - Number of characters to truncate failure messages in report data to (default: no truncation). + Number of characters to truncate failure messages in report data (default: no truncation). * `--self-signed`, `--no-self-signed` Allow remote scans with self-signed certificates (WinRM). * `--shell`, `--no-shell` @@ -370,13 +370,13 @@ This subcommand has the following additional options: * `--show-progress`, `--no-show-progress` Show progress while executing tests. * `--silence-deprecations=all|GROUP GROUP...` - Suppress deprecation warnings. See install_dir/etc/deprecations.json for list of GROUPs or use 'all'. + Suppress deprecation warnings. See install_dir/etc/deprecations.json for a list of GROUPs or use 'all'. * `--ssh-config-file=one two three` A list of paths to the SSH configuration file, for example: `~/.ssh/config` or `/etc/ssh/ssh_config`. * `--ssl`, `--no-ssl` Use SSL for transport layer encryption (WinRM). * `--ssl-peer-fingerprint` - Specify ssl peer fingerprint in lieu of certificates, for SSL authentication (WinRM). + Specify SSL peer fingerprint in place of certificates for SSL authentication (WinRM). * `--sudo`, `--no-sudo` Run scans with sudo. Only activates on Unix and non-root user. * `--sudo-command=SUDO_COMMAND` @@ -388,9 +388,9 @@ This subcommand has the following additional options: * `-t`, `--target=TARGET` Simple targeting option using URIs, e.g. ssh://user:pass@host:port. * `--target-id=TARGET_ID` - Provide a ID which will be included on reports - deprecated. + Provide an ID that is included on reports - deprecated. * `--tags=one two three` - A list of tags or a list of regular expressions that match tags. `exec` will run controls referenced by the listed or matching tags. + A list of tags or regular expressions that match tags. `exec` will run controls referenced by the listed or matching tags. * `--user=USER` The login user for a remote scan. * `--vendor-cache=VENDOR_CACHE` @@ -403,6 +403,8 @@ This subcommand has the following additional options: Whether to use disable sspi authentication, defaults to false (WinRM). * `--winrm-transport=WINRM_TRANSPORT` Specify which transport to use, defaults to negotiate (WinRM). +* `--enhanced-outcomes` + Includes enhanced outcome of controls in report data. ## habitat @@ -442,7 +444,7 @@ inspec init TEMPLATE ## json -Read all tests in path and generate a json summary. +Read all tests in the path and generate a json summary. ### Syntax @@ -463,7 +465,7 @@ This subcommand has the following additional options: * `--profiles-path=PROFILES_PATH` Folder which contains referenced profiles. * `--tags=one two three` - A list of tags that reference certain controls. Other controls are ignored. + A list of tags that reference specific controls. Other controls are ignored. * `--vendor-cache=VENDOR_CACHE` Use the given path for caching dependencies. (default: `~/.inspec/cache`). @@ -503,6 +505,13 @@ This subcommand has the following syntax: inspec schema NAME ``` +### Options + +This subcommand has the following additional option: + +* `--enhanced-outcomes` + Includes enhanced outcome of controls in report data. + ## shell Open an interactive debugging shell. @@ -540,11 +549,11 @@ This subcommand has the following additional options: * `--client-key-pass=CLIENT_CERT_PASSWORD` Specify client certificate password, if required for SSL authentication (WinRM). * `--config=CONFIG` - Read configuration from JSON file (`-` reads from stdin). + Read configuration from the JSON file (`-` reads from stdin). * `--depends=one two three` A space-delimited list of local folders containing profiles whose libraries and resources will be loaded into the new shell. * `--distinct-exit`, `--no-distinct-exit` - Exit with code 100 if any tests fail, and 101 if any are skipped but none failed (default). If disabled, exit 0 on skips and 1 for failures. + Exit with code 100 if any tests fail and 101 if any are skipped, but none failed (default). If disabled, exit 0 on skips and 1 for failures. * `--docker-url` Provides path to Docker API endpoint (Docker). Defaults to unix:///var/run/docker.sock on Unix systems and tcp://localhost:2375 on Windows. * `--enable-password=ENABLE_PASSWORD` @@ -568,7 +577,7 @@ This subcommand has the following additional options: * `--proxy-command=PROXY_COMMAND` Specifies the command to use to connect to the server. * `--reporter=one two:/output/file/path` - Enable one or more output reporters: cli, documentation, html, progress, json, json-min, json-rspec, junit. + Enable one or more output reporters: cli, documentation, html2, progress, json, json-min, json-rspec, junit2. * `--self-signed`, `--no-self-signed` Allow remote scans with self-signed certificates (WinRM). * `--shell`, `--no-shell` @@ -582,7 +591,7 @@ This subcommand has the following additional options: * `--ssl`, `--no-ssl` Use SSL for transport layer encryption (WinRM). * `--ssl-peer-fingerprint=SSL_PEER_FINGERPRINT` - Specify ssl peer fingerprint in lieu of certificates, for SSL authentication (WinRM). + Specify SSL peer fingerprint in place of certificates for SSL authentication (WinRM). * `--sudo`, `--no-sudo` Run scans with sudo. Only activates on Unix and non-root user. * `--sudo-command=SUDO_COMMAND` @@ -603,6 +612,8 @@ This subcommand has the following additional options: Whether to use disable sspi authentication, defaults to false (WinRM). * `--winrm-transport=WINRM_TRANSPORT` Specify which transport to use, defaults to negotiate (WinRM). +* `--enhanced-outcomes` + Includes enhanced outcome of controls in report data. ## supermarket @@ -640,7 +651,7 @@ inspec vendor PATH This subcommand has additional options: * `--overwrite`, `--no-overwrite` - Overwrite existing vendored dependencies and lockfile. + Overwrite existing vendored dependencies and lockfiles. ## version diff --git a/docs-chef-io/content/inspec/dsl_inspec.md b/docs-chef-io/content/inspec/dsl_inspec.md index 99646e49b..99a4ab882 100644 --- a/docs-chef-io/content/inspec/dsl_inspec.md +++ b/docs-chef-io/content/inspec/dsl_inspec.md @@ -249,6 +249,7 @@ end ``` This example checks for if certain pip packages are installed, but only if '/root/.aws' exists: + ```ruby control 'pip-packages-installed' do title 'Check if essential pips are installed' @@ -269,14 +270,37 @@ certain controls, which would 100% fail due to the way servers are prepared, but you know that the same control suites are reused later in different circumstances by different teams. +This example checks whether the Gnome Desktop is installed. If not installed, it resets the impact of the control to the new value which is passed as a hash with the impact key. + +Here, it resets it to 0: + +```ruby +control 'gnome-destkop-settings' do + impact 0.5 + desc 'some good settings' + desc 'check', 'check the settings file for good things' + desc 'fix', 'set the good things in the file /etc/gnome/settings' + tag nist: 'CM-6' + + only_if("The Gnome Desktop is not installed, this control is Not Applicable", impact: 0) { + package('gnome-desktop').installed? + } + + describe gnome_settings do + it should_be set_well + end +end +``` + Some notes about `only_if`: -- `only_if` applies to the entire `control`. If the results of the `only_if` +* `only_if` applies to the entire `control`. If the results of the `only_if` block evaluate to false, any Chef InSpec resources mentioned as part of a `describe` block will not be run. Additionally, the contents of the describe blocks will not be run. However, bare Ruby expressions and bare Chef InSpec resources (not assocated with a describe block) preceding the only_if statement will run +* `only_if` also accepts hash with impact key to reset the impact value of the control. Control's impact is helpful in determining it is enhanced outcome. To illustrate: diff --git a/docs-chef-io/content/inspec/profiles.md b/docs-chef-io/content/inspec/profiles.md index de0282590..0298fdcef 100644 --- a/docs-chef-io/content/inspec/profiles.md +++ b/docs-chef-io/content/inspec/profiles.md @@ -19,7 +19,7 @@ is a standalone structure with its own distribution and execution flow. A profile should have the following structure: -```YAML +```yaml examples/profile ├── README.md ├── controls @@ -67,7 +67,7 @@ Each profile must have an `inspec.yml` file that defines the following informati `name` is required; all other profile settings are optional. For example: -```YAML +```yaml name: ssh title: Basic SSH maintainer: Chef Software, Inc. @@ -89,7 +89,7 @@ inspec_version: "~> 2.1" The `inspec.yml` also supports embedded ERB in the file. For example: -```YAML +```yaml name: dummy title: InSpec Profile maintainer: The Authors @@ -130,7 +130,7 @@ platforms. The new families can restrict the platform family to `os`, `aws`, `az For example, to target anything running Debian Linux, use: -```YAML +```yaml name: ssh supports: - platform-name: debian @@ -138,7 +138,7 @@ supports: To target only Ubuntu version 20.04, use: -```YAML +```yaml name: ssh supports: - platform-name: ubuntu @@ -147,7 +147,7 @@ supports: To target the entire release of Ubuntu version 20.x, use: -```YAML +```yaml name: ssh supports: - platform-name: ubuntu @@ -156,7 +156,7 @@ supports: To target the Red Hat and derivative platforms such as CentOS and Oracle Linux, use: -```YAML +```yaml name: ssh supports: - platform-family: redhat @@ -164,7 +164,7 @@ supports: To target the entire Windows 2019 platform family, including Datacenter and Core Servers, use: -```YAML +```yaml name: ssh supports: - platform-name: windows_server_2019* @@ -172,7 +172,7 @@ supports: To target anything running on Amazon AWS, use: -```YAML +```yaml name: ssh supports: - platform: aws @@ -180,7 +180,7 @@ supports: To target all of these examples in a single `inspec.yml` file, use: -```YAML +```yaml name: ssh supports: - platform-name: debian @@ -206,7 +206,7 @@ needs to be specified in the including profile’s `inspec.yml` file in the `dep section. For each profile to be included, a location for the profile from where to be fetched and a name for the profile should be included. For example: -```YAML +```yaml depends: - name: linux-baseline url: https://github.com/dev-sec/linux-baseline/archive/master.tar.gz @@ -221,7 +221,7 @@ Chef InSpec supports a number of dependency sources. The `path` setting defines a profile that is located on disk. This setting is typically used during development of profiles and when debugging profiles. -```YAML +```yaml depends: - name: my-profile path: /absolute/path @@ -235,17 +235,17 @@ The `url` setting specifies a profile that is located at an HTTP- or HTTPS-based URL. The profile must be accessible via a HTTP GET operation and must be a valid profile archive (zip, tar, or tar.gz format). -```YAML +```yaml depends: - name: my-profile url: https://my.domain/path/to/profile.tgz - name: profile-via-git - url: https://github.com/myusername/myprofile-repo/archive/master.tar.gz + url: https://github.com/username/myprofile-repo/archive/master.tar.gz ``` `url` also supports basic authentication. -```YAML +```yaml depends: - name: my-profile url: https://my.domain/path/to/profile.tgz @@ -260,7 +260,7 @@ optional settings for branch, tag, commit, version, and relative_path. The sourc location is translated into a URL upon resolution. This type of dependency supports version constraints via semantic versioning as git tags. -```YAML +```yaml depends: - name: git-profile git: http://url/to/repo @@ -278,7 +278,7 @@ on Chef Supermarket. The source location is translated into a URL upon resolutio For example: -```YAML +```yaml depends: - name: supermarket-profile supermarket: supermarket-username/supermarket-profile @@ -293,7 +293,7 @@ or Chef Compliance server. For example: -```YAML +```yaml depends: - name: linux compliance: base/linux @@ -304,8 +304,7 @@ Any profile with ruby gem dependencies that need to be installed can be specifie For example, if you required any ruby library in a custom resource that needs a specific gem to be installed, then you can specify those gems in the metadata file. Chef InSpec will prompt to install the gems to `~/.inspec/gems` when you run your profile the first time. To skip the prompt and automatically install, pass the `--auto-install-gems` option to `inspec exec`. - -```YAML +```yaml gem_dependencies: - name: "mongo" version: ">= 2.3.12" @@ -412,7 +411,7 @@ for use in your profile. If two of your dependencies provide a resource with the same name, you can use the `require_resource` DSL function to disambiguate the two: -```YAML +```yaml require_resource(profile: 'my_dep', resource: 'my_res', as: 'my_res2') ``` @@ -436,7 +435,7 @@ of a profile. They are accessed by their name relative to this folder with Here is an example for reading and testing a list of ports. The folder structure is: -```YAML +```yaml examples/profile ├── controls │ ├── example.rb @@ -447,7 +446,7 @@ examples/profile With `services.yml` containing: -```YAML +```yaml - service_name: httpd-alpha port: 80 - service_name: httpd-beta diff --git a/docs-chef-io/content/inspec/reporters.md b/docs-chef-io/content/inspec/reporters.md index 0d025a98d..fbcd49e97 100644 --- a/docs-chef-io/content/inspec/reporters.md +++ b/docs-chef-io/content/inspec/reporters.md @@ -11,17 +11,17 @@ gh_repo = "inspec" weight = 50 +++ -Introduced in Chef InSpec 1.51.6 +A `reporter` is a facility for formatting and delivering the results of a Chef InSpec auditing run. Reporters were introduced in Chef InSpec 1.51.6. -A `reporter` is a facility for formatting and delivering the results of a Chef InSpec auditing run. +Chef InSpec allows you to output your test results to one or more reporters. -Chef InSpec allows you to output your test results to one or more reporters. Configure the reporter(s) using either the `--reporter` option or as part of the general config file using the `--config` (or `--json-config`, prior to v3.6) option. While you can configure multiple reporters to write to different files, only one reporter can output to the screen(stdout). +Configure the reporter(s) using either the `--reporter` option or as part of the general configuration file using the `--config` (or `--json-config`, prior to v3.6) option. While you can configure multiple reporters to write to different files, only one reporter can output to the screen(stdout). ## Syntax -You can specify one or more reporters using the `--reporter` cli flag. You can also specify a output by appending a path separated by a colon. +You can specify one or more reporters using the `--reporter` CLI flag. You can also specify an output by appending a path separated by a colon. -Output json to screen. +**Output json to screen** ```bash inspec exec example_profile --reporter json @@ -29,7 +29,7 @@ inspec exec example_profile --reporter json inspec exec example_profile --reporter json:- ``` -Output yaml to screen +**Output yaml to screen.** ```bash inspec exec example_profile --reporter yaml @@ -37,33 +37,33 @@ inspec exec example_profile --reporter yaml inspec exec example_profile --reporter yaml:- ``` -Output cli to screen and write json to a file. +**Output cli to screen and write json to a file.** ```bash inspec exec example_profile --reporter cli json:/tmp/output.json ``` -Output nothing to screen and write junit and html to a file. +**Output nothing to screen and write junit and html to a file.** ```bash inspec exec example_profile --reporter junit2:/tmp/junit.xml html:www/index.html ``` -Output json to screen and write to a file. Write junit to a file. +**Output json to screen and write to a file. Write junit to a file.** ```bash inspec exec example_profile --reporter json junit2:/tmp/junit.xml | tee out.json ``` -If you wish to pass the profiles directly after specifying the reporters you will need to use the end of options flag `--`. +If you wish to pass the profiles directly after specifying the reporters, you must use the end of options flag `--`. ```bash inspec exec --reporter json junit2:/tmp/junit.xml -- profile1 profile2 ``` -If you are using the cli option `--config`, you can also set reporters. +Using the CLI option `--config`, you can also set reporters. -Output cli to screen. +**Output cli to screen.** ```json { @@ -75,7 +75,7 @@ Output cli to screen. } ``` -Output cli to screen and write json to a file. +**Output cli to screen and write json to a file.** ```json { @@ -91,24 +91,25 @@ Output cli to screen and write json to a file. } ``` -Output real-time progress to screen with a progress bar. +**Output real-time progress to screen with a progress bar.** + ```bash inspec exec example_profile --reporter progress-bar ``` ## Reporter Options -The following are CLI options that may be used to modify reporter behavior. Many of these options allow you to limit the size of the report, because some reporters (such as the json-automate reporter) have a limit on the total size of the report that can be processed. +The following are CLI options that are used to modify reporter behavior. Many of these options allow you to limit the report size because some reporters (such as the json-automate reporter) limit on the total size of the report that can be processed. `--diff`, `--no-diff` -: Include a `diff` comparison of textual differences in failed test output (default: `true`). +: Include a `diff` comparison of textual differences in the failed test output (default: `true`). : Use `--no-diff` to limit the size of the report output when tests contain large amounts of text output. `--filter-empty-profiles` -: Remove empty profiles (those containing zero controls, such as resource packs) from the output of the reporter. +: Remove empty profiles (those containing zero controls, such as resource packs) from the reporter's output. `--reporter-backtrace-inclusion`, `--no-reporter-backtrace-inclusion` @@ -126,13 +127,21 @@ The following are CLI options that may be used to modify reporter behavior. Many : This may be used to limit the size of reports when failure messages are exceptionally large. +`--enhanced-outcomes` + +: Includes enhanced outcome of controls in report data. + +: The control level status outcomes are `Passed`, `Failed`, `Not Applicable (N/A)`, `Not Reviewed (N/R)`, or `Error (ERR)`. + +: Only supported for cli, progress-bar, html2, json, json-automate, automate, and yaml reporters. + ## Supported Reporters -The following are the current supported reporters: +The following are the currently supported reporters: ### cli -This is the basic text base report. It includes details about which tests passed and failed and includes an overall summary at the end. +This is the basic text based report. It includes details about tests that passed and failed and an overall summary at the end. ### json @@ -172,29 +181,39 @@ This reporter outputs the standard JUnit spec in XML format and is recommended f #### junit -This legacy reporter outputs nonstandard JUnit XML and is provided only for backwards compatibility. +This legacy reporter outputs nonstandard JUnit XML and is provided only for backward compatibility. ### progress -This reporter is very condensed and gives you a `.`(pass), `f`(fail), or `*`(skip) character per test and a small summary at the end. +This reporter is very condensed and provides you a `.`(pass), `f`(fail), or `*`(skip) character per test and a small summary at the end. ### progress-bar -This reporter outputs real-time progress of a running InSpec profile using a progress bar and prints running control's ID with an indicator of control's status (Passed, failed or skipped). +This reporter outputs the real-time progress of a running InSpec profile using a progress bar and prints the running control's ID with an indicator of the control's status (`Passed`, `failed`, or `skipped`). + +For example: + +![Progress Bar Reporter Outcome](/images/inspec/reporter_outcome_progress_bar.png) + +And reporter outcome with `--enhanced-outcomes` option: + +![Progress Bar Reporter Outcome with enhanced outcomes](/images/inspec/reporter_outcome_progress_bar_enhanced_outcomes.png) ### json-rspec -This reporter includes all information from the rspec runner. Unlike the json reporter this includes rspec specific details. +This reporter includes all information from the Rspec runner. Unlike the json reporter, this includes Rspec-specific details. ### html -This reporter is the legacy RSpec HTML reporter, which is retained for backwards compatibility. The report generated is not aware of profiles or controls, and only contains unsorted test information. Most users should migrate to the `html2` reporter for more complete data. +This reporter is the legacy RSpec HTML reporter retained for backward compatibility. The report generated is unaware of profiles or controls and only contains unsorted test information. Most users should migrate to the `html2` reporter for more complete data. ### html2 This reporter is an improved HTML reporter that contains full data about the structure of the profile, controls, and tests. The generated report renders HTML code for viewing your tests in a browser. -The `html2` reporter requires no configuration to function. However, two options--`alternate_css_file` and `alternate_js_file`--are available for customization. The options are set in the JSON-formatted configuration file that Chef InSpec consumes. For details, see [our configuration file documentation](/inspec/config/). +The `html2` reporter requires no configuration to function. However, options `--alternate_css_file` and `--alternate_js_file` are available for customization. The options are set in the JSON-formatted configuration file that Chef InSpec consumes. + +For details, see [our configuration file documentation](/inspec/config/). For example: @@ -212,17 +231,17 @@ For example: #### alternate_css_file -Specifies the full path to the location of a CSS file that will be read and inlined into the HTML report. The default CSS will not be included. +Specifies the full path to the location of a CSS file that is read and inlined into the HTML report. The default CSS is not included. #### alternate_js_file -Specifies the full path to the location of a JavaScript file that will be read and inlined into the HTML report. The default JavaScript will not be included. The JavaScript file should implement at least a `pageLoaded()` function, which will be called by the `onload` event of the HTML `body` element. +Specifies the full path to the location of a JavaScript file that is read and inlined into the HTML report. The default JavaScript is included. The JavaScript file should implement at least a `pageLoaded()` function, which is called by the `onload` event of the HTML `body` element. ## Automate Reporter -The `automate` reporter type is a special reporter which will send its results over the network to [Chef Automate]({{< relref "/automate/">}}). To use this reporter you must pass in the correct configuration via a json config `--config`. +The `automate` reporter type is a special reporter which sends its results over the network to [Chef Automate]({{< relref "/automate/">}}). To use this reporter, you must pass in the correct configuration via a json configuration `--config`. -Example config: +Example Configuration: ```json { @@ -241,44 +260,34 @@ Example config: ### Mandatory fields -#### stdout +`stdout` +: Either suppress or shows the automate report in the CLI screen on completion. -This will either suppress or show the automate report in the CLI screen on completion +`url` +: Automate 2 url. Append `data-collector/v0/` at the end. -#### url - -This is your Automate 2 url. Append `data-collector/v0/` at the end. - -#### token - -This is your Automate 2 token. You can generate this token by navigating to the admin tab of A2 and then api keys. +`token` +: Automate 2 tokens. You can generate this token by navigating to the **admin** tab of A2 and then clicking **API keys**. ### Optional fields -#### insecure +`insecure` +: Disables or enables the SSL check when accessing the Automate 2 instance. -This will disable or enable the ssl check when accessing the Automate 2 instance. +`node_name` +: Node name which shows up in Automate. -#### node_name +`node_uuid` +: Node UUID, which shows up in Chef Automate. Use a single static UUID per node for all your reports. You must specify a `node_uuid` in the Chef InSpec configuration file if running Chef InSpec outside of an audit cookbook or another environment where a `chef_guid` or `node_uuid` is already known to Chef InSpec. -This will be the node name which shows up in Automate. +`environment` +: Sets the environment metadata for Automate. -#### node_uuid +## json-Automate Reporter -This will be the node UUID which shows up in Chef Automate. Use a single static UUID -per node for all your reports. You must specify a `node_uuid` in the Chef InSpec -configuration file if running Chef InSpec outside of an audit cookbook or another -environment where a `chef_guid` or `node_uuid` is already known to Chef InSpec. +The `json-automate` reporter is a special reporter that prepares the data format used by the Automate reporter. `json-automate` does not communicate on the network; instead, it simply produces the JSON report format that Automate would be consuming. Notably, the report is based on the `json` reporter, with the following modifications: -#### environment +- Controls appearing in child profiles are de-duplicated by ID, merging into the parent profile. +- Child profiles are deleted, flattening the report. -This will set the environment metadata for Automate. - -## JSON-Automate Reporter - -The `json-automate` reporter is a special reporter that prepares the data format used by the Automate reporter. `json-automate` does not communicate on the network; rather it simply produces the JSON report format that Automate would be consuming. Notably, the report is based on the `json` reporter, with the following modifications: - - * Controls that appear in child profiles are de-duplicated by ID, merging up into the parent profile. - * Child profiles are deleted, flattening the report. - -The `json-automate` reporter is primarily used for internal needs, but some users may find it useful if they want a JSON based reporter that merges controls. +The `json-automate` reporter is primarily used for internal needs, but some users may find it helpful if they want a JSON-based reporter that merges controls. diff --git a/docs-chef-io/content/inspec/resources/_index.md b/docs-chef-io/content/inspec/resources/_index.md index 887bc1cc4..8e4ca031e 100644 --- a/docs-chef-io/content/inspec/resources/_index.md +++ b/docs-chef-io/content/inspec/resources/_index.md @@ -56,3 +56,7 @@ The following resources work on Windows operating systems. ## Habitat {{< inspec/inspec_resources platform="habitat" >}} + +## Kubernetes + +{{< inspec/inspec_resources platform="k8s" >}} diff --git a/docs-chef-io/content/inspec/resources/auditd.md b/docs-chef-io/content/inspec/resources/auditd.md index eec369226..6d90aa565 100644 --- a/docs-chef-io/content/inspec/resources/auditd.md +++ b/docs-chef-io/content/inspec/resources/auditd.md @@ -11,7 +11,7 @@ platform = "linux" parent = "inspec/resources/os" +++ -Use the `auditd` Chef InSpec audit resource to test the rules for logging that exist on the system. The audit.rules file is typically located under /etc/audit/ and contains the list of rules that define what is captured in log files. These rules are output using the auditctl -l command. This resource supports versions of `audit` >= 2.3. +Use the `auditd` Chef InSpec audit resource to test the rules for logging that exist on the system. The audit.rules file is typically located under /etc/audit/ and contains the list of rules that define what is captured in log files. These rules are output using the `auditctl -l` command. This resource supports versions of `audit` >= 2.3. ## Availability diff --git a/docs-chef-io/content/inspec/resources/azure_virtual_machine_data_disk.md b/docs-chef-io/content/inspec/resources/azure_virtual_machine_data_disk.md deleted file mode 100644 index 001290719..000000000 --- a/docs-chef-io/content/inspec/resources/azure_virtual_machine_data_disk.md +++ /dev/null @@ -1,226 +0,0 @@ -+++ -title = "azure_virtual_machine_data_disk resource" -draft = false -gh_repo = "inspec" -platform = "azure" - -[menu] - [menu.inspec] - title = "azure_virtual_machine_data_disk" - identifier = "inspec/resources/azure/azure_virtual_machine_data_disk.md azure_virtual_machine_data_disk resource" - parent = "inspec/resources/azure" -+++ - -Use this resource to ensure that a specific data disk attached to a machine has been created properly. - -## Availability - -### Installation - -{{% inspec/inspec_installation %}} - -### Version - -This resource first became available in v2.0.16 of InSpec. - -## Syntax - -The name of the resource group and machine are required to use this resource. - - describe azure_virtual_machine_data_disk(group_name: 'InSpec-Azure', name: 'MyVM') do - its('property') { should eq 'value' } - end - -where: - -- `MyVm` is the name of the virtual machine as seen in Azure. (It is **not** the hostname of the machine) -- `InSpec-Azure` is the name of the resource group that the machine is in. -- `property` is a resource property -- `value` is the expected output from the matcher - -## Examples - -The following examples show to use this Chef InSpec audit resource. - -### Check that the first data disk is of the correct size - - describe azure_virtual_machine_data_disk(group_name: 'InSpec-Azure', name: 'Linux-Internal-VM').where(number: 1) do - its('size') { should cmp >= 15 } - end - -## Resource Parameters - -- `group_name` -- `name` -- `apiversion` - -## Parameter Examples - -The options that can be passed to the resource are as follows. - -### `group_name` (required) - -Use this parameter to define the Azure Resource Group to be tested. - - describe azure_virtual_machine_data_disk(group_name: 'InSpec-Azure') do - ... - end - -### name - -Use this parameter to define the name of the Azure resource to test. - - describe azure_virtual_machine_data_disk(group_name: 'InSpec-Azure', name: 'Windows-Internal-VM') do - ... - end - -### apiversion - -The API Version to use when querying the resource. Defaults to the latest version for the resource. - - describe azure_virtual_machine_data_disk(group_name: 'InSpec-Azure', name: 'Windows-Internal-VM', apiversion: '2.0') do - ... - end - -These options can also be set using the environment variables: - -- `AZURE_RESOURCE_GROUP_NAME` -- `AZURE_RESOURCE_NAME` -- `AZURE_RESOURCE_API_VERSION` - -When the options have been set as well as the environment variables, the environment variables take priority. - -## Filter Criteria - -- `number` -- `disk` - -## Filter Examples - -### disk - -The zero based index of the disk attached to the machine. - - describe azure_virtual_machine_data_disk(group_name: 'InSpec-Azure', name: 'Windows-Internal-VM').where(disk: 0) - end - -### number - -The '1' based index of the disk attached to the machine. - - describe azure_virtual_machine_data_disk(group_name: 'InSpec-Azure', name: 'Windows-Internal-VM').where(number: 1) - end - -## Properties - -- `count`, `disk`, `number`, `name`, `size`, `lun`, `caching`, `create_option`, `is_managed_disk?`, `vhd_uri`, `storage_account_name`, `storage_account_type`, `id`, `subscription_id`, `resource_group` - -## Property Examples - -### count - -Returns the number of data disks attached to the machine - - its('count') { should eq 1 } - -### name - -Returns a string of the name of the disk. - - its('name') { should cmp 'linux-external-datadisk-1' } - -### size - -Returns an integer of size of this disk in GB. - - its('size') { should cmp >= 15 } - -### lun - -The disk number as reported by Azure. Has a zero-based index value. - - its('lun') { should cmp 0 } - -### caching - -String stating the caching that has been set on the disk. - - its('caching') { should cmp 'none' } - -### create_option - -How the disk was created. Typically for data disks, this will be the string value 'Empty'. - - its('create_option') { should cmp 'Empty' } - -### is_managed_disk? - -Boolean stating if the disk is a managed disk or not. If it is not a managed disk then it is one that is stored in a Storage Account. - - its('is_managed_disk?') { should cmp 'false' } - -### vhd_uri - -If this is _not_ a managed disk, then the `vhd_uri` will be the full URI to the disk in the storage account. - - its('vhd_uri') { should cmp 'https://primary_storage.blob.core.windows.net/container_name/vm_name.vhd' } - -### storage_account_name - -If this is _not_ a managed disk this will be the storage account name in which the disk is stored. - -This derived from the `vhd_uri`. - - its('storage_account_name') { should cmp 'primary_storage' } - -### storage_account_type - -If this is a managed disk this is the storage account type, e.g. `Standard_LRS`. - - its('storage_account_type') { should cmp 'Standard_LRS' } - -### id - -If this is a managed disk then this is the fully qualified id for the disk in Azure. - - its('id') { should cmp '/subscriptions/1234abcd-e567-890f-g123-456h78i9jkl0/resourceGroups/InSpec-Azure' } - -### subscription_id - -If this is a managed disk, this returns the subscription id of where the disk is stored. - -This is derived from the `id`. - - its('subscription_id') { should cmp '1234abcd-e567-890f-g123-456h78i9jkl0' } - -### resource_group - -If this is a managed disk, this returns the resource group in which the disk is stored. - -This is derived from the `id`. - - its('resource_group') { should cmp 'InSpec-Azure' } - -## Matchers - -This Chef InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [matchers page](/inspec/matchers/). - -The following properties are applied to the virtual machine itself and not specific disks. - -### have_data_disks - -Returns a boolean denoting if any data disks are attached to the machine. - - it { should have_data_disks } - -### have_managed_disks - -Returns a boolean stating if the machine has Managed Disks for data disks. - - it { should have_managed_disks } - -## References - -- [Azure Ruby SDK - Compute](https://github.com/Azure/azure-sdk-for-ruby/tree/master/management/azure_mgmt_compute) -- [Linux Internal Data Disks](https://github.com/inspec/inspec/blob/main/test/integration/azure/verify/controls/virtual_machine_linux_external_vm_datadisk.rb) -- [Windows Internal Data Disk](https://github.com/inspec/inspec/blob/main/test/integration/azure/verify/controls/virtual_machine_windows_internal_vm_datadisk.rb) diff --git a/docs-chef-io/content/inspec/resources/azurerm_ad_users.md b/docs-chef-io/content/inspec/resources/azurerm_ad_users.md index e69224ba3..1e9d37124 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_ad_users.md +++ b/docs-chef-io/content/inspec/resources/azurerm_ad_users.md @@ -82,7 +82,7 @@ The following examples show how to use this InSpec audit resource. Filters the results to include only those Users that match the given name. This is a string value. - describe azurerm_ad_users.where{ displayName.eql?('Joe Bloggs') } do + describe azurerm_ad_users.where{ displayName.eql?('Haris Shefu') } do it { should exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_cosmosdb_database_account.md b/docs-chef-io/content/inspec/resources/azurerm_cosmosdb_database_account.md index 5ceb3791e..94a46310b 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_cosmosdb_database_account.md +++ b/docs-chef-io/content/inspec/resources/azurerm_cosmosdb_database_account.md @@ -104,7 +104,7 @@ Indicates the type of database account, e.g. `GlobalDocumentDB`, `MongoDB` ### tags -Resource tags applied to the ComsosDb Account. +Resource tags applied to the Cosmos DB Account. ### properties diff --git a/docs-chef-io/content/inspec/resources/azurerm_event_hub_authorization_rule.md b/docs-chef-io/content/inspec/resources/azurerm_event_hub_authorization_rule.md index 02d987fa4..5fb6e7492 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_event_hub_authorization_rule.md +++ b/docs-chef-io/content/inspec/resources/azurerm_event_hub_authorization_rule.md @@ -49,7 +49,7 @@ This resource first became available in 1.11.0 of the inspec-azure resource pack The `resource_group`, `namespace_name`, `event_hub_name` and `authorization_rule_name` must be given as a parameter. - describe azurerm_event_hub_authorization_rule(resource_group: 'my-rg', namespace_name 'my-event-hub-ns', event_hub_name: 'myeventhub', authorization_rule_name: 'my-auth-rule') do + describe azurerm_event_hub_authorization_rule(resource_group: 'my-rg', namespace_name 'event-hub-namespace', event_hub_name: 'event-hub', authorization_rule_name: 'my-auth-rule') do it { should exist } end @@ -57,13 +57,13 @@ The `resource_group`, `namespace_name`, `event_hub_name` and `authorization_rule If an Event Hub Authorization Rule is referenced with a valid `Resource Group`, `Namespace Name`, `Event Hub Name` and `Authorization Rule Name` - describe azurerm_event_hub_authorization_rule(resource_group: 'my-rg', namespace_name: 'my-event-hub-ns', event_hub_endpoint: 'myeventhub', authorization_rule: 'my-auth-rule') do + describe azurerm_event_hub_authorization_rule(resource_group: 'my-rg', namespace_name: 'event-hub-namespace', event_hub_endpoint: 'event-hub', authorization_rule: 'my-auth-rule') do it { should exist } end If a Event Hub Authorization Rule is referenced with an invalid `Resource Group`, `Namespace Name`, `Event Hub Name` or `Authorization Rule Name` - describe azurerm_event_hub_namespace(resource_group: 'invalid-rg', namespace_name: 'i-dont-exist', event_hub_endpoint: 'fakeendpoint', authorization_rule: 'fake-auth-rule') do + describe azurerm_event_hub_namespace(resource_group: 'invalid-rg', namespace_name: 'i-do-not-exist', event_hub_endpoint: 'fake-endpoint', authorization_rule: 'fake-auth-rule') do it { should_not exist } end @@ -117,7 +117,7 @@ requests are always welcome. ### exists - describe azurerm_event_hub_authorization_rule(resource_group: 'my-rg', namespace_name 'my-event-hub-ns', event_hub_name: 'myeventhub', authorization_rule_name: 'my-auth-rule') do + describe azurerm_event_hub_authorization_rule(resource_group: 'my-rg', namespace_name 'event-hub-namespace', event_hub_name: 'event-hub', authorization_rule_name: 'my-auth-rule') do it { should exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_event_hub_event_hub.md b/docs-chef-io/content/inspec/resources/azurerm_event_hub_event_hub.md index 67e9514f3..c86e843b6 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_event_hub_event_hub.md +++ b/docs-chef-io/content/inspec/resources/azurerm_event_hub_event_hub.md @@ -49,7 +49,7 @@ This resource first became available in 1.11.0 of the inspec-azure resource pack The `resource_group`, `namespace_name` and `event_hub_name` must be given as a parameter. - describe azurerm_event_hub_event_hub(resource_group: 'my-rg', namespace_name 'my-event-hub-ns', event_hub_name 'myeventhub') do + describe azurerm_event_hub_event_hub(resource_group: 'my-rg', namespace_name 'my-event-hub-ns', event_hub_name 'event-hub') do it { should exist } end @@ -57,13 +57,13 @@ The `resource_group`, `namespace_name` and `event_hub_name` must be given as a p If an Event Hub Event Hub is referenced with a valid `Resource Group`, `Namespace Name` and `Event Hub Name` - describe azurerm_event_hub_event_hub(resource_group: 'my-rg', namespace_name: 'my-event-hub-ns', event_hub_name 'myeventhub') do + describe azurerm_event_hub_event_hub(resource_group: 'my-rg', namespace_name: 'my-event-hub-ns', event_hub_name 'event-hub') do it { should exist } end If a Event Hub Event Hub is referenced with an invalid `Resource Group`, `Namespace Name` and `Event Hub Name` - describe azurerm_event_hub_event_hub(resource_group: 'invalid-rg', namespace_name: 'i-dont-exist', event_hub_name 'i-dont-exist') do + describe azurerm_event_hub_event_hub(resource_group: 'invalid-rg', namespace_name: 'i-do-not-exist', event_hub_name 'i-do-not-exist') do it { should_not exist } end @@ -86,7 +86,7 @@ Azure resource ID. ### name -Event Hub name, e.g. `myeventhub`. +Event Hub name, e.g. `event-hub`. ### type @@ -116,7 +116,7 @@ requests are always welcome. ### exists - describe azurerm_event_hub_event_hub(resource_group: 'my-rg', namespace_name: 'my-event-hub-ns', event_hub_name: 'myeventhub') do + describe azurerm_event_hub_event_hub(resource_group: 'my-rg', namespace_name: 'my-event-hub-ns', event_hub_name: 'event-hub') do it { should exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_event_hub_namespace.md b/docs-chef-io/content/inspec/resources/azurerm_event_hub_namespace.md index 187ae7192..9f3087290 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_event_hub_namespace.md +++ b/docs-chef-io/content/inspec/resources/azurerm_event_hub_namespace.md @@ -63,7 +63,7 @@ If an Event Hub Namespace is referenced with a valid `Resource Group` and `Names If an Event Hub Namespace is referenced with an invalid `Resource Group` or `Namespace Name` - describe azurerm_event_hub_namespace(resource_group: 'invalid-rg', namespace_name: 'i-dont-exist') do + describe azurerm_event_hub_namespace(resource_group: 'invalid-rg', namespace_name: 'i-do-not-exist') do it { should_not exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_iothub.md b/docs-chef-io/content/inspec/resources/azurerm_iothub.md index adbbecc67..9b96fc186 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_iothub.md +++ b/docs-chef-io/content/inspec/resources/azurerm_iothub.md @@ -63,7 +63,7 @@ If an IoT Hub is referenced with a valid `Resource Group` and `Resource Name` If an IoT Hub is referenced with an invalid `Resource Group` or `Resource Name` - describe azurerm_iothub(resource_group: 'invalid-rg', resource_name: 'i-dont-exist') do + describe azurerm_iothub(resource_group: 'invalid-rg', resource_name: 'i-do-not-exist') do it { should_not exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_iothub_event_hub_consumer_group.md b/docs-chef-io/content/inspec/resources/azurerm_iothub_event_hub_consumer_group.md index a23bab423..6823ad40f 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_iothub_event_hub_consumer_group.md +++ b/docs-chef-io/content/inspec/resources/azurerm_iothub_event_hub_consumer_group.md @@ -50,7 +50,7 @@ This resource first became available in 1.11.0 of the inspec-azure resource pack The `resource_group`, `resource_name`, `event_hub_endpoint` and `consumer_group` must be given as a parameter. - describe azurerm_iothub_event_hub_consumer_group(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'myeventhub', consumer_group: 'my-consumer-group') do + describe azurerm_iothub_event_hub_consumer_group(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'event-hub', consumer_group: 'my-consumer-group') do it { should exist } end @@ -58,13 +58,13 @@ The `resource_group`, `resource_name`, `event_hub_endpoint` and `consumer_group` If an IoT Hub Event Hub Consumer Group is referenced with a valid `Resource Group`, `Resource Name`, `Event Hub Endpoint` and `Consumer Group` - describe azurerm_iothub_event_hub_consumer_group(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'myeventhub', consumer_group: 'my-consumer-group') do + describe azurerm_iothub_event_hub_consumer_group(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'event-hub', consumer_group: 'my-consumer-group') do it { should exist } end If an IoT Hub Event Hub Consumer Group is referenced with an invalid `Resource Group`, `Resource Name`, `Event Hub Endpoint` or `Consumer Group` - describe azurerm_iothub_event_hub_consumer_group(resource_group: 'invalid-rg', resource_name: 'invalid-resource', event_hub_endpoint: 'invalideventhub', consumer_group: 'invalid-consumer-group') do + describe azurerm_iothub_event_hub_consumer_group(resource_group: 'invalid-rg', resource_name: 'invalid-resource', event_hub_endpoint: 'invalid-event-hub', consumer_group: 'invalid-consumer-group') do it { should_not exist } end @@ -123,7 +123,7 @@ requests are always welcome. ### exists - describe azurerm_iothub_event_hub_consumer_group(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'myeventhub', consumer_group: 'my-consumer-group') do + describe azurerm_iothub_event_hub_consumer_group(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'event-hub', consumer_group: 'my-consumer-group') do it { should exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_iothub_event_hub_consumer_groups.md b/docs-chef-io/content/inspec/resources/azurerm_iothub_event_hub_consumer_groups.md index b78b3c85c..3dead3273 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_iothub_event_hub_consumer_groups.md +++ b/docs-chef-io/content/inspec/resources/azurerm_iothub_event_hub_consumer_groups.md @@ -49,7 +49,7 @@ This resource first became available in 1.11.0 of the inspec-azure resource pack The `resource_group`, `resource_name` and `event_hub_endpoint` must be given as a parameter. - describe azurerm_iothub_event_hub_consumer_groups(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'myeventhub') do + describe azurerm_iothub_event_hub_consumer_groups(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'event-hub') do its('names') { should include "my-consumer-group"} its('types') { should include 'Microsoft.Devices/IotHubs/EventHubEndpoints/ConsumerGroups' } end @@ -58,7 +58,7 @@ The `resource_group`, `resource_name` and `event_hub_endpoint` must be given as If a IoT Hub Event Hub Consumer Groups is referenced with a valid `Resource Group`, `Resource Name` and `Event Hub Endpoint` - describe azurerm_iothub_event_hub_consumer_groups(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'myeventhub') do + describe azurerm_iothub_event_hub_consumer_groups(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'event-hub') do it { should exist } end @@ -126,7 +126,7 @@ requests are always welcome. ### exists - describe azurerm_iothub_event_hub_consumer_group(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'myeventhub') do + describe azurerm_iothub_event_hub_consumer_group(resource_group: 'my-rg', resource_name 'my-iot-hub', event_hub_endpoint: 'event-hub') do it { should exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_load_balancer.md b/docs-chef-io/content/inspec/resources/azurerm_load_balancer.md index d33f12cef..b5d010ba4 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_load_balancer.md +++ b/docs-chef-io/content/inspec/resources/azurerm_load_balancer.md @@ -63,7 +63,7 @@ If a Load Balancer is referenced with a valid `Resource Group` and `Load balance If a Load Balancer is referenced with an invalid `Resource Group` or `Load balancer Name` - describe azurerm_load_balancer(resource_group: 'invalid-rg', loadbalancer_name: 'i-dont-exist') do + describe azurerm_load_balancer(resource_group: 'invalid-rg', loadbalancer_name: 'i-do-not-exist') do it { should_not exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_management_group.md b/docs-chef-io/content/inspec/resources/azurerm_management_group.md index 6a5aca62f..2e1f57569 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_management_group.md +++ b/docs-chef-io/content/inspec/resources/azurerm_management_group.md @@ -256,12 +256,12 @@ requests are always welcome. ### exists # If a management group is found it will exist - describe azurerm_management_group(groupd_id: 'MyGroupId') do + describe azurerm_management_group(group_id: 'MyGroupId') do it { should exist } end # management groups that aren't found will not exist - describe azurerm_management_group(groupd_id: 'DoesNotExist') do + describe azurerm_management_group(group_id: 'DoesNotExist') do it { should_not exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_mysql_databases.md b/docs-chef-io/content/inspec/resources/azurerm_mysql_databases.md index 74c7eb74f..2ab3a29a9 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_mysql_databases.md +++ b/docs-chef-io/content/inspec/resources/azurerm_mysql_databases.md @@ -46,7 +46,7 @@ This resource first became available in 1.6.0 of the inspec-azure resource pack. ## Syntax -An `azurerm_mysql_databases` resource block returns all MySQL Databases on a MySQL Server, within a Rsource Group. +An `azurerm_mysql_databases` resource block returns all MySQL Databases on a MySQL Server, within a resource group. describe azurerm_mysql_databases(resource_group: ..., server_name: ...) do ... diff --git a/docs-chef-io/content/inspec/resources/azurerm_mysql_server.md b/docs-chef-io/content/inspec/resources/azurerm_mysql_server.md index fac816de9..99f86c05c 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_mysql_server.md +++ b/docs-chef-io/content/inspec/resources/azurerm_mysql_server.md @@ -63,7 +63,7 @@ If a SQL Server is referenced with a valid `Resource Group` and `Server Name` If a SQL Server is referenced with an invalid `Resource Group` or `Server Name` - describe azurerm_sql_server(resource_group: 'invalid-rg', server_name: 'i-dont-exist') do + describe azurerm_sql_server(resource_group: 'invalid-rg', server_name: 'i-do-not-exist') do it { should_not exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_network_interface.md b/docs-chef-io/content/inspec/resources/azurerm_network_interface.md index 778edc7fa..f26eec141 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_network_interface.md +++ b/docs-chef-io/content/inspec/resources/azurerm_network_interface.md @@ -62,7 +62,7 @@ If a Network Interface is referenced with a valid `Resource Group` and `Name` If a Network Interface is referenced with an invalid `Resource Group` or `Name` - describe azurerm_network_interface(resource_group: 'invalid-rg', name: 'i-dont-exist') do + describe azurerm_network_interface(resource_group: 'invalid-rg', name: 'i-do-not-exist') do it { should_not exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_postgresql_server.md b/docs-chef-io/content/inspec/resources/azurerm_postgresql_server.md index df89f0139..6636d2f66 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_postgresql_server.md +++ b/docs-chef-io/content/inspec/resources/azurerm_postgresql_server.md @@ -63,7 +63,7 @@ If a PostgreSQL Server is referenced with a valid `Resource Group` and `Server N If a PostgreSQL Server is referenced with an invalid `Resource Group` or `Server Name` - describe azurerm_postgresql_server(resource_group: 'invalid-rg', server_name: 'i-dont-exist') do + describe azurerm_postgresql_server(resource_group: 'invalid-rg', server_name: 'i-do-not-exist') do it { should_not exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_sql_databases.md b/docs-chef-io/content/inspec/resources/azurerm_sql_databases.md index 7b3641ef7..9a68ce00b 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_sql_databases.md +++ b/docs-chef-io/content/inspec/resources/azurerm_sql_databases.md @@ -46,7 +46,7 @@ This resource first became available in 1.2.0 of the inspec-azure resource pack. ## Syntax -An `azurerm_sql_databases` resource block returns all SQL Databases on a SQL Server, within a Rsource Group. +An `azurerm_sql_databases` resource block returns all SQL Databases on a SQL Server, within a resource group. describe azurerm_sql_databases(resource_group: ..., server_name: ...) do ... diff --git a/docs-chef-io/content/inspec/resources/azurerm_sql_server.md b/docs-chef-io/content/inspec/resources/azurerm_sql_server.md index 7c23e13aa..2bb603db0 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_sql_server.md +++ b/docs-chef-io/content/inspec/resources/azurerm_sql_server.md @@ -63,7 +63,7 @@ If a SQL Server is referenced with a valid `Resource Group` and `Server Name` If a SQL Server is referenced with an invalid `Resource Group` or `Server Name` - describe azurerm_sql_server(resource_group: 'invalid-rg', server_name: 'i-dont-exist') do + describe azurerm_sql_server(resource_group: 'invalid-rg', server_name: 'i-do-not-exist') do it { should_not exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_subnet.md b/docs-chef-io/content/inspec/resources/azurerm_subnet.md index 92ddb8865..b0ab59c1b 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_subnet.md +++ b/docs-chef-io/content/inspec/resources/azurerm_subnet.md @@ -123,13 +123,13 @@ The subnet's id. Id will be in format: - '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/Inspec-Azure-mmclane/providers/Microsoft.Network/virtualNetworks/Inspec-VNet/subnets/Inspec-Subnet' + '/subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks/Inspec-VNet/subnets/Inspec-Subnet' ### name The subnets's name. - its('name') { should eq('MySubnetName') } + its('name') { should eq('SubnetName') } ### type diff --git a/docs-chef-io/content/inspec/resources/azurerm_subnets.md b/docs-chef-io/content/inspec/resources/azurerm_subnets.md index 8cb9f88f5..2d1d2ed86 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_subnets.md +++ b/docs-chef-io/content/inspec/resources/azurerm_subnets.md @@ -55,7 +55,7 @@ The `resource_group` and 'vnet' must be given as a parameter. ## Examples - # Exists if any subnetss exist for a given virtual network in the resource group + # Exists if any subnets exist for a given virtual network in the resource group describe azurerm_subnets(resource_group: 'MyResourceGroup', vnet: 'MyVnetName') do it { should exist } end diff --git a/docs-chef-io/content/inspec/resources/azurerm_virtual_network.md b/docs-chef-io/content/inspec/resources/azurerm_virtual_network.md index c3d737788..116b375d8 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_virtual_network.md +++ b/docs-chef-io/content/inspec/resources/azurerm_virtual_network.md @@ -121,7 +121,7 @@ The virtual network's id. Id will be in format: - '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/Inspec-Azure-mmclane/providers/Microsoft.Network/virtualNetworks/MyVnetName' + '/subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks/MyVnetName' ### name diff --git a/docs-chef-io/content/inspec/resources/azurerm_webapp.md b/docs-chef-io/content/inspec/resources/azurerm_webapp.md index a46207fbd..e660d0cfc 100644 --- a/docs-chef-io/content/inspec/resources/azurerm_webapp.md +++ b/docs-chef-io/content/inspec/resources/azurerm_webapp.md @@ -100,7 +100,7 @@ The Resource Group as well as the Webapp name. - `auth_settings` - `configuration` -All of the attributes are avialable via dot notation. This is an example of the currently available attributes. +All of the attributes are available via dot notation. This is an example of the currently available attributes. ```ruby control 'azurerm_webapp' do diff --git a/docs-chef-io/content/inspec/resources/command.md b/docs-chef-io/content/inspec/resources/command.md index 7b851d30d..ed498e7c3 100644 --- a/docs-chef-io/content/inspec/resources/command.md +++ b/docs-chef-io/content/inspec/resources/command.md @@ -167,7 +167,7 @@ By default the command that is ran is shown in the Chef InSpec output. This can The following examples show how to use `redact_regex`: # Example without capture groups - describe command('myapp -p secretpassword -d no_redact', redact_regex: /-p .* -d/) do + describe command('myapp -p secret_password -d no_redact', redact_regex: /-p .* -d/) do its('exit_status') { should cmp 0 } end @@ -178,7 +178,7 @@ The following examples show how to use `redact_regex`: # Example with capture groups # Each set of parenthesis is a capture group. # Anything in the two capture groups will not be 'REDACTED' - describe command('myapp -p secretpassword -d no_redact', redact_regex: /(-p ).*( -d)/) do + describe command('myapp -p secret_password -d no_redact', redact_regex: /(-p ).*( -d)/) do its('exit_status') { should cmp 0 } end diff --git a/docs-chef-io/content/inspec/resources/cpan.md b/docs-chef-io/content/inspec/resources/cpan.md index ae85e2865..d0752e118 100644 --- a/docs-chef-io/content/inspec/resources/cpan.md +++ b/docs-chef-io/content/inspec/resources/cpan.md @@ -60,7 +60,7 @@ This resource uses package names and perl library paths as resource parameters. Hint: You can pass multiple paths separated with a colon `/path/to/perl5/lib:/usr/share/perl5/vendor_perl/lib/perl5` - describe cpan('DBD::Pg', '/home/jdoe/perl5/lib/perl5') do + describe cpan('DBD::Pg', '/home/username/perl5/lib/perl5') do it { should be_installed } end diff --git a/docs-chef-io/content/inspec/resources/cron.md b/docs-chef-io/content/inspec/resources/cron.md index 69ab1df81..c27fe9273 100644 --- a/docs-chef-io/content/inspec/resources/cron.md +++ b/docs-chef-io/content/inspec/resources/cron.md @@ -55,7 +55,7 @@ The following examples show how to use this audit resource. it { should have_entry "5 * * * * /some/scheduled/task.sh" } end -### Test to ensure myuser's crontab has a particular cron entry +### Test to ensure a user's crontab has a particular cron entry describe cron('MY_USER') do it { should have_entry "5 * * * * /some/scheduled/task.sh" } diff --git a/docs-chef-io/content/inspec/resources/crontab.md b/docs-chef-io/content/inspec/resources/crontab.md index c30e50358..1dd99dcb5 100644 --- a/docs-chef-io/content/inspec/resources/crontab.md +++ b/docs-chef-io/content/inspec/resources/crontab.md @@ -55,9 +55,9 @@ The following examples show how to use this Chef InSpec audit resource. its('commands') { should include '/path/to/some/script -option arg' } end -### Test that myuser's crontab entry for command '/home/myuser/build.sh' runs every minute +### Test that username's crontab entry for command '/home/username/build.sh' runs every minute - describe crontab('myuser').commands('/home/myuser/build.sh') do + describe crontab('username').commands('/home/username/build.sh') do its('hours') { should cmp '*' } its('minutes') { should cmp '*' } end diff --git a/docs-chef-io/content/inspec/resources/docker.md b/docs-chef-io/content/inspec/resources/docker.md index 6cc96dc68..ad0d81cb2 100644 --- a/docs-chef-io/content/inspec/resources/docker.md +++ b/docs-chef-io/content/inspec/resources/docker.md @@ -25,7 +25,7 @@ This resource first became available in v1.21.0 of InSpec. ## Syntax -A `docker` resource block declares allows you to write test for many containers: +A `docker` resource block allows you to write tests for many containers: describe docker.containers do its('images') { should_not include 'u12:latest' } @@ -33,7 +33,7 @@ A `docker` resource block declares allows you to write test for many containers: or: - describe docker.containers.where { names == 'flamboyant_colden' } do + describe docker.containers.where { names == 'flamboyant_allen' } do it { should be_running } end @@ -45,7 +45,7 @@ where The `docker` resource block also declares allows you to write test for many images: describe docker.images do - its('repositories') { should_not include 'inssecure_image' } + its('repositories') { should_not include 'insecure_image' } end or if you want to query specific images: diff --git a/docs-chef-io/content/inspec/resources/docker_service.md b/docs-chef-io/content/inspec/resources/docker_service.md index f3f0da345..3323852f8 100644 --- a/docs-chef-io/content/inspec/resources/docker_service.md +++ b/docs-chef-io/content/inspec/resources/docker_service.md @@ -29,7 +29,7 @@ A `docker_service` resource block declares the service by name: describe docker_service('foo') do it { should exist } - its('id') { should eq '2ghswegspre1' } + its('id') { should eq 'docker-service-id' } its('repo') { should eq 'alpine' } its('tag') { should eq 'latest' } end @@ -38,7 +38,7 @@ A `docker_service` resource block declares the service by name: The resource allows you to pass in a service id: - describe docker_service(id: '2ghswegspre1') do + describe docker_service(id: 'docker-service-id') do ... end @@ -56,7 +56,7 @@ The following examples show how to use Chef InSpec `docker_service` resource. The `id` property returns the service id: - its('id') { should eq '2ghswegspre1' } + its('id') { should eq 'docker-service-id' } ### image @@ -104,7 +104,7 @@ The `tag` property tests the value of image tag: describe docker_service('foo') do it { should exist } - its('id') { should eq '2ghswegspre1' } + its('id') { should eq 'docker-service-id' } its('repo') { should eq 'alpine' } its('tag') { should eq 'latest' } end diff --git a/docs-chef-io/content/inspec/resources/etc_hosts.md b/docs-chef-io/content/inspec/resources/etc_hosts.md index 6b2c70e2a..e3016166d 100644 --- a/docs-chef-io/content/inspec/resources/etc_hosts.md +++ b/docs-chef-io/content/inspec/resources/etc_hosts.md @@ -77,7 +77,8 @@ The `all_host_names` property returns a two-dimensional string array where each its('ip_address') { should cmp '127.0.1.154' } end -### Test the primay name for where ip address is '::1' +### Test the primary name for where IP address is '::1' + describe etc_hosts.where { ip_address == '::1' } do its('primary_name') { should cmp 'localhost' } end diff --git a/docs-chef-io/content/inspec/resources/filesystem.md b/docs-chef-io/content/inspec/resources/filesystem.md index 70283a575..5e9463739 100644 --- a/docs-chef-io/content/inspec/resources/filesystem.md +++ b/docs-chef-io/content/inspec/resources/filesystem.md @@ -61,7 +61,7 @@ The `free_kb` property returns the size of available space on the partition in k its('size_kb') { should be >= 32000 } -## percent_free (Integrer) +## percent_free (Integer) The `percent_free` property returns the available free space on the partition, ranges from 0 to 100. diff --git a/docs-chef-io/content/inspec/resources/http.md b/docs-chef-io/content/inspec/resources/http.md index edac6b1a4..08d7b8912 100644 --- a/docs-chef-io/content/inspec/resources/http.md +++ b/docs-chef-io/content/inspec/resources/http.md @@ -180,7 +180,7 @@ You can include the username and password in the `proxy` parameter: The `proxy` parameter also accepts proxy options in hash format: - describe http('http://localhost:8080/ping', proxy: { uri: 'http://www.example.com:3128', user: 'username', password: 'proxypassword'}) do + describe http('http://localhost:8080/ping', proxy: { uri: 'http://www.example.com:3128', user: 'username', password: 'proxy-password'}) do ... end diff --git a/docs-chef-io/content/inspec/resources/iis_app.md b/docs-chef-io/content/inspec/resources/iis_app.md index cfe60e169..712293e06 100644 --- a/docs-chef-io/content/inspec/resources/iis_app.md +++ b/docs-chef-io/content/inspec/resources/iis_app.md @@ -70,7 +70,7 @@ For example: `physical_path` property returns the physical path of the application, such as `'C:\\inetpub\\wwwroot\\myapp'`. - its('phyiscal_path') { should eq 'C:\\inetpub\\wwwroot\\myapp' } + its('physical_path') { should eq 'C:\\inetpub\\wwwroot\\myapp' } ### protocols diff --git a/docs-chef-io/content/inspec/resources/ini.md b/docs-chef-io/content/inspec/resources/ini.md index 94c4a1bac..6d0cf6a40 100644 --- a/docs-chef-io/content/inspec/resources/ini.md +++ b/docs-chef-io/content/inspec/resources/ini.md @@ -55,7 +55,7 @@ Settings inside of sections, such as the following: In the event a section or setting name has a period in it, the alternate syntax can be used: - its(['section.with.a.dot.in.it', 'setting.name.with.dots']) { should cmp 'lotsadots' } + its(['section.with.a.dot.in.it', 'setting.name.with.dots']) { should cmp 'lots-of-dots' } ## Properties diff --git a/docs-chef-io/content/inspec/resources/ipfilter.md b/docs-chef-io/content/inspec/resources/ipfilter.md index c633b68e1..91cd47a79 100644 --- a/docs-chef-io/content/inspec/resources/ipfilter.md +++ b/docs-chef-io/content/inspec/resources/ipfilter.md @@ -69,6 +69,6 @@ For a full list of available matchers, please visit our [matchers page](/inspec/ ### have_rule -The `have_rule` matcher tests the named rule against the information in the output rule of `'ipftstat -io'`: +The `have_rule` matcher tests the named rule against the information in the output rule of `'ipfstat -io'`: it { should have_rule("RULE") } diff --git a/docs-chef-io/content/inspec/resources/key_rsa.md b/docs-chef-io/content/inspec/resources/key_rsa.md index 68b359ef8..9bb90a89a 100644 --- a/docs-chef-io/content/inspec/resources/key_rsa.md +++ b/docs-chef-io/content/inspec/resources/key_rsa.md @@ -29,16 +29,16 @@ This resource first became available in v1.18.0 of InSpec. An `key_rsa` resource block declares a `key file` to be tested. - describe key_rsa('mycertificate.key') do + describe key_rsa('certificate.key') do it { should be_private } it { should be_public } - its('public_key') { should match "-----BEGIN PUBLIC KEY-----\n3597459df9f3982" } + its('public_key') { should match "PUBLIC_KEY" } its('key_length') { should eq 2048 } end You can use an optional passphrase with `key_rsa` - describe key_rsa('mycertificate.key', 'passphrase') do + describe key_rsa('certificate.key', 'passphrase') do it { should be_private } end @@ -48,23 +48,23 @@ You can use an optional passphrase with `key_rsa` The `public_key` property returns the public part of the RSA key pair - describe key_rsa('/etc/pki/www.mywebsite.com.key') do - its('public_key') { should match "-----BEGIN PUBLIC KEY-----\n3597459df9f3982......" } + describe key_rsa('/etc/pki/www.example.com.key') do + its('public_key') { should match "RSA_PUBLIC_KEY" } end ### private_key (String) The `private_key` property returns the private key or the RSA key pair. - describe key_rsa('/etc/pki/www.mywebsite.com.key') do - its('private_key') { should match "-----BEGIN RSA PRIVATE KEY-----\nMIIJJwIBAAK......" } + describe key_rsa('/etc/pki/www.example.com.key') do + its('private_key') { should match "RSA_PRIVATE_KEY" } end ### key_length The `key_length` property allows testing the number of bits in the key pair. - describe key_rsa('/etc/pki/www.mywebsite.com.key') do + describe key_rsa('/etc/pki/www.example.com.key') do its('key_length') { should eq 2048 } end @@ -76,7 +76,7 @@ For a full list of available matchers, please visit our [matchers page](/inspec/ To verify if a key is public use the following: - describe key_rsa('/etc/pki/www.mywebsite.com.key') do + describe key_rsa('/etc/pki/www.example.com.key') do it { should be_public } end @@ -84,6 +84,6 @@ To verify if a key is public use the following: This property verifies that the key includes a private key: - describe key_rsa('/etc/pki/www.mywebsite.com.key') do + describe key_rsa('/etc/pki/www.example.com.key') do it { should be_private } end diff --git a/docs-chef-io/content/inspec/resources/mssql_session.md b/docs-chef-io/content/inspec/resources/mssql_session.md index 703d69a97..64dddbd84 100644 --- a/docs-chef-io/content/inspec/resources/mssql_session.md +++ b/docs-chef-io/content/inspec/resources/mssql_session.md @@ -59,7 +59,7 @@ The following examples show how to use this Chef InSpec audit resource. ### Test a specific host and instance - sql = mssql_session(user: 'my_user', password: 'password', host: 'mssqlserver', instance: 'foo') + sql = mssql_session(user: 'my_user', password: 'password', host: 'ms-sql-server', instance: 'foo') describe sql.query("SELECT SERVERPROPERTY('ProductVersion') as result").row(0).column('result') do its("value") { should cmp > '12.00.4457' } diff --git a/docs-chef-io/content/inspec/resources/oracledb_session.md b/docs-chef-io/content/inspec/resources/oracledb_session.md index 0720a68e3..1d5ac7978 100644 --- a/docs-chef-io/content/inspec/resources/oracledb_session.md +++ b/docs-chef-io/content/inspec/resources/oracledb_session.md @@ -25,15 +25,15 @@ This resource first became available in v1.0.0 of InSpec. ## Syntax -A `oracledb_session` resource block declares the username and password to use for the session with an optional service to connect to, and then the command to be run: +A `oracledb_session` resource block declares the username and PASSWORD to use for the session with an optional service to connect to, and then the command to be run: - describe oracledb_session(user: 'username', password: 'password', service: 'ORCL.localdomain').query('QUERY').row(0).column('result') do + describe oracledb_session(user: 'username', PASSWORD: 'PASSWORD', service: 'ORCL.localdomain').query('QUERY').row(0).column('result') do its('value') { should eq('') } end where -- `oracledb_session` declares a username and password with permission to run the query (required), and an optional parameters for host (default: `localhost`), SID (default: `nil`, which uses the default SID, and path to the sqlplus binary (default: `sqlplus`). +- `oracledb_session` declares a username and PASSWORD with permission to run the query (required), and an optional parameters for host (default: `localhost`), system identifier (SID) (default: `nil`), which uses the default SID, and path to the sqlplus binary (default: `sqlplus`). - it is possible to run queries as sysdba/sysoper by using `as_db_role option`, see examples - SQLcl can be used in place of sqlplus. Use the `sqlcl_bin` option to set the sqlcl binary path instead of `sqlplus_bin`. - `query('QUERY')` contains the query to be run @@ -51,7 +51,7 @@ The following examples show how to use this Chef InSpec audit resource. ### Test for matching databases - sql = oracledb_session(user: 'my_user', pass: 'password') + sql = oracledb_session(user: 'USERNAME', pass: 'PASSWORD') describe sql.query('SELECT NAME AS VALUE FROM v$database;').row(0).column('value') do its('value') { should cmp 'ORCL' } @@ -59,7 +59,7 @@ The following examples show how to use this Chef InSpec audit resource. ### Test for matching databases with custom host, SID and sqlplus binary location - sql = oracledb_session(user: 'my_user', pass: 'password', host: 'oraclehost', sid: 'mysid', sqlplus_bin: '/u01/app/oracle/product/12.1.0/dbhome_1/bin/sqlplus') + sql = oracledb_session(user: 'USERNAME', pass: 'PASSWORD', host: 'ORACLE_HOST', sid: 'ORACLE_SID', sqlplus_bin: '/u01/app/oracle/product/12.1.0/dbhome_1/bin/sqlplus') describe sql.query('SELECT NAME FROM v$database;').row(0).column('name') do its('value') { should cmp 'ORCL' } @@ -67,9 +67,9 @@ The following examples show how to use this Chef InSpec audit resource. ### Test for table contains a specified value in any row for the given column name - sql = oracledb_session(user: 'my_user', pass: 'password', service: 'MYSID') + sql = oracledb_session(user: 'USERNAME', pass: 'PASSWORD', service: 'ORACLE_SID') - describe sql.query('SELECT * FROM my_table;').column('my_column') do + describe sql.query('SELECT * FROM my_table;').column('COLUMN') do it { should include 'my_value' } end @@ -77,16 +77,16 @@ The following examples show how to use this Chef InSpec audit resource. The check will change user (with su) to specified user and run 'sqlplus / as sysdba' (sysoper, sysasm) - sql = oracledb_session(as_os_user: 'oracle', as_db_role: 'sysdba', service: 'MYSID') + sql = oracledb_session(as_os_user: 'oracle', as_db_role: 'sysdba', service: 'ORACLE_SID') describe sql.query('SELECT tablespace_name AS name FROM dba_tablespaces;').column('name') do - it { should include 'MYTABLESPACE' } + it { should include 'TABLE_SPACE' } end NOTE: option `as_os_user` available only on unix-like systems and not supported on Windows. Also this option requires that you are running inspec as `root` or with `--sudo` ### Test number of rows in the query result - sql = oracledb_session(user: 'my_user', pass: 'password') + sql = oracledb_session(user: 'USERNAME', pass: 'PASSWORD') describe sql.query('SELECT * FROM my_table;').rows do its('count') { should eq 20 } @@ -94,7 +94,7 @@ The following examples show how to use this Chef InSpec audit resource. ### Use data out of (remote) DB query to build other tests - sql = oracledb_session(user: 'my_user', pass: 'password', host: 'my.remote.db', service: 'MYSID') + sql = oracledb_session(user: 'USERNAME', pass: 'PASSWORD', host: 'my.remote.db', service: 'ORACLE_SID') sql.query('SELECT * FROM files;').rows.each do |file_row| describe file(file_row['path']) do diff --git a/docs-chef-io/content/inspec/resources/passwd.md b/docs-chef-io/content/inspec/resources/passwd.md index 5cc21a261..5abe103b4 100644 --- a/docs-chef-io/content/inspec/resources/passwd.md +++ b/docs-chef-io/content/inspec/resources/passwd.md @@ -58,7 +58,7 @@ where ### gids -The `gids` property tests if the group indentifiers in the test match group identifiers in `/etc/passwd`: +The `gids` property tests if the group identifiers in the test match group identifiers in `/etc/passwd`: its('gids') { should include 1234 } its('gids') { should cmp 0 } diff --git a/docs-chef-io/content/inspec/resources/podman.md b/docs-chef-io/content/inspec/resources/podman.md new file mode 100644 index 000000000..c45c67670 --- /dev/null +++ b/docs-chef-io/content/inspec/resources/podman.md @@ -0,0 +1,218 @@ ++++ +title = "podman resource" +draft = false +gh_repo = "inspec" +platform = "unix" + +[menu] + [menu.inspec] + title = "podman" + identifier = "inspec/resources/os/podman.md podman resource" + parent = "inspec/resources/os" ++++ + +Use the `podman` Chef InSpec audit resource to test the configuration data for the Podman resources. + +## Availability + +### Installation + +This resource is distributed with Chef InSpec and is automatically available for use. + +## Syntax + +A `podman` resource block allows you to write a test for many `containers`. + +```ruby + describe podman.containers do + its('ids') { should include "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7" } + its('images) { should include "docker.io/library/ubuntu:latest" } + end +``` + +Or, if you want to query a specific `container`: + +```ruby + describe podman.containers.where(id: "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7") do + its('status') { should include "Up 44 hours ago" } + end +``` + +> Where +> +> - `.where()` specifies a specific item and value to which the resource parameters are compared. +> - `commands`, `created_at`, `ids`, `images`, `names`, `status`, `image_ids`, `labels`, `mounts`, `networks`, `pods`, `ports`, `running_for`, and `sizes` are valid parameters for `containers`. + +The `podman` resource block also allows you to write a test for many `images`. + +```ruby + describe podman.images do + its('repositories') { should_not include 'docker.io/library/nginx' } + end +``` + +Or, if you want to query a specific `image`: + +```ruby + describe podman.images.where(id: "c7db653c4397e6a4d1e468bb7c6400c022c62623bdb87c173d54bac7995b6d8f") do + it { should exist } + end +``` + +> Where +> +> - `.where()` specifies a specific filter and expected value, against which parameters are compared. +> - `repositories`, `tags`, `sizes`, `digests`, `history`, `created_at`, `history`, and`created_since` are valid parameters for `images`. + +The `podman` resource block also allows you to write a test for many `networks`. + +```ruby + describe podman.networks do + its("names") { should include "podman" } + end +``` + +Or, if you want to query a specific `network`: + +```ruby + describe podman.networks.where(id: "c7db653c4397e6a4d1e468bb7c6400c022c62623bdb87c173d54bac7995b6d8f") do + it { should exist } + end +``` + +> Where +> +> - `.where()` specifies a specific filter and expected value, against which parameters are compared. +> - `ids`, `names`, `drivers`, `network_interfaces`, `created`, `subnets`, `ipv6_enabled`, `internal`, `dns_enabled`, `options`, `labels`, and `ipam_options` are valid parameters for `networks`. + +The `podman` resource block also allows you to write a test for many `pods`. + +```ruby + describe podman.pods do + its("names") { should include "cranky_allen" } + end +``` + +Or, if you want to query a specific `pod`: + +```ruby + describe podman.pods.where(id: "95cadbb84df71e6374fceb3fd89ee3b8f2c7e1a831062cd9cea7d0e3e4b1dbcc") do + it { should exist } + end +``` + +> Where +> +> - `.where()` may specify a specific filter and expected value, against which parameters are compared. +> - `ids`, `cgroups`, `containers`, `created`, `infraids`, `names`, `namespaces`, `networks`, `status`, and `labels` are valid parameters for `pods`. + +## Examples + +The following examples show how to use this Chef InSpec audit resource. + +### Returns all running containers + +```ruby + podman.containers.running?.ids.each do |id| + describe podman.object(id) do + its('State.Health.Status') { should eq 'healthy' } + end + end +``` + +## Resource Parameter Examples + +### containers + +`containers` returns information about containers as returned by [podman ps -a](https://docs.podman.io/en/latest/markdown/podman.1.html). + +```ruby + describe podman.containers do + its("ids") { should include "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7" } + its("labels") { should include "maintainer" => "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e" } + its('names') { should include "sweet_mendeleev" } + its("images") { should include "docker.io/library/nginx:latest" } + end +``` + +### images + +`images` returns information about a Podman image as returned by [podman images -a](https://docs.podman.io/en/latest/markdown/podman-images.1.html). + +```ruby + describe podman.images do + its('ids') { should include 'sha256:c7db653c4397e6a4d1e468bb7c6400c022c62623bdb87c173d54bac7995b6d8f ' } + its('sizes') { should_not include '80.3 GB' } + its('repositories") { should include "docker.io/library/nginx"} + end +``` + +### pods + +`pods` returns information about pods as returned by [podman pod ps](https://docs.podman.io/en/latest/markdown/podman-pod-ps.1.html). + +```ruby + describe podman.pods do + its("ids") { should include "95cadbb84df71e6374fceb3fd89ee3b8f2c7e1a831062cd9cea7d0e3e4b1dbcc" } + its("containers") { should eq [{ "Id" => "a218dfc58fa28e0c58c55e508e5b57084876b42e894b98073c69c45dea06cbb2", "Names" => "95cadbb84df7-infra", "Status" => "running" } ]} + its("names") { should include "cranky_allen" } + end +``` + +### networks + +`networks` returns information about a Podman network as returned by [podman network ls](https://docs.podman.io/en/latest/markdown/podman-network-ls.1.html). + +```ruby + describe podman.networks do + its("names") { should include "podman" } + its("ids") { should include "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9" } + its("ipv6_enabled") { should eq [false] } + end +``` + +### volumes + +`volumes` returns information about a Podman volume as returned by [podman volume ls](https://docs.podman.io/en/latest/markdown/podman-volume-ls.1.html). + +```ruby + describe podman.volumes do + its('names') { should include 'ae6be9ba838b9b150de47657229bb9b67142dbdb3d1ddbc5efa245cf1e95536a' } + its('drivers') { should include 'local' } + end +``` + +### info + +`info` returns the parsed result of [podman info](https://docs.podman.io/en/latest/markdown/podman-info.1.html). + +```ruby + describe podman.info do + its("host.os") { should eq "linux" } + end +``` + +### version + +`version` returns the parsed result of [podman version](https://docs.podman.io/en/latest/markdown/podman-version.1.html) + +```ruby + describe podman.version do + its("Client.Version") { should eq "4.1.0"} + its('Server.Version') { should eq '4.1.0'} + end +``` + +### object('id') + +`object` returns low-level information about Podman objects as returned by [podman inspect](https://docs.podman.io/en/latest/markdown/podman-inspect.1.html). + +```ruby + describe docker.object(id) do + its('State.Running') { should eq true } + end +``` + +## Matchers + +For a full list of available matchers, please visit our [matchers page](/inspec/matchers/). diff --git a/docs-chef-io/content/inspec/resources/podman_container.md b/docs-chef-io/content/inspec/resources/podman_container.md new file mode 100644 index 000000000..334335c54 --- /dev/null +++ b/docs-chef-io/content/inspec/resources/podman_container.md @@ -0,0 +1,149 @@ ++++ +title = "podman_container resource" +draft = false +gh_repo = "inspec" +platform = "unix" + +[menu] + [menu.inspec] + title = "podman_container" + identifier = "inspec/resources/os/podman_container.md podman_container resource" + parent = "inspec/resources/os" ++++ + +Use the `podman_container` Chef InSpec audit resource to test the ... + +## Availability + +### Installation + +This resource is distributed with Chef InSpec and is automatically available for use. + +## Syntax + +A `podman_container` Chef InSpec audit resource ... + +```ruby + describe podman_container("sweet_mendeleev") do + it { should exist } + it { should be_running } + its("id") { should eq "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7" } + its("image") { should eq "docker.io/library/nginx:latest" } + its("labels") { should include "maintainer"=>"NGINX Docker Maintainers " } + its("ports") { should eq nil } + end +``` + +## Resource Parameter Examples + +### name + +The container name can be provided with the `name` resource parameter. + +```ruby + describe podman_container(name: 'an-echo-server') do + it { should exist } + it { should be_running } + end +``` + +### container ID + +Alternatively, you can pass the container ID. + +```ruby + describe podman_container(id: '71b5df59442b') do + it { should exist } + it { should be_running } + end +``` + +## Properties + +## Property Examples + +The following examples show how to use this Chef InSpec resource. + +### id + +The `id` property tests the container ID. + +```ruby + its('id') { should eq '71b5df59...442b' } +``` + +### image + +The `image` property tests the value of the container image. + +```ruby + its('image') { should eq 'docker.io/library/nginx:latest' } +``` + +### labels + +The `labels` property tests the value of container image labels. + +```ruby + its('labels') { should eq "maintainer" => "NGINX Docker Maintainers " } +``` + +### ports + +The `ports` property tests the value of the Podmans ports. + +```ruby + its('ports') { should eq '0.0.0.0:1234->1234/tcp' } +``` + +### command + +The `command` property tests the value of the container run command. + +```ruby + its('command') { should eq 'nc -ll -p 1234 -e /bin/cat' } +``` + +## Matchers + +For a full list of available matchers, please visit our [matchers page](/inspec/matchers/). The specific matchers of this resource are: `exist` and `be_running`. + +### exist + +The `exist` matcher specifies if the container exists. + +```ruby + it { should exist } +``` + +### be_running + +The `be_running` matcher checks if the container is running. + +```ruby + it { should be_running } +``` + +## Examples + +The following examples show how to use this Chef InSpec audit resource. + +### Ensures container exists + +The below test passes if the container `sweet_mendeleev` exists as part of the Podman instances. + +```ruby + describe podman_container('sweet_mendeleev') do + it { should exist } + end +``` + +### Ensures container is in running status + +The below test passes if the container `sweet_mendeleev` exists as part of the Podman instances and the status is running. + +```ruby + describe podman_container('sweet_mendeleev') do + it { should be_running } + end +``` diff --git a/docs-chef-io/content/inspec/resources/podman_image.md b/docs-chef-io/content/inspec/resources/podman_image.md new file mode 100644 index 000000000..29be516d1 --- /dev/null +++ b/docs-chef-io/content/inspec/resources/podman_image.md @@ -0,0 +1,189 @@ ++++ +title = "podman_image resource" +draft = false +gh_repo = "inspec" +platform = "unix" + +[menu] + [menu.inspec] + title = "podman_image" + identifier = "inspec/resources/os/podman_image.md podman_image resource" + parent = "inspec/resources/os" ++++ + +Use the `podman_image` Chef InSpec audit resource to test the properties of a container image on Podman. + +## Availability + +### Installation + +This resource is distributed with Chef InSpec and is automatically available for use. + +## Syntax + +A `podman_image` Chef InSpec audit resource aids in testing the properties of a container image on Podman. + +```ruby + describe podman_image("docker.io/library/busybox") do + it { should exist } + its("id") { should eq "3c19bafed22355e11a608c4b613d87d06b9cdd37d378e6e0176cbc8e7144d5c6" } + its("repo_tags") { should include "docker.io/library/busybox:latest" } + its("size") { should eq 1636053 } + its("os") { should eq "linux" } + end +``` + +> where +> +> - `id`, `repo_tags`, `size`, and `os` are properties of this resource to fetch the respective value of the container image. +> - `exist` is a matcher of this resource. + +### Resource Parameter Examples + +- The resource allows you to pass an image name. If the tag is missing for an image, `latest` is assumed as default. + +```ruby + describe podman_image("docker.io/library/busybox") do + it { should exist } + end +``` + +- The resource allows you to pass the repository and tag values as separate values. + +```ruby + describe podman_image(repo: "docker.io/library/busybox", tag: "latest") do + it { should exist } + end +``` + +- The resource allows you to pass with an image ID. + +```ruby + describe podman_image(id: "8847e9bf6df8") do + it { should exist } + end +``` + +## Properties + +### id + +The `id` property returns the full image ID. + +```ruby + its("id") { should eq "3c19bafed22355e11a608c4b613d87d06b9cdd37d378e6e0176cbc8e7144d5c6" } +``` + +### repo_tags + +The `repo_tags` property tests the value of the repository name. + +```ruby + its("repo_tags") { should include "docker.io/library/busybox:latest" } +``` + +### size + +The `size` property tests the size of the image in bytes + +```ruby + its("size") { should eq 1636053 } +``` + +### digest + +The `digest` property tests the value of the image digest. + +```ruby + its("digest") { should eq "sha256:3614ca5eacf0a3a1bcc361c939202a974b4902b9334ff36eb29ffe9011aaad83" } +``` + +### created_at + +The `created_at` property tests the time of the image creation. + +```ruby + its("created_at") { should eq "2022-06-08T00:39:28.175020858Z" } +``` + +### version + +The `version` property tests the version of the image. + +```ruby + its("version") { should eq "20.10.12" } +``` + +### names_history + +The `names_history` property tests the names history of the image. + +```ruby + its("names_history") { should include "docker.io/library/busybox:latest" } +``` + +### repo_digests + +The `repo_digests` tests the digest of the repository of the given image. + +```ruby + its("repo_digests") { should include "docker.io/library/busybox@sha256:2c5e2045f35086c019e80c86880fd5b7c7a619878b59e3b7592711e1781df51a" } +``` + +### architecture + +The `architecture` tests the architecture of the given image. + +```ruby + its("architecture") { should eq "arm64" } +``` + +### os + +The `os` property tests the operating system of the given image. + +```ruby + its("os") { should eq "linux" } +``` + +### virtual_size + +The `virtual_size` property tests the virtual size of the given image. + +```ruby + its("virtual_size") { should eq 1636053 } +``` + +## Matchers + +For a full list of available matchers, please visit our [matchers page](/inspec/matchers/). + +### exist + +The `exist` matcher tests if the image is available on Podman. + +```ruby + it { should exist } +``` + +## Examples + +### Test if an image exists on Podman and verify the various image properties + +```ruby + describe podman_image("docker.io/library/busybox") do + it { should exist } + its("id") { should eq "3c19bafed22355e11a608c4b613d87d06b9cdd37d378e6e0176cbc8e7144d5c6" } + its("repo_tags") { should include "docker.io/library/busybox:latest" } + its("size") { should eq 1636053 } + its("digest") { should eq "sha256:3614ca5eacf0a3a1bcc361c939202a974b4902b9334ff36eb29ffe9011aaad83" } + its("created_at") { should eq "2022-06-08T00:39:28.175020858Z" } + its("version") { should eq "20.10.12" } + its("names_history") { should include "docker.io/library/busybox:latest" } + its("repo_digests") { should include "docker.io/library/busybox@sha256:2c5e2045f35086c019e80c86880fd5b7c7a619878b59e3b7592711e1781df51a" } + its("architecture") { should eq "arm64" } + its("os") { should eq "linux" } + its("virtual_size") { should eq 1636053 } + its("resource_id") { should eq "docker.io/library/busybox:latest" } + end +``` diff --git a/docs-chef-io/content/inspec/resources/podman_network.md b/docs-chef-io/content/inspec/resources/podman_network.md new file mode 100644 index 000000000..6d9bf9e44 --- /dev/null +++ b/docs-chef-io/content/inspec/resources/podman_network.md @@ -0,0 +1,189 @@ ++++ +title = "podman_network resource" +draft = false +gh_repo = "inspec" +platform = "unix" + +[menu] + [menu.inspec] + title = "podman_network" + identifier = "inspec/resources/os/podman_network.md podman_network resource" + parent = "inspec/resources/os" ++++ + +Use the `podman_network` Chef InSpec audit resource to test the properties of existing Podman networks. + +## Availability + +### Installation + +This resource is distributed with Chef InSpec and is automatically available for use. + +## Syntax + +A `podman_network` Chef InSpec audit resource aids in testing the properties of a Podman network. + +```ruby + describe podman_network("minikube") do + it { should exist } + its("id") { should eq "3a7c94d937d5f3a0f1a9b1610589945aedfbe56207fd5d32fc8154aa1a8b007f" } + its("name") { should eq "minikube" } + its("ipv6_enabled") { should eq false } + its("network_interface") { should eq "podman1" } + end +``` + +> where +> +> - `id`, `name`, `ipv6_enabled`, and `network_interface` are properties of this resource to fetch the respective value of the Podman network. +> - `exist` is a matcher of this resource. + +### Resource Parameter Examples + +- The resource allows you to pass a network name. + +```ruby + describe podman_network("minikube") do + it { should exist } + end +``` + +- The resource allows you to pass with a Network ID. + +```ruby + describe podman_network("3a7c94d937d5") do + it { should exist } + end +``` + +## Properties + +### id + +The `id` property returns the full Podman Network ID. + +```ruby + its("id") { should eq "3c19bafed22355e11a608c4b613d87d06b9cdd37d378e6e0176cbc8e7144d5c6" } +``` + +### name + +The `name` property tests the value of the Podman network name. + +```ruby + its("name") { should eq "minikube" } +``` + +### ipv6_enabled + +The `ipv6_enabled` property tests whether ipv6 is enabled on the Podman network. + +```ruby + its("ipv6_enabled") { should eq true } +``` + +### network_interface + +The `network_interface` property tests the value of the network interface settings on the Podman network. + +```ruby + its("network_interface") { should eq "podman0" } +``` + +### created + +The `created` property tests the timestamp when the Podman network was created. + +```ruby + its("created") { should eq "2022-07-06T08:51:11.735432521+05:30" } +``` + +### subnets + +The `subnets` property tests the list of subnets on the Podman network. + +```ruby + its("subnets") { should inclue "gateway"=>"192.168.49.1", "subnet"=>"192.168.49.0/24" } +``` + +### dns_enabled + +The `dns_enabled` property tests whether the Podman network has DNS enabled. + +```ruby + its("dns_enabled") { should be false } +``` + +### internal + +The `internal` property tests whether the specified Podman network is internal. + +```ruby + its("internal") { should eq true } +``` + +### ipam_options + +The `ipam_options` property tests the IPAM options of the given Podman network. + +```ruby + its("ipam_options") { should eq "driver" => "host-local" } +``` + +### labels + +The `labels` property tests the labels set for the specified Podman network. + +```ruby + its("labels") { should eq "created_by.minikube.sigs.k8s.io"=>"true", "name.minikube.sigs.k8s.io"=>"minikube" } +``` + +### driver + +The `driver` property tests the value of the Podman network driver. + +```ruby + its("driver") { should eq "bridge" } +``` + +### options + +The `options` property tests the network options for the specified Podman network. + +```ruby + its("options") { should eq nil } +``` + +## Matchers + +For a full list of available matchers, please visit our [matchers page](/inspec/matchers/). + +### exist + +The `exist` matcher tests if the specified network is available on Podman. + +```ruby + it { should exist } +``` + +## Examples + +### Tests if a given Podman network exists and verifies the various network properties + +```ruby + describe podman_network("minikube") do + it { should exist } + its("id") { should eq "3a7c94d937d5f3a0f1a9b1610589945aedfbe56207fd5d32fc8154aa1a8b007f" } + its("name") { should eq "minikube" } + its("ipv6_enabled") { should eq false } + its("network_interface") { should eq "podman1" } + its("subnets") { should include "gateway"=>"192.168.49.1", "subnet"=>"192.168.49.0/24" } + its("dns_enabled") { should eq true } + its("internal") { should eq false } + its("created") { should eq "2022-07-06T08:51:11.735432521+05:30" } + its("ipam_options") { should eq "driver" => "host-local" } + its("labels") { should eq "created_by.minikube.sigs.k8s.io"=>"true", "name.minikube.sigs.k8s.io"=>"minikube" } + its("driver") { should eq "bridge" } + its("options") { should eq nil } + end +``` diff --git a/docs-chef-io/content/inspec/resources/podman_pod.md b/docs-chef-io/content/inspec/resources/podman_pod.md new file mode 100644 index 000000000..a2a62a731 --- /dev/null +++ b/docs-chef-io/content/inspec/resources/podman_pod.md @@ -0,0 +1,210 @@ ++++ +title = "podman_pod resource" +draft = false +gh_repo = "inspec" +platform = "unix" + +[menu] + [menu.inspec] + title = "podman_pod" + identifier = "inspec/resources/os/podman_pod.md podman_pod resource" + parent = "inspec/resources/os" ++++ + +Use the `podman_pod` Chef InSpec audit resource to test the properties of a pod on Podman. + +## Availability + +### Installation + +This resource is distributed with Chef InSpec and is automatically available for use. + +## Syntax + +A `podman_pod` Chef InSpec audit resource aids in testing the properties of a pod on Podman. + +```ruby + describe podman_pod("nginx-frontend") do + it { should exist } + its("id") { should eq "fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4" } + its("name") { should eq "nginx-frontend" } + its("created_at") { should eq "2022-07-14T15:47:47.978078124+05:30" } + its("create_command") { should include "new:nginx-frontend" } + its("state") { should eq "Running" } + end +``` + +> where +> +> - `'nginx-frontend'` is the name of the pod. Pod ID and Pod names are valid parameters accepted by `podman_pod`. +> - `'id'`, `'name'`, `'created_at'`, `'create_command'`, and `'state'`, are properties of this resource to fetch the respective value of the podman pod. +> - `exist` is a matcher of this resource. + +## Properties + +- Properties of the resources are: `'id'`, `'name'`, `'created_at'`, `'create_command'`, `'state'`, `'hostname'`, `'create_cgroup'`, `'cgroup_parent'`, `cgroup_path`, `'create_infra'`, `'infra_container_id'`, `'infra_config'`, `'shared_namespaces'`, `'num_containers'`, and `'containers'` + +### `id` + +The `id` property returns the id of the pod. + +```ruby + its("id") { should eq "fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4" } +``` + +### `name` + +The `name` property returns the name of the pod. + +```ruby + its("name") { should eq "nginx-frontend" } +``` + +### `created_at` + +The `created_at` property returns the creation date of the pod. + +```ruby + its("created_at") { should eq "2022-07-14T15:47:47.978078124+05:30" } +``` + +### `create_command` + +The `create_command` property returns an array of commands used to create the pod. + +```ruby + its("create_command") { should include "new:nginx-frontend" } +``` + +### `state` + +The `state` property returns the state of the pod. + +```ruby + its("state") { should eq "Running" } +``` + +### `hostname` + +The `hostname` property returns the hostname of the pod. + +```ruby + its("hostname") { should eq "" } +``` + +### `create_cgroup` + +The `create_cgroup` property returns a boolean value for cgroup creation of the pod. + +```ruby + its("create_cgroup") { should eq true } +``` + +### `cgroup_parent` + +The `cgroup_parent` property returns the name of the cgroup parent of the pod. + +```ruby + its("cgroup_parent") { should eq "user.slice" } +``` + +### `cgroup_path` + +The `cgroup_path` property returns the path of the cgroup parent of the pod. + +```ruby + its("cgroup_path") { should eq "user.slice/user-libpod_pod_fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4.slice" } +``` + +### `create_infra` + +The `create_infra` property returns a boolean value for the pod infra creation. + +```ruby + its("create_infra") { should eq true } +``` + +### `infra_container_id` + +The `infra_container_id` property returns the infra container ID of the pod. + +```ruby + its("infra_container_id") { should eq "727538044b32a165934729dc2d47d9d5e981b6496aebfad7de470f7e76ea4251" } +``` + +### `infra_config` + +The `infra_config` property returns a hash of the infra configuration of the pod. + +```ruby + its("infra_config") { should include "DNSOption" } +``` + +### `shared_namespaces` + +The `shared_namespaces` property returns an array of shared namespaces of the pod. + +```ruby + its("shared_namespaces") { should include "ipc" } +``` + +### `num_containers` + +The `num_containers` property returns the number of containers in the pod. + +```ruby + its("num_containers") { should eq 2 } +``` + +### `containers` + +The `containers` property returns an array of hashes about the information of containers in the pod. + +```ruby + its("containers") { should_not be nil } +``` + +## Matchers + +For a full list of available matchers, please visit our [matchers page](/inspec/matchers/). + +### exist + +The `exist` matcher tests if the pod is available on Podman. + +```ruby + it { should exist } +``` + +## Examples + +### Test if a pod exists on Podman and verifies pod properties + +```ruby + describe podman_pod("nginx-frontend") do + it { should exist } + its("id") { should eq "fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4" } + its("name") { should eq "nginx-frontend" } + its("created_at") { should eq "2022-07-14T15:47:47.978078124+05:30" } + its("create_command") { should include "new:nginx-frontend" } + its("state") { should eq "Running" } + its("hostname") { should eq "" } + its("create_cgroup") { should eq true } + its("cgroup_parent") { should eq "user.slice" } + its("cgroup_path") { should eq "user.slice/user-libpod_pod_fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4.slice" } + its("create_infra") { should eq true } + its("infra_container_id") { should eq "727538044b32a165934729dc2d47d9d5e981b6496aebfad7de470f7e76ea4251" } + its("infra_config") { should include "DNSOption" } + its("shared_namespaces") { should include "ipc" } + its("num_containers") { should eq 2 } + its("containers") { should_not be nil } + end +``` + +### Test if a pod does not exist on Podman + +```ruby + describe podman_pod("non_existing_pod") do + it { should_not exist } + end +``` diff --git a/docs-chef-io/content/inspec/resources/podman_volume.md b/docs-chef-io/content/inspec/resources/podman_volume.md new file mode 100644 index 000000000..5e1d37f37 --- /dev/null +++ b/docs-chef-io/content/inspec/resources/podman_volume.md @@ -0,0 +1,155 @@ ++++ +title = "podman_volume resource" +draft = false +gh_repo = "inspec" +platform = "unix" + +[menu] + [menu.inspec] + title = "podman_volume" + identifier = "inspec/resources/os/podman_volume.md podman_volume resource" + parent = "inspec/resources/os" ++++ + +Use the `podman_volume` Chef InSpec audit resource to test the properties of a volume on Podman. + +## Availability + +### Installation + +This resource is distributed with Chef InSpec and is automatically available for use. + +## Syntax + +A `podman_volume` Chef InSpec audit resource aids in testing the properties of a volume on Podman. + +```ruby + describe podman_volume("my_volume") do + it { should exist } + its("name") { should eq "my_volume" } + its("driver") { should eq "local" } + its("mountpoint") { should eq "/var/home/core/.local/share/containers/storage/volumes/my_volume/_data" } + its("created_at") { should eq "2022-07-14T13:21:19.965421792+05:30" } + end +``` + +> where +> +> - `'name'`, `'driver'`, `'mountpoint'`, and `'created_at'` are properties of this resource to fetch the respective value of the podman volume. +> - `exist` is a matcher of this resource. + +## Properties + +- Properties of the resources: `name`, `driver`, `mountpoint`, `created_at`, `labels`, `scope`, `options`, `mount_count`, `needs_copy_up`, and `needs_chown`. + +### name + +The `name` property returns the name of the volume. + +```ruby + its("name") { should eq "my_volume" } +``` + +### driver + +The `driver` property returns the value for the volume's driver environment. + +```ruby + its("driver") { should eq "local" } +``` + +### mountpoint + +The `mountpoint` property returns the value for the volume's mount path. + +```ruby + its("mountpoint") { should eq "/var/home/core/.local/share/containers/storage/volumes/my_volume/_data" } +``` + +### created_at + +The `created_at` property returns the creation date of the volume. + +```ruby + its("created_at") { should eq "2022-07-14T13:21:19.965421792+05:30" } +``` + +### labels + +The `labels` property returns the labels associated with the volume. + +```ruby + its("labels") { should eq({}) } +``` + +### scope + +The `scope` property returns the scope of the volume. + +```ruby + its("scope") { should eq "local" } +``` + +### options + +The `options` property returns the options associated with the volume. + +```ruby + its("options") { should eq({}) } +``` + +### mount_count + +The `mount_count` property returns the **MountCount** value from the volume's inspect information. + +```ruby + its("mount_count") { should eq 0 } +``` + +### needs_copy_up + +The `needs_copy_up` property returns the **NeedsCopyUp** value from the volume's inspect information. + +```ruby + its("needs_copy_up") { should eq true } +``` + +### needs_chown + +The `needs_chown` property returns the **NeedsChown** value from the volume's inspect information. + +```ruby + its("needs_chown") { should eq true } +``` + +## Matchers + +For a full list of available matchers, please visit our [matchers page](/inspec/matchers/). + +### exist + +The `exist` matcher tests if the volume is available on Podman. + +```ruby + it { should exist } +``` + +## Examples + +### Test if a volume exists on Podman and verifies volume properties + +```ruby + describe podman_volume("my_volume") do + it { should exist } + its("name") { should eq "my_volume" } + its("driver") { should eq "local" } + its("mountpoint") { should eq "/var/home/core/.local/share/containers/storage/volumes/my_volume/_data" } + its("created_at") { should eq "2022-07-14T13:21:19.965421792+05:30" } + its("labels") { should eq({}) } + its("scope") { should eq "local" } + its("options") { should eq({}) } + its("mount_count") { should eq 0 } + its("needs_copy_up") { should eq true } + its("needs_chown") { should eq true } + end +``` diff --git a/docs-chef-io/content/inspec/resources/postfix_conf.md b/docs-chef-io/content/inspec/resources/postfix_conf.md index 092bf52a3..0868e87bb 100644 --- a/docs-chef-io/content/inspec/resources/postfix_conf.md +++ b/docs-chef-io/content/inspec/resources/postfix_conf.md @@ -42,7 +42,7 @@ When using `postfix_conf` with a custom configuration directory, the following s where -- `'path'` is the path to your Postfix configuration (ex. '/etc/my/postfix/path/main.cf') +- `'path'` is the path to your Postfix configuration (ex. '/etc/path/to/postfix/main.cf') ## Properties diff --git a/docs-chef-io/content/inspec/resources/security_identifier.md b/docs-chef-io/content/inspec/resources/security_identifier.md index 133155f22..ae166f57b 100644 --- a/docs-chef-io/content/inspec/resources/security_identifier.md +++ b/docs-chef-io/content/inspec/resources/security_identifier.md @@ -30,13 +30,13 @@ A `security_identifier` resource should specify the name and type of the trustee where - `group:` specifies that `'Everyone'` should be a group. `user:` can be used to specify a user account. - \*\* It is necessary to declare the type of the trustee because Windows allows users, groups and other entities to share names. If you really need to not specify the type, `unspecified:` can be used. This will attempt to match the name to a group and then a useraccount. This may take longer to execute and comes with the risk of Chef InSpec matching the name to an unintended trustee. + \*\* It is necessary to declare the type of the trustee because Windows allows users, groups and other entities to share names. If you really need to not specify the type, `unspecified:` can be used. This will attempt to match the name to a group and then a user account. This may take longer to execute and comes with the risk of Chef InSpec matching the name to an unintended trustee. ## Examples The following examples show how to use this Chef InSpec resource. -### Verify that the Admnistrator user has a SID +### Verify that the Administrator user has a SID describe security_identifier(user: 'Administrator') do it { should exist } diff --git a/docs-chef-io/content/inspec/resources/shadow.md b/docs-chef-io/content/inspec/resources/shadow.md index 9db7f9be2..61df02c2a 100644 --- a/docs-chef-io/content/inspec/resources/shadow.md +++ b/docs-chef-io/content/inspec/resources/shadow.md @@ -26,7 +26,7 @@ The format for `/etc/shadow` includes: These entries are defined as a colon-delimited row in the file, one row per user: - dannos:Gb7crrO5CDF.:10063:0:99999:7::: + username:Gb7crrO5CDF.:10063:0:99999:7::: The `shadow` resource understands this format, allows you to search on the fields, and exposes the selected users' properties. diff --git a/docs-chef-io/content/inspec/resources/users.md b/docs-chef-io/content/inspec/resources/users.md index 7c7ef8cea..d4c567de1 100644 --- a/docs-chef-io/content/inspec/resources/users.md +++ b/docs-chef-io/content/inspec/resources/users.md @@ -142,7 +142,7 @@ The `badpasswordattempts` property tests the count of bad password attempts for where `0` is the count of bad passwords for a user. On Linux based operating systems it relies on `lastb` and for Windows it uses information stored for the user object. -These settings will be resetted to `0` depending on your operating system configuration. +These settings will reset to `0` depending on your operating system configuration. ## Examples diff --git a/docs-chef-io/content/inspec/resources/virtualization.md b/docs-chef-io/content/inspec/resources/virtualization.md index de1e6474d..08a76ac91 100644 --- a/docs-chef-io/content/inspec/resources/virtualization.md +++ b/docs-chef-io/content/inspec/resources/virtualization.md @@ -72,7 +72,7 @@ This helper returns, if any of the supported virtualization platforms was detect ### virtualization.physical_system? Helper -If no virtualization platform is detected, this will return `true`. For unsupported virtualization platforms this can result in false posititves. +If no virtualization platform is detected, this will return `true`. For unsupported virtualization platforms this can result in false positives. ### virtualization.system names diff --git a/docs-chef-io/content/inspec/resources/windows_task.md b/docs-chef-io/content/inspec/resources/windows_task.md index d32453151..0c9ea509d 100644 --- a/docs-chef-io/content/inspec/resources/windows_task.md +++ b/docs-chef-io/content/inspec/resources/windows_task.md @@ -70,9 +70,9 @@ The following examples show how to use this Chef InSpec resource. it { should exist } end -## Gathering Tasknames +## Gathering Task Names -Rather then use the GUI you can use the `schtasks.exe` to output a full list of tasks available on the system +Rather than use the GUI, you can use the `schtasks.exe` to output a full list of tasks available on the system `schtasks /query /FO list` diff --git a/docs-chef-io/content/inspec/resources/x509_certificate.md b/docs-chef-io/content/inspec/resources/x509_certificate.md index fc99048ab..eb2ef2457 100644 --- a/docs-chef-io/content/inspec/resources/x509_certificate.md +++ b/docs-chef-io/content/inspec/resources/x509_certificate.md @@ -29,13 +29,13 @@ This resource is available from InSpec version 1.18. An `x509_certificate` resource block declares a certificate `key file` to be tested. - describe x509_certificate('mycertificate.pem') do + describe x509_certificate('certificate.pem') do its('validity_in_days') { should be > 30 } end The `filepath` property can also be used. - describe x509_certificate(filepath: 'mycertificate.pem') do + describe x509_certificate(filepath: 'certificate.pem') do its('validity_in_days') { should be > 30 } end @@ -55,8 +55,8 @@ The `content` value is used if the `content` and `filepath` are specified. The `subject` (string) property accesses the individual subject elements. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do - its('subject.CN') { should eq "www.mywebsite.com" } + describe x509_certificate('/etc/pki/www.example.com.pem') do + its('subject.CN') { should eq "www.example.com" } end ### subject_dn @@ -65,15 +65,15 @@ The `subject_dn` (string) property returns the distinguished name of the subject For example, `/C=US/L=Seattle/O=Chef Software Inc/OU=Chefs/CN=Richard Nixon` - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do - its('subject_dn') { should match "CN=www.mywebsite.com" } + describe x509_certificate('/etc/pki/www.example.com.pem') do + its('subject_dn') { should match "CN=www.example.com" } end ### issuer.XX The `issuer` (string) property accesses the individual issuer elements. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('issuer.CN') { should eq "Acme Trust CA" } end @@ -83,7 +83,7 @@ During the certificate signing process, the `issuer_dn` (string) property is the For example, `/C=US/L=Seattle/CN=Acme Trust CA/emailAddress=support@acmetrust.org` - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('issuer_cn') { should match "CN=NAME CA" } end @@ -91,7 +91,7 @@ For example, `/C=US/L=Seattle/CN=Acme Trust CA/emailAddress=support@acmetrust.or The `public_key` (string) property returns a base64 encoded public key in PEM format. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('public_key') { should match "-----BEGIN PUBLIC KEY-----\nblah blah blah..." } end @@ -99,7 +99,7 @@ The `public_key` (string) property returns a base64 encoded public key in PEM fo The `key_length` (integer) property calculates the number of bits in the public key. If the length of bits in the public key increases, the public keys are secure. However, at the cost of speed and compatibility. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('key_length') { should be 2048 } end @@ -107,7 +107,7 @@ The `key_length` (integer) property calculates the number of bits in the public The `keylength` (integer) property is an alias of the `key_length` property. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('keylength') { should be 2048 } end @@ -115,7 +115,7 @@ The `keylength` (integer) property is an alias of the `key_length` property. The `signature_algorithm` (string) property describes the CA's hash function to sign the certificate. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('signature_algorithm') { should be 'sha256WithRSAEncryption' } end @@ -123,7 +123,7 @@ The `signature_algorithm` (string) property describes the CA's hash function to The `validity_in_days` (float) property is used to check the validity of the certificates. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('validity_in_days') { should be > 30 } end @@ -131,7 +131,7 @@ The `validity_in_days` (float) property is used to check the validity of the cer The `not_before` and `not_after` (time) properties expose the start and end dates of certificate validity. These dates are exposed as Ruby **Time** class and perform date calculations. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('not_before') { should be <= Time.utc.now } its('not_after') { should be >= Time.utc.now } end @@ -140,7 +140,7 @@ The `not_before` and `not_after` (time) properties expose the start and end date The `serial` (integer) property exposes the certificate's serial number. The CA sets the serial number during the signing process and should be unique within that CA. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('serial') { should eq 9623283588743302433 } end @@ -148,7 +148,7 @@ The `serial` (integer) property exposes the certificate's serial number. The CA The `version` (integer) property exposes the certificate version. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('version') { should eq 2 } end @@ -156,7 +156,7 @@ The `version` (integer) property exposes the certificate version. The `extensions` (hash) property is mainly used to determine the purpose of the certificate. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do # Check what extension categories we have its('extensions') { should include 'keyUsage' } its('extensions') { should include 'extendedKeyUsage' } @@ -179,16 +179,16 @@ The `extensions` (hash) property is mainly used to determine the purpose of the The `email` (string) property checks for the email address of the certificate. This is equivalent to invoking the property `subject.emailAddress`. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('email') { should_not be_empty } - its('email') { should eq 'admin@mywebsite.com' } + its('email') { should eq 'admin@example.com' } end ### subject_alt_names The `subject_alt_names` (string) property checks for the subject alternative names (additional host names) of the certificate. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do its('subject_alt_names') { should include 'DNS:example.com' } its('subject_alt_names') { should include 'DNS:www.example.com' } end @@ -203,7 +203,7 @@ The specific matchers of this resource are: `be_valid`, `be_certificate` and `ha The `be_valid` matcher tests if the specified certificate is valid. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do it { should be_valid } end @@ -211,7 +211,7 @@ The `be_valid` matcher tests if the specified certificate is valid. The `be_certificate` matcher tests if the specified content or file is a certificate. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do it { should be_certificate } end @@ -219,7 +219,7 @@ The `be_certificate` matcher tests if the specified content or file is a certifi The `have_purpose` matcher tests if the certificate meets the specified purpose. - describe x509_certificate('/etc/pki/www.mywebsite.com.pem') do + describe x509_certificate('/etc/pki/www.example.com.pem') do it { should have_purpose('SSL client CA : Yes') } it { should have_purpose('SSL server CA : Yes') } end diff --git a/docs-chef-io/content/inspec/shell.md b/docs-chef-io/content/inspec/shell.md index 45108f447..b1e947b63 100644 --- a/docs-chef-io/content/inspec/shell.md +++ b/docs-chef-io/content/inspec/shell.md @@ -112,12 +112,12 @@ $ inspec shell Welcome to the interactive InSpec Shell To find out how to use it, type: help -inspec> file('/Users/myuser').directory? +inspec> file('/Users/username').directory? => true inspec> os_env('HOME') => Environment variable HOME inspec> os_env('HOME').content -=> /Users/myuser +=> /Users/username inspec> exit ``` @@ -141,10 +141,10 @@ replaced with the redefinition and the control is re-run. ```bash inspec> control 'my_control' do inspec> describe os_env('HOME') do -inspec> its('content') { should eq '/Users/myuser' } +inspec> its('content') { should eq '/Users/username' } inspec> end inspec> end - ✔ my_control: Environment variable HOME content should eq "/Users/myuser" + ✔ my_control: Environment variable HOME content should eq "/Users/username" Summary: 1 successful, 0 failures, 0 skipped ``` @@ -173,10 +173,10 @@ If you wish to run a single Chef InSpec command and fetch its results, you may use the `-c` flag. This is similar to using `bash -c`. ```bash -$ inspec shell -c 'describe file("/Users/myuser") do it { should exist } end' +$ inspec shell -c 'describe file("/Users/username") do it { should exist } end' Target: local:// - ✔ File /Users/myuser should exist + ✔ File /Users/username should exist Summary: 1 successful, 0 failures, 0 skipped ``` diff --git a/docs-chef-io/content/inspec/waivers.md b/docs-chef-io/content/inspec/waivers.md index ef9f90f29..ca97deac9 100644 --- a/docs-chef-io/content/inspec/waivers.md +++ b/docs-chef-io/content/inspec/waivers.md @@ -12,8 +12,7 @@ gh_repo = "inspec" +++ Waivers is a mechanism to mark controls as "waived" for various reasons, and to -control the running and/or reporting of those controls. It uses a YAML input file -that identifies: +control the running and/or reporting of those controls. A waiver file identifies: 1. which controls are waived 1. a description of why it is waived @@ -31,7 +30,7 @@ inspec exec path/to/profile --waiver-file waivers.yaml ## File Format -Waiver files are [input files](/inspec/inputs/) with a specific format: +Waiver files support YAML, JSON, CSV, XLSX & XLS format. ```yaml control_id: @@ -40,6 +39,18 @@ control_id: justification: "reason for waiving this control" ``` +OR + +```json +{ + "control_id": { + "expiration_date": "YYYY-MM-DD", + "run": false, + "justification": "reason for waiving this control" + } +} +``` + - `expiration_date` sets the day that the waiver file will expire in YYYY-MM-DD format. Waiver files expire at 00:00 at the local time of the system on the specified date. Waiver files without an expiration date are permanent. `expiration_date` is optional. - `run` is optional. If absent or true, the control will run and be reported, but failures in it won't make the overall run fail. If present and false, the control will not be run. You may use any of yes, no, true or false. To avoid confusion, it is good practice to explicitly specify whether the control should run. @@ -48,6 +59,8 @@ control_id: ### Examples: +Example in YAML: + ```yaml waiver_control_1_2_3: expiration_date: 2019-10-15 @@ -58,3 +71,34 @@ xccdf_org.cisecurity.benchmarks_rule_1.1.1.4_Ensure_mounting_of_hfs_filesystems_ justification: "This might be a bug in the test. @qateam" run: false ``` + +Example in JSON: + +```json +{ + "waiver_control_1_2_3": { + "expiration_date": "2019-10-15T00:00:00.000Z", + "justification": "Not needed until Q3. @secteam" + }, + "xccdf_org.cisecurity.benchmarks_rule_1.1.1.4_Ensure_mounting_of_hfs_filesystems_is_disabled": { + "expiration_date": "2020-03-01T00:00:00.000Z", + "justification": "This might be a bug in the test. @qateam", + "run": false + } +} +``` + +Example in CSV/XLSX/XLS: + +These file formats support the following fields in a file: + +* `control_id` + _Required_. +* `justification` + _Required_. +* `run` + _Optional_. +* `expiration_date` + _Optional_. + +![Waiver File Excel Example](/images/inspec/waivers_file_excel.png) \ No newline at end of file diff --git a/docs-chef-io/static/images/inspec/reporter_outcome_progress_bar.png b/docs-chef-io/static/images/inspec/reporter_outcome_progress_bar.png new file mode 100644 index 000000000..f8a2d1b86 Binary files /dev/null and b/docs-chef-io/static/images/inspec/reporter_outcome_progress_bar.png differ diff --git a/docs-chef-io/static/images/inspec/reporter_outcome_progress_bar_enhanced_outcomes.png b/docs-chef-io/static/images/inspec/reporter_outcome_progress_bar_enhanced_outcomes.png new file mode 100644 index 000000000..7712245fc Binary files /dev/null and b/docs-chef-io/static/images/inspec/reporter_outcome_progress_bar_enhanced_outcomes.png differ diff --git a/docs-chef-io/static/images/inspec/waivers_file_excel.png b/docs-chef-io/static/images/inspec/waivers_file_excel.png new file mode 100644 index 000000000..17f85be14 Binary files /dev/null and b/docs-chef-io/static/images/inspec/waivers_file_excel.png differ diff --git a/inspec.gemspec b/inspec.gemspec index 890295ca4..fb6fb99e5 100644 --- a/inspec.gemspec +++ b/inspec.gemspec @@ -34,6 +34,10 @@ Gem::Specification.new do |spec| # progress bar streaming reporter plugin support spec.add_dependency "progress_bar", "~> 1.3.3" + # roo support for reading excel waiver files + spec.add_dependency "roo", "~> 2.9.0" + spec.add_dependency "roo-xls" # extension for roo to read xls files + # Used for Azure profile until integrated into train spec.add_dependency "faraday_middleware", ">= 0.12.2", "< 1.1" diff --git a/lib/inspec/base_cli.rb b/lib/inspec/base_cli.rb index f876af6f8..89ce04c7f 100644 --- a/lib/inspec/base_cli.rb +++ b/lib/inspec/base_cli.rb @@ -205,6 +205,8 @@ module Inspec long_desc: "Maximum seconds to allow commands to run during execution. A timed out command is considered an error." option :reporter_include_source, type: :boolean, default: false, desc: "Include full source code of controls in the CLI report" + option :enhanced_outcomes, type: :boolean, + desc: "Show enhanced outcomes in output" end def self.help(*args) diff --git a/lib/inspec/cli.rb b/lib/inspec/cli.rb index 052d837bb..894dd46bc 100644 --- a/lib/inspec/cli.rb +++ b/lib/inspec/cli.rb @@ -415,6 +415,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI desc: "Load one or more input files, a YAML file with values for the shell to use" option :input, type: :array, banner: "name1=value1 name2=value2", desc: "Specify one or more inputs directly on the command line to the shell, as --input NAME=VALUE. Accepts single-quoted YAML and JSON structures." + option :enhanced_outcomes, type: :boolean, + desc: "Show enhanced outcomes in output" def shell_func o = config deprecate_target_id(config) @@ -461,11 +463,13 @@ class Inspec::InspecCLI < Inspec::BaseCLI pretty_handle_exception(e) end + option :enhanced_outcomes, type: :boolean, + desc: "Show enhanced outcomes output" desc "schema NAME", "print the JSON schema", hide: true def schema(name) require "inspec/schema/output_schema" - - puts Inspec::Schema::OutputSchema.json(name) + o = config + puts Inspec::Schema::OutputSchema.json(name, o) rescue StandardError => e puts e puts "Valid schemas are #{Inspec::Schema::OutputSchema.names.join(", ")}" diff --git a/lib/inspec/enhanced_outcomes.rb b/lib/inspec/enhanced_outcomes.rb new file mode 100644 index 000000000..fd3e3a784 --- /dev/null +++ b/lib/inspec/enhanced_outcomes.rb @@ -0,0 +1,18 @@ +module Inspec + module EnhancedOutcomes + + def self.determine_status(results, impact) + if results.any? { |r| !r[:exception].nil? && !r[:backtrace].nil? } + "error" + elsif !impact.nil? && impact.to_f == 0.0 + "not_applicable" + elsif results.all? { |r| r[:status] == "skipped" } + "not_reviewed" + elsif results.any? { |r| r[:status] == "failed" } + "failed" + else + "passed" + end + end + end +end diff --git a/lib/inspec/exceptions.rb b/lib/inspec/exceptions.rb index 5782bfd30..6bf386d58 100644 --- a/lib/inspec/exceptions.rb +++ b/lib/inspec/exceptions.rb @@ -10,5 +10,7 @@ module Inspec class SecretsBackendNotFound < ArgumentError; end class ProfileValidationKeyNotFound < ArgumentError; end class ProfileSigningKeyNotFound < ArgumentError; end + class WaiversFileNotReadable < ArgumentError; end + class WaiversFileDoesNotExist < ArgumentError; end end end diff --git a/lib/inspec/formatters/base.rb b/lib/inspec/formatters/base.rb index baf29be9c..bc397722f 100644 --- a/lib/inspec/formatters/base.rb +++ b/lib/inspec/formatters/base.rb @@ -1,12 +1,13 @@ require "rspec/core" require "rspec/core/formatters/base_formatter" require "set" unless defined?(Set) +require "inspec/enhanced_outcomes" module Inspec::Formatters class Base < RSpec::Core::Formatters::BaseFormatter RSpec::Core::Formatters.register self, :close, :dump_summary, :stop - attr_accessor :backend, :run_data + attr_accessor :backend, :run_data, :enhanced_outcomes def initialize(output) super(output) @@ -17,6 +18,7 @@ module Inspec::Formatters @backend = nil @all_controls_count = nil @control_checks_count_map = {} + @enhanced_outcomes = nil end # RSpec Override: #dump_summary @@ -50,7 +52,6 @@ module Inspec::Formatters else hash[:message] = exception_message(e) end - next if e.is_a? RSpec::Expectations::ExpectationNotMetError hash[:exception] = e.class.name @@ -68,6 +69,8 @@ module Inspec::Formatters # flesh out the profiles key with additional profile information run_data[:profiles] = profiles_info + add_enhanced_outcomes_to_controls if enhanced_outcomes + # add the platform information for this particular target run_data[:platform] = { name: platform(:name), @@ -110,6 +113,20 @@ module Inspec::Formatters private + def add_enhanced_outcomes_to_controls + all_unique_controls.each do |control| + control[:status] = determine_control_enhanced_outcome(control) + end + end + + def determine_control_enhanced_outcome(control) + if control[:results] + Inspec::EnhancedOutcomes.determine_status(control[:results], control[:impact]) + else + "passed" + end + end + def all_unique_controls unique_controls = Set.new run_data[:profiles].each do |profile| @@ -120,25 +137,59 @@ module Inspec::Formatters end def statistics + error = 0 + not_applicable = 0 + not_reviewed = 0 failed = 0 - skipped = 0 passed = 0 + skipped = 0 + enhanced_outcomes_summary = {} + if enhanced_outcomes + all_unique_controls.each do |control| - all_unique_controls.each do |control| - next unless control[:results] + if control[:status] == "error" + error += 1 + elsif control[:status] == "not_applicable" + not_applicable += 1 + elsif control[:status] == "not_reviewed" + not_reviewed += 1 + elsif control[:status] == "failed" + failed += 1 + elsif control[:status] == "passed" + passed += 1 + end + + # added this additionally because stats summary is also used for determining exit code in runner rspec + skipped += 1 if control[:results].any? { |r| r[:status] == "skipped" } - if control[:results].any? { |r| r[:status] == "failed" } - failed += 1 - elsif control[:results].any? { |r| r[:status] == "skipped" } - skipped += 1 - else - passed += 1 end + total = error + not_applicable + not_reviewed + failed + passed + enhanced_outcomes_summary = { + not_applicable: { + total: not_applicable, + }, + not_reviewed: { + total: not_reviewed, + }, + error: { + total: error, + }, + } + else + all_unique_controls.each do |control| + next unless control[:results] + + if control[:results].any? { |r| r[:status] == "failed" } + failed += 1 + elsif control[:results].any? { |r| r[:status] == "skipped" } + skipped += 1 + else + passed += 1 + end + end + total = failed + passed + skipped end - - total = failed + passed + skipped - - { + final_summary = { total: total, passed: { total: passed, @@ -150,6 +201,8 @@ module Inspec::Formatters total: failed, }, } + + final_summary.merge!(enhanced_outcomes_summary) end def exception_message(exception) diff --git a/lib/inspec/plugin/v2/plugin_types/reporter.rb b/lib/inspec/plugin/v2/plugin_types/reporter.rb index 8dafa2cd2..d1782a0c3 100644 --- a/lib/inspec/plugin/v2/plugin_types/reporter.rb +++ b/lib/inspec/plugin/v2/plugin_types/reporter.rb @@ -7,6 +7,7 @@ module Inspec::Plugin::V2::PluginType include Inspec::Utils::RunDataFilters attr_reader :run_data + attr_accessor :enhanced_outcomes def initialize(config) @config = config diff --git a/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb b/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb index 5def981c2..07eead8b1 100644 --- a/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +++ b/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb @@ -11,6 +11,7 @@ module Inspec::Plugin::V2::PluginType @running_controls_list = [] @control_checks_count_map = {} @controls_count = nil + @notifications = {} end private @@ -49,5 +50,58 @@ module Inspec::Plugin::V2::PluginType @control_checks_count_map = RSpec.configuration.formatters.grep(Inspec::Formatters::Base).first.get_control_checks_count_map end end + + def enhanced_outcomes + @enhanced_outcomes ||= RSpec.configuration.formatters.grep(Inspec::Formatters::Base).first.enhanced_outcomes + end + + def add_enhanced_outcomes(control_id) + if control_has_error(@notifications[control_id]) + "error" + elsif control_has_impact_zero(@notifications[control_id]) + "not_applicable" + elsif control_has_all_tests_skipped(@notifications[control_id]) + "not_reviewed" + elsif control_has_any_tests_failed(@notifications[control_id]) + "failed" + else + "passed" + end + end + + def control_has_error(notifications) + notifications.any? do |notification_data| + notification, _status = notification_data + !notification.example.exception.nil? && !(notification.example.exception.is_a? RSpec::Expectations::ExpectationNotMetError) && !notification.example.exception.backtrace.nil? + end + end + + def control_has_all_tests_skipped(notifications) + notifications.all? do |notification_data| + _notification, status = notification_data + status == "skipped" + end + end + + def control_has_any_tests_failed(notifications) + notifications.any? do |notification_data| + _notification, status = notification_data + status == "failed" + end + end + + def control_has_impact_zero(notifications) + notification_data = notifications.first + notification_impact = notification_data.first.example.metadata[:impact] + notification_data && !notification_impact.nil? && notification_impact.to_f == 0.0 + end + + def collect_notifications(notification, control_id, status) + if @notifications[control_id].nil? + @notifications[control_id] = [[notification, status]] + else + @notifications[control_id].push([notification, status]) + end + end end end diff --git a/lib/inspec/profile.rb b/lib/inspec/profile.rb index 126264dc3..b6fd28333 100644 --- a/lib/inspec/profile.rb +++ b/lib/inspec/profile.rb @@ -350,22 +350,24 @@ module Inspec def load_libraries return @runner_context if @libraries_loaded - locked_dependencies.dep_list.each_with_index do |(_name, dep), i| + locked_dependencies.dep_list.each_with_index do |(_name, dep), index| d = dep.profile # this will force a dependent profile load so we are only going to add # this metadata if the parent profile is supported. if @supports_platform && !d.supports_platform? # since ruby 1.9 hashes are ordered so we can just use index values here # TODO: NO! this is a violation of encapsulation to an extreme - metadata.dependencies[i][:status] = "skipped" - msg = "Skipping profile: '#{d.name}' on unsupported platform: '#{d.backend.platform.name}/#{d.backend.platform.release}'." - metadata.dependencies[i][:status_message] = msg - metadata.dependencies[i][:skip_message] = msg # Repeat as skip_message for backward compatibility + if metadata.dependencies[index] + metadata.dependencies[index][:status] = "skipped" + msg = "Skipping profile: '#{d.name}' on unsupported platform: '#{d.backend.platform.name}/#{d.backend.platform.release}'." + metadata.dependencies[index][:status_message] = msg + metadata.dependencies[index][:skip_message] = msg # Repeat as skip_message for backward compatibility + end next - elsif metadata.dependencies[i] + elsif metadata.dependencies[index] # Currently wrapper profiles will load all dependencies, and then we # load them again when we dive down. This needs to be re-done. - metadata.dependencies[i][:status] = "loaded" + metadata.dependencies[index][:status] = "loaded" end # rubocop:disable Layout/ExtraSpacing diff --git a/lib/inspec/reporters.rb b/lib/inspec/reporters.rb index 912641cea..f026984ef 100644 --- a/lib/inspec/reporters.rb +++ b/lib/inspec/reporters.rb @@ -7,7 +7,7 @@ require "inspec/reporters/yaml" module Inspec::Reporters # rubocop:disable Metrics/CyclomaticComplexity - def self.render(reporter, run_data) + def self.render(reporter, run_data, enhanced_outcomes = false) name, config = reporter.dup config[:run_data] = run_data case name @@ -29,6 +29,7 @@ module Inspec::Reporters activator.activate! reporter = activator.implementation_class.new(config) end + reporter.enhanced_outcomes = enhanced_outcomes # optional send_report method on reporter return reporter.send_report if defined?(reporter.send_report) diff --git a/lib/inspec/reporters/base.rb b/lib/inspec/reporters/base.rb index 9060ea4df..61b83d79a 100644 --- a/lib/inspec/reporters/base.rb +++ b/lib/inspec/reporters/base.rb @@ -5,6 +5,7 @@ module Inspec::Reporters include Inspec::Utils::RunDataFilters attr_reader :run_data + attr_accessor :enhanced_outcomes def initialize(config) @config = config diff --git a/lib/inspec/reporters/cli.rb b/lib/inspec/reporters/cli.rb index 14263cdf6..a2c26c39b 100644 --- a/lib/inspec/reporters/cli.rb +++ b/lib/inspec/reporters/cli.rb @@ -9,6 +9,9 @@ module Inspec::Reporters "passed" => "\033[0;1;32m", "skipped" => "\033[0;37m", "reset" => "\033[0m", + "error" => "\033[34m", + "not_applicable" => "\033[36m", + "not_reviewed" => "\033[33m", }.freeze # Most currently available Windows terminals have poor support @@ -18,6 +21,9 @@ module Inspec::Reporters "skipped" => "[SKIP]", "passed" => "[PASS]", "unknown" => "[UNKN]", + "error" => "[ERR]", + "not_applicable" => "[N/A]", + "not_reviewed" => "[N/R]", }.freeze else # Extended colors for everyone else @@ -26,6 +32,9 @@ module Inspec::Reporters "passed" => "\033[38;5;41m", "skipped" => "\033[38;5;247m", "reset" => "\033[0m", + "error" => "\033[0;38;5;21m", + "not_applicable" => "\033[0;38;5;117m", + "not_reviewed" => "\033[0;38;5;214m", }.freeze # Groovy UTF-8 characters for everyone else... @@ -35,6 +44,9 @@ module Inspec::Reporters "skipped" => "↺", "passed" => "✔", "unknown" => "?", + "error" => "ERR", + "not_applicable" => "N/A", + "not_reviewed" => "N/R", }.freeze end @@ -63,7 +75,11 @@ module Inspec::Reporters end output("") - print_profile_summary + if enhanced_outcomes + print_control_outcomes_summary + else + print_profile_summary + end print_tests_summary end @@ -88,6 +104,7 @@ module Inspec::Reporters def print_standard_control_results(profile) standard_controls_from_profile(profile).each do |control_from_profile| control = Control.new(control_from_profile) + control.enhanced_outcomes = enhanced_outcomes next if control.results.nil? output(format_control_header(control)) @@ -122,7 +139,7 @@ module Inspec::Reporters end def format_control_header(control) - impact = control.impact_string + impact = enhanced_outcomes ? control.impact_string_for_enhanced_outcomes : control.impact_string format_message( color: impact, indicator: impact, @@ -292,6 +309,68 @@ module Inspec::Reporters } end + def control_outcomes_summary + failed = 0 + passed = 0 + error = 0 + not_reviewed = 0 + not_applicable = 0 + + all_unique_controls.each do |control| + next if control[:status].empty? + + if control[:status] == "failed" + failed += 1 + elsif control[:status] == "error" + error += 1 + elsif control[:status] == "not_reviewed" + not_reviewed += 1 + elsif control[:status] == "not_applicable" + not_applicable += 1 + else + passed += 1 + end + end + + total = failed + passed + error + not_reviewed + not_applicable + + { + "total" => total, + "failed" => failed, + "passed" => passed, + "error" => error, + "not_reviewed" => not_reviewed, + "not_applicable" => not_applicable, + } + end + + def print_control_outcomes_summary + summary = control_outcomes_summary + return unless summary["total"] > 0 + + success_str = summary["passed"] == 1 ? "1 successful control" : "#{summary["passed"]} successful controls" + failed_str = summary["failed"] == 1 ? "1 control failure" : "#{summary["failed"]} control failures" + error_str = summary["error"] == 1 ? "1 control has error" : "#{summary["error"]} controls have error" + not_rev_str = summary["not_reviewed"] == 1 ? "1 control not reviewed" : "#{summary["not_reviewed"]} controls not reviewed" + not_app_str = summary["not_applicable"] == 1 ? "1 control not applicable" : "#{summary["not_applicable"]} controls not applicable" + + success_color = summary["passed"] > 0 ? "passed" : "no_color" + failed_color = summary["failed"] > 0 ? "failed" : "no_color" + error_color = summary["error"] > 0 ? "error" : "no_color" + not_rev_color = summary["not_reviewed"] > 0 ? "not_reviewed" : "no_color" + not_app_color = summary["not_applicable"] > 0 ? "not_applicable" : "no_color" + + s = format( + "Profile Summary: %s, %s, %s, %s, %s", + format_with_color(success_color, success_str), + format_with_color(failed_color, failed_str), + format_with_color(not_rev_color, not_rev_str), + format_with_color(not_app_color, not_app_str), + format_with_color(error_color, error_str) + ) + output(s) if summary["total"] > 0 + end + def print_profile_summary summary = profile_summary return unless summary["total"] > 0 @@ -350,6 +429,7 @@ module Inspec::Reporters class Control attr_reader :data + attr_accessor :enhanced_outcomes def initialize(control_hash) @data = control_hash @@ -379,6 +459,10 @@ module Inspec::Reporters id.start_with?("(generated from ") end + def status + data[:status] + end + def title_for_report # if this is an anonymous control, just grab the resource title from any result entry return results.first[:resource_title] if anonymous? @@ -392,10 +476,17 @@ module Inspec::Reporters # append a failure summary if appropriate. title_for_report += " (#{failure_count} failed)" if failure_count > 0 title_for_report += " (#{skipped_count} skipped)" if skipped_count > 0 - title_for_report end + def impact_string_for_enhanced_outcomes + if impact.nil? + "unknown" + else + status + end + end + def impact_string if anonymous? nil diff --git a/lib/inspec/reporters/json.rb b/lib/inspec/reporters/json.rb index dbe584754..85b2f2917 100644 --- a/lib/inspec/reporters/json.rb +++ b/lib/inspec/reporters/json.rb @@ -114,7 +114,7 @@ module Inspec::Reporters def profile_controls(profile) (profile[:controls] || []).map { |c| - { + control_hash = { id: c[:id], title: c[:title], desc: c.dig(:descriptions, :default), @@ -130,6 +130,8 @@ module Inspec::Reporters waiver_data: c[:waiver_data] || {}, results: profile_results(c), } + control_hash.merge!({ status: c[:status] }) if enhanced_outcomes + control_hash } end diff --git a/lib/inspec/reporters/yaml.rb b/lib/inspec/reporters/yaml.rb index f327b5c4b..4cb44779a 100644 --- a/lib/inspec/reporters/yaml.rb +++ b/lib/inspec/reporters/yaml.rb @@ -3,7 +3,9 @@ require "yaml" module Inspec::Reporters class Yaml < Base def render - output(Inspec::Reporters::Json.new({ run_data: run_data }).report.to_yaml, false) + json_reporter_obj = Inspec::Reporters::Json.new({ run_data: run_data }) + json_reporter_obj.enhanced_outcomes = enhanced_outcomes + output(json_reporter_obj.report.to_yaml, false) end def report diff --git a/lib/inspec/resources/podman.rb b/lib/inspec/resources/podman.rb new file mode 100644 index 000000000..90987ada9 --- /dev/null +++ b/lib/inspec/resources/podman.rb @@ -0,0 +1,353 @@ +require "inspec/resources/command" +require "inspec/utils/filter" +require "hashie/mash" + +module Inspec::Resources + class Podman < Inspec.resource(1) + # Resource requires an internal name. + name "podman" + + # Restrict to only run on the below platforms (if none were given, + # all OS's and cloud API's supported) + supports platform: "unix" + + desc "A resource to retrieve information about podman" + + example <<~EXAMPLE + describe podman.containers do + its('images') { should include "docker.io/library/ubuntu:latest" } + end + + describe podman.images do + its('names') { should_not include "docker.io/library/ubuntu:latest" } + end + + describe podman.pods do + its("ids") { should include "95cadbb84df71e6374fceb3fd89ee3b8f2c7e1a831062cd9cea7d0e3e4b1dbcc" } + end + + describe podman.info.host do + its("os") { should eq "linux"} + end + + describe podman.version do + its("Client.Version") { should eq "4.1.0"} + end + + podman.containers.ids.each do |id| + # call podman inspect for a specific container id + describe podman.object(id) do + its("State.OciVersion") { should eq "1.0.2-dev" } + its("State.Running") { should eq true} + end + end + EXAMPLE + + def containers + PodmanContainerFilter.new(parse_containers) + end + + def images + PodmanImageFilter.new(parse_images) + end + + def networks + PodmanNetworkFilter.new(parse_networks) + end + + def pods + PodmanPodFilter.new(parse_pods) + end + + def volumes + PodmanVolumeFilter.new(parse_volumes) + end + + def version + return @version if defined?(@version) + + sub_cmd = "version --format json" + output = run_command(sub_cmd) + @version = Hashie::Mash.new(JSON.parse(output)) + rescue JSON::ParserError => _e + Hashie::Mash.new({}) + end + + def info + return @info if defined?(@info) + + sub_cmd = "info --format json" + output = run_command(sub_cmd) + @info = Hashie::Mash.new(JSON.parse(output)) + rescue JSON::ParserError => _e + Hashie::Mash.new({}) + end + + # returns information about podman objects + def object(id) + return @inspect if defined?(@inspect) + + output = run_command("inspect #{id} --format json") + data = JSON.parse(output) + data = data[0] if data.is_a?(Array) + @inspect = Hashie::Mash.new(data) + rescue JSON::ParserError => _e + Hashie::Mash.new({}) + end + + def to_s + "Podman" + end + + private + + # Calls the run_command method to get all podman containers and parse the command output. + # Returns the parsed command output. + def parse_containers + labels = %w{ID Image ImageID Command CreatedAt RunningFor Status Pod Ports Size Names Networks Labels Mounts} + parse_json_command(labels, "ps -a --no-trunc --size") + end + + # Calls the run_command method to get all podman images and parse the command output. + # Returns the parsed command output. + def parse_images + labels = %w{ID Repository Tag Size Digest CreatedAt CreatedSince History} + parse_json_command(labels, "images -a --no-trunc") + end + + # Calls the run_command method to get all podman network list and parse the command output. + # Returns the parsed command output. + def parse_networks + labels = %w{ID Name Driver Labels Options IPAMOptions Created Internal IPv6Enabled DNSEnabled NetworkInterface Subnets} + parse_json_command(labels, "network ls --no-trunc") + end + + # Calls the run_command method to get all podman pod list and parse the command output. + # Returns the parsed command output. + def parse_pods + sub_cmd = "pod ps --no-trunc --format json" + output = run_command(sub_cmd) + parse(output) + end + + # Calls the run_command method to get all podman volume list and parse the command output. + # Returns the parsed command output. + def parse_volumes + sub_cmd = "volume ls --format json" + output = run_command(sub_cmd) + parse(output) + end + + # Runs the given podman command on the host machine on which podman is installed + # Returns the command output or raises the command execution error. + def run_command(subcommand) + result = inspec.command("podman #{subcommand}") + if result.stderr.empty? + result.stdout + else + raise "Error while running command \'podman #{subcommand}\' : #{result.stderr}" + end + end + + def parse_json_command(labels, subcommand) + # build command + format = labels.map { |label| "\"#{label}\": {{json .#{label}}}" } + raw = inspec.command("podman #{subcommand} --format '{#{format.join(", ")}}'").stdout + output = [] + + raw.each_line do |entry| + # convert all keys to lower_case to work well with ruby and filter table + row = JSON.parse(entry).map do |key, value| + [key.downcase, value] + end.to_h + + # ensure all keys are there + row = ensure_keys(row, labels) + output.push(row) + end + + output + rescue JSON::ParserError => _e + warn "Could not parse `podman #{subcommand}` output" + [] + end + + def ensure_keys(entry, labels) + labels.each do |key| + entry[key.downcase] = nil unless entry.key?(key.downcase) + end + entry + end + + # Method to parse JDON content. + # Returns: Parsed data. + def parse(content) + require "json" unless defined?(JSON) + output = JSON.parse(content) + parsed_output = [] + output.each do |entry| + entry = entry.map do |k, v| + [k.downcase, v] + end.to_h + parsed_output << entry + end + parsed_output + rescue => e + raise Inspec::Exceptions::ResourceFailed, "Unable to parse command JSON output: #{e.message}" + end + end + + # class for podman.containers plural resource + class PodmanContainerFilter + filter = FilterTable.create + filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? } + filter.register_column(:commands, field: "command") + .register_column(:ids, field: "id") + .register_column(:created_at, field: "createdat") + .register_column(:images, field: "image") + .register_column(:names, field: "names") + .register_column(:status, field: "status") + .register_column(:image_ids, field: "image_id") + .register_column(:labels, field: "labels", style: :simple) + .register_column(:mounts, field: "mounts") + .register_column(:networks, field: "networks") + .register_column(:pods, field: "pod") + .register_column(:ports, field: "ports") + .register_column(:sizes, field: "size") + .register_column(:running_for, field: "running_for") + .register_custom_matcher(:running?) do |x| + x.where { status.downcase.start_with?("up") } + end + filter.install_filter_methods_on_resource(self, :containers) + + attr_reader :containers + def initialize(containers) + @containers = containers + end + + def to_s + "Podman Containers" + end + + def resource_id + "Podman Containers" + end + end + + # class for podman.images plural resource + class PodmanImageFilter + filter = FilterTable.create + filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? } + filter.register_column(:ids, field: "id") + .register_column(:repositories, field: "repository") + .register_column(:tags, field: "tag") + .register_column(:sizes, field: "size") + .register_column(:digests, field: "digest") + .register_column(:created_at, field: "createdat") + .register_column(:created_since, field: "createdsince") + .register_column(:history, field: "history") + filter.install_filter_methods_on_resource(self, :images) + + attr_reader :images + def initialize(images) + @images = images + end + + def to_s + "Podman Images" + end + + def resource_id + "Podman Images" + end + end + + class PodmanNetworkFilter + filter = FilterTable.create + filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? } + .register_column(:ids, field: "id") + .register_column(:names, field: "name") + .register_column(:drivers, field: "driver") + .register_column(:network_interfaces, field: "networkinterface") + .register_column(:created, field: "created") + .register_column(:subnets, field: "subnets") + .register_column(:ipv6_enabled, field: "ipv6enabled") + .register_column(:internal, field: "internal") + .register_column(:dns_enabled, field: "dnsenabled") + .register_column(:ipam_options, field: "ipamoptions") + .register_column(:options, field: "options") + .register_column(:labels, field: "labels") + filter.install_filter_methods_on_resource(self, :networks) + + attr_reader :networks + def initialize(networks) + @networks = networks + end + + def to_s + "Podman Networks" + end + + def resource_id + "Podman Networks" + end + end + + class PodmanPodFilter + filter = FilterTable.create + filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? } + .register_column(:ids, field: "id") + .register_column(:cgroups, field: "cgroup") + .register_column(:containers, field: "containers") + .register_column(:created, field: "created") + .register_column(:infraids, field: "infraid") + .register_column(:names, field: "name") + .register_column(:namespaces, field: "namespace") + .register_column(:networks, field: "networks") + .register_column(:status, field: "status") + .register_column(:labels, field: "labels") + filter.install_filter_methods_on_resource(self, :pods) + + attr_reader :pods + def initialize(pods) + @pods = pods + end + + def to_s + "Podman Pods" + end + + def resource_id + "Podman Pods" + end + end + + class PodmanVolumeFilter + filter = FilterTable.create + filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? } + .register_column(:names, field: "name") + .register_column(:drivers, field: "driver") + .register_column(:mountpoints, field: "mountpoint") + .register_column(:createdat, field: "createdat") + .register_column(:labels, field: "labels") + .register_column(:scopes, field: "scope") + .register_column(:options, field: "options") + .register_column(:mountcount, field: "mountcount") + .register_column(:needscopyup, field: "needscopyup") + .register_column(:needschown, field: "needschown") + filter.install_filter_methods_on_resource(self, :volumes) + + attr_reader :volumes + def initialize(volumes) + @volumes = volumes + end + + def to_s + "Podman Volumes" + end + + def resource_id + "Podman Volumes" + end + end +end diff --git a/lib/inspec/resources/podman_container.rb b/lib/inspec/resources/podman_container.rb new file mode 100644 index 000000000..86aef3c7f --- /dev/null +++ b/lib/inspec/resources/podman_container.rb @@ -0,0 +1,84 @@ +require "inspec/resources/podman" +require_relative "docker_object" + +# Change module if required +module Inspec::Resources + class PodmanContainer < Inspec.resource(1) + include Inspec::Resources::DockerObject + name "podman_container" + supports platform: "unix" + + desc "Inspec core resource to retrieve information about podman container" + + example <<~EXAMPLE + describe podman_container("sweet_mendeleev") do + it { should exist } + it { should be_running } + its("id") { should eq "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7" } + its("image") { should eq "docker.io/library/nginx:latest" } + its("labels") { should include "maintainer"=>"NGINX Docker Maintainers " } + its("ports") { should eq nil } + end + + describe podman_container(id: "591270d8d80d2667") do + it { should exist } + it { should be_running } + end + EXAMPLE + + def initialize(opts = {}) + skip_resource "The `podman_container` resource is not yet available on your OS." unless inspec.os.unix? + + # if a string is provided, we expect it is the name + if opts.is_a?(String) + @opts = { name: opts } + else + @opts = opts + end + end + + def running? + status.downcase.start_with?("up") if object_info.entries.length == 1 + end + + def status + object_info.status[0] if object_info.entries.length == 1 + end + + def labels + object_info.labels + end + + def ports + object_info.ports[0] if object_info.entries.length == 1 + end + + def command + return unless object_info.entries.length == 1 + + object_info.commands[0] + end + + def image + object_info.images[0] if object_info.entries.length == 1 + end + + def resource_id + object_info.ids[0] || @opts[:id] || @opts[:name] || "" + end + + def to_s + name = @opts[:name] || @opts[:id] + "Podman Container #{name}" + end + + private + + def object_info + return @info if defined?(@info) + + opts = @opts + @info = inspec.podman.containers.where { names == opts[:name] || (!id.nil? && !opts[:id].nil? && (id == opts[:id] || id.start_with?(opts[:id]))) } + end + end +end diff --git a/lib/inspec/resources/podman_image.rb b/lib/inspec/resources/podman_image.rb new file mode 100644 index 000000000..5fa72f9c6 --- /dev/null +++ b/lib/inspec/resources/podman_image.rb @@ -0,0 +1,108 @@ +require "inspec/resources/command" +require_relative "docker_object" +require "inspec/utils/podman" + +module Inspec::Resources + class PodmanImage < Inspec.resource(1) + include Inspec::Resources::DockerObject + include Inspec::Utils::Podman + + name "podman_image" + supports platform: "unix" + + desc "InSpec core resource to retrieve information about podman image" + + example <<~EXAMPLE + describe podman_image("docker.io/library/busybox") do + it { should exist } + its("repo_tags") { should include "docker.io/library/busybox:latest" } + its("size") { should eq 1636053 } + its("resource_id") { should eq "docker.io/library/busybox:latest" } + end + + describe podman_image("docker.io/library/busybox:latest") do + it { should exist } + end + + describe podman_image(repo: "docker.io/library/busybox", tag: "latest") do + it { should exist } + end + + describe podman_image(id: "3c19bafed223") do + it { should exist } + end + EXAMPLE + + attr_reader :opts, :image_info + + def initialize(opts) + skip_resource "The `podman_image` resource is not yet available on your OS." unless inspec.os.unix? + opts = { image: opts } if opts.is_a?(String) + @opts = sanitize_options(opts) + raise Inspec::Exceptions::ResourceFailed, "Podman is not running. Please make sure it is installed and running." unless podman_running? + + @image_info = get_image_info + end + + LABELS = { + "id" => "ID", + "repo_tags" => "RepoTags", + "size" => "Size", + "digest" => "Digest", + "created_at" => "Created", + "version" => "Version", + "names_history" => "NamesHistory", + "repo_digests" => "RepoDigests", + "architecture" => "Architecture", + "os" => "Os", + "virtual_size" => "VirtualSize", + }.freeze + + ## This creates all the required properties methods dynamically. + LABELS.each do |k, v| + define_method(k) do + image_info[k.to_s] + end + end + + def exist? + ! image_info.empty? + end + + def resource_id + opts[:id] || opts[:image] || "" + end + + def to_s + "podman_image #{resource_id}" + end + + private + + def sanitize_options(opts) + opts.merge!(parse_components_from_image(opts[:image])) + + # assume a "latest" tag if we don't have one + opts[:tag] ||= "latest" + + # Assemble/reassemble the image from the repo and tag + opts[:image] = "#{opts[:repo]}:#{opts[:tag]}" unless opts[:repo].nil? + + opts + end + + def get_image_info + current_image = opts[:id] || opts[:image] || opts[:repo] + ":" + opts[:tag] + json_key_label = generate_go_template(LABELS) + podman_inspect_cmd = inspec.command("podman image inspect #{current_image} --format '{#{json_key_label}}'") + + if podman_inspect_cmd.exit_status == 0 + parse_command_output(podman_inspect_cmd.stdout) + elsif podman_inspect_cmd.stderr =~ /failed to find image/ + {} + else + raise Inspec::Exceptions::ResourceFailed, "Unable to retrieve podman image information for #{current_image}.\nError message: #{podman_inspect_cmd.stderr}" + end + end + end +end diff --git a/lib/inspec/resources/podman_network.rb b/lib/inspec/resources/podman_network.rb new file mode 100644 index 000000000..e9b604da9 --- /dev/null +++ b/lib/inspec/resources/podman_network.rb @@ -0,0 +1,81 @@ +require "inspec/resources/command" +require "inspec/utils/podman" +module Inspec::Resources + class PodmanNetwork < Inspec.resource(1) + include Inspec::Utils::Podman + + name "podman_network" + + supports platform: "unix" + + desc "InSpec core resource to retrive information about the given Podman network" + + example <<~EXAMPLE + describe podman_network("podman") do + it { should exist } + end + describe podman_network("3a7c94d937d5f3a0f1a9b1610589945aedfbe56207fd5d32fc8154aa1a8b007f") do + its("driver") { should eq bridge } + end + EXAMPLE + + LABELS = { + id: "ID", + name: "Name", + driver: "Driver", + labels: "Labels", + options: "Options", + ipam_options: "IPAMOptions", + internal: "Internal", + created: "Created", + ipv6_enabled: "IPv6Enabled", + dns_enabled: "DNSEnabled", + network_interface: "NetworkInterface", + subnets: "Subnets", + }.freeze + + attr_reader :param, :network_info + def initialize(param) + skip_resource "The `podman_network` resource is not yet available on your OS." unless inspec.os.unix? + + @param = param + raise Inspec::Exceptions::ResourceFailed, "Podman is not running. Please make sure it is installed and running." unless podman_running? + + @network_info = get_network_info + end + + ## This creates all the required properties methods dynamically. + LABELS.each do |k, v| + define_method(k) do + network_info[k.to_s] + end + end + + def exist? + !network_info.empty? + end + + def resource_id + id || param || "" + end + + def to_s + "podman_network #{resource_id}" + end + + private + + def get_network_info + go_template_format = generate_go_template(LABELS) + result = inspec.command("podman network inspect #{param} --format '{#{go_template_format}}'") + + if result.exit_status == 0 + parse_command_output(result.stdout) + elsif result.stderr =~ /network not found/ + {} + else + raise Inspec::Exceptions::ResourceFailed, "Unable to retrieve podman network information for #{param}.\nError message: #{result.stderr}" + end + end + end +end diff --git a/lib/inspec/resources/podman_pod.rb b/lib/inspec/resources/podman_pod.rb new file mode 100644 index 000000000..cc3d0deca --- /dev/null +++ b/lib/inspec/resources/podman_pod.rb @@ -0,0 +1,101 @@ +require "inspec/resources/command" +require "inspec/utils/podman" + +module Inspec::Resources + class PodmanPod < Inspec.resource(1) + include Inspec::Utils::Podman + + name "podman_pod" + supports platform: "unix" + + desc "InSpec core resource to retrieve information about podman pod" + + example <<~EXAMPLE + describe podman_pod("nginx-frontend") do + it { should exist } + its("id") { should eq "fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4" } + its("name") { should eq "nginx-frontend" } + its("created_at") { should eq "2022-07-14T15:47:47.978078124+05:30" } + its("create_command") { should include "new:nginx-frontend" } + its("state") { should eq "Running" } + its("hostname") { should eq "" } + its("create_cgroup") { should eq true } + its("cgroup_parent") { should eq "user.slice" } + its("cgroup_path") { should eq "user.slice/user-libpod_pod_fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4.slice" } + its("create_infra") { should eq true } + its("infra_container_id") { should eq "727538044b32a165934729dc2d47d9d5e981b6496aebfad7de470f7e76ea4251" } + its("infra_config") { should include "DNSOption" } + its("shared_namespaces") { should include "ipc" } + its("num_containers") { should eq 2 } + its("containers") { should_not be nil } + end + + describe podman_pod("non-existing-pod") do + it { should_not exist } + end + EXAMPLE + + attr_reader :pod_info, :pod_id + + def initialize(pod_id) + skip_resource "The `podman_pod` resource is not yet available on your OS." unless inspec.os.unix? + raise Inspec::Exceptions::ResourceFailed, "Podman is not running. Please make sure it is installed and running." unless podman_running? + + @pod_id = pod_id + @pod_info = get_pod_info + end + + LABELS = { + "id" => "ID", + "name" => "Name", + "created_at" => "Created", + "create_command" => "CreateCommand", + "state" => "State", + "hostname" => "Hostname", + "create_cgroup" => "CreateCgroup", + "cgroup_parent" => "CgroupParent", + "cgroup_path" => "CgroupPath", + "create_infra" => "CreateInfra", + "infra_container_id" => "InfraContainerID", + "infra_config" => "InfraConfig", + "shared_namespaces" => "SharedNamespaces", + "num_containers" => "NumContainers", + "containers" => "Containers", + }.freeze + + # This creates all the required properties methods dynamically. + LABELS.each do |k, _| + define_method(k) do + pod_info[k.to_s] + end + end + + def exist? + !pod_info.empty? + end + + def resource_id + pod_id + end + + def to_s + "Podman Pod #{resource_id}" + end + + private + + def get_pod_info + json_key_label = generate_go_template(LABELS) + + inspect_pod_cmd = inspec.command("podman pod inspect #{pod_id} --format '{#{json_key_label}}'") + + if inspect_pod_cmd.exit_status == 0 + parse_command_output(inspect_pod_cmd.stdout) + elsif inspect_pod_cmd.stderr =~ /no pod with name or ID/ + {} + else + raise Inspec::Exceptions::ResourceFailed, "Unable to retrieve podman pod information for #{pod_id}.\nError message: #{inspect_pod_cmd.stderr}" + end + end + end +end diff --git a/lib/inspec/resources/podman_volume.rb b/lib/inspec/resources/podman_volume.rb new file mode 100644 index 000000000..099512dfa --- /dev/null +++ b/lib/inspec/resources/podman_volume.rb @@ -0,0 +1,87 @@ +require "inspec/resources/command" +require "inspec/utils/podman" + +module Inspec::Resources + class PodmanVolume < Inspec.resource(1) + include Inspec::Utils::Podman + + name "podman_volume" + supports platform: "unix" + + desc "InSpec core resource to retrieve information about podman volume" + + example <<~EXAMPLE + describe podman_volume("my_volume") do + it { should exist } + its("name") { should eq "my_volume" } + its("driver") { should eq "local" } + its("mountpoint") { should eq "/var/home/core/.local/share/containers/storage/volumes/my_volume/_data" } + its("created_at") { should eq "2022-07-14T13:21:19.965421792+05:30" } + its("labels") { should eq({}) } + its("scope") { should eq "local" } + its("options") { should eq({}) } + its("mount_count") { should eq 0 } + its("needs_copy_up") { should eq true } + its("needs_chown") { should eq true } + end + EXAMPLE + + attr_reader :volume_info, :volume_name + + def initialize(volume_name) + skip_resource "The `podman_volume` resource is not yet available on your OS." unless inspec.os.unix? + raise Inspec::Exceptions::ResourceFailed, "Podman is not running. Please make sure it is installed and running." unless podman_running? + + @volume_name = volume_name + @volume_info = get_volume_info + end + + LABELS = { + "name" => "Name", + "driver" => "Driver", + "mountpoint" => "Mountpoint", + "created_at" => "CreatedAt", + "labels" => "Labels", + "scope" => "Scope", + "options" => "Options", + "mount_count" => "MountCount", + "needs_copy_up" => "NeedsCopyUp", + "needs_chown" => "NeedsChown", + }.freeze + + # This creates all the required properties methods dynamically. + LABELS.each do |k, _| + define_method(k) do + volume_info[k.to_s] + end + end + + def exist? + !volume_info.empty? + end + + def resource_id + volume_name + end + + def to_s + "podman_volume #{resource_id}" + end + + private + + def get_volume_info + json_key_label = generate_go_template(LABELS) + + inspect_volume_cmd = inspec.command("podman volume inspect #{volume_name} --format '{#{json_key_label}}'") + + if inspect_volume_cmd.exit_status == 0 + parse_command_output(inspect_volume_cmd.stdout) + elsif inspect_volume_cmd.stderr =~ /inspecting object: no such/ + {} + else + raise Inspec::Exceptions::ResourceFailed, "Unable to retrieve podman volume information for #{volume_name}.\nError message: #{inspect_volume_cmd.stderr}" + end + end + end +end diff --git a/lib/inspec/rule.rb b/lib/inspec/rule.rb index 26b302b4c..326566448 100644 --- a/lib/inspec/rule.rb +++ b/lib/inspec/rule.rb @@ -8,6 +8,8 @@ require "inspec/impact" require "inspec/resource" require "inspec/resources/os" require "inspec/input_registry" +require "inspec/waiver_file_reader" +require "inspec/utils/convert" module Inspec class Rule @@ -133,10 +135,11 @@ module Inspec # # @param [Type] &block returns true if tests are added, false otherwise # @return [nil] - def only_if(message = nil) + def only_if(message = nil, impact: nil) return unless block_given? return if @__skip_only_if_eval == true + self.impact(impact) if impact && !yield @__skip_rule[:result] ||= !yield @__skip_rule[:type] = :only_if @__skip_rule[:message] = message @@ -337,17 +340,20 @@ module Inspec # only_if mechanism) # Double underscore: not intended to be called as part of the DSL def __apply_waivers - input_name = @__rule_id # TODO: control ID slugging - registry = Inspec::InputRegistry.instance - input = registry.inputs_by_profile.dig(__profile_id, input_name) - return unless input && input.has_value? && input.value.is_a?(Hash) + control_id = @__rule_id # TODO: control ID slugging + waiver_files = Inspec::Config.cached.final_options["waiver_file"] if Inspec::Config.cached.respond_to?(:final_options) + + waiver_data_by_profile = Inspec::WaiverFileReader.fetch_waivers_by_profile(__profile_id, waiver_files) unless waiver_files.nil? + + return unless waiver_data_by_profile && waiver_data_by_profile[control_id] && waiver_data_by_profile[control_id].is_a?(Hash) # An InSpec Input is a datastructure that tracks a profile parameter # over time. Its value can be set by many sources, and it keeps a # log of each "set" event so that when it is collapsed to a value, # it can determine the correct (highest priority) value. # Store in an instance variable for.. later reading??? - @__waiver_data = input.value + @__waiver_data = waiver_data_by_profile[control_id] + __waiver_data["skipped_due_to_waiver"] = false __waiver_data["message"] = "" @@ -376,6 +382,7 @@ module Inspec # expiration_date. We only care here if it has a "run" key and it # is false-like, since all non-skipped waiver operations are handled # during reporting phase. + __waiver_data["run"] = Converter.to_boolean(__waiver_data["run"]) if __waiver_data.key?("run") return unless __waiver_data.key?("run") && !__waiver_data["run"] # OK, apply a skip. diff --git a/lib/inspec/run_data/control.rb b/lib/inspec/run_data/control.rb index 4bd42f482..3791300f0 100644 --- a/lib/inspec/run_data/control.rb +++ b/lib/inspec/run_data/control.rb @@ -1,3 +1,5 @@ +require "inspec/enhanced_outcomes" + module Inspec class RunData Control = Struct.new( @@ -31,6 +33,10 @@ module Inspec ].each do |field| self[field] = raw_ctl_data[field] end + + def status + Inspec::EnhancedOutcomes.determine_status(results, impact) + end end end diff --git a/lib/inspec/run_data/statistics.rb b/lib/inspec/run_data/statistics.rb index 8b4a9d2f9..104f28828 100644 --- a/lib/inspec/run_data/statistics.rb +++ b/lib/inspec/run_data/statistics.rb @@ -16,14 +16,20 @@ module Inspec :total, :passed, :skipped, - :failed + :failed, + :not_reviewed, + :not_applicable, + :error ) do include HashLikeStruct def initialize(raw_stat_ctl_data) self.total = raw_stat_ctl_data[:total] self.passed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:passed][:total]) - self.skipped = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:skipped][:total]) self.failed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:failed][:total]) + self.skipped = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:skipped][:total]) if raw_stat_ctl_data[:skipped] + self.not_reviewed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:not_reviewed][:total]) if raw_stat_ctl_data[:not_reviewed] + self.not_applicable = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:not_applicable][:total]) if raw_stat_ctl_data[:not_applicable] + self.error = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:error][:total]) if raw_stat_ctl_data[:error] end end class Controls diff --git a/lib/inspec/runner.rb b/lib/inspec/runner.rb index 5faaa4325..f6c510a35 100644 --- a/lib/inspec/runner.rb +++ b/lib/inspec/runner.rb @@ -60,9 +60,11 @@ module Inspec end if @conf[:waiver_file] - waivers = @conf.delete(:waiver_file) - @conf[:input_file] ||= [] - @conf[:input_file].concat waivers + @conf[:waiver_file].each do |file| + unless File.file?(file) + raise Inspec::Exceptions::WaiversFileDoesNotExist, "Waiver file #{file} does not exist." + end + end end # About reading inputs: @@ -133,12 +135,20 @@ module Inspec all_controls.each do |rule| unless rule.nil? register_rule(rule) - checks = ::Inspec::Rule.prepare_checks(rule) - unless checks.empty? + total_checks = 0 + control_describe_checks = ::Inspec::Rule.prepare_checks(rule) + + examples = control_describe_checks.flat_map do |m, a, b| + get_check_example(m, a, b) + end.compact + + examples.map { |example| total_checks += example.examples.count } + + unless control_describe_checks.empty? # controls with empty tests are avoided # checks represent tests within control - controls_count += 1 - control_checks_count_map[rule.to_s] = checks.count + controls_count += 1 if control_checks_count_map[rule.to_s].nil? + control_checks_count_map[rule.to_s] = control_checks_count_map[rule.to_s].to_i + total_checks end end end @@ -158,7 +168,7 @@ module Inspec return if @conf["reporter"].nil? @conf["reporter"].each do |reporter| - result = Inspec::Reporters.render(reporter, run_data) + result = Inspec::Reporters.render(reporter, run_data, @conf["enhanced_outcomes"]) raise Inspec::ReporterError, "Error generating reporter '#{reporter[0]}'" if result == false end end diff --git a/lib/inspec/runner_rspec.rb b/lib/inspec/runner_rspec.rb index e14d53476..3d34d6731 100644 --- a/lib/inspec/runner_rspec.rb +++ b/lib/inspec/runner_rspec.rb @@ -107,11 +107,11 @@ module Inspec stats = @formatter.results[:statistics][:controls] load_failures = @formatter.results[:profiles]&.select { |p| p[:status] == "failed" }&.any? skipped = @formatter.results.dig(:profiles, 0, :status) == "skipped" - if stats[:failed][:total] == 0 && stats[:skipped][:total] == 0 && !skipped && !load_failures + if stats[:failed][:total] == 0 && stats[:skipped][:total] == 0 && !skipped && !load_failures && (stats[:error] && stats[:error][:total] == 0) # placed error count condition because of enhanced outcomes 0 elsif load_failures @conf["distinct_exit"] ? 102 : 1 - elsif stats[:failed][:total] > 0 + elsif stats[:failed][:total] > 0 || (stats[:error] && stats[:error][:total] > 0) @conf["distinct_exit"] ? 100 : 1 elsif stats[:skipped][:total] > 0 || skipped @conf["distinct_exit"] ? 101 : 0 @@ -196,6 +196,7 @@ module Inspec def configure_output RSpec.configuration.output_stream = $stdout @formatter = RSpec.configuration.add_formatter(Inspec::Formatters::Base) + @formatter.enhanced_outcomes = @conf.final_options["enhanced_outcomes"] RSpec.configuration.add_formatter(Inspec::Formatters::ShowProgress, $stderr) if @conf[:show_progress] set_optional_formatters RSpec.configuration.color = @conf["color"] diff --git a/lib/inspec/schema.rb b/lib/inspec/schema.rb index c1d405d54..727e2f0cb 100644 --- a/lib/inspec/schema.rb +++ b/lib/inspec/schema.rb @@ -111,6 +111,43 @@ module Inspec }, }.freeze + CONTROL_ENHANCED_OUTCOME = { + "type" => "object", + "additionalProperties" => false, + "properties" => { + "id" => { "type" => "string" }, + "title" => { "type" => %w{string null} }, + "desc" => { "type" => %w{string null} }, + "descriptions" => { "type" => %w{array} }, + "impact" => { "type" => "number" }, + "status" => { + "enum" => %w{passed failed not_applicable not_reviewed error}, + "description" => Primitives.desc(Primitives::STRING, "The enhanced outcome status of the control"), + }, + "refs" => REFS, + "tags" => TAGS, + "code" => { "type" => "string" }, + "source_location" => { + "type" => "object", + "properties" => { + "ref" => { "type" => "string" }, + "line" => { "type" => "number" }, + }, + }, + "results" => { "type" => "array", "items" => RESULT }, + "waiver_data" => { + "type" => "object", + "properties" => { + "skipped_due_to_waiver" => { "type" => "string" }, + "run" => { "type" => "boolean" }, + "message" => { "type" => "string" }, + "expiration_date" => { "type" => "string" }, + "justification" => { "type" => "string" }, + }, + }, + }, + }.freeze + SUPPORTS = { "type" => "object", "additionalProperties" => false, @@ -173,6 +210,45 @@ module Inspec }, }.freeze + PROFILE_ENHANCED_OUTCOME = { + "type" => "object", + "additionalProperties" => false, + "properties" => { + "name" => { "type" => "string" }, + "version" => { "type" => "string", "optional" => true }, + "sha256" => { "type" => "string", "optional" => false }, + + "title" => { "type" => "string", "optional" => true }, + "maintainer" => { "type" => "string", "optional" => true }, + "copyright" => { "type" => "string", "optional" => true }, + "copyright_email" => { "type" => "string", "optional" => true }, + "license" => { "type" => "string", "optional" => true }, + "summary" => { "type" => "string", "optional" => true }, + "status" => { "type" => "string", "optional" => false }, + "status_message" => { "type" => "string", "optional" => true }, + # skip_message is deprecated, status_message should be used to store the reason for skipping + "skip_message" => { "type" => "string", "optional" => true }, + + "supports" => { + "type" => "array", + "items" => SUPPORTS, + "optional" => true, + }, + "controls" => { + "type" => "array", + "items" => CONTROL_ENHANCED_OUTCOME, + }, + "groups" => { + "type" => "array", + "items" => CONTROL_GROUP, + }, + "attributes" => { # TODO: rename to inputs, refs #3802 + "type" => "array", + # TODO: more detailed specification needed + }, + }, + }.freeze + EXEC_JSON = { "type" => "object", "additionalProperties" => false, @@ -187,6 +263,20 @@ module Inspec }, }.freeze + EXEC_JSON_ENHANCED_OUTCOME = { + "type" => "object", + "additionalProperties" => false, + "properties" => { + "platform" => PLATFORM, + "profiles" => { + "type" => "array", + "items" => PROFILE_ENHANCED_OUTCOME, + }, + "statistics" => STATISTICS, + "version" => { "type" => "string" }, + }, + }.freeze + MIN_CONTROL = { "type" => "object", "additionalProperties" => false, @@ -228,6 +318,7 @@ module Inspec LIST = { "exec-json" => EXEC_JSON, "exec-jsonmin" => EXEC_JSONMIN, + "exec-json-enhanced-outcome" => EXEC_JSON_ENHANCED_OUTCOME, "platforms" => PLATFORMS, }.freeze diff --git a/lib/inspec/schema/exec_json.rb b/lib/inspec/schema/exec_json.rb index 3f104c1c2..c591cb81c 100644 --- a/lib/inspec/schema/exec_json.rb +++ b/lib/inspec/schema/exec_json.rb @@ -19,8 +19,8 @@ module Inspec # Lists the potential values for a control result CONTROL_RESULT_STATUS = Primitives::SchemaType.new("Control Result Status", { "type" => "string", - "enum" => %w{passed failed skipped error}, - }, [], "The status of a control. Should be one of 'passed', 'failed', 'skipped', or 'error'.") + "enum" => %w{passed failed skipped}, + }, [], "The status of a control. Should be one of 'passed', 'failed', or 'skipped'.") # Represents the statistics/result of a control"s execution CONTROL_RESULT = Primitives::SchemaType.new("Control Result", { @@ -75,6 +75,36 @@ module Inspec }, }, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT], "Describes a control and any findings it has.") + # Represents a control produced with enhanced outcomes option + ENHANCED_OUTCOME_CONTROL = Primitives::SchemaType.new("Exec JSON Control", { + "type" => "object", + "additionalProperties" => true, + "required" => %w{id title desc impact refs tags code source_location results}, + "properties" => { + "id" => Primitives.desc(Primitives::STRING, "The id."), + "title" => Primitives.desc({ "type" => %w{string null} }, "The title - is nullable."), # Nullable string + "desc" => Primitives.desc({ "type" => %w{string null} }, "The description for the overarching control."), + "descriptions" => Primitives.desc(Primitives.array(CONTROL_DESCRIPTION.ref), "A set of additional descriptions. Example: the 'fix' text."), + "impact" => Primitives.desc(Primitives::IMPACT, "The impactfulness or severity."), + "status" => { + "enum" => %w{passed failed not_applicable not_reviewed error}, + "description" => Primitives.desc(Primitives::STRING, "The enhanced outcome status of the control"), + }, + "refs" => Primitives.desc(Primitives.array(Primitives::REFERENCE.ref), "The set of references to external documents."), + "tags" => Primitives.desc(Primitives::TAGS, "A set of tags - usually metadata."), + "code" => Primitives.desc(Primitives::STRING, "The raw source code of the control. Note that if this is an overlay control, it does not include the underlying source code."), + "source_location" => Primitives.desc(Primitives::SOURCE_LOCATION.ref, "The explicit location of the control within the source code."), + "results" => Primitives.desc(Primitives.array(CONTROL_RESULT.ref), %q( + The set of all tests within the control and their results and findings. Example: + For Chef Inspec, if in the control's code we had the following: + describe sshd_config do + its('Port') { should cmp 22 } + end + The findings from this block would be appended to the results, as well as those of any other blocks within the control. + )), + }, + }, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT], "Describes a control and any findings it has.") + # Based loosely on https://docs.chef.io/inspec/profiles/ as of July 3, 2019 # However, concessions were made to the reality of current reporters, specifically # with how description is omitted and version/inspec_version aren't as advertised online @@ -112,6 +142,40 @@ module Inspec }, }, [CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::SUPPORT], "Information on the set of controls assessed. Example: it can include the name of the Inspec profile and any findings.") + ENHANCED_OUTCOME_PROFILE = Primitives::SchemaType.new("Exec JSON Profile", { + "type" => "object", + "additionalProperties" => true, + "required" => %w{name sha256 supports attributes groups controls}, + # Name is mandatory in inspec.yml. + # supports, controls, groups, and attributes are always present, even if empty + # sha256, status, status_message + "properties" => { + # These are provided in inspec.yml + "name" => Primitives.desc(Primitives::STRING, "The name - must be unique."), + "title" => Primitives.desc(Primitives::STRING, "The title - should be human readable."), + "maintainer" => Primitives.desc(Primitives::STRING, "The maintainer(s)."), + "copyright" => Primitives.desc(Primitives::STRING, "The copyright holder(s)."), + "copyright_email" => Primitives.desc(Primitives::STRING, "The email address or other contact information of the copyright holder(s)."), + "depends" => Primitives.desc(Primitives.array(Primitives::DEPENDENCY.ref), "The set of dependencies this profile depends on. Example: an overlay profile is dependent on another profile."), + "parent_profile" => Primitives.desc(Primitives::STRING, "The name of the parent profile if the profile is a dependency of another."), + "license" => Primitives.desc(Primitives::STRING, "The copyright license. Example: the full text or the name, such as 'Apache License, Version 2.0'."), + "summary" => Primitives.desc(Primitives::STRING, "The summary. Example: the Security Technical Implementation Guide (STIG) header."), + "version" => Primitives.desc(Primitives::STRING, "The version of the profile."), + "supports" => Primitives.desc(Primitives.array(Primitives::SUPPORT.ref), "The set of supported platform targets."), + "description" => Primitives.desc(Primitives::STRING, "The description - should be more detailed than the summary."), + "inspec_version" => Primitives.desc(Primitives::STRING, "The version of Inspec."), + + # These are generated at runtime, and all except status_message and skip_message are guaranteed + "sha256" => Primitives.desc(Primitives::STRING, "The checksum of the profile."), + "status" => Primitives.desc(Primitives::STRING, "The status. Example: loaded."), # enum? loaded, failed, skipped + "status_message" => Primitives.desc(Primitives::STRING, "The reason for the status. Example: why it was skipped or failed to load."), + "skip_message" => Primitives.desc(Primitives::STRING, "The reason for skipping if it was skipped."), # Deprecated field - status_message should be used instead. + "controls" => Primitives.desc(Primitives.array(CONTROL.ref), "The set of controls including any findings."), + "groups" => Primitives.desc(Primitives.array(Primitives::CONTROL_GROUP.ref), "A set of descriptions for the control groups. Example: the ids of the controls."), + "attributes" => Primitives.desc(Primitives.array(Primitives::INPUT), "The input(s) or attribute(s) used in the run."), + }, + }, [ENHANCED_OUTCOME_CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::SUPPORT], "Information on the set of controls assessed. Example: it can include the name of the Inspec profile and any findings.") + # Result of exec json. Top level value # TODO: Include the format of top level controls. This was omitted for lack of sufficient examples OUTPUT = Primitives::SchemaType.new("Exec JSON Output", { @@ -125,6 +189,18 @@ module Inspec "version" => Primitives.desc(Primitives::STRING, "Version number of the tool that generated the findings. Example: '4.18.108' is a version of Chef InSpec."), }, }, [Primitives::PLATFORM, PROFILE, Primitives::STATISTICS], "The top level value containing all of the results.") + + ENHANCED_OUTCOME_OUTPUT = Primitives::SchemaType.new("Exec JSON Output", { + "type" => "object", + "additionalProperties" => true, + "required" => %w{platform profiles statistics version}, + "properties" => { + "platform" => Primitives.desc(Primitives::PLATFORM.ref, "Information on the platform the run from the tool that generated the findings was from. Example: the name of the operating system."), + "profiles" => Primitives.desc(Primitives.array(PROFILE.ref), "Information on the run(s) from the tool that generated the findings. Example: the findings."), + "statistics" => Primitives.desc(Primitives::STATISTICS.ref, "Statistics for the run(s) from the tool that generated the findings. Example: the runtime duration."), + "version" => Primitives.desc(Primitives::STRING, "Version number of the tool that generated the findings. Example: '4.18.108' is a version of Chef InSpec."), + }, + }, [Primitives::PLATFORM, ENHANCED_OUTCOME_PROFILE, Primitives::STATISTICS], "The top level value containing all of the results.") end end end diff --git a/lib/inspec/schema/output_schema.rb b/lib/inspec/schema/output_schema.rb index ad713a725..3d48f6566 100644 --- a/lib/inspec/schema/output_schema.rb +++ b/lib/inspec/schema/output_schema.rb @@ -30,6 +30,8 @@ module Inspec "profile-json" => OutputSchema.finalize(Schema::ProfileJson::PROFILE), "exec-json" => OutputSchema.finalize(Schema::ExecJson::OUTPUT), "exec-jsonmin" => OutputSchema.finalize(Schema::ExecJsonMin::OUTPUT), + "profile-json-enhanced-outcomes" => OutputSchema.finalize(Schema::ProfileJson::ENHANCED_OUTCOME_PROFILE), + "exec-json-enhanced-outcomes" => OutputSchema.finalize(Schema::ExecJson::ENHANCED_OUTCOME_OUTPUT), "platforms" => PLATFORMS, }.freeze @@ -37,7 +39,8 @@ module Inspec LIST.keys end - def self.json(name) + def self.json(name, opts) + name += "-enhanced-outcomes" if opts["enhanced_outcomes"] if !LIST.key?(name) raise("Cannot find schema #{name.inspect}.") elsif LIST[name].is_a?(Proc) diff --git a/lib/inspec/schema/profile_json.rb b/lib/inspec/schema/profile_json.rb index 5806a464e..d112bfbee 100644 --- a/lib/inspec/schema/profile_json.rb +++ b/lib/inspec/schema/profile_json.rb @@ -31,6 +31,28 @@ module Inspec }, }, [CONTROL_DESCRIPTIONS, Primitives::REFERENCE, Primitives::SOURCE_LOCATION], "The set of all tests within the control.") + # Represents a control with enhanced outcomes status information + ENHANCED_OUTCOME_CONTROL = Primitives::SchemaType.new("Profile JSON Control", { + "type" => "object", + "additionalProperties" => true, + "required" => %w{id title desc impact tags code}, + "properties" => { + "id" => Primitives.desc(Primitives::STRING, "The id."), + "title" => Primitives.desc({ "type" => %w{string null} }, "The title - is nullable."), + "desc" => Primitives.desc({ "type" => %w{string null} }, "The description for the overarching control."), + "descriptions" => Primitives.desc(CONTROL_DESCRIPTIONS.ref, "A set of additional descriptions. Example: the 'fix' text."), + "impact" => Primitives.desc(Primitives::IMPACT, "The impactfulness or severity."), + "status" => { + "enum" => %w{passed failed not_applicable not_reviewed error}, + "description" => Primitives.desc(Primitives::STRING, "The enhanced outcome status of the control"), + }, + "refs" => Primitives.desc(Primitives.array(Primitives::REFERENCE.ref), "The set of references to external documents."), + "tags" => Primitives.desc(Primitives::TAGS, "A set of tags - usually metadata."), + "code" => Primitives.desc(Primitives::STRING, "The raw source code of the control. Note that if this is an overlay control, it does not include the underlying source code."), + "source_location" => Primitives.desc(Primitives::SOURCE_LOCATION.ref, "The explicit location of the control within the source code."), + }, + }, [CONTROL_DESCRIPTIONS, Primitives::REFERENCE, Primitives::SOURCE_LOCATION], "The set of all tests within the control.") + # A profile that has not been run. PROFILE = Primitives::SchemaType.new("Profile JSON Profile", { "type" => "object", @@ -55,6 +77,30 @@ module Inspec "depends" => Primitives.desc(Primitives.array(Primitives::DEPENDENCY.ref), "The set of dependencies this profile depends on. Example: an overlay profile is dependent on another profile."), # Can have depends, but NOT a parentprofile }, }, [Primitives::SUPPORT, CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::GENERATOR], "Information on the set of controls that can be assessed. Example: it can include the name of the Inspec profile.") + + ENHANCED_OUTCOME_PROFILE = Primitives::SchemaType.new("Profile JSON Profile", { + "type" => "object", + "additionalProperties" => true, # Anything in the yaml will be put in here. LTTODO: Make this stricter! + "required" => %w{name supports controls groups sha256}, + "properties" => { + "name" => Primitives.desc(Primitives::STRING, "The name - must be unique."), + "supports" => Primitives.desc(Primitives.array(Primitives::SUPPORT.ref), "The set of supported platform targets."), + "controls" => Primitives.desc(Primitives.array(CONTROL.ref), "The set of controls - contains no findings as the assessment has not yet occurred."), + "groups" => Primitives.desc(Primitives.array(Primitives::CONTROL_GROUP.ref), "A set of descriptions for the control groups. Example: the ids of the controls."), + "inputs" => Primitives.desc(Primitives.array(Primitives::INPUT), "The input(s) or attribute(s) used to be in the run."), + "sha256" => Primitives.desc(Primitives::STRING, "The checksum of the profile."), + "status" => Primitives.desc(Primitives::STRING, "The status. Example: skipped."), + "generator" => Primitives.desc(Primitives::GENERATOR.ref, "The tool that generated this file. Example: Chef Inspec."), + "version" => Primitives.desc(Primitives::STRING, "The version of the profile."), + + # Other properties possible in inspec docs, but that aren"t guaranteed + "title" => Primitives.desc(Primitives::STRING, "The title - should be human readable."), + "maintainer" => Primitives.desc(Primitives::STRING, "The maintainer(s)."), + "copyright" => Primitives.desc(Primitives::STRING, "The copyright holder(s)."), + "copyright_email" => Primitives.desc(Primitives::STRING, "The email address or other contract information of the copyright holder(s)."), + "depends" => Primitives.desc(Primitives.array(Primitives::DEPENDENCY.ref), "The set of dependencies this profile depends on. Example: an overlay profile is dependent on another profile."), # Can have depends, but NOT a parentprofile + }, + }, [Primitives::SUPPORT, ENHANCED_OUTCOME_CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::GENERATOR], "Information on the set of controls that can be assessed. Example: it can include the name of the Inspec profile.") end end end diff --git a/lib/inspec/utils/convert.rb b/lib/inspec/utils/convert.rb index 3ff7a9fbe..abb13b377 100644 --- a/lib/inspec/utils/convert.rb +++ b/lib/inspec/utils/convert.rb @@ -5,4 +5,12 @@ module Converter val = val.to_i if val =~ /^\d+$/ val end + + def self.to_boolean(value) + if ["true", "True", "TRUE", true, "yes", "y", "YES", "Y"].include? value + true + elsif ["false", "False", "FALSE", false, "no", "n", "NO", "N"].include? value + false + end + end end diff --git a/lib/inspec/utils/podman.rb b/lib/inspec/utils/podman.rb new file mode 100644 index 000000000..69247e15a --- /dev/null +++ b/lib/inspec/utils/podman.rb @@ -0,0 +1,24 @@ +require "inspec/resources/command" + +module Inspec + module Utils + module Podman + def podman_running? + inspec.command("podman version").exit_status == 0 + end + + # Generates the template in this format using labels hash: "\"id\": {{json .ID}}, \"name\": {{json .Name}}", + def generate_go_template(labels) + (labels.map { |k, v| "\"#{k}\": {{json .#{v}}}" }).join(", ") + end + + def parse_command_output(output) + require "json" unless defined?(JSON) + JSON.parse(output) + rescue JSON::ParserError => _e + warn "Could not parse the command output" + {} + end + end + end +end \ No newline at end of file diff --git a/lib/inspec/utils/waivers/csv_file_reader.rb b/lib/inspec/utils/waivers/csv_file_reader.rb new file mode 100644 index 000000000..eda6f9f16 --- /dev/null +++ b/lib/inspec/utils/waivers/csv_file_reader.rb @@ -0,0 +1,34 @@ +require "csv" unless defined?(CSV) + +module Waivers + class CSVFileReader + def self.resolve(path) + return nil unless File.file?(path) + + @headers ||= [] + fetch_data(path) + end + + def self.fetch_data(path) + waiver_data_hash = {} + CSV.foreach(path, headers: true) do |row| + row_hash = row.to_hash + @headers = row_hash.keys if @headers.empty? + control_id = row_hash["control_id"] + # delete keys and values not required in final hash + row_hash.delete("control_id") + row_hash.delete_if { |k, v| k.nil? || v.nil? } + + waiver_data_hash[control_id] = row_hash if control_id && !row_hash.blank? + end + + waiver_data_hash + rescue CSV::MalformedCSVError => e + raise "Error reading InSpec waivers in CSV: #{e}" + end + + def self.headers + @headers + end + end +end \ No newline at end of file diff --git a/lib/inspec/utils/waivers/excel_file_reader.rb b/lib/inspec/utils/waivers/excel_file_reader.rb new file mode 100644 index 000000000..46de1fc87 --- /dev/null +++ b/lib/inspec/utils/waivers/excel_file_reader.rb @@ -0,0 +1,39 @@ +require "roo" +require "roo-xls" + +module Waivers + class ExcelFileReader + def self.resolve(path) + return nil unless File.file?(path) + + @headers ||= [] + fetch_data(path) + end + + def self.fetch_data(path) + waiver_data_hash = {} + file_extension = File.extname(path) == ".xlsx" ? :xlsx : :xls + excel_file = Roo::Spreadsheet.open(path, extension: file_extension) + excel_file.sheet(0).parse(headers: true).each_with_index do |row, index| + if index == 0 + @headers = row.keys + else + row_hash = row + control_id = row_hash["control_id"] + # delete keys and values not required in final hash + row_hash.delete("control_id") + row_hash.delete_if { |k, v| k.nil? || v.nil? } + end + + waiver_data_hash[control_id] = row_hash if control_id && !row_hash.blank? + end + waiver_data_hash + rescue Exception => e + raise "Error reading InSpec waivers in Excel: #{e}" + end + + def self.headers + @headers + end + end +end \ No newline at end of file diff --git a/lib/inspec/utils/waivers/json_file_reader.rb b/lib/inspec/utils/waivers/json_file_reader.rb new file mode 100644 index 000000000..2bdd269fb --- /dev/null +++ b/lib/inspec/utils/waivers/json_file_reader.rb @@ -0,0 +1,15 @@ +module Waivers + class JSONFileReader + def self.resolve(path) + return nil unless File.file?(path) + + fetch_data(path) + end + + def self.fetch_data(path) + JSON.parse(File.read(path)) + rescue JSON::ParserError => e + raise "Error reading InSpec waivers in JSON: #{e}" + end + end +end \ No newline at end of file diff --git a/lib/inspec/waiver_file_reader.rb b/lib/inspec/waiver_file_reader.rb new file mode 100644 index 000000000..87b1ba623 --- /dev/null +++ b/lib/inspec/waiver_file_reader.rb @@ -0,0 +1,66 @@ +require "inspec/secrets/yaml" +require "inspec/utils/waivers/csv_file_reader" +require "inspec/utils/waivers/json_file_reader" +require "inspec/utils/waivers/excel_file_reader" + +module Inspec + class WaiverFileReader + + def self.fetch_waivers_by_profile(profile_id, files) + read_waivers_from_file(profile_id, files) if @waivers_data.nil? || @waivers_data[profile_id].nil? + @waivers_data[profile_id] + end + + def self.read_waivers_from_file(profile_id, files) + @waivers_data ||= {} + output = {} + + files.each do |file_path| + file_extension = File.extname(file_path) + data = nil + if [".yaml", ".yml"].include? file_extension + data = Secrets::YAML.resolve(file_path) + data = data.inputs unless data.nil? + validate_json_yaml(data) + elsif file_extension == ".csv" + data = Waivers::CSVFileReader.resolve(file_path) + headers = Waivers::CSVFileReader.headers + validate_headers(headers) + elsif file_extension == ".json" + data = Waivers::JSONFileReader.resolve(file_path) + validate_json_yaml(data) + elsif [".xls", ".xlsx"].include? file_extension + data = Waivers::ExcelFileReader.resolve(file_path) + headers = Waivers::ExcelFileReader.headers + validate_headers(headers) + end + output.merge!(data) if !data.nil? && data.is_a?(Hash) + + if data.nil? + raise Inspec::Exceptions::WaiversFileNotReadable, + "Cannot find parser for waivers file '#{file_path}'. " \ + "Check to make sure file has the appropriate extension." + end + end + + @waivers_data[profile_id] = output + end + + def self.validate_headers(headers, json_yaml = false) + required_fields = json_yaml ? %w{justification} : %w{control_id justification} + all_fields = %w{control_id justification expiration_date run} + + Inspec::Log.warn "Missing column headers: #{(required_fields - headers)}" unless (required_fields - headers).empty? + Inspec::Log.warn "Invalid column header: Column can't be nil" if headers.include? nil + Inspec::Log.warn "Extra column headers: #{(headers - all_fields)}" unless (headers - all_fields).empty? + end + + def self.validate_json_yaml(data) + headers = [] + data.each_value do |value| + headers.push value.keys + end + validate_headers(headers.flatten.uniq, true) + end + end +end \ No newline at end of file diff --git a/lib/plugins/inspec-reporter-html2/templates/body.html.erb b/lib/plugins/inspec-reporter-html2/templates/body.html.erb index daa827abf..206fe6948 100644 --- a/lib/plugins/inspec-reporter-html2/templates/body.html.erb +++ b/lib/plugins/inspec-reporter-html2/templates/body.html.erb @@ -36,8 +36,14 @@ Control Statistics

Control Statistics

Passed:<%= run_data.statistics.controls.passed.total %> - Skipped:<%= run_data.statistics.controls.skipped.total %> Failed:<%= run_data.statistics.controls.failed.total %> + <% if enhanced_outcomes %> + Not Reviewed:<%= run_data.statistics.controls.not_reviewed.total %> + Not Applicable:<%= run_data.statistics.controls.not_applicable.total %> + Error:<%= run_data.statistics.controls.error.total %> + <% else %> + Skipped:<%= run_data.statistics.controls.skipped.total %> + <% end %> Duration:<%= run_data.statistics.duration %> seconds Time Finished:<%= Time.now %> diff --git a/lib/plugins/inspec-reporter-html2/templates/control.html.erb b/lib/plugins/inspec-reporter-html2/templates/control.html.erb index b011649be..6e9183a4e 100644 --- a/lib/plugins/inspec-reporter-html2/templates/control.html.erb +++ b/lib/plugins/inspec-reporter-html2/templates/control.html.erb @@ -1,11 +1,15 @@ <% slugged_id = control.id.tr(" ", "_") %> <% - # Determine status of control - status = "passed" - if control.results.any? { |r| r.status == "failed" } - status = "failed" - elsif control.results.any? { |r| r.status == "skipped" } - status = "skipped" + if enhanced_outcomes + status = control.status + else + # Determine status of control + status = "passed" + if control.results.any? { |r| r.status == "failed" } + status = "failed" + elsif control.results.any? { |r| r.status == "skipped" } + status = "skipped" + end end %> diff --git a/lib/plugins/inspec-reporter-html2/templates/default.css b/lib/plugins/inspec-reporter-html2/templates/default.css index 7e12ac01e..0e161cb92 100644 --- a/lib/plugins/inspec-reporter-html2/templates/default.css +++ b/lib/plugins/inspec-reporter-html2/templates/default.css @@ -60,6 +60,18 @@ pre code { .result-metadata .status-skipped div { background-color: grey; } +.control-metadata .status-error div, +.result-metadata .status-error div { + background-color: rgb(63, 15, 183); +} +.control-metadata .status-not_applicable div, +.result-metadata .status-not_applicable div { + background-color: rgb(135, 206, 250); +} +.control-metadata .status-not_reviewed div, +.result-metadata .status-not_reviewed div { + background-color: rgb(255, 194, 0); +} .result-metadata, .control-metadata { margin: 0 0 0 5%; diff --git a/lib/plugins/inspec-reporter-html2/templates/selector.html.erb b/lib/plugins/inspec-reporter-html2/templates/selector.html.erb index b340403dd..062e55166 100644 --- a/lib/plugins/inspec-reporter-html2/templates/selector.html.erb +++ b/lib/plugins/inspec-reporter-html2/templates/selector.html.erb @@ -1,8 +1,14 @@

Display controls that are:

- + <% if enhanced_outcomes %> + + + + <% else %> + + <% end %>

Display profiles that are:

diff --git a/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb b/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb index 92028d43e..18b4eae3c 100644 --- a/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +++ b/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb @@ -20,6 +20,9 @@ module InspecPlugins::StreamingReporterProgressBar "passed" => "\033[0;1;32m", "skipped" => "\033[0;37m", "reset" => "\033[0m", + "error" => "\033[34m", + "not_applicable" => "\033[36m", + "not_reviewed" => "\033[33m", }.freeze # Most currently available Windows terminals have poor support @@ -28,6 +31,9 @@ module InspecPlugins::StreamingReporterProgressBar "failed" => "[FAIL]", "skipped" => "[SKIP]", "passed" => "[PASS]", + "error" => " [ERROR] ", + "not_applicable" => " [N/A] ", + "not_reviewed" => " [N/R] ", }.freeze else # Extended colors for everyone else @@ -36,6 +42,9 @@ module InspecPlugins::StreamingReporterProgressBar "passed" => "\033[38;5;41m", "skipped" => "\033[38;5;247m", "reset" => "\033[0m", + "error" => "\033[0;38;5;21m", + "not_applicable" => "\033[0;38;5;117m", + "not_reviewed" => "\033[0;38;5;214m", }.freeze # Groovy UTF-8 characters for everyone else... @@ -44,6 +53,9 @@ module InspecPlugins::StreamingReporterProgressBar "failed" => "× [FAILED] ", "skipped" => "↺ [SKIPPED]", "passed" => "✔ [PASSED] ", + "error" => "× [ERROR] ", + "not_applicable" => " [N/A] ", + "not_reviewed" => " [N/R] ", }.freeze end @@ -71,29 +83,37 @@ module InspecPlugins::StreamingReporterProgressBar control_id = notification.example.metadata[:id] title = notification.example.metadata[:title] full_description = notification.example.metadata[:full_description] - control_impact = notification.example.metadata[:impact] set_status_mapping(control_id, status) - show_progress(control_id, title, full_description, control_impact) if control_ended?(control_id) + collect_notifications(notification, control_id, status) + control_ended = control_ended?(control_id) + if control_ended + control_outcome = add_enhanced_outcomes(control_id) if enhanced_outcomes + show_progress(control_id, title, full_description, control_outcome) + end end - def show_progress(control_id, title, full_description, control_impact) + def show_progress(control_id, title, full_description, control_outcome) @bar ||= ProgressBar.new(controls_count, :bar, :counter, :percentage) sleep 0.1 @bar.increment! - @bar.puts format_it(control_id, title, full_description, control_impact) + @bar.puts format_it(control_id, title, full_description, control_outcome) rescue StandardError => e raise "Exception in Progress Bar streaming reporter: #{e}" end - def format_it(control_id, title, full_description, control_impact) - control_status = if @status_mapping[control_id].include? "failed" - "failed" - elsif @status_mapping[control_id].include? "passed" - "passed" - else - @status_mapping[control_id].include? "skipped" - "skipped" - end + def format_it(control_id, title, full_description, control_outcome) + if control_outcome + control_status = control_outcome + else + control_status = if @status_mapping[control_id].include? "failed" + "failed" + elsif @status_mapping[control_id].include? "passed" + "passed" + else + @status_mapping[control_id].include? "skipped" + "skipped" + end + end indicator = INDICATORS[control_status] message_to_format = "" message_to_format += "#{indicator} " diff --git a/omnibus/Gemfile.lock b/omnibus/Gemfile.lock index fdc9f733e..5ffd14a12 100644 --- a/omnibus/Gemfile.lock +++ b/omnibus/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/chef/omnibus-software.git - revision: a9b13a09c2f89e4618225dd41b2fd08f2150a1ba + revision: 1d540dcdefae0fa75eb832590e85294e7c26660a branch: main specs: omnibus-software (4.0.0) @@ -34,23 +34,23 @@ GEM artifactory (3.0.15) awesome_print (1.9.2) aws-eventstream (1.2.0) - aws-partitions (1.595.0) - aws-sdk-core (3.131.1) + aws-partitions (1.608.0) + aws-sdk-core (3.131.3) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.57.0) + aws-sdk-kms (1.58.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) aws-sdk-s3 (1.114.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sdk-secretsmanager (1.62.0) + aws-sdk-secretsmanager (1.64.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sigv4 (1.5.0) + aws-sigv4 (1.5.1) aws-eventstream (~> 1, >= 1.0.2) bcrypt_pbkdf (1.1.0) bcrypt_pbkdf (1.1.0-x64-mingw32) @@ -194,8 +194,8 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) ffi (1.15.5) + ffi (1.15.5-x64-mingw-ucrt) ffi (1.15.5-x64-mingw32) - ffi (1.15.5-x64-unknown) ffi (1.15.5-x86-mingw32) ffi-libarchive (1.1.3) ffi (~> 1.0) @@ -240,7 +240,7 @@ GEM iso8601 (0.13.0) jmespath (1.6.1) json (2.6.2) - kitchen-vagrant (1.11.0) + kitchen-vagrant (1.12.1) test-kitchen (>= 1.4, < 4) libyajl2 (2.1.0) license-acceptance (2.1.13) @@ -248,7 +248,7 @@ GEM tomlrb (>= 1.2, < 3.0) tty-box (~> 0.6) tty-prompt (~> 0.20) - license_scout (1.3.1) + license_scout (1.3.2) ffi-yajl (~> 2.2) mixlib-shellout (>= 2.2, < 4.0) toml-rb (>= 1, < 3) @@ -264,9 +264,9 @@ GEM mixlib-log mixlib-authentication (3.0.10) mixlib-cli (2.1.8) - mixlib-config (3.0.9) + mixlib-config (3.0.27) tomlrb - mixlib-install (3.12.16) + mixlib-install (3.12.19) mixlib-shellout mixlib-versioning thor @@ -286,7 +286,7 @@ GEM mixlib-versioning (1.2.12) molinillo (0.8.0) multi_json (1.15.0) - multipart-post (2.2.0) + multipart-post (2.2.3) net-scp (3.0.0) net-ssh (>= 2.6.5, < 7.0.0) net-sftp (3.0.0) @@ -295,7 +295,7 @@ GEM net-ssh-gateway (2.0.0) net-ssh (>= 4.0.0) nori (2.6.0) - octokit (4.23.0) + octokit (4.25.1) faraday (>= 1, < 3) sawyer (~> 0.9) ohai (17.9.0) @@ -327,7 +327,7 @@ GEM coderay (~> 1.1) method_source (~> 1.0) public_suffix (4.0.7) - rack (2.2.3.1) + rack (2.2.4) rainbow (3.1.1) retryable (3.0.5) rexml (3.2.5) @@ -351,7 +351,7 @@ GEM ruby2_keywords (0.0.5) rubyntlm (0.6.3) rubyzip (2.3.2) - sawyer (0.9.1) + sawyer (0.9.2) addressable (>= 2.3.5) faraday (>= 0.17.3, < 3) semverse (3.0.2) @@ -366,7 +366,7 @@ GEM strings-ansi (0.2.0) structured_warnings (0.4.0) syslog-logger (1.6.8) - test-kitchen (3.2.2) + test-kitchen (3.3.1) bcrypt_pbkdf (~> 1.0) chef-utils (>= 16.4.35) ed25519 (~> 1.2) @@ -381,10 +381,10 @@ GEM winrm-elevated (~> 1.0) winrm-fs (~> 1.1) thor (1.2.1) - toml-rb (2.1.2) + toml-rb (2.2.0) citrus (~> 3.0, > 3.0) tomlrb (1.3.0) - train-core (3.9.2) + train-core (3.10.1) addressable (~> 2.5) ffi (!= 1.13.0) json (>= 1.8, < 3.0) @@ -413,7 +413,7 @@ GEM pastel (~> 0.8) strings (~> 0.2.0) tty-screen (~> 0.8) - unicode-display_width (2.1.0) + unicode-display_width (2.2.0) unicode_utils (1.4.0) uuidtools (2.2.0) vault (0.17.0) diff --git a/test/fixtures/cmd/podman-errors b/test/fixtures/cmd/podman-errors new file mode 100644 index 000000000..de08dcc8c --- /dev/null +++ b/test/fixtures/cmd/podman-errors @@ -0,0 +1,6 @@ +Error: inspecting object: network min: network not found +Error: inspecting object: unable to inspect \"abc\": failed to find image abc: abc: image not known +Error: no pod with name or ID non_existing_pod found: no such pod +[] +error inspecting object: no such object: "non_existing_volume" +Error: inspecting object: no such object: "volume" \ No newline at end of file diff --git a/test/fixtures/cmd/podman-images-a b/test/fixtures/cmd/podman-images-a new file mode 100644 index 000000000..f3c927482 --- /dev/null +++ b/test/fixtures/cmd/podman-images-a @@ -0,0 +1,4 @@ +{ "id": "sha256:c7db653c4397e6a4d1e468bb7c6400c022c62623bdb87c173d54bac7995b6d8f", "repository": "localhost/podman-pause", "tag": "4.1.0-1651853754", "size": "816 kB", "digest": "sha256:e6e9fffed42f600c811af34569268c07d063f12507457493c608d944a1fdac3f", "createdat": "2022-07-01 07:38:09 +0000 UTC", "createdsince": "5 days ago", "history": "localhost/podman-pause:4.1.0-1651853754" } +{ "id": "sha256:55f4b40fe486a5b734b46bb7bf28f52fa31426bf23be068c8e7b19e58d9b8deb", "repository": "docker.io/library/nginx", "tag": "latest", "size": "146 MB", "digest": "sha256:10f14ffa93f8dedf1057897b745e5ac72ac5655c299dade0aa434c71557697ea", "createdat": "2022-06-23 04:13:24 +0000 UTC", "createdsince": "13 days ago", "history": "docker.io/library/nginx:latest" } +{ "id": "sha256:27941809078cc9b2802deb2b0bb6feed6c236cde01e487f200e24653533701ee", "repository": "docker.io/library/ubuntu", "tag": "latest", "size": "80.3 MB", "digest": "sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac", "createdat": "2022-06-06 22:21:26 +0000 UTC", "createdsince": "4 weeks ago", "history": "docker.io/library/ubuntu:latest" } +{ "id": "sha256:3a66698e604003f7822a0c73e9da50e090fda9a99fe1f2e1e2e7fe796cc803d5", "repository": "registry.fedoraproject.org/fedora", "tag": "latest", "size": "169 MB", "digest": "sha256:38813cf0913241b7f13c7057e122f7c3cfa2e7c427dca3194f933d94612e280b", "createdat": "2022-05-06 10:11:58 +0000 UTC", "createdsince": "2 months ago", "history": "registry.fedoraproject.org/fedora:latest" } \ No newline at end of file diff --git a/test/fixtures/cmd/podman-info b/test/fixtures/cmd/podman-info new file mode 100644 index 000000000..3eaa0a66d --- /dev/null +++ b/test/fixtures/cmd/podman-info @@ -0,0 +1,150 @@ +{ + "host": { + "arch": "amd64", + "buildahVersion": "1.26.1", + "cgroupManager": "systemd", + "cgroupVersion": "v2", + "cgroupControllers": [ + "cpu", + "io", + "memory", + "pids" + ], + "conmon": { + "package": "conmon-2.1.0-2.fc36.x86_64", + "path": "/usr/bin/conmon", + "version": "conmon version 2.1.0, commit: " + }, + "cpus": 1, + "cpuUtilization": { + "userPercent": 0.03, + "systemPercent": 0.09, + "idlePercent": 99.89 + }, + "distribution": { + "distribution": "fedora", + "variant": "coreos", + "version": "36" + }, + "eventLogger": "journald", + "hostname": "localhost.localdomain", + "idMappings": { + "gidmap": [ + { + "container_id": 0, + "host_id": 1000, + "size": 1 + }, + { + "container_id": 1, + "host_id": 100000, + "size": 1000000 + } + ], + "uidmap": [ + { + "container_id": 0, + "host_id": 1005691005, + "size": 1 + }, + { + "container_id": 1, + "host_id": 100000, + "size": 1000000 + } + ] + }, + "kernel": "5.17.5-300.fc36.x86_64", + "logDriver": "journald", + "memFree": 1668063232, + "memTotal": 2066817024, + "networkBackend": "netavark", + "ociRuntime": { + "name": "crun", + "package": "crun-1.4.4-1.fc36.x86_64", + "path": "/usr/bin/crun", + "version": "crun version 1.4.4\ncommit: 6521fcc5806f20f6187eb933f9f45130c86da230\nspec: 1.0.0\n+SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL" + }, + "os": "linux", + "remoteSocket": { + "path": "/run/user/1005691005/podman/podman.sock", + "exists": true + }, + "serviceIsRemote": true, + "security": { + "apparmorEnabled": false, + "capabilities": "CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_FOWNER,CAP_FSETID,CAP_KILL,CAP_NET_BIND_SERVICE,CAP_SETFCAP,CAP_SETGID,CAP_SETPCAP,CAP_SETUID,CAP_SYS_CHROOT", + "rootless": true, + "seccompEnabled": true, + "seccompProfilePath": "/usr/share/containers/seccomp.json", + "selinuxEnabled": true + }, + "slirp4netns": { + "executable": "/usr/bin/slirp4netns", + "package": "slirp4netns-1.2.0-0.2.beta.0.fc36.x86_64", + "version": "slirp4netns version 1.2.0-beta.0\ncommit: 477db14a24ff1a3de3a705e51ca2c4c1fe3dda64\nlibslirp: 4.6.1\nSLIRP_CONFIG_VERSION_MAX: 3\nlibseccomp: 2.5.3" + }, + "swapFree": 0, + "swapTotal": 0, + "uptime": "12h 40m 12.19s (Approximately 0.50 days)", + "linkmode": "dynamic" + }, + "store": { + "configFile": "/var/home/core/.config/containers/storage.conf", + "containerStore": { + "number": 5, + "paused": 0, + "running": 3, + "stopped": 2 + }, + "graphDriverName": "overlay", + "graphOptions": { + + }, + "graphRoot": "/var/home/core/.local/share/containers/storage", + "graphRootAllocated": 106825756672, + "graphRootUsed": 2833563648, + "graphStatus": { + "Backing Filesystem": "xfs", + "Native Overlay Diff": "true", + "Supports d_type": "true", + "Using metacopy": "false" + }, + "imageCopyTmpDir": "/var/tmp", + "imageStore": { + "number": 4 + }, + "runRoot": "/run/user/1005691005/containers", + "volumePath": "/var/home/core/.local/share/containers/storage/volumes" + }, + "registries": { + "search": [ + "docker.io" +] + }, + "plugins": { + "volume": [ + "local" + ], + "network": [ + "bridge", + "macvlan" + ], + "log": [ + "k8s-file", + "none", + "passthrough", + "journald" + ] + }, + "version": { + "APIVersion": "4.1.0", + "Version": "4.1.0", + "GoVersion": "go1.18", + "GitCommit": "", + "BuiltTime": "Fri May 6 21:45:54 2022", + "Built": 1651853754, + "OsArch": "linux/amd64", + "Os": "linux" + } +} diff --git a/test/fixtures/cmd/podman-inspec b/test/fixtures/cmd/podman-inspec new file mode 100644 index 000000000..b8df0e831 --- /dev/null +++ b/test/fixtures/cmd/podman-inspec @@ -0,0 +1,10 @@ +[ + { + "Id": "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7", + "Created": "2022-06-28T16:34:10.965113607+05:30", + "Path": "/docker-entrypoint.sh", + "Args": [ + "/bin/bash" + ] + } +] diff --git a/test/fixtures/cmd/podman-inspect-info b/test/fixtures/cmd/podman-inspect-info new file mode 100644 index 000000000..4e4682bbb --- /dev/null +++ b/test/fixtures/cmd/podman-inspect-info @@ -0,0 +1 @@ +{"id": "3c19bafed22355e11a608c4b613d87d06b9cdd37d378e6e0176cbc8e7144d5c6", "repo_tags": ["docker.io/library/busybox:latest"], "size": 1636053, "digest": "sha256:3614ca5eacf0a3a1bcc361c939202a974b4902b9334ff36eb29ffe9011aaad83", "created_at": "2022-06-08T00:39:28.175020858Z", "version": "20.10.12", "names_history": ["docker.io/library/busybox:latest"], "repo_digests": ["docker.io/library/busybox@sha256:2c5e2045f35086c019e80c86880fd5b7c7a619878b59e3b7592711e1781df51a","docker.io/library/busybox@sha256:3614ca5eacf0a3a1bcc361c939202a974b4902b9334ff36eb29ffe9011aaad83"], "architecture": "arm64", "os": "linux", "virtual_size": 1636053} \ No newline at end of file diff --git a/test/fixtures/cmd/podman-network b/test/fixtures/cmd/podman-network new file mode 100644 index 000000000..663a9ccc1 --- /dev/null +++ b/test/fixtures/cmd/podman-network @@ -0,0 +1 @@ +{ "id": "3a7c94d937d5f3a0f1a9b1610589945aedfbe56207fd5d32fc8154aa1a8b007f", "name": "minikube", "driver": "bridge", "labels": {"created_by.minikube.sigs.k8s.io": "true", "name.minikube.sigs.k8s.io": "minikube"}, "options": null, "ipam_options": {"driver": "host-local"}, "internal": false, "created": "2022-07-10T19:37:11.656610731+05:30", "ipv6_enabled": false, "dns_enabled": true, "network_interface": "podman1", "subnets": [{"subnet": "192.168.49.0/24", "gateway": "192.168.49.1"}] } \ No newline at end of file diff --git a/test/fixtures/cmd/podman-network-ls b/test/fixtures/cmd/podman-network-ls new file mode 100644 index 000000000..e912dc59d --- /dev/null +++ b/test/fixtures/cmd/podman-network-ls @@ -0,0 +1 @@ +{ "id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9", "name": "podman", "driver": "bridge", "labels": "", "options": null, "IpamOptions": {"driver":"host-local"}, "created": "2022-07-06T10:32:00.879655095+05:30", "internal": false, "Ipv6Enabled": false, "DnsEnabled": false, "NetworkInterface": "podman0", "Subnets": [{"subnet":"10.88.0.0/16","gateway":"10.88.0.1"}] } \ No newline at end of file diff --git a/test/fixtures/cmd/podman-pod-inspect b/test/fixtures/cmd/podman-pod-inspect new file mode 100644 index 000000000..079602f55 --- /dev/null +++ b/test/fixtures/cmd/podman-pod-inspect @@ -0,0 +1 @@ +{"id": "fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4", "name": "nginx-frontend", "created_at": "2022-07-14T15:47:47.978078124+05:30", "create_command": ["podman","run","-dt","--pod","new:nginx-frontend","-p","8080:80","nginx"], "state": "Running", "hostname": "", "create_cgroup": true, "cgroup_parent": "user.slice", "cgroup_path": "user.slice/user-libpod_pod_fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4.slice", "create_infra": true, "infra_container_id": "727538044b32a165934729dc2d47d9d5e981b6496aebfad7de470f7e76ea4251", "infra_config": {"PortBindings":{"80/tcp":[{"HostIp":"","HostPort":"8080"}]},"HostNetwork":false,"StaticIP":"","StaticMAC":"","NoManageResolvConf":false,"DNSServer":null,"DNSSearch":null,"DNSOption":null,"NoManageHosts":false,"HostAdd":null,"Networks":["podman"],"NetworkOptions":null,"pid_ns":"private","userns":"host"}, "shared_namespaces": ["uts","ipc","net"], "num_containers": 2, "containers": [{"Id":"3c8a4782f3401033a2ff2bedd9c002762c9c47e6194ceafbb6cfed8312b24de9","Name":"epic_hodgkin","State":"running"},{"Id":"727538044b32a165934729dc2d47d9d5e981b6496aebfad7de470f7e76ea4251","Name":"fcfe4d471cff-infra","State":"running"}]} \ No newline at end of file diff --git a/test/fixtures/cmd/podman-pod-ps b/test/fixtures/cmd/podman-pod-ps new file mode 100644 index 000000000..9c34610d7 --- /dev/null +++ b/test/fixtures/cmd/podman-pod-ps @@ -0,0 +1,29 @@ +[ + { + "Cgroup": "user.slice", + "Containers": [ + { + "Id": "a218dfc58fa28e0c58c55e508e5b57084876b42e894b98073c69c45dea06cbb2", + "Names": "95cadbb84df7-infra", + "Status": "running" + }, + { + "Id": "b36abf69b8af6f8a8305ab2d9b209c2acaeece41dbc4f242f8e45caf6e02504b", + "Names": "pensive_mccarthy", + "Status": "running" + } + ], + "Created": "2022-07-01T13:08:09.662082101+05:30", + "Id": "95cadbb84df71e6374fceb3fd89ee3b8f2c7e1a831062cd9cea7d0e3e4b1dbcc", + "InfraId": "a218dfc58fa28e0c58c55e508e5b57084876b42e894b98073c69c45dea06cbb2", + "Name": "cranky_allen", + "Namespace": "", + "Networks": [ + "podman" + ], + "Status": "Running", + "Labels": { + + } + } +] diff --git a/test/fixtures/cmd/podman-ps-a b/test/fixtures/cmd/podman-ps-a new file mode 100644 index 000000000..f964292d0 --- /dev/null +++ b/test/fixtures/cmd/podman-ps-a @@ -0,0 +1,5 @@ +{ "id": "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7", "image": "docker.io/library/nginx:latest", "image_id": "55f4b40fe486a5b734b46bb7bf28f52fa31426bf23be068c8e7b19e58d9b8deb", "command": "/bin/bash", "createdat": "2022-06-28 16:34:10.965113607 +0530 IST", "running_for": "8 days ago", "status": "Up 13 hours ago", "pod": "", "ports": "", "size": "12B (virtual 142MB)", "names": "sweet_mendeleev", "networks": "podman", "labels": {"maintainer":"NGINX Docker Maintainers "}, "mounts": [] } +{ "id": "64b5562346d6b52fd40d790b34e9f18ba3b8745649c302b79ba5399d4ea00b36", "image": "docker.io/library/ubuntu:latest", "image_id": "27941809078cc9b2802deb2b0bb6feed6c236cde01e487f200e24653533701ee", "command": "/bin/bash", "createdat": "2022-06-29 08:48:45.195339311 +0530 IST", "running_for": "7 days ago", "status": "Up 13 hours ago", "pod": "", "ports": "", "size": "12B (virtual 77.8MB)", "names": "wizardly_torvalds", "networks": "podman", "labels": null, "mounts": [] } +{ "id": "437e70c45633de74be7a87ed8d94c442a3bfe0a1cdd293d5184a4af1765d8cf5", "image": "registry.fedoraproject.org/fedora:latest", "image_id": "3a66698e604003f7822a0c73e9da50e090fda9a99fe1f2e1e2e7fe796cc803d5", "command": "/bin/bash", "createdat": "2022-06-29 13:40:20.414848724 +0530 IST", "running_for": "7 days ago", "status": "Created", "pod": "", "ports": "", "size": "0B (virtual 163MB)", "names": "confident_bell", "networks": "podman", "labels": {"license":"MIT","name":"fedora","vendor":"Fedora Project","version":"36"}, "mounts": [] } +{ "id": "a218dfc58fa28e0c58c55e508e5b57084876b42e894b98073c69c45dea06cbb2", "image": "localhost/podman-pause:4.1.0-1651853754", "image_id": "c7db653c4397e6a4d1e468bb7c6400c022c62623bdb87c173d54bac7995b6d8f", "command": "", "createdat": "2022-07-01 13:08:09.685404054 +0530 IST", "running_for": "5 days ago", "status": "Created", "pod": "95cadbb84df71e6374fceb3fd89ee3b8f2c7e1a831062cd9cea7d0e3e4b1dbcc", "ports": "", "size": "12B (virtual 812kB)", "names": "95cadbb84df7-infra", "networks": "podman", "labels": {"io.buildah.version":"1.26.1"}, "mounts": [] } +{ "id": "b36abf69b8af6f8a8305ab2d9b209c2acaeece41dbc4f242f8e45caf6e02504b", "image": "docker.io/library/ubuntu:latest", "image_id": "27941809078cc9b2802deb2b0bb6feed6c236cde01e487f200e24653533701ee", "command": "bash", "createdat": "2022-07-01 22:05:09.624021187 +0530 IST", "running_for": "4 days ago", "status": "Created", "pod": "95cadbb84df71e6374fceb3fd89ee3b8f2c7e1a831062cd9cea7d0e3e4b1dbcc", "ports": "", "size": "12B (virtual 77.8MB)", "names": "pensive_mccarthy", "networks": "", "labels": null, "mounts": [] } \ No newline at end of file diff --git a/test/fixtures/cmd/podman-version b/test/fixtures/cmd/podman-version new file mode 100644 index 000000000..7f1c51bfb --- /dev/null +++ b/test/fixtures/cmd/podman-version @@ -0,0 +1 @@ +{"Client":{"APIVersion":"4.1.0","Version":"4.1.0","GoVersion":"go1.18.1","GitCommit":"","BuiltTime":"Fri May 6 01:37:47 2022","Built":1651781267,"OsArch":"darwin/amd64","Os":"darwin"},"Server":{"APIVersion":"4.1.0","Version":"4.1.0","GoVersion":"go1.18","GitCommit":"","BuiltTime":"Fri May 6 21:45:54 2022","Built":1651853754,"OsArch":"linux/amd64","Os":"linux"}} diff --git a/test/fixtures/cmd/podman-volume-inspect b/test/fixtures/cmd/podman-volume-inspect new file mode 100644 index 000000000..11b3fc0b1 --- /dev/null +++ b/test/fixtures/cmd/podman-volume-inspect @@ -0,0 +1 @@ +{"name": "my_volume", "driver": "local", "mountpoint": "/var/home/core/.local/share/containers/storage/volumes/my_volume/_data", "created_at": "2022-07-14T13:21:19.965421792+05:30", "labels": {}, "scope": "local", "options": {}, "mount_count": 0, "needs_copy_up": true, "needs_chown": true} \ No newline at end of file diff --git a/test/fixtures/cmd/podman-volume-ls b/test/fixtures/cmd/podman-volume-ls new file mode 100644 index 000000000..a28945b1f --- /dev/null +++ b/test/fixtures/cmd/podman-volume-ls @@ -0,0 +1,18 @@ +[ + { + "Name": "ae6be9ba838b9b150de47657229bb9b67142dbdb3d1ddbc5efa245cf1e95536a", + "Driver": "local", + "Mountpoint": "/var/home/core/.local/share/containers/storage/volumes/ae6be9ba838b9b150de47657229bb9b67142dbdb3d1ddbc5efa245cf1e95536a/_data", + "CreatedAt": "2022-07-02T12:40:37.012062614+05:30", + "Labels": { + + }, + "Scope": "local", + "Options": { + + }, + "MountCount": 0, + "NeedsCopyUp": true, + "NeedsChown": true + } +] diff --git a/test/fixtures/profiles/enhanced-outcomes-test/controls/example.rb b/test/fixtures/profiles/enhanced-outcomes-test/controls/example.rb new file mode 100644 index 000000000..06918c7d6 --- /dev/null +++ b/test/fixtures/profiles/enhanced-outcomes-test/controls/example.rb @@ -0,0 +1,79 @@ +# Error +control "tmp-1.0.1" do + impact 0.7 + describe "a.1" do + it { should_bot "a.1" } + end +end + +control "tmp-1.0.2" do + impact 0.0 + describe "a.2" do + it { should_bot "a.2" } + end +end + +# Not Applicable +control "tmp-2.0.1" do + impact 0.0 + describe "b.1" do + it { should cmp "b.1" } + end +end + +control "tmp-2.0.2" do + impact 0.0 + only_if { false } + describe "b.2" do + it { should cmp "b.2" } + end +end + +# Not Reviewed +control "tmp-3.0.1" do + only_if { false } + describe "c.1" do + it { should cmp "c.1" } + end +end + +control "tmp-3.0.2" do + only_if { false } + describe "c.2" do + it { should_bot "c.2" } + end +end + +# Failed +control "tmp-4.0" do + impact 0.7 + describe "d.1" do + it { should_not cmp "d.1" } + it { should cmp "d.1" } + end +end + +# Passed +control "tmp-5.0" do + impact 0.7 + describe "e.1" do + it { should cmp "e.1" } + end +end + +# Example of setting impact using code and marking it N/A +control "tmp-6.0.1" do + impact 0.5 + only_if("Some reason for N/A", impact: 0.0) { false } + describe file("/tmp") do + it { should be_directory } + end +end + +# Example of setting impact using code and not marked as N/A +control "tmp-6.0.2" do + only_if(impact: 0.5) { false } + describe file("/tmp") do + it { should be_directory } + end +end diff --git a/test/fixtures/profiles/enhanced-outcomes-test/inspec.yml b/test/fixtures/profiles/enhanced-outcomes-test/inspec.yml new file mode 100644 index 000000000..ae4d39f20 --- /dev/null +++ b/test/fixtures/profiles/enhanced-outcomes-test/inspec.yml @@ -0,0 +1,10 @@ +name: enhanced-outcomes-test +title: InSpec Profile +maintainer: The Authors +copyright: The Authors +copyright_email: you@example.com +license: Apache-2.0 +summary: An InSpec Compliance Profile +version: 0.1.0 +supports: + platform: os \ No newline at end of file diff --git a/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-1/controls/example.rb b/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-1/controls/example.rb new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-1/inspec.yml b/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-1/inspec.yml new file mode 100644 index 000000000..82ea2930e --- /dev/null +++ b/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-1/inspec.yml @@ -0,0 +1,15 @@ +name: child-profile-1 +title: InSpec Profile +maintainer: The Authors +copyright: The Authors +copyright_email: you@example.com +license: Apache-2.0 +summary: An InSpec Compliance Profile +version: 0.1.0 +supports: + platform: os +depends: + - name: ssh + git: https://github.com/dev-sec/windows-baseline.git + tag: 1.1.2 + \ No newline at end of file diff --git a/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-2/controls/example.rb b/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-2/controls/example.rb new file mode 100644 index 000000000..e69de29bb diff --git a/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-2/inspec.yml b/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-2/inspec.yml new file mode 100644 index 000000000..25cc3afde --- /dev/null +++ b/test/fixtures/profiles/git-fetcher/inheritance-windows/child-profile-2/inspec.yml @@ -0,0 +1,14 @@ +name: child-profile-2 +title: InSpec Profile +maintainer: The Authors +copyright: The Authors +copyright_email: you@example.com +license: Apache-2.0 +summary: An InSpec Compliance Profile +version: 0.1.0 +supports: + platform: os +depends: + - name: ssh + git: https://github.com/dev-sec/windows-baseline.git + tag: 1.1.0 \ No newline at end of file diff --git a/test/fixtures/profiles/git-fetcher/inheritance-windows/parent-profile/controls/example.rb b/test/fixtures/profiles/git-fetcher/inheritance-windows/parent-profile/controls/example.rb new file mode 100644 index 000000000..71ed7c9b3 --- /dev/null +++ b/test/fixtures/profiles/git-fetcher/inheritance-windows/parent-profile/controls/example.rb @@ -0,0 +1,2 @@ +include_controls "child-profile-1" +include_controls "child-profile-2" \ No newline at end of file diff --git a/test/fixtures/profiles/git-fetcher/inheritance-windows/parent-profile/inspec.yml b/test/fixtures/profiles/git-fetcher/inheritance-windows/parent-profile/inspec.yml new file mode 100644 index 000000000..da868a46f --- /dev/null +++ b/test/fixtures/profiles/git-fetcher/inheritance-windows/parent-profile/inspec.yml @@ -0,0 +1,15 @@ +name: parent-profile +title: InSpec Profile +maintainer: The Authors +copyright: The Authors +copyright_email: you@example.com +license: Apache-2.0 +summary: An InSpec Compliance Profile +version: 0.1.0 +supports: + platform: os +depends: + - name: child-profile-2 + path: ../child-profile-2 + - name: child-profile-1 + path: ../child-profile-1 \ No newline at end of file diff --git a/test/fixtures/profiles/only_if/skip-control/controls/skip-control.rb b/test/fixtures/profiles/only_if/skip-control/controls/skip-control.rb index 8aaf8f945..e9618ab93 100644 --- a/test/fixtures/profiles/only_if/skip-control/controls/skip-control.rb +++ b/test/fixtures/profiles/only_if/skip-control/controls/skip-control.rb @@ -1,4 +1,5 @@ control "control-start" do + impact 0.1 desc "This control should not get skipped" describe "a string" do it { should cmp "a string" } @@ -6,6 +7,7 @@ control "control-start" do end control "control-skip-no-message" do + impact 0.0 desc "This control should get skipped" only_if { false } describe "a string" do @@ -14,6 +16,7 @@ control "control-skip-no-message" do end control "control-skip-with-message" do + impact 0.1 desc "This control should get skipped" only_if("here is a message") { false } describe "a string" do @@ -22,6 +25,7 @@ control "control-skip-with-message" do end control "control-skip-test-body" do + impact 0.1 desc "This control should demo that the test body does not get evaluated" only_if { false } describe "infinity" do @@ -30,6 +34,7 @@ control "control-skip-test-body" do end control "control-skip-test-outer-error" do + impact 0.5 desc "This control should demo that following test resources do not get evaluated" only_if { false } describe 1/0 do # does not error! @@ -38,6 +43,7 @@ control "control-skip-test-outer-error" do end control "control-skip-test-outer-resource-test-first" do + impact 0.5 desc "This control should demo that preceding test resources DO NOT get evaluated" describe command("echo toldyaso") do # does exec its("stdout") { should include "toldya" } @@ -46,6 +52,7 @@ control "control-skip-test-outer-resource-test-first" do end control "multi-skip" do + impact 0.1 desc "This control should get skipped" only_if("here is the intended message") { false } only_if("here is a different message") { false } diff --git a/test/fixtures/profiles/waivers/basic/files/waivers.csv b/test/fixtures/profiles/waivers/basic/files/waivers.csv new file mode 100644 index 000000000..d64f0b819 --- /dev/null +++ b/test/fixtures/profiles/waivers/basic/files/waivers.csv @@ -0,0 +1,8 @@ +control_id,justification,run,expiration_date,,, +03_waivered_no_expiry_ran_passes,Sound reasoning,TRUE,,,, +04_waivered_no_expiry_ran_fails,Unassailable thinking,TRUE,2077-11-10T00:00:00Z,,, +,,,,,, +05_waivered_no_expiry_not_ran,Sheer cleverness,FALSE,,,, +06_waivered_expiry_in_past_ran_passes,Necessity,TRUE,,,, +14_waivered_expiry_in_future_z_not_ran,Lack of imagination,FALSE,2077-11-10T00:00:00Z,,, +random contorl id with no data,,,,,,random data in csv! \ No newline at end of file diff --git a/test/fixtures/profiles/waivers/basic/files/waivers.json b/test/fixtures/profiles/waivers/basic/files/waivers.json new file mode 100644 index 000000000..a3d55a001 --- /dev/null +++ b/test/fixtures/profiles/waivers/basic/files/waivers.json @@ -0,0 +1,77 @@ +{ + "03_waivered_no_expiry_ran_passes": { + "justification": "Sound reasoning", + "run": true + }, + "04_waivered_no_expiry_ran_fails": { + "justification": "Unassailable thinking", + "run": true + }, + "05_waivered_no_expiry_not_ran": { + "justification": "Sheer cleverness", + "run": false + }, + "06_waivered_expiry_in_past_ran_passes": { + "expiration_date": "1977-06-01T00:00:00.000Z", + "justification": "Necessity", + "run": true + }, + "07_waivered_expiry_in_past_ran_fails": { + "expiration_date": "1977-06-01T00:00:00.000Z", + "justification": "Whimsy", + "run": true + }, + "08_waivered_expiry_in_past_not_ran": { + "expiration_date": "1977-06-01T00:00:00.000Z", + "justification": "Contrariness", + "run": false + }, + "09_waivered_expiry_in_future_ran_passes": { + "expiration_date": "2077-06-01T00:00:00.000Z", + "justification": "Handwaving", + "run": true + }, + "10_waivered_expiry_in_future_ran_fails": { + "expiration_date": "2077-06-01T00:00:00.000Z", + "justification": "Didn't feel like it", + "run": true + }, + "11_waivered_expiry_in_future_not_ran": { + "expiration_date": "2077-06-01T00:00:00.000Z", + "justification": "Lack of imagination", + "run": false + }, + "12_waivered_expiry_in_future_z_ran_passes": { + "expiration_date": "2077-11-10T00:00:00.000Z", + "justification": "Handwaving", + "run": true + }, + "13_waivered_expiry_in_future_z_ran_fails": { + "expiration_date": "2077-11-10T00:00:00.000Z", + "justification": "Didn't feel like it", + "run": true + }, + "14_waivered_expiry_in_future_z_not_ran": { + "expiration_date": "2077-11-10T00:00:00.000Z", + "justification": "Lack of imagination", + "run": false + }, + "15_waivered_expiry_in_future_string_ran_passes": { + "expiration_date": "2077-06-01", + "justification": "Handwaving", + "run": true + }, + "16_waivered_expiry_in_future_string_ran_fails": { + "expiration_date": "2077-06-01", + "justification": "Didn't feel like it", + "run": true + }, + "17_waivered_expiry_in_future_string_not_ran": { + "expiration_date": "2077-06-01", + "justification": "Lack of imagination", + "run": false + }, + "18_waivered_no_expiry_default_run": { + "justification": "Too lazy to specify run, which defaults to true" + } +} \ No newline at end of file diff --git a/test/fixtures/profiles/waivers/basic/files/waivers.xls b/test/fixtures/profiles/waivers/basic/files/waivers.xls new file mode 100644 index 000000000..ae415ca10 Binary files /dev/null and b/test/fixtures/profiles/waivers/basic/files/waivers.xls differ diff --git a/test/fixtures/profiles/waivers/basic/files/waivers.xlsx b/test/fixtures/profiles/waivers/basic/files/waivers.xlsx new file mode 100644 index 000000000..a5d05f8f6 Binary files /dev/null and b/test/fixtures/profiles/waivers/basic/files/waivers.xlsx differ diff --git a/test/fixtures/profiles/waivers/basic/files/wrong-headers.csv b/test/fixtures/profiles/waivers/basic/files/wrong-headers.csv new file mode 100644 index 000000000..86c07c571 --- /dev/null +++ b/test/fixtures/profiles/waivers/basic/files/wrong-headers.csv @@ -0,0 +1,8 @@ +control_id_random,justification_random,run_random,expiration_date_random,,, +03_waivered_no_expiry_ran_passes,Sound reasoning,TRUE,,,, +04_waivered_no_expiry_ran_fails,Unassailable thinking,TRUE,2077-11-10T00:00:00Z,,, +,,,,,, +05_waivered_no_expiry_not_ran,Sheer cleverness,FALSE,,,, +06_waivered_expiry_in_past_ran_passes,Necessity,TRUE,,,, +14_waivered_expiry_in_future_z_not_ran,Lack of imagination,FALSE,2077-11-10T00:00:00Z,,, +random contorl id with no data,,,,,,random data in csv! \ No newline at end of file diff --git a/test/fixtures/profiles/waivers/basic/files/wrong-headers.json b/test/fixtures/profiles/waivers/basic/files/wrong-headers.json new file mode 100644 index 000000000..90fc0dff1 --- /dev/null +++ b/test/fixtures/profiles/waivers/basic/files/wrong-headers.json @@ -0,0 +1,9 @@ +{ + "03_waivered_no_expiry_ran_passes": { + "justification_random": "Sound reasoning", + "run_random": true, + "expiration_date_random": "1977-06-01T00:00:00.000Z" + }, + "04_waivered_no_expiry_ran_fails": { + } +} \ No newline at end of file diff --git a/test/fixtures/profiles/waivers/basic/files/wrong-headers.xls b/test/fixtures/profiles/waivers/basic/files/wrong-headers.xls new file mode 100644 index 000000000..9b720a4e2 Binary files /dev/null and b/test/fixtures/profiles/waivers/basic/files/wrong-headers.xls differ diff --git a/test/fixtures/profiles/waivers/basic/files/wrong-headers.xlsx b/test/fixtures/profiles/waivers/basic/files/wrong-headers.xlsx new file mode 100644 index 000000000..60a2f386c Binary files /dev/null and b/test/fixtures/profiles/waivers/basic/files/wrong-headers.xlsx differ diff --git a/test/fixtures/profiles/waivers/basic/files/wrong-headers.yaml b/test/fixtures/profiles/waivers/basic/files/wrong-headers.yaml new file mode 100644 index 000000000..f6748a16c --- /dev/null +++ b/test/fixtures/profiles/waivers/basic/files/wrong-headers.yaml @@ -0,0 +1,4 @@ +03_waivered_no_expiry_ran_passes: + justification_random: Sound reasoning + run_random: true + expiration_date_random: 2077-11-10T00:00:00Z \ No newline at end of file diff --git a/test/functional/helper.rb b/test/functional/helper.rb index 81f6aa009..87591611e 100644 --- a/test/functional/helper.rb +++ b/test/functional/helper.rb @@ -39,6 +39,7 @@ module FunctionalHelper let(:simple_inheritance) { File.join(profile_path, "simple-inheritance") } let(:sensitive_profile) { File.join(examples_path, "profile-sensitive") } let(:config_dir_path) { File.join(mock_path, "config_dirs") } + let(:enhanced_outcome_profile) { "#{profile_path}/enhanced-outcomes-test" } let(:dst) do # create a temporary path, but we only want an auto-clean helper @@ -186,6 +187,7 @@ module FunctionalHelper prefix += assemble_env_prefix(opts[:env]) command_line += " --reporter json " if opts[:json] && command_line =~ /\bexec\b/ command_line += " --no-create-lockfile " if (!opts[:lock]) && command_line =~ /\bexec\b/ + command_line += " --enhanced_outcomes " if opts[:enhanced_outcomes] && command_line =~ /\bexec\b/ run_result = nil if opts[:tmpdir] diff --git a/test/functional/inspec_exec_json_test.rb b/test/functional/inspec_exec_json_test.rb index bf4d5271b..0b14e6dfb 100644 --- a/test/functional/inspec_exec_json_test.rb +++ b/test/functional/inspec_exec_json_test.rb @@ -547,4 +547,21 @@ describe "inspec exec with json formatter" do end end + + describe "when running a profile with enhanced_outcomes option" do + it "can execute a profile and validate the json schema" do + out = inspec("exec " + enhanced_outcome_profile + " --reporter json --no-create-lockfile --enhanced-outcomes") + data = JSON.parse(out.stdout) + sout = inspec("schema exec-json") + schema = JSONSchemer.schema(sout.stdout) + _(schema.validate(data).to_a).must_equal [] + _(out.stderr).must_equal "" + _(data["profiles"].first["controls"][0]["status"]).must_equal "error" + _(data["profiles"].first["controls"][2]["status"]).must_equal "not_applicable" + _(data["profiles"].first["controls"][4]["status"]).must_equal "not_reviewed" + _(data["profiles"].first["controls"][6]["status"]).must_equal "failed" + _(data["profiles"].first["controls"][7]["status"]).must_equal "passed" + assert_exit_code 100, out + end + end end diff --git a/test/functional/inspec_exec_streaming_progress_bar_test.rb b/test/functional/inspec_exec_streaming_progress_bar_test.rb index 9f2bdfa38..ab784add2 100644 --- a/test/functional/inspec_exec_streaming_progress_bar_test.rb +++ b/test/functional/inspec_exec_streaming_progress_bar_test.rb @@ -28,8 +28,6 @@ describe "inspec exec with streaming progress bar reporter" do it "can execute a profile with dependent profiles" do profile = File.join(profile_path, "dependencies", "inheritance") out = inspec("exec " + profile + " --reporter progress-bar --no-create-lockfile") - _(out.stderr).must_include "[100.00%]" - _(out.stderr).must_include "[6/6]" assert_exit_code 0, out end @@ -49,10 +47,10 @@ describe "inspec exec with streaming progress bar reporter" do end it "can execute multiple profiles" do - out = inspec("exec " + File.join(profile_path, "dependencies", "inheritance") + " " + File.join(profile_path, "controls-option-test") + " --no-create-lockfile --reporter progress-bar") + out = inspec("exec " + File.join(profile_path, "control-tags") + " " + File.join(profile_path, "controls-option-test") + " --no-create-lockfile --reporter progress-bar") _(out.stderr).must_include "[100.00%]" - _(out.stderr).must_include "[11/11]" - assert_exit_code 0, out + _(out.stderr).must_include "[10/10]" + assert_exit_code 100, out end it "can execute and print proper output when tests are failed" do @@ -77,4 +75,20 @@ describe "inspec exec with streaming progress bar reporter" do assert_exit_code 101, out end + it "shows enhanced_outcomes with enhanced_outcomes flag" do + skip_windows! + + out = inspec("exec " + File.join(profile_path, "enhanced-outcomes-test") + " --no-create-lockfile --reporter progress-bar --enhanced-outcomes") + _(out.stderr).must_include "[ERROR] tmp-1.0.1" + _(out.stderr).must_include "[ERROR] tmp-1.0.2" + _(out.stderr).must_include "[N/A] tmp-2.0.1" + _(out.stderr).must_include "[N/A] tmp-2.0.2" + _(out.stderr).must_include "[N/R] tmp-3.0.1" + _(out.stderr).must_include "[N/R] tmp-3.0.2" + _(out.stderr).must_include "[N/R] tmp-3.0.2" + _(out.stderr).must_include "[FAILED] tmp-4.0" + _(out.stderr).must_include "[PASSED] tmp-5.0" + assert_exit_code 100, out + end + end diff --git a/test/functional/inspec_exec_test.rb b/test/functional/inspec_exec_test.rb index b8779edc5..75cf378f1 100644 --- a/test/functional/inspec_exec_test.rb +++ b/test/functional/inspec_exec_test.rb @@ -1306,15 +1306,108 @@ EOT end end - describe "when profiles are dependent on different versions of same profile" do - let(:profile) { "#{profile_path}/git-fetcher/inheritance/parent-profile" } - let(:run_result) { run_inspec_process("exec #{profile}") } - it "should evaluate all test controls of all versions correctly" do + unless windows? + describe "when profiles are dependent on different versions of same profile - test in unix" do + let(:profile) { "#{profile_path}/git-fetcher/inheritance/parent-profile" } + let(:run_result) { run_inspec_process("exec #{profile}") } + it "should evaluate all test controls of all versions correctly" do + skip_windows! + _(run_result.stderr).must_be_empty + _(run_result.stdout).must_include "2.7.0" + _(run_result.stdout).must_include "2.6.0" + _(run_result.stdout).must_include "sshd-01" + _(run_result.stdout).must_include "sshd-50" + end + end + end + + if windows? + describe "when profiles are dependent on different versions of same profile - test in windows" do + let(:profile) { "#{profile_path}/git-fetcher/inheritance-windows/parent-profile" } + let(:run_result) { run_inspec_process("exec #{profile}") } + it "should evaluate all test controls of all versions correctly" do + _(run_result.stdout).must_include "1.1.2" + _(run_result.stdout).must_include "1.1.0" + end + end + end + + describe "when running profile with enhanced_outcomes option" do + let(:run_result) { run_inspec_process("exec #{profile} --no-create-lockfile", enhanced_outcomes: true) } + let(:profile) { "#{profile_path}/enhanced-outcomes-test" } + it "should evaluate all test controls correctly" do _(run_result.stderr).must_be_empty - _(run_result.stdout).must_include "2.7.0" - _(run_result.stdout).must_include "2.6.0" - _(run_result.stdout).must_include "sshd-01" - _(run_result.stdout).must_include "sshd-50" + end + + it "should show enhanced_outcomes for skipped tests in controls" do + _(run_result.stdout).must_include "5 skipped" + _(run_result.stdout).must_include "3 controls not reviewed" + _(run_result.stdout).must_include "N/R" + end + + it "should show enhanced_outcomes for controls with impact 0" do + _(run_result.stdout).must_include "5 skipped" + _(run_result.stdout).must_include "3 controls not applicable" + _(run_result.stdout).must_include "N/A" + end + + it "should show enhanced_outcomes for controls with errors" do + _(run_result.stdout).must_include "3 failures" + _(run_result.stdout).must_include "2 controls have error" + _(run_result.stdout).must_include "ERR" + end + + it "should show enhanced_outcomes for controls with failures" do + _(run_result.stdout).must_include "1 control failure" + end + + it "should show enhanced_outcomes for passed controls" do + _(run_result.stdout).must_include "1 successful control" + end + + it "should mark control as N/A using zero impact from only_if" do + if windows? + _(run_result.stdout).must_include "[N/A] tmp-6.0.1" + else + _(run_result.stdout).must_include "N/A tmp-6.0.1" + end + _(run_result.stdout).must_include "Some reason for N/A" + end + + it "should not mark control as N/A using non-zeo impact from only_if" do + if windows? + _(run_result.stdout).must_include "[N/R] tmp-6.0.2" + else + _(run_result.stdout).must_include "N/R tmp-6.0.2" + end + end + end + + describe "when running profile with enhanced_outcomes option and yaml reporter" do + let(:run_result) { run_inspec_process("exec #{profile} --no-create-lockfile --reporter yaml", enhanced_outcomes: true) } + let(:profile) { "#{profile_path}/enhanced-outcomes-test" } + it "should evaluate all test controls correctly" do + _(run_result.stderr).must_be_empty + end + + it "should show enhanced_outcomes for skipped tests in controls" do + _(run_result.stdout).must_include ":status: not_reviewed" + end + + it "should show enhanced_outcomes for controls with impact 0" do + _(run_result.stdout).must_include ":status: not_applicable" + end + + it "should show enhanced_outcomes for controls with errors" do + _(run_result.stdout).must_include ":status: error" + end + + it "should show enhanced_outcomes for controls with failures" do + _(run_result.stdout).must_include ":status: failed" + end + + it "should show enhanced_outcomes for passed controls" do + _(run_result.stdout).must_include ":status: passed" end end end diff --git a/test/functional/inspec_schema_test.rb b/test/functional/inspec_schema_test.rb index 12e0ee3d2..d78c12c6e 100644 --- a/test/functional/inspec_schema_test.rb +++ b/test/functional/inspec_schema_test.rb @@ -25,6 +25,16 @@ describe "inspec schema" do out = inspec("schema exec-json") json_output = JSON.parse(out.stdout) _(json_output["definitions"]["Control_Result"]["properties"]["resource_id"]).wont_be_nil + # status value to be nil when not using enhanced outcomes flag + assert_nil(json_output["definitions"]["Exec_JSON_Control"]["properties"]["status"]) + end + end + + describe "validate schema of exec-json with enhanced_outcomes option" do + it "contains resource_id key" do + out = inspec("schema exec-json --enhanced-outcomes") + json_output = JSON.parse(out.stdout) + _(json_output["definitions"]["Exec_JSON_Control"]["properties"]["status"]).wont_be_nil end end end diff --git a/test/functional/inspec_shell_test.rb b/test/functional/inspec_shell_test.rb index 07ff1b9e3..f0affdce7 100644 --- a/test/functional/inspec_shell_test.rb +++ b/test/functional/inspec_shell_test.rb @@ -7,8 +7,9 @@ describe "inspec shell tests" do parallelize_me! describe "cmd" do - def assert_shell_c(code, exit_status, json = false, stderr = "") + def assert_shell_c(code, exit_status, json = false, stderr = "", enhanced_outcomes = false) json_suffix = " --reporter 'json'" if json + json_suffix = " --enhanced_outcomes" if enhanced_outcomes command = "shell -c '#{code.tr("'", "\\'")}'#{json_suffix}" # On darwin this value is: # shell -c 'describe file(\"/Users/nickschwaderer/Documents/inspec/inspec/test/functional/inspec_shell_test.rb\") do it { should exist } end' --reporter 'json'" @@ -219,6 +220,42 @@ describe "inspec shell tests" do _(out.stdout).must_include "1 successful" _(out.stdout).must_include "0 failures" end + + it "runs controls having skipped tests with enhanced_outcomes option" do + skip_windows! # Breakage confirmed + out = assert_shell_c("control \"test\" do \n only_if { false }\n describe file(\"#{__FILE__}\") do it { should exist } end end", 101 , false, "", true) + _(out.stdout).must_include "not reviewed" + _(out.stdout).must_include "0 successful" + _(out.stdout).must_include "0 failures" + _(out.stdout).must_include "1 skipped" + end + + it "runs zero impact controls with enhanced_outcomes option" do + skip_windows! # Breakage confirmed + out = assert_shell_c("control \"test\" do \n impact 0.0 \n describe file(\"#{__FILE__}\") do it { should exist } end end", 0, false, "", true) + _(out.stdout).must_include "not applicable" + _(out.stdout).must_include "1 successful" + _(out.stdout).must_include "0 failures" + _(out.stdout).must_include "0 skipped" + end + + it "runs zero impact controls with skipped test and enhanced_outcomes option" do + skip_windows! # Breakage confirmed + out = assert_shell_c("control \"test\" do \n impact 0.0 \n only_if { false } \n describe file(\"#{__FILE__}\") do it { should exist } end end", 101, false, "", true) + _(out.stdout).must_include "not applicable" + _(out.stdout).must_include "0 successful" + _(out.stdout).must_include "0 failures" + _(out.stdout).must_include "1 skipped" + end + + it "runs control with error and enhanced_outcomes option" do + skip_windows! # Breakage confirmed + out = assert_shell_c("control \"test\" do \n impact 0.0 \n describe file(\"#{__FILE__}\") do it { must_bot exist } end end", 100, false, "", true) + _(out.stdout).must_include "has error" + _(out.stdout).must_include "0 successful" + _(out.stdout).must_include "1 failure" + _(out.stdout).must_include "0 skipped" + end end # Pry does not support STDIN from windows currently. Skipping these for now. @@ -235,8 +272,9 @@ describe "inspec shell tests" do out.stderr.gsub(/\e\[(\d+)(;\d+)*m/, "") # strip ANSI color codes end - def do_shell(code, exit_status = 0, stderr = "") + def do_shell(code, exit_status = 0, stderr = "", enhanced_outcomes = false) cmd = "echo '#{code.tr("'", "\\'")}' | #{exec_inspec} shell" + cmd += " --enhanced_outcomes" if enhanced_outcomes self.out = CMD.run_command(cmd) assert_exit_code exit_status, out @@ -349,6 +387,38 @@ describe "inspec shell tests" do _(out.stdout).must_include "1 successful" _(out.stdout).must_include "1 failure" end + + it "runs controls having skipped tests with enhanced_outcomes option" do + out = do_shell("control \"test\" do \n only_if { false }\n describe file(\"#{__FILE__}\") do it { should exist } end end", 0 , "", true) + _(out.stdout).must_include "not reviewed" + _(out.stdout).must_include "0 successful" + _(out.stdout).must_include "0 failures" + _(out.stdout).must_include "1 skipped" + end + + it "runs zero impact controls with enhanced_outcomes option" do + out = do_shell("control \"test\" do \n impact 0.0 \n describe file(\"#{__FILE__}\") do it { should exist } end end", 0, "", true) + _(out.stdout).must_include "not applicable" + _(out.stdout).must_include "1 successful" + _(out.stdout).must_include "0 failures" + _(out.stdout).must_include "0 skipped" + end + + it "runs zero impact controls with skipped test and enhanced_outcomes option" do + out = do_shell("control \"test\" do \n impact 0.0 \n only_if { false } \n describe file(\"#{__FILE__}\") do it { should exist } end end", 0, "", true) + _(out.stdout).must_include "not applicable" + _(out.stdout).must_include "0 successful" + _(out.stdout).must_include "0 failures" + _(out.stdout).must_include "1 skipped" + end + + it "runs control with error and enhanced_outcomes option" do + out = do_shell("control \"test\" do \n impact 0.0 \n describe file(\"#{__FILE__}\") do it { must_bot exist } end end", 0, "", true) + _(out.stdout).must_include "has error" + _(out.stdout).must_include "0 successful" + _(out.stdout).must_include "1 failure" + _(out.stdout).must_include "0 skipped" + end end end end diff --git a/test/functional/waivers_test.rb b/test/functional/waivers_test.rb index 3a4faa0b6..a52d9eae0 100644 --- a/test/functional/waivers_test.rb +++ b/test/functional/waivers_test.rb @@ -8,7 +8,7 @@ describe "waivers" do let(:waivers_profiles_path) { "#{profile_path}/waivers" } let(:run_result) { run_inspec_process(cmd, json: true) } let(:controls_by_id) { run_result; @json.dig("profiles", 0, "controls").map { |c| [c["id"], c] }.to_h } - let(:cmd) { "exec #{waivers_profiles_path}/#{profile_name} --input-file #{waivers_profiles_path}/#{profile_name}/files/#{waiver_file}" } + let(:cmd) { "exec #{waivers_profiles_path}/#{profile_name} --waiver-file #{waivers_profiles_path}/#{profile_name}/files/#{waiver_file}" } attr_accessor :out @@ -115,6 +115,110 @@ describe "waivers" do end end + describe "a fully pre-slugged control file with csv format waiver file" do + let(:profile_name) { "basic" } + let(:waiver_file) { "waivers.csv" } + + # rubocop:disable Layout/AlignHash + { + "01_not_waivered_passes" => "passed", + "02_not_waivered_fails" => "failed", + "03_waivered_no_expiry_ran_passes" => "passed", + "04_waivered_no_expiry_ran_fails" => "failed", + "05_waivered_no_expiry_not_ran" => "skipped", + "06_waivered_expiry_in_past_ran_passes" => "passed", + "14_waivered_expiry_in_future_z_not_ran" => "skipped", + }.each do |control_id, expected| + it "has all of the expected outcomes #{control_id}" do + assert_test_outcome expected, control_id + + if control_id !~ /not_waivered/ + assert_waiver_annotation control_id + else + refute_waiver_annotation control_id + end + end + end + end + + describe "a fully pre-slugged control file with json format waiver file" do + let(:profile_name) { "basic" } + let(:waiver_file) { "waivers.json" } + + # rubocop:disable Layout/AlignHash + { + "01_not_waivered_passes" => "passed", + "02_not_waivered_fails" => "failed", + "03_waivered_no_expiry_ran_passes" => "passed", + "04_waivered_no_expiry_ran_fails" => "failed", + "05_waivered_no_expiry_not_ran" => "skipped", + "06_waivered_expiry_in_past_ran_passes" => "passed", + "14_waivered_expiry_in_future_z_not_ran" => "skipped", + }.each do |control_id, expected| + it "has all of the expected outcomes #{control_id}" do + assert_test_outcome expected, control_id + + if control_id !~ /not_waivered/ + assert_waiver_annotation control_id + else + refute_waiver_annotation control_id + end + end + end + end + + describe "a fully pre-slugged control file with XLSX format waiver file" do + let(:profile_name) { "basic" } + let(:waiver_file) { "waivers.xlsx" } + + # rubocop:disable Layout/AlignHash + { + "01_not_waivered_passes" => "passed", + "02_not_waivered_fails" => "failed", + "03_waivered_no_expiry_ran_passes" => "passed", + "04_waivered_no_expiry_ran_fails" => "failed", + "05_waivered_no_expiry_not_ran" => "skipped", + "06_waivered_expiry_in_past_ran_passes" => "passed", + "14_waivered_expiry_in_future_z_not_ran" => "skipped", + }.each do |control_id, expected| + it "has all of the expected outcomes #{control_id}" do + assert_test_outcome expected, control_id + + if control_id !~ /not_waivered/ + assert_waiver_annotation control_id + else + refute_waiver_annotation control_id + end + end + end + end + + describe "a fully pre-slugged control file with XLS format waiver file" do + let(:profile_name) { "basic" } + let(:waiver_file) { "waivers.xls" } + + # rubocop:disable Layout/AlignHash + { + "01_not_waivered_passes" => "passed", + "02_not_waivered_fails" => "failed", + "03_waivered_no_expiry_ran_passes" => "passed", + "04_waivered_no_expiry_ran_fails" => "failed", + "05_waivered_no_expiry_not_ran" => "skipped", + "06_waivered_expiry_in_past_ran_passes" => "passed", + "14_waivered_expiry_in_future_z_not_ran" => "skipped", + }.each do |control_id, expected| + it "has all of the expected outcomes #{control_id}" do + assert_test_outcome expected, control_id + + if control_id !~ /not_waivered/ + assert_waiver_annotation control_id + else + refute_waiver_annotation control_id + end + end + end + end + describe "with --filter-waived-controls flag" do it "can execute and not hit failures" do inspec("exec " + "#{waivers_profiles_path}/purely-broken-controls" + " --filter-waived-controls --waiver-file #{waivers_profiles_path}/purely-broken-controls/files/waivers.yml" + " --no-create-lockfile" + " --no-color") @@ -203,4 +307,64 @@ describe "waivers" do end end end + + describe "with a waiver file that does not exist" do + let(:profile_name) { "basic" } + let(:waiver_file) { "no_file.yaml" } + it "raise file does not exist standard error" do + result = run_result + assert_includes result.stderr, "no_file.yaml does not exist" + assert_equal 1, result.exit_status + end + end + + describe "with a waiver file with wrong headers" do + let(:profile_name) { "basic" } + + describe "using csv file" do + let(:waiver_file) { "wrong-headers.csv" } + it "raise warnings" do + result = run_result + assert_includes result.stderr, "Missing column headers: [\"control_id\", \"justification\"]" + assert_includes result.stderr, "Invalid column header: Column can't be nil" + assert_includes result.stderr, "Extra column headers: [\"control_id_random\", \"justification_random\", \"run_random\", \"expiration_date_random\", nil]" + end + end + + describe "using xlsx file" do + let(:waiver_file) { "wrong-headers.xlsx" } + it "raise warnings" do + result = run_result + assert_includes result.stderr, "Missing column headers: [\"control_id\", \"justification\"]" + assert_includes result.stderr, "Extra column headers: [\"control_id_random\", \"justification_random\", \"run_random\", \"expiration_date_random\"]" + end + end + + describe "using xls file" do + let(:waiver_file) { "wrong-headers.xls" } + it "raise warnings" do + result = run_result + assert_includes result.stderr, "Missing column headers: [\"control_id\", \"justification\"]" + assert_includes result.stderr, "Extra column headers: [\"control_id_random\", \"justification_random\", \"run_random\", \"expiration_date_random\"]" + end + end + + describe "using json file" do + let(:waiver_file) { "wrong-headers.json" } + it "raise warnings" do + result = run_result + assert_includes result.stderr, "Missing column headers: [\"justification\"]" + assert_includes result.stderr, "Extra column headers: [\"justification_random\", \"run_random\", \"expiration_date_random\"]" + end + end + + describe "using yaml file" do + let(:waiver_file) { "wrong-headers.yaml" } + it "raise warnings" do + result = run_result + assert_includes result.stderr, "Missing column headers: [\"justification\"]" + assert_includes result.stderr, "Extra column headers: [\"justification_random\", \"run_random\", \"expiration_date_random\"]" + end + end + end end diff --git a/test/helpers/mock_loader.rb b/test/helpers/mock_loader.rb index c1c7c0386..11e45d795 100644 --- a/test/helpers/mock_loader.rb +++ b/test/helpers/mock_loader.rb @@ -681,6 +681,25 @@ class MockLoader # file resource windows inherit "(Get-Acl 'C:/ExamlpeFolder').access| Where-Object {$_.IsInherited -eq $true} | measure | % { $_.Count }" => cmd.call("windows_file_inherit_output"), + + # podman + %{podman ps -a --no-trunc --size --format '{\"ID\": {{json .ID}}, \"Image\": {{json .Image}}, \"ImageID\": {{json .ImageID}}, \"Command\": {{json .Command}}, \"CreatedAt\": {{json .CreatedAt}}, \"RunningFor\": {{json .RunningFor}}, \"Status\": {{json .Status}}, \"Pod\": {{json .Pod}}, \"Ports\": {{json .Ports}}, \"Size\": {{json .Size}}, \"Names\": {{json .Names}}, \"Networks\": {{json .Networks}}, \"Labels\": {{json .Labels}}, \"Mounts\": {{json .Mounts}}}'} => cmd.call("podman-ps-a"), + %{podman images -a --no-trunc --format '{\"ID\": {{json .ID}}, \"Repository\": {{json .Repository}}, \"Tag\": {{json .Tag}}, \"Size\": {{json .Size}}, \"Digest\": {{json .Digest}}, \"CreatedAt\": {{json .CreatedAt}}, \"CreatedSince\": {{json .CreatedSince}}, \"History\": {{json .History}}}'} => cmd.call("podman-images-a"), + %{podman network ls --no-trunc --format '{\"ID\": {{json .ID}}, \"Name\": {{json .Name}}, \"Driver\": {{json .Driver}}, \"Labels\": {{json .Labels}}, \"Options\": {{json .Options}}, \"IPAMOptions\": {{json .IPAMOptions}}, \"Created\": {{json .Created}}, \"Internal\": {{json .Internal}}, \"IPv6Enabled\": {{json .IPv6Enabled}}, \"DNSEnabled\": {{json .DNSEnabled}}, \"NetworkInterface\": {{json .NetworkInterface}}, \"Subnets\": {{json .Subnets}}}'} => cmd.call("podman-network-ls"), + "podman pod ps --no-trunc --format json" => cmd.call("podman-pod-ps"), + "podman info --format json" => cmd.call("podman-info"), + "podman version --format json" => cmd.call("podman-version"), + "podman volume ls --format json" => cmd.call("podman-volume-ls"), + "podman inspect 591270d8d80d --format json" => cmd.call("podman-inspec"), + "podman image inspect docker.io/library/busybox:latest --format '{\"id\": {{json .ID}}, \"repo_tags\": {{json .RepoTags}}, \"size\": {{json .Size}}, \"digest\": {{json .Digest}}, \"created_at\": {{json .Created}}, \"version\": {{json .Version}}, \"names_history\": {{json .NamesHistory}}, \"repo_digests\": {{json .RepoDigests}}, \"architecture\": {{json .Architecture}}, \"os\": {{json .Os}}, \"virtual_size\": {{json .VirtualSize}}}'" => cmd.call("podman-inspect-info"), + "podman image inspect not-exist:latest --format '{\"id\": {{json .ID}}, \"repo_tags\": {{json .RepoTags}}, \"size\": {{json .Size}}, \"digest\": {{json .Digest}}, \"created_at\": {{json .Created}}, \"version\": {{json .Version}}, \"names_history\": {{json .NamesHistory}}, \"repo_digests\": {{json .RepoDigests}}, \"architecture\": {{json .Architecture}}, \"os\": {{json .Os}}, \"virtual_size\": {{json .VirtualSize}}}'" => cmd_stderr.call("podman-errors"), + "podman network inspect minikube --format '{\"id\": {{json .ID}}, \"name\": {{json .Name}}, \"driver\": {{json .Driver}}, \"labels\": {{json .Labels}}, \"options\": {{json .Options}}, \"ipam_options\": {{json .IPAMOptions}}, \"internal\": {{json .Internal}}, \"created\": {{json .Created}}, \"ipv6_enabled\": {{json .IPv6Enabled}}, \"dns_enabled\": {{json .DNSEnabled}}, \"network_interface\": {{json .NetworkInterface}}, \"subnets\": {{json .Subnets}}}'" => cmd.call("podman-network"), + "podman network inspect not-exist --format '{\"id\": {{json .ID}}, \"name\": {{json .Name}}, \"driver\": {{json .Driver}}, \"labels\": {{json .Labels}}, \"options\": {{json .Options}}, \"ipam_options\": {{json .IPAMOptions}}, \"internal\": {{json .Internal}}, \"created\": {{json .Created}}, \"ipv6_enabled\": {{json .IPv6Enabled}}, \"dns_enabled\": {{json .DNSEnabled}}, \"network_interface\": {{json .NetworkInterface}}, \"subnets\": {{json .Subnets}}}'" => cmd_stderr.call("podman-errors"), + "podman version" => empty.call, + "podman volume inspect my_volume --format '{\"name\": {{json .Name}}, \"driver\": {{json .Driver}}, \"mountpoint\": {{json .Mountpoint}}, \"created_at\": {{json .CreatedAt}}, \"labels\": {{json .Labels}}, \"scope\": {{json .Scope}}, \"options\": {{json .Options}}, \"mount_count\": {{json .MountCount}}, \"needs_copy_up\": {{json .NeedsCopyUp}}, \"needs_chown\": {{json .NeedsChown}}}'" => cmd.call("podman-volume-inspect"), + "podman volume inspect non_existing_volume --format '{\"name\": {{json .Name}}, \"driver\": {{json .Driver}}, \"mountpoint\": {{json .Mountpoint}}, \"created_at\": {{json .CreatedAt}}, \"labels\": {{json .Labels}}, \"scope\": {{json .Scope}}, \"options\": {{json .Options}}, \"mount_count\": {{json .MountCount}}, \"needs_copy_up\": {{json .NeedsCopyUp}}, \"needs_chown\": {{json .NeedsChown}}}'" => cmd_stderr.call("podman-errors"), + "podman pod inspect nginx-frontend --format '{\"id\": {{json .ID}}, \"name\": {{json .Name}}, \"created_at\": {{json .Created}}, \"create_command\": {{json .CreateCommand}}, \"state\": {{json .State}}, \"hostname\": {{json .Hostname}}, \"create_cgroup\": {{json .CreateCgroup}}, \"cgroup_parent\": {{json .CgroupParent}}, \"cgroup_path\": {{json .CgroupPath}}, \"create_infra\": {{json .CreateInfra}}, \"infra_container_id\": {{json .InfraContainerID}}, \"infra_config\": {{json .InfraConfig}}, \"shared_namespaces\": {{json .SharedNamespaces}}, \"num_containers\": {{json .NumContainers}}, \"containers\": {{json .Containers}}}'" => cmd.call("podman-pod-inspect"), + "podman pod inspect non_existing_pod --format '{\"id\": {{json .ID}}, \"name\": {{json .Name}}, \"created_at\": {{json .Created}}, \"create_command\": {{json .CreateCommand}}, \"state\": {{json .State}}, \"hostname\": {{json .Hostname}}, \"create_cgroup\": {{json .CreateCgroup}}, \"cgroup_parent\": {{json .CgroupParent}}, \"cgroup_path\": {{json .CgroupPath}}, \"create_infra\": {{json .CreateInfra}}, \"infra_container_id\": {{json .InfraContainerID}}, \"infra_config\": {{json .InfraConfig}}, \"shared_namespaces\": {{json .SharedNamespaces}}, \"num_containers\": {{json .NumContainers}}, \"containers\": {{json .Containers}}}'" => cmd_stderr.call("podman-errors"), } if @platform && (@platform[:name] == "windows" || @platform[:name] == "freebsd") diff --git a/test/unit/resources/podman_container_test.rb b/test/unit/resources/podman_container_test.rb new file mode 100644 index 000000000..7f1186518 --- /dev/null +++ b/test/unit/resources/podman_container_test.rb @@ -0,0 +1,31 @@ +require "inspec/globals" +require "#{Inspec.src_root}/test/helper" +require_relative "../../../lib/inspec/resources/podman_container" + +describe Inspec::Resources::PodmanContainer do + it "check container parsing" do + resource = load_resource("podman_container", "sweet_mendeleev") + _(resource.exist?).must_equal true + _(resource.command).must_equal "/bin/bash" + _(resource.status).must_equal "Up 13 hours ago" + _(resource.running?).must_equal true + _(resource.labels).must_include("maintainer" => "NGINX Docker Maintainers ") + _(resource.image).must_equal "docker.io/library/nginx:latest" + _(resource.ports).must_equal "" + end + + it "prints as a podman resource" do + resource = load_resource("podman_container", "sweet_mendeleev") + _(resource.to_s).must_equal "Podman Container sweet_mendeleev" + end + + it "prints the resource id of the current resource" do + resource = load_resource("podman_container", "sweet_mendeleev") + _(resource.resource_id).must_equal "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7" + end + + it "skips the resource for unsupported platform" do + resource = MockLoader.new(:mock).load_resource("podman_container", "sweet_mendeleev") + _(resource.resource_skipped?).must_equal true + end +end diff --git a/test/unit/resources/podman_image_test.rb b/test/unit/resources/podman_image_test.rb new file mode 100644 index 000000000..a35758e95 --- /dev/null +++ b/test/unit/resources/podman_image_test.rb @@ -0,0 +1,37 @@ +# If we can load the InSpec globals definition file... +require "inspec/globals" +require "#{Inspec.src_root}/test/helper" +require_relative "../../../lib/inspec/resources/podman_image" + +describe Inspec::Resources::PodmanImage do + it "test podman image properties and matchers" do + resource = MockLoader.new("unix".to_sym).load_resource("podman_image", "docker.io/library/busybox") + _(resource.exist?).must_equal true + _(resource.id).must_equal "3c19bafed22355e11a608c4b613d87d06b9cdd37d378e6e0176cbc8e7144d5c6" + _(resource.repo_tags).must_include "docker.io/library/busybox:latest" + _(resource.created_at).must_equal "2022-06-08T00:39:28.175020858Z" + _(resource.version).must_equal "20.10.12" + _(resource.size).must_equal 1636053 + _(resource.digest).must_equal "sha256:3614ca5eacf0a3a1bcc361c939202a974b4902b9334ff36eb29ffe9011aaad83" + _(resource.names_history).must_include "docker.io/library/busybox:latest" + _(resource.repo_digests).must_include "docker.io/library/busybox@sha256:2c5e2045f35086c019e80c86880fd5b7c7a619878b59e3b7592711e1781df51a" + _(resource.architecture).must_equal "arm64" + _(resource.os).must_equal "linux" + _(resource.virtual_size).must_equal 1636053 + _(resource.resource_id).must_equal "docker.io/library/busybox:latest" + _(resource.to_s).must_equal "podman_image docker.io/library/busybox:latest" + end + + it "test for a non-existing container image" do + resource = MockLoader.new("ubuntu".to_sym).load_resource("podman_image", "not-exist") + _(resource.exist?).must_equal false + assert_nil resource.repo_tags + assert_nil resource.size + assert_nil resource.digest + assert_nil resource.names_history + assert_nil resource.os + assert_nil resource.virtual_size + assert_nil resource.architecture + assert_nil resource.repo_digests + end +end diff --git a/test/unit/resources/podman_network_test.rb b/test/unit/resources/podman_network_test.rb new file mode 100644 index 000000000..0400c527a --- /dev/null +++ b/test/unit/resources/podman_network_test.rb @@ -0,0 +1,125 @@ +require "inspec/globals" +require "#{Inspec.src_root}/test/helper" +require_relative "../../../lib/inspec/resources/podman_network" + +describe Inspec::Resources::PodmanNetwork do + describe "when Podman Network with given name exist" do + let(:resource) { MockLoader.new(:unix).load_resource("podman_network", "minikube") } + + describe "exist?" do + it "returns true" do + _(resource.exist?).must_equal true + end + end + + describe "id" do + it "returns the id of the network" do + _(resource.id).must_equal "3a7c94d937d5f3a0f1a9b1610589945aedfbe56207fd5d32fc8154aa1a8b007f" + end + end + + describe "name" do + it "returns the name of the network" do + _(resource.name).must_equal "minikube" + end + end + + describe "network_interface" do + it "returns the network_interface of the network" do + _(resource.network_interface).must_equal "podman1" + end + end + + describe "driver" do + it "returns the driver details of the network" do + _(resource.driver).must_equal "bridge" + end + end + + describe "labels" do + it "returns the labels of the network" do + _(resource.labels).must_equal "created_by.minikube.sigs.k8s.io" => "true", "name.minikube.sigs.k8s.io" => "minikube" + end + end + + describe "options" do + it "returns the options of the network" do + assert_nil resource.options + end + end + + describe "ipv6_enabled" do + it "returns the true if the ipv6 is enabled for the network" do + _(resource.ipv6_enabled).must_equal false + end + end + + describe "ipam_options" do + it "returns the ipam options values for the Network" do + _(resource.ipam_options).must_equal "driver" => "host-local" + end + end + + describe "dns_enabled" do + it "returns true if dns is enabled for the network" do + _(resource.dns_enabled).must_equal true + end + end + + describe "subnets" do + it "returns the subnet list for the network" do + _(resource.subnets).must_equal [{ "subnet" => "192.168.49.0/24", "gateway" => "192.168.49.1" }] + end + end + + describe "internal" do + it "returns true if the network is internal" do + _(resource.internal).must_equal false + end + end + + describe "created" do + it "returns the timestamp when the network was created" do + _(resource.created).must_equal "2022-07-10T19:37:11.656610731+05:30" + end + end + + describe "to_s" do + it "returns the Podman Nework resource name string" do + _(resource.to_s).must_equal "podman_network 3a7c94d937d5f3a0f1a9b1610589945aedfbe56207fd5d32fc8154aa1a8b007f" + end + end + + describe "resource_id" do + it "returns the resource id for the current resource" do + _(resource.resource_id).must_equal "3a7c94d937d5f3a0f1a9b1610589945aedfbe56207fd5d32fc8154aa1a8b007f" + end + end + end + + describe "when Podman Network with given name does not exist" do + let(:resource) { MockLoader.new(:unix).load_resource("podman_network", "not-exist") } + + describe "exist?" do + it "returns false" do + _(resource.exist?).must_equal false + end + end + + describe "all other properties" do + it "returns nil" do + assert_nil resource.name + assert_nil resource.driver + assert_nil resource.ipv6_enabled + assert_nil resource.dns_enabled + assert_nil resource.options + assert_nil resource.ipam_options + assert_nil resource.subnets + assert_nil resource.created + assert_nil resource.internal + assert_nil resource.network_interface + assert_nil resource.labels + end + end + end +end diff --git a/test/unit/resources/podman_pod_test.rb b/test/unit/resources/podman_pod_test.rb new file mode 100644 index 000000000..3fc233941 --- /dev/null +++ b/test/unit/resources/podman_pod_test.rb @@ -0,0 +1,50 @@ +require "inspec/globals" +require "#{Inspec.src_root}/test/helper" +require_relative "../../../lib/inspec/resources/podman_pod" + +describe Inspec::Resources::PodmanPod do + it "checks podman pod parameter and works correctly" do + resource = MockLoader.new("unix".to_sym).load_resource("podman_pod", "nginx-frontend") + _(resource.exist?).must_equal true + _(resource.id).must_equal "fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4" + _(resource.name).must_equal "nginx-frontend" + _(resource.created_at).must_equal "2022-07-14T15:47:47.978078124+05:30" + _(resource.create_command).must_include "new:nginx-frontend" + _(resource.create_command).must_include "podman" + _(resource.state).must_equal "Running" + _(resource.hostname).must_equal "" + _(resource.create_cgroup).must_equal true + _(resource.cgroup_parent).must_equal "user.slice" + _(resource.cgroup_path).must_equal "user.slice/user-libpod_pod_fcfe4d471cfface0d1b39bce23af7d31ab8736cd68c0360ade0b4afe364f79d4.slice" + _(resource.create_infra).must_equal true + _(resource.infra_container_id).must_equal "727538044b32a165934729dc2d47d9d5e981b6496aebfad7de470f7e76ea4251" + _(resource.infra_config).must_include "DNSOption" + _(resource.shared_namespaces).must_include "net" + _(resource.shared_namespaces).must_include "ipc" + _(resource.num_containers).must_equal 2 + _(resource.containers).must_be_kind_of Array + _(resource.resource_id).must_equal "nginx-frontend" + _(resource.to_s).must_equal "Podman Pod nginx-frontend" + end + + it "checks for a non-existing podman pod" do + resource = MockLoader.new("unix".to_sym).load_resource("podman_pod", "non_existing_pod") + _(resource.exist?).must_equal false + assert_nil resource.name + assert_nil resource.created_at + assert_nil resource.create_command + assert_nil resource.state + assert_nil resource.hostname + assert_nil resource.create_cgroup + assert_nil resource.cgroup_parent + assert_nil resource.cgroup_path + assert_nil resource.create_infra + assert_nil resource.infra_container_id + assert_nil resource.infra_config + assert_nil resource.shared_namespaces + assert_nil resource.num_containers + assert_nil resource.containers + _(resource.resource_id).must_equal "non_existing_pod" + _(resource.to_s).must_equal "Podman Pod non_existing_pod" + end +end diff --git a/test/unit/resources/podman_test.rb b/test/unit/resources/podman_test.rb new file mode 100644 index 000000000..3a5f57fb3 --- /dev/null +++ b/test/unit/resources/podman_test.rb @@ -0,0 +1,163 @@ +require "inspec/globals" +require "#{Inspec.src_root}/test/helper" +require_relative "../../../lib/inspec/resources/podman" + +describe Inspec::Resources::Podman do + let(:resource) { load_resource("podman") } + + it "prints as a Podman resource" do + _(resource.to_s).must_equal "Podman" + end + + it "prints as Podman containers plural resource" do + _(resource.containers.to_s).must_equal "Podman Containers" + end + + it "prints the resource id of Podman containers plural resource" do + _(resource.containers.resource_id).must_equal "Podman Containers" + end + + it "returns the parsed details of Podman containers" do + _(resource.containers.exists?).must_equal true + _(resource.containers.commands).must_equal ["/bin/bash", "/bin/bash", "/bin/bash", "", "bash"] + _(resource.containers.ids).must_equal %w{591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7 64b5562346d6b52fd40d790b34e9f18ba3b8745649c302b79ba5399d4ea00b36 437e70c45633de74be7a87ed8d94c442a3bfe0a1cdd293d5184a4af1765d8cf5 a218dfc58fa28e0c58c55e508e5b57084876b42e894b98073c69c45dea06cbb2 b36abf69b8af6f8a8305ab2d9b209c2acaeece41dbc4f242f8e45caf6e02504b} + _(resource.containers.images).must_equal %w{docker.io/library/nginx:latest docker.io/library/ubuntu:latest registry.fedoraproject.org/fedora:latest localhost/podman-pause:4.1.0-1651853754 docker.io/library/ubuntu:latest} + _(resource.containers.names).must_equal %w{sweet_mendeleev wizardly_torvalds confident_bell 95cadbb84df7-infra pensive_mccarthy} + _(resource.containers.status).must_equal ["Up 13 hours ago", "Up 13 hours ago", "Created", "Created", "Created"] + _(resource.containers.image_ids).must_include "55f4b40fe486a5b734b46bb7bf28f52fa31426bf23be068c8e7b19e58d9b8deb" + _(resource.containers.labels).must_include "maintainer" => "NGINX Docker Maintainers " + _(resource.containers.mounts).must_include [] + _(resource.containers.pods).must_include "95cadbb84df71e6374fceb3fd89ee3b8f2c7e1a831062cd9cea7d0e3e4b1dbcc" + _(resource.containers.ports).must_include "" + _(resource.containers.sizes).must_include "12B (virtual 142MB)" + _(resource.containers.created_at).must_include "2022-06-29 08:48:45.195339311 +0530 IST" + _(resource.containers.networks).must_include "podman" + _(resource.containers.running_for).must_include "8 days ago" + + end + + it "returns false if container with specific id does not exist" do + _(resource.containers.where(id: "979453ff4b40fe486a5b734b46bb7bf28f52fa31426bf23be068c8e7b19e58d9b8deb").exists?).must_equal false + end + + it "prints as Podman images plural resource" do + _(resource.images.to_s).must_equal "Podman Images" + end + + it "prints the resource id of Podman images plural resource" do + _(resource.images.resource_id).must_equal "Podman Images" + end + + it "returns the parsed details of podman images" do + _(resource.images.exists?).must_equal true + _(resource.images.ids).must_equal %w{sha256:c7db653c4397e6a4d1e468bb7c6400c022c62623bdb87c173d54bac7995b6d8f sha256:55f4b40fe486a5b734b46bb7bf28f52fa31426bf23be068c8e7b19e58d9b8deb sha256:27941809078cc9b2802deb2b0bb6feed6c236cde01e487f200e24653533701ee sha256:3a66698e604003f7822a0c73e9da50e090fda9a99fe1f2e1e2e7fe796cc803d5} + _(resource.images.repositories).must_equal %w{localhost/podman-pause docker.io/library/nginx docker.io/library/ubuntu registry.fedoraproject.org/fedora} + _(resource.images.tags).must_equal %w{4.1.0-1651853754 latest latest latest} + _(resource.images.sizes).must_equal ["816 kB", "146 MB", "80.3 MB", "169 MB"] + _(resource.images.digests).must_equal %w{sha256:e6e9fffed42f600c811af34569268c07d063f12507457493c608d944a1fdac3f sha256:10f14ffa93f8dedf1057897b745e5ac72ac5655c299dade0aa434c71557697ea sha256:b6b83d3c331794420340093eb706a6f152d9c1fa51b262d9bf34594887c2c7ac sha256:38813cf0913241b7f13c7057e122f7c3cfa2e7c427dca3194f933d94612e280b} + _(resource.images.history).must_equal %w{localhost/podman-pause:4.1.0-1651853754 docker.io/library/nginx:latest docker.io/library/ubuntu:latest registry.fedoraproject.org/fedora:latest} + _(resource.images.created_since).must_equal ["5 days ago", "13 days ago", "4 weeks ago", "2 months ago"] + _(resource.images.created_at).must_equal ["2022-07-01 07:38:09 +0000 UTC", "2022-06-23 04:13:24 +0000 UTC", "2022-06-06 22:21:26 +0000 UTC", "2022-05-06 10:11:58 +0000 UTC"] + end + + it "returns false if image with specific id does not exist" do + _(resource.images.where(id: "979453ff4b40fe486a5b734b46bb7bf28f52fa31426bf23be068c8e7b19e58d9b8deb").exists?).must_equal false + end + + it "prints as Podman networks plural resource" do + _(resource.networks.to_s).must_equal "Podman Networks" + end + + it "prints the resource id of Podman networks plural resource" do + _(resource.networks.resource_id).must_equal "Podman Networks" + end + + it "returns the parsed details of podman networks" do + _(resource.networks.exists?).must_equal true + _(resource.networks.ids).must_equal %w{2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9} + _(resource.networks.names).must_equal %w{podman} + _(resource.networks.drivers).must_equal %w{bridge} + _(resource.networks.network_interfaces).must_equal %w{podman0} + _(resource.networks.created).must_equal %w{2022-07-06T10:32:00.879655095+05:30} + _(resource.networks.subnets).must_equal [[{ "subnet" => "10.88.0.0/16", "gateway" => "10.88.0.1" }]] + _(resource.networks.ipv6_enabled).must_equal [false] + _(resource.networks.internal).must_equal [false] + _(resource.networks.dns_enabled).must_equal [false] + _(resource.networks.ipam_options).must_equal [{ "driver" => "host-local" }] + _(resource.networks.labels).must_equal [""] + _(resource.networks.options).must_include nil + end + + it "returns false if network with specific id does not exist" do + _(resource.networks.where(id: "979453ff4b40fe486a5b734b46bb7bf28f52fa31426bf23be068c8e7b19e58d9b8deb").exists?).must_equal false + end + + it "returns true if network with specific id exist" do + _(resource.networks.where(id: "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9").exists?).must_equal true + end + + it "prints as Podman pods plural resource" do + _(resource.pods.to_s).must_equal "Podman Pods" + end + + it "prints the resource id of Podman pods plural resource" do + _(resource.pods.resource_id).must_equal "Podman Pods" + end + + it "returns the parsed details of podman pods" do + _(resource.pods.ids).must_equal %w{95cadbb84df71e6374fceb3fd89ee3b8f2c7e1a831062cd9cea7d0e3e4b1dbcc} + _(resource.pods.cgroups).must_equal %w{user.slice} + _(resource.pods.containers).must_equal [[{ "Id" => "a218dfc58fa28e0c58c55e508e5b57084876b42e894b98073c69c45dea06cbb2", "Names" => "95cadbb84df7-infra", "Status" => "running" }, { "Id" => "b36abf69b8af6f8a8305ab2d9b209c2acaeece41dbc4f242f8e45caf6e02504b", "Names" => "pensive_mccarthy", "Status" => "running" }]] + _(resource.pods.created).must_equal %w{2022-07-01T13:08:09.662082101+05:30} + _(resource.pods.infraids).must_equal %w{a218dfc58fa28e0c58c55e508e5b57084876b42e894b98073c69c45dea06cbb2} + _(resource.pods.names).must_equal %w{cranky_allen} + _(resource.pods.namespaces).must_equal [""] + _(resource.pods.networks).must_equal [["podman"]] + _(resource.pods.status).must_equal %w{Running} + _(resource.pods.labels).must_equal [{}] + end + + it "returns false if pod with specific id does not exist" do + _(resource.pods.where(id: "979453ff4b40fe486a5b734b46bb7bf28f52fa31426bf23be068c8e7b19e58d9b8deb").exists?).must_equal false + end + + it "checks podman info parsing" do + _(resource.info.host.os).must_equal "linux" + _(resource.info.version.Version).must_equal "4.1.0" + end + + it "checks podman version parsing" do + _(resource.version.Server.Version).must_equal "4.1.0" + _(resource.version.Client.Version).must_equal "4.1.0" + end + + it "prints as Podman volumes plural resource" do + _(resource.volumes.to_s).must_equal "Podman Volumes" + end + + it "prints the resource id of Podman volumes plural resource" do + _(resource.volumes.resource_id).must_equal "Podman Volumes" + end + + it "returns parsed details of podman volumes" do + _(resource.volumes.names).must_equal %w{ae6be9ba838b9b150de47657229bb9b67142dbdb3d1ddbc5efa245cf1e95536a} + _(resource.volumes.drivers).must_equal %w{local} + _(resource.volumes.mountpoints).must_equal %w{/var/home/core/.local/share/containers/storage/volumes/ae6be9ba838b9b150de47657229bb9b67142dbdb3d1ddbc5efa245cf1e95536a/_data} + _(resource.volumes.createdat).must_equal %w{2022-07-02T12:40:37.012062614+05:30} + _(resource.volumes.labels).must_equal [{}] + _(resource.volumes.scopes).must_equal %w{local} + _(resource.volumes.options).must_equal [{}] + _(resource.volumes.mountcount).must_equal [0] + _(resource.volumes.needscopyup).must_equal [true] + _(resource.volumes.needschown).must_equal [true] + end + + it "returns false if volume with specific name does not exist" do + _(resource.volumes.where(name: "6bb7bf28f52fa31426bf23be068c8e7b19e58d9b8deb").exists?).must_equal false + end + + it "check podman object parsing" do + _(resource.object("591270d8d80d").Id).must_equal "591270d8d80d26671fd6ed622f367fbe19004d16e3b519c292313feb5f22e7f7" + _(resource.object("591270d8d80d").Path).must_equal "/docker-entrypoint.sh" + end +end diff --git a/test/unit/resources/podman_volume_test.rb b/test/unit/resources/podman_volume_test.rb new file mode 100644 index 000000000..92cf5ea90 --- /dev/null +++ b/test/unit/resources/podman_volume_test.rb @@ -0,0 +1,40 @@ +require "inspec/globals" +require "#{Inspec.src_root}/test/helper" +require_relative "../../../lib/inspec/resources/podman_volume" + +describe Inspec::Resources::PodmanVolume do + it "checks podman volume parameter and works correctly" do + resource = MockLoader.new("unix".to_sym).load_resource("podman_volume", "my_volume") + _(resource.exist?).must_equal true + _(resource.name).must_equal "my_volume" + _(resource.driver).must_equal "local" + _(resource.mountpoint).must_equal "/var/home/core/.local/share/containers/storage/volumes/my_volume/_data" + _(resource.created_at).must_equal "2022-07-14T13:21:19.965421792+05:30" + _(resource.labels).must_equal({}) + _(resource.scope).must_equal "local" + _(resource.options).must_equal({}) + _(resource.mount_count).must_equal 0 + _(resource.needs_copy_up).must_equal true + _(resource.needs_chown).must_equal true + _(resource.resource_id).must_equal "my_volume" + _(resource.to_s).must_equal "podman_volume my_volume" + end + + it "checks for a non-existing podman volume" do + resource = MockLoader.new("unix".to_sym).load_resource("podman_volume", "non_existing_volume") + _(resource.exist?).must_equal false + assert_nil resource.name + assert_nil resource.driver + assert_nil resource.mountpoint + assert_nil resource.created_at + assert_nil resource.labels + assert_nil resource.scope + assert_nil resource.options + assert_nil resource.mount_count + assert_nil resource.needs_copy_up + assert_nil resource.needs_chown + _(resource.resource_id).must_equal "non_existing_volume" + _(resource.to_s).must_equal "podman_volume non_existing_volume" + end +end +