Adding inspec init profile for GCP. (#3484)

* Adding inspec init profile for GCP.
* Adding final newline, thanks rubocop.
* Ensure README headings are at the same level.
* Move OS-specific default profile to new location
* Enforce os-platform restriction on default profile template
* Use profile templates in subdirs.
* Updates to address PR feedback after rebasing from #3491.
* Alter test setup to properly use YAML

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
Stuart Paterson 2018-10-25 19:16:31 +01:00 committed by Jared Quick
parent 329da7f679
commit f64da78edb
13 changed files with 195 additions and 24 deletions

View file

@ -6,23 +6,37 @@ require_relative 'renderer'
module InspecPlugins
module Init
class CLI < Inspec.plugin(2, :cli_command)
subcommand_desc 'init SUBCOMMAND', 'Initialize InSpec objects'
subcommand_desc 'init SUBCOMMAND', 'Generate InSpec code'
# Look in the 'template' directory, and register a subcommand
# for each template directory found there.
template_dir = File.join(File.dirname(__FILE__), 'templates')
Dir.glob(File.join(template_dir, '*')) do |template|
template_name = Pathname.new(template).relative_path_from(Pathname.new(template_dir)).to_s
#-------------------------------------------------------------------#
# inspec init profile
#-------------------------------------------------------------------#
def self.valid_profile_platforms
# Look in the 'template/profiles' directory and detect which platforms are available.
profile_templates_dir = File.join(File.dirname(__FILE__), 'templates', 'profiles')
Dir.glob(File.join(profile_templates_dir, '*')).select { |p| File.directory?(p) }.map { |d| File.basename(d) }
end
# register command for the template
desc "#{template_name} NAME", "Create a new #{template_name}"
option :overwrite, type: :boolean, default: false,
desc: 'Overwrites existing directory'
define_method template_name.to_sym do |name_for_new_structure|
renderer = InspecPlugins::Init::Renderer.new(self, options)
renderer.render_with_values(template_name, name: name_for_new_structure)
no_commands do
def valid_profile_platforms
self.class.valid_profile_platforms
end
end
desc 'profile [OPTIONS] NAME', 'Generate a new profile'
option :platform, default: 'os', type: :string, aliases: [:p],
desc: "Which platform to generate a platform for: choose from #{valid_profile_platforms.join(', ')}"
option :overwrite, type: :boolean, default: false,
desc: 'Overwrites existing directory'
def profile(new_profile_name)
unless valid_profile_platforms.include?(options[:platform])
puts "Unable to generate profile: No template available for platform '#{options[:platform]}' (expected one of: #{valid_profile_platforms.join(', ')})"
exit 1
end
template_path = File.join('profiles', options[:platform])
renderer = InspecPlugins::Init::Renderer.new(self, options)
renderer.render_with_values(template_path, name: new_profile_name)
end
end
end
end

View file

@ -16,9 +16,9 @@ module InspecPlugins
end
# rubocop: disable Metrics/AbcSize
def render_with_values(template_type, template_values = {})
def render_with_values(template_subdir_path, template_values = {})
# look for template directory
base_dir = File.join(File.dirname(__FILE__), 'templates', template_type)
base_dir = File.join(File.dirname(__FILE__), 'templates', template_subdir_path)
# prepare glob for all subdirectories and files
template_glob = File.join(base_dir, '**', '{*,.*}')
# Use the name attribute to define the path to the profile.
@ -28,7 +28,10 @@ module InspecPlugins
template_values[:name] = template_values[:name].split(%r{\\|\/}).last
# Generate the full full_destination_root_path path on disk
full_destination_root_path = Pathname.new(Dir.pwd).join(profile_path)
ui.plain_text "Create new #{template_type} at #{ui.mark_text(full_destination_root_path)}"
# This is a bit gross
generator_type = template_subdir_path.split(%r{[\/]}).first.sub(/s$/, '')
ui.plain_text "Create new #{generator_type} at #{ui.mark_text(full_destination_root_path)}"
# check that the directory does not exist
if File.exist?(full_destination_root_path) && !overwrite_mode

View file

@ -0,0 +1,66 @@
# Example InSpec Profile For GCP
This example shows the implementation of an InSpec profile for GCP that depends on the [InSpec GCP Resource Pack](https://github.com/inspec/inspec-gcp). See the [README](https://github.com/inspec/inspec-gcp) for instructions on setting up appropriate GCP credentials.
## Create a profile
```
$ inspec init profile --platform gcp my-profile
Create new profile at /Users/spaterson/my-profile
* Create directory libraries
* Create file README.md
* Create directory controls
* Create file controls/example.rb
* Create file inspec.yml
* Create file attributes.yml
* Create file libraries/.gitkeep
```
## Update `attributes.yml` to point to your project
```
gcp_project_id: 'my-gcp-project'
```
## Run the tests
```
$ cd gcp-profile/
$ inspec exec . -t gcp:// --attrs attributes.yml
Profile: GCP InSpec Profile (my-profile)
Version: 0.1.0
Target: gcp://local-service-account@my-gcp-project.iam.gserviceaccount.com
✔ gcp-single-region-1.0: Ensure single region has the correct properties.
✔ Region europe-west2 zone_names should include "europe-west2-a"
✔ gcp-regions-loop-1.0: Ensure regions have the correct properties in bulk.
✔ Region asia-east1 should be up
✔ Region asia-northeast1 should be up
✔ Region asia-south1 should be up
✔ Region asia-southeast1 should be up
✔ Region australia-southeast1 should be up
✔ Region europe-north1 should be up
✔ Region europe-west1 should be up
✔ Region europe-west2 should be up
✔ Region europe-west3 should be up
✔ Region europe-west4 should be up
✔ Region northamerica-northeast1 should be up
✔ Region southamerica-east1 should be up
✔ Region us-central1 should be up
✔ Region us-east1 should be up
✔ Region us-east4 should be up
✔ Region us-west1 should be up
✔ Region us-west2 should be up
Profile: Google Cloud Platform Resource Pack (inspec-gcp)
Version: 0.5.0
Target: gcp://local-service-account@my-gcp-project.iam.gserviceaccount.com
No tests executed.
Profile Summary: 2 successful controls, 0 control failures, 0 controls skipped
Test Summary: 18 successful, 0 failures, 0 skipped
```

View file

@ -0,0 +1,2 @@
# Below is to be uncommented and set with your GCP project ID:
# gcp_project_id: 'your-gcp-project'

View file

@ -0,0 +1,28 @@
# encoding: utf-8
# copyright: 2018, The Authors
title 'Sample Section'
gcp_project_id = attribute('gcp_project_id')
# you add controls here
control 'gcp-single-region-1.0' do # A unique ID for this control
impact 1.0 # The criticality, if this control fails.
title 'Ensure single region has the correct properties.' # A human-readable title
desc 'An optional description...'
describe google_compute_region(project: gcp_project_id, name: 'europe-west2') do # The actual test
its('zone_names') { should include 'europe-west2-a' }
end
end
# plural resources can be leveraged to loop across many resources
control 'gcp-regions-loop-1.0' do # A unique ID for this control
impact 1.0 # The criticality, if this control fails.
title 'Ensure regions have the correct properties in bulk.' # A human-readable title
desc 'An optional description...'
google_compute_regions(project: gcp_project_id).region_names.each do |region_name| # Loop across all regions by name
describe google_compute_region(project: gcp_project_id, name: region_name) do # The test for a single region
it { should be_up }
end
end
end

View file

@ -0,0 +1,19 @@
name: <%= name %>
title: GCP InSpec Profile
maintainer: The Authors
copyright: The Authors
copyright_email: you@example.com
license: Apache-2.0
summary: An InSpec Compliance Profile For GCP
version: 0.1.0
inspec_version: '>= 2.3.5'
attributes:
- name: gcp_project_id
required: true
description: 'The GCP project identifier.'
type: string
depends:
- name: inspec-gcp
url: https://github.com/inspec/inspec-gcp/archive/master.tar.gz
supports:
- platform: gcp

View file

@ -6,3 +6,5 @@ copyright_email: you@example.com
license: Apache-2.0
summary: An InSpec Compliance Profile
version: 0.1.0
supports:
platform: os

View file

@ -1,5 +1,6 @@
# encoding: utf-8
require 'yaml'
require_relative '../../../shared/core_plugin_test_helper.rb'
class InitCli < MiniTest::Test
@ -17,6 +18,28 @@ class InitCli < MiniTest::Test
end
end
def test_generating_inspec_profile_with_explicit_platform
Dir.mktmpdir do |dir|
profile = File.join(dir, 'test-profile')
out = run_inspec_process("init profile --platform os test-profile", prefix: "cd #{dir} &&")
assert_equal 0, out.exit_status
assert_includes out.stdout, 'Create new profile at'
assert_includes out.stdout, profile
assert_includes Dir.entries(profile).join, 'inspec.yml'
assert_includes Dir.entries(profile).join, 'README.md'
end
end
def test_generating_inspec_profile_with_bad_platform
Dir.mktmpdir do |dir|
profile = File.join(dir, 'test-profile')
out = run_inspec_process("init profile --platform nonesuch test-profile", prefix: "cd #{dir} &&")
assert_equal 1, out.exit_status
assert_includes out.stdout, 'Unable to generate profile'
assert_includes out.stdout, "No template available for platform 'nonesuch'"
end
end
def test_profile_with_slash_name
Dir.mktmpdir do |dir|
profile = dir + '/test/deeper/profile'
@ -27,4 +50,16 @@ class InitCli < MiniTest::Test
assert_equal 'profile', profile['name']
end
end
def test_generating_inspec_profile_gcp
Dir.mktmpdir do |dir|
profile = File.join(dir, 'test-gcp-profile')
out = run_inspec_process("init profile --platform gcp test-gcp-profile", prefix: "cd #{dir} &&")
assert_equal 0, out.exit_status
assert_includes out.stdout, 'Create new profile at'
assert_includes out.stdout, profile
assert_includes Dir.entries(profile).join, 'inspec.yml'
assert_includes Dir.entries(profile).join, 'README.md'
end
end
end

View file

@ -1,6 +1,7 @@
require 'functional/helper'
require 'fileutils'
require 'tmpdir'
require 'yaml'
describe 'profiles with git-based dependencies' do
include FunctionalHelper
@ -22,14 +23,15 @@ describe 'profiles with git-based dependencies' do
CMD.run_command("git tag antag")
end
File.open(File.join(@profile_dir, "inspec.yml"), 'a') do |f|
f.write <<EOF
depends:
- name: git-dep
git: #{@git_dep_dir}
tag: antag
EOF
end
inspec_yml = YAML.load(File.read(File.join(@profile_dir, "inspec.yml")))
inspec_yml["depends"] = [
{
'name' => 'git-dep',
'git' => @git_dep_dir,
'tag' => 'antag'
}
]
File.write(File.join(@profile_dir, "inspec.yml"), YAML.dump(inspec_yml))
end
after(:all) do