inspec-chef 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f13512640633f549c0a190e61f94cc2044c9d551401ccb4371e8f1cd37cd102b
4
- data.tar.gz: a47a60292470aad2385de9697c024f7457883fc64eedff78b455a79f7406b367
3
+ metadata.gz: e3689a6a012e43692366a808c0b50e67e7ef4cb35b303593f84ca0d0cd485fd9
4
+ data.tar.gz: 9c21ced8d8c706daf806553c6317b8ff375b3f5d40b0a6ac06573a33c6cb5df4
5
5
  SHA512:
6
- metadata.gz: e848365caa7f68c3a0f4509284a530f62095274540787201a458c39fdd077862765261b46d95acc9267874580c0f9045914172f5a53da284114e889831639936
7
- data.tar.gz: 4ff246d1449d0266ebb3c8bb9774f7f77f0ff209f04cb9263defb492bc628d0ba964740c8fcd803a011507dca113a842a15e8797496b7319ff76cceaef8f91ce
6
+ metadata.gz: b2f09ae34094154758c65afa303f4311ab2a4ea85f70b0a9e96a4cf58791c03cf74b33efce3f9477c26464de24027ce36c80fed92b3902949a7ecbef0219db62
7
+ data.tar.gz: a71465e35f0ed3a99b5ce1e61f8252669b8a4544d1bddac24c447bdfbf55d66a58598bc4644b412975c50fdf8c8ca5a5ca269255d46a7552030b094459385dae
data/README.md CHANGED
@@ -53,10 +53,10 @@ This plugin supports the following options:
53
53
 
54
54
  ## Configuration for TestKitchen
55
55
 
56
- To allow dev/prod parity, this input plugin detects if it is called from within
57
- TestKitchen. As these tests should not access the Chef Server (to provide the
58
- needed test data instead of live data), it will then revert on using the
59
- `data_bags_path` and `attributes` from kitchen's `provisioner` section:
56
+ To allow for more dev/prod parity, this input plugin detects if it is called
57
+ from within TestKitchen. As these tests should not access the Chef Server (to
58
+ provide the needed test data instead of live data), it will then revert on using
59
+ the `data_bags_path` and `attributes` from kitchen's `provisioner` section:
60
60
 
61
61
  ```yaml
62
62
  suites:
@@ -78,34 +78,32 @@ and below of the `kitchen-inspec` verifier plugin. Please check
78
78
  When this plugin is loaded, you can use databag items as inputs:
79
79
 
80
80
  ```ruby
81
- hostname = input('databag://name/item/some/nested/value')
81
+ hostname = input('databag://databag_name/item/some/nested/value')
82
82
 
83
83
  describe host(hostname, port: 80, protocol: 'tcp') do
84
84
  it { should be_reachable }
85
85
  end
86
86
  ```
87
87
 
88
- In the same way, you can also add attributes of arbitary nodes:
88
+ In the same way, you can also add attributes of arbitrary nodes:
89
89
 
90
90
  ```ruby
91
- hostname = input('node://name/attributes/some/nested/attribute')
91
+ hostname = input('node://node_name/attributes/some/nested/attribute')
92
92
 
93
93
  describe host(hostname, port: 80, protocol: 'tcp') do
94
94
  it { should be_reachable }
95
95
  end
96
96
  ```
97
97
 
98
- InSpec will go through all loaded input plugins by priority and determine the value.
98
+ It is possible to skip the node name, in which case the plugin will try to look up
99
+ the Chef client name of the node being scanned. This will, depending on the address
100
+ passed to InSpec, check via `ipaddress:`, `hostname:` or `fqdn:` queries. If this
101
+ fails, another lookup will be tried for Amazon EC2 via `ec2_public_ipv4` and
102
+ `ec2_public_hostname`.
99
103
 
100
- Keep in mind, that the node executing your InSpec runs needs to be
101
- registered with the chef server to be able to access the data. Lookup
102
- is __not__ done on the clients tested, but the executing workstation.
103
-
104
- ## Example
104
+ ## Databags Example
105
105
 
106
- With this plugin, the input names consist of the name of the databag,
107
- the item and a path getting a specific value within an item.
108
- This way, you can have a databag "configuration" with an item "database" like:
106
+ If you have a databag "configuration" with an item "database" like:
109
107
 
110
108
  ```json
