inspec 2.1.43 → 2.1.54

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.
@@ -5,7 +5,7 @@ platform: linux
5
5
 
6
6
  # docker
7
7
 
8
- Use the `docker` InSpec audit resource to test configuration data for the Docker daemon. It is a very comprehensive resource. See also: [docker_container](docker_container) and [docker_image](docker_image), too.
8
+ Use the `docker` InSpec audit resource to test configuration data for the Docker daemon. It is a very comprehensive resource. See also: [docker_container](https://www.inspec.io/docs/reference/resources/docker_container/) and [docker_image](https://www.inspec.io/docs/reference/resources/docker_image/), too.
9
9
 
10
10
  <br>
11
11
 
@@ -108,7 +108,7 @@ where `0` represents the maximum number of days.
108
108
 
109
109
  The `shell` matcher tests the path to the default shell for the user:
110
110
 
111
- its('shell') { should eq '/bin/bash' }
111
+ its('shells') { should eq ['/bin/bash'] }
112
112
 
113
113
  ### uid
114
114
 
@@ -7,6 +7,7 @@ provisioner:
7
7
  # Not installing chef since inspec is used for testing
8
8
  require_chef_for_busser: false
9
9
  manifests_path: manifests
10
+ modules_path: modules
10
11
 
11
12
  verifier:
12
13
  name: inspec
@@ -37,6 +37,14 @@ Commands:
37
37
  inspec compliance version # displays the version of the Chef Compliance server
38
38
  ```
39
39
 
40
+ ### Login with Chef Automate2
41
+
42
+ You will need an API token for authentication. You can retrieve one via the admin section of your A2 web gui.
43
+
44
+ ```
45
+ $ inspec compliance login https://automate2.compliance.test --insecure --user 'admin' --token 'zuop..._KzE'
46
+ ```
47
+
40
48
  ### Login with Chef Automate
41
49
 
42
50
  You will need an access token for authentication. You can retrieve one via [UI](https://docs.chef.io/api_delivery.html) or [CLI](https://docs.chef.io/ctl_delivery.html#delivery-token).
@@ -4,6 +4,7 @@
4
4
 
5
5
  require 'net/http'
6
6
  require 'uri'
7
+ require 'json'
7
8
 
8
9
  require_relative 'api/login'
9
10
 
@@ -18,12 +19,15 @@ module Compliance
18
19
  # return all compliance profiles available for the user
19
20
  # the user is either specified in the options hash or by default
20
21
  # the username of the account is used that is logged in
21
- def self.profiles(config)
22
+ def self.profiles(config) # rubocop:disable PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength
22
23
  owner = config['owner'] || config['user']
23
24
 
24
25
  # Chef Compliance
25
26
  if is_compliance_server?(config)
26
27
  url = "#{config['server']}/user/compliance"
28
+ # Chef Automate2
29
+ elsif is_automate2_server?(config)
30
+ url = "#{config['server']}/compliance/profiles/search"
27
31
  # Chef Automate
28
32
  elsif is_automate_server?(config)
29
33
  url = "#{config['server']}/profiles/#{owner}"
@@ -32,7 +36,13 @@ module Compliance
32
36
  end
33
37
 
34
38
  headers = get_headers(config)
35
- response = Compliance::HTTP.get(url, headers, config['insecure'])
39
+
40
+ if is_automate2_server?(config)
41
+ body = { owner: owner }.to_json
42
+ response = Compliance::HTTP.post_with_headers(url, headers, body, config['insecure'])
43
+ else
44
+ response = Compliance::HTTP.get(url, headers, config['insecure'])
45
+ end
36
46
  data = response.body
37
47
  response_code = response.code
38
48
  case response_code
@@ -48,6 +58,11 @@ module Compliance
48
58
  # Chef Automate pre 0.8.0
49
59
  elsif is_automate_server_pre_080?(config)
50
60
  mapped_profiles = profiles.values.flatten
61
+ elsif is_automate2_server?(config)
62
+ mapped_profiles = []
63
+ profiles['profiles'].each { |p|
64
+ mapped_profiles << p
65
+ }
51
66
  else
52
67
  mapped_profiles = profiles.map { |e|
53
68
  e['owner_id'] = owner
@@ -97,7 +112,8 @@ module Compliance
97
112
 
98
113
  if !profiles.empty?
99
114
  profiles.any? do |p|
100
- p['owner_id'] == owner &&
115
+ profile_owner = p['owner_id'] || p['owner']
116
+ profile_owner == owner &&
101
117
  p['name'] == id &&
102
118
  (ver.nil? || p['version'] == ver)
103
119
  end
@@ -113,13 +129,20 @@ module Compliance
113
129
  # Chef Automate pre 0.8.0
114
130
  elsif is_automate_server_pre_080?(config)
115
131
  url = "#{config['server']}/#{owner}"
132
+ elsif is_automate2_server?(config)
133
+ url = "#{config['server']}/compliance/profiles?owner=#{owner}"
116
134
  # Chef Automate
117
135
  else
118
136
  url = "#{config['server']}/profiles/#{owner}"
119
137
  end
120
138
 
121
139
  headers = get_headers(config)
122
- res = Compliance::HTTP.post_file(url, headers, archive_path, config['insecure'])
140
+ if is_automate2_server?(config)
141
+ res = Compliance::HTTP.post_multipart_file(url, headers, archive_path, config['insecure'])
142
+ else
143
+ res = Compliance::HTTP.post_file(url, headers, archive_path, config['insecure'])
144
+ end
145
+
123
146
  [res.is_a?(Net::HTTPSuccess), res.body]
124
147
  end
125
148
 
@@ -173,7 +196,7 @@ module Compliance
173
196
 
174
197
  def self.get_headers(config)
175
198
  token = get_token(config)
176
- if is_automate_server?(config)
199
+ if is_automate_server?(config) || is_automate2_server?(config)
177
200
  headers = { 'chef-delivery-enterprise' => config['automate']['ent'] }
178
201
  if config['automate']['token_type'] == 'dctoken'
179
202
  headers['x-data-collector-token'] = token
@@ -196,6 +219,7 @@ module Compliance
196
219
  def self.target_url(config, profile)
197
220
  owner, id, ver = profile_split(profile)
198
221
 
222
+ return "#{config['server']}/compliance/profiles/tar" if is_automate2_server?(config)
199
223
  return "#{config['server']}/owners/#{owner}/compliance/#{id}/tar" unless is_automate_server?(config)
200
224
 
201
225
  if ver.nil?
@@ -238,6 +262,10 @@ module Compliance
238
262
  !server_version_from_config(config).nil?
239
263
  end
240
264
 
265
+ def self.is_automate2_server?(config)
266
+ config['server_type'] == 'automate2'
267
+ end
268
+
241
269
  def self.is_automate_server?(config)
242
270
  config['server_type'] == 'automate'
243
271
  end
@@ -251,7 +279,9 @@ module Compliance
251
279
  end
252
280
 
253
281
  def self.determine_server_type(url, insecure)
254
- if target_is_automate_server?(url, insecure)
282
+ if target_is_automate2_server?(url, insecure)
283
+ :automate2
284
+ elsif target_is_automate_server?(url, insecure)
255
285
  :automate
256
286
  elsif target_is_compliance_server?(url, insecure)
257
287
  :compliance
@@ -261,6 +291,20 @@ module Compliance
261
291
  end
262
292
  end
263
293
 
294
+ def self.target_is_automate2_server?(url, insecure)
295
+ automate_endpoint = '/dex/auth'
296
+ response = Compliance::HTTP.get(url + automate_endpoint, nil, insecure)
297
+ if response.code == '400'
298
+ Inspec::Log.debug(
299
+ "Received 400 from #{url}#{automate_endpoint} - " \
300
+ 'assuming target is a Chef Automate2 instance',
301
+ )
302
+ true
303
+ else
304
+ false
305
+ end
306
+ end
307
+
264
308
  def self.target_is_automate_server?(url, insecure)
265
309
  automate_endpoint = '/compliance/version'
266
310
  response = Compliance::HTTP.get(url + automate_endpoint, nil, insecure)
@@ -16,15 +16,56 @@ module Compliance
16
16
  options['server_type'] = Compliance::API.determine_server_type(options['server'], options['insecure'])
17
17
 
18
18
  case options['server_type']
19
+ when :automate2
20
+ Login::Automate2Server.login(options)
19
21
  when :automate
20
- config = Login::AutomateServer.login(options)
22
+ Login::AutomateServer.login(options)
21
23
  when :compliance
22
- config = Login::ComplianceServer.login(options)
24
+ Login::ComplianceServer.login(options)
23
25
  else
24
26
  raise CannotDetermineServerType, "Unable to determine if #{options['server']} is a Chef Automate or Chef Compliance server"
25
27
  end
28
+ end
26
29
 
27
- puts "Stored configuration for Chef #{config['server_type'].capitalize}: #{config['server']}' with user: '#{config['user']}'"
30
+ module Automate2Server
31
+ def self.login(options)
32
+ verify_thor_options(options)
33
+
34
+ options['url'] = options['server'] + '/api/v0'
35
+ token = options['dctoken'] || options['token']
36
+ store_access_token(options, token)
37
+ end
38
+
39
+ def self.store_access_token(options, token)
40
+ config = Compliance::Configuration.new
41
+ config.clean
42
+
43
+ config['automate'] = {}
44
+ config['automate']['ent'] = 'automate'
45
+ config['automate']['token_type'] = 'dctoken'
46
+ config['server'] = options['url']
47
+ config['user'] = options['user']
48
+ config['owner'] = options['user']
49
+ config['insecure'] = options['insecure'] || false
50
+ config['server_type'] = options['server_type'].to_s
51
+ config['token'] = token
52
+ config['version'] = '0'
53
+
54
+ config.store
55
+ config
56
+ end
57
+
58
+ def self.verify_thor_options(o)
59
+ error_msg = []
60
+
61
+ error_msg.push('Please specify a user using `--user=\'USER\'`') if o['user'].nil?
62
+
63
+ if o['token'].nil? && o['dctoken'].nil?
64
+ error_msg.push('Please specify a token using `--token=\'APITOKEN\'`')
65
+ end
66
+
67
+ raise ArgumentError, error_msg.join("\n") unless error_msg.empty?
68
+ end
28
69
  end
29
70
 
30
71
  module AutomateServer
@@ -44,6 +44,8 @@ module Compliance
44
44
  def login(server)
45
45
  options['server'] = server
46
46
  Compliance::API.login(options)
47
+ config = Compliance::Configuration.new
48
+ puts "Stored configuration for Chef #{config['server_type'].capitalize}: #{config['server']}' with user: '#{config['user']}'"
47
49
  end
48
50
 
49
51
  desc 'profiles', 'list all available profiles in Chef Compliance'
@@ -62,7 +64,8 @@ module Compliance
62
64
  # iterate over profiles
63
65
  headline('Available profiles:')
64
66
  profiles.each { |profile|
65
- li("#{profile['title']} v#{profile['version']} (#{mark_text(profile['owner_id'] + '/' + profile['name'])})")
67
+ owner = profile['owner_id'] || profile['owner']
68
+ li("#{profile['title']} v#{profile['version']} (#{mark_text(owner + '/' + profile['name'])})")
66
69
  }
67
70
  else
68
71
  puts msg, 'Could not find any profiles'
@@ -194,8 +197,11 @@ module Compliance
194
197
  puts "Start upload to #{config['owner']}/#{profile_name}"
195
198
  pname = ERB::Util.url_encode(profile_name)
196
199
 
197
- Compliance::API.is_automate_server?(config) ? upload_msg = 'Uploading to Chef Automate' : upload_msg = 'Uploading to Chef Compliance'
198
- puts upload_msg
200
+ if Compliance::API.is_automate_server?(config) || Compliance::API.is_automate2_server?(config)
201
+ puts 'Uploading to Chef Automate'
202
+ else
203
+ puts 'Uploading to Chef Compliance'
204
+ end
199
205
  success, msg = Compliance::API.upload(config, config['owner'], pname, archive_path)
200
206
 
201
207
  if success
@@ -229,7 +235,7 @@ module Compliance
229
235
  unless config.supported?(:oidc) || config['token'].nil? || config['server_type'] == 'automate'
230
236
  config = Compliance::Configuration.new
231
237
  url = "#{config['server']}/logout"
232
- Compliance::API.post(url, config['token'], config['insecure'], !config.supported?(:oidc))
238
+ Compliance::HTTP.post(url, config['token'], config['insecure'], !config.supported?(:oidc))
233
239
  end
234
240
  success = config.destroy
235
241
 
@@ -33,6 +33,16 @@ module Compliance
33
33
  send_request(uri, req, insecure)
34
34
  end
35
35
 
36
+ def self.post_with_headers(url, headers, body, insecure)
37
+ uri = _parse_url(url)
38
+ req = Net::HTTP::Post.new(uri.path)
39
+ req.body = body unless body.nil?
40
+ headers&.each do |key, value|
41
+ req.add_field(key, value)
42
+ end
43
+ send_request(uri, req, insecure)
44
+ end
45
+
36
46
  # post a file
37
47
  def self.post_file(url, headers, file_path, insecure)
38
48
  uri = _parse_url(url)
@@ -58,6 +68,35 @@ module Compliance
58
68
  res
59
69
  end
60
70
 
71
+ def self.post_multipart_file(url, headers, file_path, insecure)
72
+ uri = _parse_url(url)
73
+ raise "Unable to parse URL: #{url}" if uri.nil? || uri.host.nil?
74
+ http = Net::HTTP.new(uri.host, uri.port)
75
+
76
+ # set connection flags
77
+ http.use_ssl = (uri.scheme == 'https')
78
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if insecure
79
+
80
+ req = Net::HTTP::Post.new(uri)
81
+ headers.each do |key, value|
82
+ req.add_field(key, value)
83
+ end
84
+
85
+ boundry = 'AaB03x'
86
+ req.add_field('Content-Type', "multipart/form-data; boundary=#{boundry}")
87
+
88
+ post_body = []
89
+ post_body << "--#{boundry}\r\n"
90
+ post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{File.basename(file_path)}\"\r\n"
91
+ post_body << "Content-Type: application/x-gtar\r\n\r\n"
92
+ post_body << File.read(file_path)
93
+ post_body << "\r\n\r\n--#{boundry}--\r\n"
94
+ req.body = post_body.join
95
+
96
+ res=http.request(req)
97
+ res
98
+ end
99
+
61
100
  # sends a http requests
62
101
  def self.send_request(uri, req, insecure)
63
102
  opts = {
@@ -13,7 +13,7 @@ module Compliance
13
13
  class Fetcher < Fetchers::Url
14
14
  name 'compliance'
15
15
  priority 500
16
- def self.resolve(target) # rubocop:disable PerceivedComplexity, Metrics/CyclomaticComplexity
16
+ def self.resolve(target) # rubocop:disable PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize
17
17
  uri = if target.is_a?(String) && URI(target).scheme == 'compliance'
18
18
  URI(target)
19
19
  elsif target.respond_to?(:key?) && target.key?(:compliance)
@@ -33,6 +33,9 @@ module Compliance
33
33
  if config['server_type'] == 'automate'
34
34
  server = 'automate'
35
35
  msg = 'inspec compliance login https://your_automate_server --user USER --ent ENT --dctoken DCTOKEN or --token USERTOKEN'
36
+ elsif config['server_type'] == 'automate2'
37
+ server = 'automate2'
38
+ msg = 'inspec compliance login https://your_automate2_server --user USER --token APITOKEN'
36
39
  else
37
40
  server = 'compliance'
38
41
  msg = "inspec compliance login https://your_compliance_server --user admin --insecure --token 'PASTE TOKEN HERE' "
@@ -57,6 +60,10 @@ module Compliance
57
60
  end
58
61
  # We need to pass the token to the fetcher
59
62
  config['token'] = Compliance::API.get_token(config)
63
+
64
+ # Needed for automate2 post request
65
+ config['profile'] = Compliance::API.profile_split(profile)
66
+
60
67
  new(profile_fetch_url, config)
61
68
  rescue URI::Error => _e
62
69
  nil
@@ -136,7 +136,44 @@ module Fetchers
136
136
  end
137
137
 
138
138
  def temp_archive_path
139
- @temp_archive_path ||= download_archive_to_temp
139
+ @temp_archive_path ||= if @config['server_type'] == 'automate2'
140
+ download_automate2_archive_to_temp
141
+ else
142
+ download_archive_to_temp
143
+ end
144
+ end
145
+
146
+ def download_automate2_archive_to_temp
147
+ return @temp_archive_path if !@temp_archive_path.nil?
148
+
149
+ Inspec::Log.debug("Fetching URL: #{@target}")
150
+ json = {
151
+ owner: @config['profile'][0],
152
+ name: @config['profile'][1],
153
+ version: @config['profile'][2],
154
+ }.to_json
155
+
156
+ uri = URI.parse(@target)
157
+ opts = http_opts
158
+ opts[:use_ssl] = uri.scheme == 'https'
159
+
160
+ req = Net::HTTP::Post.new(uri)
161
+ opts.each do |key, value|
162
+ req.add_field(key, value)
163
+ end
164
+ req.body = json
165
+ res = Net::HTTP.start(uri.host, uri.port, opts) { |http|
166
+ http.request(req)
167
+ }
168
+
169
+ @archive_type = '.tar.gz'
170
+ archive = Tempfile.new(['inspec-dl-', @archive_type])
171
+ archive.binmode
172
+ archive.write(res.body)
173
+ archive.rewind
174
+ archive.close
175
+ Inspec::Log.debug("Archive stored at temporary location: #{archive.path}")
176
+ @temp_archive_path = archive.path
140
177
  end
141
178
 
142
179
  # Downloads archive to temporary file with side effect :( of setting @archive_type
@@ -155,7 +192,7 @@ module Fetchers
155
192
  end
156
193
 
157
194
  def download_archive(path)
158
- download_archive_to_temp
195
+ temp_archive_path
159
196
  final_path = "#{path}#{@archive_type}"
160
197
  FileUtils.mkdir_p(File.dirname(final_path))
161
198
  FileUtils.mv(temp_archive_path, final_path)
@@ -168,7 +205,7 @@ module Fetchers
168
205
  opts = {}
169
206
  opts[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if @insecure
170
207
 
171
- if @config['server_type'] == 'automate'
208
+ if @config['server_type'] =~ /automate/
172
209
  opts['chef-delivery-enterprise'] = @config['automate']['ent']
173
210
  if @config['automate']['token_type'] == 'dctoken'
174
211
  opts['x-data-collector-token'] = @config['token']
@@ -240,6 +240,9 @@ module Inspec
240
240
  raise ArgumentError, "Please provide a value for --#{v}. For example: --#{v}=hello."
241
241
  end
242
242
 
243
+ # check for compliance settings
244
+ Compliance::API.login(o['compliance']) if o['compliance']
245
+
243
246
  o
244
247
  end
245
248
 
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '2.1.43'
7
+ VERSION = '2.1.54'
8
8
  end