inspec-iggy 0.5.0 → 0.6.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.
@@ -0,0 +1,130 @@
1
+ # parses Terraform d.tfstate files
2
+
3
+ require 'inspec/objects/control'
4
+ require 'inspec/objects/ruby_helper'
5
+ require 'inspec/objects/describe'
6
+
7
+ require 'inspec-iggy/file_helper'
8
+ require 'inspec-iggy/inspec_helper'
9
+
10
+ module InspecPlugins::Iggy::Terraform
11
+ class Generate
12
+ # parse through the JSON and generate InSpec controls
13
+ def self.parse_generate(tf_file, resource_path, platform)
14
+ # parse the tfstate file to get the Terraform resources
15
+ tfstate = InspecPlugins::Iggy::FileHelper.parse_json(tf_file)
16
+ absolutename = File.absolute_path(tf_file)
17
+
18
+ # take those Terraform resources and map to InSpec resources by name and keep all attributes
19
+ # resources -> [{name1 -> {unfiltered_attributes}, name2 -> {unfiltered_attributes}]
20
+ parsed_resources = parse_resources(tfstate, resource_path, platform)
21
+
22
+ # InSpec controls generated from matched_resources and attributes
23
+ generated_controls = parse_controls(parsed_resources, absolutename, platform)
24
+
25
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_generate generated_controls = #{generated_controls}"
26
+ generated_controls
27
+ end
28
+
29
+ # returns the list of all InSpec resources found in the tfstate file
30
+ def self.parse_resources(tfstate, resource_path, _platform)
31
+ # iterate over the resources
32
+ resources = {}
33
+ tfstate['modules'].each do |m|
34
+ tf_resources = m['resources']
35
+ tf_resources.keys.each do |tf_res|
36
+ resource_type = tf_resources[tf_res]['type']
37
+
38
+ next if resource_type.eql?('random_id') # this is a Terraform resource, not a provider resource
39
+
40
+ # load resource pack resources
41
+ InspecPlugins::Iggy::InspecHelper.load_resource_pack(resource_path) if resource_path
42
+
43
+ # add translation layer
44
+ if InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES.key?(resource_type)
45
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_resources resource_type = #{resource_type} #{InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[resource_type]} TRANSLATED"
46
+ resource_type = InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[resource_type]
47
+ end
48
+ resources[resource_type] = {} if resources[resource_type].nil?
49
+ # does this match an InSpec resource?
50
+ if InspecPlugins::Iggy::InspecHelper.available_resources.include?(resource_type)
51
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_resources resource_type = #{resource_type} MATCHED"
52
+ resource_id = tf_resources[tf_res]['primary']['id']
53
+ resource_attributes = tf_resources[tf_res]['primary']['attributes']
54
+ resources[resource_type][resource_id] = resource_attributes
55
+ else
56
+ Inspec::Log.debug "Iggy::Terraform.Generate.parse_generate resource_type = #{resource_type} SKIPPED"
57
+ end
58
+ end
59
+ end
60
+ resources
61
+ end
62
+
63
+ # take the resources and map to describes
64
+ def self.parse_controls(resources, absolutename, platform) # rubocop:disable Metrics/AbcSize
65
+ controls = []
66
+ # iterate over the resources types and their ids
67
+ resources.keys.each do |resource_type|
68
+ resources[resource_type].keys.each do |resource_id|
69
+ # insert new control based off the resource's ID
70
+ ctrl = Inspec::Control.new
71
+ ctrl.id = "#{resource_type}::#{resource_id}"
72
+ ctrl.title = "InSpec-Iggy #{resource_type}::#{resource_id}"
73
+ ctrl.descriptions[:default] = "#{resource_type}::#{resource_id} from the source file #{absolutename}\nGenerated by InSpec-Iggy v#{InspecPlugins::Iggy::VERSION}"
74
+ ctrl.impact = '1.0'
75
+
76
+ describe = Inspec::Describe.new
77
+ case platform
78
+ when 'aws' # rubocop:disable Lint/EmptyWhen
79
+ when 'azure' # rubocop:disable Lint/EmptyWhen
80
+ # this is a hack for azure, we need a better longterm solution
81
+ # if resource.start_with?('azure_')
82
+ # name = resource_id.split('/').last
83
+ # else
84
+ # name = resource_id
85
+ # end
86
+
87
+ # if resource_type.start_with?('azure_')
88
+ # if resource_type.eql?('azure_resource_group')
89
+ # describe.qualifier.push([resource_type, name: name])
90
+ # else
91
+ # resource_group = resource_id.split('resourceGroups/').last.split('/').first
92
+ # describe.qualifier.push([resource_type, name: name, group_name: resource_group])
93
+ # end
94
+ when 'gcp'
95
+ qualifier = [resource_type, {}]
96
+ if InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform).key?(resource_type)
97
+ InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform)[resource_type].each do |parameter|
98
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} qualifier found = #{parameter} MATCHED"
99
+ value = resources[resource_type][resource_id][parameter.to_s] # pull value out of the tf attributes
100
+ qualifier[1][parameter] = value
101
+ end
102
+ end
103
+ describe.qualifier.push(qualifier)
104
+ end
105
+
106
+ # ensure the resource exists unless Azure, which currently doesn't support it as of InSpec 2.2
107
+ describe.add_test(nil, 'exist', nil) unless resource_type.start_with?('azure_')
108
+
109
+ # if there's a match, see if there are matching InSpec properties
110
+ inspec_properties = InspecPlugins::Iggy::InspecHelper.resource_properties(resource_type, platform)
111
+ # push stuff back into inspec_properties?
112
+ resources[resource_type][resource_id].keys.each do |attr|
113
+ if inspec_properties.member?(attr)
114
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} inspec_property = #{attr} MATCHED"
115
+ value = resources[resource_type][resource_id][attr]
116
+ describe.add_test(attr, 'cmp', value)
117
+ else
118
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} inspec_property = #{attr} SKIPPED"
119
+ end
120
+ end
121
+
122
+ ctrl.add_test(describe)
123
+ controls.push(ctrl)
124
+ end
125
+ end
126
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_generate controls = #{controls}"
127
+ controls
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,112 @@
1
+ # returns negative of Terraform tfstate file coverage
2
+
3
+ require 'inspec/objects/control'
4
+ require 'inspec/objects/ruby_helper'
5
+ require 'inspec/objects/describe'
6
+
7
+ require 'inspec-iggy/file_helper'
8
+ require 'inspec-iggy/inspec_helper'
9
+ require 'inspec-iggy/terraform/generate'
10
+
11
+ module InspecPlugins::Iggy::Terraform
12
+ class Negative
13
+ # parse through the JSON and generate InSpec controls
14
+ def self.parse_negative(tf_file, resource_path, platform)
15
+ tfstate = InspecPlugins::Iggy::FileHelper.parse_json(tf_file)
16
+ sourcefile = File.absolute_path(tf_file)
17
+
18
+ # take those Terraform resources and map to InSpec resources by name and keep all attributes
19
+ parsed_resources = InspecPlugins::Iggy::Terraform::Generate.parse_resources(tfstate, resource_path, platform)
20
+
21
+ # subtract matched resources from all available resources
22
+ negative_controls = parse_unmatched_resources(parsed_resources, sourcefile, platform)
23
+ negative_controls += parse_matched_resources(parsed_resources, sourcefile, platform)
24
+
25
+ negative_controls
26
+ end
27
+
28
+ # return controls for the iterators of things unmatched in the terraform.tfstate
29
+ def self.parse_unmatched_resources(resources, sourcefile, platform)
30
+ resources.extend Hashie::Extensions::DeepFind # use to find iterators' values from other attributes
31
+ unmatched_resources = InspecPlugins::Iggy::InspecHelper.available_resource_iterators(platform).keys - resources.keys
32
+ Inspec::Log.debug "Terraform::Negative.parse_unmatched_resources unmatched_resources #{unmatched_resources}"
33
+ unmatched_controls = []
34
+ unmatched_resources.each do |unmatched|
35
+ unresources = InspecPlugins::Iggy::InspecHelper.available_resource_iterators(platform)[unmatched]
36
+ iterator = unresources['iterator']
37
+ ctrl = Inspec::Control.new
38
+ ctrl.id = "NEGATIVE-COVERAGE:#{iterator}"
39
+ ctrl.title = "InSpec-Iggy NEGATIVE-COVERAGE:#{iterator}"
40
+ ctrl.descriptions[:default] = "NEGATIVE-COVERAGE:#{iterator} from the source file #{sourcefile}\nGenerated by InSpec-Iggy v#{InspecPlugins::Iggy::VERSION}"
41
+ ctrl.impact = '1.0'
42
+ describe = Inspec::Describe.new
43
+ qualifier = [iterator, {}]
44
+ unresources['qualifiers'].each do |parameter|
45
+ Inspec::Log.debug "Terraform::Negative.parse_unmatched_resources #{iterator} qualifier found = #{parameter} MATCHED"
46
+ value = resources.deep_find(parameter.to_s) # value comes from another likely source. Assumption is values are consistent for this type of field
47
+ qualifier[1][parameter] = value
48
+ end
49
+ describe.qualifier.push(qualifier)
50
+ describe.add_test(nil, 'exist', nil, { negated: true }) # last field is negated
51
+ ctrl.add_test(describe)
52
+ unmatched_controls.push(ctrl)
53
+ end
54
+ Inspec::Log.debug "Terraform::Negative.parse_unmatched_resources negative_controls = #{unmatched_controls}"
55
+ unmatched_controls
56
+ end
57
+
58
+ # controls for iterators minus the matched resources
59
+ def self.parse_matched_resources(resources, sourcefile, platform) # rubocop:disable Metrics/AbcSize
60
+ Inspec::Log.debug "Terraform::Negative.parse_matched_resources matched_resources #{resources.keys}"
61
+ matched_controls = []
62
+ resources.keys.each do |resource|
63
+ resources[resource].extend Hashie::Extensions::DeepFind # use to find iterators' values from other attributes
64
+ resource_iterators = InspecPlugins::Iggy::InspecHelper.available_resource_iterators(platform)[resource]
65
+ if resource_iterators.nil?
66
+ Inspec::Log.warn "No iterator matching #{resource} for #{platform} found!"
67
+ next
68
+ else
69
+ iterator = resource_iterators['iterator']
70
+ index = resource_iterators['index']
71
+ Inspec::Log.debug "Terraform::Negative.parse_matched_resources iterator:#{iterator} index:#{index}"
72
+ end
73
+ # Nothing but the finest bespoke hand-built InSpec
74
+ ctrl = "control 'NEGATIVE-COVERAGE:#{iterator}' do\n"
75
+ ctrl += " title 'InSpec-Iggy NEGATIVE-COVERAGE:#{iterator}'\n"
76
+ ctrl += " desc \"\n"
77
+ ctrl += " NEGATIVE-COVERAGE:#{iterator} from the source file #{sourcefile}\n\n"
78
+ ctrl += " Generated by InSpec-Iggy v#{InspecPlugins::Iggy::VERSION}\"\n\n"
79
+ ctrl += " impact 1.0\n"
80
+ # get the qualifiers for the resource iterator
81
+ ctrl += " (#{iterator}({ "
82
+ resource_iterators['qualifiers'].each do |parameter|
83
+ Inspec::Log.debug "Terraform::Negative.parse_matched_resources #{iterator} qualifier found = #{parameter} MATCHED"
84
+ value = resources[resource].deep_find(parameter.to_s) # value comes from resources being evaluated. Assumption is values are consistent for this type of field
85
+ ctrl += "#{parameter}: '#{value}', "
86
+ end
87
+ ctrl += "}).#{index} - [\n"
88
+ # iterate over the resources
89
+ resources[resource].keys.each do |resource_name|
90
+ ctrl += " '#{resource_name}',\n"
91
+ end
92
+ ctrl += " ]).each do |name|\n"
93
+ ctrl += " describe #{resource}({ name: name, "
94
+ # iterate over resource qualifiers
95
+ InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform)[resource].each do |parameter|
96
+ next if parameter.eql?(:name)
97
+ Inspec::Log.debug "Iggy::Terraform::Negative.parse_matched_resources #{resource} qualifier found = #{parameter} MATCHED"
98
+ value = resources[resource].deep_find(parameter.to_s) # value comes from resources being evaluated. Assumption is values are consistent for this type of field
99
+ ctrl += "#{parameter}: '#{value}', "
100
+ end
101
+ ctrl += "}) do\n"
102
+ ctrl += " it { should_not exist }\n"
103
+ ctrl += " end\n"
104
+ ctrl += " end\n"
105
+ ctrl += "end\n\n"
106
+ matched_controls.push(ctrl)
107
+ end
108
+ Inspec::Log.debug "Terraform::Negative.parse_matched_resources negative_controls = #{matched_controls}"
109
+ matched_controls
110
+ end
111
+ end
112
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module InspecPlugins
4
4
  module Iggy
