inspec-iggy 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +8 -6
- data/README.md +91 -40
- data/inspec-iggy.gemspec +13 -13
- data/lib/inspec-iggy.rb +11 -2
- data/lib/inspec-iggy/cloudformation/cli_command.rb +92 -0
- data/lib/inspec-iggy/cloudformation/parser.rb +106 -0
- data/lib/inspec-iggy/inspec_helper.rb +62 -40
- data/lib/inspec-iggy/plugin.rb +43 -0
- data/lib/inspec-iggy/profile.rb +74 -0
- data/lib/inspec-iggy/terraform/cli_command.rb +94 -0
- data/lib/inspec-iggy/terraform/parser.rb +147 -0
- data/lib/inspec-iggy/version.rb +4 -4
- metadata +13 -16
- data/lib/inspec-iggy/cli.rb +0 -53
- data/lib/inspec-iggy/cloudformation.rb +0 -104
- data/lib/inspec-iggy/terraform.rb +0 -146
- data/test/bad.json +0 -6
- data/test/bjc-demo-aws-4.5.4.json +0 -851
- data/test/main.tf +0 -156
- data/test/outputs.tf +0 -11
- data/test/terraform.tfstate +0 -383
- data/test/variables.tf +0 -35
@@ -4,56 +4,78 @@
|
|
4
4
|
# Copyright:: 2018, Chef Software, Inc <legal@chef.io>
|
5
5
|
#
|
6
6
|
|
7
|
-
require
|
7
|
+
require 'inspec'
|
8
8
|
|
9
|
-
module
|
10
|
-
|
9
|
+
module InspecPlugins
|
10
|
+
module Iggy
|
11
|
+
class InspecHelper
|
12
|
+
# constants for the InSpec resources
|
13
|
+
RESOURCES = Inspec::Resource.registry.keys
|
11
14
|
|
12
|
-
|
13
|
-
|
15
|
+
# translate Terraform resource name to InSpec
|
16
|
+
TRANSLATED_RESOURCES = {
|
17
|
+
'aws_instance' => 'aws_ec2_instance',
|
18
|
+
# 'aws_route' => 'aws_route_table' # needs route_table_id instead of id
|
19
|
+
}.freeze
|
14
20
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
# # there really should be some way to get this directly from InSpec's resources
|
22
|
+
def self.resource_properties(resource)
|
23
|
+
# remove the common methods, in theory only leaving only unique InSpec properties
|
24
|
+
inspec_properties = Inspec::Resource.registry[resource].instance_methods - COMMON_PROPERTIES
|
25
|
+
# get InSpec properties by method names
|
26
|
+
inspec_properties.collect!(&:to_s)
|
27
|
+
Inspec::Log.debug "InspecHelper.resource_properties #{resource} properties = #{inspec_properties}"
|
20
28
|
|
21
|
-
|
22
|
-
|
23
|
-
# remove the common methods, in theory only leaving only unique InSpec properties
|
24
|
-
inspec_properties = Inspec::Resource.registry[resource].instance_methods - COMMON_PROPERTIES
|
25
|
-
# get InSpec properties by method names
|
26
|
-
inspec_properties.collect! { |x| x.to_s }
|
27
|
-
Inspec::Log.debug "Iggy::InspecHelper.resource_properties #{resource} properties = #{inspec_properties}"
|
28
|
-
|
29
|
-
inspec_properties
|
30
|
-
end
|
29
|
+
inspec_properties
|
30
|
+
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
32
|
+
def self.print_commands(extracted_profiles)
|
33
|
+
extracted_profiles.keys.each do |cmd|
|
34
|
+
type = extracted_profiles[cmd]['type']
|
35
|
+
url = extracted_profiles[cmd]['url']
|
36
|
+
key_name = extracted_profiles[cmd]['key_name']
|
37
|
+
if type == 'aws_instance'
|
38
|
+
ip = extracted_profiles[cmd]['public_ip']
|
39
|
+
puts "inspec exec #{url} -t ssh://#{ip} -i #{key_name}"
|
40
|
+
else
|
41
|
+
puts "inspec exec #{url} -t aws://us-west-2"
|
42
|
+
end
|
42
43
|
end
|
43
44
|
end
|
44
|
-
end
|
45
45
|
|
46
|
-
|
47
|
-
|
46
|
+
def self.tf_controls(title, generated_controls)
|
47
|
+
content = "# encoding: utf-8\n#\n\n"
|
48
48
|
|
49
|
-
|
49
|
+
content += "title \"#{title}: generated by Iggy v#{Iggy::VERSION}\"\n"
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
# write all controls
|
52
|
+
content + generated_controls.flatten.map(&:to_ruby).join("\n\n")
|
53
|
+
end
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
def self.cfn_controls(title, generated_controls, stack)
|
56
|
+
content = "# encoding: utf-8\n#\n\n"
|
57
|
+
|
58
|
+
content += "begin\n"
|
59
|
+
content += " awsclient = Aws::CloudFormation::Client.new()\n"
|
60
|
+
content += " cfn = awsclient.list_stack_resources({ stack_name: \"#{stack}\" }).to_hash\n"
|
61
|
+
content += " resources = {}\n"
|
62
|
+
content += " cfn[:stack_resource_summaries].each { |r| resources[r[:logical_resource_id]] = r[:physical_resource_id] }\n"
|
63
|
+
content += "rescue Exception => e\n"
|
64
|
+
content += " raise(e) unless @conf['profile'].check_mode\n"
|
65
|
+
content += "end\n\n"
|
66
|
+
|
67
|
+
content += "title \"#{title}: generated by Iggy v#{Iggy::VERSION}\"\n"
|
68
|
+
|
69
|
+
# get the controls, insert lookups for physical_resource_ids
|
70
|
+
controls = generated_controls.flatten.map(&:to_ruby).join("\n\n")
|
71
|
+
controls.gsub!(/\"resources\[/, 'resources["')
|
72
|
+
controls.gsub!(/\]\"/, '"]')
|
73
|
+
content + controls
|
74
|
+
end
|
75
|
+
|
76
|
+
# a hack for sure, finds common methods as proxy for InSpec properties
|
77
|
+
COMMON_PROPERTIES = Inspec::Resource.registry['aws_subnet'].instance_methods &
|
78
|
+
Inspec::Resource.registry['directory'].instance_methods
|
79
|
+
end
|
58
80
|
end
|
59
81
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Plugin Definition file
|
4
|
+
# The purpose of this file is to declare to InSpec what plugin_types (capabilities)
|
5
|
+
# are included in this plugin, and provide hooks that will load them as needed.
|
6
|
+
|
7
|
+
# It is important that this file load successfully and *quickly*.
|
8
|
+
# Your plugin's functionality may never be used on this InSpec run; so we keep things
|
9
|
+
# fast and light by only loading heavy things when they are needed.
|
10
|
+
|
11
|
+
require 'inspec/plugin/v2'
|
12
|
+
|
13
|
+
# The InspecPlugins namespace is where all plugins should declare themselves.
|
14
|
+
# The 'Inspec' capitalization is used throughout the InSpec source code; yes, it's
|
15
|
+
# strange.
|
16
|
+
module InspecPlugins
|
17
|
+
# Pick a reasonable namespace here for your plugin. A reasonable choice
|
18
|
+
# would be the CamelCase version of your plugin gem name.
|
19
|
+
module Iggy
|
20
|
+
class Plugin < ::Inspec.plugin(2)
|
21
|
+
# Internal machine name of the plugin. InSpec will use this in errors, etc.
|
22
|
+
plugin_name :'inspec-iggy'
|
23
|
+
|
24
|
+
cli_command :terraform do
|
25
|
+
# Calling this hook doesn't mean iggy is being executed - just
|
26
|
+
# that we should be ready to do so. So, load the file that defines the
|
27
|
+
# functionality.
|
28
|
+
# For example, InSpec will activate this hook when `inspec help` is
|
29
|
+
# executed, so that this plugin's usage message will be included in the help.
|
30
|
+
require 'inspec-iggy/terraform/cli_command'
|
31
|
+
|
32
|
+
# Having loaded our functionality, return a class that will let the
|
33
|
+
# CLI engine tap into it.
|
34
|
+
InspecPlugins::Iggy::Terraform::CliCommand
|
35
|
+
end
|
36
|
+
|
37
|
+
cli_command :cloudformation do
|
38
|
+
require 'inspec-iggy/cloudformation/cli_command'
|
39
|
+
InspecPlugins::Iggy::CloudFormation::CliCommand
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Matt Ray (<matt@chef.io>)
|
4
|
+
#
|
5
|
+
# Copyright:: 2018, Chef Software, Inc <legal@chef.io>
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
module InspecPlugins
|
11
|
+
module Iggy
|
12
|
+
class Profile
|
13
|
+
# match the output of 'inspec init profile'
|
14
|
+
def self.render_profile(cli_ui, options, source_file, controls)
|
15
|
+
name = options[:name]
|
16
|
+
overwrite_mode = options[:overwrite]
|
17
|
+
# Create new profile at /Users/mattray/ws/inspec-iggy/foobar
|
18
|
+
full_destination_root_path = Pathname.new(Dir.pwd).join(name)
|
19
|
+
cli_ui.plain_text "Create new profile at #{cli_ui.mark_text(full_destination_root_path)}"
|
20
|
+
if File.exist?(full_destination_root_path) && !overwrite_mode
|
21
|
+
cli_ui.plain_text "#{cli_ui.mark_text(full_destination_root_path)} exists already, use --overwrite"
|
22
|
+
cli_ui.exit(1)
|
23
|
+
end
|
24
|
+
# ensure that full_destination_root_path directory is available
|
25
|
+
FileUtils.mkdir_p(full_destination_root_path)
|
26
|
+
# * Create directory controls
|
27
|
+
cli_ui.li "Create directory #{cli_ui.mark_text("#{name}/controls")}"
|
28
|
+
FileUtils.mkdir_p("#{name}/controls")
|
29
|
+
render_readme_md(cli_ui, name, source_file)
|
30
|
+
render_inspec_yml(cli_ui, name, source_file, options)
|
31
|
+
render_controls_rb(cli_ui, name, controls)
|
32
|
+
end
|
33
|
+
|
34
|
+
# * Create file README.md
|
35
|
+
def self.render_readme_md(cli_ui, name, source_file)
|
36
|
+
render_file = "#{name}/README.md"
|
37
|
+
cli_ui.li "Create file #{cli_ui.mark_text(render_file)}"
|
38
|
+
f = File.new(render_file, 'w')
|
39
|
+
f.puts("# #{name}")
|
40
|
+
f.puts
|
41
|
+
f.puts("This profile was generated by InSpec-Iggy v#{Iggy::VERSION} from the #{source_file} source file.")
|
42
|
+
f.close
|
43
|
+
end
|
44
|
+
|
45
|
+
# * Create file inspec.yml
|
46
|
+
def self.render_inspec_yml(cli_ui, name, source_file, options)
|
47
|
+
render_file = "#{name}/inspec.yml"
|
48
|
+
cli_ui.li "Create file #{cli_ui.mark_text(render_file)}"
|
49
|
+
yml = {}
|
50
|
+
yml['name'] = name
|
51
|
+
yml['title'] = options[:title]
|
52
|
+
yml['maintainer'] = options[:maintainer]
|
53
|
+
yml['copyright'] = options[:copyright]
|
54
|
+
yml['copyright_email'] = options[:email]
|
55
|
+
yml['license'] = options[:license]
|
56
|
+
yml['summary'] = options[:summary]
|
57
|
+
yml['version'] = options[:version]
|
58
|
+
yml['description'] = "Generated by InSpec-Iggy v#{Iggy::VERSION} from the #{source_file} source file."
|
59
|
+
f = File.new(render_file, 'w')
|
60
|
+
f.write(yml.to_yaml)
|
61
|
+
f.close
|
62
|
+
end
|
63
|
+
|
64
|
+
# * Create file controls/example.rb
|
65
|
+
def self.render_controls_rb(cli_ui, name, controls)
|
66
|
+
render_file = "#{name}/controls/controls.rb"
|
67
|
+
cli_ui.li "Create file #{cli_ui.mark_text(render_file)}"
|
68
|
+
f = File.new(render_file, 'w')
|
69
|
+
f.write(controls)
|
70
|
+
f.close
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Author:: Matt Ray (<matt@chef.io>)
|
4
|
+
#
|
5
|
+
# Copyright:: 2018, Chef Software, Inc <legal@chef.io>
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'inspec/plugin/v2'
|
9
|
+
|
10
|
+
require 'inspec-iggy/version'
|
11
|
+
require 'inspec-iggy/profile'
|
12
|
+
require 'inspec-iggy/terraform/parser'
|
13
|
+
|
14
|
+
module InspecPlugins::Iggy
|
15
|
+
module Terraform
|
16
|
+
class CliCommand < Inspec.plugin(2, :cli_command)
|
17
|
+
subcommand_desc 'terraform SUBCOMMAND ...', 'Extract or generate InSpec from Terraform'
|
18
|
+
|
19
|
+
# Thor.map(Hash) allows you to make aliases for commands.
|
20
|
+
map('-v' => 'version') # Treat `inspec terraform -v`` as `inspec terraform version`
|
21
|
+
map('--version' => 'version') # Treat `inspec terraform -version`` as `inspec terraform version`
|
22
|
+
|
23
|
+
desc 'version', 'Display version information', hide: true
|
24
|
+
def version
|
25
|
+
say("Iggy v#{InspecPlugins::Iggy::VERSION}")
|
26
|
+
end
|
27
|
+
|
28
|
+
option :debug,
|
29
|
+
desc: 'Verbose debugging messages',
|
30
|
+
type: :boolean,
|
31
|
+
default: false
|
32
|
+
|
33
|
+
option :copyright,
|
34
|
+
desc: 'Name of the copyright holder',
|
35
|
+
default: 'The Authors'
|
36
|
+
|
37
|
+
option :email,
|
38
|
+
desc: 'Email address of the author',
|
39
|
+
default: 'you@example.com'
|
40
|
+
|
41
|
+
option :license,
|
42
|
+
desc: 'License for the profile',
|
43
|
+
default: 'Apache-2.0'
|
44
|
+
|
45
|
+
option :maintainer,
|
46
|
+
desc: 'Name of the copyright holder',
|
47
|
+
default: 'The Authors'
|
48
|
+
|
49
|
+
option :summary,
|
50
|
+
desc: 'One line summary for the profile',
|
51
|
+
default: 'An InSpec Compliance Profile'
|
52
|
+
|
53
|
+
option :title,
|
54
|
+
desc: 'Human-readable name for the profile',
|
55
|
+
default: 'InSpec Profile'
|
56
|
+
|
57
|
+
option :version,
|
58
|
+
desc: 'Specify the profile version',
|
59
|
+
default: '0.1.0'
|
60
|
+
|
61
|
+
option :overwrite,
|
62
|
+
desc: 'Overwrites existing profile directory',
|
63
|
+
type: :boolean,
|
64
|
+
default: false
|
65
|
+
|
66
|
+
option :name,
|
67
|
+
aliases: '-n',
|
68
|
+
required: true,
|
69
|
+
desc: 'Name of profile to be generated'
|
70
|
+
|
71
|
+
option :tfstate,
|
72
|
+
aliases: '-t',
|
73
|
+
desc: 'Specify path to the input terraform.tfstate',
|
74
|
+
default: 'terraform.tfstate'
|
75
|
+
|
76
|
+
desc 'generate [options]', 'Generate InSpec compliance controls from terraform.tfstate'
|
77
|
+
def generate
|
78
|
+
Inspec::Log.level = :debug if options[:debug]
|
79
|
+
generated_controls = InspecPlugins::Iggy::Terraform::Parser.parse_generate(options[:tfstate])
|
80
|
+
printable_controls = InspecPlugins::Iggy::InspecHelper.tf_controls(options[:title], generated_controls)
|
81
|
+
InspecPlugins::Iggy::Profile.render_profile(self, options, options[:tfstate], printable_controls)
|
82
|
+
exit 0
|
83
|
+
end
|
84
|
+
|
85
|
+
desc 'extract [options]', 'Extract tagged InSpec profiles from terraform.tfstate'
|
86
|
+
def extract
|
87
|
+
Inspec::Log.level = :debug if options[:debug]
|
88
|
+
extracted_profiles = InspecPlugins::Iggy::Terraform::Parser.parse_extract(options[:tfstate])
|
89
|
+
puts InspecPlugins::Iggy::InspecHelper.print_commands(extracted_profiles)
|
90
|
+
exit 0
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Ray (<matt@chef.io>)
|
3
|
+
#
|
4
|
+
# Copyright:: 2018, Chef Software, Inc <legal@chef.io>
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
require 'inspec/objects/control'
|
10
|
+
require 'inspec/objects/ruby_helper'
|
11
|
+
require 'inspec/objects/describe'
|
12
|
+
|
13
|
+
require 'inspec-iggy/inspec_helper'
|
14
|
+
|
15
|
+
module InspecPlugins::Iggy::Terraform
|
16
|
+
class Parser
|
17
|
+
# makes it easier to change out later
|
18
|
+
TAG_NAME = 'iggy_name_'.freeze
|
19
|
+
TAG_URL = 'iggy_url_'.freeze
|
20
|
+
|
21
|
+
# boilerplate tfstate parsing
|
22
|
+
def self.parse_tfstate(file)
|
23
|
+
Inspec::Log.debug "Iggy::Terraform.parse_tfstate file = #{file}"
|
24
|
+
begin
|
25
|
+
unless File.file?(file)
|
26
|
+
STDERR.puts "ERROR: #{file} is an invalid file, please check your path."
|
27
|
+
exit(-1)
|
28
|
+
end
|
29
|
+
JSON.parse(File.read(file))
|
30
|
+
rescue JSON::ParserError => e
|
31
|
+
STDERR.puts e.message
|
32
|
+
STDERR.puts "ERROR: Parsing error in #{file}."
|
33
|
+
exit(-1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# parse through the JSON for the tagged Resources
|
38
|
+
def self.parse_extract(file) # rubocop:disable Metrics/AbcSize
|
39
|
+
tfstate = parse_tfstate(file)
|
40
|
+
# InSpec profiles extracted
|
41
|
+
extracted_profiles = {}
|
42
|
+
|
43
|
+
# iterate over the resources
|
44
|
+
tf_resources = tfstate['modules'][0]['resources']
|
45
|
+
tf_resources.keys.each do |tf_res|
|
46
|
+
tf_res_id = tf_resources[tf_res]['primary']['id']
|
47
|
+
|
48
|
+
# get the attributes, see if any of them have a tagged profile attached
|
49
|
+
tf_resources[tf_res]['primary']['attributes'].keys.each do |attr|
|
50
|
+
next unless attr.start_with?('tags.' + TAG_NAME)
|
51
|
+
Inspec::Log.debug "Iggy::Terraform.parse_extract tf_res = #{tf_res} attr = #{attr} MATCHED TAG"
|
52
|
+
# get the URL and the name of the profiles
|
53
|
+
name = attr.split(TAG_NAME)[1]
|
54
|
+
url = tf_resources[tf_res]['primary']['attributes']["tags.#{TAG_URL}#{name}"]
|
55
|
+
if tf_res.start_with?('aws_vpc') # should this be VPC or subnet?
|
56
|
+
# if it's a VPC, store it as the VPC id + name
|
57
|
+
key = tf_res_id + ':' + name
|
58
|
+
Inspec::Log.debug "Iggy::Terraform.parse_extract aws_vpc tagged with InSpec #{key}"
|
59
|
+
extracted_profiles[key] = {
|
60
|
+
'type' => 'aws_vpc',
|
61
|
+
'az' => 'us-west-2',
|
62
|
+
'url' => url,
|
63
|
+
}
|
64
|
+
elsif tf_res.start_with?('aws_instance')
|
65
|
+
# if it's a node, get information about the IP and SSH/WinRM
|
66
|
+
key = tf_res_id + ':' + name
|
67
|
+
Inspec::Log.debug "Iggy::Terraform.parse_extract aws_instance tagged with InSpec #{key}"
|
68
|
+
extracted_profiles[key] = {
|
69
|
+
'type' => 'aws_instance',
|
70
|
+
'public_ip' => tf_resources[tf_res]['primary']['attributes']['public_ip'],
|
71
|
+
'key_name' => tf_resources[tf_res]['primary']['attributes']['key_name'],
|
72
|
+
'url' => url,
|
73
|
+
}
|
74
|
+
else
|
75
|
+
# should generic AWS just be the default except for instances?
|
76
|
+
STDERR.puts "ERROR: #{file} #{tf_res_id} has an InSpec-tagged resource but #{tf_res} is currently unsupported."
|
77
|
+
exit(-1)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
Inspec::Log.debug "Iggy::Terraform.parse_extract extracted_profiles = #{extracted_profiles}"
|
82
|
+
extracted_profiles
|
83
|
+
end
|
84
|
+
|
85
|
+
# parse through the JSON and generate InSpec controls
|
86
|
+
def self.parse_generate(file) # rubocop:disable all
|
87
|
+
tfstate = parse_tfstate(file)
|
88
|
+
absolutename = File.absolute_path(file)
|
89
|
+
|
90
|
+
# InSpec controls generated
|
91
|
+
generated_controls = []
|
92
|
+
|
93
|
+
# iterate over the resources
|
94
|
+
tfstate['modules'].each do |m|
|
95
|
+
tf_resources = m['resources']
|
96
|
+
tf_resources.keys.each do |tf_res|
|
97
|
+
tf_res_type = tf_resources[tf_res]['type']
|
98
|
+
|
99
|
+
# add translation layer
|
100
|
+
if InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES.key?(tf_res_type)
|
101
|
+
Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} #{InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[tf_res_type]} TRANSLATED"
|
102
|
+
tf_res_type = InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[tf_res_type]
|
103
|
+
end
|
104
|
+
|
105
|
+
# does this match an InSpec resource?
|
106
|
+
if InspecPlugins::Iggy::InspecHelper::RESOURCES.include?(tf_res_type)
|
107
|
+
Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} MATCH"
|
108
|
+
tf_res_id = tf_resources[tf_res]['primary']['id']
|
109
|
+
|
110
|
+
# insert new control based off the resource's ID
|
111
|
+
ctrl = Inspec::Control.new
|
112
|
+
ctrl.id = "#{tf_res_type}::#{tf_res_id}"
|
113
|
+
ctrl.title = "InSpec-Iggy #{tf_res_type}::#{tf_res_id}"
|
114
|
+
ctrl.descriptions[:default] = "#{tf_res_type}::#{tf_res_id} from the source file #{absolutename}\nGenerated by InSpec-Iggy v#{InspecPlugins::Iggy::VERSION}"
|
115
|
+
ctrl.impact = '1.0'
|
116
|
+
|
117
|
+
describe = Inspec::Describe.new
|
118
|
+
# describes the resourde with the id as argument
|
119
|
+
describe.qualifier.push([tf_res_type, tf_res_id])
|
120
|
+
|
121
|
+
# ensure the resource exists
|
122
|
+
describe.add_test(nil, 'exist', nil)
|
123
|
+
|
124
|
+
# if there's a match, see if there are matching InSpec properties
|
125
|
+
inspec_properties = InspecPlugins::Iggy::InspecHelper.resource_properties(tf_res_type)
|
126
|
+
tf_resources[tf_res]['primary']['attributes'].keys.each do |attr|
|
127
|
+
if inspec_properties.member?(attr)
|
128
|
+
Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} MATCH"
|
129
|
+
value = tf_resources[tf_res]['primary']['attributes'][attr]
|
130
|
+
describe.add_test(attr, 'eq', value)
|
131
|
+
else
|
132
|
+
Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} SKIP"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
ctrl.add_test(describe)
|
137
|
+
generated_controls.push(ctrl)
|
138
|
+
else
|
139
|
+
Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} SKIP"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
Inspec::Log.debug "Iggy::Terraform.parse_generate generated_controls = #{generated_controls}"
|
144
|
+
generated_controls
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|