inspec 2.1.43 → 2.1.54

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- catch_aws_errors do
43
- @resp = backend.describe_delivery_channels(query)
44
- end
45
- @exists = !@resp.empty?
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
- @channel = @resp.delivery_channels.first.to_h
49
- @channel_name = @channel[:name]
50
- @s3_bucket_name = @channel[:s3_bucket_name]
51
- @s3_key_prefix = @channel[:s3_key_prefix]
52
- @sns_topic_arn = @channel[:sns_topic_arn]
53
- @delivery_frequency_in_hours = @channel[:config_snapshot_delivery_properties][:delivery_frequency] unless @channel[:config_snapshot_delivery_properties].nil?
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, :resp
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
- @resp = backend.describe_configuration_recorder_status(@query)
34
- @status = @resp.configuration_recorders_status.first.to_h
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
- @query = { configuration_recorder_names: [@recorder_name] }
58
+ query = @recorder_name ? { configuration_recorder_names: [@recorder_name] } : {}
59
+ response = backend.describe_configuration_recorders(query)
64
60
 
65
- catch_aws_errors do
66
- begin
67
- @resp = backend.describe_configuration_recorders(@query)
68
- rescue Aws::ConfigService::Errors::NoSuchConfigurationRecorderException
69
- @exists = false
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
- policy['Statement'].count
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
- values = statement[crit_name] # This is an array due to normalize_statements
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
@@ -11,6 +11,10 @@ class AwsIamRole < Inspec.resource(1)
11
11
  include AwsSingularResourceMixin
12
12
  attr_reader :description, :role_name
13
13
 
14
+ def to_s
15
+ "IAM Role #{role_name}"
16
+ end
17
+
14
18
  private
15
19
 
16
20
  def validate_params(raw_params)
@@ -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, :has_console_password, :has_mfa_enabled, :username
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(:username, field: :user_name)
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
@@ -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].start_with?('20\d\d')
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.43
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-12 00:00:00.000000000 Z
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