5
- VERSION = '0.5.0'.freeze
5
+ VERSION = '0.6.0'.freeze
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec-iggy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Ray
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-29 00:00:00.000000000 Z
11
+ date: 2019-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inspec
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '2.3'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: 4.0.0
22
+ version: '5'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,9 +29,9 @@ dependencies:
29
29
  version: '2.3'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: 4.0.0
33
- description: InSpec plugin to generate InSpec compliance profiles from Terraform and
34
- CloudFormation.
32
+ version: '5'
33
+ description: InSpec plugin to generate InSpec profiles from Terraform and CloudFormation
34
+ to ensure automatic compliance coverage.
35
35
  email:
36
36
  - matt@chef.io
37
37
  executables: []
@@ -43,13 +43,17 @@ files:
43
43
  - inspec-iggy.gemspec
44
44
  - lib/inspec-iggy.rb
45
45
  - lib/inspec-iggy/cloudformation/cli_command.rb
46
- - lib/inspec-iggy/cloudformation/parser.rb
46
+ - lib/inspec-iggy/cloudformation/generate.rb
47
47
  - lib/inspec-iggy/file_helper.rb
48
48
  - lib/inspec-iggy/inspec_helper.rb
49
+ - lib/inspec-iggy/platforms/aws_helper.rb
50
+ - lib/inspec-iggy/platforms/azure_helper.rb
51
+ - lib/inspec-iggy/platforms/gcp_helper.rb
49
52
  - lib/inspec-iggy/plugin.rb
