Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Static Web Application Distribution #74

Merged
merged 1 commit into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ The s3hub command provides following features:
|[Lambda with API Gateway](./cloudformation/lambda-with-api-gw/README.md)|✅|100%|
|[Daily Cost Notification](./cloudformation/daily-cost-notification/README.md)|✅|100%|
|[CloudWatch Real User Monitoring (RUM)](./cloudformation/cloudwatch-rum/README.md)|✅|100%|
|[Static Web Application Distribution](./cloudformation/static-web-site-distribution/README.md)|✅|100%|


## LICENSE
Expand Down
9 changes: 6 additions & 3 deletions cloudformation/static-web-site-distribution/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ help: ## Show this help message

test-deploy: ## Deploy CloudFormation Template to localstack
@echo "Deploying S3 and CloudFront Template"
aws cloudformation create-stack --endpoint-url "http://localhost:4566" --stack-name "static-web-site-distribution" \
aws cloudformation create-stack --endpoint-url "http://localhost:4566" --stack-name "static-web-site-distribution" --region ap-northeast-1 \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The addition of --region ap-northeast-1 parameter ensures that the deployment is targeted to the specified AWS region. This is a good practice for clarity and control over deployment locations. However, consider making the region a variable to enhance flexibility and reusability of the Makefile across different AWS regions.

-	aws cloudformation create-stack --endpoint-url "http://localhost:4566" --stack-name "static-web-site-distribution"  --region ap-northeast-1 \
+	AWS_REGION ?= ap-northeast-1
+	aws cloudformation create-stack --endpoint-url "http://localhost:4566" --stack-name "static-web-site-distribution"  --region $(AWS_REGION) \

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
aws cloudformation create-stack --endpoint-url "http://localhost:4566" --stack-name "static-web-site-distribution" --region ap-northeast-1 \
AWS_REGION ?= ap-northeast-1
aws cloudformation create-stack --endpoint-url "http://localhost:4566" --stack-name "static-web-site-distribution" --region $(AWS_REGION) \

--template-body "file://template.yml" --parameters "file://parameters.json" --capabilities CAPABILITY_NAMED_IAM

deploy: ## Deploy CloudFormation Template
@echo "Deploying S3 and CloudFront Template"
aws cloudformation create-stack --stack-name "static-web-site-distribution" \
--template-body "file://template.yml" --parameters "file://parameters.json" --capabilities CAPABILITY_NAMED_IAM
aws cloudformation create-stack --stack-name "static-web-site-distribution" --region ap-northeast-1 \
Comment on lines +15 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same refinement suggestion applies here for the deploy target. Using a variable for the AWS region increases the script's flexibility and usability across different environments.

-	aws cloudformation create-stack --stack-name "static-web-site-distribution"  --region ap-northeast-1 \
+	AWS_REGION ?= ap-northeast-1
+	aws cloudformation create-stack --stack-name "static-web-site-distribution"  --region $(AWS_REGION) \

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
aws cloudformation create-stack --stack-name "static-web-site-distribution" --region ap-northeast-1 \
--template-body "file://template.yml" --parameters "file://parameters.json" --capabilities CAPABILITY_NAMED_IAM
AWS_REGION ?= ap-northeast-1
aws cloudformation create-stack --stack-name "static-web-site-distribution" --region $(AWS_REGION) \
--template-body "file://template.yml" --parameters "file://parameters.json" --capabilities CAPABILITY_NAMED_IAM

--template-body "file://template.yml" --parameters "file://parameters.json" --capabilities CAPABILITY_NAMED_IAM

upload: ## Upload index.html
Comment on lines +18 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upload target is a valuable addition for automating the upload process of index.html to an S3 bucket. However, the bucket name content-bucket-rainbow-spa is hardcoded. Consider parameterizing the bucket name to enhance the script's flexibility and to avoid potential conflicts or errors in environments with different bucket names.

