Merge pull request #6559 from inspec/inspec-6-from-prime-2

Add chef-licensing, syncing from inspec-prime repo inspec-6 branch
This commit is contained in:
Clinton Wolfe 2023-07-07 17:13:26 -04:00 committed by GitHub
commit d64488f333
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
96 changed files with 1640 additions and 1171 deletions

View file

@ -0,0 +1,182 @@
# Documentation available at https://expeditor.chef.io/docs/getting-started/
---
product_key: inspec
rubygems:
- inspec
- inspec-core
- inspec-bin:
gemspec_path: ./inspec-bin/
- inspec-core-bin:
gemspec_path: ./inspec-bin/
pipelines:
- habitat/build:
env:
- HAB_NONINTERACTIVE: "true"
- HAB_NOCOLORING: "true"
- HAB_STUDIO_SECRET_HAB_NONINTERACTIVE: "true"
- docker/build
- omnibus/release:
env:
# The git cache is corrupt more often than not. This always purges the cache.
# https://chefio.atlassian.net/wiki/spaces/RELENGKB/pages/2204336129/Resolving+git+cache+build+errors+in+Omnibus
- EXPIRE_CACHE: 1
- IGNORE_ARTIFACTORY_RUBY_PROXY: true # Artifactory is throwing 500's when downloading some gems like ffi.
- omnibus/adhoc:
definition: .expeditor/release.omnibus.yml
env:
- ADHOC: true
- EXPIRE_CACHE: 1
- verify:
description: Pull Request validation tests
public: true
env:
- LANG: "C.UTF-8"
- SLOW: 1
- NO_AWS: 1
- MT_CPU: 5
- coverage:
description: Unit test coverage
# Private due to use of tokens
trigger: pull_request
env:
- LANG: "C.UTF-8"
- SLOW: 1
- NO_AWS: 1
- MT_CPU: 5
# This has been disabled because it regularly hits Docker API rate limits and fails
# - integration/resources:
# description: Test core resources with test-kitchen.
# definition: .expeditor/integration.resources.yml
# trigger: pull_request
- artifact/habitat:
description: Execute tests against the habitat artifact
definition: .expeditor/artifact.habitat.yml
env:
- HAB_NONINTERACTIVE: "true"
- HAB_NOCOLORING: "true"
- HAB_STUDIO_SECRET_HAB_NONINTERACTIVE: "true"
trigger: pull_request
schedules:
- name: integration_schedule
description: Periodic Integration Testing
cronline: "0 8 * * *"
slack:
notify_channel: inspec-notify
github:
delete_branch_on_merge: true
version_tag_format: v{{version}}
minor_bump_labels:
- "Expeditor: Bump Minor Version"
# allow bumping the major release via label
major_bump_labels:
- "Expeditor: Bump Major Version"
release_branches:
- inspec-6:
version_constraint: 6.*
- main:
version_constraint: 5.*
- inspec-4:
version_constraint: 4.*
changelog:
categories:
- "Type: New Resource": "New Resources"
- "Type: New Feature": "New Features"
- "Type: Enhancement": "Enhancements"
- "Type: Bug": "Bug Fixes"
subscriptions:
- workload: pull_request_merged:{{github_repo}}:{{release_branch}}:*
actions:
- built_in:bump_version:
ignore_labels:
- "Expeditor: Skip All"
- "Expeditor: Skip Version Bump"
only_if_modified:
- .expeditor/*
- docs-chef-io/*
- etc/*
- habitat/*
- inspec-bin/*
- lib/*
- omnibus/*
- support/*
- tasks/*
- test/*
- Gemfile*
- LICENSE
- "*.gemspec"
- "*.md"
- bash:.expeditor/update_version.sh:
only_if: built_in:bump_version
- built_in:update_changelog:
ignore_labels:
- "Expeditor: Skip All"
- "Expeditor: Skip Changelog"
- trigger_pipeline:omnibus/adhoc:
not_if: built_in:bump_version
ignore_labels:
- "Expeditor: Skip Omnibus"
- "Expeditor: Skip All"
- trigger_pipeline:artifact/habitat:
only_if: built_in:bump_version
ignore_labels:
- "Expeditor: Skip Habitat"
- "Expeditor: Skip All"
- trigger_pipeline:omnibus/release:
only_if: built_in:bump_version
ignore_labels:
- "Expeditor: Skip Omnibus"
- "Expeditor: Skip All"
- trigger_pipeline:habitat/build:
only_if: built_in:bump_version
ignore_labels:
- "Expeditor: Skip Habitat"
- "Expeditor: Skip All"
- built_in:build_gem:
only_if:
- built_in:bump_version
- workload: artifact_published:unstable:inspec:{{version_constraint}}
actions:
- trigger_pipeline:docker/build
- bash:.expeditor/buildkite/wwwrelease.sh:
post_commit: true
- workload: artifact_published:current:inspec:{{version_constraint}}
actions:
- built_in:promote_docker_images
- built_in:promote_habitat_packages
- workload: project_promoted:{{agent_id}}:*
actions:
- built_in:promote_artifactory_artifact
- workload: artifact_published:stable:inspec:{{version_constraint}}
actions:
- bash:.expeditor/update_dockerfile.sh
- built_in:rollover_changelog
- built_in:publish_rubygems
- built_in:create_github_release
- built_in:promote_docker_images
- built_in:promote_habitat_packages
- bash:.expeditor/publish-release-notes.sh:
post_commit: true
- purge_packages_chef_io_fastly:{{target_channel}}/inspec/latest:
post_commit: true
- bash:.expeditor/announce-release.sh:
post_commit: true
- built_in:notify_chefio_slack_channels
- workload: pull_request_opened:{{github_repo}}:{{release_branch}}:*
actions:
- post_github_comment:.expeditor/templates/pull_request.mustache:
ignore_team_members:
- inspec/owners
- inspec/inspec-core-team
- built_in:github_auto_assign_author:
only_if_team_member:
- inspec/owners
- inspec/inspec-core-team

View file

@ -9,26 +9,20 @@ expeditor:
steps:
- label: lint-ruby-3.0
- label: lint-ruby-3.1
command:
- RAKE_TASK=test:lint /workdir/.expeditor/buildkite/verify.sh
expeditor:
secrets: true
executor:
docker:
image: ruby:3.0
- label: run-tests-ruby-2.7
command:
- /workdir/.expeditor/buildkite/verify.sh
expeditor:
executor:
docker:
image: ruby:2.7
image: ruby:3.1
- label: run-tests-ruby-3.0
command:
- /workdir/.expeditor/buildkite/verify.sh
expeditor:
secrets: true
executor:
docker:
image: ruby:3.0
@ -37,6 +31,7 @@ steps:
command:
- /workdir/.expeditor/buildkite/verify.sh
expeditor:
secrets: true
executor:
docker:
image: ruby:3.1
@ -45,6 +40,7 @@ steps:
command:
- RAKE_TASK=test:isolated /workdir/.expeditor/buildkite/verify.sh
expeditor:
secrets: true
executor:
docker:
image: ruby:3.0
@ -53,6 +49,7 @@ steps:
command:
- RAKE_TASK=test:isolated /workdir/.expeditor/buildkite/verify.sh
expeditor:
secrets: true
executor:
docker:
image: ruby:3.1
@ -61,6 +58,7 @@ steps:
command:
- /workdir/.expeditor/buildkite/verify.ps1
expeditor:
secrets: true
executor:
docker:
environment:

View file

@ -1,21 +1,61 @@
# Change Log
<!-- usage documentation: http://expeditor-docs.es.chef.io/configuration/changelog/ -->
<!-- latest_release unreleased -->
## Unreleased
<!-- latest_release 6.4.33 -->
## [v6.4.33](https://github.com/inspec/inspec-prime/tree/v6.4.33) (2023-07-07)
#### Merged Pull Requests
- inspec-6 CI - Add secrets: true to private verify pipeline, delete ruby 2.7 config [#6558](https://github.com/inspec/inspec/pull/6558) ([clintoncwolfe](https://github.com/clintoncwolfe))
- forcing private in the configuration file [#6556](https://github.com/inspec/inspec/pull/6556) ([sean-simmons-progress](https://github.com/sean-simmons-progress))
- Adds test for licensing_config [#57](https://github.com/inspec/inspec-prime/pull/57) ([Vasu1105](https://github.com/Vasu1105))
<!-- latest_release -->
<!-- release_rollup since=5.18.14 -->
### Changes since 5.18.14 release
#### Merged Pull Requests
- inspec-6 CI - Add secrets: true to private verify pipeline, delete ruby 2.7 config [#6558](https://github.com/inspec/inspec/pull/6558) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.46 -->
- forcing private in the configuration file [#6556](https://github.com/inspec/inspec/pull/6556) ([sean-simmons-progress](https://github.com/sean-simmons-progress)) <!-- 6.2.46 -->
- Privatize verify pipeline [#6555](https://github.com/inspec/inspec/pull/6555) ([sean-simmons-progress](https://github.com/sean-simmons-progress)) <!-- 6.2.46 -->
- Prep CI system for inspec-prime merge [#6553](https://github.com/inspec/inspec/pull/6553) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.45 -->
- inspec-6 CI - Add secrets: true to private verify pipeline, delete ruby 2.7 config [#6558](https://github.com/inspec/inspec/pull/6558) ([clintoncwolfe](https://github.com/clintoncwolfe))
- forcing private in the configuration file [#6556](https://github.com/inspec/inspec/pull/6556) ([sean-simmons-progress](https://github.com/sean-simmons-progress))
- Adds test for licensing_config [#57](https://github.com/inspec/inspec-prime/pull/57) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.4.33 -->
- Configure to use `Inspec::Log` in Chef Licensing [#67](https://github.com/inspec/inspec-prime/pull/67) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.32 -->
- Crossport public 6549: Drop testing on EOL ruby 2.7, and run linter on Ruby 3.1 [#76](https://github.com/inspec/inspec-prime/pull/76) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.31 -->
- Case correction of product name in licensing config [#78](https://github.com/inspec/inspec-prime/pull/78) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.30 -->
- Foreport - Add postgres support for custom port with a socket connection [#40](https://github.com/inspec/inspec-prime/pull/40) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.29 -->
- Bump omnibus-software from `88169e3` to `4b08f0b` in /omnibus [#73](https://github.com/inspec/inspec-prime/pull/73) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.28 -->
- CHEF-3759 Crossport public 6540 Fix for inspec parallel on windows crashing due to error log rename [#74](https://github.com/inspec/inspec-prime/pull/74) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.27 -->
- Foreports #6526 and #6541: Update Docker base image to be ubuntu 22.04 [#64](https://github.com/inspec/inspec-prime/pull/64) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.26 -->
- Crossport Public 6545 Fix for InSpec Parallel hangs on certain CIS profiles [#71](https://github.com/inspec/inspec-prime/pull/71) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.26 -->
- Bump omnibus from `15122f2` to `9c0643a` in /omnibus [#70](https://github.com/inspec/inspec-prime/pull/70) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.25 -->
- Bump berkshelf from 8.0.2 to 8.0.7 in /omnibus [#63](https://github.com/inspec/inspec-prime/pull/63) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.24 -->
- Foreport #6523: Update RSpec to 3.12 [#65](https://github.com/inspec/inspec-prime/pull/65) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.23 -->
- Bump omnibus from `cf97613` to `15122f2` in /omnibus [#62](https://github.com/inspec/inspec-prime/pull/62) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.22 -->
- Bump omnibus-software from `225e357` to `88169e3` in /omnibus [#61](https://github.com/inspec/inspec-prime/pull/61) ([dependabot[bot]](https://github.com/dependabot[bot])) <!-- 6.4.21 -->
- CHEF-3704 Modify help for local licensing service mode and other distros [#59](https://github.com/inspec/inspec-prime/pull/59) ([Nik08](https://github.com/Nik08)) <!-- 6.4.20 -->
- restrict license commands only to inspec distro [#58](https://github.com/inspec/inspec-prime/pull/58) ([sathish-progress](https://github.com/sathish-progress)) <!-- 6.4.19 -->
- CHEF-3184 Error handling for inspec license add command - disabled in local mode [#52](https://github.com/inspec/inspec-prime/pull/52) ([Nik08](https://github.com/Nik08)) <!-- 6.4.18 -->
- CHEF-3403: Default server URL to production value [#50](https://github.com/inspec/inspec-prime/pull/50) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.17 -->
- CHEF-3186: Remove fetching of bearer auth token from vault [#48](https://github.com/inspec/inspec-prime/pull/48) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.16 -->
- CHEF 83 Revert attestations changes [#47](https://github.com/inspec/inspec-prime/pull/47) ([sathish-progress](https://github.com/sathish-progress)) <!-- 6.4.15 -->
- Foreports 6489 (CHEF-1458 Multiple values changes in SimpleConfig library) [#28](https://github.com/inspec/inspec-prime/pull/28) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.14 -->
- Foreport - Add nftables resources (#6499) [#44](https://github.com/inspec/inspec-prime/pull/44) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.13 -->
- Foreport - Update host resource to resolve all ipaddresses (#6481) [#39](https://github.com/inspec/inspec-prime/pull/39) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.12 -->
- Foreport - Bump rack from 2.2.6.2 to 2.2.6.4 in /omnibus (#6490) [#42](https://github.com/inspec/inspec-prime/pull/42) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.11 -->
- Foreport - fix: ensure Invoke-WebRequest headers can be configured (#6484) [#41](https://github.com/inspec/inspec-prime/pull/41) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.10 -->
- Foreport - CHEF-2438 Add train-kubernetes to inspec gemspec (#6512) [#43](https://github.com/inspec/inspec-prime/pull/43) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.9 -->
- Foreport - Clarify key_rsa docs regarding SSH keys (#6507) [#45](https://github.com/inspec/inspec-prime/pull/45) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.8 -->
- CHEF-3105 Fix windows openssl issue [#37](https://github.com/inspec/inspec-prime/pull/37) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.4.7 -->
- CHEF-2743: Set chef executable name to display in help messages of chef-licensing [#34](https://github.com/inspec/inspec-prime/pull/34) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.6 -->
- CHEF-2994: Add license command to list of allowed commands [#35](https://github.com/inspec/inspec-prime/pull/35) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.5 -->
- CHEF-1957: Update chef-licesing api call `license_keys` to `fetch_and_persist` [#30](https://github.com/inspec/inspec-prime/pull/30) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.4 -->
- Remove kitchen group from Gemfile [#31](https://github.com/inspec/inspec-prime/pull/31) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.3 -->
- CHEF-52: Add licensing information to help output [#27](https://github.com/inspec/inspec-prime/pull/27) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.2 -->
- Add command to list license information [#10](https://github.com/inspec/inspec-prime/pull/10) ([ahasunos](https://github.com/ahasunos)) <!-- 6.4.1 -->
- Licensing - Integrates Software Entitlement [#13](https://github.com/inspec/inspec-prime/pull/13) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.4.0 -->
- Integration of chef licensing with inspec [#12](https://github.com/inspec/inspec-prime/pull/12) ([Nik08](https://github.com/Nik08)) <!-- 6.3.0 -->
- CI - Use License Key and API Key Secrets from Vault [#26](https://github.com/inspec/inspec-prime/pull/26) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.49 -->
- Update Gemfile to add artifactory as source for chef-licensing gem dependency [#25](https://github.com/inspec/inspec-prime/pull/25) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.48 -->
- testing version bump and pipeline creation [#16](https://github.com/inspec/inspec-prime/pull/16) ([sean-simmons-progress](https://github.com/sean-simmons-progress)) <!-- 6.2.47 -->
- CHEF-1267 Add omnibus release and adhoc pipelines [#15](https://github.com/inspec/inspec-prime/pull/15) ([clintoncwolfe](https://github.com/clintoncwolfe)) <!-- 6.2.46 -->
- testing version bump [#9](https://github.com/inspec/inspec-prime/pull/9) ([sean-simmons-progress](https://github.com/sean-simmons-progress)) <!-- 6.2.45 -->
- Forport 6388 [#6477](https://github.com/inspec/inspec/pull/6477) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.44 -->
- Foreport 6360 [#6476](https://github.com/inspec/inspec/pull/6476) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.43 -->
- Foreport-6423 [#6474](https://github.com/inspec/inspec/pull/6474) ([Vasu1105](https://github.com/Vasu1105)) <!-- 6.2.42 -->
@ -5171,4 +5211,4 @@
- make default rake tasks test+lint [\#108](https://github.com/chef/inspec/pull/108) ([arlimus](https://github.com/arlimus))
- Improve unit tests [\#106](https://github.com/chef/inspec/pull/106) ([chris-rock](https://github.com/chris-rock))
- add to\_s methods to resources, fixes \#98 [\#105](https://github.com/chef/inspec/pull/105) ([chris-rock](https://github.com/chris-rock))
- 0.7.0 release [\#104](https://github.com/chef/inspec/pull/104) ([chris-rock](https://github.com/chris-rock))
- 0.7.0 release [\#104](https://github.com/chef/inspec/pull/104) ([chris-rock](https://github.com/chris-rock))

View file

@ -1,4 +1,4 @@
FROM ubuntu:18.04
FROM --platform=linux/amd64 ubuntu:22.04
LABEL maintainer="Chef Software, Inc. <docker@chef.io>"
ARG VERSION=5.18.14

17
Gemfile
View file

@ -48,19 +48,6 @@ group :deploy do
gem "inquirer"
end
group :kitchen do
gem "berkshelf"
# Chef 18 requires ruby 3
if Gem.ruby_version >= Gem::Version.new("3.0.0")
gem "chef", ">= 17.0"
else
# Ruby 2.7 presumably - TODO remove this when 2.7 is sunsetted
gem "chef", "~> 16.0"
end
gem "test-kitchen", ">= 2.8"
gem "kitchen-inspec", ">= 2.0"
gem "kitchen-dokken", ">= 2.11"
gem "git"
source "https://artifactory-internal.ps.chef.co/artifactory/api/gems/omnibus-gems-local/" do
gem "chef-licensing"
end

View file

@ -1,5 +1,5 @@
# Chef InSpec: Inspect Your Infrastructure
* **Project State: Active**
* **Issues Response SLA: 14 business days**
* **Pull Request Response SLA: 14 business days**

View file

@ -1 +1 @@
6.2.46
6.4.33

View file

@ -1,82 +0,0 @@
# 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`, `CSV`, or `JSON`.
#### YAML and JSON
An array of Hashes.
#### CSV
CSV 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.

View file

@ -1,179 +0,0 @@
+++
title = "Attestations"
draft = false
gh_repo = "inspec"
[menu]
[menu.inspec]
title = "Attestations"
identifier = "inspec/reference/attestations.md Attestations"
parent = "inspec/reference"
weight = 140
+++
Attestations is a mechanism to mark the `Not Reviewed (N/R)` tests as `passed` or `failed` manually using an attestations file.
## Example
A fire alarm needs to be audited, but it cannot be reviewed (N/R) through automation. Hence, to audit the fire alarm using an InSpec profile, the outcome of its working must be marked as `passed` or `failed` in a test through manual intervention. By using attestations and passing the status using an attestations file, we can audit the fire alarm.
### Attestations File to an audit fire alarm
```yaml
fire-alarm-1:
expiration_date: 2090-10-1
status: passed
justification: "Fire alarm 1 was tested manually and it works."
fire-alarm-2:
expiration_date: 2090-10-1
status: failed
justification: "Fire alarm 2 was tested manually and it does not work."
```
### InSpec Test
```ruby
control "fire-alarm-1" do
only_if("Fire alarm 1 needs to be tested manually") {
false
}
end
control "fire-alarm-2" do
only_if("Fire alarm 2 needs to be tested manually") {
false
}
end
```
### Running attestations to an audit fire alarm
```bash
inspec exec path/to/fire-alarm-audit-profile --attestation-file attestation.yaml
Profile: InSpec Profile (attestation)
Version: 0.1.0
Target: local://
Target ID: fa3923b9-f806-4cc2-960d-1ddefb4c7654
✔ fire-alarm-1: No-op (1 skipped)
↺ Skipped control due to only_if condition: Fire alarm 1 needs to be tested manually
✔ Control Attested : Fire alarm 1 was tested manually and it works.
× fire-alarm-2: No-op (1 failed) (1 skipped)
↺ Skipped control due to only_if condition: Fire alarm 2 needs to be tested manually
× Control Attested : Fire alarm 2 was tested manually and it does not work.
Profile Summary: 1 successful control, 1 control failure, 0 controls not reviewed, 0 controls not applicable, 0 controls have error
Test Summary: 1 successful, 1 failure, 2 skipped
```
## Attestations Fields
An attestations file identifies:
1. the controls need to be attested.
1. an explanation of why it is manually attested.
1. control status `passed` or `failed` to attest controls.
1. (optional) an URL pointing to a website containing information on control attestation.
1. (optional) an expiration date of attestation.
## Usage
To use attestations, you must have a correctly formatted attestations file and
invoke `inspec exec` with `--attestation-file [path]`.
```bash
inspec exec path/to/profile --attestation-file attestation.yaml
```
## File Format
Attestations files support YAML, JSON, and CSV formats.
```yaml
control_id:
expiration_date: YYYY-MM-DD
status: passed
justification: "reason for attesting this control"
evidence_url: "URL pointing to a website containing information on control attestation"
```
OR
```json
{
"control_id": {
"expiration_date": "YYYY-MM-DD",
"status": "passed",
"justification": "reason for attesting this control",
"evidence_url": "URL pointing to a website containing information on control attestation"
}
}
```
- `status` is mandatory. If absent, the control will not be attested. It can only be `passed` or `failed`.
- `expiration_date` sets the day the attestations file expires in **YYYY-MM-DD** format. Attestations files expire at 00:00 at the local time of the system on the specified date. Attestations files without expiration date are permanent. `expiration_date` is optional.
- `justification` is a text containing the reason why attestations is required. It might as well as include information on who initiated the attestation. If it is absent, it shows a warning message to include justification in the attestations file.
- `evidence_url` is an URL of a website containing information on control attestation. It is optional.
### File Format Examples
#### Example in YAML
```yaml
example-3.0.1:
justification: "Passed by the auditor manually"
evidence_url: "https://www.attestation-info-chef-example/"
expiration_date: 2050-06-01
status: passed
example-3.0.2:
justification: "Failed by the auditor manually"
evidence_url: "https://www.attestation-info-chef-example/"
expiration_date: 2050-07-01
status: failed
```
#### Example in JSON
```json
{
"example-3.0.1": {
"justification": "Passed by the auditor manually",
"evidence_url": "https://www.attestation-info-chef-example/",
"expiration_date": "2050-06-01",
"status": "passed"
},
"example-3.0.2": {
"justification": "Failed by the auditor manually",
"evidence_url": "https://www.attestation-info-chef-example/",
"expiration_date": "2050-07-01",
"status": "failed"
}
}
```
#### Example in CSV
These file formats support the following fields in a file:
- `control_id`
_Required_.
- `justification`
_Required_.
- `status`
_Required_.
- `evidence_url`
_Optional_.
- `expiration_date`
_Optional_.
![Attestations File Example](/images/inspec/attestations_file_excel.png)
{{< note >}}
How is the Attestations mechanism different than Waivers?
The waivers mechanism skips the controls for various reasons which are required for waiving. Whereas attestations mark the skipped controls which are not reviewed as `passed` or `failed` using the status passed through the attestations file by the auditor.
{{< /note >}}

View file

@ -13,7 +13,7 @@ platform = "os"
Use the `key_rsa` Chef InSpec audit resource to test RSA public/private keypairs.
This resource is mainly useful when used in conjunction with the x509_certificate resource but it can also be used for checking SSH keys.
This resource is mainly useful when used in conjunction with the x509_certificate resource, but it can also be used for checking RSA-based SSH keys.
## Availability

View file

@ -0,0 +1,140 @@
+++
title = "nftables resource"
draft = false
gh_repo = "inspec"
platform = "linux"
[menu]
[menu.inspec]
title = "nftables"
identifier = "inspec/resources/os/nftables.md nftables resource"
parent = "inspec/resources/os"
+++
Use the `nftables` Chef InSpec audit resource to test rules and sets that are defined using `nftables`, which maintains tables of IP packet filtering rules. There may be more than one table. Each table contains one (or more) chains. A chain is a list of rules that match packets. When a rule matches a packet, the rule defines what target to assign to the packet.
## Availability
### Installation
{{% inspec/inspec_installation %}}
### Version
This resource first became available in v5.21.30 of InSpec.
## Syntax
A `nftables` resource block declares tests for rules in IP tables:
```ruby
describe nftables(family:'name', table:'name', chain: 'name') do
its('PROPERTY') { should eq 'value' }
it { should have_rule('RULE') }
end
describe nftables(family:'name', table:'name', set: 'name') do
its('PROPERTY') { should eq 'value' }
it { should have_element('ELEMENT') }
end
```
where
- `nftables()` has to specify `family` and `table`. It also has to specify one of `chain` or `set` (exclusively).
- `family:'name'` is the name of the `family` the table belongs to, one of `ip`, `ip6`, `inet`, `arp`, `bridge`, `netdev`.
- `table:'name'` is the packet matching table against which the test is run.
- `chain: 'name'` is the name of a user-defined chain.
- `set: 'name'` is the name of a user-defined named set.
- `have_rule('RULE')` tests that the chain has a given rule in the nftables ruleset. This must match the entire line taken from `nftables -nn list chain FAMILY TABLE CHAIN`.
- `have_element('ELEMENT')` tests that element is a member of the nftables named set.
See the [NFT man page](https://www.netfilter.org/projects/nftables/manpage.html) and [nftables wiki](https://wiki.nftables.org/wiki-nftables/index.php/Main_Page) for more information about nftables.
## Properties
### Chain Properties
`hook`
: The hook type. Possible values: `ingress`, `prerouting`, `forward`, `input`, `output`, `postrouting`, and `egress`.
`prio`
: The numerical chain priority.
`policy`
: The policy type. Possible values: `accept`, `drop`.
`type`
: The chain type. Possible values: `filter`, `nat`, and `route`.
### Set Properties
`flags`
: The set flags. Possible values: `constant`, `dynamic`, `interval`, and `timeout`.
`size`
: The maximum number of elements in the set.
`type`
: The data type of set elements. Possible values: `ipv4_addr`, `ipv6_addr`, `ether_addr`, `inet_proto`, `inet_service`, and `mark`.
## Examples
The following examples show how to use this Chef InSpec audit resource.
### Test if the `CHAIN_NAME` chain from the `TABLE_NAME` table has the default `accept` policy
```ruby
describe nftables(family: 'inet', table: 'TABLE_NAME', chain: 'CHAIN_NAME') do
its('policy') { should eq 'accept' }
end
```
### Test the attributes of the `CHAIN_NAME` chain from the `TABLE_NAME` table
```ruby
describe nftables(family: 'inet', table: 'mangle', chain: 'INPUT') do
its('type') { should eq 'filter' }
its('hook') { should eq 'input' }
its('prio') { should eq (-150) } # mangle
its('policy') { should eq 'accept' }
end
```
### Test if there is a rule allowing Postgres (5432/TCP) traffic
```ruby
describe nftables(family: 'inet', table: 'TABLE_NAME', chain: 'CHAIN_NAME') do
it { should have_rule('tcp dport 5432 comment "postgres" accept') }
end
```
Note that the rule specification must exactly match what's in the output of `nftables -nn list chain inet TABLE_NAME CHAIN_NAME`, which will depend on how you've built your rules.
### Test if there is an element `1.1.1.1` in the `SET_NAME` named set
```ruby
describe nftables(family: 'inet', table: 'TABLE_NAME', set: 'SET_NAME') do
it { should have_element('1.1.1.1') }
end
```
## Matchers
For a full list of available matchers, please visit our [matchers page](/inspec/matchers/).
### have_rule
The `have_rule` matcher tests the named rule against the information in the `nftables` ruleset:
```ruby
it { should have_rule('RULE') }
```
### have_element
The `have_element` matcher tests the named set against the information in the `nftables` ruleset:
```ruby
it { should have_element('SET_ELEMENT') }
```

View file

@ -11,6 +11,7 @@ gh_repo = "inspec"
weight = 140
+++
Waivers allow you to waive controls and to dictate the running and/or reporting of those controls. A waiver file identifies:
1. which controls are waived

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,6 +1,6 @@
KsCa3OgmGAdfLvm0JOqzILc2SUL3FKTQ+XCpaFLiiwefhhKirl+ddcpj10g7
rtufpHQal6qoQ8PoCP4BBLOtrShyXYhOBqoQXLNiMYQAEdWTobOIz6naicE/
z23mweizu3C4qS4yG5hz4BwNhWSTVFrkhQZaF6KS5mMBHrULCV3i7Fs0BFWy
lhO3NS9GXaD+1RSKcTsUWTuNlK2R0TWqHgWiDDy+P80XFW3DvBjPweRyghJa
sW+5fwMYGyZPULt8lx8U8Ec05XQiIxeneosRGtdvjIh7JzhJr/UXJsIMhJvV
eNWfNJrWJkzzeeSXVV3E/VeYBhZGkI5ra5guf05Ifw==
wNEzKHmtSf1pIdciEC6DOs5SlOs3IbW1psVFLlmZc0NbnHe6MEahAnKWmHUP
9YrDv2JMQo1I8MM/cez8XDxpK4O5y4HT66RqoAlfBkg82LmYC7f1Cy34ByCj
LBZg5o/IVBGnY+Ksbhtp0mQYEyU048FnXIfh9uOfbKahU8HkPJssTkIw3fjL
Vrd5GQ4ssfW1XXFaxx7DjxWlPmWBVhd8c1Y2RlACZyI+w1DQNYimrWvgiFym
0VbnndiSX+2x84AZHE9AmsebcAYk9QlqO1N0VeYqBZj45FXLtpsNwYo0amDa
D/wyKGxRQLUYXyd2tDVJMWbeHPHy8UIK17RoSctrEg==

View file

@ -82,8 +82,6 @@
description: Use JUnit2 reporter.
inspec-reporter-html2:
description: Use HTML reporter.
inspec-attestations:
description: Use attestations mechanism with one or more attestations files.
inspec-reporter-progress-bar:
description: Use progress bar streaming reporter
inspec-reporter-child-status:

View file

@ -1,5 +1,5 @@
# This file managed by automation - do not edit manually
module InspecBin
INSPECBIN_ROOT = File.expand_path("..", __dir__)
VERSION = "6.2.46".freeze
VERSION = "6.4.33".freeze
end

View file

@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "thor", ">= 0.20", "< 2.0"
spec.add_dependency "method_source", ">= 0.8", "< 2.0"
spec.add_dependency "rubyzip", ">= 1.2.2", "< 3.0"
spec.add_dependency "rspec", ">= 3.9", "<= 3.11"
spec.add_dependency "rspec", ">= 3.9", "<= 3.12"
spec.add_dependency "rspec-its", "~> 1.2"
spec.add_dependency "pry", "~> 0.13"
spec.add_dependency "hashie", ">= 3.4", "< 5.0"

View file

@ -38,9 +38,11 @@ Gem::Specification.new do |spec|
spec.add_dependency "faraday_middleware", ">= 0.12.2", "< 1.1"
# Train plugins we ship with InSpec
spec.add_dependency "train-habitat", "~> 0.1"
spec.add_dependency "train-aws", "~> 0.2"
spec.add_dependency "train-winrm", "~> 0.2"
spec.add_dependency "train-habitat", "~> 0.1"
spec.add_dependency "train-aws", "~> 0.2"
spec.add_dependency "train-winrm", "~> 0.2"
spec.add_dependency "train-kubernetes", "~> 0.1"
spec.add_dependency "mongo", "= 2.13.2" # 2.14 introduces a broken symlink in mongo-2.14.0/spec/support/ocsp
end

View file

@ -1,7 +1,7 @@
---
driver:
name: dokken
chef_version: :latest
chef_version: 17
privileged: true # because Docker and SystemD/Upstart
transport:
@ -64,7 +64,7 @@ platforms:
- name: opensuse-leap
driver:
image: dokken/opensuse-leap-15
pid_one_command: /bin/systemd
pid_one_command: /usr/lib/systemd/systemd
- name: ubuntu-16.04
driver:

View file

@ -4,6 +4,7 @@ libdir = File.dirname(__FILE__)
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
require "inspec/version"
require "inspec/utils/licensing_config"
require "inspec/exceptions"
require "inspec/utils/deprecation"
require "inspec/profile"
@ -30,4 +31,4 @@ require "inspec/source_reader"
require "inspec/resource"
require "inspec/dependency_loader"
require "inspec/dependency_installer"
require "inspec/dependency_installer"

View file

@ -1,50 +0,0 @@
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 AttestationFileReader < WaiverFileReader
# Invoked from rule.rb and streaming reporter base class to fetch attestation data
def self.fetch_attestation_by_profile(profile_id, files)
read_attestation_from_file(profile_id, files) if @attestation_data.nil? || @attestation_data[profile_id].nil?
@attestation_data[profile_id]
end
def self.read_attestation_from_file(profile_id, files)
@attestation_data ||= {}
output = {}
files.each do |file_path|
data = read_from_file(file_path)
output.merge!(data) if !data.nil? && data.is_a?(Hash)
if data.nil?
raise Inspec::Exceptions::AttestationFileNotReadable,
"Cannot find parser for attestation file '#{file_path}'. " \
"Check to make sure file has the appropriate extension."
end
end
@attestation_data[profile_id] = output
end
# Attestation file has different headers than waiver file
# Overriding header validation logic of WaiverFileReader
def self.validate_headers(headers, json_yaml = false)
required_fields = json_yaml ? %w{status} : %w{control_id status}
missing_cols = (required_fields - headers)
missing_cols << "justification" if (!headers.include? "justification") && (!headers.include? "explanation")
Inspec::Log.warn "Missing column headers: #{missing_cols}" unless missing_cols.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
# defining all fields used in attestation files of different formats
def self.all_fields
%w{control_id justification expiration_date evidence_url status explanation frequency updated}
end
end
end

View file

@ -1,155 +0,0 @@
module Inspec
module Attestations
# Invoked from reporters base classes & run_data.rb to modify run data
def self.attest(run_data)
run_data[:profiles].each do |profile|
profile[:controls].each do |control|
# logic for attestation applied for N/R controls here.
if control[:status] == "not_reviewed" && !control[:attestation_data].empty?
expiry = determine_expiry(control[:attestation_data], control[:id])
# if expiration date parsing was successful
if expiry
control[:attestation_data]["message"] = validate_attestation_expiry(expiry, control[:id])
attestation_result = attestation_check(control[:attestation_data]["message"], control[:attestation_data], control[:id])
if attestation_result
status, attestation_msg = attestation_result
control[:status] = status # N/R status -> to passed/failed based on attestation logic
# replicated test result hash to invoke pass/fail test
control[:results].push({
status: control[:status],
code_desc: attestation_msg,
expectation_message: attestation_msg,
})
end
end
end
end
end
end
# Invoked from streaming reporter base class
def self.attest_streaming_data(attestation_data, status, control_id)
# logic check for N/R controls here for streaming reporters
if status == "not_reviewed" && !attestation_data.blank?
expiry = determine_expiry(attestation_data, control_id)
# if expiration date parsing was successful
if expiry
expiry_message = validate_attestation_expiry(expiry, control_id)
attestation_check(expiry_message, attestation_data, control_id)
end
end
end
def self.validate_attestation_expiry(expiry, control_id)
# logic to check for expiry
if [Date, Time].include?(expiry.class) || (expiry.is_a?(String) && Time.parse(expiry).year != 0)
expiry = expiry.to_time if expiry.is_a? Date
expiry = Time.parse(expiry) if expiry.is_a? String
if expiry < Time.now # If the attestation expired, return - no attestation done
expiry_message = "Attestation expired on #{expiry}"
expiry_message
end
else
ui = Inspec::UI.new
ui.error("Unable to parse attestation expiration date '#{expiry}' for control #{control_id}")
ui.exit(:usage_error)
end
rescue => e
ui = Inspec::UI.new
ui.error("Unable to parse attestation expiration date '#{expiry}' for control #{control_id}. Error: #{e.message}")
ui.exit(:usage_error)
end
def self.attestation_check(expiry_message, attestation_data, control_id)
# logic to update enhanced outcome status
status, msg = nil
if %w{passed failed}.include? attestation_data["status"]
if expiry_message
status = "failed"
msg = "Control not attested : #{expiry_message}"
else
# use justification and evidence url to show information in msg
attestation_message = attestation_data["justification"] || attestation_data["explanation"] || ""
unless attestation_data["evidence_url"].blank?
if attestation_message.blank?
attestation_message = "Evidence URL: #{attestation_data["evidence_url"]}"
else
attestation_message += " | Evidence URL: #{attestation_data["evidence_url"]}"
end
end
status = attestation_data["status"]
if attestation_message.blank?
msg = "Control Attested : No justification provided."
else
msg = "Control Attested : #{attestation_message}"
end
end
else
if attestation_data["status"].blank?
Inspec::Log.warn "No attestation status for control #{control_id}. Use 'passed' or 'failed'."
else
Inspec::Log.warn "Invalid attestation status '#{attestation_data["status"]}' for control #{control_id}. Use 'passed' or 'failed'."
end
return nil
end
[status, msg]
end
def self.determine_expiry(attestation_data, control_id)
if attestation_data["expiration_date"]
attestation_data["expiration_date"]
elsif !attestation_data["updated"].blank? && !attestation_data["frequency"].blank?
begin
calculate_expiry(attestation_data["updated"], attestation_data["frequency"], control_id)
rescue => e
ui = Inspec::UI.new
ui.error("Unable to parse attestation updated date '#{attestation_data["updated"]}' for control #{control_id}. Error: #{e.message}")
ui.exit(:usage_error)
end
end
end
def self.calculate_expiry(updated_date, frequency, control_id)
# logic to find expiration date using frequency and updated date.
if updated_date.is_a?(Date) || (updated_date.is_a?(String) && Date.parse(updated_date).year != 0)
updated_date = Date.parse(updated_date) if updated_date.is_a? String
if updated_date < Time.now.to_date
case frequency
when "annually"
updated_date.to_date.next_year(1)
when "semiannually"
updated_date.next_month(6)
when "quarterly"
updated_date.next_month(3)
when "monthly"
updated_date.next_month(1)
when "every2weeks"
updated_date.next_day(14)
when "weekly"
updated_date.next_day(7)
when "every3days"
updated_date.next_day(3)
when "daily"
updated_date.next_day(1)
else
Inspec::Log.warn "Invalid frequency value '#{frequency}' for control #{control_id}."
updated_date
end
else
updated_date
end
else
ui = Inspec::UI.new
ui.error("Unable to parse attestation updated date '#{updated_date}' for control #{control_id}")
ui.exit(:usage_error)
end
end
end
end

View file

@ -1,9 +1,11 @@
require "thor" # rubocop:disable Chef/Ruby/UnlessDefinedRequire
require "chef-licensing"
require "inspec/log"
require "inspec/ui"
require "inspec/config"
require "inspec/dist"
require "inspec/utils/deprecation/global_method"
require "inspec/utils/licensing_config"
# Allow end of options during array type parsing
# https://github.com/erikhuda/thor/issues/631
@ -30,11 +32,34 @@ module Inspec
end
def self.start(given_args = ARGV, config = {})
check_license! if config[:enforce_license] || config[:enforce_license].nil?
if Inspec::Dist::EXEC_NAME == "inspec"
check_license! if config[:enforce_license] || config[:enforce_license].nil?
fetch_and_persist_license
end
super(given_args, config)
end
def self.fetch_and_persist_license
allowed_commands = ["-h", "--help", "help", "-v", "--version", "version", "license"]
begin
if (allowed_commands & ARGV.map(&:downcase)).empty? && !ARGV.empty?
license_keys = ChefLicensing.fetch_and_persist
# Only if EULA acceptance or license key args are present. And licenses are successfully persisted, do clean exit.
if ARGV.select { |arg| !(arg.include? "--chef-license") }.empty? && !license_keys.blank?
Inspec::UI.new.exit
end
end
rescue ChefLicensing::LicenseKeyFetcher::LicenseKeyNotFetchedError
Inspec::Log.error "#{Inspec::Dist::PRODUCT_NAME} cannot execute without valid licenses."
Inspec::UI.new.exit(:usage_error)
rescue ChefLicensing::Error => e
Inspec::Log.error e.message
Inspec::UI.new.exit(:usage_error)
end
end
# EULA acceptance
def self.check_license!
allowed_commands = ["-h", "--help", "help", "-v", "--version", "version"]
@ -48,9 +73,6 @@ module Inspec
Inspec::VERSION,
logger: Inspec::Log
)
if license_acceptor_output && ARGV.count == 1 && (ARGV.first.include? "--chef-license")
Inspec::UI.new.exit
end
license_acceptor_output
end
rescue LicenseAcceptance::LicenseNotAcceptedError
@ -177,8 +199,6 @@ module Inspec
desc: "Load one or more input files, a YAML file with values for the profile to use."
option :waiver_file, type: :array,
desc: "Load one or more waiver files."
option :attestation_file, type: :array,
desc: "Load one or more attestation files."
option :attrs, type: :array,
desc: "Legacy name for --input-file - deprecated."
option :create_lockfile, type: :boolean,
@ -213,6 +233,30 @@ module Inspec
def self.help(*args)
super(*args)
if Inspec::Dist::EXEC_NAME == "inspec"
puts <<~CHEF_LICENSE_HELP
Chef Compliance has three tiers of licensing:
* Free-Tier
Users are limited to audit maximum of 10 targets
Entitled for personal or non-commercial use
* Trial
Entitled for unlimited number of targets
Entitled for 30 days only
Entitled for commercial use
* Commercial
Entitled for purchased number of targets
Entitled for period of subscription purchased
Entitled for commercial use
inspec license add: This command helps users to generate or add an additional license (not applicable to local licensing service)
For more information please visit:
www.chef.io/licensing/faqs
CHEF_LICENSE_HELP
end
puts "\nAbout #{Inspec::Dist::PRODUCT_NAME}:"
puts " Patents: chef.io/patents\n\n"
end

View file

@ -62,6 +62,11 @@ class Inspec::InspecCLI < Inspec::BaseCLI
require "license_acceptance/cli_flags/thor"
include LicenseAcceptance::CLIFlags::Thor
if Inspec::Dist::EXEC_NAME == "inspec"
require "chef-licensing/cli_flags/thor"
include ChefLicensing::CLIFlags::Thor
end
desc "json PATH", "read all tests in the PATH and generate a JSON summary."
option :output, aliases: :o, type: :string,
desc: "Save the created profile to a path."

View file

@ -12,7 +12,5 @@ module Inspec
class ProfileSigningKeyNotFound < ArgumentError; end
class WaiversFileNotReadable < ArgumentError; end
class WaiversFileDoesNotExist < ArgumentError; end
class AttestationFileNotReadable < ArgumentError; end
class AttestationFileDoesNotExist < ArgumentError; end
end
end

View file

@ -236,7 +236,6 @@ module Inspec::Formatters
resource_title: example.metadata[:described_class] || example.metadata[:example_group][:description],
expectation_message: format_expectation_message(example),
waiver_data: example.metadata[:waiver_data],
attestation_data: example.metadata[:attestation_data],
# This enforces the resource name as expected based off of the class
# name. However, if we wanted the `name` attribute against the class
# to be canonical for this case (consider edge cases!) we would use
@ -359,7 +358,6 @@ module Inspec::Formatters
# (that is, per-describe-block) basis, because that is the only granularity
# available to us in the RSpec report data structure which we use as a vehicle.
control[:waiver_data] ||= example[:waiver_data] || {}
control[:attestation_data] ||= example[:attestation_data] || {}
end
end
end

View file

@ -1,4 +1,3 @@
require "inspec/attestations"
module Inspec::Plugin::V2::PluginType
class StreamingReporter < Inspec::Plugin::V2::PluginBase
register_plugin_type(:streaming_reporter)
@ -14,7 +13,6 @@ module Inspec::Plugin::V2::PluginType
@controls_count = nil
@notifications = {}
@enhanced_outcome_control_wise = {}
@attestation_message_control_wise = {}
end
private
@ -37,7 +35,7 @@ module Inspec::Plugin::V2::PluginType
unless @control_checks_count_map[control_id].nil?
@control_checks_count_map[control_id] -= 1
control_ended = @control_checks_count_map[control_id] == 0
# after a control has ended it checks for certain operations, like enhanced outcomes & attestations
# after a control has ended it checks for certain operations, like enhanced outcomes
run_control_operations(notification, control_id) if control_ended
control_ended
else
@ -47,7 +45,6 @@ module Inspec::Plugin::V2::PluginType
def run_control_operations(notification, control_id)
check_for_enhanced_outcomes(notification, control_id)
check_for_attestation(notification, control_id)
end
def check_for_enhanced_outcomes(notification, control_id)
@ -57,25 +54,12 @@ module Inspec::Plugin::V2::PluginType
end
end
def check_for_attestation(notification, control_id)
control_outcome = control_outcome(control_id)
if control_outcome
attestation_result = attest_control(notification, control_id, control_outcome)
unless attestation_result.blank?
@enhanced_outcome_control_wise[control_id] = attestation_result[0]
@attestation_message_control_wise[control_id] = attestation_result[1]
end
end
end
def format_message(indicator, control_id, title, full_description)
message_to_format = ""
message_to_format += "#{indicator} "
message_to_format += "#{control_id.to_s.strip.dup.force_encoding(Encoding::UTF_8)} "
message_to_format += "#{title.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " if title
message_to_format += "#{full_description.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " unless title
# append attestation message if control is attested
message_to_format += "#{@attestation_message_control_wise[control_id].gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " if @attestation_message_control_wise[control_id]
message_to_format
end
@ -147,23 +131,5 @@ module Inspec::Plugin::V2::PluginType
@notifications[control_id].push([notification, status])
end
end
def attest_control(notification, control_id, control_outcome)
status = control_outcome
attestation_data = read_attestation_file(notification, control_id)
Inspec::Attestations.attest_streaming_data(attestation_data, status, control_id) unless attestation_data.blank?
end
def read_attestation_file(notification, control_id)
# need to re-read the file from config since not using run data for streaming reporters.
profile_id = notification.example.metadata[:profile_id]
attestation_files = Inspec::Config.cached.final_options["attestation_file"] if Inspec::Config.cached.respond_to?(:final_options)
attestation_data_by_profile = Inspec::AttestationFileReader.fetch_attestation_by_profile(profile_id, attestation_files) unless attestation_files.nil?
return unless attestation_data_by_profile && attestation_data_by_profile[control_id] && attestation_data_by_profile[control_id].is_a?(Hash)
attestation_data_by_profile[control_id]
end
end
end

View file

@ -1,5 +1,4 @@
require_relative "../utils/run_data_filters"
require "inspec/attestations"
module Inspec::Reporters
class Base
@ -13,8 +12,6 @@ module Inspec::Reporters
@run_data = config[:run_data] || {}
apply_run_data_filters_to_hash
# only try for attestation when attestation file is passed
Inspec::Attestations.attest(@run_data) if Inspec::Config.cached[:attestation_file]
@output = ""
end

View file

@ -73,6 +73,7 @@ require "inspec/resources/mssql_sys_conf"
require "inspec/resources/mysql"
require "inspec/resources/mysql_conf"
require "inspec/resources/mysql_session"
require "inspec/resources/nftables"
require "inspec/resources/nginx"
require "inspec/resources/nginx_conf"
require "inspec/resources/npm"

View file

@ -319,15 +319,9 @@ module Inspec::Resources
return nil
end
resolve_ipv4 = resolve_ipv4.inject(:merge) if resolve_ipv4.is_a?(Array)
# Append the ipv4 addresses
resolve_ipv4.each_value do |ip|
matched = ip.to_s.chomp.match(Resolv::IPv4::Regex)
next if matched.nil? || addresses.include?(matched.to_s)
addresses << matched.to_s
end
resolve_ipv4 = [resolve_ipv4] unless resolve_ipv4.is_a?(Array)
resolve_ipv4.each { |entry| addresses << entry["IPAddress"] }
# -Type AAAA is the DNS query for IPv6 server Address.
cmd = inspec.command("Resolve-DnsName Type AAAA #{hostname} | ConvertTo-Json")
@ -337,15 +331,9 @@ module Inspec::Resources
return nil
end
resolve_ipv6 = resolve_ipv6.inject(:merge) if resolve_ipv6.is_a?(Array)
# Append the ipv6 addresses
resolve_ipv6.each_value do |ip|
matched = ip.to_s.chomp.match(Resolv::IPv6::Regex)
next if matched.nil? || addresses.include?(matched.to_s)
addresses << matched.to_s
end
resolve_ipv6 = [resolve_ipv6] unless resolve_ipv6.is_a?(Array)
resolve_ipv6.each { |entry| addresses << entry["IPAddress"] }
addresses
end

View file

@ -304,11 +304,11 @@ module Inspec::Resources
# Insecure not supported simply https://stackoverflow.com/questions/11696944/powershell-v3-invoke-webrequest-https-error
cmd << "-MaximumRedirection #{max_redirects}" unless max_redirects.nil?
request_headers["Authorization"] = """ '\"Basic ' + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(\"#{username}:#{password}\")) +'\"' """ unless username.nil? || password.nil?
request_header_string = nil
request_header_array = []
request_headers.each do |k, v|
request_header_string << " #{k} = #{v}"
request_header_array << " '#{k}' = '#{v}'"
end
cmd << "-Headers @{#{request_header_string.join(";")}}" unless request_header_string.nil?
cmd << "-Headers @{#{request_header_array.join(";")}}" unless request_header_array.empty?
if params.nil?
cmd << "'#{url}'"
else

View file

@ -0,0 +1,251 @@
require "inspec/resources/command"
require "json" unless defined?(JSON)
# @see https://wiki.nftables.org/
# @see https://www.netfilter.org/projects/nftables/manpage.html
# rubocop:disable Style/ClassVars
module Inspec::Resources
class NfTables < Inspec.resource(1)
name "nftables"
supports platform: "linux"
desc "Use the nftables InSpec audit resource to test rules and sets that are defined in nftables, which maintains tables of IP packet filtering rules. There may be more than one table. Each table contains one (or more) chains. A chain is a list of rules that match packets. When the rule matches, the rule defines what target to assign to the packet."
example <<~EXAMPLE
describe nftables(family:'inet', table:'filter', chain: 'INPUT') do
its('type') { should eq 'filter' }
its('hook') { should eq 'input' }
its('prio') { should eq 0 } # filter
its('policy') { should eq 'drop' }
it { should have_rule('tcp dport { 22, 80, 443 } accept') }
end
describe nftables(family: 'inet', table: 'filter', set: 'OPEN_PORTS') do
its('type') { should eq 'ipv4_addr . inet_proto . inet_service' }
its('flags') { should include 'interval' }
it { should have_element('1.1.1.1 . tcp . 25-27') }
end
EXAMPLE
@@bin = nil
@@nft_params = {}
@@nft_params["json"] = ""
@@nft_params["stateless"] = ""
@@nft_params["num"] = ""
def initialize(params = {})
@family = params[:family] || nil
@table = params[:table] || nil
@chain = params[:chain] || nil
@set = params[:set] || nil
@ignore_comments = params[:ignore_comments] || false
unless @@bin
@@bin = find_nftables_or_error
end
# Some old versions of `nft` do not support JSON output or stateless modifier
res = inspec.command("#{@@bin} --version").stdout
version = Gem::Version.new(/^nftables v(\S+) .*/.match(res)[1])
case
when version < Gem::Version.new("0.8.0")
@@nft_params["num"] = "-nn"
when version < Gem::Version.new("0.9.0")
@@nft_params["stateless"] = "-s"
@@nft_params["num"] = "-nn"
when version < Gem::Version.new("0.9.3")
@@nft_params["json"] = "-j"
@@nft_params["stateless"] = "-s"
@@nft_params["num"] = "-nn"
when version >= Gem::Version.new("0.9.3")
@@nft_params["json"] = "-j"
@@nft_params["stateless"] = "-s"
@@nft_params["num"] = "-y"
## --terse
end
# family and table attributes are mandatory
fail_resource "nftables family and table are mandatory." if @family.nil? || @family.empty? || @table.nil? || @table.empty?
# chain name or set name has to be specified and are mutually exclusive
fail_resource "You must specify either a chain or a set name." if (@chain.nil? || @chain.empty?) && (@set.nil? || @set.empty?)
fail_resource "You must specify either a chain or a set name, not both." if !(@chain.nil? || @chain.empty?) && !(@set.nil? || @set.empty?)
# we're done if we are on linux
return if inspec.os.linux?
# ensures, all calls are aborted for non-supported os
@nftables_cache = {}
skip_resource "The `nftables` resource is not supported on your OS yet."
end
# Let's have a generic method to retrieve attributes for chains and sets
def _get_attr(name)
# Some attributes are valid for chains only, for sets only or for both
valid = {
"chains" => %w{hook policy prio type},
"sets" => %w{flags size type},
}
target_obj = @set.nil? ? "chains" : "sets"
if valid[target_obj].include?(name)
attrs = @set.nil? ? retrieve_chain_attrs : retrieve_set_attrs
else
raise Inspec::Exceptions::ResourceSkipped, "`#{name}` attribute is not valid for #{target_obj}"
end
# flags attribute is an array, if not retrieved ensure we return an empty array
# otherwise return an empty string
default = name == "flags" ? [] : ""
val = attrs.key?(name) ? attrs[name] : default
# When set type is has multiple data types it's retrieved as an array, make humans life easier
# by returning a string representation
if name == "type" && target_obj == "sets" && val.is_a?(Array)
return val.join(" . ")
end
val
end
# Create a method for each attribute
%i{flags hook policy prio size type}.each do |attr_method|
define_method attr_method do
_get_attr(attr_method.to_s)
end
end
def has_rule?(rule = nil, _family = nil, _table = nil, _chain = nil)
# checks if the rule is part of the chain
# for now, we expect an exact match
retrieve_chain_rules.any? { |line| line.casecmp(rule) == 0 }
end
def has_element?(element = nil, _family = nil, _table = nil, _chain = nil)
# checks if the element is part of the set
# for now, we expect an exact match
retrieve_set_elements.any? { |line| line.casecmp(element) == 0 }
end
def retrieve_set_elements
idx = "set_#{@family}_#{@table}_#{@set}"
return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
@nftables_cache = {} unless defined?(@nftables_cache)
elem_cmd = "list set #{@family} #{@table} #{@set}"
nftables_cmd = format("%s %s %s", @@bin, @@nft_params["stateless"], elem_cmd).strip
cmd = inspec.command(nftables_cmd)
return [] if cmd.exit_status.to_i != 0
@nftables_cache[idx] = cmd.stdout.gsub("\t", "").split("\n").reject { |line| line =~ /^(table|set|type|size|flags|typeof|auto-merge)/ || line =~ /^}$/ }.map { |line| line.sub("elements = {", "").sub("}", "").split(",") }.flatten.map(&:strip)
end
def retrieve_chain_rules
idx = "rule_#{@family}_#{@table}_#{@chain}"
return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
@nftables_cache = {} unless defined?(@nftables_cache)
# construct nftables command to read all rules of the given chain
chain_cmd = "list chain #{@family} #{@table} #{@chain}"
nftables_cmd = format("%s %s %s %s", @@bin, @@nft_params["stateless"], @@nft_params["num"], chain_cmd).strip
cmd = inspec.command(nftables_cmd)
return [] if cmd.exit_status.to_i != 0
rules = cmd.stdout.gsub("\t", "").split("\n").reject { |line| line =~ /^(table|chain)/ || line =~ /^}$/ }
if @ignore_comments
# split rules, returns array or rules without any comment
@nftables_cache[idx] = remove_comments_from_rules(rules)
else
# split rules, returns array or rules
@nftables_cache[idx] = rules.map(&:strip)
end
end
def retrieve_chain_attrs
idx = "chain_attrs_#{@family}_#{@table}_#{@chain}"
return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
@nftables_cache = {} unless defined?(@nftables_cache)
chain_cmd = "list chain #{@family} #{@table} #{@chain}"
nftables_cmd = format("%s %s %s %s", @@bin, @@nft_params["stateless"], @@nft_params["json"], chain_cmd).strip
cmd = inspec.command(nftables_cmd)
return {} if cmd.exit_status.to_i != 0
if @@nft_params["json"].empty?
res = cmd.stdout.gsub("\t", "").split("\n").select { |line| line =~ /^type/ }[0]
parsed = /type (\S+) hook (\S+) priority (\S+); policy (\S+);/.match(res)
@nftables_cache[idx] = { "type" => parsed[1], "hook" => parsed[2], "prio" => parsed[3].to_i, "policy" => parsed[4] }
else
@nftables_cache[idx] = JSON.parse(cmd.stdout)["nftables"].select { |line| line.key?("chain") }[0]["chain"]
end
end
def retrieve_set_attrs
idx = "set_attrs_#{@family}_#{@table}_#{@chain}"
return @nftables_cache[idx] if defined?(@nftables_cache) && @nftables_cache.key?(idx)
@nftables_cache = {} unless defined?(@nftables_cache)
chain_cmd = "list set #{@family} #{@table} #{@set}"
nftables_cmd = format("%s %s %s %s", @@bin, @@nft_params["stateless"], @@nft_params["json"], chain_cmd).strip
cmd = inspec.command(nftables_cmd)
return {} if cmd.exit_status.to_i != 0
if @@nft_params["json"].empty?
type = ""
size = 0
flags = []
res = cmd.stdout.gsub("\t", "").split("\n").select { |line| line =~ /^(type|size|flags)/ }
res.each do |line|
parsed = /^type (.*)/.match(line)
if parsed
type = parsed[1]
end
parsed = /^flags (.*)/.match(line)
if parsed
flags = parsed[1].split(",")
end
parsed = /^size (.*)/.match(line)
if parsed
size = parsed[1].to_i
end
end
@nftables_cache[idx] = { "type" => type, "size" => size, "flags" => flags }
else
@nftables_cache[idx] = JSON.parse(cmd.stdout)["nftables"].select { |line| line.key?("set") }[0]["set"]
end
end
def resource_id
to_s || "nftables"
end
def to_s
format("nftables (%s %s %s %s)", @family && "family: #{@family}", @table && "table: #{@table}", @chain && "chain: #{@chain}", @set && "set: #{@set}").strip
end
private
def remove_comments_from_rules(rules)
rules.each do |rule|
next if rule.nil?
rule.gsub!(/ comment "([^"]*)"/, "")
rule.strip
end
rules
end
def find_nftables_or_error
%w{/usr/sbin/nft /sbin/nft nft}.each do |cmd|
return cmd if inspec.command(cmd).exist?
end
raise Inspec::Exceptions::ResourceFailed, "Could not find `nft`"
end
end
end