50
53
  - lib/inspec-iggy/profile_helper.rb
51
54
  - lib/inspec-iggy/terraform/cli_command.rb
52
- - lib/inspec-iggy/terraform/parser.rb
55
+ - lib/inspec-iggy/terraform/generate.rb
56
+ - lib/inspec-iggy/terraform/negative.rb
53
57
  - lib/inspec-iggy/version.rb
54
58
  homepage: https://github.com/mattray/inspec-iggy
55
59
  licenses:
@@ -1,148 +0,0 @@
1
- # parses Terraform d.tfstate files
2
-
3
- require 'inspec/objects/control'
4
- require 'inspec/objects/ruby_helper'
5
- require 'inspec/objects/describe'
6
-
7
- require 'inspec-iggy/file_helper'
8
- require 'inspec-iggy/inspec_helper'
9
-
10
- module InspecPlugins::Iggy::Terraform
11
- class Parser
12
- # disabled extract functionality
13
- # makes it easier to change out later
14
- # TAG_NAME = 'iggy_name_'.freeze
15
- # TAG_URL = 'iggy_url_'.freeze
16
-
17
- # parse through the JSON and generate InSpec controls
18
- def self.parse_generate(tf_file) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
19
- tfstate = InspecPlugins::Iggy::FileHelper.parse_json(tf_file)
20
- absolutename = File.absolute_path(tf_file)
21
-
22
- # InSpec controls generated
23
- generated_controls = []
24
-
25
- # iterate over the resources
26
- tfstate['modules'].each do |m|
27
- tf_resources = m['resources']
28
- tf_resources.keys.each do |tf_res|
29
- tf_res_type = tf_resources[tf_res]['type']
30
-
31
- # add translation layer
32
- if InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES.key?(tf_res_type)
33
- Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} #{InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[tf_res_type]} TRANSLATED"
34
- tf_res_type = InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[tf_res_type]
35
- end
36
-
37
- # does this match an InSpec resource?
38
- if InspecPlugins::Iggy::InspecHelper::RESOURCES.include?(tf_res_type)
39
- Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} MATCHED"
40
- tf_res_id = tf_resources[tf_res]['primary']['id']
41
-
42
- # insert new control based off the resource's ID
43
- ctrl = Inspec::Control.new
44
- ctrl.id = "#{tf_res_type}::#{tf_res_id}"
45
- ctrl.title = "InSpec-Iggy #{tf_res_type}::#{tf_res_id}"
46
- ctrl.descriptions[:default] = "#{tf_res_type}::#{tf_res_id} from the source file #{absolutename}\nGenerated by InSpec-Iggy v#{InspecPlugins::Iggy::VERSION}"
47
- ctrl.impact = '1.0'
48
-
49
- describe = Inspec::Describe.new
50
- # describes the resource with the name as argument
51
- # this is a hack for azure, we need a better longterm solution
52
- if tf_res_type.start_with?('azure_')
53
- name = tf_res_id.split('/').last
54
- else
55
- name = tf_res_id
56
- end
57
-
58
- # describes the resource with the id as argument
59
- # going to need to move the special Azure code out, and add helpers for each provider
60
- if tf_res_type.start_with?('azure_')
61
- if tf_res_type.eql?('azure_resource_group')
62
- describe.qualifier.push([tf_res_type, name: name])
63
- else
64
- resource_group = tf_res_id.split('resourceGroups/').last.split('/').first
65
- describe.qualifier.push([tf_res_type, name: name, group_name: resource_group])
66
- end
67
- else
68
- describe.qualifier.push([tf_res_type, tf_res_id])
69
- end
70
-
71
- # ensure the resource exists unless Azure, which currently doesn't support it as of InSpec 2.2
72
- describe.add_test(nil, 'exist', nil) unless tf_res_type.start_with?('azure_')
73
-
74
- # if there's a match, see if there are matching InSpec properties
75
- inspec_properties = InspecPlugins::Iggy::InspecHelper.resource_properties(tf_res_type)
76
- # push stuff back into inspec_properties?
77
- inspec_properties.push('name') if tf_res_type.start_with?('azure_')
78
- tf_resources[tf_res]['primary']['attributes'].keys.each do |attr|
79
- if inspec_properties.member?(attr)
80
- Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} MATCHED"
81
- value = tf_resources[tf_res]['primary']['attributes'][attr]
82
- describe.add_test(attr, 'cmp', value)
83
- else
84
- Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} SKIPPED"
85
- end
86
- end
87
-
88
- ctrl.add_test(describe)
89
- generated_controls.push(ctrl)
90
- else
91
- Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} SKIPPED"
92
- end
93
- end
94
- end
95
- Inspec::Log.debug "Iggy::Terraform.parse_generate generated_controls = #{generated_controls}"
96
- generated_controls
97
- end
98
-
99
- # disabled extract functionality
100
- # # parse through the JSON for the tagged Resources
101
- # def self.parse_extract(file)
102
- # tfstate = parse_tfstate(file)
103
- # # InSpec profiles extracted
104
- # extracted_profiles = {}
105
-
106
- # # iterate over the resources
107
- # tf_resources = tfstate['modules'][0]['resources']
108
- # tf_resources.keys.each do |tf_res|
109
- # tf_res_id = tf_resources[tf_res]['primary']['id']
110
-
111
- # # get the attributes, see if any of them have a tagged profile attached
112
- # tf_resources[tf_res]['primary']['attributes'].keys.each do |attr|
113
- # next unless attr.start_with?('tags.' + TAG_NAME)
114
- # Inspec::Log.debug "Iggy::Terraform.parse_extract tf_res = #{tf_res} attr = #{attr} MATCHED TAG"
115
- # # get the URL and the name of the profiles
116
- # name = attr.split(TAG_NAME)[1]
117
- # url = tf_resources[tf_res]['primary']['attributes']["tags.#{TAG_URL}#{name}"]
118
- # if tf_res.start_with?('aws_vpc') # should this be VPC or subnet?
119
- # # if it's a VPC, store it as the VPC id + name
120
- # key = tf_res_id + ':' + name
121
- # Inspec::Log.debug "Iggy::Terraform.parse_extract aws_vpc tagged with InSpec #{key}"
122
- # extracted_profiles[key] = {
123
- # 'type' => 'aws_vpc',
124
- # 'az' => 'us-west-2',
125
- # 'url' => url,
126
- # }
127
- # elsif tf_res.start_with?('aws_instance')
128
- # # if it's a node, get information about the IP and SSH/WinRM
129
- # key = tf_res_id + ':' + name
130
- # Inspec::Log.debug "Iggy::Terraform.parse_extract aws_instance tagged with InSpec #{key}"
131
- # extracted_profiles[key] = {
132
- # 'type' => 'aws_instance',
133
- # 'public_ip' => tf_resources[tf_res]['primary']['attributes']['public_ip'],
134
- # 'key_name' => tf_resources[tf_res]['primary']['attributes']['key_name'],
135
- # 'url' => url,
136
- # }
137
- # else
138
- # # should generic AWS just be the default except for instances?
139
- # STDERR.puts "ERROR: #{file} #{tf_res_id} has an InSpec-tagged resource but #{tf_res} is currently unsupported."
140
- # exit(-1)
141
- # end
142
- # end
143
- # end
144
- # Inspec::Log.debug "Iggy::Terraform.parse_extract extracted_profiles = #{extracted_profiles}"
145
- # extracted_profiles
146
- # end
147
- end
148
- end