inspec-chef 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: afe50e575ce5384475bd4c1f0ba240df92727baf14cde3a96f35e2a474eceb8c
4
+ data.tar.gz: 6f51ad5775fa43d5ea7eff553608f91f24de4e3248ffcd8722fd92c4e393fba7
5
+ SHA512:
6
+ metadata.gz: 645ab50c1f28a99b7b545d205aa460a819bcdc1380853ed6cbdf0538a53dfb140dd08d0733dab722c9a2cc9b9f4198ae808ff5fe0efb8a2e5ac05ca7e7d2b01a
7
+ data.tar.gz: e7a43170a680e81d11416e72aca2b81e85aa961ca6120fe44e2d4a0c366cebab152741336d3ff5e4ad9f06b914e66de73790ca3ae67bf59f53a4cd832b32491d
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
5
+
6
+ gem "inspec-bin"
7
+
8
+ group :development do
9
+ gem "chefstyle", "0.13.0"
10
+ gem "m"
11
+ gem "bundler"
12
+ gem "rake"
13
+ gem "rubocop"
14
+ end
data/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # inspec-chef
2
+
3
+ Input plugin for InSpec to access Chef Server data within profiles
4
+
5
+ ## Use Case
6
+
7
+ Some systems rely on Chef Databags or Attributes to be provisioned in the right
8
+ state, triggering specific configuration which is then hard to verify using
9
+ InSpec. For example, one system could have an SQL Server 2012 installed
10
+ while the other has SQL Server 2019. While both might perform an identical
11
+ role, the InSpec tests have to distinguish between both and get some
12
+ information on the characteristics to test.
13
+
14
+ As the configuration information is already present in those constructs,
15
+ it makes little sense to manually configure separate profiles.
16
+
17
+ ## Installation
18
+
19
+ Simply execute `inspec plugin install inspec-chef`, which will get
20
+ the plugin from RubyGems and install/register it with InSpec.
21
+
22
+ You can verify successful installation via `inspec plugin list`
23
+
24
+ ## Configuration
25
+
26
+ Each plugin option may be set either as an environment variable, or as a plugin
27
+ option in your Chef InSpec configuration file at ~/.inspec/config.json. For
28
+ example, to set the chef server information in the config file, lay out the
29
+ config file as follows:
30
+
31
+ ```json
32
+ {
33
+ "version": "1.2",
34
+ "plugins":{
35
+ "inspec-chef":{
36
+ "chef_api_endpoint": "https://chef.example.com/organizations/testing",
37
+ "chef_api_client": "workstation",
38
+ "chef_api_key": "/etc/chef/workstation.pem"
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ Config file option names are always lowercase.
45
+
46
+ This plugin supports the following options:
47
+
48
+ | Environment Variable | config.json Option | Description |
49
+ | - | - | - |
50
+ | INSPEC_CHEF_ENDPOINT | chef_api_endpoint | The URL of your Chef server, including the organization |
51
+ | INSPEC_CHEF_CLIENT | chef_api_client | The name of the client of the Chef server to connect as |
52
+ | INSPEC_CHEF_KEY | chef_api_key | Path to the private certificate identifying the node |
53
+
54
+ ## Usage
55
+
56
+ When this plugin is loaded, you can use databag items as inputs:
57
+
58
+ ```ruby
59
+ hostname = input('databag://name/item/some/nested/value')
60
+
61
+ describe host(hostname, port: 80, protocol: 'tcp') do
62
+ it { should be_reachable }
63
+ end
64
+ ```
65
+
66
+ In the same way, you can also add attributes of arbitary nodes:
67
+
68
+ ```ruby
69
+ hostname = input('node://name/attributes/some/nested/attribute')
70
+
71
+ describe host(hostname, port: 80, protocol: 'tcp') do
72
+ it { should be_reachable }
73
+ end
74
+ ```
75
+
76
+ InSpec will go through all loaded input plugins by priority and determine the value.
77
+
78
+ Keep in mind, that the node executing your InSpec runs needs to be
79
+ registered with the chef server to be able to access the data. Lookup
80
+ is __not__ done on the clients tested, but the executing workstation.
81
+
82
+ ## Example
83
+
84
+ With this plugin, the input names consist of the name of the databag,
85
+ the item and a path getting a specific value within an item.
86
+ This way, you can have a databag "configuration" with an item "database" like:
87
+
88
+ ```json
89
+ {
90
+ "SQL": {
91
+ "Type": "SQL2019"
92
+ }
93
+ }
94
+ ```
95
+
96
+ and then use `input('databag://configuration/database/SQL/Type')` to get the
97
+ "SQL2019" value out.
98
+
99
+ ## Limitations
100
+
101
+ There currently is no support for arrays or more complex expressions within
102
+ the query, but support via JMESPath expressions is planned.
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+
3
+ # As plugins are usually packaged and distributed as a RubyGem,
4
+ # we have to provide a .gemspec file, which controls the gembuild
5
+ # and publish process. This is a fairly generic gemspec.
6
+
7
+ # It is traditional in a gemspec to dynamically load the current version
8
+ # from a file in the source tree. The next three lines make that happen.
9
+ lib = File.expand_path("../lib", __FILE__)
10
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
11
+ require "inspec-chef/version"
12
+
13
+ Gem::Specification.new do |spec|
14
+ # Importantly, all InSpec plugins must be prefixed with `inspec-` (most
15
+ # plugins) or `train-` (plugins which add new connectivity features).
16
+ spec.name = "inspec-chef"
17
+
18
+ # It is polite to namespace your plugin under InspecPlugins::YourPluginInCamelCase
19
+ spec.version = InspecPlugins::Chef::VERSION
20
+ spec.authors = ["Thomas Heinen"]
21
+ spec.email = ["theinen@tecracer.de"]
22
+ spec.summary = "InSpec input plugin to access Chef Infra Server databags and attributes."
23
+ spec.description = "This plugin allows InSpec 'inputs' to be provided by Chef Server."
24
+ spec.homepage = "https://github.com/tecracer-theinen/inspec-chef"
25
+ spec.license = "Apache-2.0"
26
+
27
+ # Though complicated-looking, this is pretty standard for a gemspec.
28
+ # It just filters what will actually be packaged in the gem (leaving
29
+ # out tests, etc)
30
+ spec.files = %w{
31
+ README.md inspec-chef.gemspec Gemfile
32
+ } + Dir.glob(
33
+ "lib/**/*", File::FNM_DOTMATCH
34
+ ).reject { |f| File.directory?(f) }
35
+ spec.require_paths = ["lib"]
36
+
37
+ spec.add_dependency "chef-api", "~> 0.10"
38
+ spec.add_dependency "jmespath", "~> 1.4"
39
+ end
@@ -0,0 +1,103 @@
1
+ require "chef-api"
2
+ require "jmespath"
3
+ require "uri"
4
+
5
+ module InspecPlugins::Chef
6
+ class Input < Inspec.plugin(2, :input)
7
+ VALID_PATTERNS = [
8
+ Regexp.new("^databag://[^/]+/[^/]+/.+$"),
9
+ Regexp.new("^node://[^/]+/attributes/.+$"),
10
+ ].freeze
11
+
12
+ attr_reader :plugin_conf, :chef_endpoint, :chef_client, :chef_api_key
13
+ attr_reader :chef_api
14
+
15
+ # Set up new class
16
+ def initialize
17
+ @plugin_conf = Inspec::Config.cached.fetch_plugin_config("inspec-chef")
18
+
19
+ @chef_endpoint = fetch_plugin_setting("endpoint")
20
+ @chef_client = fetch_plugin_setting("client")
21
+ @chef_api_key = fetch_plugin_setting("key")
22
+
23
+ if chef_endpoint.nil? || chef_client.nil? || chef_api_key.nil?
24
+ raise "ERROR: Need configuration of chef endpoint, client name and api key."
25
+ end
26
+
27
+ connect_to_chef_server
28
+ end
29
+
30
+ # Fetch method used for Input plugins
31
+ def fetch(_profile_name, input_uri)
32
+ return nil unless valid_plugin_input? input_uri
33
+
34
+ input = parse_input(input_uri)
35
+
36
+ if input[:type] == :databag
37
+ data = get_databag_item(input[:object], input[:item])
38
+ elsif input[:type] == :node
39
+ data = get_attributes(input[:object]) if input[:item] == "attributes"
40
+ end
41
+
42
+ JMESPath.search(input[:query].join("."), data)
43
+ end
44
+
45
+ private
46
+
47
+ # Get plugin setting via environment, config file or default
48
+ def fetch_plugin_setting(setting_name, default = nil)
49
+ env_var_name = "INSPEC_CHEF_#{setting_name.upcase}"
50
+ config_name = "chef_api_#{setting_name.downcase}"
51
+ ENV[env_var_name] || plugin_conf[config_name] || default
52
+ end
53
+
54
+ # Establish a Chef Server connection
55
+ def connect_to_chef_server
56
+ @chef_api ||= ChefAPI::Connection.new(
57
+ endpoint: chef_endpoint,
58
+ client: chef_client,
59
+ key: chef_api_key
60
+ )
61
+ end
62
+
63
+ # Retrieve a Databag item from Chef Server
64
+ def get_databag_item(databag, item)
65
+ chef_api.data_bag_item.fetch(item, bag: databag).data
66
+ end
67
+
68
+ # Retrieve attributes of a node
69
+ def get_attributes(node)
70
+ data = get_search(:node, "name:#{node}")
71
+
72
+ merge_attributes(data)
73
+ end
74
+
75
+ # Low-level Chef search expression
76
+ def get_search(index, expression)
77
+ chef_api.search.query(index, expression).rows.first
78
+ end
79
+
80
+ # Merge attributes in hierarchy like Chef
81
+ def merge_attributes(data)
82
+ data["default"].merge(data["normal"]).merge(data["override"]).merge(data["automatic"])
83
+ end
84
+
85
+ # Verify if input is valid for this plugin
86
+ def valid_plugin_input?(input)
87
+ VALID_PATTERNS.any? { |regex| regex.match? input }
88
+ end
89
+
90
+ # Parse InSpec input name into Databag, Item and search query
91
+ def parse_input(input_uri)
92
+ uri = URI(input_uri)
93
+ item, *components = uri.path.slice(1..-1).split("/")
94
+
95
+ {
96
+ type: uri.scheme.to_sym,
97
+ object: uri.host,
98
+ item: item,
99
+ query: components,
100
+ }
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,25 @@
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
+ require "inspec-chef/version"
12
+ module InspecPlugins
13
+ module Chef
14
+ class Plugin < ::Inspec.plugin(2)
15
+ # Internal machine name of the plugin. InSpec will use this in errors, etc.
16
+ plugin_name :'inspec-chef'
17
+
18
+ # Define an Input plugin type.
19
+ input :chef do
20
+ require_relative "input.rb"
21
+ InspecPlugins::Chef::Input
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,10 @@
1
+ # encoding: UTF-8
2
+
3
+ # This file simply makes it easier for CI engines to update
4
+ # the version stamp, and provide a clean way for the gemspec
5
+ # to learn the current version.
6
+ module InspecPlugins
7
+ module Chef
8
+ VERSION = "0.1.0".freeze
9
+ end
10
+ end
@@ -0,0 +1,4 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require "inspec-chef/plugin"
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inspec-chef
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Heinen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-01-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chef-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jmespath
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ description: This plugin allows InSpec 'inputs' to be provided by Chef Server.
42
+ email:
43
+ - theinen@tecracer.de
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - Gemfile
49
+ - README.md
50
+ - inspec-chef.gemspec
51
+ - lib/inspec-chef.rb
52
+ - lib/inspec-chef/input.rb
53
+ - lib/inspec-chef/plugin.rb
54
+ - lib/inspec-chef/version.rb
55
+ homepage: https://github.com/tecracer-theinen/inspec-chef
56
+ licenses:
57
+ - Apache-2.0
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.0.3
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: InSpec input plugin to access Chef Infra Server databags and attributes.
78
+ test_files: []