View file

@ -81,7 +81,8 @@ module Inspec::Resources
# Socket path and empty host in the connection string establishes socket connection
# Socket connection only enabled for non-windows platforms
# Windows does not support unix domain sockets
"psql -d postgresql://#{@user}:#{@pass}@/#{dbs}?host=#{@socket_path} -A -t -w -c #{escaped_query(query)}"
option_port = @port.nil? ? "" : "-p #{@port}" # add explicit port if specified
"psql -d postgresql://#{@user}:#{@pass}@/#{dbs}?host=#{@socket_path} #{option_port} -A -t -w -c #{escaped_query(query)}"
else
# Host in connection string establishes tcp/ip connection
if inspec.os.windows?

View file

@ -9,7 +9,6 @@ require "inspec/resource"
require "inspec/resources/os"
require "inspec/input_registry"
require "inspec/waiver_file_reader"
require "inspec/attestation_file_reader"
require "inspec/utils/convert"
module Inspec
@ -17,7 +16,6 @@ module Inspec
include ::RSpec::Matchers
attr_reader :__waiver_data
attr_reader :__attestation_data
attr_accessor :resource_dsl, :na_impact_freeze
attr_reader :__profile_id
@ -53,7 +51,6 @@ module Inspec
# By applying waivers *after* the instance eval, we assure that
# waivers have higher precedence than only_if.
__apply_waivers
__add_attestation_data
rescue SystemStackError, StandardError => e
# We've encountered an exception while trying to eval the code inside the
@ -425,19 +422,6 @@ module Inspec
__waiver_data["skipped_due_to_waiver"] = true
end
# fetches attestation data for the rule which is used in runner_rspec.rb to assign it inside metadata
def __add_attestation_data
# this adds attestation data to a rule, accesible on run data layer.
control_id = @__rule_id
attestation_files = Inspec::Config.cached.final_options["attestation_file"] if Inspec::Config.cached.respond_to?(:final_options)
attestation_data_by_profile = Inspec::AttestationFileReader.fetch_attestation_by_profile(__profile_id, attestation_files) unless attestation_files.nil?
return unless attestation_data_by_profile && attestation_data_by_profile[control_id] && attestation_data_by_profile[control_id].is_a?(Hash)
@__attestation_data = attestation_data_by_profile[control_id]
end
#
# Takes a block and returns a block that will run the given block
# with access to the resource_dsl of the current class. This is to

View file

@ -29,9 +29,6 @@ module Inspec
def initialize(raw_run_data)
@raw_run_data = raw_run_data
# only try for attestation when attestation file is passed
Inspec::Attestations.attest(@raw_run_data) if Inspec::Config.cached[:attestation_file]
self.controls = @raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
self.profiles = @raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
self.statistics = Inspec::RunData::Statistics.new(@raw_run_data[:statistics])

View file

@ -13,8 +13,7 @@ module Inspec
:source_location, # Complex local
:tags, # Hash with custom keys
:title, # String
:waiver_data, # Complex local
:attestation_data # Complex local
:waiver_data # Complex local
) do
include HashLikeStruct
def initialize(raw_ctl_data)
@ -22,7 +21,6 @@ module Inspec
self.results = (raw_ctl_data[:results] || []).map { |r| Inspec::RunData::Result.new(r) }
self.source_location = Inspec::RunData::Control::SourceLocation.new(raw_ctl_data[:source_location] || {})
self.waiver_data = Inspec::RunData::Control::WaiverData.new(raw_ctl_data[:waiver_data] || {})
self.attestation_data = Inspec::RunData::Control::AttestationData.new(raw_ctl_data[:attestation_data] || {})
[
:code, # String
@ -86,26 +84,6 @@ module Inspec
}.each { |f| self[f] = raw_wv_data[f.to_s] }
end
end
AttestationData = Struct.new(
:expiration_date,
:justification,
:evidence_url,
:status,
:message
) do
include HashLikeStruct
def initialize(raw_attestation_data)
# These have string keys in the raw data!
%i{
expiration_date
justification
evidence_url
status
message
}.each { |f| self[f] = raw_attestation_data[f.to_s] }
end
end
end
end
end

