inspec-iggy 0.6.0 → 0.7.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.
@@ -1,89 +1,80 @@
1
1
  # Terraform CLI command and options
2
2
 
3
- require 'inspec/plugin/v2'
3
+ require "inspec/plugin/v2"
4
4
 
5
- require 'inspec-iggy/version'
6
- require 'inspec-iggy/profile_helper'
7
- require 'inspec-iggy/terraform/generate'
8
- require 'inspec-iggy/terraform/negative'
5
+ require "inspec-iggy/version"
6
+ require "inspec-iggy/profile_helper"
7
+ require "inspec-iggy/terraform/generate"
8
+ require "inspec-iggy/terraform/negative"
9
9
 
10
10
  module InspecPlugins::Iggy
11
11
  module Terraform
12
12
  class CliCommand < Inspec.plugin(2, :cli_command)
13
- subcommand_desc 'terraform SUBCOMMAND ...', 'Generate an InSpec profile from Terraform'
14
-
15
- # Thor.map(Hash) allows you to make aliases for commands.
16
- map('-v' => 'version') # Treat `inspec terraform -v`` as `inspec terraform version`
17
- map('--version' => 'version') # Treat `inspec terraform -version`` as `inspec terraform version`
18
-
19
- desc 'version', 'Display version information', hide: true
20
- def version
21
- say("Iggy v#{InspecPlugins::Iggy::VERSION}")
22
- end
13
+ subcommand_desc "terraform SUBCOMMAND ...", "Generate an InSpec profile from Terraform"
23
14
 
24
15
  class_option :debug,
25
- desc: 'Verbose debugging messages',
16
+ desc: "Verbose debugging messages",
26
17
  type: :boolean,
27
18
  default: false
28
19
 
29
20
  class_option :copyright,
30
- desc: 'Name of the copyright holder',
31
- default: 'The Authors'
21
+ desc: "Name of the copyright holder",
22
+ default: "The Authors"
32
23
 
33
24
  class_option :email,
34
- desc: 'Email address of the author',
35
- default: 'you@example.com'
25
+ desc: "Email address of the author",
26
+ default: "you@example.com"
36
27
 
37
28
  class_option :license,
38
- desc: 'License for the profile',
39
- default: 'Apache-2.0'
29
+ desc: "License for the profile",
30
+ default: "Apache-2.0"
40
31
 
41
32
  class_option :maintainer,
42
- desc: 'Name of the copyright holder',
43
- default: 'The Authors'
33
+ desc: "Name of the copyright holder",
34
+ default: "The Authors"
44
35
 
45
36
  class_option :summary,
46
- desc: 'One line summary for the profile',
47
- default: 'An InSpec Compliance Profile'
37
+ desc: "One line summary for the profile",
38
+ default: "An InSpec Compliance Profile"
48
39
 
49
40
  class_option :title,
50
- desc: 'Human-readable name for the profile',
51
- default: 'InSpec Profile'
41
+ desc: "Human-readable name for the profile",
42
+ default: "InSpec Profile"
52
43
 
53
44
  class_option :version,
54
- desc: 'Specify the profile version',
55
- default: '0.1.0'
45
+ desc: "Specify the profile version",
46
+ default: "0.1.0"
56
47
 
57
48
  class_option :overwrite,
58
- desc: 'Overwrites existing profile directory',
49
+ desc: "Overwrites existing profile directory",
59
50
  type: :boolean,
60
51
  default: false
61
52
 
62
53
  class_option :name,
63
- aliases: '-n',
54
+ aliases: "-n",
64
55
  required: true,
65
- desc: 'Name of profile to be generated'
56
+ desc: "Name of profile to be generated"
66
57
 
67
58
  class_option :tfstate,
68
- aliases: '-t',
69
- desc: 'Specify path to the input terraform.tfstate',
70
- default: 'terraform.tfstate'
59
+ aliases: "-t",
60
+ desc: "Specify path to the input terraform.tfstate",
61
+ default: "terraform.tfstate"
71
62
 
72
63
  class_option :platform,
73
- desc: 'The InSpec platform providing the necessary resources (aws, azure, or gcp)'
64
+ desc: "The InSpec platform providing the necessary resources (aws, azure, or gcp)"
74
65
 
75
66
  class_option :resourcepath,
76
- desc: 'Specify path to the InSpec Resource Pack providing the necessary resources'
67
+ desc: "Specify path to the InSpec Resource Pack providing the necessary resources"
77
68
 
78
- desc 'generate [options]', 'Generate InSpec compliance controls from terraform.tfstate'
69
+ desc "generate [options]", "Generate InSpec compliance controls from terraform.tfstate"
79
70
  def generate
80
71
  Inspec::Log.level = :debug if options[:debug]
