cloudposse / terraform-aws-cloudfront-s3-cdn

Terraform module to easily provision CloudFront CDN backed by an S3 origin
https://cloudposse.com/accelerate
Apache License 2.0
274 stars 247 forks source link

IAM Continuously Updates Between Apply #226

Closed lgants closed 2 years ago

lgants commented 2 years ago

Slack Community

Describe the Bug

The IAM permissions are repeatedly updated in-place on each apply. The permission changes alternate between:

# module.s3_bucket_origin_django.aws_s3_bucket_policy.default[0] will be updated in-place
  ~ resource "aws_s3_bucket_policy" "default" {
        id     = "dev-origin-project"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Action    = "s3:GetObject" -> "s3:PutObject"
                      + Condition = {
                          + StringNotEquals = {
                              + s3:x-amz-server-side-encryption = [
                                  + "AES256",
                                ]
                            }
                        }
                      ~ Effect    = "Allow" -> "Deny"
                      ~ Principal = {
                          - AWS = "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ABCDEFGHIJKLMN"
                        } -> "*"
                      ~ Sid       = "S3GetObjectForCloudFront" -> "DenyIncorrectEncryptionHeader"
                        # (1 unchanged element hidden)
                    },
                  ~ {
                      ~ Action    = "s3:ListBucket" -> "s3:PutObject"
                      + Condition = {
                          + Null = {
                              + s3:x-amz-server-side-encryption = [
                                  + "true",
                                ]
                            }
                        }
                      ~ Effect    = "Allow" -> "Deny"
                      ~ Principal = {
                          - AWS = "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ABCDEFGHIJKLMN"
                        } -> "*"
                      ~ Resource  = "arn:aws:s3:::dev-origin-project" -> "arn:aws:s3:::dev-origin-project/*"
                      ~ Sid       = "S3ListBucketForCloudFront" -> "DenyUnEncryptedObjectUploads"
                    },
                  ~ {
                      ~ Condition = {
                          ~ Bool = {
                              ~ aws:SecureTransport = "false" -> [
                                  + "false",
                                ]
                            }
                        }
                        # (5 unchanged elements hidden)
                    },
                  ~ {
                      ~ Action    = [
                          - "s3:PutObjectAcl",
                            "s3:PutObject",
                          - "s3:ListBucketMultipartUploads",
                            "s3:ListBucket",
                          - "s3:GetObjectAcl",
                            "s3:GetObject",
                            "s3:GetBucketLocation",
                          - "s3:DeleteObject",
                          - "s3:AbortMultipartUpload",
                        ]
                      ~ Principal = {
                          ~ AWS = "arn:aws:iam::012345678901:user/dev-origin-project" -> "arn:aws:iam::012345678901:user/john.doe"
                        }
                      ~ Resource  = [
                          - "arn:aws:s3:::dev-origin-project/static/*",
                          - "arn:aws:s3:::dev-origin-project/media/*",
                            "arn:aws:s3:::dev-origin-project/*",
                            # (1 unchanged element hidden)
                        ]
                      ~ Sid       = "" -> "AllowPrivilegedPrincipal[0]"
                        # (1 unchanged element hidden)
                    },
                ]
                # (1 unchanged element hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }
