diff --git a/README.md b/README.md index 230fe01..333f67b 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cloudformation/static-web-site-distribution/Makefile b/cloudformation/static-web-site-distribution/Makefile index e4aea4a..fcae5e2 100644 --- a/cloudformation/static-web-site-distribution/Makefile +++ b/cloudformation/static-web-site-distribution/Makefile @@ -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 \ --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 \ No newline at end of file + 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 + +upload: ## Upload index.html + aws s3 cp ./index.html s3://content-bucket-rainbow-spa \ No newline at end of file diff --git a/cloudformation/static-web-site-distribution/README.md b/cloudformation/static-web-site-distribution/README.md index ee79d28..0edf082 100644 --- a/cloudformation/static-web-site-distribution/README.md +++ b/cloudformation/static-web-site-distribution/README.md @@ -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). @@ -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) \ No newline at end of file +- [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:// --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://.cloudfront.net +``` + +![spa](./spa.png) \ No newline at end of file diff --git a/cloudformation/static-web-site-distribution/index.html b/cloudformation/static-web-site-distribution/index.html new file mode 100644 index 0000000..7b5104a --- /dev/null +++ b/cloudformation/static-web-site-distribution/index.html @@ -0,0 +1,12 @@ + + + + + + Simple Page + + +

Hello, World!

+

This is a simple HTML page.

+ + \ No newline at end of file diff --git a/cloudformation/static-web-site-distribution/parameters.json b/cloudformation/static-web-site-distribution/parameters.json index a4bbbbe..a5a5504 100644 --- a/cloudformation/static-web-site-distribution/parameters.json +++ b/cloudformation/static-web-site-distribution/parameters.json @@ -1,6 +1,6 @@ [ { "ParameterKey" : "ContentBucketName", - "ParameterValue" : "ContentBucketNameRainbow" + "ParameterValue" : "content-bucket-rainbow-spa" } ] \ No newline at end of file diff --git a/cloudformation/static-web-site-distribution/spa.png b/cloudformation/static-web-site-distribution/spa.png new file mode 100644 index 0000000..b9b88af Binary files /dev/null and b/cloudformation/static-web-site-distribution/spa.png differ diff --git a/cloudformation/static-web-site-distribution/template.yml b/cloudformation/static-web-site-distribution/template.yml index de1187e..8f355b9 100644 --- a/cloudformation/static-web-site-distribution/template.yml +++ b/cloudformation/static-web-site-distribution/template.yml @@ -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: @@ -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 @@ -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: @@ -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