#1 Are you running your credential report?
We recommend generating your credential report every four hours and saving the content to S3. This can be accomplished with a scheduled lambda function or through automated governance software like Turbot Cloud.
To generate from the CLI:
$ aws iam generate-credential-report{"State": "STARTED","Description": "No report exists. Starting a new report generation task"}$ aws iam generate-credential-report{"State": "COMPLETE"}
Now that it's generated, we can do a quick query using steampipe to get the contents:
select * from aws_iam_credential_report;
+----------------+-----------------------------------------------+---------------------+---------------------+---------------------+---------------------------+-----------------------------+-------------------------------+--------------------------------+---------------------+---------------------------+-----------------------------+ | user_name | user_arn | user_creation_time | generated_time | access_key_1_active | access_key_1_last_rotated | access_key_1_last_used_date | access_key_1_last_used_region | access_key_1_last_used_service | access_key_2_active | access_key_2_last_rotated | access_key_2_last_used_date | +----------------+-----------------------------------------------+---------------------+---------------------+---------------------+---------------------------+-----------------------------+-------------------------------+--------------------------------+---------------------+---------------------------+-----------------------------+ | <root_account> | arn:aws:iam::899221540641:root | 2019-07-15 14:44:33 | 2021-02-09 08:42:56 | false | | | | | false | | | | pam_beasly | arn:aws:iam::899221540641:user/pam_beasly | 2019-11-13 18:32:34 | 2021-02-09 08:42:56 | true | 2020-06-18 19:12:27 | 2021-02-09 00:36:00 | us-east-1 | iam | true | 2020-12-03 01:20:25 | 2020-12-03 01:33:00 | | darryl_philbin | arn:aws:iam::899221540641:user/darryl_philbin | 2021-01-25 19:12:26 | 2021-02-09 08:42:56 | true | 2021-01-25 19:12:26 | | | | false | | | | dwight_schrute | arn:aws:iam::899221540641:user/dwight_schrute | 2021-01-25 19:10:51 | 2021-02-09 08:42:56 | false | | | | | false | | | | kelly_kapoor | arn:aws:iam::899221540641:user/kelly_kapoor | 2021-01-25 19:13:23 | 2021-02-09 08:42:56 | false | | | | | false | | | | ryan_howard | arn:aws:iam::899221540641:user/ryan_howard | 2021-01-25 19:26:22 | 2021-02-09 08:42:56 | false | | | | | false | | | +----------------+-----------------------------------------------+---------------------+---------------------+---------------------+---------------------------+-----------------------------+-------------------------------+--------------------------------+---------------------+---------------------------+-----------------------------
That is a very wide table, let's break it down to make it more manageable and check some specific key controls.
Root Account Checks
Every AWS Account has a root
user with the name <root_account>
. This should be considered a "break glass" user and therefore rarely be used; however, there are a few troubleshooting situations that require use of the Root Account. Any usage of the root account should always be done under change control with multiple people coordinating to gain access to the account (e.g., Security pulls the MFA and operations retrieves the root account password from your vault).
#2 Has the root user been accessed recently?
In my example, I know that there haven't been any tickets that required usage of the root account in the last 90 days, so my credential report should reflect that the account hasn't been accessed in the same time period.
select user_name, password_last_used, age(date(current_timestamp), date(password_last_used)) as pw_last_usedfrom aws_iam_credential_reportwhere user_name = '<root_account>';
+----------------+---------------------+-----------------------+ | user_name | password_last_used | pw_last_used | +----------------+---------------------+-----------------------+ | <root_account> | 2019-07-17 04:49:39 | 1 year 6 mons 23 days | +----------------+---------------------+-----------------------+
#3 Does the root account have multi-factor authentication (MFA) enabled?
Seems self-explanatory that the Root Account should have a MFA device associated with it.
select user_name, mfa_activefrom aws_iam_credential_reportwhere user_name = '<root_account>';
+----------------+------------+ | user_name | mfa_active | +----------------+------------+ | <root_account> | true | +----------------+------------+
#4 Does the root account have access keys enabled?
Root accounts should never be used for programmatic access, so no access keys should exist.
select user_name, access_key_1_active, access_key_2_activefrom aws_iam_credential_reportwhere user_name = '<root_account>';
+----------------+---------------------+---------------------+ | user_name | access_key_1_active | access_key_2_active | +----------------+---------------------+---------------------+ | <root_account> | false | false | +----------------+---------------------+---------------------+
IAM User Account Checks
If you allow IAM users to be created in your account (many organizations do not), there are also a number of best practices checks to do for those users.
#5 Are there any users with access keys and console credentials?
Users should either be console users or API users, not both; check for users with both console credentials and programmatic credentials (access keys).
select user_name, password_enabled, access_key_1_active, access_key_2_activefrom aws_iam_credential_reportwhere password_enabled and ( access_key_1_active or access_key_2_active );
+----------------+------------------+---------------------+---------------------+ | user_name | password_enabled | access_key_1_active | access_key_2_active | +----------------+------------------+---------------------+---------------------+ | dwight_schrute | true | true | false | +----------------+------------------+---------------------+---------------------+
#6 Check for inactive users and users that have never logged in.
Find and delete any accounts where the users have passwords but have never logged in and accounts that haven't been used in last 90 days.
select user_name, password_enabled, password_last_used, age(date(current_timestamp), date(password_last_used)) as last_used_agefrom aws_iam_credential_reportwhere user_name != '<root_account>' and password_enabled and ( password_last_used is null or (date(current_timestamp) - date(password_last_used)) > 90 );
+----------------+------------------+---------------------+-----------------------+ | user_name | password_enabled | password_last_used | pw_last_used | +----------------+------------------+---------------------+-----------------------+ | dwight_schrute | true | | | | kelly_kapoor | true | | | | ryan_howard | true | | | | pam_beesly | true | 2019-07-17 04:49:39 | 1 year 6 mons 23 days | +----------------+------------------+---------------------+-----------------------+
#7 Find any users with passwords that have not been rotated.
For users with password, they should be rotated based on company policy, in this case 90 days.
select user_name, password_last_changed, age(date(current_timestamp), date(password_last_changed)) as pw_last_changedfrom aws_iam_credential_reportwhere user_name != '<root_account>' and password_enabled and (date(current_timestamp) - date(password_last_changed)) > 90;
+----------------+-----------------------+-------------------+ | user_name | password_last_changed | pw_last_changed | +----------------+-----------------------+-------------------+ | ryan_howard | 2020-10-25 19:26:22 | 3 months, 15 days | | kelly_kapoor | 2020-09-22 19:13:23 | 4 months, 18 days | | dwight_schrute | 2020-01-12 19:10:51 | 1 year, 28 days | +----------------+-----------------------+-------------------+
#8 Is MFA enabled for all users?
Identify users that are not using multi-factor authentication (MFA).
select user_name, mfa_activefrom aws_iam_credential_reportwhere not mfa_active;
+----------------+------------+ | user_name | mfa_active | +----------------+------------+ | darryl_philbin | false | | ryan_howard | false | | dwight_schrute | false | | kelly_kapoor | false | +----------------+------------+
#9 Are access keys being rotated?
For users with access keys, they should be rotated based on company policy, in this case 90 days.
select user_name, access_key_1_active, age( date(current_timestamp), date(access_key_1_last_rotated) ) as key1_last_changed, access_key_2_active, age( date(current_timestamp), date(access_key_2_last_rotated) ) as key2_last_changedfrom aws_iam_credential_reportwhere ( access_key_1_active and ( date(current_timestamp) - date(access_key_1_last_rotated) ) > 90 ) or ( access_key_2_active and ( date(current_timestamp) - date(access_key_2_last_rotated) ) > 90 );
+------------+---------------------+-------------------+---------------------+-------------------+ | user_name | access_key_1_active | key1_last_changed | access_key_2_active | key2_last_changed | +------------+---------------------+-------------------+---------------------+-------------------+ | pam_beesly | true | 7 mons 21 days | true | 2 mons 6 days | +------------+---------------------+-------------------+---------------------+-------------------+
#10 Can you find any users with unused access keys?
For users with access keys, they should be in active use. Let's find any users with access keys that have not been used in the last 30 days or that have never been used.
select user_name, access_key_1_last_used_date, age( date(current_timestamp), date(access_key_1_last_used_date) ) as key1_last_used, access_key_2_last_used_date, age( date(current_timestamp), date(access_key_2_last_used_date) ) as key2_last_usedfrom aws_iam_credential_reportwhere ( access_key_1_active and ( access_key_1_last_used_date is null or ( date(current_timestamp) - date(access_key_1_last_used_date) ) > 30 ) ) or ( access_key_2_active and ( access_key_2_last_used_date is null or( date(current_timestamp) - date(access_key_2_last_used_date) ) > 30 ) );
+----------------+----------------------+----------------+----------------------+----------------+ | user_name | key_1_last_used_date | key1_last_used | key_2_last_used_date | key2_last_used | +----------------+----------------------+----------------+----------------------+----------------+ | darryl_philbin | | | | | | pam_beesly | 2021-02-08 12:22:00 | 1 days | 2020-12-03 01:33:00 | 2 mons 6 days | +----------------+----------------------+----------------+----------------------+----------------+
In this case Darryl has an access key, but has never used it, Pam on the other hand has two access keys, one in active use and another that hasn't been used in 66 days.
Conclusion
SQL makes it easy to inspect your AWS IAM Credential Report and to run routine checks for common IAM user compliance issues; best of all Steampipe is 100% open source and available for download today. Install, query and get cloud work done with Steampipe.
Subscribe to our newsfeed https://news.steampipe.io for the latest supported resource types from both Turbot and the Steampipe community, and for your own personal guided tour of steampipe, checkout our documentation: https://steampipe.io/docs.
If you experience any issues, please report them on our GitHub issue tracker or join our Slack workspace.