AWS & MFA

Amazon Web Service is a key threat for hackers. If hackers have access to your AWS account, they can endure you a tremendous cost. The trend has been to use AWS credentials found online (through an unwanted commit, or other means) to spin up EC2 instances that mine on some kind of blockchain.

One way to mitigate the risk, which Amazon recommends, is to use MFA (Multi Factor Authentication). However, AWS’ recommendation only applies to the Web Console, not programmatic access. If your users have programmatic access (with a key secret pair), it can be harder to enforce requiring MFA.

One way to achieve this is through a policy that is applied to a group. I end up having a group called Human where all employees are a member of. That has a policy that requires actions to have been validated with a MFA. You will find a Terraform configuration at the end of the post.

Now, in order to make any request, CLI or otherwise, the user has to autneticate with MFA.

A little tool comes in handy to help the workflow: mfaws. Configured properly, this will allow the user to get prompted for an MFA with their programmatic access. With that, they will be able to execute commands on AWS for a limited period of time before they need to enter their MFA again.

Finally, if you use a Yubikey that is always plugged in, you can then edit your crontab (crontab -e). Alternatives like 1Password CLI or OAuthTool will also do the trick.

# crontab
*/5 * * * * mfaws --force -t $($(brew --prefix)/bin/ykman oath accounts code 'Amazon Web Services' -s) # ykman
*/5 * * * * mfaws --force -t $(oathtool --totp --base32 SECRETKEY) # oauthtool

This will ensure fresh credentials every 5 minutes. So now all users will be protected by MFA should their credentials be leaked. However their workflow will barely be impacted as they will receive new credentials by the time they sipped their coffee in the morning.

Notes

This does not prevent proper security protocols from still being in place, such as strong password, and rotating the password regularly.

Appendix

Terraform code for requiring MFA for all users

resource "aws_iam_group" "human" {
  name = "Human"
}

resource "aws_iam_group_policy_attachment" "human" {
  group      = aws_iam_group.human.name
  policy_arn = aws_iam_policy.force-mfa.arn
}

resource "aws_iam_policy" "force-mfa" {
  name = "RequireMFA"
  # From https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws_my-sec-creds-self-manage.html
  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Sid" : "AllowViewAccountInfo",
        "Effect" : "Allow",
        "Action" : [
          "iam:GetAccountPasswordPolicy",
          "iam:GetAccountSummary",
          "iam:ListVirtualMFADevices"
        ],
        "Resource" : "*"
      },
      {
        "Sid" : "AllowManageOwnPasswords",
        "Effect" : "Allow",
        "Action" : [
          "iam:ChangePassword",
          "iam:GetUser"
        ],
        "Resource" : "arn:aws:iam::*:user/$${aws:username}"
      },
      {
        "Sid" : "AllowManageOwnAccessKeys",
        "Effect" : "Allow",
        "Action" : [
          "iam:CreateAccessKey",
          "iam:DeleteAccessKey",
          "iam:ListAccessKeys",
          "iam:UpdateAccessKey"
        ],
        "Resource" : "arn:aws:iam::*:user/$${aws:username}"
      },
      {
        "Sid" : "AllowManageOwnSigningCertificates",
        "Effect" : "Allow",
        "Action" : [
          "iam:DeleteSigningCertificate",
          "iam:ListSigningCertificates",
          "iam:UpdateSigningCertificate",
          "iam:UploadSigningCertificate"
        ],
        "Resource" : "arn:aws:iam::*:user/$${aws:username}"
      },
      {
        "Sid" : "AllowManageOwnSSHPublicKeys",
        "Effect" : "Allow",
        "Action" : [
          "iam:DeleteSSHPublicKey",
          "iam:GetSSHPublicKey",
          "iam:ListSSHPublicKeys",
          "iam:UpdateSSHPublicKey",
          "iam:UploadSSHPublicKey"
        ],
        "Resource" : "arn:aws:iam::*:user/$${aws:username}"
      },
      {
        "Sid" : "AllowManageOwnGitCredentials",
        "Effect" : "Allow",
        "Action" : [
          "iam:CreateServiceSpecificCredential",
          "iam:DeleteServiceSpecificCredential",
          "iam:ListServiceSpecificCredentials",
          "iam:ResetServiceSpecificCredential",
          "iam:UpdateServiceSpecificCredential"
        ],
        "Resource" : "arn:aws:iam::*:user/$${aws:username}"
      },
      {
        "Sid" : "AllowManageOwnVirtualMFADevice",
        "Effect" : "Allow",
        "Action" : [
          "iam:CreateVirtualMFADevice",
          "iam:DeleteVirtualMFADevice"
        ],
        "Resource" : "arn:aws:iam::*:mfa/$${aws:username}"
      },
      {
        "Sid" : "AllowManageOwnUserMFA",
        "Effect" : "Allow",
        "Action" : [
          "iam:DeactivateMFADevice",
          "iam:EnableMFADevice",
          "iam:ListMFADevices",
          "iam:ResyncMFADevice"
        ],
        "Resource" : "arn:aws:iam::*:user/$${aws:username}"
      },
      {
        "Sid" : "DenyAllExceptListedIfNoMFA",
        "Effect" : "Deny",
        "NotAction" : [
          "iam:CreateVirtualMFADevice",
          "iam:EnableMFADevice",
          "iam:GetUser",
          "iam:ListMFADevices",
          "iam:ListVirtualMFADevices",
          "iam:ResyncMFADevice",
          "sts:GetSessionToken"
        ],
        "Resource" : "*",
        "Condition" : {
          "BoolIfExists" : {
            "aws:MultiFactorAuthPresent" : "false"
          }
        }
      }
    ]
  })
}

References

https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws_my-sec-creds-self-manage.html