81
72
  platform = options[:platform]
82
73
  resource_path = options[:resourcepath]
83
74
  # require validation that if platform or resourcepath are passed, both are available
84
- if platform or resource_path
85
- unless platform and resource_path
86
- error 'You must pass both --platform and --resourcepath if using either'
75
+ if platform || resource_path
76
+ unless platform && resource_path
77
+ error "You must pass both --platform and --resourcepath if using either"
87
78
  exit(1)
88
79
  end
89
80
  end
@@ -93,15 +84,15 @@ module InspecPlugins::Iggy
93
84
  exit 0
94
85
  end
95
86
 
96
- desc 'negative [options]', 'Generate negative InSpec compliance controls from terraform.tfstate'
87
+ desc "negative [options]", "Generate negative InSpec compliance controls from terraform.tfstate"
97
88
  def negative
98
89
  Inspec::Log.level = :debug if options[:debug]
99
90
  platform = options[:platform]
100
91
  resource_path = options[:resourcepath]
101
92
  # require validation that if platform or resourcepath are passed, both are available
102
- if platform or resource_path
103
- unless platform and resource_path
104
- error 'You must pass both --platform and --resourcepath if using either'
93
+ if platform || resource_path
94
+ unless platform && resource_path
95
+ error "You must pass both --platform and --resourcepath if using either"
105
96
  exit(1)
106
97
  end
107
98
  end
@@ -1,11 +1,11 @@
1
1
  # parses Terraform d.tfstate files
2
2
 
3
- require 'inspec/objects/control'
4
- require 'inspec/objects/ruby_helper'
5
- require 'inspec/objects/describe'
3
+ require "inspec/objects/control"
4
+ require "inspec/objects/ruby_helper"
5
+ require "inspec/objects/describe"
6
6
 
7
- require 'inspec-iggy/file_helper'
8
- require 'inspec-iggy/inspec_helper'
7
+ require "inspec-iggy/file_helper"
8
+ require "inspec-iggy/inspec_helper"
9
9
 
10
10
  module InspecPlugins::Iggy::Terraform
11
11
  class Generate
@@ -30,31 +30,30 @@ module InspecPlugins::Iggy::Terraform
30
30
  def self.parse_resources(tfstate, resource_path, _platform)
31
31
  # iterate over the resources
32
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']
33
+ tf_resources = tfstate["resources"]
34
+ tf_resources.each do |tf_res|
35
+ resource_type = tf_res["type"]
36
+ next if resource_type.eql?("random_id") # this is a Terraform resource, not a provider resource
37
+
38
+ # load resource pack resources
39
+ InspecPlugins::Iggy::InspecHelper.load_resource_pack(resource_path) if resource_path
40
+
41
+ # add translation layer
42
+ if InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES.key?(resource_type)
43
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_resources resource_type = #{resource_type} #{InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[resource_type]} TRANSLATED"
44
+ resource_type = InspecPlugins::Iggy::InspecHelper::TRANSLATED_RESOURCES[resource_type]
45
+ end
46
+ resources[resource_type] = {} if resources[resource_type].nil?
47
+ # does this match an InSpec resource?
48
+ if InspecPlugins::Iggy::InspecHelper.available_resources.include?(resource_type)
49
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_resources resource_type = #{resource_type} MATCHED"
50
+ tf_res["instances"].each do |instance|
51
+ resource_id = instance["attributes"]["id"]
52
+ resource_attributes = instance["attributes"]
54
53
  resources[resource_type][resource_id] = resource_attributes
55
- else
56
- Inspec::Log.debug "Iggy::Terraform.Generate.parse_generate resource_type = #{resource_type} SKIPPED"
57
54
  end
55
+ else
56
+ Inspec::Log.debug "Iggy::Terraform.Generate.parse_generate resource_type = #{resource_type} SKIPPED"
58
57
  end
59
58
  end
60
59
  resources
@@ -71,12 +70,27 @@ module InspecPlugins::Iggy::Terraform
71
70
  ctrl.id = "#{resource_type}::#{resource_id}"
72
71
  ctrl.title = "InSpec-Iggy #{resource_type}::#{resource_id}"
73
72
  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'
73
+ ctrl.impact = "1.0"
75
74
 
76
75
  describe = Inspec::Describe.new
