inspec-iggy 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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