inspec-iggy 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 77411a21b6c3a1f74cdc23ab20f48d96af93a985
4
- data.tar.gz: 0b32db4421f119d92343a7cd834e2953989ee3b7
2
+ SHA256:
3
+ metadata.gz: 2fda35bc2132112d64bad18dab4c2082adb075b9e9a4f9e809297dcba8f34512
4
+ data.tar.gz: 3ece40720d734afcb5af8acd8eb54e193dc70c28ca4c87d38bf6d62bcb7c9637
5
5
  SHA512:
6
- metadata.gz: eac8bdf420cb4951742c5e66038bfda33c7776d7c75b78fab08f8cff81c18883c956670991ea62a8c4822a290bb24981db267b93ca68a5aa0487db57ee0462ce
7
- data.tar.gz: b01d470916ca825f4a30cc3376a3979815160efd76fe2ee6d1ecae8b98d6881b937b6fa139a050719b6b38dcd9c056e725a6984b4e389ad862be2d16af179279
6
+ metadata.gz: 1e34a5bee669e7e63948af184efe3395c45cc46e270d0ed929a8a3d11787d3a5c14f0827ab911050a0a9e1b8be00e32fca212d303b31f46e45c80d4b1119ccc1
7
+ data.tar.gz: 99ef09d4c2fd5d30896b934b7eda623f37ff24f018bc143dcb1f87f19ba13d70b6f6f620feb38dc44d99eac042baf801e48c77c6203cae796850bb4e316a8925
data/Gemfile CHANGED
@@ -3,15 +3,11 @@ source 'http://rubygems.org'
3
3
 
4
4
  gemspec
5
5
 
6
+ # follows InSpec's versions
6
7
  group :test do
7
- gem 'rake' # Build task manager
8
- gem 'chefstyle'
9
- gem 'byebug' # A debugger REPL
10
- gem 'rubocop' # Needed for style linting
8
+ gem 'minitest', '~> 5.5'
9
+ gem 'rake', '>= 10'
10
+ gem 'rubocop', '= 0.49.1'
11
11
  gem 'm'
12
- end
13
-
14
- group :tools do
15
- gem 'github_changelog_generator'
16
- gem 'rb-readline'
12
+ gem 'pry-byebug'
17
13
  end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Description #
2
2
 