77
- case platform
78
- when 'aws' # rubocop:disable Lint/EmptyWhen
79
- when 'azure' # rubocop:disable Lint/EmptyWhen
76
+ case platform # this may need to get refactored away once Azure is tested
77
+ when "aws"
78
+ qualifier = [resource_type, {}]
79
+ if InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform).key?(resource_type) # there are additional qualifiers
80
+ first = true
81
+ InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform)[resource_type].each do |parameter|
82
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} qualifier found = #{parameter} MATCHED"
83
+ if first # this is the id for the resource
84
+ value = resources[resource_type][resource_id]["id"] # pull value out of the tf attributes
85
+ first = false
86
+ else
87
+ value = resources[resource_type][resource_id][parameter.to_s] # pull value out of the tf attributes
88
+ end
89
+ qualifier[1][parameter] = value
90
+ end
91
+ end
92
+ describe.qualifier.push(qualifier)
93
+ when "azure" # rubocop:disable Lint/EmptyWhen
80
94
  # this is a hack for azure, we need a better longterm solution
81
95
  # if resource.start_with?('azure_')
82
96
  # name = resource_id.split('/').last
@@ -91,11 +105,11 @@ module InspecPlugins::Iggy::Terraform
91
105
  # resource_group = resource_id.split('resourceGroups/').last.split('/').first
92
106
  # describe.qualifier.push([resource_type, name: name, group_name: resource_group])
93
107
  # end
94
- when 'gcp'
108
+ when "gcp"
95
109
  qualifier = [resource_type, {}]
96
110
  if InspecPlugins::Iggy::InspecHelper.available_resource_qualifiers(platform).key?(resource_type)
97
111
  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"
112
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} qualifier found = #{parameter} MATCHED"
99
113
  value = resources[resource_type][resource_id][parameter.to_s] # pull value out of the tf attributes
100
114
  qualifier[1][parameter] = value
101
115
  end
@@ -104,7 +118,7 @@ module InspecPlugins::Iggy::Terraform
104
118
  end
105
119
 
106
120
  # 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_')
121
+ describe.add_test(nil, "exist", nil) unless resource_type.start_with?("azure_")
108
122
 
109
123
  # if there's a match, see if there are matching InSpec properties
110
124
  inspec_properties = InspecPlugins::Iggy::InspecHelper.resource_properties(resource_type, platform)
@@ -113,7 +127,13 @@ module InspecPlugins::Iggy::Terraform
113
127
  if inspec_properties.member?(attr)
114
128
  Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} inspec_property = #{attr} MATCHED"
115
129
  value = resources[resource_type][resource_id][attr]
116
- describe.add_test(attr, 'cmp', value)
130
+ if value
131
+ # check to see if there is a translate for this attr
132
+ property = InspecPlugins::Iggy::InspecHelper.translated_resource_property(platform, resource_type, attr)
133
+ describe.add_test(property, "cmp", value)
134
+ else
135
+ Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} inspec_property = #{attr} SKIPPED FOR NIL"
136
+ end
117
137
  else
118
138
  Inspec::Log.debug "Iggy::Terraform::Generate.parse_controls #{resource_type} inspec_property = #{attr} SKIPPED"
119
139
  end
@@ -1,12 +1,14 @@
1
1
  # returns negative of Terraform tfstate file coverage
2
2
 
3
- require 'inspec/objects/control'
4
- require 'inspec/objects/ruby_helper'
5
- require 'inspec/objects/describe'
3
+ require "hashie"
6
4
 
7
- require 'inspec-iggy/file_helper'
8
- require 'inspec-iggy/inspec_helper'
9
- require 'inspec-iggy/terraform/generate'
5
+ require "inspec/objects/control"
6
+ require "inspec/objects/ruby_helper"
7
+ require "inspec/objects/describe"
8
+
9
+ require "inspec-iggy/file_helper"
10
+ require "inspec-iggy/inspec_helper"
11
+ require "inspec-iggy/terraform/generate"
10
12
 
11
13
  module InspecPlugins::Iggy::Terraform
12
14
  class Negative
@@ -33,21 +35,21 @@ module InspecPlugins::Iggy::Terraform
33
35
  unmatched_controls = []
34
36
  unmatched_resources.each do |unmatched|
35
37
  unresources = InspecPlugins::Iggy::InspecHelper.available_resource_iterators(platform)[unmatched]
36
- iterator = unresources['iterator']
38
+ iterator = unresources["iterator"]
37
39
  ctrl = Inspec::Control.new
38
40
  ctrl.id = "NEGATIVE-COVERAGE:#{iterator}"
39
41
  ctrl.title = "InSpec-Iggy NEGATIVE-COVERAGE:#{iterator}"
40
42
  ctrl.descriptions[:default] = "NEGATIVE-COVERAGE:#{iterator} from the source file #{sourcefile}\nGenerated by InSpec-Iggy v#{InspecPlugins::Iggy::VERSION}"
41
- ctrl.impact = '1.0'
43
+ ctrl.impact = "1.0"
42
44
  describe = Inspec::Describe.new
43
45
  qualifier = [iterator, {}]
