Enforce MFA for IAM users

aws May 2, 2021

It's not easy to protect AWS accounts and one of the most common issues is related to credentials leakage that gives access even to all company's resources. Multi-Factor Authentication (MFA) is one way to drastically mitigate most of those attacks.

Why use MFA?

Do you need some reference for how important it is? Check it out:

Time and time again we see user passwords treated with minimal to no security. They are kept in plaintext, reused again and again by employees, and left to fend for themselves in the form of single-factor authentication. This practice has resulted in billions of dollars stolen and enormous data breaches from which it takes organizations months, sometimes years, to recover. Or even worse, threat actors sell your legitimate credentials over and over, meaning your organization never has time to recover and is constantly on the defense. Sound familiar? If we know the problem, we can begin to work toward the solution.
- SANS white paper Bye Bye Passwords: New Ways to Authenticate
Source: Microsoft Blog - One simple action you can take to prevent 99.9 percent of attacks on your accounts

AWS Policy to block any action without MFA

If you just want to copy and paste the JSON, here it is. But if you want to understand each statement, check the content below the JSON code.

This Policy will:

  • If MFA is disabled
    • Allow user to add MFA
    • Allow user to change own password
    • Block user to add MFA to someone else account
  • If MFA is enabled
    • Allow user to resync MFA
    • Allow user to change own password
    • Block user to disable own MFA

IMPORTANT!
Will may need to logout and relogin after apply this policy to your own user.
This happen cause some user state, like the MFA been activated, are related to the session.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "BlockMostAccessUnlessSignedInWithMFA",
            "Effect": "Deny",
            "NotAction": [
                "iam:CreateVirtualMFADevice",
                "iam:ListVirtualMFADevices",
                "iam:EnableMFADevice",
                "iam:ListAccountAliases",
                "iam:ListUsers",
                "iam:ListSSHPublicKeys",
                "iam:ListAccessKeys",
                "iam:ListServiceSpecificCredentials",
                "iam:ListMFADevices",
                "iam:GetAccountSummary",
                "iam:ChangePassword",
                "sts:GetSessionToken"
            ],
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        },
        {
            "Sid": "RestrictChangeOwnPasswordAndEnableMFAWithoutMFA",
            "Effect": "Allow",
            "Action": [
                "iam:ChangePassword",
                "iam:EnableMFADevice"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}",
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        },
        {
            "Sid": "AllowNavigateOnIAMAndGetTokenMFA",
            "Effect": "Allow",
            "Action": [
                "iam:CreateVirtualMFADevice",
                "iam:ListVirtualMFADevices",
                "iam:ListAccountAliases",
                "iam:ListUsers",
                "iam:ListSSHPublicKeys",
                "iam:ListAccessKeys",
                "iam:ListServiceSpecificCredentials",
                "iam:ListMFADevices",
                "iam:GetAccountSummary",
                "sts:GetSessionToken"
            ],
            "Resource": "*",
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        },
        {
            "Sid": "RestrictToAllowChangeOwnPasswordAndResyncMFA",
            "Effect": "Allow",
            "Action": [
                "iam:ChangePassword",
                "iam:EnableMFADevice",
                "iam:ResyncMFADevice"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}",
            "Condition": {
                "Bool": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        }
    ]
}
enforce-mfa-policy.json

Why so many statement blocks?

The logic behind this policy is simple:

  • If MFA is disabled
    • Allow every Action that we need, nothing more
    • Restrict some Actions to a specific condition
  • If MFA is enabled
    • Allow Actions that don't need to have restriction
    • Allow some Actions, but restrict it to a specific condition

That's almost the same approach if MFA is enabled or disabled, as you can see above. So let's explain each Statement block:

  • BlockMostAccessUnlessSignedInWithMFA: Make sure to block every Action but the ones listed in NotAction. But we have a problem, this Statement allows a user without MFA to add MFA or change the password of any other user, even an Administrator. So we need to restrict these actions.
  • RestrictChangeOwnPasswordAndEnableMFAWithoutMFA: This Statement fixes the problem. This will make sure the User will be able to change their own password and enable their own MFA.
  • AllowNavigateOnIAMAndGetTokenMFA: Allow users to navigate to the IAM console page and get the authorization token. This is the minimum requirement of non-restricted Actions for the next Statement.
  • RestrictToAllowChangeOwnPasswordAndResyncMFA: Now the user can navigate in the IAM console, so this Statement It will allow the user to change their own password and resync the MFA, but with a restriction to prevent changing someone else's password.

That's it :)

If you have some questions, leave me a comment below. Bye.

References

Tags

Luiz Costa

I am a senior software engineer at Red Hat / Ansible. I love automation tools, games, and coffee. I am also an active contributor to open-source projects on GitHub.