This CDK+Python project is designed to support the web administered app model (eg WordPress, Node-RED), where the admin interface is hosted within the same codebase as the front-end application.
To achieve this outcome more securely, this project creates two groups of server hosts - one with admin access and one with only read access. This enables you to restrict all write operations to a server (or servers) that can only be accessed from specific allowed IP addresses.
All the needed component layers (Network, Database, Compute, Load Balancing and CDN layers) for a secure and scalable web app solution on AWS are included.
The solution is built around shared storage, provided by Amazon Elastic File System (EFS). All the compute (EC2) instances mount this file system and use it to share the app code and other content required to make the app work.
For the database layer, you can either create a new database on the Amazon Relational Database Service (RDS), or you can bring an RDS snapshot and its Secrets Manager-hosted credentials. If you have an existing database host, you can also supply your credentials for that in a Secrets Manager secret.
This solution includes two Autoscaling Groups (ASGs) of EC2 instances - one to serve your site to the public and one to allow administration of it. Both are fronted by a single Application Load Balancer (ALB) that has rules configured to conditionally direct traffic to the different ALB Target Groups.
Amazon CloudFront is configured to be the entry point for all traffic, and it is configured to use AWS Web Application Firewall (WAF) with AWS Managed rules designed to block common types of malicious traffic (eg XSS and database injection).
Data in-transit is encrypted through the use of AWS Certificate Manager (ACM) certificates. This project leverages Amazon Route 53 to manage the creation of DNS names and the certificates required to securely access your application.
You can specify the AMI to be used in your application stack, which means you can leverage EC2 Image Builder to pre-configure AMIs with needed software. If you don't need to do that, you can simply supply userdata scripts or commands that will configure the instance before bringing it into service.
To enable this project to be used to deploy multiple different web projects, all configuration is done in the parameters.properties
file. The configuration items are named and annotated with comments that explain their function.
The [default]
stanza sets the default app name and environment that will be deployed if you don't specify them on the command line.
The remaining stanzas are all named by combining valid app
and env
values, eg <app>-<env>
. So if you want to support three different WordPress deployments (for instance) you might specify that app name as wp
and for env you might want dev
, test
and prod
giving you three stanzas wp-dev
, wp-test
and wp-prod
. Each stanza can specify its own regions, accounts, instance types, instance counts, hostnames etc.
To deploy a specific stanza's config, issue a deploy command using the appropriately matching stack name and context variables, eg:
$ cdk deploy wp-dev-network-stack -c env=dev -c app=wp
Refer to the instructions near the bottom of this README to see instructions about installing CDK and running this CDK project.
You will need to bootstrap both the us-east-1
Region and the home region you plan to host your application into, eg
cdk bootstrap aws://123456789012/us-east-1
cdk bootstrap aws://123456789012/ap-southeast-2
where 123456789012
is replaced with your account ID and ap-southeast-2
is replaced with your primary Region.
This is needed to enable the creation of DNS records and certificates for your site. This needs to be manually created via the AWS console and can be achieved by following the instructions here. You can delegate any domain you control to Route 53 and use it with this project. You can also register a domain via Route 53 if you don't currently have one.
Clone the project to your local machine and navigate to the project root. Follow the steps in the Generic CDK instructions to create the Python virtual environment (venv
) and install the dependencies.
Copy the parameters-template.properties
file (in the root folder of the project) to a file called parameters.properties
and save it in the root folder. Open it with a text editor and
- replace
example.com
with the name of the hosted zone you created in the previous step. - replace
192.0.2.0
with your admin IP address (usually the public IP of the computer you are using now).
If you want to restrict public access to your site, change 192.0.2.0/24
to the IP range you want to allow (don't forget to also include your admin IP in CIDR notation (ie include the netmask, as in the example)). You can add multiple allowed CIDR blocks by providing a comma-separated list of allowedIps
.
If you don't want to restrict public access, set allowedIps=*
instead.
If you have forked this project into your own private repository, you can commit the
parameters.properties
file to your repo. To do that, comment out the line in the.gitignore
file.
A CloudFormation Custom Resource is used to do cross-region configuration management. You will need to run the following command in the custom_resource directory to install the needed python package:
cd custom_resource
pip install crhelper -t .
You can read more about this solution in this blog post.
Before you start deploying stacks, decide whether you want to include a data layer as part of this deployment or not. The dbConfig
parameter determines what will happen.
If dbConfig
is:
- left empty - no database will be created and no db credentials will be available in your compute stacks
- set to
instance
- you will get a new RDS instance - set to
cluster
- you will get an Aurora RDS cluster - set to
none
- any previously-created database will be deleted
If you specify either instance
or cluster
you should also configure the other database parameters to match your requirements:
dbEngine
- set the database engine to eithermysql
orpostgres
dbSnapshot
- specify the named snapshot for your databasedbSecret
- if using an existing database, the ARN of the secret where the database credentials and DNS endpoint can be found (see below)dbMajorVersion
the major version of the engine you have chosen - leave blank to get the default versiondbFullVersion
the minor version of the engine you have chosen - leave blank to get the default versiondbInstanceType
the instance type you want (NB these vary by service) - don't prefix withdb.
as CDK will automatically prepend it.- if requesting a cluster,
dbClusterSize
will determine how many Aurora replicas are created
You can choose between mysql
or postgres
for the database engine, and the other settings will be determined by that choice.
Note you will need to use an AMI that has the CLI pre-installed, like Amazon Linux 2, or install the AWS CLI yourself with a user data command If instead of creating a new empty database you want to spin one up from a snapshot, you can supply the snapshot name via the dbSnapshot parameter.
If you create an RDS instance or Aurora cluster as part of this deployment the secret will be created and managed for you.
When you choose to create a new database instance or cluster, credentials are automatically created. These credentials are stored in a Secrets Manager secret and made available to your Compute stack via the db_secret_command
that can be interpolated into your user data commands. It is a single-line bash command that returns the JSON from the AWS CLI command aws secretsmanager get-secret-value
.
Your script can then reference these values like this:
SECRET=$({db_secret_command})
USERNAME=`echo $SECRET | jq -r '.username'`
PASSWORD=`echo $SECRET | jq -r '.password'`
DBNAME=`echo $SECRET | jq -r '.dbname'`
HOST=`echo $SECRET | jq -r '.host'`
If you are creating a database from a snapshot, make sure your Secrets Manager secret and RDS Snapshot are in the target region.
If supplying the secret for an existing database, the secret must be contain at least the following four key-value pairs:
{
"password":"<password>",
"dbname":"wordpress",
"host":"hostname.rds.amazonaws.com",
"username":"admin"
}
The name for the secret must match the following template - the
app
value followed by theenv
value (both in Pascal case), followed by "DatabaseSecret" and whatever other characters you like. Eg for app=wp and env=dev your secret name should beWpDevDatabaseSecret-optionalstuffhere
The commands to deploy the stacks defined in the CDK app are listed below. You can deploy them all individually if you like, by using the specific stack names (these will vary as per the info above), eg:
cdk deploy wp-dev-network-stack -c app=wp -c env=dev
cdk deploy wp-dev-database-stack -c app=wp -c env=dev
cdk deploy wp-dev-compute-stack -c app=wp -c env=dev
cdk deploy wp-dev-cdn-stack -c app=wp -c env=dev
If you are creating a database stack, you will need to deploy the Network and Database stacks first:
cdk deploy wp-dev-network-stack -c app=wp -c env=dev
cdk deploy wp-dev-database-stack -c app=wp -c env=dev
You can then trigger the deployment of the compute stack by deploying:
cdk deploy wp-dev-compute-stack -c app=wp -c env=dev
Once the compute stack is deployed you will be able to
cdk deploy wp-dev-cdn-stack -c env=dev
This one will deploy the global CDN infrastructure to the us-east-1
region.
All the stacks are deployed to the region you specified in your config stanza, apart from the CDN stack, which is deployed to the Global region.
Assuming all your stacks deploy as expected, your site will appear at either subdomain
.hostedZone
(if you specified a value for subdomain
) or appName
-env
.hostedZone
(if you didn't), eg www.example.com
or wp-dev.example.com
.
If you connect via the IP address you configured in the adminIps
configuration, you should be connected to the admin
instance for your site. The admin instances can modify the filesystem so they are where you should do your administration tasks.
Users who connect to your site from an IP not in your allowedIps
list will be connected to your fleet
instances and will be unable to alter the filesystem (eg install plugins, upload media etc).
If you find you need to re-deploy the same app-env combination, manually remove the parameter store items and the replicated Secret created in
us-east-1
. You should also delete thecdk.context.json
file, as it caches values you will be replacing.
The properties file supplied with this project has configurations that will deploy WordPress and Node-RED.
You can use these configurations as-is to deploy your own WordPress or Node-RED instances, or you can create your own config stanzas for other applications, including bespoke applications you have created.
If you try to access wp-admin
from an IP not in the adminIps
list, you should see the following response:
You don't have permission to access this resource.
As this is a Node.js app, the fleet instances need to respond to changes to the flows
files, so nodemon
is used to watch the EFS mount and reload Node-RED when there are changes.
If you deploy this CDK app, but then decide it's not for you, a simple
cdk destroy wp-dev-database-stack -c env=dev -c app=wp
(if you created the db stack) followed by
cdk destroy wp-dev-network-stack -c env=dev -c app=wp
(customised with your env and app names) should destroy all the stacks and everything in them, except for the parameter store items and the replicated Secret that were created in us-east-1
- you will need to remove those manually.
This is a project built using CDK with Python. Install CDK by using the instructions here: https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html - be sure to choose the Python instructions.
The cdk.json
file tells the CDK Toolkit how to execute your app.
This project is set up like a standard Python project. The initialization process also creates a virtualenv within this project, stored under the .venv
directory. To create the virtualenv it assumes that there is a python3
(or python
for Windows) executable in your path with access to the venv
package. If for any reason the automatic creation of the virtualenv fails, you can create the virtualenv manually.
To manually create a virtualenv on MacOS and Linux:
$ python3 -m venv .venv
After the init process completes and the virtualenv is created, you can use the following step to activate your virtualenv.
$ source .venv/bin/activate
If you are a Windows platform, you would activate the virtualenv like this:
% .venv\Scripts\activate.bat
Once the virtualenv is activated, you can install the required dependencies.
$ pip install -r requirements.txt
At this point you can now synthesize the CloudFormation template for this code.
$ cdk synth
To add additional dependencies, for example other CDK libraries, just add them to your setup.py
file and rerun the pip install -r requirements.txt
command.
cdk ls
list all stacks in the appcdk synth
emits the synthesized CloudFormation templatecdk deploy
deploy this stack to your default AWS account/regioncdk diff
compare deployed stack with current statecdk docs
open CDK documentation
Enjoy!