diff --git a/config/_default/params.toml b/config/_default/params.toml index 4eaa1ad..107ffa7 100644 --- a/config/_default/params.toml +++ b/config/_default/params.toml @@ -34,6 +34,6 @@ showLastUpdated = false # set Twitter handles for Twitter cards # see https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started#card-and-content-attribution # do not include @ - creator = "" - site = "" + creator = "alejandr0angul0" + site = "alejandr0angul0" diff --git a/content/posts/hosting-static-site-aws.md b/content/posts/hosting-static-site-aws.md index f674745..2140ff5 100644 --- a/content/posts/hosting-static-site-aws.md +++ b/content/posts/hosting-static-site-aws.md @@ -1,13 +1,319 @@ +++ -title = "How to host a static site on AWS" +title = "Hosting a Static Site on AWS" date = "2020-07-17T12:31:32-07:00" author = "alejandro" -cover = "pldfdf" -tags = ["a", "b"] -keywords = ["", ""] -description = "test" +tags = ["info-dump"] +keywords = ["AWS", "S3", "CloudFront", "Route 53"] showFullContent = false -draft = true +++ -content +I bought a domain name on a whim recently and felt like I had to justify my purchase by building a site to make use of it. In a +past life I might have used WordPress or Django but I was feeling especially lazy. At first I was going to use GitHub Pages but +that seemed too easy. I haven't really touched AWS before and I felt like I might as well make this into a learning opportunity. +As you can probably tell by the title, I ended up using AWS to host a static site. This post documents the steps I took to get +this working. These steps were compiled after I had everything working so it's possible there may be some typos. If you stumbled +upon this and have a question on a step, feel free to reach out (you can find my contact info on the [about +page](/about#contact)). + +## Configuring AWS CLI + +Log on to the AWS console and create an IAM user with administrator access. Keep the user's credentials safe since this user has +full access to your AWS account. Install the aws cli utility and run `aws configure` and use the admin user's credentials that +were just generated. It's not necessary but I also went ahead and set my region to `us-east-1`. + +## Setting up S3 Bucket + +I didn't already have an AWS account (or at least not one that I had the password to) so I fired up `https://aws.amazon.com/` and +created an account. From there, I went to [the AWS console](https://s3.console.aws.amazon.com/) and created a new bucket with the +same name as my domain name (this part is important). So if your domain name is `supercool.tld` your bucket name would also be +`supercool.tld`. Once the bucket is created, static website hosting needs to be enabled. Set the index document value to +`index.html` even though there's nothing in the bucket yet. + +An S3 bucket is used to house the site's static files. + +```bash +aws s3 mb s3:// +``` + +The bucket also has to be configured for static website hosting and the files should be public. Prepare a json file to give anyone +read permissions to objects in the bucket. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": "*", + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::/*" + } + ] +} +``` + +```bash +aws s3api put-bucket-policy --bucket --policy file:// +aws s3 website s3:// --index-document index.html +``` + +## Setting up Route 53 + +Create a hosted zone in AWS. A hosted zone contains the configuration (A records, CNAME, records, etc.) for a domain. + +```bash +aws route53 create-hosted-zone --name --caller-reference $(date -Ins) +``` + +Caller reference just needs to be a unique string so we use the current datetime (with nanosecond precision). Now that a hosted +zone is setup, we'll need to get it's zone ID so we can set up a certificate. + +```bash +aws route53 list-hosted-zones +``` + +## Setting up Certificate + +A cerificate is needed for https support. To do so a cert has to be requeted from AWS's aptly named AWS Certificate Manager (ACM). +Once a request is in, domain ownership needs to be validated (AWS can't be giving out certs for just any domain). Validation can +be done through DNS or email. Email validation requires controlling an email address like `admin@suprecool.tld` and clicking a +link in an email sent to it. DNS validation requires adding a CNAME record in a hosted zone. + +ACM certs are automatically renewed as long as the domain validation does not expire. Email validation has to be revalidated every +825 days (about every two years). For DNS validation, this just means leaving the CNAME record up. DNS validation is a bit more +straightforward so we'll use that. Run the following command and make note of the ARN, it'll be needed later. + +```bash +aws acm request-certificate --domain-name --validation-method DNS +``` + +ACM will provide the CNAME data expected for validation. Run the command below and look at `DomainValidationOptions` for the +expected values. + +```bash +aws acm describe-certificate --certificate-arn +``` + +Prepare a json file to add the required CNAME. + +```json +{ + "Changes": [{ + "Action": "CREATE", + "ResourceRecordSet": { + "Name": "", + "Type": "CNAME", + "TTL": 300, + "ResourceRecords": [{ + "Value": "" + }] + } + }] +} +``` + +```bash +aws route53 change-resource-record-sets --hosted-zone-id --change-batch-file file:// +``` + +It'll take a bit for ACM to validate your new CNAME record. But once it's validated you'll be able to see the certificate by +running the following: + +```bash +aws acm get-certificate --certificate-arn +``` + +## Setting up CloudFront + +Since S3 website endpoints don't support HTTPS, we'll configure CloudFront (AWS's CDN service) to serve up our files. The template +for the JSON required is below. The region name is whatever was configured when `aws configure` was ran earlier. + +{{}} +{ + "Aliases": { + "Quantity": 1, + "Items": [ + "" + ] + }, + "DefaultRootObject": "index.html", + "Origins": { + "Quantity": 1, + "Items": [ + { + "Id": "S3-Website-.s3-website-.amazonaws.com", + "DomainName": ".s3-website-.amazonaws.com", + "OriginPath": "", + "CustomHeaders": { + "Quantity": 0 + }, + "CustomOriginConfig": { + "HTTPPort": 80, + "HTTPSPort": 443, + "OriginProtocolPolicy": "http-only", + "OriginSslProtocols": { + "Quantity": 3, + "Items": [ + "TLSv1", + "TLSv1.1", + "TLSv1.2" + ] + }, + "OriginReadTimeout": 30, + "OriginKeepaliveTimeout": 5 + }, + "ConnectionAttempts": 3, + "ConnectionTimeout": 10 + } + ] + }, + "OriginGroups": { + "Quantity": 0 + }, + "DefaultCacheBehavior": { + "TargetOriginId": "S3-Website-.s3-website-.amazonaws.com", + "ForwardedValues": { + "QueryString": false, + "Cookies": { + "Forward": "none" + }, + "Headers": { + "Quantity": 0 + }, + "QueryStringCacheKeys": { + "Quantity": 0 + } + }, + "TrustedSigners": { + "Enabled": false, + "Quantity": 0 + }, + "ViewerProtocolPolicy": "redirect-to-https", + "MinTTL": 0, + "AllowedMethods": { + "Quantity": 2, + "Items": [ + "HEAD", + "GET" + ], + "CachedMethods": { + "Quantity": 2, + "Items": [ + "HEAD", + "GET" + ] + } + }, + "SmoothStreaming": false, + "DefaultTTL": 86400, + "MaxTTL": 31536000, + "Compress": false, + "LambdaFunctionAssociations": { + "Quantity": 0 + }, + "FieldLevelEncryptionId": "" + }, + "CacheBehaviors": { + "Quantity": 0 + }, + "CustomErrorResponses": { + "Quantity": 1, + "Items": [ + { + "ErrorCode": 404, + "ResponsePagePath": "/404.html", + "ResponseCode": "404", + "ErrorCachingMinTTL": 60 + } + ] + }, + "Comment": "", + "Logging": { + "Enabled": false, + "IncludeCookies": false, + "Bucket": "", + "Prefix": "" + }, + "PriceClass": "PriceClass_All", + "Enabled": true, + "ViewerCertificate": { + "ACMCertificateArn": "", + "SSLSupportMethod": "sni-only", + "MinimumProtocolVersion": "TLSv1.2_2018", + "Certificate": "", + "CertificateSource": "acm" + }, + "Restrictions": { + "GeoRestriction": { + "RestrictionType": "none", + "Quantity": 0 + } + }, + "WebACLId": "", + "HttpVersion": "http2", + "IsIPV6Enabled": true +} +{{}} + +```bash +aws create-distribution --distribution-config file:// +``` + +## Finish Setting up Route 53 + +Now that that CloudFront is configured we can add an A record to our DNS config. Prepare a JSON file for the request. Make sure to +include the trailing `.` for the `Name` and `DNSName` values. Speaking of `DNSName`, run the following to get the CloudFront +distribution's domain name: + +```bash +aws cloudfront list-distributions | grep DomainName | head -n1 +``` + +The above command only works correctly if you only have one CloudFront distribution configured in your account. If you have more +than one distribution, log in to the AWS console and grab the domain name from the distribution that way. + +Prepare a json file for the Route 53 request. Fun fact: `HostedZoneId` is hardcoded for CloudFront distributions. + +```json +{ + "Changes": [{ + "Action": "CREATE", + "ResourceRecordSet": { + "Name": ".", + "Type": "A", + "AliasTarget": { + "HostedZoneId": "Z2FDTNDATAQYW2", + "DNSName": ".", + "EvaluateTargetHealth": false + } + } + }] +} +``` + +```bash +aws route53 change-resource-record-sets --hosted-zone-id --change-batch-file file:// +``` + +## Upload Files + +Everything should be ready now, we just need to add some files. Create a sample index file and uptload it to test. + +```html +

It works!

+``` + +Let's also create a sample 404 error page file. + +```html +

Page does not exist

+``` + +```bash +aws s3 cp s3:///index.html +aws s3 cp s3:///404.html +``` + +Fire up your site and cross your fingers. If all went well you should see the `It works!` message when you point your broweser to +your URL and a page does not exist message when you try accessing something like `/foo`. +