使用CloudFront、S3和Terraform在AWS中托管静态网站
作者:互联网
在你开始之前
我想强调以下几点,如果你来自敏捷的背景(谁不是嗯?)说这些是我们的用户故事
- 我们希望网站托管我们的静态内容
- 内容应安全地存储在S3存储桶中(没有公共访问存储桶)
- 网站的流量应该得到保护(HTTPS)
- 利用CloudFront从存储桶中交付静态内容
- 利用CloudFront边缘功能来支持多页路由
- 该网站有自己的存储库和发布周期
- 配置IAM用户将内容部署到S3存储桶,并使CloudFront缓存无效
设置东西
在我们开始之前,我们将建立我们的地球环境。在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
}
这里有几件事需要注意,
- S3存储桶网站配置:在这里,我们设置索引文档和错误文档的路径。您甚至可以拉出此路径并将其定义为变量。
- S3桶CORS配置:对于我们的场景来说,CORS配置是一个相当通用的配置。
- 阻止公众对桶的访问:我们不希望我们的桶对象公开可用。
- 设置桶对象所有权:为了避免复杂情况,我们将对象所有权设置为桶所有者。
- S3存储桶策略:这里我们指的是在data.tf中指定的S3存储桶策略。
让我们看看政策文件。参见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}/*"
]
}
}
在这里,我们指定了两个不同的语句。
- 让管道部署器用户列出存储桶
- 读取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
在部署期间,部署者用户需要:
- 将构建工件推送到S3桶
- 是否使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
}
对于前面提到的每个用例,我们将有两份政策文件,
- 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}"
]
}
}
- 管理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访问密钥对。
根据您的配置,您可以使用CLI应用Terraform计划,或使用Terraform云执行计划。
部署环境后,您可能需要使用AWS控制台手动创建IAM密钥对,并在CI/CD管道中使用它
标签:AWS CloudFront,S3,Terraform 来源: