mirror of
https://github.com/inspec/inspec
synced 2024-11-10 07:04:15 +00:00
CHEF 83 Revert attestations changes (#47)
* revert attestation related files Signed-off-by: Sathish <sbabu@progress.com> * revert attestation changes to existing files Signed-off-by: Sathish <sbabu@progress.com> * update signature Signed-off-by: Sathish <sbabu@progress.com> --------- Signed-off-by: Sathish <sbabu@progress.com>
This commit is contained in:
parent
3b9ca4fe18
commit
4ec735d09d
35 changed files with 10 additions and 991 deletions
|
@ -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.
|
|
@ -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 >}}
|
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
|
@ -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==
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -199,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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -70,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.
|
||||
|
@ -188,8 +178,7 @@ module Inspec
|
|||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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,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"
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
|
@ -1,5 +0,0 @@
|
|||
tmp-3.0.2:
|
||||
justification: Unassailable thinking
|
||||
evidence_url: Dummy url
|
||||
expiration_date: bad date
|
||||
status: passed
|
|
@ -1,6 +0,0 @@
|
|||
tmp-3.0.1:
|
||||
justification: Sound reasoning
|
||||
evidence_url: Dummy url
|
||||
status: failed
|
||||
updated: bad date
|
||||
frequency: semiannually
|
|
@ -1,6 +0,0 @@
|
|||
tmp-3.0.4:
|
||||
justification: Sound reasoning
|
||||
evidence_url: Dummy url
|
||||
status: failed
|
||||
updated: 2021-06-01
|
||||
frequency: biweekly
|
|
@ -1,5 +0,0 @@
|
|||
tmp-3.0.3:
|
||||
justification: Unassailable thinking
|
||||
evidence_url: Dummy url
|
||||
expiration_date: 2050-06-01
|
||||
status: pass
|
|
@ -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
|
|
@ -1,4 +0,0 @@
|
|||
tmp-3.0.3:
|
||||
justification: Unassailable thinking
|
||||
evidence_url: Dummy url
|
||||
expiration_date: 2050-06-01
|
|
@ -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,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"
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -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
|
10
test/fixtures/profiles/attestation/inspec.yml
vendored
10
test/fixtures/profiles/attestation/inspec.yml
vendored
|
@ -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
|
|
@ -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
|
Loading…
Reference in a new issue