View file

@ -11,6 +11,7 @@ require "inspec/dependencies/cache"
require "inspec/dist"
require "inspec/reporters"
require "inspec/runner_rspec"
require "chef-licensing"
# spec requirements
module Inspec
@ -69,16 +70,6 @@ module Inspec
}
end
if @conf[:attestation_file]
Inspec.with_feature("inspec-attestations") {
@conf[:attestation_file].each do |file|
unless File.file?(file)
raise Inspec::Exceptions::AttestationFileDoesNotExist, "Attestation file #{file} does not exist."
end
end
}
end
# About reading inputs:
# @conf gets passed around a lot, eventually to
# Inspec::InputRegistry.register_external_inputs.
@ -171,17 +162,23 @@ module Inspec
end
def run(with = nil)
ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
load
run_tests(with)
rescue ChefLicensing::SoftwareNotEntitled
Inspec::Log.error "License is not entitled to use InSpec."
Inspec::UI.new.exit(:license_not_entitled)
rescue ChefLicensing::Error => e
Inspec::Log.error e.message
Inspec::UI.new.exit(:usage_error)
end
def render_output(run_data)
return if @conf["reporter"].nil?
@conf["reporter"].each do |reporter|
# if attestation file is used then we need enhanced outcomes
enhanced_outcome_flag = @conf["attestation_file"] ? true : @conf["enhanced_outcomes"]
enhanced_outcome_flag = @conf["enhanced_outcomes"]
result = Inspec::Reporters.render(reporter, run_data, enhanced_outcome_flag)
raise Inspec::ReporterError, "Error generating reporter '#{reporter[0]}'" if result == false
end

View file

@ -199,8 +199,7 @@ module Inspec
RSpec.configuration.output_stream = $stdout
@formatter = RSpec.configuration.add_formatter(Inspec::Formatters::Base)
# if attestation file is used then we need enhanced outcomes
@formatter.enhanced_outcomes = @conf.final_options["attestation_file"] ? true : @conf.final_options["enhanced_outcomes"]
@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"]
@ -232,7 +231,6 @@ module Inspec
metadata[:code] = rule.instance_variable_get(:@__code)
metadata[:source_location] = rule.instance_variable_get(:@__source_location)
metadata[:waiver_data] = rule.__waiver_data
metadata[:attestation_data] = rule.__attestation_data # data fetched from rule object
end
end
end

View file

@ -1,3 +1,6 @@
require "chef-licensing"
require "inspec/dist"
autoload :Pry, "pry"
module Inspec
@ -10,6 +13,7 @@ module Inspec
end
def start
ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
# This will hold a single evaluation binding context as opened within
# the instance_eval context of the anonymous class that the profile
# context creates to evaluate each individual test file. We want to
@ -18,6 +22,12 @@ module Inspec
@ctx_binding = @runner.eval_with_virtual_profile("binding")
configure_pry
@ctx_binding.pry
rescue ChefLicensing::SoftwareNotEntitled
Inspec::Log.error "License is not entitled to use InSpec."
Inspec::UI.new.exit(:license_not_entitled)
rescue ChefLicensing::Error => e
Inspec::Log.error e.message
Inspec::UI.new.exit(:usage_error)
end
def configure_pry # rubocop:disable Metrics/AbcSize

View file

@ -33,6 +33,8 @@ module Inspec
EXIT_GEM_DEPENDENCY_LOAD_ERROR = 4
EXIT_BAD_SIGNATURE = 5
EXIT_LICENSE_NOT_ACCEPTED = 172
EXIT_LICENSE_NOT_ENTITLED = 173
EXIT_LICENSE_NOT_SET = 174
EXIT_FAILED_TESTS = 100
EXIT_SKIPPED_TESTS = 101
EXIT_TERMINATED_BY_CTL_C = 130

View file

@ -0,0 +1,9 @@
require_relative "../log"
require "chef-licensing"
ChefLicensing.configure do |config|
config.chef_product_name = "InSpec"
config.chef_entitlement_id = "3ff52c37-e41f-4f6c-ad4d-365192205968"
config.chef_executable_name = "inspec"
config.license_server_url = "https://licensing.chef.co/License"
config.logger = Inspec::Log
end

View file

@ -57,11 +57,18 @@ class SimpleConfig
m = opts[:assignment_regex].match(line)
return nil if m.nil?
values = parse_values(m, opts[:key_values])
if opts[:multiple_values]
@vals[m[1]] ||= []
@vals[m[1]].push(parse_values(m, opts[:key_values]))
if opts[:multiple_value_regex] # can be used only if multiple values is set as true
value_to_array = values.split(opts[:multiple_value_regex])
@vals[m[1]].concat(value_to_array)
else
@vals[m[1]].push(values)
end
else
@vals[m[1]] = parse_values(m, opts[:key_values])
@vals[m[1]] = values
end
end
@ -116,6 +123,7 @@ class SimpleConfig
key_values: 1, # default for key=value, may require for 'key val1 val2 val3'
standalone_comments: false,
multiple_values: false,
multiple_value_regex: nil,
}
end
end

View file

@ -1,3 +1,3 @@
module Inspec
VERSION = "6.2.46".freeze
VERSION = "6.4.33".freeze
end

View file

@ -148,7 +148,7 @@ RSpec::Matchers.define :be_resolvable do
end
end
# matcher for iptables and ip6tables
# matcher for iptables, ip6tables and nftables
RSpec::Matchers.define :have_rule do |rule|
match do |tables|
tables.has_rule?(rule)
@ -163,6 +163,13 @@ RSpec::Matchers.define :have_rule do |rule|
end
end
# matcher for nftables sets
RSpec::Matchers.define :have_element do |elem|
match do |sets|
sets.has_element?(elem)
end
end
# `be_in` matcher
# You can use it in the following cases:
# - check if an item or array is included in a given array

View file

@ -0,0 +1,16 @@
# License Plugin
## license list
Implements the `inspec license list` CLI command.
## license add
Implements the `inspec license add` CLI command.
### What This Plugin Does
This plugin consists of the following subcommands:
1. `add`: helps to add a new license
2. `list`: helps to list all the licenses for the current user

