From 20c53451469b4b49c8bfbee15c0dce118b60e0c1 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Sat, 27 Mar 2021 10:54:52 -0500 Subject: [PATCH 01/11] Create optional sync user --- README.md | 9 ++++++++- main.tf | 44 ++++++++++++++++++++++++++++++++++++++++++++ outputs.tf | 12 ++++++++++++ variables.tf | 6 ++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de6f4ca..a079cd4 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,9 @@ No modules. | Name | Type | |------|------| | [aws_cloudfront_distribution.site_cloudfront_distribution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution) | resource | +| [aws_iam_access_key.content_sync_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource | +| [aws_iam_user.content_sync](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource | +| [aws_iam_user_policy.content_sync_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user_policy) | resource | | [aws_route53_record.site_tld_record](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | | [aws_route53_record.site_www_record](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | | [aws_route53_zone.primary_site_tld](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_zone) | resource | @@ -100,6 +103,7 @@ No modules. | [cloudfront\_price\_class](#input\_cloudfront\_price\_class) | Price class for Cloudfront. | `string` | `"PriceClass_100"` | no | | [content\_bucket\_versioning](#input\_content\_bucket\_versioning) | Defines whether or not to set versioning on the content bucket. | `bool` | `true` | no | | [create\_cloudfront\_distribution](#input\_create\_cloudfront\_distribution) | Defines whether or not to create a CloudFront distribution for the S3 bucket. | `bool` | `true` | no | +| [create\_content\_sync\_user](#input\_create\_content\_sync\_user) | Optionally create an IAM user and access keys to sync the content bucket. Note that this will store access information in your state file. Protect it accordingly. | `bool` | `false` | no | | [create\_public\_dns\_site\_record](#input\_create\_public\_dns\_site\_record) | If set to true, creates a public DNS record in your site\_tld hosted zone. If you do not already have a hosted zone for this TLD, you should set create\_public\_dns\_zone to true. Otherwise, this will try to create a record in an existing zone or fail. | `string` | `"true"` | no | | [create\_public\_dns\_www\_record](#input\_create\_public\_dns\_www\_record) | Defines whether or not to create a WWW DNS record for the site. | `bool` | `false` | no | | [create\_public\_dns\_zone](#input\_create\_public\_dns\_zone) | If set to true, creates a public hosted zone in Route53 for your site. | `string` | `"false"` | no | @@ -114,5 +118,8 @@ No modules. ## Outputs -No outputs. +| Name | Description | +|------|-------------| +| [content\_sync\_access\_key](#output\_content\_sync\_access\_key) | Access key ID of the optional content sync user. | +| [content\_sync\_access\_secret](#output\_content\_sync\_access\_secret) | Secret Access key of the optional content sync user. This is marked as sensitive and will not show in plan output, but be aware that it is stored in your state file. Encrypt accordingly. | \ No newline at end of file diff --git a/main.tf b/main.tf index 25d8c20..7068a69 100644 --- a/main.tf +++ b/main.tf @@ -221,3 +221,47 @@ resource "aws_route53_record" "site_www_record" { records = [var.site_tld] } +# Optional content sync user + +resource "aws_iam_user" "content_sync" { + count = var.create_content_sync_user == true ? 1 : 0 + name = "${var.site_tld}-content-sync-user" + + /* tags = { + tag-key = "tag-value" + } */ +} + +resource "aws_iam_access_key" "content_sync_key" { + count = var.create_content_sync_user == true ? 1 : 0 + user = aws_iam_user.content_sync.name +} + +resource "aws_iam_user_policy" "content_sync_policy" { + count = var.create_content_sync_user == true ? 1 : 0 + name = "${var.site_tld}-content-sync-policy" + user = aws_iam_user.content_sync.name + + policy = < Date: Sat, 27 Mar 2021 11:01:26 -0500 Subject: [PATCH 02/11] Proper conditional on output --- outputs.tf | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/outputs.tf b/outputs.tf index 4b2ac0e..6eeaa0b 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,12 +1,10 @@ output "content_sync_access_key" { - count = var.create_content_sync_user == true ? 1 : 0 - value = aws_iam_access_key.content_sync_key[0].id + value = var.create_content_sync_user ? aws_iam_access_key.content_sync_key[0].id : "" description = "Access key ID of the optional content sync user." } output "content_sync_access_secret" { - count = var.create_content_sync_user == true ? 1 : 0 - value = aws_iam_access_key.content_sync_key[0].secret + value = var.create_content_sync_user ? aws_iam_access_key.content_sync_key[0].secret : "" sensitive = true description = "Secret Access key of the optional content sync user. This is marked as sensitive and will not show in plan output, but be aware that it is stored in your state file. Encrypt accordingly." } From ec17d42cf05704bade377ebc7d6aec55026df40d Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Sat, 27 Mar 2021 11:03:17 -0500 Subject: [PATCH 03/11] Proper reference to user count --- main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.tf b/main.tf index 7068a69..8746e4d 100644 --- a/main.tf +++ b/main.tf @@ -234,13 +234,13 @@ resource "aws_iam_user" "content_sync" { resource "aws_iam_access_key" "content_sync_key" { count = var.create_content_sync_user == true ? 1 : 0 - user = aws_iam_user.content_sync.name + user = aws_iam_user.content_sync[count.index].name } resource "aws_iam_user_policy" "content_sync_policy" { count = var.create_content_sync_user == true ? 1 : 0 name = "${var.site_tld}-content-sync-policy" - user = aws_iam_user.content_sync.name + user = aws_iam_user.content_sync[count.index].name policy = < Date: Sat, 27 Mar 2021 11:07:41 -0500 Subject: [PATCH 04/11] Set version types --- README.md | 10 ++++------ variables.tf | 27 +++++++++++++++------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a079cd4..e67fb86 100644 --- a/README.md +++ b/README.md @@ -99,22 +99,20 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [acm\_site\_certificate\_arn](#input\_acm\_site\_certificate\_arn) | ARN of an ACM certificate to use for https on the CloudFront distribution. | `any` | n/a | yes | +| [acm\_site\_certificate\_arn](#input\_acm\_site\_certificate\_arn) | ARN of an ACM certificate to use for https on the CloudFront distribution. | `string` | n/a | yes | | [cloudfront\_price\_class](#input\_cloudfront\_price\_class) | Price class for Cloudfront. | `string` | `"PriceClass_100"` | no | | [content\_bucket\_versioning](#input\_content\_bucket\_versioning) | Defines whether or not to set versioning on the content bucket. | `bool` | `true` | no | | [create\_cloudfront\_distribution](#input\_create\_cloudfront\_distribution) | Defines whether or not to create a CloudFront distribution for the S3 bucket. | `bool` | `true` | no | | [create\_content\_sync\_user](#input\_create\_content\_sync\_user) | Optionally create an IAM user and access keys to sync the content bucket. Note that this will store access information in your state file. Protect it accordingly. | `bool` | `false` | no | -| [create\_public\_dns\_site\_record](#input\_create\_public\_dns\_site\_record) | If set to true, creates a public DNS record in your site\_tld hosted zone. If you do not already have a hosted zone for this TLD, you should set create\_public\_dns\_zone to true. Otherwise, this will try to create a record in an existing zone or fail. | `string` | `"true"` | no | +| [create\_public\_dns\_site\_record](#input\_create\_public\_dns\_site\_record) | If set to true, creates a public DNS record in your site\_tld hosted zone. If you do not already have a hosted zone for this TLD, you should set create\_public\_dns\_zone to true. Otherwise, this will try to create a record in an existing zone or fail. | `bool` | `true` | no | | [create\_public\_dns\_www\_record](#input\_create\_public\_dns\_www\_record) | Defines whether or not to create a WWW DNS record for the site. | `bool` | `false` | no | -| [create\_public\_dns\_zone](#input\_create\_public\_dns\_zone) | If set to true, creates a public hosted zone in Route53 for your site. | `string` | `"false"` | no | -| [create\_sns\_topic](#input\_create\_sns\_topic) | Defines whether or not to create an SNS topic for notifications about events. | `bool` | `false` | no | +| [create\_public\_dns\_zone](#input\_create\_public\_dns\_zone) | If set to true, creates a public hosted zone in Route53 for your site. | `bool` | `false` | no | | [create\_www\_redirect\_bucket](#input\_create\_www\_redirect\_bucket) | Defines whether or not to create a www redirect S3 bucket. | `bool` | `true` | no | | [error\_page\_object](#input\_error\_page\_object) | The error page object for the Cloudfront/S3 distribution. | `string` | `"404.html"` | no | | [log\_include\_cookies](#input\_log\_include\_cookies) | Defines whether or not CloudFront should log cookies. | `bool` | `false` | no | | [root\_page\_object](#input\_root\_page\_object) | The root page object for the Cloudfront/S3 distribution. | `string` | `"index.html"` | no | | [site\_region](#input\_site\_region) | Region in which to provision the site. Default: us-east-1 | `string` | `"us-east-1"` | no | -| [site\_tld](#input\_site\_tld) | TLD of the website you want to create. A bucket will be created that is named this. Note that the module will error out if this bucket already exists in AWS. Example: example.com | `any` | n/a | yes | -| [sns\_topic\_name](#input\_sns\_topic\_name) | Name for the SNS topic. | `string` | `"website-notifications"` | no | +| [site\_tld](#input\_site\_tld) | TLD of the website you want to create. A bucket will be created that is named this. Note that the module will error out if this bucket already exists in AWS. Example: example.com | `string` | n/a | yes | ## Outputs diff --git a/variables.tf b/variables.tf index c64eab7..ec8d92d 100644 --- a/variables.tf +++ b/variables.tf @@ -1,75 +1,78 @@ # Creation flags first variable "site_region" { + type = string description = "Region in which to provision the site. Default: us-east-1" default = "us-east-1" } variable "create_www_redirect_bucket" { + type = bool description = "Defines whether or not to create a www redirect S3 bucket." default = true } variable "content_bucket_versioning" { + type = bool description = "Defines whether or not to set versioning on the content bucket." default = true } variable "create_cloudfront_distribution" { + type = bool description = "Defines whether or not to create a CloudFront distribution for the S3 bucket." default = true } variable "log_include_cookies" { + type = bool description = "Defines whether or not CloudFront should log cookies." default = false } -variable "create_sns_topic" { - description = "Defines whether or not to create an SNS topic for notifications about events." - default = false -} - -variable "sns_topic_name" { - description = "Name for the SNS topic." - default = "website-notifications" -} - variable "site_tld" { + type = string description = "TLD of the website you want to create. A bucket will be created that is named this. Note that the module will error out if this bucket already exists in AWS. Example: example.com" } variable "create_public_dns_zone" { + type = bool description = "If set to true, creates a public hosted zone in Route53 for your site." - default = "false" + default = false } variable "create_public_dns_site_record" { + type = bool description = "If set to true, creates a public DNS record in your site_tld hosted zone. If you do not already have a hosted zone for this TLD, you should set create_public_dns_zone to true. Otherwise, this will try to create a record in an existing zone or fail." - default = "true" + default = true } variable "create_public_dns_www_record" { + type = bool description = "Defines whether or not to create a WWW DNS record for the site." default = false } variable "root_page_object" { + type = string description = "The root page object for the Cloudfront/S3 distribution." default = "index.html" } variable "error_page_object" { + type = string description = "The error page object for the Cloudfront/S3 distribution." default = "404.html" } variable "cloudfront_price_class" { + type = string description = "Price class for Cloudfront." default = "PriceClass_100" } variable "acm_site_certificate_arn" { + type = string description = "ARN of an ACM certificate to use for https on the CloudFront distribution." } From 1217e10c08e9549cc9f79406b5ed7679009a3a81 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Sat, 27 Mar 2021 11:13:02 -0500 Subject: [PATCH 05/11] Fix bad comma --- main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 8746e4d..d164d30 100644 --- a/main.tf +++ b/main.tf @@ -258,7 +258,7 @@ resource "aws_iam_user_policy" "content_sync_policy" { "Effect": "Allow", "Resource": [ "arn:aws:s3:::${random_uuid.random_bucket_name.result}/*", - "arn:aws:s3:::${random_uuid.random_bucket_name.result}*", + "arn:aws:s3:::${random_uuid.random_bucket_name.result}*" ] } ] From 8f9c35e81be6838fc43476b28213a0eb12bd5c96 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Sat, 27 Mar 2021 11:19:57 -0500 Subject: [PATCH 06/11] That bool switch doh --- main.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.tf b/main.tf index d164d30..e8cb913 100644 --- a/main.tf +++ b/main.tf @@ -79,7 +79,7 @@ resource "aws_s3_bucket_public_access_block" "content_bucket_block" { # S3 bucket for www redirect (optional) resource "aws_s3_bucket" "site_www_redirect" { - count = var.create_www_redirect_bucket == "true" ? 1 : 0 + count = var.create_www_redirect_bucket == true ? 1 : 0 bucket = "www.${random_uuid.random_bucket_name.result}" # region = var.site_region acl = "private" @@ -190,7 +190,7 @@ resource "aws_cloudfront_distribution" "site_cloudfront_distribution" { # DNS entry pointing to public site - optional resource "aws_route53_zone" "primary_site_tld" { - count = var.create_public_dns_zone == "true" ? 1 : 0 + count = var.create_public_dns_zone == true ? 1 : 0 name = var.site_tld } @@ -199,7 +199,7 @@ data "aws_route53_zone" "site_tld_selected" { } resource "aws_route53_record" "site_tld_record" { - count = var.create_public_dns_site_record == "true" ? 1 : 0 + count = var.create_public_dns_site_record == true ? 1 : 0 zone_id = data.aws_route53_zone.site_tld_selected.zone_id name = "${var.site_tld}." type = "A" @@ -212,7 +212,7 @@ resource "aws_route53_record" "site_tld_record" { } resource "aws_route53_record" "site_www_record" { - count = var.create_public_dns_www_record == "true" ? 1 : 0 + count = var.create_public_dns_www_record == true ? 1 : 0 zone_id = data.aws_route53_zone.site_tld_selected.zone_id name = "www" type = "CNAME" From 8340ad962ea93447fe3ce8c044d5714c0fef4bc1 Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Sat, 27 Mar 2021 11:37:33 -0500 Subject: [PATCH 07/11] Fix IAM policy --- main.tf | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/main.tf b/main.tf index e8cb913..214d63c 100644 --- a/main.tf +++ b/main.tf @@ -244,24 +244,38 @@ resource "aws_iam_user_policy" "content_sync_policy" { policy = < Date: Sat, 27 Mar 2021 12:07:47 -0500 Subject: [PATCH 08/11] Add content bucket output --- README.md | 1 + outputs.tf | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index e67fb86..15f42f0 100644 --- a/README.md +++ b/README.md @@ -120,4 +120,5 @@ No modules. |------|-------------| | [content\_sync\_access\_key](#output\_content\_sync\_access\_key) | Access key ID of the optional content sync user. | | [content\_sync\_access\_secret](#output\_content\_sync\_access\_secret) | Secret Access key of the optional content sync user. This is marked as sensitive and will not show in plan output, but be aware that it is stored in your state file. Encrypt accordingly. | +| [content\_sync\_bucket\_name](#output\_content\_sync\_bucket\_name) | Bucket name that contains the content for the site. | \ No newline at end of file diff --git a/outputs.tf b/outputs.tf index 6eeaa0b..6c9629a 100644 --- a/outputs.tf +++ b/outputs.tf @@ -8,3 +8,8 @@ output "content_sync_access_secret" { sensitive = true description = "Secret Access key of the optional content sync user. This is marked as sensitive and will not show in plan output, but be aware that it is stored in your state file. Encrypt accordingly." } + +output "content_sync_bucket_name" { + value = random_uuid.random_bucket_name.result + description = "Bucket name that contains the content for the site." +} From a97c88176e8f625b5453fb7d248177e33fb8eafe Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Sat, 27 Mar 2021 13:20:13 -0500 Subject: [PATCH 09/11] Add CloudFront distribution ID output --- README.md | 1 + outputs.tf | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 15f42f0..4bdb39a 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ No modules. | Name | Description | |------|-------------| +| [cloudfront\_distribution\_id](#output\_cloudfront\_distribution\_id) | CloudFront distribution ID. | | [content\_sync\_access\_key](#output\_content\_sync\_access\_key) | Access key ID of the optional content sync user. | | [content\_sync\_access\_secret](#output\_content\_sync\_access\_secret) | Secret Access key of the optional content sync user. This is marked as sensitive and will not show in plan output, but be aware that it is stored in your state file. Encrypt accordingly. | | [content\_sync\_bucket\_name](#output\_content\_sync\_bucket\_name) | Bucket name that contains the content for the site. | diff --git a/outputs.tf b/outputs.tf index 6c9629a..1457988 100644 --- a/outputs.tf +++ b/outputs.tf @@ -13,3 +13,8 @@ output "content_sync_bucket_name" { value = random_uuid.random_bucket_name.result description = "Bucket name that contains the content for the site." } + +output "cloudfront_distribution_id" { + value = aws_cloudfront_distribution.site_cloudfront_distribution.id + description = "CloudFront distribution ID." +} From 6ed60e22d1f3654bfb9af8585e817c78fc92787b Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Sat, 27 Mar 2021 13:36:13 -0500 Subject: [PATCH 10/11] Add CloudFront permissions for invalidations --- main.tf | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/main.tf b/main.tf index 214d63c..889258a 100644 --- a/main.tf +++ b/main.tf @@ -246,6 +246,28 @@ resource "aws_iam_user_policy" "content_sync_policy" { { "Version": "2012-10-17", "Statement": [ + { + "Sid": "CloudFrontStuff", + "Effect": "Allow", + "Action": [ + "acm:ListCertificates", + "cloudfront:GetDistribution", + "cloudfront:GetStreamingDistribution", + "cloudfront:GetDistributionConfig", + "cloudfront:ListDistributions", + "cloudfront:ListCloudFrontOriginAccessIdentities", + "cloudfront:CreateInvalidation", + "cloudfront:GetInvalidation", + "cloudfront:ListInvalidations", + "elasticloadbalancing:DescribeLoadBalancers", + "iam:ListServerCertificates", + "sns:ListSubscriptionsByTopic", + "sns:ListTopics", + "waf:GetWebACL", + "waf:ListWebACLs" + ], + Resource": "${aws_cloudfront_distribution.site_cloudfront_distribution.arn}" + } { "Sid": "BucketStuff", "Effect": "Allow", From acd46d2fbad82617037284e067f3a879c8d0894d Mon Sep 17 00:00:00 2001 From: Jason Miller Date: Sat, 27 Mar 2021 13:39:56 -0500 Subject: [PATCH 11/11] Fix typos in policy --- main.tf | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/main.tf b/main.tf index 889258a..4e31745 100644 --- a/main.tf +++ b/main.tf @@ -266,37 +266,37 @@ resource "aws_iam_user_policy" "content_sync_policy" { "waf:GetWebACL", "waf:ListWebACLs" ], - Resource": "${aws_cloudfront_distribution.site_cloudfront_distribution.arn}" - } - { - "Sid": "BucketStuff", - "Effect": "Allow", - "Action": [ - "s3:GetBucketTagging", - "s3:ListBucket", - "s3:GetBucketLocation" - ], - "Resource": "arn:aws:s3:::${random_uuid.random_bucket_name.result}" + "Resource": "${aws_cloudfront_distribution.site_cloudfront_distribution.arn}" }, { - "Sid": "ObjectStuff", - "Effect": "Allow", - "Action": [ - "s3:PutObject", - "s3:GetObject", - "s3:DeleteObject" - ], - "Resource": [ - "arn:aws:s3:::${random_uuid.random_bucket_name.result}/*", - "arn:aws:s3:::${random_uuid.random_bucket_name.result}" - ] + "Sid": "BucketStuff", + "Effect": "Allow", + "Action": [ + "s3:GetBucketTagging", + "s3:ListBucket", + "s3:GetBucketLocation" + ], + "Resource": "arn:aws:s3:::${random_uuid.random_bucket_name.result}" }, { - "Sid": "HighLevelStuff", - "Effect": "Allow", - "Action": "s3:ListAllMyBuckets", - "Resource": "*" - } + "Sid": "ObjectStuff", + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::${random_uuid.random_bucket_name.result}/*", + "arn:aws:s3:::${random_uuid.random_bucket_name.result}" + ] + }, + { + "Sid": "HighLevelStuff", + "Effect": "Allow", + "Action": "s3:ListAllMyBuckets", + "Resource": "*" + } ] } EOF