111
109
  {
@@ -115,10 +113,20 @@ This way, you can have a databag "configuration" with an item "database" like:
115
113
  }
116
114
  ```
117
115
 
118
- and then use `input('databag://configuration/database/SQL/Type')` to get the
119
- "SQL2019" value out.
116
+ you can use `input('databag://configuration/database/SQL/Type')` to get the
117
+ "SQL2019" value.
118
+
119
+ ### Important Note
120
+
121
+ Keep in mind, that the node executing your InSpec runs needs to be
122
+ registered with the Chef Infra Server to be able to access the data. Lookup
123
+ is __not__ done on the clients tested, but the workstation executing InSpec.
120
124
 
121
125
  ## Limitations
122
126
 
123
- There currently is no support for arrays or more complex expressions within
124
- the query, but support via JMESPath expressions is planned.
127
+ - There currently is no support for arrays or more complex expressions within
128
+ the query, but support via JMESPath expressions is planned.
129
+ - Automatic Chef node name lookup will fail for addresses not searchable in the
130
+ `ipaddress`, `hostname` or `fqdn` fields. One case would be IPv6 target
131
+ nodes. Trying to resolve will result in error "Unable too lookup remote Chef
132
+ client name"
@@ -1,12 +1,13 @@
1
1
  require "chef-api"
2
2
  require "jmespath"
3
+ require "resolv"
3
4
  require "uri"
4
5
 
5
6
  module InspecPlugins::Chef
6
7
  class Input < Inspec.plugin(2, :input)
7
8
  VALID_PATTERNS = [
8
9
  Regexp.new("^databag://[^/]+/[^/]+/.+$"),
9
- Regexp.new("^node://[^/]+/attributes/.+$"),
10
+ Regexp.new("^node://[^/]*/attributes/.+$"),
10
11
  ].freeze
11
12
 
12
13
  attr_reader :plugin_conf, :chef_endpoint, :chef_client, :chef_api_key
@@ -16,7 +17,7 @@ module InspecPlugins::Chef
16
17
  def initialize
17
18
  @plugin_conf = Inspec::Config.cached.fetch_plugin_config("inspec-chef")
18
19
 
19
- unless Inspec::Config.cached.final_options.logger.is_a?(Kitchen::Logger)
20
+ unless defined?(Kitchen)
20
21
  @chef_endpoint = fetch_plugin_setting("endpoint")
21
22
  @chef_client = fetch_plugin_setting("client")
22
23
  @chef_api_key = fetch_plugin_setting("key")
@@ -41,7 +42,10 @@ module InspecPlugins::Chef
41
42
  data = get_attributes(input[:object]) if input[:item] == "attributes"
42
43
  end
43
44
 
44
- JMESPath.search(input[:query].join("."), data)
45
+ result = JMESPath.search(input[:query].join("."), data)
46
+ raise format("Could not resolve value for %s, check if databag/item or attribute exist", input_uri) if result.nil?
47
+
48
+ result
45
49
  end
46
50
 
47
51
  private
@@ -51,10 +55,24 @@ module InspecPlugins::Chef
51
55
  !! defined?(::Kitchen::Logger)
52
56
  end
53
57
 
58
+ # Check if this is an IP
59
+ def ip?(ip_or_name)
60
+ # Get address always returns an IP, so if nothing changes this was one
61
+ Resolv.getaddress(ip_or_name) == ip_or_name
62
+ rescue Resolv::ResolvError
63
+ false
64
+ end
65
+
66
+ # Check if this is an FQDN
67
+ def fqdn?(ip_or_name)
68
+ # If it is not an IP but contains a Dot, it is an FQDN
69
+ !ip?(ip_or_name) && ip_or_name.include?(".")
70
+ end
71
+
54
72
  # Reach for Kitchen data and return its evaluated config
55
73
  def kitchen_provisioner_config
56
- require 'binding_of_caller'
57
- kitchen = binding.callers.find { |b| b.frame_description == 'verify' }.receiver
74
+ require "binding_of_caller"
75
+ kitchen = binding.callers.find { |b| b.frame_description == "verify" }.receiver
58
76
 
59
77
  kitchen.provisioner.send(:provided_config)
60
78
  end
@@ -66,6 +84,12 @@ module InspecPlugins::Chef
66
84
  ENV[env_var_name] || plugin_conf[config_name] || default
67
85
  end
68
86
 
87
+ # Get remote address for this scan from InSpec
88
+ def inspec_target
89
+ target = Inspec::Config.cached.final_options["target"]
90
+ URI.parse(target)&.host
91
+ end
92
+
69
93
  # Establish a Chef Server connection
70
94
  def connect_to_chef_server
71
95
  @chef_api ||= ChefAPI::Connection.new(
@@ -73,6 +97,8 @@ module InspecPlugins::Chef
73
97
  client: chef_client,
74
98
  key: chef_api_key
75
99
  )
100
+
101
+ Inspec::Log.debug format("Connected to %s as client %s", chef_endpoint, chef_client)
76
102
  end
77
103
 
78
104
  # Retrieve a Databag item from Chef Server
@@ -85,12 +111,12 @@ module InspecPlugins::Chef
85
111
  chef_api.data_bag_item.fetch(item, bag: databag).data
86
112
  else
87
113
  config = kitchen_provisioner_config
88
- filename = File.join(config[:data_bags_path], databag, item + '.json')
114
+ filename = File.join(config[:data_bags_path], databag, item + ".json")
89
115
 
90
116
  begin
91
- contents = JSON.load(File.read(filename))
117
+ return JSON.load(File.read(filename))
92
118
  rescue
93
- raise format('Error accessing databag file %s, check TestKitchen configuration', filename)
119
+ raise format("Error accessing databag file %s, check TestKitchen configuration", filename)
94
120
  end
95
121
  end
96
122
  end
@@ -106,9 +132,30 @@ module InspecPlugins::Chef
106
132
  end
107
133
  end
108
134
 
135
+ # Try to look up Chef Client name by the address requested
136
+ def get_clientname(ip_or_name)
137
+ query = "hostname:%<address>s"
138
+ query = "ipaddress:%<address>s" if ip?(ip_or_name)
139
+ query = "fqdn:%<address>s" if fqdn?(ip_or_name)
140
+ result = get_search(:node, format(query, address: ip_or_name))
141
+ Inspec::Log.debug format("Automatic lookup of node name (IPv4 or hostname) returned: %s", result&.fetch("name") || "(nothing)")
142
+
143
+ # Try EC2 lookup, if nothing found (assuming public IP)
144
+ if result.nil?
145
+ query = "ec2_public_ipv4:%<address>s OR ec2_public_hostname:%<address>s"
146
+ result = get_search(:node, format(query, address: ip_or_name))
147
+ Inspec::Log.debug format("Automatic lookup of node name (EC2 public IPv4 or hostname) returned: %s", result&.fetch("name"))
148
+ end
149
+
150
+ # This will fail for cases like trying to connect to IPv6, so it will
151
+ # need extension in the future
152
+
153
+ result&.fetch("name") || raise(format("Unable too lookup remote Chef client name from %s", ip_or_name))
154
+ end
155
+
109
156
  # Low-level Chef search expression
110
157
  def get_search(index, expression)
111
- chef_api.search.query(index, expression).rows.first
158
+ chef_api.search.query(index, expression, rows: 1).rows.first
112
159
  end
113
160
 
114
161
  # Merge attributes in hierarchy like Chef
@@ -128,7 +175,7 @@ module InspecPlugins::Chef
128
175
 
129
176
  {
130
177
  type: uri.scheme.to_sym,
131
- object: uri.host,
178
+ object: uri.host || get_clientname(inspec_target),
132
179
  item: item,
133
180
  query: components,
134
181
  }
@@ -5,6 +5,6 @@
5
5
  # to learn the current version.
6
6
  module InspecPlugins
7
7
  module Chef
8
- VERSION = "0.2.0".freeze
8
+ VERSION = "0.3.0".freeze
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec-chef
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Heinen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-10 00:00:00.000000000 Z
11
+ date: 2020-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef-api