2018-01-23 04:22:37 +00:00
|
|
|
require '_aws'
|
|
|
|
|
2017-08-08 13:50:35 +00:00
|
|
|
# author: Alex Bedley
|
|
|
|
# author: Steffanie Freeman
|
|
|
|
# author: Simon Varlow
|
|
|
|
# author: Chris Redekop
|
|
|
|
class AwsIamUsers < Inspec.resource(1)
|
|
|
|
name 'aws_iam_users'
|
|
|
|
desc 'Verifies settings for AWS IAM users'
|
|
|
|
example '
|
|
|
|
describe aws_iam_users.where(has_mfa_enabled?: false) do
|
|
|
|
it { should_not exist }
|
|
|
|
end
|
|
|
|
describe aws_iam_users.where(has_console_password?: true) do
|
|
|
|
it { should exist }
|
|
|
|
end
|
|
|
|
'
|
|
|
|
|
|
|
|
filter = FilterTable.create
|
|
|
|
filter.add_accessor(:where)
|
|
|
|
.add_accessor(:entries)
|
|
|
|
.add(:exists?) { |x| !x.entries.empty? }
|
2017-12-08 18:34:09 +00:00
|
|
|
.add(:has_mfa_enabled?, field: :has_mfa_enabled)
|
|
|
|
.add(:has_console_password?, field: :has_console_password)
|
2018-02-01 16:23:25 +00:00
|
|
|
.add(:password_ever_used?, field: :password_ever_used?)
|
|
|
|
.add(:password_never_used?, field: :password_never_used?)
|
|
|
|
.add(:password_last_used_days_ago, field: :password_last_used_days_ago)
|
2017-12-08 18:34:09 +00:00
|
|
|
.add(:username, field: :user_name)
|
2017-08-08 13:50:35 +00:00
|
|
|
filter.connect(self, :collect_user_details)
|
|
|
|
|
2017-12-08 18:34:09 +00:00
|
|
|
# No resource params => no overridden constructor
|
|
|
|
# AWS API only offers filtering on path prefix;
|
|
|
|
# little other opportunity for server-side filtering.
|
2017-08-08 13:50:35 +00:00
|
|
|
|
|
|
|
def collect_user_details
|
2017-12-08 18:34:09 +00:00
|
|
|
backend = Backend.create
|
|
|
|
users = backend.list_users.users.map(&:to_h)
|
|
|
|
|
|
|
|
# TODO: lazy columns - https://github.com/chef/inspec-aws/issues/100
|
|
|
|
users.each do |user|
|
|
|
|
begin
|
|
|
|
_login_profile = backend.get_login_profile(user_name: user[:user_name])
|
|
|
|
user[:has_console_password] = true
|
|
|
|
rescue Aws::IAM::Errors::NoSuchEntity
|
|
|
|
user[:has_console_password] = false
|
|
|
|
end
|
|
|
|
user[:has_console_password?] = user[:has_console_password]
|
2017-08-08 13:50:35 +00:00
|
|
|
|
2017-12-08 18:34:09 +00:00
|
|
|
begin
|
|
|
|
aws_mfa_devices = backend.list_mfa_devices(user_name: user[:user_name])
|
|
|
|
user[:has_mfa_enabled] = !aws_mfa_devices.mfa_devices.empty?
|
|
|
|
rescue Aws::IAM::Errors::NoSuchEntity
|
|
|
|
user[:has_mfa_enabled] = false
|
|
|
|
end
|
|
|
|
user[:has_mfa_enabled?] = user[:has_mfa_enabled]
|
2018-02-01 16:23:25 +00:00
|
|
|
password_last_used = user[:password_last_used]
|
|
|
|
user[:password_ever_used?] = !password_last_used.nil?
|
|
|
|
user[:password_never_used?] = password_last_used.nil?
|
|
|
|
next unless user[:password_ever_used?]
|
|
|
|
user[:password_last_used_days_ago] = ((Time.now - password_last_used) / (24*60*60)).to_i
|
2017-12-08 18:34:09 +00:00
|
|
|
end
|
|
|
|
users
|
2017-08-08 13:50:35 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def to_s
|
|
|
|
'IAM Users'
|
|
|
|
end
|
|
|
|
|
2017-12-08 18:34:09 +00:00
|
|
|
# Entry cooker. Needs discussion.
|
|
|
|
# def users
|
|
|
|
# end
|
|
|
|
|
|
|
|
#===========================================================================#
|
|
|
|
# Backend Implementation
|
|
|
|
#===========================================================================#
|
|
|
|
class Backend
|
|
|
|
#=====================================================#
|
|
|
|
# Concrete Implementation
|
|
|
|
#=====================================================#
|
|
|
|
# Uses AWS API to really talk to AWS
|
|
|
|
class AwsClientApi < Backend
|
|
|
|
# TODO: delegate this out
|
|
|
|
def list_users(query = {})
|
|
|
|
AWSConnection.new.iam_client.list_users(query)
|
|
|
|
end
|
|
|
|
|
|
|
|
def get_login_profile(query)
|
|
|
|
AWSConnection.new.iam_client.get_login_profile(query)
|
|
|
|
end
|
|
|
|
|
|
|
|
def list_mfa_devices(query)
|
|
|
|
AWSConnection.new.iam_client.list_mfa_devices(query)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
#=====================================================#
|
|
|
|
# Factory Interface
|
|
|
|
#=====================================================#
|
|
|
|
# TODO: move this to a mix-in
|
|
|
|
DEFAULT_BACKEND = AwsClientApi
|
|
|
|
@selected_backend = DEFAULT_BACKEND
|
|
|
|
|
|
|
|
def self.create
|
|
|
|
@selected_backend.new
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.select(klass)
|
|
|
|
@selected_backend = klass
|
2017-08-08 13:50:35 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|