# module.cloudfront_s3_cdn_django.aws_s3_bucket_policy.default[0] will be updated in-place
  ~ resource "aws_s3_bucket_policy" "default" {
        id     = "dev-origin-project"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Action    = "s3:PutObject" -> "s3:GetObject"
                      - Condition = {
                          - StringNotEquals = {
                              - s3:x-amz-server-side-encryption = "AES256"
                            }
                        } -> null
                      ~ Effect    = "Deny" -> "Allow"
                      ~ Principal = "*" -> {
                          + AWS = "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ABCDEFGHIJKLMN"
                        }
                      ~ Sid       = "DenyIncorrectEncryptionHeader" -> "S3GetObjectForCloudFront"
                        # (1 unchanged element hidden)
                    },
                  ~ {
                      ~ Action    = "s3:PutObject" -> "s3:ListBucket"
                      - Condition = {
                          - Null = {
                              - s3:x-amz-server-side-encryption = "true"
                            }
                        } -> null
                      ~ Effect    = "Deny" -> "Allow"
                      ~ Principal = "*" -> {
                          + AWS = "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ABCDEFGHIJKLMN"
                        }
                      ~ Resource  = "arn:aws:s3:::dev-origin-project*" -> "arn:aws:s3:::dev-origin-project"
                      ~ Sid       = "DenyUnEncryptedObjectUploads" -> "S3ListBucketForCloudFront"
                    },
                  ~ {
                      ~ Condition = {
                          ~ Bool = {
                              ~ aws:SecureTransport = "false" -> [
                                  + "false",
                                ]
                            }
                        }
                        # (5 unchanged elements hidden)
                    },
                  ~ {
                      ~ Action    = [
                          + "s3:PutObjectAcl",
                            "s3:PutObject",
                          + "s3:ListBucketMultipartUploads",
                            "s3:ListBucket",
                          + "s3:GetObjectAcl",
                            "s3:GetObject",
                            "s3:GetBucketLocation",
                          + "s3:DeleteObject",
                          + "s3:AbortMultipartUpload",
                        ]
                      ~ Principal = {
                          ~ AWS = "arn:aws:iam::012345678901:user/john.doe" -> "arn:aws:iam::012345678901:user/dev-origin-project"
                        }
                      ~ Resource  = [
                          + "arn:aws:s3:::dev-origin-project/static/*",
                          + "arn:aws:s3:::dev-origin-project/media/*",
                            "arn:aws:s3:::dev-origin-project/*",
                            # (1 unchanged element hidden)
                        ]
                      ~ Sid       = "AllowPrivilegedPrincipal[0]" -> ""
                        # (1 unchanged element hidden)
                    },
                ]
                # (1 unchanged element hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

Expected Behavior

The IAM policies should not update between each apply when no changes are made.

Steps to Reproduce

The relevant portions of my terraform configuration are below:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.74.3"
    }
  }

  required_version = ">= 1.1.7"
}

module "cloudfront_s3_cdn_django" {
  source  = "cloudposse/cloudfront-s3-cdn/aws"
  version = "0.82.3"

  acm_certificate_arn      = module.acm_request_certificate_cdn_django.arn
  aliases                  = ["cdn.${var.dns_zone_name[terraform.workspace]}"] 
  allowed_methods          = ["GET", "HEAD"]
  cloudfront_access_log_bucket_name     = module.s3_bucket_cdn_logs.bucket_id
  cloudfront_access_log_create_bucket   = false
  cloudfront_access_log_prefix          = "logs/"
  default_root_object       = null
  deployment_actions        = [
    "s3:PutObject",
    "s3:PutObjectAcl",
    "s3:GetObject",
    "s3:GetObjectAcl",
    "s3:DeleteObject",
    "s3:ListBucket",
    "s3:ListBucketMultipartUploads",
    "s3:GetBucketLocation",
    "s3:AbortMultipartUpload"
  ]
  deployment_principal_arns = {
    "${module.s3_bucket_origin_django.user_arn}" = ["", "static/", "media/"]
  }
  dns_alias_enabled         = true
  environment               = terraform.workspace
  forward_header_values     = [
    "Access-Control-Request-Headers",
    "Access-Control-Request-Method",
    "Origin",
    "X-Correlation-ID"
  ]
  geo_restriction_locations = ["US"]
  geo_restriction_type      = "whitelist"
  name                      = "cloudfront"
  origin_bucket             = module.s3_bucket_origin_django.bucket_id
  origin_force_destroy      = true
  parent_zone_name          = "${var.dns_zone_name[terraform.workspace]}"
  web_acl_id                = module.waf_cloudfront.arn

  depends_on                = [
    module.acm_request_certificate_cdn_django,
  ]
}

Environment (please complete the following information):

Anything that will help us triage the bug will help. Here are some ideas:

lgants commented 2 years ago

The issue appears to stem from passing in a custom S3 origin bucket. I was using the cloudposse S3 bucket module and included properties on that bucket (e.g. privileged_principal_arns = true, allow_ssl_requests_only = true, and allow_encrypted_uploads_only) that appear to conflict with the policies that this module attaches to the origin bucket. I don't think it's necessarily a bug, but wanted to share findings here in case anyone else ran into the same issue.