44
- unresources['qualifiers'].each do |parameter|
46
+ unresources["qualifiers"].each do |parameter|
45
47
  Inspec::Log.debug "Terraform::Negative.parse_unmatched_resources #{iterator} qualifier found = #{parameter} MATCHED"
46
48
  value = resources.deep_find(parameter.to_s) # value comes from another likely source. Assumption is values are consistent for this type of field
47
49
  qualifier[1][parameter] = value
48
50
  end
49
51
  describe.qualifier.push(qualifier)
50
- describe.add_test(nil, 'exist', nil, { negated: true }) # last field is negated
52
+ describe.add_test(nil, "exist", nil, { negated: true }) # last field is negated
51
53
  ctrl.add_test(describe)
52
54
  unmatched_controls.push(ctrl)
53
55
  end
@@ -66,8 +68,8 @@ module InspecPlugins::Iggy::Terraform
66
68
  Inspec::Log.warn "No iterator matching #{resource} for #{platform} found!"
67
69
  next
68
70
  else
69
- iterator = resource_iterators['iterator']
70
- index = resource_iterators['index']
71
+ iterator = resource_iterators["iterator"]
72
+ index = resource_iterators["index"]
71
73
  Inspec::Log.debug "Terraform::Negative.parse_matched_resources iterator:#{iterator} index:#{index}"
72
74
  end
73
75
  # Nothing but the finest bespoke hand-built InSpec
@@ -78,25 +80,42 @@ module InspecPlugins::Iggy::Terraform
78
80
  ctrl += " Generated by InSpec-Iggy v#{InspecPlugins::Iggy::VERSION}\"\n\n"
79
81
  ctrl += " impact 1.0\n"
80
82
  # 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}', "
83
+ ctrl += " (#{iterator}.where({ "
84
+ if resource_iterators["qualifiers"]
85
+ resource_iterators["qualifiers"].each do |parameter|
86
+ Inspec::Log.debug "Terraform::Negative.parse_matched_resources #{iterator} qualifier found = #{parameter} MATCHED"
87
+ value = resources[resource].deep_find(parameter.to_s) # value comes from resources being evaluated. Assumption is values are consistent for this type of field
88
+ unless value
89
+ Inspec::Log.warn "Terraform::Negative.parse_matched_resources #{resource} no #{parameter} value found, searching outside scope."
90
+ value = resources.deep_find(parameter.to_s)
91
+ end
92
+ ctrl += "#{parameter}: '#{value}', "
93
+ end
86
94
  end
87
95
  ctrl += "}).#{index} - [\n"
88
96
  # iterate over the resources
89
97
  resources[resource].keys.each do |resource_name|
90
98
  ctrl += " '#{resource_name}',\n"
91
99
  end
92
- ctrl += " ]).each do |name|\n"
93
- ctrl += " describe #{resource}({ name: name, "
100
+ ctrl += " ]).each do |id|\n"
101
+ ctrl += " describe #{resource}({ "
94
102
  # iterate over resource qualifiers
103
+ first = true
95
104
  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}', "
105
+ if first # index is first
106
+ ctrl += "#{parameter}: id, "
107
+ first = false
108
+ next
109
+ end
110
+ property = parameter.to_s
111
+ properties = InspecPlugins::Iggy::InspecHelper.available_translated_resource_properties(platform, resource)
112
+ if properties && properties.value?(parameter.to_s)
113
+ property = properties.key(parameter.to_s) # translate back if necessary
114
+ end
115
+ # instead of looking up the key, find by value?
116
+ Inspec::Log.debug "Iggy::Terraform::Negative.parse_matched_resources #{resource} qualifier found = #{property} MATCHED"
117
+ value = resources[resource].deep_find(property) # value comes from resources being evaluated. Assumption is values are consistent for this type of field
118
+ ctrl += "#{property}: '#{value}', "
100
119
  end
101
120
  ctrl += "}) do\n"
102
121
  ctrl += " it { should_not exist }\n"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module InspecPlugins
4
4
  module Iggy
5
- VERSION = '0.6.0'.freeze
5
+ VERSION = "0.7.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.6.0
4
+ version: 0.7.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-07-15 00:00:00.000000000 Z
11
+ date: 2019-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inspec
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.3'
19
+ version: '3'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '5'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '2.3'
29
+ version: '3'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '5'
@@ -45,6 +45,7 @@ files:
45
45
  - lib/inspec-iggy/cloudformation/cli_command.rb
46
46
  - lib/inspec-iggy/cloudformation/generate.rb
47
47
  - lib/inspec-iggy/file_helper.rb
48
+ - lib/inspec-iggy/iggy_cli_command.rb
48
49
  - lib/inspec-iggy/inspec_helper.rb
49
50
  - lib/inspec-iggy/platforms/aws_helper.rb
50
51
  - lib/inspec-iggy/platforms/azure_helper.rb