AWS Setup: Secure Identity Foundation with Terraform

AWS Setup: Secure Identity Foundation with Terraform

When it comes to access management in AWS, often I see a basic setup, with Users in IAM, as described here. Clearly, most people focus on building actual running applications, at first. After the first running POCs, the next migrations are on the road map; your architecture evolves, but the initial IAM setup stays. So it’s better to have a super secure set-up right from the beginning.

Hopefully, you have followed the AWS best practice guide to secure the root user (i.e. the credentials that consist of an e-mail address and a password), switched to one IAM user per individual and informed everyone they currently have admin rights within AWS. Most of our customers even set up IAM groups to bundle all the Admin users (in most cases, all AWS IAM users, except technical users) and tell people to require a multi factor authentication (MFA) set-up.

login-workflow not available

Unfortunately, this only protects the AWS console access (i.e. access through the web UI). For signing API calls (e.g. through the aws-cli) one uses an access key and a secret access key. Writing out these keys inside a test script and pushing it to git with some commit is common at the beginning of one’s cloud journey. Then someone gets unauthorized access to git and uses your credit card to set up a Minecraft server (or even worse). You are not the first, and will not be the last, to unintentionally share access keys.

credentials not available

So, to protect your access keys, one should switch to a role based model: using IAM roles instead of IAM users. The rough idea is to use Users solely as an Identity Provider (or your own AD) and allow users just to assume roles. You can add conditions for assuming roles (i.e. getting temporary credentials from STS). Let’s take a befitting example for a use-case that requires MFA.

To enforce MFA for all API calls, revoke the admin rights of IAM user:

  1. Set up different IAM groups with restricted permissions. For example, create a default IAM group, that permits everyone attached, to change their own password and enable MFA for their own IAM user. Nothing else!
  2. Create two IAM roles, one with read only permissions and one with full admin permissions, which are allowed to be assumed by users in their own AWS account.
  3. Add a condition for assuming the roles, only if MFA is present.
  4. Create two IAM groups with a simple policy attached, that only allows sts:assumeRole on the respective IAM role (plus a condition to double enforce MFA usage).
  5. Attach the designated Users to the respective groups and revoke admin rights from these users (and your own).

From now on, the only chance of seeing and changing something is via MFA.

Terraform: Exploring the code

To get this running, basic knowlegde of Terraform is required, as well as an installed AWS CLI. Download the full Terraform example with running code here.

Let me guide you through the code, a bit. At first, we define some variables for naming and tagging. Change the name that is used for prefixing AWS resource names if more than one state should be rolled out in a single AWS account.

# Variables

variable "name" {
  description = "Name for prefixes (including tailing dash)"
  type        = string
  default     = "initial"
}

variable "tags-to-value" {
  type = map
  default = {
    contact     = "felix@tecracer.de"
    ttl         = "infinity"
    deployed-by = "terraform"
  }
  description = "Map of mandatory tags"
}

As described above, there will be three IAM groups with corresponding IAM policies and policy attachments with a mix of AWS and self managed IAM policies.

# IAM

resource "aws_iam_group" "default-user-group" {
  name = format("%sdefault-users", var.name)
}

resource "aws_iam_group" "readonly-user-group" {
  name = format("%sreadonly-users", var.name)
}

## Read Only group

data "aws_iam_policy" "readonly-access-managed-policy" {
  arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

resource "aws_iam_group_policy_attachment" "readonly-attach" {
  group      = aws_iam_group.readonly-user-group.name
  policy_arn = data.aws_iam_policy.readonly-access-managed-policy.arn
  depends_on = [
    aws_iam_group.readonly-user-group,
    data.aws_iam_policy.readonly-access-managed-policy,
  ]
}

## Admin group

resource "aws_iam_group" "mfa-only-admin-group" {
  name = format("%smfa-only-admins", var.name)
}

Last, but not least, the Admin role, that requires MFA to be assumed, will be created as follows:

resource "aws_iam_role" "admin-role" {
  name_prefix          = substr(format("%smfa-only-admins-role", var.name), 0, 31)
  assume_role_policy   = data.aws_iam_policy_document.admin-assume-role-document.json
  max_session_duration = 43200 # 43200 seconds is the supported maximum, i.e. 12 hours
}

resource "aws_iam_policy" "assume-role-mfa-policy" {
  depends_on  = [aws_iam_group.mfa-only-admin-group]
  name_prefix = substr(format("%sassume-admin-role-with-mfa-policy2", var.name), 0, 31)
  path        = "/"
  description = format("Assume admin role with MFA only policy for %s", var.name)

  policy = <<EOP
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "${aws_iam_role.admin-role.arn}",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "true"
                }
            }
        }
    ]
}
EOP

}

Learn how to configure named profiles for the AWS CLI here.

Use awsume or aws-mfa for integration with 3rd party CLI/SDK tools.

Title Photo by Markus Winkler on Unsplash

Similar Posts You Might Enjoy

Advanced Credential Rotation for IAM Users with a Grace Period

Rotating credentials with grace can be challenging when the underlying service doesn’t support scheduled deletion. Today I will show you how to implement access key rotation for an IAM user while supporting a grace period where both the new and old credentials are valid. - by Maurice Borgmeier

Bridging the terraform - CloudFormation gap

CloudFormation does not cover all AWS Resource types. Terraform does a better job in covering resource types just in time. So if you want to use a resource type which CloudFormation does not support yet, but you want to use CloudFormation, you have to build a Custom Resource with an own Lambda Function. CDK to the rescue: use AwsCustomResource. - by Gernot Glawe

Rotate your credentials and don't forget MFA

According to the Well-Architected Framework and the least privileges principle, you should change your access keys and login password regularly. Therefore the user should have the right to edit their credentials. But only their own. Also using MFA - multi-factor authentication enhances the security even more. Therefore the user should be able to change MFA. But only their own. But how to do that? You have to combine two parts of AWS documentation. We will show you how you provide a “self-editing” group for your users with the CDK. - by Gernot Glawe