IAM identities (users, groups, or roles) must be assigned explicit permissions to access AWS resources. The associated IAM policy determines the privileges available to an IAM identity. Policies are JSON documents that define explicit allow/deny privileges to specific resources or resource groups.
There are advantages to managing IAM policies in Terraform rather than manually in AWS. With Terraform, you can reuse your policy templates and ensure the principle of least privilege with resource interpolation.
In this tutorial, you will create an IAM user and an S3 bucket. Then, you will map permissions for that bucket with an IAM policy. Finally, you will attach that policy to the new user and learn how to iterate on more complex policies.
»Prerequisites
To follow along with this tutorial, you will need:
- Terraform 0.14
- An AWS account with IAM administrative permissions.
»Clone the example repository
Clone the Create IAM policies with Terraform repository.
$ git clone https://github.com/hashicorp/learn-terraform-iam-policy.git
Change into the repository directory.
$ cd learn-terraform-iam-policy
»Review the IAM policy resource
The IAM policy resource is the starting point for creating an IAM policy in Terraform.
The main.tf
file contains an IAM policy resource, an S3 bucket, and a new IAM user. Open the main.tf
file in your code editor and review the IAM policy resource. The name
in your policy is a random_pet
string to avoid duplicate policy names.
resource "aws_iam_policy" "policy" {
name = "${random_pet.pet_name.id}-policy"
description = "My test policy"
policy = <<EOT
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:ListAllMyBuckets"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": [
"s3:*"
],
"Effect": "Allow",
"Resource": "${aws_s3_bucket.bucket.arn}"
}
]
}
EOT
}
The resource's policy
attribute uses a multi-line heredoc string. For simple policies or one-off configurations, this approach is acceptable. However, as your policies grow more complex and you begin to reuse them throughout your environment, it can be difficult to parse policies using heredoc strings.
»Refactor your policy
The aws_iam_policy_document
data source uses HCL to generate a JSON representation of an IAM policy document. Writing the policy as a Terraform configuration has several advantages over defining your policy inline in the aws_iam_policy
resource.
- Writing your policy with this data source makes applying policies to your AWS resources more flexible. You can overwrite, append, or update policies with this resource by using the
source_json
andoverride_json
arguments. - Terraform error checking automatically formats your policy document into correct JSON when you run your apply.
- Data sources make it easier to reuse policies throughout your environment.
Copy the aws_iam_policy_document
configuration below into your main.tf
file. This data source uses HCL syntax to define the same IAM privileges as the policy in the heredoc string.
data "aws_iam_policy_document" "example" {
statement {
actions = ["s3:ListAllMyBuckets"]
resources = ["arn:aws:s3:::*"]
effect = "Allow"
}
statement {
actions = ["s3:*"]
resources = [aws_s3_bucket.bucket.arn]
effect = "Allow"
}
}
Both statements in this policy apply to any user, group, or role with this policy attached. The first policy statement allows the user to list every S3 bucket in the AWS account. The second policy statement allows the user to perform any action on the bucket you create in this configuration, but not on other buckets in the account.
Update your iam_policy
resource policy
attribute to use the IAM policy document and save your changes.
resource "aws_iam_policy" "policy" {
name = "${random_pet.pet_name.id}-policy"
description = "My test policy"
+ policy = data.aws_iam_policy_document.example.json
- policy = <<EOT
- {
- "Version": "2012-10-17",
- "Statement": [
- {
- "Action": [
- "s3:ListAllMyBuckets"
- ],
- "Effect": "Allow",
- "Resource": "*"
- },
- {
- "Action": [
- "s3:*"
- ],
- "Effect": "Allow",
- "Resource": "${aws_s3_bucket.bucket.arn}"
- }
- ]
- }
- EOT
- }
»Create a policy attachment
The iam_policy
resource and iam_policy_document
data source used together will create a policy, but this configuration does not apply this policy to any users or roles. You must create a policy attachment for your policy to apply to your users.
In your main.tf
file, add a new policy attachment resource to apply your policy to the user created in this configuration.
resource "aws_iam_user_policy_attachment" "attachment" {
user = aws_iam_user.new_user.name
policy_arn = aws_iam_policy.policy.arn
}
The policy attachment resource has two required attributes: the user
and the policy_arn
. Terraform interpolates your policy Amazon Resource Name (ARN) from your previously defined iam_policy
resource when you apply this configuration. Each AWS resource has an ARN, and Terraform passes that unique identifier to the AWS API.
This resource assigns your policy to a specific user. If you are creating a group or a role, you can use the group or role attachment resources instead.
Add an output for your JSON-rendered policy to the end of your configuration file.
output "rendered_policy" {
value = data.aws_iam_policy_document.example.json
}
»Create your user, bucket, and policy
Now that you have created and attached a policy in your configuration, apply your changes.
In your terminal, initialize your Terraform configuration.
$ terraform init
Apply your configuration. Enter yes
when prompted to accept your changes.
$ terraform apply
## ...
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Outputs:
rendered_policy = <<EOT
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Action": "s3:ListAllMyBuckets",
"Resource": "arn:aws:s3:::*"
},
{
"Sid": "",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "arn:aws:s3:::primarily-liberal-spider-bucket"
}
]
}
EOT
The output should contain a rendered policy with the complete ARN of your newly created bucket.
»Test the policy
Test this policy in the AWS Policy Simulator. This tool lets you test an IAM policy by simulating whether a user would be allowed to run AWS operations.
Use the Policy Simulator to test if your user can delete any objects or buckets in the S3 service.
- First, select
new_user
, then your policy name from the left sidebar. Your policy name should start with your random animal and-policy-
. - In the "Select service" drop-down, select "S3".
- In the "Select actions" drop-down, choose "DeleteObject" and "DeleteBucket".
- Click "Run Simulation" and verify the simulator denies both actions as intended.
Next, test if your user can delete objects in the test bucket you created or delete the bucket itself.
- Copy the ARN of your test bucket. Your ARN should start with
arn:aws:s3:::<RANDOM_ANIMAL>-bucket-
. - Expand the simulation result drop-down for each action and add the bucket ARN.
- Select "Run Simulation" and verify the simulator allows both actions as intended.
»Clean up your infrastructure
Before moving on, destroy the infrastructure you created in this tutorial.
$ terraform destroy
Be sure to respond to the confirmation prompt with yes.
»Next Steps
In this tutorial, you created and refactored an AWS IAM policy with Terraform. To learn more about creating policies with Terraform, consider the resources below.
- S3 bucket policies differ from IAM policies. To learn more about S3 bucket policy resources, review the S3 bucket policy resource.
- The
templatefile()
function allows you to create templatized policies for use in your configuration. - For an alternate to heredoc formatting, use the
jsonencode()
function.