Deploy A Static Website On AWS In Minutes
Code to Deploy A Website Hosted in An S3 Bucket, A TLS Certificate, and CloudFront Distribution With CloudFormation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
⚙️ A series on Security Automation. The Code.
🔒 Related Stories: Cybersecurity | Penetration Tests | AI
💻 Free Content on Jobs in Cybersecurity | ✉️ Sign up for the Email List
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
TLDR;
To deploy a static website with a CloudFront distribution, a TLS certificate, and a CloudFront distribution you can use this code:
All the posts I wrote while developing it are in the readme.
Run this in CloudShell in a non-production account in AWS CloudShell.
# run these commands in the AWS account
# where you want to deploy the website
cd ~
rm -rf ai-cfn
git clone https://github.com/2ndSightLab/ai-cfn.git
cd ai-cfn/staticweb
chmod 700 deploy.sh
chmod 700 scripts/deploy/route53-tls-cert-validation.sh
./deploy.sh
# run these commands to update the
# name servers where the primary domain exists
# for a non-production domain only
# entering the name servers output by the above script
cd ~
rm -rf ai-cfn
git clone https://github.com/2ndSightLab/ai-cfn.git
cd ai-cfn/staticweb
chmod 700 update-nameservers.sh
./update-nameservers.sh
Answer the questions. More details below.
Note: When I initially wrote this post, wild cards and www domains did not work. They should now. Also, fixed some other bugs and streamlined some code since first publication.
I chronicled creating this code showing how I used Q and Amazon Bedrock, when they helped, failed, or caused problems. See the related section of posts here:
The goal: A script to quickly deploy a static website
I’ve spent the last few days using Amazon Q to try to complete some reusable code to deploy static websites in S3 buckets. I started this post in my series on using batch jobs for security automation.
I never finished that I think probably because the CloudFront template was going to be daunting. I decided to try to use Amazon Q (AI) to see if it made the process any easier. Well, Q was helpful in some ways, time consuming in others, and completely led me to dead ends at some point where I had to figure out another path. I’ll cover that in another post.
For this post, I just want to explain how the use the scripts I produced in part with Amazon Q (generative AI chatbot) and Amazon Bedrock (Amazon’s Generative AI product that lets you choose different models to use in your applications.)
This is non-production code
Now mind you, this is not how you should be deploying in production. For production deployments you’ll want additional security measures such as those I wrote about in this whole series where I explore secure deployments of AWS resources in batch jobs:
What you could do is take this code I’m demonstrating in this post and move it into a batch job in a private network. You could put secrets in Secrets Manager protected with MFA and network restrictions and use those to create a repository and automatically update your website — topics I covered in the above series and the post on Components of a Static Web Site on AWS above.
About the code
The code is in 2nd Sight Lab’s ai-cfn git repository in the staticweb folder.
The file: cloudshell-command.sh has the lines of bash code at the top of this script used to execute the code in AWS CloudShell (or any other command line environment with appropriate credentials and permissions to run the AWS CLI.
The file deploy.sh runs the deployment of the website. It calls all the other scripts in the account where you are deploying the website.
The file update-nameservers.sh updates the name servers in the account where the domain name is registered (if you have registered your domain on AWS or moved your domain to AWS) with the NS records of your hosted zone. (See instructions below.)
What you’ll notice about this code is that I use microtemplates:
There’s for the most part one CloudFormation resource per template. That makes it easier to individually test and reuse templates. It also speeds up redeployments as I’ll show you below.
Along with that I have one script per function or resource. That makes it easier to find the code.
Examples:
The function named check_certificate_exists exists in check-certificate-exists.sh.
The script that deploys the cloudfront distribution resource is in deploy-cloudfront-distribution.sh.
The code is currently in two directories: functions and deploy:
The deploy directory contains scripts that deploy a specific resource.
The functions are functions or blocks of code that perform a specific action related to the name — typically one discrete action or function.
The functions are typically bash functions and where they are not I want to fix that.
How to use the code
Run the script in: cloudshell-command.sh shown below in CloudShell (or similar) in the account where you are deploying the website.
cd ~
rm -rf ai-cfn
git clone https://github.com/2ndSightLab/ai-cfn.git
cd ai-cfn/staticweb
chmod 700 deploy.sh
chmod 700 scripts/deploy-tls-cert-validation.sh
./deploy.sh
The code does the following:
- Removes the repo if it has already been cloned to avoid conflicts.
- Clones the Github repository (2ndSightLab/ai-cfn).
- Moves into the staticweb folder.
- It makes the necessary files executable.
- Executes a bash script named deploy.sh.
That script drives the the whole process, including other scripts that deploy each of the resources required to run your static website.
You will be asked a series of questions when the script runs.
You can choose a region, but note that I have only tested in this in us-east-1. I used that region because regardless of which region you start with the TLS certificate has to be deployed in us-east-1 if you’re using it with S3. The rest of the resources will be deployed in whatever region you specify here.
If you hit enter, the default value will be used.
Next enter a prefix for your CloudFormation stacks. I used my domain name but changed the dots to dashes. I was testing with dev.rainierrhododendrons.com in the first post above so:
That way all your stacks will start with a similar prefix so they are easier to find in the list of stacks in your AWS account.
Next you will be asked if you want to deploy a Route 53 hosted zone.
Type y and hit enter.
Type the domain name for your website:
Enter the type of website you want to deploy — NOTE: I have only tested the basic website. I will fix issues with the others as I test and use them.
Once that stack has deployed, which you can check in the CloudFormation console:
The script will display the following information. Note that it prints out the name server records in the Route 53 hosted zone you just deployed. You will need to update the Name Servers in your account where you registered the domain with those records.
Update the name servers for your domain:
Next you need to update the name servers for your domain or subdomain. These instructions are for updating the name servers for your domain.
Note that my script below is for the primary domain. If you update your name servers and you have other things using that domain that depend on the existing name servers (like email or a production website if you are setting up a subdomain for testing that do not exist in your new hosted zone), you will break things.
I put the script to update the name servers in a separate script because you may need to run that in a different account, depending on where you registered the domain. I recommend locking down domains in a separate account, even when just testing.
More on DNS security here:
The above posts also explain why I am not using a cross-account role in the same session to update the primary domain. If I did use a cross-account role, I would want it to enforce MFA on role switching.
More on dealing with subdomains on Route 53 here:
Go to the AWS account where your domain is registered.
The commands you can run in a spearate account to update your domain name are in: cloudshell-command.sh, similar to the commands to run the first script:
The script you need to run is named: update-nameservers.sh
Run these commands in the AWS account where your domain names are registered to pull the script from GitHub and execute it.
cd ~
rm -rf ai-cfn
git clone https://github.com/2ndSightLab/ai-cfn.git
cd ai-cfn/staticweb
chmod 700 update-nameservers.sh
./update-nameservers.sh
Enter your domain name.
The second prompt will ask you for your name servers.
Copy the name servers:
Paste them in the above prompt and hit enter.
The script will update the name servers to match those in the hosted zone.
The update takes awhile but usually not 48 hours within AWS or if you live in the United States. I don’t know about DNS servers in other parts of the world where the records need to get propagated for this to work.
Return to the website script and hit enter once that script has executed successfully.
Type y and enter to deploy a TLS certificate.
If you want to redeploy a certificate delete any prior certificates first but on your first deployment you can type n here:
Choose 1 for DNS validation:
The script will deploy the certificate, then deploy the DNS validation records in your hosted zone and then the TLS certificate stack will complete.
This takes some time. You can view the stacks in CloudFormation.
Now what I really want to know here from Q or even in the console is specifically why the TLS certificate is failing to completely deploy, when it does.
It could be any number of issues. Initially I had a bug that wasn’t deploying the A records correctly.
There’s a period of time required for all the records to propagate before the certificate process can see the validation records in your hosted zone.
Make sure the DNS records in the events of the TLS certificate:
Match the values passed in as parameters to the validation stack:
You can also go to the AWS Certificate Manager screen, click on the certificate that is in pending status, and check for more information. It may be that you are missing records in your hosted zone that are associated with the certificate, which I will demonstrate below.
Because I don’t want to mess with subdomains (the “dev” in dev.rainierrhododenrons.com) right now I’m going to use the top level domain to demonstrate this script using a different domain.
Run the script up to this point.
Then head over to he account where you registered the domain.
Update name servers:
Enter and follow the prompts to deploy the TLS certificate.
And wait…
Or you can kill the script and skip the TLS certificate deployment:
…and your stack will still keep waiting for things to propagate, and you can continue to deploy the rest of the resources:
Note that I have only tested a distribution and got it working with OAC but OAI might also work now that I fixed the caching issues. AWS recommends using OAC.
Note that it asks you if you want to use OAC and then if you want to deploy OAC. Always choose to use OAC even if you don’t want to deploy it again because otherwise the templates will use OAI (not recommended). See prior posts for more details.
Then basically you can go through all the rest of the resources and choose y to deploy them.
When I get to the CloudFront distribution the stack fails because the TLS certificate is not fully deployed. Now this entire script worked multiple times with another domain so I don’t know why it wouldn’t work with this one. I’ll just presume it’s the time it takes the domains to propagate.
So the reason the certificate did not initially deploy for this particular certificate is because I have a bug.
Alright I’ve fixed a number of things since I first wrote this post.
Some things I know could still be fixed:
- What happens if a certificate is used for multiple subdomains?
- What if we have an existing hosted zone and we want to copy records to the new hosted zone?
- What about mail records, DKIM, DNSSEC, and CAA?
- The logging is not getting set up properly on the CloudFront distribution.
- We could add a WAF, geo-restrictions and other security protections available for CloudFront distributions.
Most importantly — the deployment could be more secure with all the security options explained in this post to help prevent supply chain attacks, ransomware, defacement, data destruction, and unauthorized changes:
Follow for updates.
Teri Radichel | © 2nd Sight Lab 2025
About Teri Radichel:
~~~~~~~~~~~~~~~~~~~~
⭐️ Author: Cybersecurity Books
⭐️ Presentations: Presentations by Teri Radichel
⭐️ Recognition: SANS Award, AWS Security Hero, IANS Faculty
⭐️ Certifications: SANS ~ GSE 240
⭐️ Education: BA Business, Master of Software Engineering, Master of Infosec
⭐️ Company: Penetration Tests, Assessments, Phone Consulting ~ 2nd Sight Lab
Cloud, SAAS, and Application Penetration Testing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
🔒 Request a penetration test
Follow for more stories like this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
❤️ Sign Up my Medium Email List
❤️ Twitter: @teriradichel
❤️ LinkedIn: https://www.linkedin.com/in/teriradichel
❤️ Mastodon: @teriradichel@infosec.exchange
❤️ Facebook: 2nd Sight Lab
❤️ YouTube: @2ndsightlab