Search code examples
amazon-s3terraformdjango-staticfilesstatic-filesterraform-provider-aws

Configure AWS S3 bucket for Django static files with Terraform


I'm new to Terraform.

I'm trying to configure S3 bucket to serve Django static files.
There should be unrestricted access for HTTP GET requests for these static files but there should also be AWS user - this user account will be used by Django to upload updated static files to S3 bucket.

I've written this:

resource "aws_iam_user" "integrations_lite_staticfiles_s3_bucket_user" {
  name = "Integrations-Lite-staticfiles-user"
}

resource "aws_iam_access_key" "integrations_lite_staticfiles_s3_bucket_user_key" {
  user = "${aws_iam_user.integrations_lite_staticfiles_s3_bucket_user.name}"
}

data "aws_iam_policy_document" "integrations_lite_staticfiles_s3_user_policy" {
  statement {
    effect = "Allow"
    actions = ["s3:*"]
    resources = ["${aws_s3_bucket.integrations_lite_staticfiles_s3_bucket.arn}"]
  }
}

resource "aws_iam_user_policy" "integrations_lite_staticfiles_s3_user_policy" {
  name = "Integrations-Lite-staticfiles-user-policy"
  user = "${aws_iam_user.integrations_lite_staticfiles_s3_bucket_user.name}"
  policy = "${data.aws_iam_policy_document.integrations_lite_staticfiles_s3_user_policy.json}"
}

data "aws_iam_policy_document" "integrations_lite_staticfiles_s3_bucket_policy" {
  "statement" {
    sid = "PublicReadForGetBucketObjects"
    effect = "Allow"
    actions = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.integrations_lite_staticfiles_s3_bucket.arn}"]
    principals {
      identifiers = ["*"]
      type = "AWS"
    }
  }
}

resource "aws_s3_bucket_policy" "integrations_lite_staticfiles_s3_bucket_policy" {
  bucket = "${aws_s3_bucket.integrations_lite_staticfiles_s3_bucket.id}"
  policy = "${data.aws_iam_policy_document.integrations_lite_staticfiles_s3_user_policy.json}"
}

resource "aws_s3_bucket" "integrations_lite_staticfiles_s3_bucket" {
  region = "${var.region}"
  bucket = "integrations-lite-staticfiles"
  acl = "public-read"
  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["PUT","POST"]
    allowed_origins = ["*"]
    expose_headers = ["ETag"]
    max_age_seconds = 3000
  }
  website {
    index_document = "index.html"
  }
}

but terraform apply results in:

* aws_s3_bucket_policy.integrations_lite_staticfiles_s3_bucket_policy: 1 error(s) occurred:

* aws_s3_bucket_policy.integrations_lite_staticfiles_s3_bucket_policy: Error putting S3 policy: MalformedPolicy: Missing required field Principal
    status code: 400, request id: 724BC650DFFCE3B7, host id: ####

However adding principals to aws_s3_bucket_policy.integrations_lite_staticfiles_s3_bucket_policy results in:

Error: aws_s3_bucket_policy.integrations_lite_staticfiles_s3_bucket_policy: : invalid or unknown key: principals

Solution

  • I've found a solution:

    resource "aws_iam_group" "manage-integrations-lite-staticfiles-s3-bucket" {
      name = "Manage-Integrations-Lite-static-files"
    }
    
    resource "aws_iam_user" "manage-integrations-lite-staticfiles-s3-bucket" {
      name = "Manage-Integrations-Lite-static-files"
    }
    
    resource "aws_iam_group_membership" "manage-integrations-lite-staticfiles-s3-bucket" {
      group = "${aws_iam_group.manage-integrations-lite-staticfiles-s3-bucket.name}"
      name = "Manage-Integrations-Lite-static-files"
      users = ["${aws_iam_user.manage-integrations-lite-staticfiles-s3-bucket.name}"]
    }
    
    resource "aws_iam_group_policy" "manage-integrations-lite-staticfiles-s3-bucket" {
      group = "${aws_iam_group.manage-integrations-lite-staticfiles-s3-bucket.name}"
      policy =<<POLICY
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "ManageIntegrationsLiteStaticfilesBucket",
          "Effect": "Allow",
          "Action": "s3:*",
          "Resource": [
              "arn:aws:s3:::integrations-lite-staticfiles",
              "arn:aws:s3:::integrations-lite-staticfiles/*"
          ]
        }
      ]
    }
    POLICY
    }
    
    resource "aws_s3_bucket" "integrations-lite-staticfiles-s3-bucket" {
      region = "${var.region}"
      bucket = "integrations-lite-staticfiles"
      acl = "public-read"
      cors_rule {
        allowed_headers = ["*"]
        allowed_methods = ["GET", "HEAD"]
        allowed_origins = ["*"]
        expose_headers = ["ETag"]
        max_age_seconds = 3000
      }
      website {
        index_document = "index.html"
      }
      policy =<<POLICY
    {
      "Version":"2012-10-17",
      "Statement":[{
        "Sid":"PublicReadGetObject",
        "Effect":"Allow",
        "Principal": "*",
        "Action":["s3:GetObject"],
        "Resource":[
          "arn:aws:s3:::integrations-lite-staticfiles",
          "arn:aws:s3:::integrations-lite-staticfiles/*"
        ]
      }]
    }
    POLICY
    }
    

    Note: I intentionally removed api key part. I prefer to generate key id and secret manually via AWS console.