-	aws s3 cp ./index.html s3://content-bucket-rainbow-spa
+	BUCKET_NAME ?= content-bucket-rainbow-spa
+	aws s3 cp ./index.html s3://$(BUCKET_NAME)

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
upload: ## Upload index.html
aws s3 cp ./index.html s3://content-bucket-rainbow-spa
upload: ## Upload index.html
BUCKET_NAME ?= content-bucket-rainbow-spa
aws s3 cp ./index.html s3://$(BUCKET_NAME)

aws s3 cp ./index.html s3://content-bucket-rainbow-spa
32 changes: 30 additions & 2 deletions cloudformation/static-web-site-distribution/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Static Web Site Distribution With CloudFront and S3
## Static Web Application Distribution With CloudFront and S3
### Overview
The simplest way to deploy the static website is to store the content in Amazon S3 (Simple Storage Service) and distribute it using CloudFront (Content Delivery Network).

Expand Down Expand Up @@ -37,4 +37,32 @@ OAC also enhances security measures by supporting shorter credential durations a
3. Compliance with Legal Requirements: Some industries or legal requirements may mandate the retention of access logs and their accessibility when needed to comply with regulations.

#### CloudFront Cache
- [Understanding AWS CloudFront Caching: A Guide for Beginners](https://aws.plainenglish.io/understanding-aws-cloudfront-caching-a-guide-for-beginners-ce0169d3c724)
- [Understanding AWS CloudFront Caching: A Guide for Beginners](https://aws.plainenglish.io/understanding-aws-cloudfront-caching-a-guide-for-beginners-ce0169d3c724)

### How to deploy
> [!NOTE]
> Before running `make deploy`, ensure you have configured AWS credentials and set the correct region. Otherwise, you use single sign-on (SSO).

```shell
$ make deploy
```

If you want to upload the content of the static website to S3, you can use the following command:

```shell
aws s3 cp ./static-website s3://<your-bucket-name> --recursive
```

For example, if you upload the index.html file to the S3 bucket, you can use the following command:

```shell
aws s3 cp ./index.html s3://content-bucket-rainbow-spa
```

After deployment, you can access the static website. The URL syntax for the CloudFront distribution is as follows:

```
https://<distribution-id>.cloudfront.net
```

![spa](./spa.png)
12 changes: 12 additions & 0 deletions cloudformation/static-web-site-distribution/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Page</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>This is a simple HTML page.</p>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{
"ParameterKey" : "ContentBucketName",
"ParameterValue" : "ContentBucketNameRainbow"
"ParameterValue" : "content-bucket-rainbow-spa"
}
]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
176 changes: 108 additions & 68 deletions cloudformation/static-web-site-distribution/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,9 @@ Resources:
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
Properties:
BucketName: !Ref ContentBucketName
BucketName: !Sub "${ContentBucketName}"
VersioningConfiguration:
Status: Enabled
ObjectLockConfiguration:
ObjectLockEnabled: Enabled
Rule:
DefaultRetention:
Days: 1
Mode: GOVERNANCE
ObjectLockEnabled: true
OwnershipControls:
Rules:
Expand All @@ -33,16 +27,6 @@ Resources:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
ReplicationConfiguration:
Role: !GetAtt ContentBucketReplicationRole.Arn
Rules:
- Destination:
Bucket: !Ref ContentS3BucketReplica
Status: Enabled
Prefix: "replicated/"
LoggingConfiguration:
DestinationBucketName: !Ref ContentS3BucketReplica
LogFilePrefix: "logs/"

ContentBucketPolicy:
Type: AWS::S3::BucketPolicy
Expand All @@ -51,38 +35,47 @@ Resources:
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action: "s3:*"
- Sid: "DenyNonSecureConnections"
Action: "s3:*"
Effect: Deny
Principal: "*"
Resource: "*"
Resource:
- !Sub "arn:aws:s3:::${ContentBucketName}/*"
- !Sub "arn:aws:s3:::${ContentBucketName}"
Condition:
Bool:
"aws:SecureTransport": false

ContentBucketReplicationRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${AWS::StackName}-bucket-source-role-${AWS::Region}"
Description: "Role For S3"
Path: "/service/"
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
- Sid: "AllowCloudFrontToGetContent"
Effect: Allow
Principal:
Service:
- s3.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonS3FullAccess"
Service: "cloudfront.amazonaws.com"
Action:
- "s3:GetObject"
- "s3:ListBucket"
Resource:
- !Sub "arn:aws:s3:::${ContentBucketName}/*"
- !Sub "arn:aws:s3:::${ContentBucketName}"
Condition:
Bool:
"aws:SecureTransport": true

ContentS3BucketReplica:
LogBucket:
Type: "AWS::S3::Bucket"
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
Properties:
BucketName: "content-s3-bucket-replica"
BucketName: !Sub "${ContentBucketName}-log"
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
ObjectLockConfiguration:
ObjectLockEnabled: Enabled
Rule:
Expand All @@ -92,35 +85,82 @@ Resources:
ObjectLockEnabled: true
OwnershipControls:
Rules:
- ObjectOwnership: BucketOwnerEnforced
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Metadata:
guard:
SuppressedRules:
- S3_BUCKET_REPLICATION_ENABLED
- S3_BUCKET_LOGGING_ENABLED
- ObjectOwnership: ObjectWriter

ContentBucketReplicaPolicy:
Type: AWS::S3::BucketPolicy
CloufFrontDistribution:
Type: "AWS::CloudFront::Distribution"
Properties:
Bucket: !Ref ContentS3BucketReplica
PolicyDocument:
Version: "2012-10-17"
Statement:
- Action: "s3:*"
Effect: Deny
Principal: "*"
Resource: "*"
Condition:
Bool:
"aws:SecureTransport": false
DistributionConfig:
Comment: "CloudFront Distribution"
Origins:
- DomainName: !GetAtt ContentBucket.RegionalDomainName
Id: "S3Origin"
OriginAccessControlId: !Ref CloudFrontOriginAccessControl
S3OriginConfig:
OriginAccessIdentity: ""
DefaultRootObject: "index.html"
Enabled: true
DefaultCacheBehavior:
TargetOriginId: "S3Origin"
CachePolicyId: !Ref OriginCachePolicy
OriginRequestPolicyId: !Ref OriginRequestPolicy
AllowedMethods:
- GET
- HEAD
- OPTIONS
ForwardedValues:
QueryString: false
ViewerProtocolPolicy: "redirect-to-https"
DefaultTTL: 1
MaxTTL: 1
MinTTL: 1
PriceClass: "PriceClass_100"
Logging:
Bucket: !GetAtt LogBucket.DomainName
IncludeCookies: false
Prefix: "logs/"
HttpVersion: "http2and3"
IPV6Enabled: true
ViewerCertificate:
CloudFrontDefaultCertificate: true

CloudFrontOriginAccessControl:
Type: "AWS::CloudFront::OriginAccessControl"
Properties:
OriginAccessControlConfig:
Name: "Origin Accress Control for S3 bucket"
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4

OriginRequestPolicy:
Type: "AWS::CloudFront::OriginRequestPolicy"
Properties:
OriginRequestPolicyConfig:
Name: !Sub "${AWS::StackName}-cloudfront-request-policy"
Comment: "Origin Request Policy for S3 bucket"
CookiesConfig:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: none

OriginCachePolicy:
Type: "AWS::CloudFront::CachePolicy"
Properties:
CachePolicyConfig:
Name: !Sub "${AWS::StackName}-cloudfront-cache-policy"
Comment: "Cache Policy for S3 bucket"
DefaultTTL: 1
MaxTTL: 1
MinTTL: 1
ParametersInCacheKeyAndForwardedToOrigin:
CookiesConfig:
CookieBehavior: none
HeadersConfig:
HeaderBehavior: none
QueryStringsConfig:
QueryStringBehavior: none
EnableAcceptEncodingGzip: true
EnableAcceptEncodingBrotli: true
Loading