Implementing AWS IAM Permissions with Terraform: A Practical Guide

Implementing AWS IAM Permissions with Terraform: A Practical Guide

In the fast-paced world of tech startups, securing cloud infrastructure is critical. In this post, I'll walk you through a practical example of building AWS IAM permissions using Terraform, where we structured user access for different groups—Developers, Operations, Finance, and Analysts—in an example startup scenario.


Background:

Imagine you're a cloud engineer at a startup that just launched its first fitness-tracking application. AWS was set up quickly to meet launch deadlines, and now it's time to address technical debt that’s accrued. Each team needs specific permissions, such as managing EC2, accessing CloudWatch logs, or viewing cost reports.

We’ll use Terraform, an Infrastructure-as-Code tool, to automate the process and ensure repeatability.


Step 1: Enforce Strong Security Policies

1. Password Policy: To ensure all users adhere to strong password requirements, we defined an account-wide password policy:

resource "aws_iam_account_password_policy" "strong_password_policy" {
  minimum_password_length        = 12
  require_symbols                = true
  require_numbers                = true
  require_uppercase_characters   = true
  require_lowercase_characters   = true
  allow_users_to_change_password = true
  max_password_age               = 90
  password_reuse_prevention      = 5
}

This policy enforces security while providing flexibility for users to manage their credentials.

2. MFA Enforcement: To add an extra layer of protection, we enforced MFA for all users:

resource "aws_iam_policy" "mfa_enforcement" {
  name = "MFAEnforcementPolicy"
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect    = "Deny",
        Action    = "*",
        Resource  = "*",
        Condition = {
          Bool: {
            "aws:MultiFactorAuthPresent": "false"
          }
        }
      }
    ]
  })
}

We attached this policy to all IAM groups to ensure compliance.


Step 2: Role-Based Access with Terraform

Developers: Developers need access to manage EC2 instances, S3 buckets, and CloudWatch logs. Here's how we implemented it:

resource "aws_iam_policy" "developers_policy" {
  name = "DevelopersPolicy"
  policy = jsonencode({
    Statement = [
      {
        Effect   = "Allow",
        Action   = ["ec2:DescribeInstances", "ec2:StartInstances", "ec2:StopInstances"],
        Resource = "*"
      },
      {
        Effect   = "Allow",
        Action   = ["s3:ListBucket", "s3:GetObject"],
        Resource = ["arn:aws:s3:::your-bucket-name", "arn:aws:s3:::your-bucket-name/*"]
      },
      {
        Effect   = "Allow",
        Action   = ["logs:DescribeLogGroups", "logs:GetLogEvents"],
        Resource = "*"
      }
    ]
  })
}

resource "aws_iam_group_policy_attachment" "developers_policy_attachment" {
  group      = aws_iam_group.developers_group.name
  policy_arn = aws_iam_policy.developers_policy.arn
}

Operations: The operations team needs broader access, including Systems Manager and RDS:

resource "aws_iam_policy" "operations_policy" {
  name = "OperationsPolicy"
  policy = jsonencode({
    Statement = [
      { "Effect": "Allow", "Action": ["ec2:*"], "Resource": "*" },
      { "Effect": "Allow", "Action": ["cloudwatch:*"], "Resource": "*" },
      { "Effect": "Allow", "Action": ["ssm:*"], "Resource": "*" },
      { "Effect": "Allow", "Action": ["rds:*"], "Resource": "*" }
    ]
  })
}

Finance: For finance, we provided read-only access to billing-related services:

resource "aws_iam_policy" "finance_policy" {
  name = "FinancePolicy"
  policy = jsonencode({
    Statement = [
      { "Effect": "Allow", "Action": ["ce:GetCostAndUsage"], "Resource": "*" },
      { "Effect": "Allow", "Action": ["budgets:ViewBudget"], "Resource": "*" },
      { "Effect": "Allow", "Action": ["s3:ListBucket", "s3:GetObject"], "Resource": "*" }
    ]
  })
}

Analysts: Analysts required read-only access to databases and S3 buckets:

resource "aws_iam_policy" "analysts_policy" {
  name = "AnalystsPolicy"
  policy = jsonencode({
    Statement = [
      { "Effect": "Allow", "Action": ["s3:GetObject", "s3:ListBucket"], "Resource": "*" },
      { "Effect": "Allow", "Action": ["rds:Describe*"], "Resource": "*" }
    ]
  })
}

Step 3: Automating with Terraform

Using Terraform's for_each, we dynamically created IAM users and assigned them to groups:

variable "developer_names" {
  default = ["developer1", "developer2"]
}

resource "aws_iam_user" "developers" {
  for_each = toset(var.developer_names)
  name     = each.value
}

resource "aws_iam_user_group_membership" "developers_group" {
  name  = "developers-membership"
  group = aws_iam_group.developers_group.name
  users = [for user in aws_iam_user.developers : user.name] 
}

This approach minimizes duplication and allows easy scaling. We’ll do this for each respective group.


Step 4: Deployment Process

  1. Initialize Terraform:

     terraform init
    
  2. Validate Configuration:

     terraform validate
    
  3. Plan and Apply:

     terraform plan
     terraform apply
    

Results:

With this setup:

  • Developers can manage EC2 and S3 for their applications.

  • Operations has full control over infrastructure.

  • Finance has secure, read-only billing access.

  • Analysts have the data access they need without overreaching permissions.


Conclusion:

Using Terraform to manage IAM permissions ensures a scalable, repeatable, and secure workflow for cloud infrastructure. By assigning role-based access, we’ve met the startup’s security needs while empowering teams with the right tools.

Want to secure your cloud infrastructure? Start implementing IAM permissions with Terraform today!