mirror of
https://github.com/inspec/inspec
synced 2025-02-23 01:18:44 +00:00
Added enhanced outcomes option to schema subcommand and updated schema
Signed-off-by: Nikita Mathur <nikita.mathur@chef.io>
This commit is contained in:
parent
dfd66012b4
commit
a9fae7cfe2
6 changed files with 231 additions and 5 deletions
lib/inspec
test/functional
|
@ -463,11 +463,13 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
pretty_handle_exception(e)
|
||||
end
|
||||
|
||||
option :enhanced_outcomes, type: :boolean,
|
||||
desc: "Show enhanced outcomes output"
|
||||
desc "schema NAME", "print the JSON schema", hide: true
|
||||
def schema(name)
|
||||
require "inspec/schema/output_schema"
|
||||
|
||||
puts Inspec::Schema::OutputSchema.json(name)
|
||||
o = config
|
||||
puts Inspec::Schema::OutputSchema.json(name, o)
|
||||
rescue StandardError => e
|
||||
puts e
|
||||
puts "Valid schemas are #{Inspec::Schema::OutputSchema.names.join(", ")}"
|
||||
|
|
|
@ -111,6 +111,43 @@ module Inspec
|
|||
},
|
||||
}.freeze
|
||||
|
||||
CONTROL_ENHANCED_OUTCOME = {
|
||||
"type" => "object",
|
||||
"additionalProperties" => false,
|
||||
"properties" => {
|
||||
"id" => { "type" => "string" },
|
||||
"title" => { "type" => %w{string null} },
|
||||
"desc" => { "type" => %w{string null} },
|
||||
"descriptions" => { "type" => %w{array} },
|
||||
"impact" => { "type" => "number" },
|
||||
"status" => {
|
||||
"enum" => %w{passed failed not_applicable not_reviewed error},
|
||||
"description" => Primitives.desc(Primitives::STRING, "The enhanced outcome status of the control"),
|
||||
},
|
||||
"refs" => REFS,
|
||||
"tags" => TAGS,
|
||||
"code" => { "type" => "string" },
|
||||
"source_location" => {
|
||||
"type" => "object",
|
||||
"properties" => {
|
||||
"ref" => { "type" => "string" },
|
||||
"line" => { "type" => "number" },
|
||||
},
|
||||
},
|
||||
"results" => { "type" => "array", "items" => RESULT },
|
||||
"waiver_data" => {
|
||||
"type" => "object",
|
||||
"properties" => {
|
||||
"skipped_due_to_waiver" => { "type" => "string" },
|
||||
"run" => { "type" => "boolean" },
|
||||
"message" => { "type" => "string" },
|
||||
"expiration_date" => { "type" => "string" },
|
||||
"justification" => { "type" => "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}.freeze
|
||||
|
||||
SUPPORTS = {
|
||||
"type" => "object",
|
||||
"additionalProperties" => false,
|
||||
|
@ -173,6 +210,45 @@ module Inspec
|
|||
},
|
||||
}.freeze
|
||||
|
||||
PROFILE_ENHANCED_OUTCOME = {
|
||||
"type" => "object",
|
||||
"additionalProperties" => false,
|
||||
"properties" => {
|
||||
"name" => { "type" => "string" },
|
||||
"version" => { "type" => "string", "optional" => true },
|
||||
"sha256" => { "type" => "string", "optional" => false },
|
||||
|
||||
"title" => { "type" => "string", "optional" => true },
|
||||
"maintainer" => { "type" => "string", "optional" => true },
|
||||
"copyright" => { "type" => "string", "optional" => true },
|
||||
"copyright_email" => { "type" => "string", "optional" => true },
|
||||
"license" => { "type" => "string", "optional" => true },
|
||||
"summary" => { "type" => "string", "optional" => true },
|
||||
"status" => { "type" => "string", "optional" => false },
|
||||
"status_message" => { "type" => "string", "optional" => true },
|
||||
# skip_message is deprecated, status_message should be used to store the reason for skipping
|
||||
"skip_message" => { "type" => "string", "optional" => true },
|
||||
|
||||
"supports" => {
|
||||
"type" => "array",
|
||||
"items" => SUPPORTS,
|
||||
"optional" => true,
|
||||
},
|
||||
"controls" => {
|
||||
"type" => "array",
|
||||
"items" => CONTROL_ENHANCED_OUTCOME,
|
||||
},
|
||||
"groups" => {
|
||||
"type" => "array",
|
||||
"items" => CONTROL_GROUP,
|
||||
},
|
||||
"attributes" => { # TODO: rename to inputs, refs #3802
|
||||
"type" => "array",
|
||||
# TODO: more detailed specification needed
|
||||
},
|
||||
},
|
||||
}.freeze
|
||||
|
||||
EXEC_JSON = {
|
||||
"type" => "object",
|
||||
"additionalProperties" => false,
|
||||
|
@ -187,6 +263,20 @@ module Inspec
|
|||
},
|
||||
}.freeze
|
||||
|
||||
EXEC_JSON_ENHANCED_OUTCOME = {
|
||||
"type" => "object",
|
||||
"additionalProperties" => false,
|
||||
"properties" => {
|
||||
"platform" => PLATFORM,
|
||||
"profiles" => {
|
||||
"type" => "array",
|
||||
"items" => PROFILE_ENHANCED_OUTCOME,
|
||||
},
|
||||
"statistics" => STATISTICS,
|
||||
"version" => { "type" => "string" },
|
||||
},
|
||||
}.freeze
|
||||
|
||||
MIN_CONTROL = {
|
||||
"type" => "object",
|
||||
"additionalProperties" => false,
|
||||
|
@ -228,6 +318,7 @@ module Inspec
|
|||
LIST = {
|
||||
"exec-json" => EXEC_JSON,
|
||||
"exec-jsonmin" => EXEC_JSONMIN,
|
||||
"exec-json-enhanced-outcome" => EXEC_JSON_ENHANCED_OUTCOME,
|
||||
"platforms" => PLATFORMS,
|
||||
}.freeze
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ module Inspec
|
|||
# Lists the potential values for a control result
|
||||
CONTROL_RESULT_STATUS = Primitives::SchemaType.new("Control Result Status", {
|
||||
"type" => "string",
|
||||
"enum" => %w{passed failed skipped error},
|
||||
}, [], "The status of a control. Should be one of 'passed', 'failed', 'skipped', or 'error'.")
|
||||
"enum" => %w{passed failed skipped},
|
||||
}, [], "The status of a control. Should be one of 'passed', 'failed', or 'skipped'.")
|
||||
|
||||
# Represents the statistics/result of a control"s execution
|
||||
CONTROL_RESULT = Primitives::SchemaType.new("Control Result", {
|
||||
|
@ -75,6 +75,36 @@ module Inspec
|
|||
},
|
||||
}, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT], "Describes a control and any findings it has.")
|
||||
|
||||
# Represents a control produced with enhanced outcomes option
|
||||
ENHANCED_OUTCOME_CONTROL = Primitives::SchemaType.new("Exec JSON Control", {
|
||||
"type" => "object",
|
||||
"additionalProperties" => true,
|
||||
"required" => %w{id title desc impact refs tags code source_location results},
|
||||
"properties" => {
|
||||
"id" => Primitives.desc(Primitives::STRING, "The id."),
|
||||
"title" => Primitives.desc({ "type" => %w{string null} }, "The title - is nullable."), # Nullable string
|
||||
"desc" => Primitives.desc({ "type" => %w{string null} }, "The description for the overarching control."),
|
||||
"descriptions" => Primitives.desc(Primitives.array(CONTROL_DESCRIPTION.ref), "A set of additional descriptions. Example: the 'fix' text."),
|
||||
"impact" => Primitives.desc(Primitives::IMPACT, "The impactfulness or severity."),
|
||||
"status" => {
|
||||
"enum" => %w{passed failed not_applicable not_reviewed error},
|
||||
"description" => Primitives.desc(Primitives::STRING, "The enhanced outcome status of the control"),
|
||||
},
|
||||
"refs" => Primitives.desc(Primitives.array(Primitives::REFERENCE.ref), "The set of references to external documents."),
|
||||
"tags" => Primitives.desc(Primitives::TAGS, "A set of tags - usually metadata."),
|
||||
"code" => Primitives.desc(Primitives::STRING, "The raw source code of the control. Note that if this is an overlay control, it does not include the underlying source code."),
|
||||
"source_location" => Primitives.desc(Primitives::SOURCE_LOCATION.ref, "The explicit location of the control within the source code."),
|
||||
"results" => Primitives.desc(Primitives.array(CONTROL_RESULT.ref), %q(
|
||||
The set of all tests within the control and their results and findings. Example:
|
||||
For Chef Inspec, if in the control's code we had the following:
|
||||
describe sshd_config do
|
||||
its('Port') { should cmp 22 }
|
||||
end
|
||||
The findings from this block would be appended to the results, as well as those of any other blocks within the control.
|
||||
)),
|
||||
},
|
||||
}, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT], "Describes a control and any findings it has.")
|
||||
|
||||
# Based loosely on https://docs.chef.io/inspec/profiles/ as of July 3, 2019
|
||||
# However, concessions were made to the reality of current reporters, specifically
|
||||
# with how description is omitted and version/inspec_version aren't as advertised online
|
||||
|
@ -112,6 +142,40 @@ module Inspec
|
|||
},
|
||||
}, [CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::SUPPORT], "Information on the set of controls assessed. Example: it can include the name of the Inspec profile and any findings.")
|
||||
|
||||
ENHANCED_OUTCOME_PROFILE = Primitives::SchemaType.new("Exec JSON Profile", {
|
||||
"type" => "object",
|
||||
"additionalProperties" => true,
|
||||
"required" => %w{name sha256 supports attributes groups controls},
|
||||
# Name is mandatory in inspec.yml.
|
||||
# supports, controls, groups, and attributes are always present, even if empty
|
||||
# sha256, status, status_message
|
||||
"properties" => {
|
||||
# These are provided in inspec.yml
|
||||
"name" => Primitives.desc(Primitives::STRING, "The name - must be unique."),
|
||||
"title" => Primitives.desc(Primitives::STRING, "The title - should be human readable."),
|
||||
"maintainer" => Primitives.desc(Primitives::STRING, "The maintainer(s)."),
|
||||
"copyright" => Primitives.desc(Primitives::STRING, "The copyright holder(s)."),
|
||||
"copyright_email" => Primitives.desc(Primitives::STRING, "The email address or other contact information of the copyright holder(s)."),
|
||||
"depends" => Primitives.desc(Primitives.array(Primitives::DEPENDENCY.ref), "The set of dependencies this profile depends on. Example: an overlay profile is dependent on another profile."),
|
||||
"parent_profile" => Primitives.desc(Primitives::STRING, "The name of the parent profile if the profile is a dependency of another."),
|
||||
"license" => Primitives.desc(Primitives::STRING, "The copyright license. Example: the full text or the name, such as 'Apache License, Version 2.0'."),
|
||||
"summary" => Primitives.desc(Primitives::STRING, "The summary. Example: the Security Technical Implementation Guide (STIG) header."),
|
||||
"version" => Primitives.desc(Primitives::STRING, "The version of the profile."),
|
||||
"supports" => Primitives.desc(Primitives.array(Primitives::SUPPORT.ref), "The set of supported platform targets."),
|
||||
"description" => Primitives.desc(Primitives::STRING, "The description - should be more detailed than the summary."),
|
||||
"inspec_version" => Primitives.desc(Primitives::STRING, "The version of Inspec."),
|
||||
|
||||
# These are generated at runtime, and all except status_message and skip_message are guaranteed
|
||||
"sha256" => Primitives.desc(Primitives::STRING, "The checksum of the profile."),
|
||||
"status" => Primitives.desc(Primitives::STRING, "The status. Example: loaded."), # enum? loaded, failed, skipped
|
||||
"status_message" => Primitives.desc(Primitives::STRING, "The reason for the status. Example: why it was skipped or failed to load."),
|
||||
"skip_message" => Primitives.desc(Primitives::STRING, "The reason for skipping if it was skipped."), # Deprecated field - status_message should be used instead.
|
||||
"controls" => Primitives.desc(Primitives.array(CONTROL.ref), "The set of controls including any findings."),
|
||||
"groups" => Primitives.desc(Primitives.array(Primitives::CONTROL_GROUP.ref), "A set of descriptions for the control groups. Example: the ids of the controls."),
|
||||
"attributes" => Primitives.desc(Primitives.array(Primitives::INPUT), "The input(s) or attribute(s) used in the run."),
|
||||
},
|
||||
}, [ENHANCED_OUTCOME_CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::SUPPORT], "Information on the set of controls assessed. Example: it can include the name of the Inspec profile and any findings.")
|
||||
|
||||
# Result of exec json. Top level value
|
||||
# TODO: Include the format of top level controls. This was omitted for lack of sufficient examples
|
||||
OUTPUT = Primitives::SchemaType.new("Exec JSON Output", {
|
||||
|
@ -125,6 +189,18 @@ module Inspec
|
|||
"version" => Primitives.desc(Primitives::STRING, "Version number of the tool that generated the findings. Example: '4.18.108' is a version of Chef InSpec."),
|
||||
},
|
||||
}, [Primitives::PLATFORM, PROFILE, Primitives::STATISTICS], "The top level value containing all of the results.")
|
||||
|
||||
ENHANCED_OUTCOME_OUTPUT = Primitives::SchemaType.new("Exec JSON Output", {
|
||||
"type" => "object",
|
||||
"additionalProperties" => true,
|
||||
"required" => %w{platform profiles statistics version},
|
||||
"properties" => {
|
||||
"platform" => Primitives.desc(Primitives::PLATFORM.ref, "Information on the platform the run from the tool that generated the findings was from. Example: the name of the operating system."),
|
||||
"profiles" => Primitives.desc(Primitives.array(PROFILE.ref), "Information on the run(s) from the tool that generated the findings. Example: the findings."),
|
||||
"statistics" => Primitives.desc(Primitives::STATISTICS.ref, "Statistics for the run(s) from the tool that generated the findings. Example: the runtime duration."),
|
||||
"version" => Primitives.desc(Primitives::STRING, "Version number of the tool that generated the findings. Example: '4.18.108' is a version of Chef InSpec."),
|
||||
},
|
||||
}, [Primitives::PLATFORM, ENHANCED_OUTCOME_PROFILE, Primitives::STATISTICS], "The top level value containing all of the results.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,6 +30,8 @@ module Inspec
|
|||
"profile-json" => OutputSchema.finalize(Schema::ProfileJson::PROFILE),
|
||||
"exec-json" => OutputSchema.finalize(Schema::ExecJson::OUTPUT),
|
||||
"exec-jsonmin" => OutputSchema.finalize(Schema::ExecJsonMin::OUTPUT),
|
||||
"profile-json-enhanced-outcomes" => OutputSchema.finalize(Schema::ProfileJson::ENHANCED_OUTCOME_PROFILE),
|
||||
"exec-json-enhanced-outcomes" => OutputSchema.finalize(Schema::ExecJson::ENHANCED_OUTCOME_OUTPUT),
|
||||
"platforms" => PLATFORMS,
|
||||
}.freeze
|
||||
|
||||
|
@ -37,7 +39,8 @@ module Inspec
|
|||
LIST.keys
|
||||
end
|
||||
|
||||
def self.json(name)
|
||||
def self.json(name, opts)
|
||||
name += "-enhanced-outcomes" if opts["enhanced_outcomes"]
|
||||
if !LIST.key?(name)
|
||||
raise("Cannot find schema #{name.inspect}.")
|
||||
elsif LIST[name].is_a?(Proc)
|
||||
|
|
|
@ -31,6 +31,28 @@ module Inspec
|
|||
},
|
||||
}, [CONTROL_DESCRIPTIONS, Primitives::REFERENCE, Primitives::SOURCE_LOCATION], "The set of all tests within the control.")
|
||||
|
||||
# Represents a control with enhanced outcomes status information
|
||||
ENHANCED_OUTCOME_CONTROL = Primitives::SchemaType.new("Profile JSON Control", {
|
||||
"type" => "object",
|
||||
"additionalProperties" => true,
|
||||
"required" => %w{id title desc impact tags code},
|
||||
"properties" => {
|
||||
"id" => Primitives.desc(Primitives::STRING, "The id."),
|
||||
"title" => Primitives.desc({ "type" => %w{string null} }, "The title - is nullable."),
|
||||
"desc" => Primitives.desc({ "type" => %w{string null} }, "The description for the overarching control."),
|
||||
"descriptions" => Primitives.desc(CONTROL_DESCRIPTIONS.ref, "A set of additional descriptions. Example: the 'fix' text."),
|
||||
"impact" => Primitives.desc(Primitives::IMPACT, "The impactfulness or severity."),
|
||||
"status" => {
|
||||
"enum" => %w{passed failed not_applicable not_reviewed error},
|
||||
"description" => Primitives.desc(Primitives::STRING, "The enhanced outcome status of the control"),
|
||||
},
|
||||
"refs" => Primitives.desc(Primitives.array(Primitives::REFERENCE.ref), "The set of references to external documents."),
|
||||
"tags" => Primitives.desc(Primitives::TAGS, "A set of tags - usually metadata."),
|
||||
"code" => Primitives.desc(Primitives::STRING, "The raw source code of the control. Note that if this is an overlay control, it does not include the underlying source code."),
|
||||
"source_location" => Primitives.desc(Primitives::SOURCE_LOCATION.ref, "The explicit location of the control within the source code."),
|
||||
},
|
||||
}, [CONTROL_DESCRIPTIONS, Primitives::REFERENCE, Primitives::SOURCE_LOCATION], "The set of all tests within the control.")
|
||||
|
||||
# A profile that has not been run.
|
||||
PROFILE = Primitives::SchemaType.new("Profile JSON Profile", {
|
||||
"type" => "object",
|
||||
|
@ -55,6 +77,30 @@ module Inspec
|
|||
"depends" => Primitives.desc(Primitives.array(Primitives::DEPENDENCY.ref), "The set of dependencies this profile depends on. Example: an overlay profile is dependent on another profile."), # Can have depends, but NOT a parentprofile
|
||||
},
|
||||
}, [Primitives::SUPPORT, CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::GENERATOR], "Information on the set of controls that can be assessed. Example: it can include the name of the Inspec profile.")
|
||||
|
||||
ENHANCED_OUTCOME_PROFILE = Primitives::SchemaType.new("Profile JSON Profile", {
|
||||
"type" => "object",
|
||||
"additionalProperties" => true, # Anything in the yaml will be put in here. LTTODO: Make this stricter!
|
||||
"required" => %w{name supports controls groups sha256},
|
||||
"properties" => {
|
||||
"name" => Primitives.desc(Primitives::STRING, "The name - must be unique."),
|
||||
"supports" => Primitives.desc(Primitives.array(Primitives::SUPPORT.ref), "The set of supported platform targets."),
|
||||
"controls" => Primitives.desc(Primitives.array(CONTROL.ref), "The set of controls - contains no findings as the assessment has not yet occurred."),
|
||||
"groups" => Primitives.desc(Primitives.array(Primitives::CONTROL_GROUP.ref), "A set of descriptions for the control groups. Example: the ids of the controls."),
|
||||
"inputs" => Primitives.desc(Primitives.array(Primitives::INPUT), "The input(s) or attribute(s) used to be in the run."),
|
||||
"sha256" => Primitives.desc(Primitives::STRING, "The checksum of the profile."),
|
||||
"status" => Primitives.desc(Primitives::STRING, "The status. Example: skipped."),
|
||||
"generator" => Primitives.desc(Primitives::GENERATOR.ref, "The tool that generated this file. Example: Chef Inspec."),
|
||||
"version" => Primitives.desc(Primitives::STRING, "The version of the profile."),
|
||||
|
||||
# Other properties possible in inspec docs, but that aren"t guaranteed
|
||||
"title" => Primitives.desc(Primitives::STRING, "The title - should be human readable."),
|
||||
"maintainer" => Primitives.desc(Primitives::STRING, "The maintainer(s)."),
|
||||
"copyright" => Primitives.desc(Primitives::STRING, "The copyright holder(s)."),
|
||||
"copyright_email" => Primitives.desc(Primitives::STRING, "The email address or other contract information of the copyright holder(s)."),
|
||||
"depends" => Primitives.desc(Primitives.array(Primitives::DEPENDENCY.ref), "The set of dependencies this profile depends on. Example: an overlay profile is dependent on another profile."), # Can have depends, but NOT a parentprofile
|
||||
},
|
||||
}, [Primitives::SUPPORT, ENHANCED_OUTCOME_CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::GENERATOR], "Information on the set of controls that can be assessed. Example: it can include the name of the Inspec profile.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,4 +27,12 @@ describe "inspec schema" do
|
|||
_(json_output["definitions"]["Control_Result"]["properties"]["resource_id"]).wont_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "validate schema of exec-json with enhanced_outcomes option" do
|
||||
it "contains resource_id key" do
|
||||
out = inspec("schema exec-json --enhanced-outcomes")
|
||||
json_output = JSON.parse(out.stdout)
|
||||
_(json_output["definitions"]["Exec_JSON_Control"]["properties"]["status"]).wont_be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue