inspec-iggy 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|