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