编程语言
首页 > 编程语言> > 使用CloudFront、S3和Terraform在AWS中托管静态网站

使用CloudFront、S3和Terraform在AWS中托管静态网站

作者:互联网

在你开始之前

我想强调以下几点,如果你来自敏捷的背景(谁不是嗯?)说这些是我们的用户故事

 

设置东西

在我们开始之前,我们将建立我们的地球环境。在Terraform中,维护您的状态文件安全至关重要。我个人更喜欢Terraform Cloud,因为配置很简单,我们不需要担心CI/CD(类似GitOps)的设置。

首先,您需要在这里创建一个Terraform云帐户(它是免费的)。然后创建一个项目并设置一个工作流程,此时,您需要有一个GitHub帐户(或来自任何VCS提供商)来维护您的基础设施代码。我不打算展示这是怎么回事,因为这非常简单。

如果您已正确设置Terraform工作区,您应该能够看到在GitHub帐户中配置的Webhook。如果您使用的是GitHub以外的VCS,则需要手动设置此网络钩子。

您可以按照这个来设置后端。

如果您参考GitHub repo,config.remote.tfbackend描述我的远程后端配置。在这种情况下,我正在使用CLI输入来配置后端。

terraform init -backend-config=config.remote.tfbackend

让我们快速了解一下我们提供商的配置。

# provider.tf
terraform {
  required_version = "~> 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.7"
    }
  }
  backend "remote" {}
}

provider "aws" {
  region = var.aws_region
}

由于我们已经设置了远程后端并征服了提供商配置,我们可以开始编写代码。

S3水桶

有关本节,请参阅s3.tf。

# S3 bucket for website.
resource "aws_s3_bucket" "blog_assets" {
  bucket = var.bucket_name

  tags = var.common_tags
}

# S3 Bucket Policy Association
resource "aws_s3_bucket_policy" "assets_bucket_cloudfront_policy_association" {
  bucket = aws_s3_bucket.blog_assets.id
  policy = data.aws_iam_policy_document.s3_bucket_policy.json
}

# S3 bucket website configuration 
resource "aws_s3_bucket_website_configuration" "assets_bucket_website" {
  bucket = aws_s3_bucket.blog_assets.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "404.html"
  }
}

# S3 bucket ACL
resource "aws_s3_bucket_acl" "assets_bucket_acl" {
  bucket     = aws_s3_bucket.blog_assets.id
  acl        = "private"
  depends_on = [aws_s3_bucket_ownership_controls.assets_bucket_acl_ownership]
}

# S3 bucket CORS configuration
resource "aws_s3_bucket_cors_configuration" "assets_bucket_cors" {
  bucket = aws_s3_bucket.blog_assets.id
  cors_rule {
    allowed_headers = ["Authorization", "Content-Length"]
    allowed_methods = ["GET", "POST"]
    allowed_origins = ["https://www.${var.domain_name}", "https://${var.domain_name}"]
    max_age_seconds = 3000
  }
}

# Set Bucket Object ownership
resource "aws_s3_bucket_ownership_controls" "assets_bucket_acl_ownership" {
  bucket = aws_s3_bucket.blog_assets.id
  rule {
    object_ownership = "BucketOwnerPreferred"
  }
  depends_on = [aws_s3_bucket_public_access_block.assets_bucket_public_access]
}

# Block public access to the bucket
resource "aws_s3_bucket_public_access_block" "assets_bucket_public_access" {
  bucket                  = aws_s3_bucket.blog_assets.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

这里有几件事需要注意,

让我们看看政策文件。参见data.tf

# S3 Bucket Policy to Associate with the S3 Bucket
data "aws_iam_policy_document" "s3_bucket_policy" {

  # Deployer User access to S3 bucket
  statement {
    sid    = "DeployerUser"
    effect = "Allow"

    actions = [
      "s3:ListBucket"
    ]

    principals {
      type        = "AWS"
      identifiers = [aws_iam_user.pipeline_deployment_user.arn]
    }

    resources = [
      "arn:aws:s3:::${var.bucket_name}"
    ]
  }

  # CloudFront access to S3 bucket
  statement {
    sid    = "CloudFront"
    effect = "Allow"

    actions = [
      "s3:GetObject",
      "s3:ListBucket"
    ]

    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }

    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"

      values = [
        "${aws_cloudfront_distribution.blog_distribution.arn}"
      ]
    }

    resources = [
      "arn:aws:s3:::${var.bucket_name}",
      "arn:aws:s3:::${var.bucket_name}/*"
    ]
  }
}

在这里,我们指定了两个不同的语句。

  1. 让管道部署器用户列出存储桶
  2. 读取S3存储桶内容的CloudFront服务

需要注意的是,我们将使用Origin Access Control(OAC)而不是传统的OAI(Origin Access Identity)来向CloudFront提供对S3存储桶内容的访问。

# cloudfront.tf
resource "aws_cloudfront_origin_access_control" "blog_distribution_origin_access" {
  name                              = "blog_distribution_origin_access"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

看这里

CloudFront

对于本文,我们将将CloudFront配置保持在最低限度。当我们在边缘函数配置Lambda时,让我们发现更多。

aws_cloudfront_distribution

在这里,我们指定了起源配置,因为大多数都是不言自明的,我不打算去解释每个配置。但我认为我们可能需要对SSL认证配置和功能关联的解释。

SSL配置

如果我们参考存储库,我的证书设置有点像“自带证书”,但我已经为电子邮件或DNS挑战验证整理了一个配置。

请参阅acm.tf。对于电子邮件验证,请使用以下内容。

# SSL Certificate
resource "aws_acm_certificate" "ssl_certificate" {
  provider                  = aws.acm_provider
  domain_name               = var.domain_name
  subject_alternative_names = ["*.${var.domain_name}"]
  validation_method         = "EMAIL"

  tags = var.common_tags

  lifecycle {
    create_before_destroy = true
  }
}

功能关联

如果你看到src/astro.js,我在那里有一个小小的边缘函数,这是不言自明的。一点小小的坦白,我的博客正在使用Astro框架,所以我从这里的文档中获取了边缘函数的代码。

// src/astro.js
function handler(event) {
  var request = event.request;
  var uri = request.uri;

  // Check whether the URI is missing a file name.
  if (uri.endsWith("/")) {
    request.uri += "index.html";
  }
  // Check whether the URI is missing a file extension.
  else if (!uri.includes(".")) {
    request.uri += "/index.html";
  }

  return request;
}
# Edge Functions
resource "aws_cloudfront_function" "astro_default_edge_function" {
  name    = "default_edge_function"
  runtime = "cloudfront-js-1.0"
  comment = "CloudFront Functions for Astro"
  publish = true
  code    = file("src/astro.js")
}

IAM

在部署期间,部署者用户需要:

  1. 将构建工件推送到S3桶
  2. 是否使CloudFront失效

如果您浏览该文件,您将很好地了解这是如何设置的。

# IAM Policy for put S3 objects
resource "aws_iam_policy" "allow_s3_put_policy" {
  name        = "allow_aws_s3_put"
  description = "Allow Pipeline Deployment to put objects in S3"
  policy      = data.aws_iam_policy_document.allow_aws_s3_put.json
}

# IAM policy for cloudfront to invalidate cache
resource "aws_iam_policy" "allow_cloudfront_invalidations_policy" {
  name        = "allow_cloudfront_invalidate"
  description = "Allow pipeline user to create CloudFront invalidation"
  policy      = data.aws_iam_policy_document.allow_cloudfront_invalidate.json
}

# IAM User group for Pipeline Deployment
resource "aws_iam_group" "pipeline_deployment_group" {
  name = "${var.domain_name}_deployment_group"
}

# IAM Policy attachment for Pipeline Deployment - S3 PUT
resource "aws_iam_group_policy_attachment" "s3_put_group_policy_attachment" {
  group      = aws_iam_group.pipeline_deployment_group.name
  policy_arn = aws_iam_policy.allow_s3_put_policy.arn
}

# IAM Policy attachment for Pipeline Deployment - CloudFront Invalidation
resource "aws_iam_group_policy_attachment" "cloudfront_invalidation_group_policy_attachment" {
  group      = aws_iam_group.pipeline_deployment_group.name
  policy_arn = aws_iam_policy.allow_cloudfront_invalidations_policy.arn
}

# IAM User for Pipeline Deployment
resource "aws_iam_user" "pipeline_deployment_user" {
  name = "${var.domain_name}_deployer"
}

# IAM User group membership for Pipeline Deployment
resource "aws_iam_group_membership" "deployment_group_membership" {
  name = "pipeline_deployment_group_membership"
  users = [
    aws_iam_user.pipeline_deployment_user.name
  ]
  group = aws_iam_group.pipeline_deployment_group.name
}

对于前面提到的每个用例,我们将有两份政策文件,

  1. CloudFront的IAM策略使缓存无效
# data.tf: IAM policy for CloudFront to invalidate cache
data "aws_iam_policy_document" "allow_cloudfront_invalidate" {
  statement {
    sid    = "AllowCloudFrontInvalidation"
    effect = "Allow"

    actions = [
      "cloudfront:CreateInvalidation",
      "cloudfront:GetInvalidation",
      "cloudfront:ListInvalidations"
    ]

    resources = [
      "${aws_cloudfront_distribution.blog_distribution.arn}"
    ]
  }
}
  1. 管理S3对象的IAM策略
# data.tf: IAM Policy for put S3 objects
data "aws_iam_policy_document" "allow_aws_s3_put" {
  statement {
    sid    = "AllowS3Put"
    effect = "Allow"

    actions = [
      "s3:GetObject*",
      "s3:GetBucket*",
      "s3:List*",
      "s3:DeleteObject*",
      "s3:PutObject",
      "s3:PutObjectLegalHold",
      "s3:PutObjectRetention",
      "s3:PutObjectTagging",
      "s3:PutObjectVersionTagging",
      "s3:Abort*"
    ]

    resources = [
      "arn:aws:s3:::${var.bucket_name}/*"
    ]
  }
}

由于它们非常通用,我不会深入挖掘,但在这里我们将这两个策略附加到部署者组调用{var.domain_name}_deployment_group,然后创建一个名为${var.domain_name}_deployer的用户并将其添加到组中。

变量

我一直在使用几个变量,就我而言,我使用terraform.tfvars文件来设置存储桶名称和域名。由于我对共享我的AWS帐户ID不太满意,我已从之前创建的证书中复制了证书ARN,并将其保存到TF_VAR_ssl_certificate_arn。您可以在Terraform Cloud中配置这些变量。

⚠️不要忘记设置您的AWS访问密钥对。

Terraform Vars

根据您的配置,您可以使用CLI应用Terraform计划,或使用Terraform云执行计划。

部署环境后,您可能需要使用AWS控制台手动创建IAM密钥对,并在CI/CD管道中使用它

标签:AWS CloudFront,S3,Terraform
来源: