inspec 2.1.43 → 2.1.54

Sign up to get free protection for your applications and to get access to all the features.
@@ -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