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:
Sathish Babu 2023-05-30 20:09:29 +05:30 committed by GitHub
parent 3b9ca4fe18
commit 4ec735d09d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 10 additions and 991 deletions

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 >}}

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,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

@ -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,

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

@ -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

@ -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

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,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