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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -22
- data/Rakefile +4 -4
- data/docs/resources/{aws_config_delivery_channel.md → aws_config_delivery_channel.md.erb} +37 -20
- data/docs/resources/aws_config_recorder.md.erb +11 -1
- data/docs/resources/aws_iam_user.md.erb +53 -2
- data/docs/resources/aws_iam_users.md.erb +194 -11
- data/docs/resources/docker.md.erb +1 -1
- data/docs/resources/users.md.erb +1 -1
- data/examples/kitchen-puppet/.kitchen.yml +1 -0
- data/examples/kitchen-puppet/modules/.gitkeep +0 -0
- data/lib/bundles/inspec-compliance/README.md +8 -0
- data/lib/bundles/inspec-compliance/api.rb +50 -6
- data/lib/bundles/inspec-compliance/api/login.rb +44 -3
- data/lib/bundles/inspec-compliance/cli.rb +10 -4
- data/lib/bundles/inspec-compliance/http.rb +39 -0
- data/lib/bundles/inspec-compliance/target.rb +8 -1
- data/lib/fetchers/url.rb +40 -3
- data/lib/inspec/base_cli.rb +3 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/aws/aws_config_delivery_channel.rb +14 -20
- data/lib/resources/aws/aws_config_recorder.rb +21 -26
- data/lib/resources/aws/aws_iam_policy.rb +19 -2
- data/lib/resources/aws/aws_iam_role.rb +4 -0
- data/lib/resources/aws/aws_iam_user.rb +32 -1
- data/lib/resources/aws/aws_iam_users.rb +40 -2
- data/lib/resources/service.rb +1 -1
- metadata +4 -3
@@ -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
|
|
data/docs/resources/users.md.erb
CHANGED
File without changes
|
@@ -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
|
-
|
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']
|
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
|
-
|
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
|
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
|
-
|
22
|
+
Login::AutomateServer.login(options)
|
21
23
|
when :compliance
|
22
|
-
|
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
|
-
|
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
|
-
|
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)
|
198
|
-
|
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::
|
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
|
data/lib/fetchers/url.rb
CHANGED
@@ -136,7 +136,44 @@ module Fetchers
|
|
136
136
|
end
|
137
137
|
|
138
138
|
def temp_archive_path
|
139
|
-
@temp_archive_path ||=
|
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
|
-
|
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']
|
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']
|
data/lib/inspec/base_cli.rb
CHANGED
data/lib/inspec/version.rb
CHANGED