From 033bc13aa00c2359289b8452afdb400929a62539 Mon Sep 17 00:00:00 2001 From: Chris Redekop Date: Tue, 8 Aug 2017 09:50:35 -0400 Subject: [PATCH] A real-world working AwsIamUsers (#71) * Add aws_iam_users Signed-off-by: Chris Redekop * Adding Filter table and Collect User Details to aws_iam_users.rb Signed-off-by: Chris Redekop * Adding Filter table and Collect User Details to aws_iam_users.rb Signed-off-by: Chris Redekop * Adding Filter table and Collect User Details to aws_iam_users.rb Signed-off-by: Chris Redekop * Get an aws_iam_users integration test to pass Signed-off-by: Chris Redekop * Fix RuboCop issues and tests Signed-off-by: Chris Redekop * Improving code based on PR feedback Signed-off-by: Chris Redekop --- libraries/aws_iam_user.rb | 20 +++-- libraries/aws_iam_user_provider.rb | 5 ++ libraries/aws_iam_users.rb | 51 ++++++++++++ .../verify/controls/aws_iam_user.rb | 6 +- .../verify/controls/aws_iam_users.rb | 3 + .../resources/aws_iam_user_provider_test.rb | 8 +- test/unit/resources/aws_iam_user_test.rb | 40 +++++++-- test/unit/resources/aws_iam_users_test.rb | 81 +++++++++++++++++++ 8 files changed, 193 insertions(+), 21 deletions(-) create mode 100644 libraries/aws_iam_users.rb create mode 100644 test/integration/verify/controls/aws_iam_users.rb create mode 100644 test/unit/resources/aws_iam_users_test.rb diff --git a/libraries/aws_iam_user.rb b/libraries/aws_iam_user.rb index d2af69470..8f90ef1ba 100644 --- a/libraries/aws_iam_user.rb +++ b/libraries/aws_iam_user.rb @@ -7,16 +7,18 @@ class AwsIamUser < Inspec.resource(1) name 'aws_iam_user' desc 'Verifies settings for AWS IAM user' example " - describe aws_iam_user('test_user_name') do + describe aws_iam_user(name: 'test_user_name') do its('has_mfa_enabled?') { should be false } its('has_console_password?') { should be true } end " - def initialize(name, aws_user_provider = AwsIam::UserProvider.new, - access_key_factory = AwsIamAccessKeyFactory.new) - - @name = name - @user = aws_user_provider.user(name) + def initialize( + opts, + aws_user_provider = AwsIam::UserProvider.new, + access_key_factory = AwsIamAccessKeyFactory.new + ) + @user = opts[:user] + @user = aws_user_provider.user(opts[:name]) if @user.nil? @access_key_factory = access_key_factory end @@ -34,8 +36,12 @@ class AwsIamUser < Inspec.resource(1) } end + def name + @user[:name] + end + def to_s - "IAM User #{@name}" + "IAM User #{name}" end class AwsIamAccessKeyFactory diff --git a/libraries/aws_iam_user_provider.rb b/libraries/aws_iam_user_provider.rb index 6139851be..7e764d3dd 100644 --- a/libraries/aws_iam_user_provider.rb +++ b/libraries/aws_iam_user_provider.rb @@ -20,6 +20,10 @@ module AwsIam end class << self + def name(aws_user) + aws_user.name + end + def has_mfa_enabled?(aws_user) !aws_user.mfa_devices.first.nil? end @@ -36,6 +40,7 @@ module AwsIam def convert(aws_user) { + name: name(aws_user), has_mfa_enabled?: has_mfa_enabled?(aws_user), has_console_password?: has_console_password?(aws_user), access_keys: access_keys(aws_user), diff --git a/libraries/aws_iam_users.rb b/libraries/aws_iam_users.rb new file mode 100644 index 000000000..9343438eb --- /dev/null +++ b/libraries/aws_iam_users.rb @@ -0,0 +1,51 @@ +# 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? } + filter.connect(self, :collect_user_details) + + def initialize(aws_user_provider = AwsIam::UserProvider.new, + user_factory = AwsIamUserFactory.new) + @user_provider = aws_user_provider + @user_factory = user_factory + end + + def collect_user_details + @users_cache ||= @user_provider.list_users unless @user_provider.nil? + end + + def users + users = [] + users ||= @user_provider.list_users unless @user_provider.nil? + users.map { |user| + @user_factory.create_user(user) + } + end + + def to_s + 'IAM Users' + end + + class AwsIamUserFactory + def create_user(user) + AwsIamUser.new(user: user) + end + end +end diff --git a/test/integration/verify/controls/aws_iam_user.rb b/test/integration/verify/controls/aws_iam_user.rb index bcc10a8f6..10a931c8b 100644 --- a/test/integration/verify/controls/aws_iam_user.rb +++ b/test/integration/verify/controls/aws_iam_user.rb @@ -13,16 +13,16 @@ access_key_user = attribute( default: 'default.access_key_user', description: 'Name of IAM user access_key_user') -describe aws_iam_user(mfa_not_enabled_user) do +describe aws_iam_user(name: mfa_not_enabled_user) do it { should_not have_mfa_enabled } it { should_not have_console_password } end -describe aws_iam_user(console_password_enabled_user) do +describe aws_iam_user(name: console_password_enabled_user) do it { should have_console_password } end -aws_iam_user(access_key_user).access_keys.each { |access_key| +aws_iam_user(name: access_key_user).access_keys.each { |access_key| describe access_key do it { should be_active } end diff --git a/test/integration/verify/controls/aws_iam_users.rb b/test/integration/verify/controls/aws_iam_users.rb new file mode 100644 index 000000000..75c7487f0 --- /dev/null +++ b/test/integration/verify/controls/aws_iam_users.rb @@ -0,0 +1,3 @@ +describe aws_iam_users.where(has_console_password?: true).where(has_mfa_enabled?: false) do + it { should exist } +end diff --git a/test/unit/resources/aws_iam_user_provider_test.rb b/test/unit/resources/aws_iam_user_provider_test.rb index fff9d7de1..9cc8afb44 100644 --- a/test/unit/resources/aws_iam_user_provider_test.rb +++ b/test/unit/resources/aws_iam_user_provider_test.rb @@ -24,15 +24,15 @@ class AwsIamUserProviderTest < Minitest::Test def test_list_users @mock_iam_resource.expect( :users, - [create_mock_user, create_mock_user, create_mock_user], + [create_mock_user, create_mock_user], ) mock_user_output = { + name: Username, has_mfa_enabled?: true, has_console_password?: true, access_keys: [], } - assert @user_provider.list_users == [mock_user_output, mock_user_output, - mock_user_output] + assert @user_provider.list_users == [mock_user_output, mock_user_output] end def test_list_users_no_users @@ -107,6 +107,7 @@ class AwsIamUserProviderTest < Minitest::Test mock_login_profile.expect :create_date, has_console_password ? 'date' : nil mock_user = Minitest::Mock.new + mock_user.expect :name, Username mock_user.expect :mfa_devices, has_mfa_enabled ? ['device'] : [] mock_user.expect :login_profile, mock_login_profile mock_user.expect :access_keys, access_keys @@ -119,6 +120,7 @@ class AwsIamUserProviderTest < Minitest::Test end mock_user = Minitest::Mock.new + mock_user.expect :name, Username mock_user.expect :mfa_devices, [] mock_user.expect :login_profile, mock_login_profile mock_user.expect :access_keys, [] diff --git a/test/unit/resources/aws_iam_user_test.rb b/test/unit/resources/aws_iam_user_test.rb index f39b87365..e9b1919a7 100644 --- a/test/unit/resources/aws_iam_user_test.rb +++ b/test/unit/resources/aws_iam_user_test.rb @@ -16,7 +16,12 @@ class AwsIamUserTest < Minitest::Test { has_mfa_enabled?: true }, [Username], ) - assert AwsIamUser.new(Username, @mock_user_provider).has_mfa_enabled? + assert( + AwsIamUser.new( + { name: Username }, + @mock_user_provider, + ).has_mfa_enabled?, + ) end def test_mfa_enabled_returns_false_if_mfa_is_not_enabled @@ -25,7 +30,12 @@ class AwsIamUserTest < Minitest::Test { has_mfa_enabled?: false }, [Username], ) - refute AwsIamUser.new(Username, @mock_user_provider).has_mfa_enabled? + refute( + AwsIamUser.new( + { name: Username }, + @mock_user_provider, + ).has_mfa_enabled?, + ) end def test_console_password_returns_true_if_console_password_has_been_set @@ -34,7 +44,12 @@ class AwsIamUserTest < Minitest::Test { has_console_password?: true }, [Username], ) - assert AwsIamUser.new(Username, @mock_user_provider).has_console_password? + assert( + AwsIamUser.new( + { name: Username }, + @mock_user_provider, + ).has_console_password?, + ) end def test_console_password_returns_false_if_console_password_has_not_been_set @@ -43,7 +58,12 @@ class AwsIamUserTest < Minitest::Test { has_console_password?: false }, [Username], ) - refute AwsIamUser.new(Username, @mock_user_provider).has_console_password? + refute( + AwsIamUser.new( + { name: Username }, + @mock_user_provider, + ).has_console_password?, + ) end def test_that_access_keys_returns_aws_iam_access_key_resources @@ -65,7 +85,7 @@ class AwsIamUserTest < Minitest::Test assert_equal( stub_access_key_resource, AwsIamUser.new( - Username, + { name: Username }, @mock_user_provider, mock_access_key_factory, ).access_keys[0], @@ -75,9 +95,13 @@ class AwsIamUserTest < Minitest::Test end def test_to_s - @mock_user_provider.expect :user, { has_mfa_enabled?: true }, [Username] - expected = 'IAM User test' - test = AwsIamUser.new(Username, @mock_user_provider).to_s + @mock_user_provider.expect( + :user, + { name: Username, has_mfa_enabled?: true }, + [Username], + ) + expected = "IAM User #{Username}" + test = AwsIamUser.new({ name: Username }, @mock_user_provider).to_s assert_equal expected, test end end diff --git a/test/unit/resources/aws_iam_users_test.rb b/test/unit/resources/aws_iam_users_test.rb new file mode 100644 index 000000000..903b43b19 --- /dev/null +++ b/test/unit/resources/aws_iam_users_test.rb @@ -0,0 +1,81 @@ +# author: Adnan Duric +# author: Steffanie Freeman +# author: Simon Varlow +# author: Chris Redekop +require 'aws-sdk' +require 'helper' +require 'aws_iam_users' + +class AwsIamUsersTest < Minitest::Test + def setup + @mock_user_factory = Minitest::Mock.new + end + + def test_users_nil_user_provider_returns_empty_list + cut = AwsIamUsers.new(nil, @mock_user_factory) + + assert_equal(cut.users, []) + end + + def test_users_empty_list_user_provider_returns_empty_list + cut = AwsIamUsers.new(create_mock_user_provider, @mock_user_factory) + + assert_equal(cut.users, []) + end + + def test_users_returns_true_for_all_users_if_mfa_enabled + cut = AwsIamUsers.new( + create_mock_user_provider(create_mock_users([true, true])), + @mock_user_factory, + ) + + cut.users.each do |user| + assert user.has_mfa_enabled? + end + end + + [ + { + name: 'test_where_returns_no_matching_rows', + user_material: [false], + }, { + name: 'test_where_returns_some_matching_rows', + user_material: [true, false], + }, { + name: 'test_where_returns_all_matching_rows', + user_material: [true], + } + ].each do |test_material| + define_method(test_material[:name]) do + cut = AwsIamUsers.new( + create_mock_user_provider( + create_mock_users(test_material[:user_material]), + ), + @mock_user_factory, + ) + + results = cut.where(has_mfa_enabled?: true) + expected_count = test_material[:user_material].count { |x| x } + + assert_equal expected_count > 0, results.exists? + assert_equal expected_count, results.entries.length + end + end + + def create_mock_user_provider(user_list = []) + mock_user_provider = Minitest::Mock.new + + mock_user_provider.expect :list_users, user_list + mock_user_provider.expect :nil?, false + + mock_user_provider + end + + def create_mock_users(has_mfa_enableds = []) + has_mfa_enableds.map { |x| create_mock_user(x) } + end + + def create_mock_user(has_mfa_enabled = true) + { has_mfa_enabled?: has_mfa_enabled } + end +end