inspec-iggy 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3dc2db85eac33aa51e03baf7f0df30858a7f1815
4
+ data.tar.gz: e44f8c04a3998030335b6a2dcf2ff225c7b18d4f
5
+ SHA512:
6
+ metadata.gz: 9c8086684b141e0590b9a55f1b135922a4c9f4f7df3c3c32ec6422c890c7fe24a61443b16eac2e35bedb02f3a031a7efc7586a81d378addcd4c0d52d5d6de037
7
+ data.tar.gz: cda93559f8d2c47aaa4447d51d6f2151a006e3249e1297f379d041402c0f168f209acfda6003e42e30859ad901063e1aa0c5973127306ef7fdfec29ef7c058de
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+ source "http://rubygems.org"
3
+
4
+ gemspec
5
+
6
+ group :test do
7
+ gem "bundler"
8
+ gem "rake"
9
+ gem "chefstyle"
10
+ end
11
+
12
+ group :tools do
13
+ gem "github_changelog_generator"
14
+ gem "rb-readline"
15
+ end
@@ -0,0 +1,114 @@
1
+ # Description #
2
+
3
+ InSpec-Iggy (InSpec Generate -> "IG" -> "Iggy") is an [InSpec](https://inspec.io) plugin for generating compliance controls and profiles from [Terraform](https://terraform.io) ```tfstate``` files. Iggy generates InSpec AWS controls by mapping Terraform resources to InSpec resources. You may also use tags to annotate your Terraform scripts to specify which compliance profiles to be used and Iggy will create a profile including those dependencies.
4
+
5
+ Iggy was originally a stand-alone CLI inspired by Christoph Hartmann's [inspec-verify-provision](https://github.com/chris-rock/inspec-verify-provision) and the blog post on testing [Terraform with InSpec](http://lollyrock.com/articles/inspec-terraform/).
6
+
7
+ The [CHANGELOG.md](https://github.com/mattray/iggy/blob/master/CHANGELOG.md) covers current, previous and future development milestones and contains the features backlog.
8
+
9
+ # Requirements #
10
+
11
+ Iggy generates compliance profiles for InSpec 2, which includes the AWS and Azure resources. Because resources are continuing to be added to InSpec, you may want the latest version to support as many resource coverage as possible.
12
+
13
+ Written and tested with Ruby 2.4.4 (or whatever InSpec 2.0 supports).
14
+
15
+ # Installation #
16
+
17
+ `inspec-iggy` is a plugin for InSpec and may be installed as follows
18
+
19
+ ```bash
20
+ # install InSpec
21
+ gem install inspec
22
+ gem install inspec-iggy
23
+ inspec terraform version
24
+ ```
25
+
26
+ ## * for development: ##
27
+
28
+ ```bash
29
+ # Install `inspec-iggy` via a symlink:
30
+ git clone git@github.com:inspec/inspec-iggy ~/inspec-iggy
31
+ mkdir -p ~/.inspec/plugins
32
+ ln -s ~/inspec-iggy/ ~/.inspec/plugins/inspec-iggy
33
+ inspec terraform version
34
+ ```
35
+
36
+ ## * or build a gem: ##
37
+
38
+ ```bash
39
+ # Build the `inspec-iggy` then install:
40
+ git clone https://github.com/inspec/inspec-iggy && cd inspec-iggy && gem build *gemspec && gem install *gem
41
+ inspec terraform version
42
+ ```
43
+
44
+ # InSpec Terraform Generate #
45
+
46
+ inspec terraform generate --tfstate terraform.tfstate
47
+
48
+ Iggy dynamically pulls the available AWS resources from InSpec and attempts to map them to the Terraform resources. Newer versions of InSpec may provide additional coverage.
49
+
50
+ # InSpec Terraform Extract (EXPERIMENTAL)#
51
+
52
+ inspec terraform extract --tfstate terraform.tfstate
53
+
54
+ ## Tagging Profiles for Extract ##
55
+
56
+ Compliance profiles are added to the Terraform Resource to be tested. The current 2 options are the ```aws_vpc``` or the ```aws_instance```. By tagging the ```aws_vpc``` you are specifying that the test is against the AWS API rather than individual machines. AWS instances tagged with compliance profiles will attempt to form command lines for ```inspec exec``` against them.
57
+
58
+ ### Tagging Format ###
59
+
60
+ Given there is not support for lists within AWS tags, we use the convention of starting our tag names with ```inspec_name_``` and ```inspec_url_```. These are extracted and split to identify the relevant compliance profiles to run.
61
+
62
+ ```
63
+ tags {
64
+ iggy_name_apache_baseline = "apache-baseline",
65
+ iggy_url_apache_baseline = "https://github.com/dev-sec/apache-baseline",
66
+ iggy_name_linux_baseline = "linux-baseline",
67
+ iggy_url_linux_baseline = "https://github.com/dev-sec/linux-baseline"
68
+ }
69
+ ```
70
+
71
+ ### Potential Enhancements ###
72
+
73
+ The current tagging for extraction implementation is directly tied to AWS. Other platforms such as Azure undoubtedly behave differently. Longterm this functionality should probably be turned into a Terraform Provider with predefined outputs.
74
+
75
+ Subnet might be a better choice for tagging than VPCs, given they list the AZ.
76
+
77
+ Currently it only supports URL-based compliance profiles. InSpec supports other formats (git, path, supermarket, compliance).
78
+
79
+ inspec exec https://github.com/dev-sec/linux-baseline -t ssh://clckwrk@52.33.203.34 -i ~/.ssh/mattray-apac
80
+
81
+ # CloudFormation Support #
82
+
83
+ **CloudFormation support has been started, but it is incomplete while focusing on Terraform.** Here is an example of the current output, note that it's not tied to an actual deployed CloudFormation Stack, so that will need to be provided for the entry point of testing.
84
+ https://gist.github.com/c4d6eda82dfb25502ef381cc631a1edd
85
+
86
+ # Testing #
87
+
88
+ Iggy uses [RSpec](http://rspec.info/) for testing. You should run the following before committing.
89
+
90
+ $ rspec
91
+
92
+ For style
93
+
94
+ $ chefstyle .
95
+
96
+ # License and Author #
97
+
98
+ | | |
99
+ |:---------------|:------------------------------------------|
100
+ | **Author** | Matt Ray (<matt@chef.io>) |
101
+ | **Copyright:** | Copyright (c) 2017 Chef Software Inc. |
102
+ | **License:** | Apache License, Version 2.0 |
103
+
104
+ Licensed under the Apache License, Version 2.0 (the "License");
105
+ you may not use this file except in compliance with the License.
106
+ You may obtain a copy of the License at
107
+
108
+ http://www.apache.org/licenses/LICENSE-2.0
109
+
110
+ Unless required by applicable law or agreed to in writing, software
111
+ distributed under the License is distributed on an "AS IS" BASIS,
112
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
113
+ See the License for the specific language governing permissions and
114
+ limitations under the License.
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require "inspec-iggy/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "inspec-iggy"
9
+ spec.version = Iggy::VERSION
10
+ spec.authors = ["Matt Ray"]
11
+ spec.email = ["matt@chef.io"]
12
+ spec.summary = "InSpec plugin to generate InSpec compliance profiles from Terraform."
13
+ spec.description = "Generate InSpec compliance profiles from Terraform by tagging instances and mapping Terraform to InSpec."
14
+ spec.homepage = "https://github.com/inspec/inspec-iggy"
15
+ spec.license = "Apache-2.0"
16
+
17
+ spec.files = %w{
18
+ README.md inspec-iggy.gemspec Gemfile
19
+ } + Dir.glob(
20
+ "{bin,docs,examples,lib,tasks,test}/**/*", File::FNM_DOTMATCH
21
+ ).reject { |f| File.directory?(f) }
22
+
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_dependency "inspec", ">=2.0", "<3.0.0"
26
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ libdir = File.dirname(__FILE__)
4
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
5
+
6
+ require "inspec-iggy/cli"
7
+ require "inspec-iggy/version"
@@ -0,0 +1,53 @@
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/plugins"
9
+ require "thor"
10
+
11
+ require "inspec-iggy/terraform"
12
+
13
+ module Iggy
14
+ class CLI < Thor
15
+ namespace "terraform"
16
+
17
+ map %w{-v --version} => "version"
18
+
19
+ desc "version", "Display version information", hide: true
20
+ def version
21
+ say("Iggy v#{Iggy::VERSION}")
22
+ end
23
+
24
+ class_option :tfstate,
25
+ :aliases => "-t",
26
+ :desc => "Specify path to the input terraform.tfstate",
27
+ :default => "terraform.tfstate"
28
+
29
+ class_option :debug,
30
+ :desc => "Verbose debugging messages",
31
+ :type => :boolean,
32
+ :default => false
33
+
34
+ desc "generate [options]", "Generate InSpec compliance controls from terraform.tfstate"
35
+ def generate
36
+ Inspec::Log.level = :debug if options[:debug]
37
+ generated_controls = Iggy::Terraform.parse_generate(options[:tfstate])
38
+ # let's just generate a control file with a set of controls for now
39
+ Iggy::InspecHelper.print_controls(options[:tfstate], generated_controls)
40
+ exit 0
41
+ end
42
+
43
+ desc "extract [options]", "Extract tagged InSpec profiles from terraform.tfstate"
44
+ def extract
45
+ Inspec::Log.level = :debug if options[:debug]
46
+ extracted_profiles = Iggy::Terraform.parse_extract(options[:tfstate])
47
+ Iggy::InspecHelper.print_commands(extracted_profiles)
48
+ exit 0
49
+ end
50
+ end
51
+
52
+ Inspec::Plugins::CLI.add_subcommand(CLI, "terraform", "terraform SUBCOMMAND ...", "Extract or generate InSpec from Terraform", {})
53
+ end
@@ -0,0 +1,104 @@
1
+ #
2
+ # Author:: Matt Ray (<matt@chef.io>)
3
+ #
4
+ # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
5
+ #
6
+
7
+ require "iggy"
8
+
9
+ require "json"
10
+ require "thor"
11
+
12
+ module Iggy
13
+ class CloudFormation < Thor
14
+ option :template,
15
+ :aliases => "-t",
16
+ :desc => "Specify path to the input CloudFormation template"
17
+
18
+ option :debug,
19
+ :desc => "Verbose debugging messages",
20
+ :type => :boolean,
21
+ :default => false
22
+
23
+ desc "generate [options]", "Generate InSpec compliance controls from CloudFormation template"
24
+ long_desc <<-LONGDESC
25
+ Reads in a CloudFormation JSON file and generates matching InSpec compliance controls.
26
+ LONGDESC
27
+ def generate
28
+ Iggy::Log.level = :debug if options[:debug]
29
+ Iggy::Log.debug "CloudFormation.generate file = #{options[:template]}"
30
+ # hash of generated controls
31
+ generated_controls = parse_generate(options[:template])
32
+ Iggy::Log.debug "CloudFormation.generate generated_controls = #{generated_controls}"
33
+ # let's just generate a control file with a set of controls for now
34
+ Iggy::Inspec.print_controls(options[:template], generated_controls)
35
+ exit 0
36
+ end
37
+
38
+ private
39
+
40
+ def parse_generate(file)
41
+ Iggy::Log.debug "CloudFormation.parse_generate file = #{file}"
42
+ begin
43
+ unless File.file?(file)
44
+ STDERR.puts "ERROR: #{file} is an invalid file, please check your path."
45
+ exit(-1)
46
+ end
47
+ template = JSON.parse(File.read(file))
48
+ rescue JSON::ParserError => e
49
+ STDERR.puts e.message
50
+ STDERR.puts "ERROR: Parsing error in #{file}."
51
+ exit(-1)
52
+ end
53
+ basename = File.basename(file)
54
+ absolutename = File.absolute_path(file)
55
+
56
+ # InSpec controls generated
57
+ generated_controls = {}
58
+
59
+ # iterate over the resources
60
+ cfn_resources = template["Resources"]
61
+ # iterate over the Resources, use these as IDs?
62
+ cfn_resources.keys.each do |cfn_res|
63
+ # split out the last ::, these are all AWS
64
+ cfn_res_type = "aws_" + cfn_resources[cfn_res]["Type"].split("::").last.downcase
65
+
66
+ # does this match an InSpec resource?
67
+ if Inspec::RESOURCES.include?(cfn_res_type)
68
+ Iggy::Log.debug "CloudFormation.parse_generate cfn_res_type = #{cfn_res_type} MATCH"
69
+ # insert new control based off the resource's ID
70
+ generated_controls[cfn_res] = {}
71
+ generated_controls[cfn_res]["name"] = "#{cfn_res_type}::#{cfn_res}"
72
+ generated_controls[cfn_res]["title"] = "Iggy #{basename} #{cfn_res_type}::#{cfn_res}"
73
+ generated_controls[cfn_res]["desc"] = "#{cfn_res_type}::#{cfn_res} from the source file #{absolutename}\nGenerated by Iggy v#{Iggy::VERSION}"
74
+ generated_controls[cfn_res]["impact"] = "1.0"
75
+ generated_controls[cfn_res]["resource"] = cfn_res_type
76
+ generated_controls[cfn_res]["parameter"] = cfn_res
77
+ generated_controls[cfn_res]["tests"] = []
78
+ generated_controls[cfn_res]["tests"][0] = "it { should exist }"
79
+
80
+ # if there's a match, see if there are matching InSpec properties
81
+ inspec_properties = Iggy::Inspec.resource_properties(cfn_res_type)
82
+
83
+ cfn_resources[cfn_res]["Properties"].keys.each do |attr|
84
+ # insert '_' on the CamelCase to get camel_case
85
+ attr_split = attr.split /(?=[A-Z])/
86
+ property = attr_split.join("_").downcase
87
+ if inspec_properties.member?(property)
88
+ Iggy::Log.debug "CloudFormation.parse_generate #{cfn_res_type} inspec_property = #{property} MATCH"
89
+ value = cfn_resources[cfn_res]["Properties"][attr]
90
+ # skip the {"Ref"=>"VPC"} for now
91
+ generated_controls[cfn_res]["tests"].push("its('#{property}') { should cmp '#{value}' }") unless value.is_a? Hash
92
+ else
93
+ Iggy::Log.debug "CloudFormation.parse_generate #{cfn_res_type} inspec_property = #{property} SKIP"
94
+ end
95
+ end
96
+ else
97
+ Iggy::Log.debug "CloudFormation.parse_generate cfn_res_type = #{cfn_res_type} SKIP"
98
+ end
99
+ end
100
+ generated_controls
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,59 @@
1
+ #
2
+ # Author:: Matt Ray (<matt@chef.io>)
3
+ #
4
+ # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
5
+ #
6
+
7
+ require "inspec"
8
+
9
+ module Iggy
10
+ class InspecHelper
11
+
12
+ # constants for the InSpec resources
13
+ RESOURCES = Inspec::Resource.registry.keys
14
+
15
+ # translate Terraform resource name to InSpec
16
+ TERRAFORM_RESOURCES = {
17
+ "aws_instance" => "aws_ec2_instance",
18
+ # 'aws_route' => 'aws_route_table' # needs route_table_id instead of id
19
+ }
20
+
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! { |x| x.to_s }
27
+ Inspec::Log.debug "Iggy::InspecHelper.resource_properties #{resource} properties = #{inspec_properties}"
28
+
29
+ inspec_properties
30
+ end
31
+
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
43
+ end
44
+ end
45
+
46
+ def self.print_controls(file, generated_controls)
47
+ puts "# encoding: utf-8\n#"
48
+
49
+ puts "\ntitle '#{File.absolute_path(file)} controls generated by Iggy v#{Iggy::VERSION}'"
50
+
51
+ # write all controls
52
+ puts generated_controls.flatten.map(&:to_ruby).join("\n\n")
53
+ end
54
+
55
+ # a hack for sure, finds common methods as proxy for InSpec properties
56
+ COMMON_PROPERTIES = Inspec::Resource.registry["aws_subnet"].instance_methods &
57
+ Inspec::Resource.registry["directory"].instance_methods
58
+ end
59
+ end
@@ -0,0 +1,146 @@
1
+ #
2
+ # Author:: Matt Ray (<matt@chef.io>)
3
+ #
4
+ # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
5
+ #
6
+
7
+ require "inspec/objects/control"
8
+ require "inspec/objects/ruby_helper"
9
+ require "inspec/objects/describe"
10
+
11
+ require "inspec-iggy/inspec_helper"
12
+
13
+ module Iggy
14
+ class Terraform
15
+
16
+ # makes it easier to change out later
17
+ TAG_NAME = "iggy_name_"
18
+ TAG_URL = "iggy_url_"
19
+
20
+ # boilerplate tfstate parsing
21
+ def self.parse_tfstate(file)
22
+ Inspec::Log.debug "Iggy::Terraform.parse_tfstate file = #{file}"
23
+ begin
24
+ unless File.file?(file)
25
+ STDERR.puts "ERROR: #{file} is an invalid file, please check your path."
26
+ exit(-1)
27
+ end
28
+ tfstate = JSON.parse(File.read(file))
29
+ rescue JSON::ParserError => e
30
+ STDERR.puts e.message
31
+ STDERR.puts "ERROR: Parsing error in #{file}."
32
+ exit(-1)
33
+ end
34
+ end
35
+
36
+ # parse through the JSON for the tagged Resources
37
+ def self.parse_extract(file)
38
+ tfstate = parse_tfstate(file)
39
+ # InSpec profiles extracted
40
+ extracted_profiles = {}
41
+
42
+ # iterate over the resources
43
+ tf_resources = tfstate["modules"][0]["resources"]
44
+ tf_resources.keys.each do |tf_res|
45
+ tf_res_id = tf_resources[tf_res]["primary"]["id"]
46
+
47
+ # get the attributes, see if any of them have a tagged profile attached
48
+ tf_resources[tf_res]["primary"]["attributes"].keys.each do |attr|
49
+ next unless attr.start_with?("tags." + TAG_NAME)
50
+ Inspec::Log.debug "Iggy::Terraform.parse_extract tf_res = #{tf_res} attr = #{attr} MATCHED TAG"
51
+ # get the URL and the name of the profiles
52
+ name = attr.split(TAG_NAME)[1]
53
+ url = tf_resources[tf_res]["primary"]["attributes"]["tags.#{TAG_URL}#{name}"]
54
+ if tf_res.start_with?("aws_vpc") # should this be VPC or subnet?
55
+ # if it's a VPC, store it as the VPC id + name
56
+ key = tf_res_id + ":" + name
57
+ Inspec::Log.debug "Iggy::Terraform.parse_extract aws_vpc tagged with InSpec #{key}"
58
+ extracted_profiles[key] = {
59
+ "type" => "aws_vpc",
60
+ "az" => "us-west-2",
61
+ "url" => url,
62
+ }
63
+ elsif tf_res.start_with?("aws_instance")
64
+ # if it's a node, get information about the IP and SSH/WinRM
65
+ key = tf_res_id + ":" + name
66
+ Inspec::Log.debug "Iggy::Terraform.parse_extract aws_instance tagged with InSpec #{key}"
67
+ extracted_profiles[key] = {
68
+ "type" => "aws_instance",
69
+ "public_ip" => tf_resources[tf_res]["primary"]["attributes"]["public_ip"],
70
+ "key_name" => tf_resources[tf_res]["primary"]["attributes"]["key_name"],
71
+ "url" => url,
72
+ }
73
+ else
74
+ # should generic AWS just be the default except for instances?
75
+ STDERR.puts "ERROR: #{file} #{tf_res_id} has an InSpec-tagged resource but #{tf_res} is currently unsupported."
76
+ exit(-1)
77
+ end
78
+ end
79
+ end
80
+ Inspec::Log.debug "Iggy::Terraform.parse_extract extracted_profiles = #{extracted_profiles}"
81
+ extracted_profiles
82
+ end
83
+
84
+ # parse through the JSON and generate InSpec controls
85
+ def self.parse_generate(file)
86
+ tfstate = parse_tfstate(file)
87
+ basename = File.basename(file)
88
+ absolutename = File.absolute_path(file)
89
+
90
+ # InSpec controls generated
91
+ generated_controls = []
92
+
93
+ # iterate over the resources
94
+ tf_resources = tfstate["modules"][0]["resources"]
95
+ tf_resources.keys.each do |tf_res|
96
+ tf_res_type = tf_resources[tf_res]["type"]
97
+
98
+ # add translation layer
99
+ if InspecHelper::TERRAFORM_RESOURCES.keys.include?(tf_res_type)
100
+ Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} #{InspecHelper::TERRAFORM_RESOURCES[tf_res_type]} TRANSLATED"
101
+ tf_res_type = InspecHelper::TERRAFORM_RESOURCES[tf_res_type]
102
+ end
103
+
104
+ # does this match an InSpec resource?
105
+ if InspecHelper::RESOURCES.include?(tf_res_type)
106
+ Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} MATCH"
107
+ tf_res_id = tf_resources[tf_res]["primary"]["id"]
108
+
109
+ # insert new control based off the resource's ID
110
+ ctrl = Inspec::Control.new
111
+ ctrl.id = "#{tf_res_type}::#{tf_res_id}"
112
+ ctrl.title = "Iggy #{basename} #{tf_res_type}::#{tf_res_id}"
113
+ ctrl.desc = "#{tf_res_type}::#{tf_res_id} from the source file #{absolutename}\nGenerated by Iggy v#{Iggy::VERSION}"
114
+ ctrl.impact = "1.0"
115
+
116
+ describe = Inspec::Describe.new
117
+ # describes the resourde with the id as argument
118
+ describe.qualifier.push([tf_res_type, tf_res_id])
119
+
120
+ # ensure the resource exists
121
+ describe.add_test(nil, "exist", nil)
122
+
123
+ # if there's a match, see if there are matching InSpec properties
124
+ inspec_properties = Iggy::InspecHelper.resource_properties(tf_res_type)
125
+ tf_resources[tf_res]["primary"]["attributes"].keys.each do |attr|
126
+ if inspec_properties.member?(attr)
127
+ Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} MATCH"
128
+ value = tf_resources[tf_res]["primary"]["attributes"][attr]
129
+ describe.add_test(attr, "cmp", value)
130
+ else
131
+ Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} SKIP"
132
+ end
133
+ end
134
+
135
+ ctrl.add_test(describe)
136
+ generated_controls.push(ctrl)
137
+ else
138
+ Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} SKIP"
139
+ end
140
+ end
141
+ Inspec::Log.debug "Iggy::Terraform.parse_generate generated_controls = #{generated_controls}"
142
+ generated_controls
143
+ end
144
+
145
+ end
146
+ end