Terraform Tutorial - AWS IAM user, group, role, and policies - part 1
In this post, we'll will create an IAM user and an S3 bucket. Then, we will map permissions for that bucket with an IAM policy and attach that policy to the new user.
This post mostly follows the guide from Create IAM Policies.
main.tf:
terraform { required_providers { aws = { source = "hashicorp/aws" version = "3.42.0" } } } provider "aws" { region = var.region } data "aws_iam_policy_document" "s3_policy" { statement { actions = ["s3:ListAllMyBuckets"] resources = ["arn:aws:s3:::*"] effect = "Allow" } statement { actions = ["s3:*"] resources = [aws_s3_bucket.bucket.arn] effect = "Allow" } } resource "aws_iam_user" "new_user" { name = "new_user" } resource "aws_iam_access_key" "my_access_key" { user = aws_iam_user.new_user.name pgp_key = var.pgp_key } resource "random_pet" "pet_name" { length = 3 separator = "-" } resource "aws_s3_bucket" "bucket" { bucket = "${random_pet.pet_name.id}-bucket" acl = "private" tags = { Name = "My bucket" Environment = "Dev" } } resource "aws_iam_policy" "policy" { name = "${random_pet.pet_name.id}-policy" description = "My test policy" policy = data.aws_iam_policy_document.s3_policy.json } output "rendered_policy" { value = data.aws_iam_policy_document.s3_policy.json } output "secret" { value = aws_iam_access_key.my_access_key.encrypted_secret sensitive = true }
variables.tf:
variable "region" { default = "us-east-1" } variable "pgp_key" { description = "Either a base-64 encoded PGP public key, or a keybase username in the form keybase:username. Used to encrypt the password and the access key on output to the console." default = "" }
Before anything else, we need to do terraform init
to Initializing the backend and provider plugins.
$ terraform init Initializing the backend... Initializing provider plugins... - Reusing previous version of hashicorp/aws from the dependency lock file - Using previously-installed hashicorp/aws v3.42.0 Terraform has been successfully initialized ...
Now, run terraform plan
and terraform apply
commands:
$ terraform plan $ terraform apply --auto-approve random_pet.pet_name: Creating... random_pet.pet_name: Creation complete after 0s [id=completely-wise-mongrel] aws_iam_user.new_user: Creating... aws_s3_bucket.bucket: Creating... aws_iam_user.new_user: Creation complete after 1s [id=new_user] aws_iam_access_key.my_access_key: Creating... aws_iam_access_key.my_access_key: Creation complete after 0s [id=AKIAQV57YS3YMIYTCU4I] aws_s3_bucket.bucket: Creation complete after 7s [id=completely-wise-mongrel-bucket] data.aws_iam_policy_document.s3_policy: Reading... data.aws_iam_policy_document.s3_policy: Read complete after 0s [id=3288376908] aws_iam_policy.policy: Creating... aws_iam_policy.policy: Creation complete after 2s [id=arn:aws:iam::047109936880:policy/completely-wise-mongrel-policy] 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:::completely-wise-mongrel-bucket" } ] } EOT
We create a new_user with a permission to work on a S3 bucket. Note that the aws_secret_access_key is stored in terraform.tfstate not encrypted:
$ cat terraform.tfstate { "version": 4, "terraform_version": "0.15.3", "serial": 87, "lineage": "5b8287a5-118e-5c42-2432-fefdb9e74f06", "outputs": { "rendered_policy": { "value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListAllMyBuckets\",\n \"Resource\": \"arn:aws:s3:::*\"\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:*\",\n \"Resource\": \"arn:aws:s3:::completely-wise-mongrel-bucket\"\n }\n ]\n}", "type": "string" } }, "resources": [ { "mode": "data", "type": "aws_iam_policy_document", "name": "s3_policy", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "id": "3288376908", "json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListAllMyBuckets\",\n \"Resource\": \"arn:aws:s3:::*\"\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:*\",\n \"Resource\": \"arn:aws:s3:::completely-wise-mongrel-bucket\"\n }\n ]\n}", "override_json": null, "policy_id": null,s "source_json": null, "statement": [ { "actions": [ "s3:ListAllMyBuckets" ], "condition": [], "effect": "Allow", "not_actions": [], "not_principals": [], "not_resources": [], "principals": [], "resources": [ "arn:aws:s3:::*" ], "sid": "" }, { "actions": [ "s3:*" ], "condition": [], "effect": "Allow", "not_actions": [], "not_principals": [], "not_resources": [], "principals": [], "resources": [ "arn:aws:s3:::completely-wise-mongrel-bucket" ], "sid": "" } ], "version": "2012-10-17" }, "sensitive_attributes": [] } ] }, { "mode": "managed", "type": "aws_iam_access_key", "name": "my_access_key", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "encrypted_secret": null, "id": "AKIAQV57YS3YMIYTCU4I", "key_fingerprint": null, "pgp_key": "", "secret": "i8lp2...iRFP", "ses_smtp_password_v4": "BDUscxy7iogk6c1MQ2pGiqJIBr1QNufrgP684yFut1Iu", "status": "Active", "user": "new_user" }, "sensitive_attributes": [], "private": "bnVsbA==", "dependencies": [ "aws_iam_user.new_user" ] } ] }, { "mode": "managed", "type": "aws_iam_policy", "name": "policy", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "arn": "arn:aws:iam::047109936880:policy/completely-wise-mongrel-policy", "description": "My test policy", "id": "arn:aws:iam::047109936880:policy/completely-wise-mongrel-policy", "name": "completely-wise-mongrel-policy", "name_prefix": null, "path": "/", "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:ListAllMyBuckets\",\n \"Resource\": \"arn:aws:s3:::*\"\n },\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Action\": \"s3:*\",\n \"Resource\": \"arn:aws:s3:::completely-wise-mongrel-bucket\"\n }\n ]\n}" }, "sensitive_attributes": [], "private": "bnVsbA==", "dependencies": [ "aws_s3_bucket.bucket", "data.aws_iam_policy_document.s3_policy", "random_pet.pet_name" ] } ] }, { "mode": "managed", "type": "aws_iam_user", "name": "new_user", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "arn": "arn:aws:iam::047109936880:user/new_user", "force_destroy": false, "id": "new_user", "name": "new_user", "path": "/", "permissions_boundary": null, "tags": null, "unique_id": "AIDAQV57YS3YPHSUKSALS" }, "sensitive_attributes": [], "private": "bnVsbA==" } ] }, { "mode": "managed", "type": "aws_s3_bucket", "name": "bucket", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "acceleration_status": "", "acl": "private", "arn": "arn:aws:s3:::completely-wise-mongrel-bucket", "bucket": "completely-wise-mongrel-bucket", "bucket_domain_name": "completely-wise-mongrel-bucket.s3.amazonaws.com", "bucket_prefix": null, "bucket_regional_domain_name": "completely-wise-mongrel-bucket.s3.amazonaws.com", "cors_rule": [], "force_destroy": false, "grant": [], "hosted_zone_id": "Z3AQBSTGFYJSTF", "id": "completely-wise-mongrel-bucket", "lifecycle_rule": [], "logging": [], "object_lock_configuration": [], "policy": null, "region": "us-east-1", "replication_configuration": [], "request_payer": "BucketOwner", "server_side_encryption_configuration": [], "tags": { "Environment": "Dev", "Name": "My bucket" }, "versioning": [ { "enabled": false, "mfa_delete": false } ], "website": [], "website_domain": null, "website_endpoint": null }, "sensitive_attributes": [], "private": "bnVsbA==", "dependencies": [ "random_pet.pet_name" ] } ] }, { "mode": "managed", "type": "random_pet", "name": "pet_name", "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", "instances": [ { "schema_version": 0, "attributes": { "id": "completely-wise-mongrel", "keepers": null, "length": 3, "prefix": null, "separator": "-" }, "sensitive_attributes": [], "private": "bnVsbA==" } ] } ] }
To store the secret in encrypted, we need to provide a pgp_key "keybase:bogotobogo" via variables.tf:
... variable "pgp_key" { description = "Either a base-64 encoded PGP public key, or a keybase username in the form keybase:username. Used to encrypt the password and the access key on output to the console." default = "keybase:bogotobogo" }
Note that to provide the pgp_key, we need to install keybase in our local machine and create an account (in this case with a username, "bogotobogo". Then, do the following:
$ keybase pgp gen Enter your real name, which will be publicly visible in your new key: bogotobogo Enter a public email address for your key: kihyuck.hong@gmail.com Enter another email address (orwhen done): When exporting to the GnuPG keychain, encrypt private keys with a passphrase? [Y/n] n ▶ INFO PGP User ID: bogotobogo [primary] ▶ INFO Generating primary key (4096 bits) ▶ INFO Generating encryption subkey (4096 bits) ▶ INFO Generated new PGP key: ▶ INFO user: bogotobogo ▶ INFO 4096-bit RSA key, ID 54BDF021CED7EC95, created 2021-05-27
Then, run terraform:
$ terraform apply --auto-approve ... 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:::completely-wise-mongrel-bucket" } ] } EOT secret = <sensitive>
Now the tf state has an encrypted secret:
$ cat terraform.tfstate ... { "mode": "managed", "type": "aws_iam_access_key", "name": "my_access_key", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 0, "attributes": { "encrypted_secret": "wcFMA7YvMLjoVTeDARAAR2VdmYhdu0ywb2SAphmDPcezHjLkNfgqg7pBoj13W3AVV6b2iPysW4s/tVrCPUOBow98VfYcLVHFDMMRcLT44uNYbT4OJiMx7vCz1z6NOHnUI/Zo2LGerXBLVjmp+ebQm/MKtPgHNROGgLB3Wu93RGJu1vBPIrjND2rDB6codLH++ySzUjk4TSSTQnvblCr6IF8Bh9USl0fUNCS2+CLZJ+Bji8iDeCCieKH+g1k/Jd3NwYk35ro8ToKDZJpXWA5gakls1XZP5N5MnbBh+3JGvvJry9A4XO5zmK1cDHkAWOGjd8/gTVDNnRWcLWHgxoJX9G+a4BDdI0Kb8NgOoqq/CDqAOScJ2B1i/sTx0w97fQM4R9EuWyngfd1bxcUUIYz7aA0QrdnSlDEKZrySXbGkcIhAy+Hw99UGCKqmvxBBRjDeX5Cuvk6+0fksXHJzCmtl5R2hdfSIJJ6reUeFb6N6ol0Gbe6XggBIn6HP4vWJ4H/GREw3oDZacM0NfD+FKIACbJ4FtvsBpXiMBUSf5FAqZ5SgW4XqXqqLmvztQKnBjeM6Cl5HB332Oavt9WMAavQAAArFknF2sx7M8HeivIxvR5JknczL3hpdHWsUPCvtVNbR1i0CIDtsMhL/pAYNDCj5Y7XNffrWYD2pnVxDJ6ZIjr+26iXb+zHjHa2wm7feiKjS4AHk5FeQx4OrhSLvUHqbJ6WIUuF1eOAc4Izh0wnga+Ig2UXZ4CLlJ/iS1iBeuz090VUhxtdNIIipqOIisEqXxneHiL3UfU7gbOOsN1kqKaPEw+B45IfhlcCnjqqQG0jFrCA5LZXipjMeM+EWkAA=", "id": "AKIAQV57YS3YEL2P2NMK", "key_fingerprint": "c5a5eb34cca30a3d8734c36f54bdf021ced7ec95", "pgp_key": "keybase:bogotobogo", "secret": null, "ses_smtp_password_v4": "BGZdLT0eD5p61no/7NcrD6KSMv1eC7WdCJdpFpjyefp/", "status": "Active", "user": "new_user" }, "sensitive_attributes": [], "private": "bnVsbA==", "dependencies": [ "aws_iam_user.new_user" ] } ] }, ...
To decrypt the secret:
$ terraform output -raw secret | base64 --decode | keybase pgp decrypt ceU17b8HenZRNg46WvWG4ha7dwTFwYm6IVFqU4k7
Terraform
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization