Organizations or individuals typically manage IAM using consoles and hesitate to use Infrastructure-as-code (IaC) as it is complex and sensitive to define IAM policies due to security risks. With frequent dynamic changes, you do not get immediate feedback. And more expertise is needed to configure and manage IAM rules with IaC. However, configuring IAM though IaC also have several benefits.
In this blog we’ll explore those benefits, discuss strategies for IAM management via Terraform, explain why implementing Zero Trust policies within IAM is crucial for security, and how to enforce IAM best practices, Policy-as-code, and IAM governance.
Why manage IAM through Infrastructure-as-code?
- Automation and consistency – It offers automation, consistency, repeatability, and versioning to IAM policies and role management.
- Audit trails – It allows you to maintain a comprehensive audit trail of changes to IAM configurations. This helps with compliance requirements and allows you to easily track who made changes when they were made, and why.
- Least Privileges – Terraform’s expressive language allows for defining complex IAM policies with fine-grained control over permissions. Teams can more easily provision their own access in a controlled manner through pull requests, which then undergo a review process before being applied, fostering a self-service infrastructure model.
Zero Trust policies within IAM
Identity and Access Management (IAM) is a critical component of Zero-Trust security, assuming you do not trust anybody. Zero trust in IAM means:
- Never Trust, Always Verify – There is no automatic trust. Always verify everyone who is trying to access the resource.
- Least Privilege Access – Limits access to resources to the minimum necessary to perform a specific task and reduce the blast radius. This is to stop people from allowing unnecessary permissions to users/roles, which can cause breaking changes.
- MFA – Multi-factor authentication enables the extra security layer on IAM users/roles.
Setting Up AWS IAM Policies with Terraform
Setting up AWS IAM policies with Terraform involves defining your IAM resources in Terraform configuration files, applying best practices for security and organization, and using Terraform’s capabilities to manage these resources as code. Below, we’ll outline a basic approach to setting up IAM policies in AWS using Terraform, including an example configuration.
In this blog post, we will cover the Terraform configs that are also compatible with OpenTofu.
Prerequisites
Before you start, ensure you have the following:
- Terraform is installed on your machine.
- An AWS account and AWS CLI configured with access credentials.
- Basic knowledge of IAM concepts (e.g., policies, roles, users) and Terraform syntax.
Step 1: Initialize Terraform Project
Create a new directory for your Terraform project and initialize it with a main.tf file. Then, run terraform init in the project directory to prepare your directory for Terraform operations.
Step 2: Define the AWS Provider
In main.tf, start by defining the AWS provider. This specifies which version of the AWS provider to use and configures the region and other provider settings.
provider “aws” {
region = “us-east-1”
#AWS account access keys credentials
access_key = “A***************”
secret_key = “U******************”
}
Step 3: Define IAM Policy
Define an IAM policy using the aws_iam_policy resource. You need to provide a name and a policy document. The policy document can be defined inline using the <<EOF … EOF syntax, or it can be loaded from a file using the file() function.
Example of an inline policy definition:
# Create IAM policy to allow S3 read access
resource “aws_iam_policy” “s3_read_policy” {
name = “s3_read_policy”
description = “Allows read access to files in the specified S3 bucket”
policy = <<EOF
{
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Action”: [
“s3:GetObject”,
“s3:ListBucket”
],
“Resource”: [
“arn:aws:s3:::your-bucket-name/*”,
“arn:aws:s3:::your-bucket-name”
]
}
]
}
EOF
}
We can see the iam_policy creation by running the terraform plan
command.
terraform plan command
output from terraform plan
Step 4: Create IAM user
Define an IAM user
# Create IAM user
resource “aws_iam_user” “iam_user” {
name = “iam_user”
}
We can see the iam_user creation by running terraform plan
command.
output for terraform plan
Step 5: Create an IAM Role and Attach the Policy
Define an IAM role and set assume role policy to allow the IAM user to assume the role
# Create an IAM role
resource “aws_iam_role” “iam_role” {
name = “iam-role”
assume_role_policy = jsonencode({
Version = “2012-10-17”,
Statement = [{
Action = “sts:AssumeRole”,
Principal = {
AWS = “arn:aws:iam::AWS_ACCOUNT_ID:user/${aws_iam_user.iam_user.name}” # Replace [AWS_ACCOUNT_ID] with Account’s AWS account ID
},
Effect = “Allow”,
Sid = “ ”
}]
})
}
We can see the iam_role creation by running terraform plan
command.
output for terraform plan
Step 6: Attach IAM policy to IAM role
# Attach IAM policy to IAM role
resource “aws_iam_policy_attachment” “s3_read_attach” {
roles = [aws_iam_role.iam_role.name]
policy_arn = aws_iam_policy.s3_read_policy.arn
name = “Attaching s3 policy to iam role”
}
We can see the iam_role creation by running terraform plan command.
iam s3 policy – terraform plan output
Step 7: Apply Configuration
The Terraform configuration has been defined. Apply it using the command terraform apply
in your project directory. This command will prompt you to review the proposed changes and confirm them. Upon confirmation, Terraform will create the resources in AWS according to your configuration.
After running the final terraform apply
command, we can see the iam-role, iam_user, and s3ReadPolicy resources.
Utilizing Terraform’s templatefile function for dynamic policy generation
The templatefile() function in Terraform allows you to dynamically generate configuration files using templates. You can use this function to generate IAM policy documents dynamically, which can be helpful in cases where policies need to be customized based on dynamic inputs.
Here’s an example of how you can use the templatefile() function to dynamically generate an IAM policy document for S3 read access:
provider “aws” {
region = “us-east-1”
#AWS account access keys credentials
access_key = “A***************”
secret_key = “U******************”
}
# Define IAM policy template. This works prior to terraform 0.12
data “template_file” “s3_read_policy_template” {
template = <<EOF
{
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Action”: [
“s3:GetObject”,
“s3:ListBucket”
],
“Resource”: [
“${bucket_arn}/*”,
“${bucket_arn}”
]
}
]
}
EOF
vars = {
bucket_arn = “arn:aws:s3:::your-bucket-name”
}
}
You can also store the above template snippet into a separate s3_read_policy.tmpl template file for Terraform above version 0.12 and reference it as shown below:
# Content of s3_read_policy.tmpl file
{
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Action”: [
“s3:GetObject”,
“s3:ListBucket”
],
“Resource”: [
“${bucket_arn}/*”,
“${bucket_arn}”
]
}
]
}
# Create IAM policy using the template
resource “aws_iam_policy” “s3_read_policy” {
provider = aws.account_a
name = “s3_read_policy”
policy = templatefile( “${path.module}/template_file.tpl”,
{
bucket_arn = “arn:aws:s3:::BUCKET_NAME” #provide the s3 bucket name
} )
}
We can see the s3_read_policy creation by running terraform plan command.
iam s3 read policy – terraform plan output
# Create IAM user
resource “aws_iam_user” “iam_user” {
name = “iam_user”
}
We can see the iam_user creation by running terraform plan command.
output for terraform plan
# Create an IAM role
resource “aws_iam_role” “reader_role” {
name = “reader_role”
assume_role_policy = jsonencode({
Version = “2012-10-17”,
Statement = [{
Action = “sts:AssumeRole”,
Principal = {
AWS = “arn:aws:iam::AWS_ACCOUNT_ID:user/${aws_iam_user.iam_user.name}” # Replace [AWS_ACCOUNT_ID] with Account’s AWS account ID
},
Effect = “Allow”,
Sid = “AssumeRole“
}]
})
}
We can see the iam_role creation by running terraform plan command.
output for terraform plan
# Attach IAM policy to IAM role
resource “aws_iam_policy_attachment” “s3_read_attach” {
name = “s3_read_attach”
roles = [aws_iam_role.reader_role.name]
policy_arn = aws_iam_policy.s3_read_policy.arn
}
We can see the iam_policy_attachment creation by running terraform plan command.
iam s3 policy – terraform plan output
This configuration reads the content of the s3_read_policy.tmpl file using the file function and then using it to create the IAM policy. You can adjust the file path, policy name, and bucket ARN per your use case.
This approach allows you to generate IAM policies dynamically based on inputs or variables, providing flexibility in your Terraform configurations. Adjust the template and variables as needed for your specific use case.
After running the final terraform apply command, we can see the iam_user, reader-role, and attached s3_read_policy resources have been created in the AWS, as shown below.
Cross-Account Management
Cross-account access is particularly beneficial when organizations maintain multiple AWS accounts for different purposes, such as development, staging, and production.
Benefits of cross-account management
- Without cross-account access, managing user access across these accounts can become complex and cumbersome.
- It helps to enforce least privilege principles by allowing administrators to define granular access controls using IAM roles. This ensures users can only access the resources required for their roles or responsibilities.
- Cross-account access facilitates centralized user management and auditing.
- Administrators can create and manage users centrally in an AWS management account, reducing the administrative overhead of managing user identities across multiple accounts.
- Auditing and tracking user access become more straightforward as all access requests and actions are logged centrally in the AWS management account.
- Cross-account access is crucial for ensuring streamlined operations in large organizations where a security team manages multiple AWS accounts centrally.
Unlocking Cross-Account Access in AWS with Terraform
Let’s use Terraform to create an IAM user in AWS Account A and establish cross-account access with the “AssumeRole” action.
In this example, we’ll create an IAM user in AWS Account A and configure a cross-account role in AWS Account B that allows the IAM user in AWS Account A to assume it and allow the CrossAccountUser in AWS Account A to read files from buckets in AWS Account B. We’ll need to define an IAM policy granting the necessary permissions and attach that policy to the cross-account role in AWS Account B.
Here’s how you can achieve this:
- Set alias for Account A
provider “aws” {
region = “us-east-1”
#AWS account A access key credentials
access_key = “A***************”
secret_key = “U******************”
alias = “account_a”
}
- Create an IAM user in AWS Account A
resource “aws_iam_user” “cross_account_user” {
provider = aws.account_a
name = “CrossAccountUser”
}
- Setup AWS Account B
provider “aws” {
region = “us-east-1”
#AWS account B access key credentials
access_key = “A***************”
secret_key = “U******************”
alias = “account_b”
}
- Define an IAM role in AWS Account B
resource “aws_iam_role” “cross_account_role” {
provider = aws.account_b
name = “CrossAccountRole”
assume_role_policy = jsonencode({
Version = “2012-10-17”,
Statement = [{
Effect = “Allow”,
Principal = {
AWS = “arn:aws:iam::Account_A_ID:user/CrossAccountUser” # Replace [Account A ID] with AWS Account A’s AWS account ID
},
Action = “sts:AssumeRole”,
}]
})
}
- Define IAM policy to allow reading files from S3 buckets in Account B
# Replace "bucket-name" with the name of your bucket in AWS Account B
resource “aws_iam_policy” “s3_read_policy” {
provider = aws.account_b
name = “S3ReadPolicy”
policy = <<EOF
{
“Version”: “2012-10-17”,
“Statement”: [{
“Effect”: “Allow”,
“Action”: [
“s3:GetObject”,
“s3:ListBucket”
],
“Resource”: [
“arn:aws:s3:::bucket-name/*”,
“arn:aws:s3:::bucket-name”
]
}]
}
EOF
}
- Attach IAM policy to the IAM role in AWS Account B
resource “aws_iam_role_policy_attachment” “s3_read_attach_policy” {
provider = aws.account_b
role = aws_iam_role.cross_account_role.name
policy_arn = aws_iam_policy.s3_read_policy.arn
}
Make sure to replace Account_A_ID with the Account A AWS account ID and “bucket-name” with the name of the s3 bucket in AWS Account B that you want to grant access to. Also, ensure that the bucket policy in AWS Account B allows access from the role CrossAccountRole.
With this setup, the CrossAccountUser in Account A can assume the CrossAccountRole in Account B and access files from the specified s3 bucket in Account B.
Enforcing IAM Best Practices with Policy-as-Code
Enforcing IAM Best Practices with Policy-as-Code ensures that security policies are consistently applied across an organization’s cloud infrastructure. By codifying IAM policies, teams can automate enforcing security controls, reducing the risk of misconfigurations and unauthorized access.
checkov is one of the Policy-as-Code tools available in cloud security. It is an open-source static code analysis tool developed by Bridgecrew.
It scans infrastructure as code (IaC) templates like Terraform and CloudFormation to detect security and compliance issues early. By analyzing configurations against predefined policies and industry standards, Checkov helps identify misconfigurations, vulnerabilities, and compliance violations. It focuses on cloud security, particularly in AWS, Azure, and GCP environments, and integrates seamlessly into CI/CD pipelines for proactive issue remediation before deployment.
Utilizing Checkov, highlighting its ability to detect IAM configuration issues early, focusing on preventing overly permissive policies.
Regarding IAM configuration issues, Checkov plays a crucial role in detecting overly permissive policies early in the development process.
Here’s how Checkov helps in detecting IAM configuration issues, mainly focusing on preventing overly permissive policies:
Static Analysis with Checkov: Configuring Checkov for IAM policy scans.
Let us set up checkov to scan this setup for potential security risks and misconfigurations using the Terraform code example
Example Terraform code we’ll be analyzing:
- AWS Provider Configuration – Set the AWS region to us-east-1.
provider “aws” {
region = “us-east-1”
access_key = “A*********************”
secret_key = “U****************************”
}
- IAM Policy for S3 Read Access – Create an IAM policy named s3_read_policy that allows read access (s3:GetObject, s3:ListBucket) to a specified S3 bucket.
resource “aws_iam_policy” “s3_read_policy” {
name = “s3_read_policy”
description = “Allows read access to files in the specified S3 bucket”
policy = <<EOF
{
“Version”: “2012-10-17”,
“Statement”: [
{
“Effect”: “Allow”,
“Action”: [
“s3:GetObject”,
“s3:ListBucket”
],
“Resource”: [
“arn:aws:s3:::your-bucket-name/*”,
“arn:aws:s3:::your-bucket-name”
]
}
]
}
EOF
}
- IAM User Creation – Define an IAM user named iam_user.
resource “aws_iam_user” “iam_user” {
name = “iam_user”
}
- IAM Role and Assume Role Policy – Set up an IAM role named iam-role with an assume role policy that allows the iam_user to assume this role.
resource “aws_iam_role” “iam_role” {
name = “iam-role”
assume_role_policy = jsonencode({
Version = “2012-10-17”,
Statement = [{
Action = “sts:AssumeRole”,
Principal = {
AWS = “arn:aws:iam::AWS_ACCOUNT_ID:user/${aws_iam_user.iam_user.name}”
},
Effect = “Allow”,
Sid = “AssumeRole”
}]
})
}
- Policy Attachment – Attaches the s3_read_policy to the iam_role.
resource “aws_iam_policy_attachment” “s3_read_attach” {
roles = [aws_iam_role.iam_role.name]
policy_arn = aws_iam_policy.s3_read_policy.arn
name = “Attaching s3 policy to iam role”
}
Configuring Checkov for IAM Policy Scans
Install Checkov – First, ensure that checkov is installed in your environment. If not, install it via pip by running
pip install checkov
Run checkov Scan -checkov will provide a detailed report of any issues, including security vulnerabilities, best practice violations, and compliance issues. In our example, checkov can help identify potential risks in IAM policies, such as overly broad permissions, and suggest mitigations, etc.
For a file – You can run it for one single file using checkov --file file_name.tf
command.
For a directory – You can go to the directory containing your Terraform files to scan all files within the directory and run command.
checkov –d .
checkov output
Refining the Scan – If you want to focus specifically on IAM-related checks, you can use checkov’s –check flag to include or exclude certain checks based on their IDs, tailoring the scan to your specific needs. For example, to ensure IAM policies that allow full “*-*” administrative privileges are not created, you can run:
checkov -d . --check CKV_AWS_62
Output for Terraform code example scan
Results – By running Checkov as described, we can identify potential security issues such as:
- Excessive permissions in the IAM policy.
- IAM policies are attached directly to users instead of roles.
- Missing or overly permissive assume role policies.
Addressing these issues involves modifying the Terraform code to adhere to best practices, such as implementing least privilege, using roles for cross-account access, and ensuring policies are scoped appropriately.
Custom Policies – checkov allows you to enforce specific security or compliance requirements that Checkov’s built-in checks might not cover. This is helpful if your organization has specific compliance requirements that the default checks do not cover.
Integrating Policy Scans in CI/CD: Automating IAM policy compliance checks before deployment.
policy compliance with CI/CD
Wrapping with IAM governance & best practices
Focusing on Identity and Access Management (IAM) governance and best practices is essential for ensuring the security and compliance of cloud environments. This approach helps systematically manage digital identities, their authentication, authorization, roles, and privileges within or across system and enterprise boundaries.
Integrating IAM Governance
IAM governance should be an integral part of any organization’s security strategy. It involves several key components:
- Organizations should strive for centralized management of user identities and their access across all systems and platforms. This simplifies the enforcement of access policies and compliance with regulatory requirements.
- Assigning permissions based on roles tightly aligned with organizational structures and job functions streamlines access management and enforces the principle of least privilege.
- Conducting regular audits of IAM policies and practices helps identify and remediate unused or excessive permissions and ensures compliance with relevant standards and regulations.
- Implementing robust processes for the entire lifecycle of user identities – from creation through management, to deletion – ensures that access rights are always up to date and reduces the risk of orphaned accounts.
IAM Best Practices
To enhance IAM governance, organizations should adhere to a set of best practices:
Ensuring that users have only the minimum levels of access required to perform their functions minimizes potential damage from errors or malicious intent.
Use multi-factor authentication (MFA) and strong password policies to enhance security. For critical resources, consider additional authentication factors and stringent authorization checks.
Separate roles and responsibilities to prevent conflicts of interest or fraud. This is crucial in preventing any single point of compromise.
Automate the process of granting and revoking access to minimize the risk of oversight.
Leverage managed policies for easier administration and reuse of standard permission sets.
Methods to perform compliance audits on IAM configurations
Open Policy Agent (OPA) is an open-source, general-purpose policy engine that unifies policy enforcement across the cloud-native stack. It can be incorporated into IaC workflows. OPA enables you to craft policies that govern and secure your cloud environments without embedding policy logic within your applications and enhances their security, compliance, and governance.
OPA Policy Example for Terraform
For creating an Open Policy Agent (OPA) policy relevant to the provided Terraform code example (which involves IAM policies for s3 read access, IAM user, and IAM role creations), we’ll focus on enforcing a rule that IAM policies should specify a specific s3 bucket and not allow broad access.
OPA Policy for Specific S3 Bucket Access in IAM Policies
OPA policies are written in a high-level declarative language called Rego. This policy aims to ensure that any IAM policy granting access to s3 buckets explicitly specifies the bucket name, rather than allowing access to all buckets.
Define a Rego policy file, e.g., iam_policy.rego, that includes the rule to check IAM policy statements for specific S3 bucket access.
package terraform.analysis
default allow = false
# Rule to check for specific bucket access in IAM policies
allow {
some i
input.resource.aws_iam_policy[i].policy
policy := json.unmarshal(input.resource.aws_iam_policy[i].policy)
policy.Statement[_].Effect == “Allow”
action_allowed(policy.Statement[_].Action)
not wildcard_bucket_access(policy.Statement[_].Resource)
}
# Helper to check if actions related to S3 read are allowed
action_allowed(actions) {
allowed_actions := [“s3:GetObject”, “s3:ListBucket”]
allowed_actions[_] == actions[_]
}
# Helper to check for wildcard bucket access
wildcard_bucket_access(resources) {
resources[_] == “arn:aws:s3:::*”
}
This Rego policy does the following:
- Checks IAM policies: It looks for aws_iam_policy resources in the Terraform plan.
- Parses the policy JSON: It unmarshals the JSON policy document to inspect the policy statements.
- Evaluate policy statements: It checks if any “Allow” statements permit s3:GetObject or s3:ListBucket actions.
- Ensures specific bucket access: It ensures that resources do not include a wildcard (arn:aws:s3:::*), indicating that the policy specifies particular buckets.
Using the OPA Policy:
To use this policy, you would typically evaluate it against your Terraform plan output in JSON format, using the opa eval command. First, generate the Terraform plan:
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
Then, evaluate your policy with OPA:
opa eval --format pretty --data iam_policy.rego --input tfplan.json “data.terraform.analysis.allow”
This OPA policy scrutinizes your Terraform plan, specifically checking whether IAM policies for S3 access are narrowly scoped to specific buckets. Enforcing such policies ensures that your cloud environment adheres to security best practices, significantly mitigating potential risks.
Conclusion
We’ve explored the nuances of managing AWS IAM through Terraform, highlighting its significance in bolstering cloud security, IAM configurations as Infrastructure-as-code, and the critical role of Zero-Trust policies within IAM. Setting up IAM policies, creating users and roles, and managing cross-account access and trust relationships.
The exploration into enforcing IAM best practices through policy-as-code with tools like checkov underscored the transformative impact of static code analysis in preempting configuration errors and security risks.
Finally, we touched upon IAM governance and compliance, underscoring methods like Rego policy definitions with OPA for performing compliance audits on IAM configurations. This ensures alignment with security best practices and regulatory standards, cementing IAM’s role in securing cloud environments.
Commonly Asked Questions
1) What are the best practices for managing AWS IAM policies with Terraform?
- Use Least Privilege Principle – Grant only the permissions necessary for a user, group, or role to perform their intended tasks.
- Separation of Concerns – Organize IAM policies logically by separating them based on roles, responsibilities, or permissions.
- Enable Policy Testing – Implement automated tests to validate IAM policies for correctness and compliance with organizational policies and regulatory requirements.
- Rotate IAM Credentials Regularly – Reduce risk and enhance security by automating the rotation of IAM access keys and credentials using AWS Secrets Manager or AWS IAM Access Analyzer.
- Use IaC to maintain the IAM policies and changes using Infrastructure-as-a-code to maintain consistency.
- Monitor and Audit IAM Changes – Implement and review logging and monitoring of IAM actions and changes using AWS CloudTrail and AWS Config.
2) Can Terraform manage dynamic IAM policies for temporary access?
Yes, Terraform can manage dynamic IAM policies for temporary access using AWS IAM Roles with session policies and AWS Security Token Service (STS)
3) How do I create and manage AWS IAM users and their access keys with Terraform?
Terraform’s AWS provider, iam users, and IAM policies allow you to create and manage AWS IAM users and their access keys.
4) What are instance profiles, and how do they relate to IAM roles in AWS?
Instance profiles associate IAM roles with EC2 instances, allowing the instances to inherit the role’s permissions. When an IAM role is attached to an EC2 instance, the corresponding instance profile is attached. This mechanism enables EC2 instances and other services to securely access AWS resources without requiring long-term credentials like access keys or passwords.