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
@@ -28,29 +28,23 @@ class AwsConfigDeliveryChannel < Inspec.resource(1)
|
|
28
28
|
allowed_scalar_type: String,
|
29
29
|
)
|
30
30
|
|
31
|
-
# Make sure channel_name is given as param
|
32
|
-
if validated_params[:channel_name].nil?
|
33
|
-
raise ArgumentError, 'You must provide a channel_name to aws_config_delivery_channel'
|
34
|
-
end
|
35
|
-
|
36
31
|
validated_params
|
37
32
|
end
|
38
33
|
|
39
34
|
def fetch_from_api
|
40
35
|
backend = BackendFactory.create(inspec_runner)
|
41
|
-
query = { delivery_channel_names: [@channel_name] }
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
return unless @exists
|
36
|
+
query = @channel_name ? { delivery_channel_names: [@channel_name] } : {}
|
37
|
+
response = backend.describe_delivery_channels(query)
|
38
|
+
|
39
|
+
@exists = !response.delivery_channels.empty?
|
40
|
+
return unless exists?
|
47
41
|
|
48
|
-
|
49
|
-
@channel_name =
|
50
|
-
@s3_bucket_name =
|
51
|
-
@s3_key_prefix =
|
52
|
-
@sns_topic_arn =
|
53
|
-
@delivery_frequency_in_hours =
|
42
|
+
channel = response.delivery_channels.first.to_h
|
43
|
+
@channel_name = channel[:name]
|
44
|
+
@s3_bucket_name = channel[:s3_bucket_name]
|
45
|
+
@s3_key_prefix = channel[:s3_key_prefix]
|
46
|
+
@sns_topic_arn = channel[:sns_topic_arn]
|
47
|
+
@delivery_frequency_in_hours = channel.dig(:config_snapshot_delivery_properties, :delivery_frequency)
|
54
48
|
frequencies = {
|
55
49
|
'One_Hour' => 1,
|
56
50
|
'TwentyFour_Hours' => 24,
|
@@ -59,6 +53,8 @@ class AwsConfigDeliveryChannel < Inspec.resource(1)
|
|
59
53
|
'Twelve_Hours' => 12,
|
60
54
|
}
|
61
55
|
@delivery_frequency_in_hours = frequencies[@delivery_frequency_in_hours]
|
56
|
+
rescue Aws::ConfigService::Errors::NoSuchDeliveryChannelException
|
57
|
+
@exists = false
|
62
58
|
end
|
63
59
|
|
64
60
|
class Backend
|
@@ -66,10 +62,8 @@ class AwsConfigDeliveryChannel < Inspec.resource(1)
|
|
66
62
|
BackendFactory.set_default_backend(self)
|
67
63
|
self.aws_client_class = Aws::ConfigService::Client
|
68
64
|
|
69
|
-
def describe_delivery_channels(query)
|
65
|
+
def describe_delivery_channels(query = {})
|
70
66
|
aws_service_client.describe_delivery_channels(query)
|
71
|
-
rescue Aws::ConfigService::Errors::NoSuchDeliveryChannelException
|
72
|
-
return {}
|
73
67
|
end
|
74
68
|
end
|
75
69
|
end
|
@@ -12,7 +12,7 @@ class AwsConfigurationRecorder < Inspec.resource(1)
|
|
12
12
|
supports platform: 'aws'
|
13
13
|
|
14
14
|
include AwsSingularResourceMixin
|
15
|
-
attr_reader :role_arn, :resource_types, :recorder_name
|
15
|
+
attr_reader :role_arn, :resource_types, :recorder_name
|
16
16
|
|
17
17
|
def to_s
|
18
18
|
"Configuration_Recorder: #{@recorder_name}"
|
@@ -27,11 +27,11 @@ class AwsConfigurationRecorder < Inspec.resource(1)
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def status
|
30
|
-
return unless @exists
|
30
|
+
return {} unless @exists
|
31
31
|
backend = BackendFactory.create(inspec_runner)
|
32
32
|
catch_aws_errors do
|
33
|
-
|
34
|
-
@status =
|
33
|
+
response = backend.describe_configuration_recorder_status(configuration_recorder_names: [@recorder_name])
|
34
|
+
@status = response.configuration_recorders_status.first.to_h
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
@@ -50,35 +50,30 @@ class AwsConfigurationRecorder < Inspec.resource(1)
|
|
50
50
|
allowed_scalar_type: String,
|
51
51
|
)
|
52
52
|
|
53
|
-
# Must give it a recorder_name
|
54
|
-
if validated_params[:recorder_name].nil?
|
55
|
-
raise ArgumentError, 'You must provide recorder_name to aws_config_recorder'
|
56
|
-
end
|
57
|
-
|
58
53
|
validated_params
|
59
54
|
end
|
60
55
|
|
61
56
|
def fetch_from_api
|
62
57
|
backend = BackendFactory.create(inspec_runner)
|
63
|
-
|
58
|
+
query = @recorder_name ? { configuration_recorder_names: [@recorder_name] } : {}
|
59
|
+
response = backend.describe_configuration_recorders(query)
|
64
60
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
return
|
71
|
-
end
|
72
|
-
@exists = !@resp.empty?
|
73
|
-
return unless @exists
|
74
|
-
|
75
|
-
@recorder = @resp.configuration_recorders.first.to_h
|
76
|
-
@recorder_name = @recorder[:name]
|
77
|
-
@role_arn = @recorder[:role_arn]
|
78
|
-
@recording_all_resource_types = @recorder[:recording_group][:all_supported]
|
79
|
-
@recording_all_global_types = @recorder[:recording_group][:include_global_resource_types]
|
80
|
-
@resource_types = @recorder[:recording_group][:resource_types]
|
61
|
+
@exists = !response.configuration_recorders.empty?
|
62
|
+
return unless exists?
|
63
|
+
|
64
|
+
if response.configuration_recorders.count > 1
|
65
|
+
raise ArgumentError, 'Internal error: unexpectedly received multiple AWS Config Recorder objects from API; expected to be singleton per-region. Please file a bug report at https://github.com/chef/inspec/issues .'
|
81
66
|
end
|
67
|
+
|
68
|
+
recorder = response.configuration_recorders.first.to_h
|
69
|
+
@recorder_name = recorder[:name]
|
70
|
+
@role_arn = recorder[:role_arn]
|
71
|
+
@recording_all_resource_types = recorder[:recording_group][:all_supported]
|
72
|
+
@recording_all_global_types = recorder[:recording_group][:include_global_resource_types]
|
73
|
+
@resource_types = recorder[:recording_group][:resource_types]
|
74
|
+
rescue Aws::ConfigService::Errors::NoSuchConfigurationRecorderException
|
75
|
+
@exists = false
|
76
|
+
return
|
82
77
|
end
|
83
78
|
|
84
79
|
class Backend
|
@@ -83,7 +83,14 @@ class AwsIamPolicy < Inspec.resource(1)
|
|
83
83
|
|
84
84
|
def statement_count
|
85
85
|
return nil unless exists?
|
86
|
-
|
86
|
+
# Typically it is an array of statements
|
87
|
+
if policy['Statement'].is_a? Array
|
88
|
+
policy['Statement'].count
|
89
|
+
else
|
90
|
+
# But if there is one statement, it is permissable to degenerate the array,
|
91
|
+
# and place the statement as a hash directly under the 'Statement' key
|
92
|
+
return 1
|
93
|
+
end
|
87
94
|
end
|
88
95
|
|
89
96
|
def has_statement?(raw_criteria = {})
|
@@ -141,6 +148,11 @@ class AwsIamPolicy < Inspec.resource(1)
|
|
141
148
|
end
|
142
149
|
|
143
150
|
def has_statement__normalize_statements
|
151
|
+
# Some single-statement policies place their statement
|
152
|
+
# directly in policy['Statement'], rather than in an
|
153
|
+
# Array within it. See arn:aws:iam::aws:policy/AWSCertificateManagerReadOnly
|
154
|
+
# Thus, coerce to Array.
|
155
|
+
policy['Statement'] = [policy['Statement']] if policy['Statement'].is_a? Hash
|
144
156
|
policy['Statement'].map do |statement|
|
145
157
|
# Coerce some values into arrays
|
146
158
|
%w{Action Resource}.each do |field|
|
@@ -177,7 +189,12 @@ class AwsIamPolicy < Inspec.resource(1)
|
|
177
189
|
def has_statement__array_criterion(crit_name, statement, criteria)
|
178
190
|
return true unless criteria.key?(crit_name)
|
179
191
|
check = criteria[crit_name]
|
180
|
-
|
192
|
+
# This is an array due to normalize_statements
|
193
|
+
# If it is nil, the statement does not have an entry for that dimension;
|
194
|
+
# but since we were asked to match on it (on nothing), we
|
195
|
+
# decide to never match
|
196
|
+
values = statement[crit_name]
|
197
|
+
return false if values.nil?
|
181
198
|
|
182
199
|
if check.is_a?(String)
|
183
200
|
# If check is a string, it only has to match one of the values
|
@@ -9,12 +9,15 @@ class AwsIamUser < Inspec.resource(1)
|
|
9
9
|
describe aws_iam_user(username: 'test_user') do
|
10
10
|
it { should have_mfa_enabled }
|
11
11
|
it { should_not have_console_password }
|
12
|
+
it { should_not have_inline_user_policies }
|
13
|
+
it { should_not have_attached_user_policies }
|
12
14
|
end
|
13
15
|
"
|
14
16
|
supports platform: 'aws'
|
15
17
|
|
16
18
|
include AwsSingularResourceMixin
|
17
|
-
attr_reader :access_keys, :
|
19
|
+
attr_reader :access_keys, :attached_policy_names, :attached_policy_arns, \
|
20
|
+
:has_console_password, :has_mfa_enabled, :inline_policy_names, :username
|
18
21
|
alias has_mfa_enabled? has_mfa_enabled
|
19
22
|
alias has_console_password? has_console_password
|
20
23
|
|
@@ -27,6 +30,16 @@ class AwsIamUser < Inspec.resource(1)
|
|
27
30
|
"IAM User #{username}"
|
28
31
|
end
|
29
32
|
|
33
|
+
def has_attached_policies?
|
34
|
+
return nil unless exists?
|
35
|
+
!attached_policy_names.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_inline_policies?
|
39
|
+
return nil unless exists?
|
40
|
+
!inline_policy_names.empty?
|
41
|
+
end
|
42
|
+
|
30
43
|
private
|
31
44
|
|
32
45
|
def validate_params(raw_params)
|
@@ -62,6 +75,10 @@ class AwsIamUser < Inspec.resource(1)
|
|
62
75
|
@aws_user_struct = backend.get_user(user_name: username)
|
63
76
|
rescue Aws::IAM::Errors::NoSuchEntity
|
64
77
|
@exists = false
|
78
|
+
@access_keys = []
|
79
|
+
@inline_policy_names = []
|
80
|
+
@attached_policy_arns = []
|
81
|
+
@attached_policy_names = []
|
65
82
|
return
|
66
83
|
end
|
67
84
|
end
|
@@ -84,6 +101,12 @@ class AwsIamUser < Inspec.resource(1)
|
|
84
101
|
@access_keys = backend.list_access_keys(user_name: username).access_key_metadata
|
85
102
|
# If the above call fails, we get nil here; but we promise access_keys will be an array.
|
86
103
|
@access_keys ||= []
|
104
|
+
|
105
|
+
@inline_policy_names = backend.list_user_policies(user_name: username).policy_names
|
106
|
+
|
107
|
+
attached_policies = backend.list_attached_user_policies(user_name: username).attached_policies
|
108
|
+
@attached_policy_arns = attached_policies.map { |p| p[:policy_arn] }
|
109
|
+
@attached_policy_names = attached_policies.map { |p| p[:policy_name] }
|
87
110
|
end
|
88
111
|
|
89
112
|
class Backend
|
@@ -106,6 +129,14 @@ class AwsIamUser < Inspec.resource(1)
|
|
106
129
|
def list_access_keys(criteria)
|
107
130
|
aws_service_client.list_access_keys(criteria)
|
108
131
|
end
|
132
|
+
|
133
|
+
def list_user_policies(criteria)
|
134
|
+
aws_service_client.list_user_policies(criteria)
|
135
|
+
end
|
136
|
+
|
137
|
+
def list_attached_user_policies(criteria)
|
138
|
+
aws_service_client.list_attached_user_policies(criteria)
|
139
|
+
end
|
109
140
|
end
|
110
141
|
end
|
111
142
|
end
|
@@ -12,6 +12,12 @@ class AwsIamUsers < Inspec.resource(1)
|
|
12
12
|
describe aws_iam_users.where(has_console_password?: true) do
|
13
13
|
it { should exist }
|
14
14
|
end
|
15
|
+
describe aws_iam_users.where(has_inline_policies?: true) do
|
16
|
+
it { should_not exist }
|
17
|
+
end
|
18
|
+
describe aws_iam_users.where(has_attached_policies?: true) do
|
19
|
+
it { should_not exist }
|
20
|
+
end
|
15
21
|
'
|
16
22
|
supports platform: 'aws'
|
17
23
|
|
@@ -23,10 +29,21 @@ class AwsIamUsers < Inspec.resource(1)
|
|
23
29
|
.add(:exists?) { |x| !x.entries.empty? }
|
24
30
|
.add(:has_mfa_enabled?, field: :has_mfa_enabled)
|
25
31
|
.add(:has_console_password?, field: :has_console_password)
|
32
|
+
.add(:has_inline_policies?, field: :has_inline_policies)
|
33
|
+
.add(:has_attached_policies?, field: :has_attached_policies)
|
26
34
|
.add(:password_ever_used?, field: :password_ever_used?)
|
27
35
|
.add(:password_never_used?, field: :password_never_used?)
|
28
36
|
.add(:password_last_used_days_ago, field: :password_last_used_days_ago)
|
29
|
-
.add(:
|
37
|
+
.add(:usernames, field: :user_name)
|
38
|
+
.add(:username) { |res| res.entries.map { |row| row[:user_name] } } # We should deprecate this; plural resources get plural properties
|
39
|
+
# Next three are needed to declare fields for use by the de-duped set
|
40
|
+
filter.add(:dupe_inline_policy_names, field: :inline_policy_names_source)
|
41
|
+
.add(:dupe_attached_policy_names, field: :attached_policy_names_source)
|
42
|
+
.add(:dupe_attached_policy_arns, field: :attached_policy_arns_source)
|
43
|
+
# These three are now able to access the above three in .entries
|
44
|
+
filter.add(:inline_policy_names) { |obj| obj.dupe_inline_policy_names.flatten.uniq }
|
45
|
+
.add(:attached_policy_names) { |obj| obj.dupe_attached_policy_names.flatten.uniq }
|
46
|
+
.add(:attached_policy_arns) { |obj| obj.dupe_attached_policy_arns.flatten.uniq }
|
30
47
|
filter.connect(self, :table)
|
31
48
|
|
32
49
|
def validate_params(raw_params)
|
@@ -49,12 +66,14 @@ class AwsIamUsers < Inspec.resource(1)
|
|
49
66
|
table
|
50
67
|
end
|
51
68
|
|
52
|
-
def fetch_from_api
|
69
|
+
def fetch_from_api # rubocop: disable Metrics/AbcSize
|
53
70
|
backend = BackendFactory.create(inspec_runner)
|
54
71
|
@table = fetch_from_api_paginated(backend)
|
55
72
|
|
56
73
|
# TODO: lazy columns - https://github.com/chef/inspec-aws/issues/100
|
57
74
|
@table.each do |user|
|
75
|
+
# Some of these throw exceptions to indicate empty results;
|
76
|
+
# others return empty arrays
|
58
77
|
begin
|
59
78
|
_login_profile = backend.get_login_profile(user_name: user[:user_name])
|
60
79
|
user[:has_console_password] = true
|
@@ -70,6 +89,17 @@ class AwsIamUsers < Inspec.resource(1)
|
|
70
89
|
user[:has_mfa_enabled] = false
|
71
90
|
end
|
72
91
|
user[:has_mfa_enabled?] = user[:has_mfa_enabled]
|
92
|
+
|
93
|
+
user[:inline_policy_names_source] = backend.list_user_policies(user_name: user[:user_name]).policy_names
|
94
|
+
user[:has_inline_policies] = !user[:inline_policy_names_source].empty?
|
95
|
+
user[:has_inline_policies?] = user[:has_inline_policies]
|
96
|
+
|
97
|
+
attached_policies = backend.list_attached_user_policies(user_name: user[:user_name]).attached_policies
|
98
|
+
user[:has_attached_policies] = !attached_policies.empty?
|
99
|
+
user[:has_attached_policies?] = user[:has_attached_policies]
|
100
|
+
user[:attached_policy_names_source] = attached_policies.map { |p| p[:policy_name] }
|
101
|
+
user[:attached_policy_arns_source] = attached_policies.map { |p| p[:policy_arn] }
|
102
|
+
|
73
103
|
password_last_used = user[:password_last_used]
|
74
104
|
user[:password_ever_used?] = !password_last_used.nil?
|
75
105
|
user[:password_never_used?] = password_last_used.nil?
|
@@ -103,6 +133,14 @@ class AwsIamUsers < Inspec.resource(1)
|
|
103
133
|
def list_mfa_devices(query)
|
104
134
|
aws_service_client.list_mfa_devices(query)
|
105
135
|
end
|
136
|
+
|
137
|
+
def list_user_policies(query)
|
138
|
+
aws_service_client.list_user_policies(query)
|
139
|
+
end
|
140
|
+
|
141
|
+
def list_attached_user_policies(query)
|
142
|
+
aws_service_client.list_attached_user_policies(query)
|
143
|
+
end
|
106
144
|
end
|
107
145
|
end
|
108
146
|
end
|
data/lib/resources/service.rb
CHANGED
@@ -162,7 +162,7 @@ module Inspec::Resources
|
|
162
162
|
elsif %w{aix}.include?(platform)
|
163
163
|
SrcMstr.new(inspec)
|
164
164
|
elsif %w{amazon}.include?(platform)
|
165
|
-
if os[:release]
|
165
|
+
if os[:release] =~ /^20\d\d/
|
166
166
|
Upstart.new(inspec, service_ctl)
|
167
167
|
else
|
168
168
|
Systemd.new(inspec, service_ctl)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.54
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dominik Richter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: train
|
@@ -312,7 +312,7 @@ files:
|
|
312
312
|
- docs/resources/aws_cloudtrail_trails.md.erb
|
313
313
|
- docs/resources/aws_cloudwatch_alarm.md.erb
|
314
314
|
- docs/resources/aws_cloudwatch_log_metric_filter.md.erb
|
315
|
-
- docs/resources/aws_config_delivery_channel.md
|
315
|
+
- docs/resources/aws_config_delivery_channel.md.erb
|
316
316
|
- docs/resources/aws_config_recorder.md.erb
|
317
317
|
- docs/resources/aws_ec2_instance.md.erb
|
318
318
|
- docs/resources/aws_iam_access_key.md.erb
|
@@ -475,6 +475,7 @@ files:
|
|
475
475
|
- examples/kitchen-puppet/README.md
|
476
476
|
- examples/kitchen-puppet/manifests/site.pp
|
477
477
|
- examples/kitchen-puppet/metadata.json
|
478
|
+
- examples/kitchen-puppet/modules/.gitkeep
|
478
479
|
- examples/kitchen-puppet/test/integration/default/web_spec.rb
|
479
480
|
- examples/meta-profile/README.md
|
480
481
|
- examples/meta-profile/controls/example.rb
|