View file

@ -0,0 +1,6 @@
Gem::Specification.new do |spec|
spec.name = "inspec-license"
spec.summary = "Plugin to list user licenses."
spec.description = ""
spec.license = "Apache-2.0"
end

View file

@ -0,0 +1,14 @@
module InspecPlugins
module License
class Plugin < ::Inspec.plugin(2)
plugin_name :"inspec-license"
if Inspec::Dist::EXEC_NAME == "inspec"
cli_command :license do
require_relative "inspec-license/cli"
InspecPlugins::License::CLI
end
end
end
end
end

View file

@ -0,0 +1,23 @@
require "chef-licensing"
module InspecPlugins::License
class CLI < Inspec.plugin(2, :cli_command)
include Inspec::Dist
subcommand_desc "license SUBCOMMAND [options]", "Manage #{PRODUCT_NAME} license"
desc "list", "List licenses (not applicable to local licensing service)"
def list
ChefLicensing.list_license_keys_info
end
desc "add", "Add a new license (not applicable to local licensing service)"
def add
ChefLicensing.add_license
rescue ChefLicensing::LicenseKeyFetcher::LicenseKeyAddNotAllowed => e
Inspec::Log.error e.message
Inspec::UI.new.exit(Inspec::UI::EXIT_LICENSE_NOT_SET)
rescue ChefLicensing::Error => e
Inspec::Log.error e.message
Inspec::UI.new.exit(Inspec::UI::EXIT_LICENSE_NOT_SET)
end
end
end

View file

@ -1,7 +1,7 @@
module InspecPlugins::Parallelism
class StreamingReporter < Inspec.plugin(2, :streaming_reporter)
# Registering these methods with RSpec::Core::Formatters class is mandatory
RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending
RSpec::Core::Formatters.register self, :example_passed, :example_failed, :example_pending, :close
def initialize(output)
@status_mapping = {}
@ -21,6 +21,11 @@ module InspecPlugins::Parallelism
set_example(notification, "skipped")
end
def close(notification)
# HACK: if we've reached the end of the execution, send a special marker, to ease EOF detection on Windows
puts "EOF_MARKER"
end
private
def set_example(notification, status)
@ -46,10 +51,6 @@ module InspecPlugins::Parallelism
display_name = control_id.to_s.lstrip.force_encoding(Encoding::UTF_8) unless title
puts "#{@control_counter}/#{stat}/#{controls_count}/#{display_name}"
# HACK: if we've reached the end of the execution, send a special marker, to ease EOF detection on Windows
if @control_counter == controls_count
puts "EOF_MARKER"
end
end
def set_status_mapping(control_id, status)

View file

@ -23,8 +23,9 @@ module InspecPlugins
def run
validate_thor_options
validate_invocations!
catch_ctl_c_and_exit unless run_in_background
Runner.new(invocations, cli_options_to_parallel_cmd, sub_cmd).run
runner = Runner.new(invocations, cli_options_to_parallel_cmd, sub_cmd)
catch_ctl_c_and_exit(runner) unless run_in_background
runner.run
end
def dry_run
@ -34,11 +35,17 @@ module InspecPlugins
private
def catch_ctl_c_and_exit
def catch_ctl_c_and_exit(runner)
puts "Press CTL+C to stop\n"
trap("SIGINT") do
puts "\n"
puts "Shutting down jobs..."
if Inspec.locally_windows?
runner.kill_child_processes
sleep 1
puts "Renaming error log files..."
runner.rename_error_log_files
end
exit Inspec::UI::EXIT_TERMINATED_BY_CTL_C
end
end

View file

@ -12,6 +12,7 @@ module InspecPlugins
@sub_cmd = sub_cmd
@total_jobs = cli_options["jobs"] || Concurrent.physical_processor_count
@child_tracker = {}
@child_tracker_persisted = {}
@run_in_background = cli_options["bg"]
unless run_in_background
@ui = InspecPlugins::Parallelism::SuperReporter.make(cli_options["ui"], total_jobs, invocations)
@ -34,6 +35,10 @@ module InspecPlugins
cleanup_child_processes
sleep 0.1
end
# Requires renaming operations on windows only
# Do Rename and delete operations after all child processes have exited successfully
rename_error_log_files if Inspec.locally_windows?
cleanup_empty_error_log_files
cleanup_daemon_process if run_in_background
end
@ -63,6 +68,36 @@ module InspecPlugins
end
end
def kill_child_processes
@child_tracker.each do |pid, info|
Process.kill("SIGKILL", pid)
rescue Exception => e
$stderr.puts "Error while shutting down process #{pid}: #{e.message}"
end
# Waiting for child processes to die after they have been killed
wait_for_child_processes_to_die
end
def wait_for_child_processes_to_die
until @child_tracker.empty?
begin
exited_pid = Process.waitpid(-1, Process::WNOHANG)
@child_tracker.delete exited_pid if exited_pid && exited_pid > 0
sleep 1
rescue Errno::ECHILD
Inspec::Log.info "Processes shutdown complete!"
rescue Exception => e
Inspec::Log.debug "Error while waiting for child processes to shutdown: #{e.message}"
end
end
end
def rename_error_log_files
@child_tracker_persisted.each do |pid, info|
rename_error_log(info[:error_log_file], pid)
end
end
def should_start_more_jobs?
@child_tracker.length < total_jobs && !invocations.empty?
end
@ -71,22 +106,25 @@ module InspecPlugins
invocation = invocations.shift[:value]
child_reader, parent_writer = IO.pipe
# Construct command-line invocation
child_pid = nil
error_log_file_name = "#{Time.now.nsec}.err"
begin
logs_dir_path = log_path || Dir.pwd
log_dir = File.join(logs_dir_path, "logs")
FileUtils.mkdir_p(log_dir)
error_log_file = File.open("#{log_dir}/#{Time.now.nsec}.err", "a+")
cmd = "#{$0} #{sub_cmd} #{invocation}"
log_msg = "#{Time.now.iso8601} Start Time: #{Time.now}\n#{Time.now.iso8601} Arguments: #{invocation}\n"
child_pid = Process.spawn(cmd, out: parent_writer, err: error_log_file_name)
# Rename error log file if exist
rename_error_log(error_log_file_name, child_pid) if File.exist?(error_log_file_name)
child_pid = Process.spawn(cmd, out: parent_writer, err: error_log_file.path)
# Logging
create_logs(child_pid, nil, $stderr)
create_logs(child_pid, log_msg)
@child_tracker[child_pid] = { io: child_reader }
# This is used to rename error log files after all child processes are exited
@child_tracker_persisted[child_pid] = { error_log_file: error_log_file }
@ui.child_spawned(child_pid, invocation)
# Close the file to unlock the error log files opened by processes
error_log_file.close
rescue StandardError => e
$stderr.puts "#{Time.now.iso8601} Error Message: #{e.message}"
$stderr.puts "#{Time.now.iso8601} Error Backtrace: #{e.backtrace}"
@ -198,7 +236,7 @@ module InspecPlugins
def create_logs(child_pid, run_log , stderr = nil)
logs_dir_path = log_path || Dir.pwd
log_dir = File.join(logs_dir_path, "logs")
FileUtils.mkdir_p(log_dir) unless File.directory?(log_dir)
FileUtils.mkdir_p(log_dir)
if stderr
log_file = File.join(log_dir, "#{child_pid}.err") unless File.exist?("#{child_pid}.err")
@ -209,11 +247,18 @@ module InspecPlugins
end
end
def rename_error_log(error_log_file_name, child_pid)
def rename_error_log(error_log_file, child_pid)
logs_dir_path = log_path || Dir.pwd
log_dir = File.join(logs_dir_path, "logs")
FileUtils.mkdir_p(log_dir) unless File.directory?(log_dir)
File.rename(error_log_file_name, "#{log_dir}/#{child_pid}.err")
FileUtils.mkdir_p(log_dir)
if error_log_file.closed? && File.exist?(error_log_file.path)
begin
File.rename("#{error_log_file.path}", "#{log_dir}/#{child_pid}.err")
rescue
$stderr.puts "Cannot rename error log file #{error_log_file.path} for child pid #{child_pid}"
end
end
end
end
end

View file

@ -89,7 +89,8 @@ module InspecPlugins::Parallelism
# Loop over slots
slots.each_index do |idx|
if slots[idx].nil?
line += "idle".center(slot_width)
# line += "idle".center(slot_width)
# Need to improve UI
elsif slots[idx] == "exited"
line += "Done".center(slot_width)
else

View file