3
- [![Build Status Master](https://travis-ci.org/inspec/inspec-iggy.svg?branch=master)](https://travis-ci.org/inspec/inspec-iggy)
3
+ [![Build Status Master](https://travis-ci.org/mattray/inspec-iggy.svg?branch=master)](https://travis-ci.org/mattray/inspec-iggy)
4
4
 
5
5
  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 and [AWS CloudFormation](https://aws.amazon.com/cloudformation/) templates. Iggy generates InSpec controls by mapping Terraform and CloudFormation resources to InSpec resources and exports a profile that may be used from the `inspec` CLI or uploaded to [Chef Automate](https://automate.chef.io/).
6
6
 
@@ -10,32 +10,34 @@ InSpec-Iggy (InSpec Generate -> "IG" -> "Iggy") is an [InSpec](https://inspec.io
10
10
 
11
11
  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 [InSpec for provisioning testing: Verify Terraform setups with InSpec](http://lollyrock.com/articles/inspec-terraform/).
12
12
 
13
- The [CHANGELOG.md](https://github.com/inspec/iggy/blob/master/CHANGELOG.md) covers current, previous and future development milestones and contains the features backlog.
13
+ The [CHANGELOG.md](https://github.com/mattray/iggy/blob/master/CHANGELOG.md) covers current, previous and future development milestones and contains the features backlog.
14
14
 
15
15
  1. [Requirements](#requirements)
16
- 2. [Installation](#installation)
17
- 3. [InSpec Terraform Generate](#itg)
18
- 4. [InSpec Terraform Extract](#ite)
16
+ 2. [Support](#support)
17
+ 3. [Installation](#installation)
18
+ 4. [InSpec Terraform Generate](#itg)
19
19
  5. [InSpec Cloudformation Generate](#icg)
20
- 6. [Testing](#testing)
20
+ 6. [Development and Testing](#development)
21
+
22
+ # Support<a name="support"></a>
23
+
24
+ InSpec-Iggy is a community-driven plugin that is not officially supported by Chef. We welcome patches, suggestions, and issues.
21
25
 
22
26
  # Requirements <a name="requirements"></a>
23
27
 
24
- Iggy generates compliance profiles for InSpec 2.3 and later, 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. It has currently been tested primarily with AWS but other InSpec-supported platforms should work as well.
28
+ Iggy generates compliance profiles for InSpec 2.3 and later, 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 much resource coverage as possible. It has currently been tested primarily with AWS but other InSpec-supported platforms should work as well.
25
29
 
26
- Written and tested with Ruby 2.5.1.
30
+ Written and tested with Ruby 2.6.
27
31
 
28
32
  # Installation <a name="installation"></a>
29
33
 
30
34
  `inspec-iggy` is a plugin for InSpec. InSpec 2.3 or later is required. To install, use:
31
35
 
32
- ```
33
- $ inspec plugin install inspec-iggy
34
- ```
36
+ $ inspec plugin install inspec-iggy
35
37
 
36
38
  # InSpec Terraform Generate<a name="itg"></a>
37
39
 
38
- inspec terraform generate --tfstate terraform.tfstate --name myprofile
40
+ inspec terraform generate --tfstate terraform.tfstate --name myprofile
39
41
 
40
42
  Iggy dynamically pulls the available AWS resources from InSpec and attempts to map them to Terraform resources, producing an InSpec profile. ```inspec terraform generate --help``` will show all available options.
41
43
 
@@ -45,7 +47,6 @@ Iggy dynamically pulls the available AWS resources from InSpec and attempts to m
45
47
 
46
48
  -n, --name=NAME Name of profile to be generated (required)
47
49
  -t, [--tfstate=TFSTATE] Specify path to the input terraform.tfstate (default: .)
48
- [--debug], [--no-debug] Verbose debugging messages
49
50
  [--copyright=COPYRIGHT] Name of the copyright holder (default: The Authors)
50
51
  [--email=EMAIL] Email address of the author (default: you@example.com)
51
52
  [--license=LICENSE] License for the profile (default: Apache-2.0)
@@ -54,37 +55,9 @@ Iggy dynamically pulls the available AWS resources from InSpec and attempts to m
54
55
  [--title=TITLE] Human-readable name for the profile (default: InSpec Profile)
55
56
  [--version=VERSION] Specify the profile version (default: 0.1.0)
56
57
  [--overwrite], [--no-overwrite] Overwrites existing profile directory
57
-
58
- # InSpec Terraform Extract (EXPERIMENTAL)<a name="ite"></a>
59
-
60
- inspec terraform extract --tfstate terraform.tfstate
61
-
62
- ## Tagging Profiles for Extract ##
63
-
64
- 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.
65
-
66
- ### Tagging Format ###
67
-
68
- 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.
69
-
70
- ```
71
- tags {
72
- iggy_name_apache_baseline = "apache-baseline",
73
- iggy_url_apache_baseline = "https://github.com/dev-sec/apache-baseline",
74
- iggy_name_linux_baseline = "linux-baseline",
75
- iggy_url_linux_baseline = "https://github.com/dev-sec/linux-baseline"
76
- }
77
- ```
78
-
79
- ### Potential Enhancements ###
80
-
81
- 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.
82
-
83
- Subnet might be a better choice for tagging than VPCs, given they list the AZ.
84
-
85
- Currently it only supports URL-based compliance profiles. InSpec supports other formats (git, path, supermarket, compliance).
86
-
87
- inspec exec https://github.com/dev-sec/linux-baseline -t ssh://clckwrk@52.33.203.34 -i ~/.ssh/mattray-apac
58
+ [--debug], [--no-debug] Verbose debugging messages
59
+ [--log-level=LOG_LEVEL] Set the log level: info (default), debug, warn, error
60
+ [--log-location=LOG_LOCATION] Location to send diagnostic log messages to. (default: STDOUT or Inspec::Log.error)
88
61
 
89
62
  # InSpec CloudFormation Generate<a name="icg"></a>
90
63
 
@@ -99,7 +72,6 @@ Iggy supports AWS CloudFormation templates by mapping the AWS resources to InSpe
99
72
  -n, --name=NAME Name of profile to be generated (required)
100
73
  -s, --stack=STACK Specify stack name or unique stack ID associated with the CloudFormation template
101
74
  -t, --template=TEMPLATE Specify path to the input CloudFormation template
102
- [--debug], [--no-debug] Verbose debugging messages
103
75
  [--copyright=COPYRIGHT] Name of the copyright holder (default: The Authors)
104
76
  [--email=EMAIL] Email address of the author (default: you@example.com)
105
77
  [--license=LICENSE] License for the profile (default: Apache-2.0)
@@ -108,48 +80,44 @@ Iggy supports AWS CloudFormation templates by mapping the AWS resources to InSpe
108
80
  [--title=TITLE] Human-readable name for the profile (default: InSpec Profile)
109
81
  [--version=VERSION] Specify the profile version (default: 0.1.0)
110
82
  [--overwrite], [--no-overwrite] Overwrites existing profile directory
83
+ [--debug], [--no-debug] Verbose debugging messages
84
+ [--log-level=LOG_LEVEL] Set the log level: info (default), debug, warn, error
85
+ [--log-location=LOG_LOCATION] Location to send diagnostic log messages to. (default: STDOUT or Inspec::Log.error)
111
86
 
112
- # Development
87
+ # Development and Testing<a name="development"></a>
88
+
89
+ The [DESIGN.md](DESIGN.md) file outlines how the code is structured if you wish to extend functionality. We welcome patches, suggestions, and issues.
113
90
 
114
91
  ## Installation
115
92
 
116
93
  To point `inspec` at a local copy of `inspec-iggy` for development, use:
117
94
 
118
- ```
119
- $ inspec plugin install path/to/your/inspec-iggy/lib/inspec-iggy.rb
120
- ```
121
-
122
- # Testing Iggy<a name="testing"></a>
95
+ $ inspec plugin install path/to/your/inspec-iggy/lib/inspec-iggy.rb
123
96
 
97
+ ## Testing Iggy
124
98
 
125
99
  Unit, Functional, and Integration tests are provided, though more are welcome. Iggy uses the Minitest library for testing, using the classic `def test...` syntax. Because Iggy loads InSpec into memory, and InSpec uses RSpec internally, Spec-style testing breaks.
126
100
 
127
101
  To run all tests, run
128
102
 
129
- ```
130
- $ bundle exec rake test
131
- ```
103
+ $ bundle exec rake test
132
104
 
133
105
  Linting is also provided via Rubocop.
134
106
 
135
107
  To check for code style issues, run:
136
108
 
137
- ```
138
- $ bundle exec rake lint
139
- ```
109
+ $ bundle exec rake lint
140
110
 
141
111
  You can auto-correct many issues:
142
112
 
143
- ```
144
- $ bundle exec rubocop -a
145
- ```
113
+ $ bundle exec rake lint:auto_correct
146
114
 
147
115
  # License and Author #
148
116
 
149
117
  | | |
150
118
  |:---------------|:------------------------------------------|
151
119
  | **Author** | Matt Ray (<matt@chef.io>) |
152
- | **Copyright:** | Copyright (c) 2017-2018 Chef Software Inc.|
120
+ | **Copyright:** | Copyright (c) 2017-2019 Chef Software Inc.|
153
121
  | **License:** | Apache License, Version 2.0 |
154
122
 
155
123
  Licensed under the Apache License, Version 2.0 (the "License");
@@ -9,9 +9,9 @@ Gem::Specification.new do |spec|
9
9
  spec.version = InspecPlugins::Iggy::VERSION
10
10
  spec.authors = ['Matt Ray']
11
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'
12
+ spec.summary = 'InSpec plugin to generate InSpec compliance profiles from Terraform and CloudFormation.'
13
+ spec.description = 'InSpec plugin to generate InSpec compliance profiles from Terraform and CloudFormation.'
14
+ spec.homepage = 'https://github.com/mattray/inspec-iggy'
15
15
  spec.license = 'Apache-2.0'
16
16
 
17
17
  spec.files = %w{
@@ -1,15 +1,7 @@
1
- # encoding: utf-8
2
-
3
- # This file is known as the "entry point."
4
- # This is the file InSpec will try to load if it
5
- # thinks your plugin is installed.
6
-
7
- # The *only* thing this file should do is setup the
8
- # load path, then load the plugin definition file.
9
-
10
1
  # Next two lines simply add the path of the gem to the load path.
11
2
  # This is not needed when being loaded as a gem; but when doing
12
- # plugin development, you may need it. Either way, it's harmless.
3
+ # plugin development, you may need it. Either way, it's harmless.
4
+
13
5
  libdir = File.dirname(__FILE__)
14
6
  $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
15
7
 
@@ -1,20 +1,15 @@
1
- # encoding: utf-8
2
- #
3
- # Author:: Matt Ray (<matt@chef.io>)
4
- #
5
- # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
6
- #
1
+ # CloudFormation CLI command and options
7
2
 
8
3
  require 'inspec/plugin/v2'
9
4
 
10
5
  require 'inspec-iggy/version'
11
- require 'inspec-iggy/profile'
6
+ require 'inspec-iggy/profile_helper'
12
7
  require 'inspec-iggy/cloudformation/parser'
13
8
 
14
9
  module InspecPlugins::Iggy
15
10
  module CloudFormation
16
11
  class CliCommand < Inspec.plugin(2, :cli_command)
17
- subcommand_desc 'cloudformation SUBCOMMAND ...', 'Generate InSpec from CloudFormation'
12
+ subcommand_desc 'cloudformation SUBCOMMAND ...', 'Generate an InSpec profile from CloudFormation'
18
13
 
19
14
  # Thor.map(Hash) allows you to make aliases for commands.
20
15
  map('-v' => 'version') # Treat `inspec terraform -v`` as `inspec terraform version`
@@ -84,7 +79,7 @@ module InspecPlugins::Iggy
84
79
  # hash of generated controls
85
80
  generated_controls = InspecPlugins::Iggy::CloudFormation::Parser.parse_generate(options[:template])
86
81
  printable_controls = InspecPlugins::Iggy::InspecHelper.cfn_controls(options[:title], generated_controls, options[:stack])
87
- InspecPlugins::Iggy::Profile.render_profile(self, options, options[:template], printable_controls)
82
+ InspecPlugins::Iggy::ProfileHelper.render_profile(self, options, options[:template], printable_controls)
88
83
  exit 0
89
84
  end
90
85
  end
@@ -1,35 +1,22 @@
1
- #
2
- # Author:: Matt Ray (<matt@chef.io>)
3
- #
4
- # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
5
- #
1
+ # parses CloudFormation JSON files
6
2
 
7
- require 'json'
8
3
  require 'inspec/objects/control'
9
4
  require 'inspec/objects/ruby_helper'
10
5
  require 'inspec/objects/describe'
11
6
 
7
+ require 'inspec-iggy/file_helper'
12
8
  require 'inspec-iggy/inspec_helper'
13
9
 
14
10
  module InspecPlugins::Iggy::CloudFormation
15
11
  class Parser
16
- def self.parse_generate(file) # rubocop:disable all
17
- Inspec::Log.debug "CloudFormation.parse_generate file = #{file}"
18
- begin
19
- unless File.file?(file)
20
- STDERR.puts "ERROR: #{file} is an invalid file, please check your path."
21
- exit(-1)
22
- end
23
- template = JSON.parse(File.read(file))
24
- rescue JSON::ParserError => e
25
- STDERR.puts e.message
26
- STDERR.puts "ERROR: Parsing error in #{file}."
27
- exit(-1)
28
- end
29
- absolutename = File.absolute_path(file)
12
+ # parse through the JSON and generate InSpec controls
13
+ def self.parse_generate(cfn_template) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
14
+ template = InspecPlugins::Iggy::FileHelper.parse_json(cfn_template)
15
+ absolutename = File.absolute_path(cfn_template)
30
16
 
31
17
  # InSpec controls generated
32
18
  generated_controls = []
19
+
33
20
  # iterate over the resources
34
21
  cfn_resources = template['Resources']
35
22
  cfn_resources.keys.each do |cfn_res|
@@ -46,7 +33,7 @@ module InspecPlugins::Iggy::CloudFormation
46
33
 
47
34
  # does this match an InSpec resource?
48
35
  if InspecPlugins::Iggy::InspecHelper::RESOURCES.include?(cfn_res_type)
49
- Inspec::Log.debug "CloudFormation.parse_generate cfn_res_type = #{cfn_res_type} MATCH"
36
+ Inspec::Log.debug "CloudFormation.parse_generate cfn_res_type = #{cfn_res_type} MATCHED"
50
37
 
51
38
  # insert new control based off the resource's ID
52
39
  ctrl = Inspec::Control.new
@@ -72,31 +59,31 @@ module InspecPlugins::Iggy::CloudFormation
72
59
  attr_split = attr.split(/(?=[A-Z])/)
73
60
  property = attr_split.join('_').downcase
74
61
  if inspec_properties.member?(property)
75
- Inspec::Log.debug "CloudFormation.parse_generate #{cfn_res_type} inspec_property = #{property} MATCH"
62
+ Inspec::Log.debug "CloudFormation.parse_generate #{cfn_res_type} inspec_property = #{property} MATCHED"
76
63
  value = cfn_resources[cfn_res]['Properties'][attr]
77
64
  if (value.is_a? Hash) || (value.is_a? Array)
78
65
  # these get replaced at inspec exec
79
66
  if property.eql?('vpc_id') # rubocop:disable Metrics/BlockNesting
80
67
  vpc = cfn_resources[cfn_res]['Properties'][attr].values.first
81
68
  # https://github.com/inspec/inspec/issues/3173
82
- describe.add_test(property, 'eq', "resources[#{vpc}]") unless cfn_res_type.eql?('aws_route_table') # rubocop:disable Metrics/BlockNesting
69
+ describe.add_test(property, 'cmp', "resources[#{vpc}]") unless cfn_res_type.eql?('aws_route_table') # rubocop:disable Metrics/BlockNesting
83
70
  # AMI is a Ref into Parameters
84
71
  elsif property.eql?('image_id') # rubocop:disable Metrics/BlockNesting
85
72
  amiref = cfn_resources[cfn_res]['Properties'][attr].values.first
86
73
  ami = template['Parameters'][amiref]['Default']
87
- describe.add_test(property, 'eq', ami)
74
+ describe.add_test(property, 'cmp', ami)
88
75
  end
89
76
  else
90
- describe.add_test(property, 'eq', value)
77
+ describe.add_test(property, 'cmp', value)
91
78
  end
92
79
  else
93
- Inspec::Log.debug "CloudFormation.parse_generate #{cfn_res_type} inspec_property = #{property} SKIP"
80
+ Inspec::Log.debug "CloudFormation.parse_generate #{cfn_res_type} inspec_property = #{property} SKIPPED"
94
81
  end
95
82
  end
96
83
  ctrl.add_test(describe)
97
84
  generated_controls.push(ctrl)
98
85
  else
99
- Inspec::Log.debug "CloudFormation.parse_generate cfn_res_type = #{cfn_res_type} SKIP"
86
+ Inspec::Log.debug "CloudFormation.parse_generate cfn_res_type = #{cfn_res_type} SKIPPED"
100
87
  end
101
88
  end
102
89
  Inspec::Log.debug "CloudFormation.parse_generate generated_controls = #{generated_controls}"
@@ -0,0 +1,40 @@
1
+ # helper methods for retrieving and parsing files
2
+
3
+ require 'json'
4
+ require 'open-uri'
5
+
6
+ module InspecPlugins
7
+ module Iggy
8
+ class FileHelper
9
+ # boilerplate JSON parsing
10
+ def self.parse_json(file)
11
+ Inspec::Log.debug "Iggy::FileHelper.parse_json file = #{file}"
12
+ lfile = fetch(file)
13
+ begin
14
+ unless File.file?(lfile)
15
+ STDERR.puts "ERROR: #{lfile} is an invalid file, please check your path."
16
+ exit(-1)
17
+ end
18
+ JSON.parse(File.read(lfile))
19
+ rescue JSON::ParserError => e
20
+ STDERR.puts e.message
21
+ STDERR.puts "ERROR: Parsing error in #{lfile}."
22
+ exit(-1)
23
+ end
24
+ end
25
+
26
+ def self.fetch(url)
27
+ # if this is a file, just return it
28
+ return url if File.exist?(url)
29
+
30
+ begin
31
+ URI.parse(url).open
32
+ rescue OpenURI::HTTPError => e
33
+ STDERR.puts e.message
34
+ STDERR.puts "ERROR: Parsing error from URL #{url}"
35
+ exit(-1)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,8 +1,4 @@
1
- #
2
- # Author:: Matt Ray (<matt@chef.io>)
3
- #
4
- # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
5
- #
1
+ # constants and helpers for working with InSpec
6
2
 
7
3
  require 'inspec'
8
4
 
@@ -15,10 +11,14 @@ module InspecPlugins
15
11
  # translate Terraform resource name to InSpec
16
12
  TRANSLATED_RESOURCES = {
17
13
  'aws_instance' => 'aws_ec2_instance',
14
+ 'aws_v_p_c' => 'aws_vpc', # CFN
15
+ 'azurerm_resource_group' => 'azure_resource_group',
16
+ 'azurerm_virtual_machine' => 'azure_virtual_machine'
17
+ # "azure_virtual_machine_data_disk",
18
18
  # 'aws_route' => 'aws_route_table' # needs route_table_id instead of id
19
19
  }.freeze
20
20
 
21
- # # there really should be some way to get this directly from InSpec's resources
21
+ # there really should be some way to get this directly from InSpec's resources
22
22
  def self.resource_properties(resource)
23
23
  # remove the common methods, in theory only leaving only unique InSpec properties
24
24
  inspec_properties = Inspec::Resource.registry[resource].instance_methods - COMMON_PROPERTIES
@@ -29,20 +29,6 @@ module InspecPlugins
29
29
  inspec_properties
30
30
  end
31
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
32
  def self.tf_controls(title, generated_controls)
47
33
  content = "# encoding: utf-8\n#\n\n"
48
34
 
@@ -77,5 +63,20 @@ module InspecPlugins
77
63
  COMMON_PROPERTIES = Inspec::Resource.registry['aws_subnet'].instance_methods &
78
64
  Inspec::Resource.registry['directory'].instance_methods
79
65
  end
66
+
67
+ # disabled extract functionality
68
+ # def self.print_commands(extracted_profiles)
69
+ # extracted_profiles.keys.each do |cmd|
70
+ # type = extracted_profiles[cmd]['type']
71
+ # url = extracted_profiles[cmd]['url']
72
+ # key_name = extracted_profiles[cmd]['key_name']
73
+ # if type == 'aws_instance'
74
+ # ip = extracted_profiles[cmd]['public_ip']
75
+ # puts "inspec exec #{url} -t ssh://#{ip} -i #{key_name}"
76
+ # else
77
+ # puts "inspec exec #{url} -t aws://us-west-2"
78
+ # end
79
+ # end
80
+ # end
80
81
  end
81
82
  end
@@ -1,30 +1,17 @@
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
1
  require 'inspec/plugin/v2'
12
2
 
13
3
  # The InspecPlugins namespace is where all plugins should declare themselves.
14
4
  # The 'Inspec' capitalization is used throughout the InSpec source code; yes, it's
15
5
  # strange.
16
6
  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
7
  module Iggy
20
8
  class Plugin < ::Inspec.plugin(2)
21
9
  # Internal machine name of the plugin. InSpec will use this in errors, etc.
22
10
  plugin_name :'inspec-iggy'
23
11
 
24
12
  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.
13
+ # Calling this hook doesn't mean iggy is being executed - just that we
14
+ # should be ready to do so. So, load the file that defines the functionality.
28
15
  # For example, InSpec will activate this hook when `inspec help` is
29
16
  # executed, so that this plugin's usage message will be included in the help.
30
17
  require 'inspec-iggy/terraform/cli_command'
@@ -1,15 +1,10 @@
1
- # -*- coding: utf-8 -*-
2
- #
3
- # Author:: Matt Ray (<matt@chef.io>)
4
- #
5
- # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
6
- #
1
+ # renders the profile from the parsed files
7
2
 
8
3
  require 'yaml'
9
4
 
10
5
  module InspecPlugins
11
6
  module Iggy
12
- class Profile
7
+ class ProfileHelper
13
8
  # match the output of 'inspec init profile'
14
9
  def self.render_profile(cli_ui, options, source_file, controls)
15
10
  name = options[:name]
@@ -61,7 +56,7 @@ module InspecPlugins
61
56
  f.close
62
57
  end
63
58
 
64
- # * Create file controls/example.rb
59
+ # * Create file controls/controls.rb
65
60
  def self.render_controls_rb(cli_ui, name, controls)
66
61
  render_file = "#{name}/controls/controls.rb"
67
62
  cli_ui.li "Create file #{cli_ui.mark_text(render_file)}"
@@ -1,20 +1,15 @@
1
- # encoding: utf-8
2
- #
3
- # Author:: Matt Ray (<matt@chef.io>)
4
- #
5
- # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
6
- #
1
+ # Terraform CLI command and options
7
2
 
8
3
  require 'inspec/plugin/v2'
9
4
 
10
5
  require 'inspec-iggy/version'
11
- require 'inspec-iggy/profile'
6
+ require 'inspec-iggy/profile_helper'
12
7
  require 'inspec-iggy/terraform/parser'
13
8
 
14
9
  module InspecPlugins::Iggy
15
10
  module Terraform
16
11
  class CliCommand < Inspec.plugin(2, :cli_command)
17
- subcommand_desc 'terraform SUBCOMMAND ...', 'Extract or generate InSpec from Terraform'
12
+ subcommand_desc 'terraform SUBCOMMAND ...', 'Generate an InSpec profile from Terraform'
18
13
 
19
14
  # Thor.map(Hash) allows you to make aliases for commands.
20
15
  map('-v' => 'version') # Treat `inspec terraform -v`` as `inspec terraform version`
@@ -78,17 +73,18 @@ module InspecPlugins::Iggy
78
73
  Inspec::Log.level = :debug if options[:debug]
79
74
  generated_controls = InspecPlugins::Iggy::Terraform::Parser.parse_generate(options[:tfstate])
80
75
  printable_controls = InspecPlugins::Iggy::InspecHelper.tf_controls(options[:title], generated_controls)
81
- InspecPlugins::Iggy::Profile.render_profile(self, options, options[:tfstate], printable_controls)
76
+ InspecPlugins::Iggy::ProfileHelper.render_profile(self, options, options[:tfstate], printable_controls)
82
77
  exit 0
83
78
  end
84
79
 
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
80
+ # disabled extract functionality
81
+ # desc 'extract [options]', 'Extract tagged InSpec profiles from terraform.tfstate'
82
+ # def extract
83
+ # Inspec::Log.level = :debug if options[:debug]
84
+ # extracted_profiles = InspecPlugins::Iggy::Terraform::Parser.parse_extract(options[:tfstate])
85
+ # puts InspecPlugins::Iggy::InspecHelper.print_commands(extracted_profiles)
86
+ # exit 0
87
+ # end
92
88
  end
93
89
  end
94
90
  end
@@ -1,91 +1,23 @@
1
- #
2
- # Author:: Matt Ray (<matt@chef.io>)
3
- #
4
- # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
5
- #
6
-
7
- require 'json'
1
+ # parses Terraform d.tfstate files
8
2
 
9
3
  require 'inspec/objects/control'
10
4
  require 'inspec/objects/ruby_helper'
11
5
  require 'inspec/objects/describe'
12
6
 
7
+ require 'inspec-iggy/file_helper'
13
8
  require 'inspec-iggy/inspec_helper'
14
9
 
15
10
  module InspecPlugins::Iggy::Terraform
16
11
  class Parser
12
+ # disabled extract functionality
17
13
  # 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
14
+ # TAG_NAME = 'iggy_name_'.freeze
15
+ # TAG_URL = 'iggy_url_'.freeze
84
16
 
85
17
  # 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)
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)
89
21
 
90
22
  # InSpec controls generated
91
23
  generated_controls = []
@@ -104,7 +36,7 @@ module InspecPlugins::Iggy::Terraform
104
36
 
105
37
  # does this match an InSpec resource?
106
38
  if InspecPlugins::Iggy::InspecHelper::RESOURCES.include?(tf_res_type)
107
- Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} MATCH"
39
+ Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} MATCHED"
108
40
  tf_res_id = tf_resources[tf_res]['primary']['id']
109
41
 
110
42
  # insert new control based off the resource's ID
@@ -115,33 +47,102 @@ module InspecPlugins::Iggy::Terraform
115
47
  ctrl.impact = '1.0'
116
48
 
117
49
  describe = Inspec::Describe.new
118
- # describes the resourde with the id as argument
119
- describe.qualifier.push([tf_res_type, tf_res_id])
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
120
57
 
121
- # ensure the resource exists
122
- describe.add_test(nil, 'exist', nil)
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_')
123
73
 
124
74
  # if there's a match, see if there are matching InSpec properties
125
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_')
126
78
  tf_resources[tf_res]['primary']['attributes'].keys.each do |attr|
127
79
  if inspec_properties.member?(attr)
128
- Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} MATCH"
80
+ Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} MATCHED"
129
81
  value = tf_resources[tf_res]['primary']['attributes'][attr]
130
- describe.add_test(attr, 'eq', value)
82
+ describe.add_test(attr, 'cmp', value)
131
83
  else
132
- Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} SKIP"
84
+ Inspec::Log.debug "Iggy::Terraform.parse_generate #{tf_res_type} inspec_property = #{attr} SKIPPED"
133
85
  end
134
86
  end
135
87
 
136
88
  ctrl.add_test(describe)
137
89
  generated_controls.push(ctrl)
138
90
  else
139
- Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} SKIP"
91
+ Inspec::Log.debug "Iggy::Terraform.parse_generate tf_res_type = #{tf_res_type} SKIPPED"
140
92
  end
141
93
  end
142
94
  end
143
95
  Inspec::Log.debug "Iggy::Terraform.parse_generate generated_controls = #{generated_controls}"
144
96
  generated_controls
145
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
146
147
  end
147
148
  end
@@ -1,11 +1,7 @@
1
- # encoding: UTF-8
2
- #
3
- # Author:: Matt Ray (<matt@chef.io>)
4
- #
5
- # Copyright:: 2018, Chef Software, Inc <legal@chef.io>
6
- #
1
+ # provide the version for the plugin
2
+
7
3
  module InspecPlugins
8
4
  module Iggy
9
- VERSION = '0.4.0'.freeze
5
+ VERSION = '0.5.0'.freeze
10
6
  end
11
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.4.0
4
+ version: 0.5.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: 2018-10-18 00:00:00.000000000 Z
11
+ date: 2019-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inspec
@@ -30,8 +30,8 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: 4.0.0
33
- description: Generate InSpec compliance profiles from Terraform by tagging instances
34
- and mapping Terraform to InSpec.
33
+ description: InSpec plugin to generate InSpec compliance profiles from Terraform and
34
+ CloudFormation.
35
35
  email:
36
36
  - matt@chef.io
37
37
  executables: []
@@ -44,13 +44,14 @@ files:
44
44
  - lib/inspec-iggy.rb
45
45
  - lib/inspec-iggy/cloudformation/cli_command.rb
46
46
  - lib/inspec-iggy/cloudformation/parser.rb
47
+ - lib/inspec-iggy/file_helper.rb
47
48
  - lib/inspec-iggy/inspec_helper.rb
48
49
  - lib/inspec-iggy/plugin.rb
49
- - lib/inspec-iggy/profile.rb
50
+ - lib/inspec-iggy/profile_helper.rb
50
51
  - lib/inspec-iggy/terraform/cli_command.rb
51
52
  - lib/inspec-iggy/terraform/parser.rb
52
53
  - lib/inspec-iggy/version.rb
53
- homepage: https://github.com/inspec/inspec-iggy
54
+ homepage: https://github.com/mattray/inspec-iggy
54
55
  licenses:
55
56
  - Apache-2.0
56
57
  metadata: {}
@@ -69,9 +70,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
70
  - !ruby/object:Gem::Version
70
71
  version: '0'
71
72
  requirements: []
72
- rubyforge_project:
73
- rubygems_version: 2.6.13
73
+ rubygems_version: 3.0.3
74
74
  signing_key:
75
75
  specification_version: 4
76
- summary: InSpec plugin to generate InSpec compliance profiles from Terraform.
76
+ summary: InSpec plugin to generate InSpec compliance profiles from Terraform and CloudFormation.
77
77
  test_files: []