inspec-chef 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +14 -0
- data/README.md +102 -0
- data/inspec-chef.gemspec +39 -0
- data/lib/inspec-chef/input.rb +103 -0
- data/lib/inspec-chef/plugin.rb +25 -0
- data/lib/inspec-chef/version.rb +10 -0
- data/lib/inspec-chef.rb +4 -0
- metadata +78 -0
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
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.
|
data/inspec-chef.gemspec
ADDED
@@ -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
|
data/lib/inspec-chef.rb
ADDED
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: []
|