@ -1,18 +1,17 @@
GIT
remote: https://github.com/chef/omnibus-software.git
revision: 225e3576c48fcd0155f6049cb032b2370eccf29a
revision: 4b08f0bc0688f750bc55a49b8103b2d12815399e
branch: main
specs:
omnibus-software (23.2.286)
omnibus-software (23.7.293)
omnibus (>= 9.0.0)
GIT
remote: https://github.com/chef/omnibus.git
revision: cf9761311577e24819625aa1d932f828740e33b4
revision: 9c0643a3a44f3e7119789c2093bfc8edd78c74ff
branch: main
specs:
omnibus (9.0.17)
omnibus (9.0.21)
aws-sdk-s3 (~> 1.116.0)
chef-cleanroom (~> 1.0)
chef-utils (>= 15.4)
@ -30,33 +29,33 @@ GIT
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.1)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
awesome_print (1.9.2)
aws-eventstream (1.2.0)
aws-partitions (1.716.0)
aws-sdk-core (3.170.0)
aws-partitions (1.783.0)
aws-sdk-core (3.176.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.62.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (1.68.0)
aws-sdk-core (~> 3, >= 3.176.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.116.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sdk-secretsmanager (1.64.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-secretsmanager (1.77.0)
aws-sdk-core (~> 3, >= 3.174.0)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.5.2)
aws-sigv4 (1.6.0)
aws-eventstream (~> 1, >= 1.0.2)
bcrypt_pbkdf (1.1.0)
bcrypt_pbkdf (1.1.0-x64-mingw32)
bcrypt_pbkdf (1.1.0-x86-mingw32)
berkshelf (8.0.2)
berkshelf (8.0.7)
chef (>= 15.7.32)
chef-config
cleanroom (~> 1.0)
@ -70,54 +69,57 @@ GEM
solve (~> 4.0)
thor (>= 0.20)
builder (3.2.4)
chef (17.10.0)
chef (18.2.7)
addressable
aws-sdk-s3 (~> 1.91)
aws-sdk-secretsmanager (~> 1.46)
chef-config (= 17.10.0)
chef-utils (= 17.10.0)
chef-config (= 18.2.7)
chef-utils (= 18.2.7)
chef-vault
chef-zero (>= 14.0.11)
corefoundation (~> 0.3.4)
diff-lcs (>= 1.2.4, < 1.6.0, != 1.4.0)
erubis (~> 2.7)
ffi (>= 1.5.0)
ffi (>= 1.15.5)
ffi-libarchive (~> 1.0, >= 1.0.3)
ffi-yajl (~> 2.2)
iniparse (~> 1.4)
inspec-core (~> 4.23)
inspec-core (>= 5)
license-acceptance (>= 1.0.5, < 3)
mixlib-archive (>= 0.4, < 2.0)
mixlib-authentication (>= 2.1, < 4)
mixlib-cli (>= 2.1.1, < 3.0)
mixlib-log (>= 2.0.3, < 4.0)
mixlib-shellout (>= 3.1.1, < 4.0)
net-sftp (>= 2.1.2, < 4.0)
ohai (~> 17.0)
net-ftp
net-sftp (>= 2.1.2, < 5.0)
ohai (~> 18.0)
plist (~> 3.2)
proxifier (~> 1.0)
proxifier2 (~> 1.1)
syslog-logger (~> 1.6)
train-core (~> 3.2, >= 3.2.28)
train-core (~> 3.10)
train-rest (>= 0.4.1)
train-winrm (>= 0.2.5)
unf_ext (>= 0.0.8.2)
uuidtools (>= 2.1.5, < 3.0)
vault (~> 0.16)
chef (17.10.0-universal-mingw32)
chef (18.2.7-x64-mingw-ucrt)
addressable
aws-sdk-s3 (~> 1.91)
aws-sdk-secretsmanager (~> 1.46)
chef-config (= 17.10.0)
chef-powershell (~> 1.0.12)
chef-utils (= 17.10.0)
chef-config (= 18.2.7)
chef-powershell (~> 18.0.0)
chef-utils (= 18.2.7)
chef-vault
chef-zero (>= 14.0.11)
corefoundation (~> 0.3.4)
diff-lcs (>= 1.2.4, < 1.6.0, != 1.4.0)
erubis (~> 2.7)
ffi (>= 1.5.0)
ffi (>= 1.15.5)
ffi-libarchive (~> 1.0, >= 1.0.3)
ffi-yajl (~> 2.2)
iniparse (~> 1.4)
inspec-core (~> 4.23)
inspec-core (>= 5)
iso8601 (>= 0.12.1, < 0.14)
license-acceptance (>= 1.0.5, < 3)
mixlib-archive (>= 0.4, < 2.0)
@ -125,17 +127,20 @@ GEM
mixlib-cli (>= 2.1.1, < 3.0)
mixlib-log (>= 2.0.3, < 4.0)
mixlib-shellout (>= 3.1.1, < 4.0)
net-sftp (>= 2.1.2, < 4.0)
ohai (~> 17.0)
net-ftp
net-sftp (>= 2.1.2, < 5.0)
ohai (~> 18.0)
plist (~> 3.2)
proxifier (~> 1.0)
proxifier2 (~> 1.1)
syslog-logger (~> 1.6)
train-core (~> 3.2, >= 3.2.28)
train-core (~> 3.10)
train-rest (>= 0.4.1)
train-winrm (>= 0.2.5)
unf_ext (>= 0.0.8.2)
uuidtools (>= 2.1.5, < 3.0)
vault (~> 0.16)
win32-api (~> 1.5.3)
win32-certstore (~> 0.6.2)
win32-api (~> 1.10.0)
win32-certstore (~> 0.6.15)
win32-event (~> 0.6.1)
win32-eventlog (= 0.6.3)
win32-mmap (~> 0.4.1)
@ -145,22 +150,22 @@ GEM
win32-taskscheduler (~> 2.0)
wmi-lite (~> 1.0)
chef-cleanroom (1.0.5)
chef-config (17.10.0)
chef-config (18.2.7)
addressable
chef-utils (= 17.10.0)
chef-utils (= 18.2.7)
fuzzyurl
mixlib-config (>= 2.2.12, < 4.0)
mixlib-shellout (>= 2.0, < 4.0)
tomlrb (~> 1.2)
chef-powershell (1.0.13)
chef-powershell (18.0.2)
ffi (~> 1.15)
ffi-yajl (~> 2.4)
chef-telemetry (1.1.1)
chef-config
concurrent-ruby (~> 1.0)
chef-utils (17.10.0)
chef-utils (18.2.7)
concurrent-ruby
chef-vault (4.1.10)
chef-vault (4.1.11)
chef-zero (15.0.11)
ffi-yajl (~> 2.2)
hashie (>= 2.0, < 5.0)
@ -175,25 +180,19 @@ GEM
contracts (0.16.1)
corefoundation (0.3.13)
ffi (>= 1.15.0)
date (3.3.3)
diff-lcs (1.5.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
ed25519 (1.3.0)
erubi (1.12.0)
erubis (2.7.0)
faraday (1.4.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
multipart-post (>= 1.2, < 3)
faraday (2.7.7)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday_middleware (1.2.0)
faraday (~> 1.0)
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-net_http (3.0.2)
ffi (1.15.5)
ffi (1.15.5-x64-mingw-ucrt)
ffi (1.15.5-x64-mingw32)
@ -211,13 +210,16 @@ GEM
builder (>= 2.1.2)
rexml (~> 3.0)
hashie (4.1.0)
http-accept (1.7.0)
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
iniparse (1.5.0)
inspec-core (4.56.20)
inspec-core (5.22.3)
addressable (~> 2.4)
chef-telemetry (~> 1.0, >= 1.0.8)
faraday (>= 0.9.0, < 1.5)
faraday_middleware (~> 1.0)
faraday (>= 1, < 3)
faraday-follow_redirects (~> 0.3)
hashie (>= 3.4, < 5.0)
license-acceptance (>= 0.2.13, < 3.0)
method_source (>= 0.8, < 2.0)
@ -233,7 +235,7 @@ GEM
sslshake (~> 1.2)
thor (>= 0.20, < 2.0)
tomlrb (>= 1.2, < 2.1)
train-core (~> 3.0)
train-core (~> 3.10)
tty-prompt (~> 0.17)
tty-table (~> 0.10)
iostruct (0.0.5)
@ -249,7 +251,7 @@ GEM
tomlrb (>= 1.2, < 3.0)
tty-box (~> 0.6)
tty-prompt (~> 0.20)
license_scout (1.3.4)
license_scout (1.3.6)
ffi-yajl (~> 2.2)
mixlib-shellout (>= 2.2, < 4.0)
toml-rb (>= 1, < 3)
@ -258,6 +260,9 @@ GEM
little-plugger (~> 1.1)
multi_json (~> 1.14)
method_source (1.0.0)
mime-types (3.4.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2023.0218.1)
minitar (0.9)
mixlib-archive (1.1.7)
mixlib-log
@ -288,20 +293,26 @@ GEM
molinillo (0.8.0)
multi_json (1.15.0)
multipart-post (2.3.0)
net-ftp (0.2.0)
net-protocol
time
net-protocol (0.2.1)
timeout
net-scp (4.0.0)
net-ssh (>= 2.6.5, < 8.0.0)
net-sftp (3.0.0)
net-ssh (>= 5.0.0, < 7.0.0)
net-ssh (6.1.0)
net-sftp (4.0.0)
net-ssh (>= 5.0.0, < 8.0.0)
net-ssh (7.1.0)
net-ssh-gateway (2.0.0)
net-ssh (>= 4.0.0)
netrc (0.11.0)
nori (2.6.0)
octokit (4.25.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
ohai (17.9.0)
chef-config (>= 14.12, < 18)
chef-utils (>= 16.0, < 18)
ohai (18.1.3)
chef-config (>= 14.12, < 19)
chef-utils (>= 16.0, < 19)
ffi (~> 1.9)
ffi-yajl (~> 2.2)
ipaddress
@ -312,24 +323,41 @@ GEM
plist (~> 3.1)
train-core
wmi-lite (~> 1.0)
parallel (1.22.1)
parallel (1.23.0)
parslet (1.8.2)
pastel (0.8.0)
tty-color (~> 0.5)
pedump (0.6.5)
pedump (0.6.6)
awesome_print
iostruct (>= 0.0.4)
multipart-post (>= 2.0.0)
rainbow
zhexdump (>= 0.0.2)
plist (3.7.0)
proxifier (1.0.3)
pry (0.14.1)
proxifier2 (1.1.0)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.1)
rack (2.2.6.2)
rack (2.2.7)
rainbow (3.1.1)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rest-client (2.1.0-x64-mingw32)
ffi (~> 1.9)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
rest-client (2.1.0-x86-mingw32)
ffi (~> 1.9)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0)
netrc (~> 0.8)
retryable (3.0.5)
rexml (3.2.5)
rspec (3.11.0)
@ -338,17 +366,17 @@ GEM
rspec-mocks (~> 3.11.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.0)
rspec-expectations (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-its (1.3.0)
rspec-core (>= 3.0.0)
rspec-expectations (>= 3.0.0)
rspec-mocks (3.11.1)
rspec-mocks (3.11.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-support (3.11.0)
ruby-progressbar (1.11.0)
rspec-support (3.11.1)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
rubyntlm (0.6.3)
rubyzip (2.3.2)
@ -381,17 +409,24 @@ GEM
winrm (~> 2.0)
winrm-elevated (~> 1.0)
winrm-fs (~> 1.1)
thor (1.2.1)
thor (1.2.2)
time (0.2.2)
date
timeout (0.4.0)
toml-rb (2.2.0)
citrus (~> 3.0, > 3.0)
tomlrb (1.3.0)
train-core (3.10.7)
train-core (3.10.8)
addressable (~> 2.5)
ffi (!= 1.13.0)
json (>= 1.8, < 3.0)
mixlib-shellout (>= 2.0, < 4.0)
net-scp (>= 1.2, < 5.0)
net-ssh (>= 2.9, < 8.0)
train-rest (0.5.0)
aws-sigv4 (~> 1.5)
rest-client (~> 2.1)
train-core (~> 3.0)
train-winrm (0.2.13)
winrm (>= 2.3.6, < 3.0)
winrm-elevated (~> 1.2.2)
@ -414,13 +449,16 @@ GEM
pastel (~> 0.8)
strings (~> 0.2.0)
tty-screen (~> 0.8)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.4.2)
unicode_utils (1.4.0)
uuidtools (2.2.0)
vault (0.17.0)
aws-sigv4
webrick (1.7.0)
win32-api (1.5.3-universal-mingw32)
webrick (1.8.1)
win32-api (1.10.1-universal-mingw32)
win32-certstore (0.6.15)
chef-powershell (>= 1.0.12)
ffi

View file

@ -53,6 +53,8 @@ dependency "shebang-cleanup"
# Ensure our SSL cert files are accessible to ruby.
dependency "openssl-customization"
dependency "ruby-msys2-devkit" if windows?
package :rpm do
signing_passphrase ENV["OMNIBUS_RPM_SIGNING_PASSPHRASE"]
compression_level 1

View file

@ -7,3 +7,5 @@ override "ruby", version: "3.1.2"
# Mac m1
override "openssl", version: "1.1.1m" if mac_os_x?
override "ruby-msys2-devkit", version: "3.1.2-1"

View file

@ -0,0 +1,442 @@
{
"Content": "<!doctype html>\n<html>\n<head>\n <title>Example Domain</title>\n\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <style type=\"text/css\">\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </style> \n</head>\n\n<body>\n<div>\n <h1>Example Domain</h1>\n <p>This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.</p>\n <p><a href=\"https://www.iana.org/domains/example\">More information...</a></p>\n</div>\n</body>\n</html>\n",
"ParsedHtml": {
"Script": {},
"all": [
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject"
],
"body": {},
"activeElement": null,
"images": [],
"applets": [],
"links": [
"System.__ComObject"
],
"forms": [],
"anchors": [],
"title": "Example Domain",
"scripts": [],
"designMode": "Inherit",
"selection": {},
"readyState": "complete",
"frames": {},
"embeds": [],
"plugins": [],
"alinkColor": "#0000ff",
"bgColor": "#f0f0f2",
"fgColor": "#000000",
"linkColor": "#0000ff",
"vlinkColor": "#800080",
"referrer": null,
"location": {},
"lastModified": "03/07/2021 23:17:45",
"url": "about:blank",
"domain": null,
"cookie": null,
"expando": true,
"charset": "unicode",
"defaultCharset": "windows-1252",
"mimeType": "Chrome HTML Document",
"fileSize": null,
"fileCreatedDate": null,
"fileModifiedDate": null,
"fileUpdatedDate": null,
"security": "This type of document does not have a security certificate.",
"protocol": "Unknown Protocol",
"nameProp": "Example Domain",
"onhelp": null,
"onclick": null,
"ondblclick": null,
"onkeyup": null,
"onkeydown": null,
"onkeypress": null,
"onmouseup": null,
"onmousedown": null,
"onmousemove": null,
"onmouseout": null,
"onmouseover": null,
"onreadystatechange": null,
"onafterupdate": null,
"onrowexit": null,
"onrowenter": null,
"ondragstart": null,
"onselectstart": null,
"parentWindow": {},
"styleSheets": [
"System.__ComObject"
],
"onbeforeupdate": null,
"onerrorupdate": null,
"documentElement": {},
"uniqueID": "ms__id1",
"onrowsdelete": null,
"onrowsinserted": null,
"oncellchange": null,
"ondatasetchanged": null,
"ondataavailable": null,
"ondatasetcomplete": null,
"onpropertychange": null,
"dir": null,
"oncontextmenu": null,
"onstop": null,
"parentDocument": null,
"enableDownload": null,
"baseUrl": null,
"inheritStyleSheets": null,
"onbeforeeditfocus": null,
"onselectionchange": null,
"namespaces": {},
"media": null,
"oncontrolselect": null,
"URLUnencoded": "about:blank",
"onmousewheel": null,
"doctype": null,
"implementation": {},
"onfocusin": null,
"onfocusout": null,
"onactivate": null,
"ondeactivate": null,
"onbeforeactivate": null,
"onbeforedeactivate": null,
"compatMode": "CSS1Compat",
"nodeType": 9,
"parentNode": null,
"childNodes": [
"System.__ComObject",
"System.__ComObject"
],
"attributes": null,
"nodeName": "#document",
"nodeValue": null,
"firstChild": {},
"lastChild": {},
"previousSibling": null,
"nextSibling": null,
"ownerDocument": null,
"IHTMLDocument2_Script": {},
"IHTMLDocument2_all": [
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject",
"System.__ComObject"
],
"IHTMLDocument2_body": {},
"IHTMLDocument2_activeElement": null,
"IHTMLDocument2_images": [],
"IHTMLDocument2_applets": [],
"IHTMLDocument2_links": [
"System.__ComObject"
],
"IHTMLDocument2_forms": [],
"IHTMLDocument2_anchors": [],
"IHTMLDocument2_title": "Example Domain",
"IHTMLDocument2_scripts": [],
"IHTMLDocument2_designMode": "Inherit",
"IHTMLDocument2_selection": {},
"IHTMLDocument2_readyState": "complete",
"IHTMLDocument2_frames": {},
"IHTMLDocument2_embeds": [],
"IHTMLDocument2_plugins": [],
"IHTMLDocument2_alinkColor": "#0000ff",
"IHTMLDocument2_bgColor": "#f0f0f2",
"IHTMLDocument2_fgColor": "#000000",
"IHTMLDocument2_linkColor": "#0000ff",
"IHTMLDocument2_vlinkColor": "#800080",
"IHTMLDocument2_referrer": null,
"IHTMLDocument2_location": null,
"IHTMLDocument2_lastModified": "03/07/2021 23:17:45",
"IHTMLDocument2_url": "about:blank",
"IHTMLDocument2_domain": null,
"IHTMLDocument2_cookie": null,
"IHTMLDocument2_expando": true,
"IHTMLDocument2_charset": "unicode",
"IHTMLDocument2_defaultCharset": "windows-1252",
"IHTMLDocument2_mimeType": "Chrome HTML Document",
"IHTMLDocument2_fileSize": null,
"IHTMLDocument2_fileCreatedDate": null,
"IHTMLDocument2_fileModifiedDate": null,
"IHTMLDocument2_fileUpdatedDate": null,
"IHTMLDocument2_security": "This type of document does not have a security certificate.",
"IHTMLDocument2_protocol": "Unknown Protocol",
"IHTMLDocument2_nameProp": "Example Domain",
"IHTMLDocument2_onhelp": null,
"IHTMLDocument2_onclick": null,
"IHTMLDocument2_ondblclick": null,
"IHTMLDocument2_onkeyup": null,
"IHTMLDocument2_onkeydown": null,
"IHTMLDocument2_onkeypress": null,
"IHTMLDocument2_onmouseup": null,
"IHTMLDocument2_onmousedown": null,
"IHTMLDocument2_onmousemove": null,
"IHTMLDocument2_onmouseout": null,
"IHTMLDocument2_onmouseover": null,
"IHTMLDocument2_onreadystatechange": null,
"IHTMLDocument2_onafterupdate": null,
"IHTMLDocument2_onrowexit": null,
"IHTMLDocument2_onrowenter": null,
"IHTMLDocument2_ondragstart": null,
"IHTMLDocument2_onselectstart": null,
"IHTMLDocument2_parentWindow": {},
"IHTMLDocument2_styleSheets": [
"System.__ComObject"
],
"IHTMLDocument2_onbeforeupdate": null,
"IHTMLDocument2_onerrorupdate": null,
"IHTMLDocument3_documentElement": {},
"IHTMLDocument3_uniqueID": "ms__id2",
"IHTMLDocument3_onrowsdelete": null,
"IHTMLDocument3_onrowsinserted": null,
"IHTMLDocument3_oncellchange": null,
"IHTMLDocument3_ondatasetchanged": null,
"IHTMLDocument3_ondataavailable": null,
"IHTMLDocument3_ondatasetcomplete": null,
"IHTMLDocument3_onpropertychange": null,
"IHTMLDocument3_dir": null,
"IHTMLDocument3_oncontextmenu": null,
"IHTMLDocument3_onstop": null,
"IHTMLDocument3_parentDocument": null,
"IHTMLDocument3_enableDownload": null,
"IHTMLDocument3_baseUrl": null,
"IHTMLDocument3_childNodes": [
"System.__ComObject",
"System.__ComObject"
],
"IHTMLDocument3_inheritStyleSheets": null,
"IHTMLDocument3_onbeforeeditfocus": null,
"IHTMLDocument4_onselectionchange": null,
"IHTMLDocument4_namespaces": {},
"IHTMLDocument4_media": null,
"IHTMLDocument4_oncontrolselect": null,
"IHTMLDocument4_URLUnencoded": "about:blank",
"IHTMLDocument5_onmousewheel": null,
"IHTMLDocument5_doctype": null,
"IHTMLDocument5_implementation": {},
"IHTMLDocument5_onfocusin": null,
"IHTMLDocument5_onfocusout": null,
"IHTMLDocument5_onactivate": null,
"IHTMLDocument5_ondeactivate": null,
"IHTMLDocument5_onbeforeactivate": null,
"IHTMLDocument5_onbeforedeactivate": null,
"IHTMLDocument5_compatMode": "CSS1Compat",
"IHTMLDOMNode_nodeType": null,
"IHTMLDOMNode_parentNode": null,
"IHTMLDOMNode_childNodes": null,
"IHTMLDOMNode_attributes": null,
"IHTMLDOMNode_nodeName": null,
"IHTMLDOMNode_nodeValue": null,
"IHTMLDOMNode_firstChild": null,
"IHTMLDOMNode_lastChild": null,
"IHTMLDOMNode_previousSibling": null,
"IHTMLDOMNode_nextSibling": null,
"IHTMLDOMNode2_ownerDocument": null
},
"Forms": [],
"InputFields": [],
"Links": [
{
"innerHTML": "More information...",
"innerText": "More information...",
"outerHTML": "<A href=\"https://www.iana.org/domains/example\">More information...</A>",
"outerText": "More information...",
"tagName": "A",
"href": "https://www.iana.org/domains/example"
}
],
"Images": [],
"Scripts": [],
"AllElements": [
{
"innerHTML": null,
"innerText": null,
"outerHTML": null,
"outerText": null,
"tagName": "!"
},
{
"innerHTML": "<HEAD><TITLE>Example Domain</TITLE>\r\n<META charset=utf-8>\r\n<META name=viewport content=\"width=device-width, initial-scale=1\">\r\n<STYLE type=text/css>\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </STYLE>\r\n</HEAD>\r\n<BODY>\r\n<DIV>\r\n<H1>Example Domain</H1>\r\n<P>This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.</P>\r\n<P><A href=\"https://www.iana.org/domains/example\">More information...</A></P></DIV></BODY>",
"innerText": "Example DomainExample Domain\r\nThis domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.\r\nMore information...",
"outerHTML": "<HTML><HEAD><TITLE>Example Domain</TITLE>\r\n<META charset=utf-8>\r\n<META name=viewport content=\"width=device-width, initial-scale=1\">\r\n<STYLE type=text/css>\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </STYLE>\r\n</HEAD>\r\n<BODY>\r\n<DIV>\r\n<H1>Example Domain</H1>\r\n<P>This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.</P>\r\n<P><A href=\"https://www.iana.org/domains/example\">More information...</A></P></DIV></BODY></HTML>",
"outerText": "Example DomainExample Domain\r\nThis domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.\r\nMore information...",
"tagName": "HTML"
},
{
"innerHTML": "<TITLE>Example Domain</TITLE>\r\n<META charset=utf-8>\r\n<META name=viewport content=\"width=device-width, initial-scale=1\">\r\n<STYLE type=text/css>\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </STYLE>",
"innerText": "Example Domain",
"outerHTML": "<HEAD><TITLE>Example Domain</TITLE>\r\n<META charset=utf-8>\r\n<META name=viewport content=\"width=device-width, initial-scale=1\">\r\n<STYLE type=text/css>\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </STYLE>\r\n</HEAD>",
"outerText": "Example Domain",
"tagName": "HEAD"
},
{
"innerHTML": "Example Domain",
"innerText": "Example Domain",
"outerHTML": "<TITLE>Example Domain</TITLE>",
"outerText": "Example Domain",
"tagName": "TITLE"
},
{
"innerHTML": null,
"innerText": null,
"outerHTML": "\r\n<META charset=utf-8>",
"outerText": null,
"tagName": "META",
"charset": "utf-8"
},
{
"innerHTML": null,
"innerText": null,
"outerHTML": null,
"outerText": null,
"tagName": "META"
},
{
"innerHTML": null,
"innerText": null,
"outerHTML": "\r\n<META name=viewport content=\"width=device-width, initial-scale=1\">",
"outerText": null,
"tagName": "META",
"name": "viewport",
"content": "width=device-width, initial-scale=1"
},
{
"innerHTML": "\r\n\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n ",
"innerText": null,
"outerHTML": "\r\n<STYLE type=text/css>\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </STYLE>",
"outerText": null,
"tagName": "STYLE",
"type": "text/css"
},
{
"innerHTML": "<DIV>\r\n<H1>Example Domain</H1>\r\n<P>This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.</P>\r\n<P><A href=\"https://www.iana.org/domains/example\">More information...</A></P></DIV>",
"innerText": "Example Domain\r\nThis domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.\r\nMore information...",
"outerHTML": "\r\n<BODY><DIV>\r\n<H1>Example Domain</H1>\r\n<P>This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.</P>\r\n<P><A href=\"https://www.iana.org/domains/example\">More information...</A></P></DIV></BODY>",
"outerText": "Example Domain\r\nThis domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.\r\nMore information...",
"tagName": "BODY"
},
{
"innerHTML": "<H1>Example Domain</H1>\r\n<P>This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.</P>\r\n<P><A href=\"https://www.iana.org/domains/example\">More information...</A></P>",
"innerText": "Example Domain\r\nThis domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.\r\nMore information...",
"outerHTML": "\r\n<DIV><H1>Example Domain</H1>\r\n<P>This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.</P>\r\n<P><A href=\"https://www.iana.org/domains/example\">More information...</A></P></DIV>",
"outerText": "Example Domain\r\nThis domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.\r\nMore information...",
"tagName": "DIV"
},
{
"innerHTML": "Example Domain",
"innerText": "Example Domain",
"outerHTML": "\r\n<H1>Example Domain</H1>",
"outerText": "Example Domain",
"tagName": "H1"
},
{
"innerHTML": "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.",
"innerText": "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.",
"outerHTML": "\r\n<P>This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.</P>",
"outerText": "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.",
"tagName": "P"
},
{
"innerHTML": "<A href=\"https://www.iana.org/domains/example\">More information...</A>",
"innerText": "More information...",
"outerHTML": "\r\n<P><A href=\"https://www.iana.org/domains/example\">More information...</A></P>",
"outerText": "More information...",
"tagName": "P"
},
{
"innerHTML": "More information...",
"innerText": "More information...",
"outerHTML": "<A href=\"https://www.iana.org/domains/example\">More information...</A>",
"outerText": "More information...",
"tagName": "A",
"href": "https://www.iana.org/domains/example"
}
],
"StatusCode": 200,
"StatusDescription": "OK",
"RawContentStream": {
"CanRead": true,
"CanSeek": true,
"CanTimeout": false,
"CanWrite": true,
"Length": 1256,
"Capacity": 10000,
"Position": 0,
"ReadTimeout": null,
"WriteTimeout": null
},
"RawContentLength": 1256,
"RawContent": "HTTP/1.1 200 OK\r\nAge: 156043\r\nVary: Accept-Encoding\r\nX-Cache: HIT\r\nAccept-Ranges: bytes\r\nContent-Length: 1256\r\nCache-Control: max-age=604800\r\nContent-Type: text/html; charset=UTF-8\r\nDate: Mon, 08 Mar 2021 07:17:44 GMT\r\nExpires: Mon, 15 Mar 2021 07:17:44 GMT\r\nETag: \"3147526947\"\r\nLast-Modified: Thu, 17 Oct 2019 07:18:26 GMT\r\nServer: ECS (oxr/836D)\r\n\r\n<!doctype html>\n<html>\n<head>\n <title>Example Domain</title>\n\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <style type=\"text/css\">\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </style> \n</head>\n\n<body>\n<div>\n <h1>Example Domain</h1>\n <p>This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.</p>\n <p><a href=\"https://www.iana.org/domains/example\">More information...</a></p>\n</div>\n</body>\n</html>\n",
"BaseResponse": {
"IsMutuallyAuthenticated": false,
"Cookies": [],
"Headers": [
"Age",
"Vary",
"X-Cache",
"Accept-Ranges",
"Content-Length",
"Cache-Control",
"Content-Type",
"Date",
"Expires",
"ETag",
"Last-Modified",
"Server"
],
"SupportsHeaders": true,
"ContentLength": 1256,
"ContentEncoding": "",
"ContentType": "text/html; charset=UTF-8",
"CharacterSet": "UTF-8",
"Server": "ECS (oxr/836D)",
"LastModified": "/Date(1571296706000)/",
"StatusCode": 200,
"StatusDescription": "OK",
"ProtocolVersion": {
"Major": 1,
"Minor": 1,
"Build": -1,
"Revision": -1,
"MajorRevision": -1,
"MinorRevision": -1
},
"ResponseUri": "https://www.example.com/",
"Method": "GET",
"IsFromCache": false
},
"Headers": {
"Age": "156043",
"Vary": "Accept-Encoding",
"X-Cache": "HIT",
"Accept-Ranges": "bytes",
"Content-Length": "1256",
"Cache-Control": "max-age=604800",
"Content-Type": "text/html; charset=UTF-8",
"Date": "Mon, 08 Mar 2021 07:17:44 GMT",
"Expires": "Mon, 15 Mar 2021 07:17:44 GMT",
"ETag": "\"3147526947\"",
"Last-Modified": "Thu, 17 Oct 2019 07:18:26 GMT",
"Server": "ECS (oxr/836D)"
}
}

7
test/fixtures/cmd/nftables-chain vendored Normal file
View file

@ -0,0 +1,7 @@
table inet filter {
chain INPUT {
type filter hook input priority 0; policy accept;
iifname "eth0" tcp dport 80 accept comment "http on 80"
jump derby-cognos-web
}
}

1
test/fixtures/cmd/nftables-chain-json vendored Normal file
View file

@ -0,0 +1 @@
{"nftables": [{"metainfo": {"version": "1.0.2", "release_name": "Lester Gooch", "json_schema_version": 1}}, {"chain": {"family": "inet", "table": "filter", "name": "INPUT", "handle": 1, "type": "filter", "hook": "input", "prio": 0, "policy": "accept"}}, {"rule": {"family": "inet", "table": "filter", "chain": "INPUT", "handle": 4, "comment": "http on 80", "expr": [{"match": {"op": "==", "left": {"meta": {"key": "iifname"}}, "right": "eth0"}}, {"match": {"op": "==", "left": {"payload": {"protocol": "tcp", "field": "dport"}}, "right": 80}}, {"accept": null}]}}, {"rule": {"family": "inet", "table": "filter", "chain": "INPUT", "handle": 5, "expr": [{"jump": {"target": "derby-cognos-web"}}]}}]}

8
test/fixtures/cmd/nftables-set vendored Normal file
View file

@ -0,0 +1,8 @@
table inet filter {
set OPEN_PORTS {
type ipv4_addr
size 65536
flags interval
elements = { 1.1.1.1 }
}
}

1
test/fixtures/cmd/nftables-set-json vendored Normal file
View file

@ -0,0 +1 @@
{"nftables": [{"metainfo": {"version": "1.0.2", "release_name": "Lester Gooch", "json_schema_version": 1}}, {"set": {"family": "inet", "name": "OPEN_PORTS", "table": "filter", "type": "ipv4_addr", "handle": 3, "size": 65536, "flags": ["interval"], "elem": ["1.1.1.1"]}}]}

1
test/fixtures/cmd/nftables-version vendored Normal file
View file

@ -0,0 +1 @@
nftables v1.0.2 (Lester Gooch)

View file

@ -1,93 +0,0 @@
# 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
control "tmp-3.0.3" do
only_if { false }
describe "c.2" do
it { should_bot "c.2" }
end
end
control "tmp-3.0.4" 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 "f.1" do
it { should cmp "f.1" }
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 "f.2" do
it { should cmp "f.2" }
end
end

View file

@ -1,5 +0,0 @@
control_id,justification,explanation,evidence_url,status,expiration_date,updated,frequency
tmp-3.0.1,Sound reasoning,,Dummy url,failed,2001-06-01T00:00:00.000Z,,,
tmp-3.0.2,,Unassailable thinking,Dummy url,failed,,2021-06-01T00:00:00.000Z,semiannually
tmp-4.0,Sheer cleverness,,Dummy url,passed,2050-06-01T00:00:00.000Z,,,
tmp-6.0.2,Sheer cleverness,,Dummy url,passed,2050-06-01T00:00:00.000Z,,,
1 control_id,justification,explanation,evidence_url,status,expiration_date,updated,frequency
2 tmp-3.0.1,Sound reasoning,,Dummy url,failed,2001-06-01T00:00:00.000Z,,,
3 tmp-3.0.2,,Unassailable thinking,Dummy url,failed,,2021-06-01T00:00:00.000Z,semiannually
4 tmp-4.0,Sheer cleverness,,Dummy url,passed,2050-06-01T00:00:00.000Z,,,
5 tmp-6.0.2,Sheer cleverness,,Dummy url,passed,2050-06-01T00:00:00.000Z,,,

View file

@ -1,27 +0,0 @@
{
"tmp-3.0.1": {
"explanation": "Sound reasoning",
"evidence_url": "Dummy url",
"status": "failed",
"updated": "2021-06-01T00:00:00.000Z",
"frequency": "semiannually"
},
"tmp-3.0.2": {
"justification": "Unassailable thinking",
"evidence_url": "Dummy url",
"expiration_date": "2001-06-01T00:00:00.000Z",
"status": "passed"
},
"tmp-4.0": {
"justification": "Sheer cleverness",
"evidence_url": "Dummy url",
"expiration_date": "2050-06-01T00:00:00.000Z",
"status": "passed"
},
"tmp-6.0.2": {
"justification": "Sheer cleverness",
"evidence_url": "Dummy url",
"expiration_date": "2050-06-01T00:00:00.000Z",
"status": "passed"
}
}

View file

@ -1,24 +0,0 @@
tmp-3.0.1:
explanation: Sound reasoning
evidence_url: Dummy url
status: failed
updated: 2021-06-01
frequency: semiannually
tmp-3.0.2:
justification: Unassailable thinking
evidence_url: Dummy url
expiration_date: 2001-06-01
status: passed
tmp-4.0:
justification: Sheer cleverness
evidence_url: Dummy url
expiration_date: 2050-06-01
status: passed
tmp-6.0.2:
justification: Sheer cleverness
evidence_url: Dummy url
expiration_date: 2050-06-01
status: passed

View file

@ -1,5 +0,0 @@
tmp-3.0.2:
justification: Unassailable thinking
evidence_url: Dummy url
expiration_date: bad date
status: passed

View file

@ -1,6 +0,0 @@
tmp-3.0.1:
justification: Sound reasoning
evidence_url: Dummy url
status: failed
updated: bad date
frequency: semiannually

View file

@ -1,6 +0,0 @@
tmp-3.0.4:
justification: Sound reasoning
evidence_url: Dummy url
status: failed
updated: 2021-06-01
frequency: biweekly

View file

@ -1,5 +0,0 @@
tmp-3.0.3:
justification: Unassailable thinking
evidence_url: Dummy url
expiration_date: 2050-06-01
status: pass

View file

@ -1,7 +0,0 @@
tmp-3.0.1:
expiration_date: 2050-06-01
status: passed
tmp-3.0.2:
evidence_url: Dummy url
expiration_date: 2050-06-01
status: passed

View file

@ -1,4 +0,0 @@
tmp-3.0.3:
justification: Unassailable thinking
evidence_url: Dummy url
expiration_date: 2050-06-01

View file

@ -1,8 +0,0 @@
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!
1 control_id_random justification_random run_random expiration_date_random
2 03_waivered_no_expiry_ran_passes Sound reasoning TRUE
3 04_waivered_no_expiry_ran_fails Unassailable thinking TRUE 2077-11-10T00:00:00Z
4
5 05_waivered_no_expiry_not_ran Sheer cleverness FALSE
6 06_waivered_expiry_in_past_ran_passes Necessity TRUE
7 14_waivered_expiry_in_future_z_not_ran Lack of imagination FALSE 2077-11-10T00:00:00Z
8 random contorl id with no data random data in csv!

View file

@ -1,21 +0,0 @@
{
"tmp-3.0.1": {
"justification": "Sound reasoning",
"evidence_url": "Dummy url",
"expiration_date": "2050-06-01T00:00:00.000Z",
"status": "passed",
"random": "haha"
},
"tmp-3.0.2": {
"justification": "Unassailable thinking",
"evidence_url": "Dummy url",
"expiration_date": "2050-06-01T00:00:00.000Z",
"status": "passed"
},
"tmp-4.0": {
"justification": "Sheer cleverness",
"evidence_url": "Dummy url",
"expiration_date": "2050-06-01T00:00:00.000Z",
"status": "passed"
}
}

View file

@ -1,18 +0,0 @@
tmp-3.0.1:
justification: Sound reasoning
evidence_url: Dummy url
expiration_date: 2050-06-01
status: passed
random: haha
tmp-3.0.2:
justification: Unassailable thinking
evidence_url: Dummy url
expiration_date: 2050-06-01
status: passed
tmp-4.0:
justification: Sheer cleverness
evidence_url: Dummy url
expiration_date: 2050-06-01
status: passed

View file

@ -1,10 +0,0 @@
name: attestation
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

View file

@ -1,177 +0,0 @@
require "functional/helper"
describe "attestations" do
include FunctionalHelper
parallelize_me!
let(:attestation_profile) { "#{profile_path}/attestation" }
let(:run_result) { run_inspec_process(cmd) }
let(:cmd) { "exec #{attestation_profile} --attestation-file #{attestation_profile}/files/#{attestation_file}" }
attr_accessor :out
def inspec(commandline, prefix = nil)
@stdout = @stderr = nil
self.out = super
end
def stdout
@stdout ||= out.stdout
.force_encoding(Encoding::UTF_8)
end
def stderr
@stderr ||= out.stderr
.force_encoding(Encoding::UTF_8)
end
describe "with a attestation file that does not exist" do
let(:attestation_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 attestation file that has wrong headers - yaml format" do
let(:attestation_file) { "wrong-headers.yaml" }
it "raise file does not exist standard error" do
result = run_result
assert_includes result.stdout, "Extra column headers: [\"random\"]"
end
end
describe "with a attestation file that has wrong headers - csv format" do
let(:attestation_file) { "wrong-headers.csv" }
it "raise file does not exist standard error" do
result = run_result
assert_includes result.stdout, "Missing column headers: [\"control_id\", \"status\", \"justification\"]"
assert_includes result.stdout, "Extra column headers: [\"control_id_random\", \"justification_random\", \"run_random\", \"expiration_date_random\", nil]\n"
end
end
describe "with a attestation file that has wrong headers - json format" do
let(:attestation_file) { "wrong-headers.json" }
it "raise file does not exist standard error" do
result = run_result
assert_includes result.stdout, "Extra column headers: [\"random\"]"
end
end
describe "running attestation on a profile - yaml" do
let(:attestation_file) { "attestations.yaml" }
it "attests N/R controls correctly" do
result = run_result
assert_includes result.stdout, "tmp-3.0.1: No-op (1 failed)"
refute_includes result.stdout, "N/R tmp-3.0.2: No-op"
refute_includes result.stdout, "N/R tmp-6.0.2: No-op"
end
it "does not attests non N/R controls" do
result = run_result
assert_includes result.stdout, "tmp-4.0: d.1 (1 failed)"
end
end
describe "running attestation on a profile - json" do
let(:attestation_file) { "attestations.json" }
it "attests N/R controls correctly" do
result = run_result
assert_includes result.stdout, "tmp-3.0.1: No-op (1 failed)"
refute_includes result.stdout, "N/R tmp-3.0.2: No-op"
refute_includes result.stdout, "N/R tmp-6.0.2: No-op"
end
it "does not attests non N/R controls" do
result = run_result
assert_includes result.stdout, "tmp-4.0: d.1 (1 failed)"
end
end
describe "running attestation on a profile - csv" do
let(:attestation_file) { "attestations.csv" }
it "attests N/R controls correctly" do
result = run_result
assert_includes result.stdout, "tmp-3.0.1: No-op (1 failed)"
refute_includes result.stdout, "N/R tmp-3.0.2: No-op"
refute_includes result.stdout, "N/R tmp-6.0.2: No-op"
end
it "does not attests non N/R controls" do
result = run_result
assert_includes result.stdout, "tmp-4.0: d.1 (1 failed)"
end
end
describe "running attestation on profile with streaming reporter" do
let(:attestation_file) { "#{attestation_profile}/files/attestations.yaml" }
it "attests controls correctly" do
inspec("exec " + "#{attestation_profile}" + " --attestation-file #{attestation_file}" + " --no-create-lockfile" + " --no-color" + " --reporter progress-bar")
if windows?
_(stderr).must_match(/\[FAIL\]\s*tmp-3.0.1\s*No-op Skipped control due to only_if condition. Control not attested : Attestation expired on 2021-12-01/)
_(stderr).must_match(/\[FAIL\]\s*tmp-3.0.2\s*No-op Skipped control due to only_if condition. Control not attested : Attestation expired on 2001-06-01/)
_(stderr).must_match(/\[PASS\]\s*tmp-6.0.2\s*No-op Skipped control due to only_if condition. Control Attested : Sheer cleverness | Evidence URL: Dummy url/)
_(stderr).must_match(/\[FAIL\]\s*tmp-4.0\s*d.1 is expected to cmp == \"d.1\"/)
else
_(stderr).must_match(/\[FAILED\]\s*tmp-3.0.1\s*No-op Skipped control due to only_if condition. Control not attested : Attestation expired on 2021-12-01/)
_(stderr).must_match(/\[FAILED\]\s*tmp-3.0.2\s*No-op Skipped control due to only_if condition. Control not attested : Attestation expired on 2001-06-01/)
_(stderr).must_match(/\[PASSED\]\s*tmp-6.0.2\s*No-op Skipped control due to only_if condition. Control Attested : Sheer cleverness | Evidence URL: Dummy url/)
_(stderr).must_match(/\[FAILED\]\s*tmp-4.0\s*d.1 is expected to cmp == \"d.1\"/)
end
end
end
describe "an attestation file with invalid dates" do
let(:attestation_file) { "bad-date.yaml" }
it "gracefully errors" do
result = run_result
assert_includes result.stdout, "ERROR"
end
end
describe "an attestation file with invalid update dates" do
let(:attestation_file) { "bad-update-date.yaml" }
it "gracefully errors" do
result = run_result
assert_includes result.stdout, "ERROR"
end
end
describe "an attestation file with invalid status" do
let(:attestation_file) { "invalid-status.yaml" }
it "throws warning" do
result = run_result
assert_includes result.stdout, "Invalid attestation status 'pass' for control tmp-3.0.3. Use 'passed' or 'failed'."
end
end
describe "an attestation file with no status" do
let(:attestation_file) { "no-status.yaml" }
it "throws warning" do
result = run_result
assert_includes result.stdout, "No attestation status for control tmp-3.0.3. Use 'passed' or 'failed'."
end
end
describe "an attestation file with invalid frequency value" do
let(:attestation_file) { "invalid-frequency.yaml" }
it "throws warning" do
result = run_result
assert_includes result.stdout, "Invalid frequency value 'biweekly' for control tmp-3.0.4."
end
end
describe "an attestation file with no justification" do
let(:attestation_file) { "no-justification.yaml" }
it "throws warning and shows proper message for justification absence" do
result = run_result
assert_includes result.stdout, "Missing column headers: [\"justification\"]"
assert_includes result.stdout, "Control Attested : No justification provided."
end
end
end

View file

@ -105,6 +105,7 @@ describe "inputs" do
# require inspec
require "inspec"
require "inspec/runner"
require "inspec/utils/licensing_config"
# inject pretty-printed runner opts
runner_args = #{options.inspect}

View file

@ -1,12 +1,14 @@
require "functional/helper"
require "inspec/runner"
require "inspec/resources/file"
require "inspec/utils/licensing_config"
describe "inspec report tests" do
include FunctionalHelper
describe "report" do
it "loads a json report" do
WebMock.allow_net_connect!
o = { "reporter" => ["json"], "report" => true }
runner = ::Inspec::Runner.new(o)
runner.add_target(example_profile)

View file

@ -385,6 +385,24 @@ class MockLoader
# ip6tables
"/usr/sbin/ip6tables -S" => cmd.call("ip6tables-s"),
%{sh -c 'type "/usr/sbin/ip6tables"'} => empty.call,
# nftables (version)
"/usr/sbin/nft --version" => cmd.call("nftables-version"),
# nftables (chain with json output)
"/usr/sbin/nft -s -j list chain inet filter INPUT" => cmd.call("nftables-chain-json"),
"/usr/sbin/nft -j list chain inet filter INPUT" => cmd.call("nftables-chain-json"),
# nftables (chain)
"/usr/sbin/nft -s list chain inet filter INPUT" => cmd.call("nftables-chain"),
"/usr/sbin/nft -s -y list chain inet filter INPUT" => cmd.call("nftables-chain"),
"/usr/sbin/nft -s -nn list chain inet filter INPUT" => cmd.call("nftables-chain"),
"/usr/sbin/nft -y list chain inet filter INPUT" => cmd.call("nftables-chain"),
"/usr/sbin/nft list chain inet filter INPUT" => cmd.call("nftables-chain"),
# nftables (set with json output)
"/usr/sbin/nft -s -j list set inet filter OPEN_PORTS" => cmd.call("nftables-set-json"),
"/usr/sbin/nft -j list set inet filter OPEN_PORTS" => cmd.call("nftables-set-json"),
# nftables (set)
"/usr/sbin/nft -s list set inet filter OPEN_PORTS" => cmd.call("nftables-set"),
"/usr/sbin/nft list set inet filter OPEN_PORTS" => cmd.call("nftables-set"),
%{sh -c 'type "/usr/sbin/nft"'} => empty.call,
# ipnat
"/usr/sbin/ipnat -l" => cmd.call("ipnat-l"),
%{type "/usr/sbin/ipnat"} => empty.call,
@ -631,6 +649,7 @@ class MockLoader
# http resource - windows
"\n$body = \n $Body = $body | ConvertFrom-Json\n #convert to hashtable\n $HashTable = @{}\n foreach ($property in $Body.PSObject.Properties) {\n $HashTable[$property.Name] = $property.Value\n }\n $response = Invoke-WebRequest -Method HEAD -TimeoutSec 120 'https://www.example.com' -Body $HashTable -UseBasicParsing\n $response | Select-Object -Property * | ConvertTo-json # We use `Select-Object -Property * ` to get around an odd PowerShell error" => cmd.call("http-windows-remote-no-options"),
"\n$body = \n $Body = $body | ConvertFrom-Json\n #convert to hashtable\n $HashTable = @{}\n foreach ($property in $Body.PSObject.Properties) {\n $HashTable[$property.Name] = $property.Value\n }\n $response = Invoke-WebRequest -Method GET -TimeoutSec 120 'https://www.example.com' -Body $HashTable -UseBasicParsing\n $response | Select-Object -Property * | ConvertTo-json # We use `Select-Object -Property * ` to get around an odd PowerShell error" => cmd.call("http-windows-remote-head"),
"\n$body = \n $Body = $body | ConvertFrom-Json\n #convert to hashtable\n $HashTable = @{}\n foreach ($property in $Body.PSObject.Properties) {\n $HashTable[$property.Name] = $property.Value\n }\n $response = Invoke-WebRequest -Method GET -TimeoutSec 120 -Headers @{ 'X-Test-Header' = 'test/value'; 'foo' = 'bar'} 'https://www.example.com' -Body $HashTable -UseBasicParsing\n $response | Select-Object -Property * | ConvertTo-json # We use `Select-Object -Property * ` to get around an odd PowerShell error" => cmd.call("http-windows-remote-get-headers"),
"\n$body = '{ \"a\" : \"1\", \"b\" : \"five\" }'\n $Body = $body | ConvertFrom-Json\n #convert to hashtable\n $HashTable = @{}\n foreach ($property in $Body.PSObject.Properties) {\n $HashTable[$property.Name] = $property.Value\n }\n $response = Invoke-WebRequest -Method POST -TimeoutSec 120 'https://www.example.com' -Body $HashTable -UseBasicParsing\n $response | Select-Object -Property * | ConvertTo-json # We use `Select-Object -Property * ` to get around an odd PowerShell error" => cmd.call("http-windows-remote-head"),
# elasticsearch resource
"curl -H 'Content-Type: application/json' http://localhost:9200/_nodes" => cmd.call("elasticsearch-cluster-nodes-default"),

View file

@ -1,2 +1,3 @@
default["osprepare"]["docker"] = false
default["osprepare"]["application"] = true
default["osprepare"]["nftables"] = false

View file

@ -26,7 +26,11 @@ include_recipe("os_prepare::service")
include_recipe("os_prepare::package")
include_recipe("os_prepare::registry_key")
include_recipe("os_prepare::iis")
include_recipe("os_prepare::iptables")
if node["osprepare"]["nftables"]
include_recipe("os_prepare::nftables")
else
include_recipe("os_prepare::iptables")
end
include_recipe("os_prepare::x509")
include_recipe("os_prepare::dh_params")

View file

@ -0,0 +1,12 @@
if platform_family?("rhel", "debian", "fedora", "suse")
package "nftables"
execute "nft flush ruleset"
execute "nft add table inet filter"
execute 'nft add chain inet filter INPUT \{ type filter hook input priority 0\; policy accept\; \}'
execute "nft add chain inet filter derby-cognos-web"
execute 'nft add set inet filter OPEN_PORTS \{ type ipv4_addr\; size 65536\; flags interval\; \}'
execute 'nft add rule inet filter INPUT iifname eth0 tcp dport 80 accept comment \"http on 80\"'
execute "nft add rule inet filter INPUT jump derby-cognos-web"
execute 'nft add rule inet filter derby-cognos-web tcp dport 80 accept comment "derby-cognos-web"'
execute 'nft add element inet filter OPEN_PORTS \{ 1.1.1.1/32 \}'
end

View file

@ -2,6 +2,10 @@ unless ENV['IPV6']
$stderr.puts "\033[1;33mTODO: Not running #{__FILE__.split("/").last} because we are running without IPv6\033[0m"
return
end
if ENV['NFTABLES']
$stderr.puts "\033[1;33mTODO: Not running #{__FILE__.split("/").last} because we are running with nftables\033[0m"
return
end
case os[:family]
when 'ubuntu', 'fedora', 'debian', 'suse'

View file

@ -1,3 +1,8 @@
if ENV['NFTABLES']
$stderr.puts "\033[1;33mTODO: Not running #{__FILE__.split("/").last} because we are running with nftables\033[0m"
return
end
case os[:family]
when 'ubuntu', 'fedora', 'debian', 'suse'
describe iptables do

View file

@ -0,0 +1,23 @@
unless ENV['NFTABLES']
$stderr.puts "\033[1;33mTODO: Not running #{__FILE__.split("/").last} because we are running with iptables\033[0m"
return
end
case os[:family]
when 'ubuntu', 'fedora', 'debian', 'suse', 'redhat', 'centos'
describe nftables(family: 'inet', table: 'filter', chain: 'INPUT') do
its('type') { should eq 'filter' }
its('hook') { should eq 'input' }
its('prio') { should eq 0 }
its('policy') { should eq 'accept' }
it { should have_rule('iifname "eth0" tcp dport 80 accept comment "http on 80"') }
it { should_not have_rule('iifname "eth1" tcp dport 80 accept') }
end
describe nftables(family: 'inet', table: 'filter', set: 'OPEN_PORTS') do
its('type') { should eq 'ipv4_addr' }
its('flags') { should include 'interval' }
it { should have_element('1.1.1.1') }
it { should_not have_element('2.2.2.2') }
end
end

View file

@ -323,7 +323,7 @@ class PluginLoaderTests < Minitest::Test
skip "not valid in this env" unless using_bundler?
with_empty_registry do
exp = %i{ train-aws train-habitat train-winrm }
exp = %i{ train-aws train-habitat train-kubernetes train-winrm }
exp_err = ""
assert_detect_system_plugins exp, exp_err do |loader|

View file

@ -35,7 +35,7 @@ describe "Inspec::Resources::Host" do
resource = MockLoader.new(:windows).load_resource("host", "microsoft.com")
_(resource.resolvable?).must_equal true
_(resource.reachable?).must_equal false
_(resource.ipaddress).must_equal ["134.170.188.221", "2404:6800:4009:827::200e"]
_(resource.ipaddress).must_equal ["134.170.185.46", "134.170.188.221", "2404:6800:4009:827::200e"]
_(resource.to_s).must_equal "Host microsoft.com"
_(resource.resource_id).must_equal "microsoft.com"
end
@ -107,7 +107,7 @@ describe "Inspec::Resources::Host" do
resource = MockLoader.new(:windows).load_resource("host", "microsoft.com", port: 1234, protocol: "tcp")
_(resource.resolvable?).must_equal true
_(resource.reachable?).must_equal true
_(resource.ipaddress).must_equal ["134.170.188.221", "2404:6800:4009:827::200e"]
_(resource.ipaddress).must_equal ["134.170.185.46", "134.170.188.221", "2404:6800:4009:827::200e"]
_(resource.to_s).must_equal "Host microsoft.com port 1234 proto tcp"
_(resource.resource_id).must_equal "microsoft.com-1234-tcp"
end
@ -379,7 +379,7 @@ describe Inspec::Resources::UnixHostProvider do
it "checks ipv4_address and ipv6_address properties on windows" do
resource = MockLoader.new(:windows).load_resource("host", "microsoft.com")
_(resource.ipv4_address).must_equal ["134.170.188.221"]
_(resource.ipv4_address).must_equal ["134.170.185.46", "134.170.188.221"]
_(resource.ipv4_address).must_include "134.170.188.221"
_(resource.ipv6_address).must_equal ["2404:6800:4009:827::200e"]
_(resource.ipv6_address).must_include "2404:6800:4009:827::200e"

View file

@ -314,4 +314,25 @@ describe "Inspec::Resources::Http" do
_(worker.resource_id).must_equal "https://www.example.com"
end
end
describe "Windows-Get-With-Headers" do
let(:backend) { MockLoader.new(:windows).backend }
let(:http_method) { "GET" }
let(:url) { "https://www.example.com" }
let(:opts) { { headers: { "X-Test-Header" => "test/value", "foo" => "bar" } } }
let(:worker) { Inspec::Resources::Http::Worker::Remote.new(backend, http_method, url, opts) }
describe "simple HTTP request with headers" do
it "returns correct data" do
Inspec::Resources::Cmd.any_instance
.stubs(:exist?)
.returns(true)
_(worker.status).must_equal 200
_(worker.response_headers["Content-Type"]).must_equal "text/html; charset=UTF-8"
_(worker.resource_id).must_equal "https://www.example.com"
expected_cmd = "Invoke-WebRequest -Method GET -TimeoutSec 120 -Headers @{ 'X-Test-Header' = 'test/value'; 'foo' = 'bar'} 'https://www.example.com' -Body $HashTable -UseBasicParsing"
_(worker.send(:load_powershell_command)).must_include expected_cmd
end
end
end
end

View file

@ -0,0 +1,27 @@
require "helper"
require "inspec/resource"
require "inspec/resources/nftables"
describe "Inspec::Resources::NfTables" do
# ubuntu
it "verify nftables chain on ubuntu" do
resource = MockLoader.new(:ubuntu).load_resource("nftables", { family: "inet", table: "filter", chain: "INPUT" })
_(resource.type).must_equal "filter"
_(resource.hook).must_equal "input"
_(resource.prio).must_equal 0
_(resource.policy).must_equal "accept"
_(resource.has_rule?('iifname "eth0" tcp dport 80 accept comment "http on 80"')).must_equal true
_(resource.has_rule?('iifname "eth1" tcp dport 80 accept')).must_equal false
_(resource.resource_id).must_equal "nftables (family: inet table: filter chain: INPUT )"
end
it "verify nftables set on ubuntu" do
resource = MockLoader.new(:ubuntu).load_resource("nftables", { family: "inet", table: "filter", set: "OPEN_PORTS" })
_(resource.type).must_equal "ipv4_addr"
_(resource.flags).must_include "interval"
_(resource.size).must_equal 65536
_(resource.has_element?("1.1.1.1")).must_equal true
_(resource.has_element?("2.2.2.2")).must_equal false
_(resource.resource_id).must_equal "nftables (family: inet table: filter set: OPEN_PORTS)"
end
end

View file

@ -39,7 +39,11 @@ describe "Inspec::Resources::PostgresSession" do
end
it "verify postgres_session create_psql_cmd in socket connection" do
resource = load_resource("postgres_session", "myuser", "mypass", "127.0.0.1", 5432, "/var/run/postgresql")
_(resource.send(:create_psql_cmd, "SELECT * FROM STUDENTS;", ["testdb"])).must_equal "psql -d postgresql://myuser:mypass@/testdb?host=/var/run/postgresql -A -t -w -c SELECT\\ \\*\\ FROM\\ STUDENTS\\;"
_(resource.send(:create_psql_cmd, "SELECT * FROM STUDENTS;", ["testdb"])).must_equal "psql -d postgresql://myuser:mypass@/testdb?host=/var/run/postgresql -p 5432 -A -t -w -c SELECT\\ \\*\\ FROM\\ STUDENTS\\;"
end
it "verify postgres_session create_psql_cmd in socket connection" do
resource = load_resource("postgres_session", "myuser", "mypass", "127.0.0.1", 1234, "/var/run/postgresql")
_(resource.send(:create_psql_cmd, "SELECT * FROM STUDENTS;", ["testdb"])).must_equal "psql -d postgresql://myuser:mypass@/testdb?host=/var/run/postgresql -p 1234 -A -t -w -c SELECT\\ \\*\\ FROM\\ STUDENTS\\;"
end
it "fails when no connection established in linux" do

View file

@ -4,6 +4,7 @@ require "helper"
require "inspec/secrets"
require "inspec/runner"
require "inspec/fetcher/mock"
require "inspec/utils/licensing_config"
describe Inspec::Runner do
let(:runner) { Inspec::Runner.new({ command_runner: :generic, reporter: [] }) }
@ -73,6 +74,7 @@ describe Inspec::Runner do
describe "testing runner.run exit codes" do
it "returns proper exit code when no profile is added" do
WebMock.allow_net_connect!
_(runner.run).must_equal 0
end
end

View file

@ -0,0 +1,18 @@
require "helper"
require "inspec/utils/licensing_config"
describe "ChefLicensing::Config" do
it "returns the default chef product name as foo" do
expect(ChefLicensing::Config.chef_product_name).must_equal("InSpec")
end
it "returns the default chef_entitlement_id" do
expect(ChefLicensing::Config.chef_entitlement_id).must_equal("3ff52c37-e41f-4f6c-ad4d-365192205968")
end
it "returns the default chef_executable_name" do
expect(ChefLicensing::Config.chef_executable_name).must_equal("inspec")
end
# TODO: Need to add the test for license_server_url.
end

View file

@ -101,4 +101,9 @@ describe "SimpleConfig Default Parser" do
cur = SimpleConfig.new("1:2:3", assignment_regex: /^(.*):(.*):(.*)$/, key_values: 4)
_(cur.params).must_equal({ "1" => ["2", "3", nil, nil] })
end
it "supports :mulitple values and returns array of values" do
cur = SimpleConfig.new("foo: bar\nbiz: baz boz bop\nbiz: cdsdcs cdscs csc\nbiz: ada\nfoz: foo [!deny] bar", assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/, multiple_values: true, multiple_value_regex: /\s+/)
_(cur.params).must_equal({ "foo" => ["bar"], "biz" => %w{baz boz bop cdsdcs cdscs csc ada}, "foz" => ["foo", "[!deny]", "bar"] })
end
end