diff --git a/README.md b/README.md index 889faff..2fcf73b 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,12 @@ These apps and services are located in the `apps` directory. In there you can fi | :-------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | | [Start and Stop EC2 Instance](./apps/start-stop-ec2-instances/) | This is a Serverless Framework based project to start and stop EC2 instances based on a schedule. | +## Best practices + +- We recommend using automated code scanning tools to improve security and quality of the code. This pattern was scanned using [Checkov](https://www.checkov.io/) - a static code analysis tool for infrastructure-as-code. It scans cloud infrastructure code defined using Terraform, CloudFormation, Kubernetes, Helm, ARM Templates and Serverless framework platforms and detects security and compliance misconfigurations. +- Additionally, we recommend at minimum to perform basic validation and formatting checks using `terraform validate` and `terraform fmt -check -recursive` Terraform commands. +- It’s a good practice to add automated tests for infrastructure code. You can refer to this Terraform Blog post to learn more about different approaches to testing Terraform code. + ## Contributing We appreciate contributions from the open-source community. Any contributions you make are **truly appreciated**. Please refer to our [contribution guidelines](./CONTRIBUTING.md) for more information. diff --git a/modules/bastion/README.md b/modules/bastion/README.md index bcbbfb6..b49bb2a 100644 --- a/modules/bastion/README.md +++ b/modules/bastion/README.md @@ -18,16 +18,40 @@ module "bastion" { } ``` -## EC2 Instance Provisioning +## Architecture -The created EC2 instance is provisioned using [cloud-init](https://cloudinit.readthedocs.io/en/latest/). The following steps are performed: +### Target technology stack -- Install the latest version of Docker -- Setup AWS CloudWatch Logs agent -- Setup SSH access for the specified key pair -- Setup a user account for the specified key pair -- Setup a user account for the specified IAM instance profile -- Install AWS Session Manager Agent +- VPC with at least one private subnet +- 3 VPC (Interface) Endpoints with corresponding security groups and security group rules +- EC2 instance (t3.nano) with Ubuntu 24.04 installed with associated: + - IAM role + - IAM instance profile + - security group + +### Target architecture + +![Architecture](docs/secure_bastion_host.png) + +We deploy single isolated EC2 instance (t3.nano) with Ubuntu 24.04 installed inside of the private subnet in created +VPC. Instance does not expose any ports and does not have public IP assigned. It is fully isolated from the Internet and +uses VPC (Interface) Endpoints to communicate with AWS services (Systems Manager and EC2). You are going to assume an +IAM role with associated IAM policies that grant you required privileges to authenticate, authorize and connect to EC2 +instance. +Following [the least privilege principle](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege), +created EC2 instance does not have access to other resources in your AWS account. You have to explicitly grant it by +assigning IAM policies to IAM role used by EC2 instance. + +### Automation and scale + +This pattern can be adopted to be a part of a larger infrastructure code and deployed in an automated way using CI/CD +tools. You can modify the code to change the type of deployed EC2 instance to adjust its parameters to your specific +needs. + +### EC2 Instance Provisioning + +The created EC2 instance is provisioned using [cloud-init](https://cloudinit.readthedocs.io/en/latest/). You can +customize the instance provisioning by modifying the content of the `cloud_init` template file at `templates/cloud_init.tpl`. ## Connecting to the Bastion Host Using Session Manager @@ -35,7 +59,8 @@ The created EC2 instance is provisioned using [cloud-init](https://cloudinit.rea Ensure the following prerequisites are met: -1. **Session Manager Plugin**: Ensure the Session Manager Plugin is installed on your local machine. Check the [AWS Session Manager Plugin Installation Guide](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) for more information. +1. **AWS Command Line Interface v2**: Ensure the AWS CLI is installed on your local machine. Check the [AWS CLI Installation Guide](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) for more information. +2. **Session Manager Plugin**: Ensure the Session Manager Plugin is installed on your local machine. Check the [AWS Session Manager Plugin Installation Guide](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html) for more information. ### Setup Local Environment diff --git a/modules/bastion/docs/secure_bastion_host.png b/modules/bastion/docs/secure_bastion_host.png new file mode 100644 index 0000000..defe1c3 Binary files /dev/null and b/modules/bastion/docs/secure_bastion_host.png differ diff --git a/modules/bastion/ec2.tf b/modules/bastion/ec2.tf index 3f738fb..b59a3bc 100644 --- a/modules/bastion/ec2.tf +++ b/modules/bastion/ec2.tf @@ -6,7 +6,7 @@ locals { // Script to configure the server - this is where most of the magic occurs! data "template_file" "user_data" { - template = file("${path.module}/templates/cloud-init.yml") + template = file("${path.module}/templates/cloud-init.yml.tpl") } // EC2 instance for the server - tune instance_type to fit your performance and budget requirements diff --git a/modules/bastion/templates/cloud-init.yml b/modules/bastion/templates/cloud-init.yml.tpl similarity index 100% rename from modules/bastion/templates/cloud-init.yml rename to modules/bastion/templates/cloud-init.yml.tpl diff --git a/modules/bastion/variables.tf b/modules/bastion/variables.tf index 90c484b..8c7367a 100644 --- a/modules/bastion/variables.tf +++ b/modules/bastion/variables.tf @@ -18,7 +18,7 @@ variable "ami" { variable "instance_type" { description = "EC2 instance type/size - the default is not part of free tier!" type = string - default = "t2.medium" + default = "t3.nano" } variable "allowed_cidrs" { diff --git a/tools/dac/.gitignore b/tools/dac/.gitignore new file mode 100644 index 0000000..b37c8ae --- /dev/null +++ b/tools/dac/.gitignore @@ -0,0 +1,152 @@ +##### IDE's ##### +# VisualStudioCode +/.vscode/ +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace +.history + +# IntelliJ IDEA +.idea/* + +##### Database ##### +*.accdb +*.db +*.dbf +*.mdb +*.pdb +*.sqlite3 +alembic/versions/* + + +##### Logs ##### +*.log +*.log* + +##### Python ##### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64 +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +bin/ + +# Environments +Makefile +.env +.env.* +.env.local +!.env.example +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.flaskenv +pyvenv.cfg + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Flask stuff: +instance/ +.webassets-cache + +# Translations +*.mo +*.pot + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython +profile_default/ +ipython_config.py + +# PEP 582 +__pypackages__/ + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + + +# ruff +.ruff_cache/ + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# C extensions +*.so + +##### Heroku (old) ##### +Procfile + +# Serverless +node_modules/ +.serverless/ + +# wsgi +wsgi_handler.py +serverless_wsgi.py +.serverless-wsgi + +#OSx +.DS_Store + +run.Ps1 diff --git a/tools/dac/.python-version b/tools/dac/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/tools/dac/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/tools/dac/README.md b/tools/dac/README.md new file mode 100644 index 0000000..bcdf00d --- /dev/null +++ b/tools/dac/README.md @@ -0,0 +1,76 @@ +# πŸ“Š Diagram as Code Scripts + +This directory contains scripts to generate infrastructure diagrams using Python. + +## πŸš€ Getting Started + +### Prerequisites + +Ensure you have the following installed: + +- Python (managed by `pyenv`) +- `pyenv` +- `pipenv` + +### πŸ“‚ Project Structure + +``` +. +β”œβ”€β”€ live_core.py # Diagram as Code script for live production infrastructure +β”œβ”€β”€ Pipfile # Pipenv configuration file +β”œβ”€β”€ Pipfile.lock # Pipenv lock file +└── .python-version # Python version file managed by pyenv +``` + +### πŸ”§ Setup Instructions + +1. **Install `pyenv`**: + + ```sh + curl https://pyenv.run | bash + ``` + + Follow the instructions to add `pyenv` to your shell. + +2. **Install the required Python version**: + + ```sh + pyenv install + pyenv local + ``` + +3. **Install `pipenv`**: + + ```sh + pip install --user pipenv + ``` + +4. **Create and activate a virtual environment using `pipenv`**: + + ```sh + pipenv install + pipenv shell + ``` + +5. **Install project dependencies**: + + ```sh + pipenv install --dev + ``` + +### πŸ“œ Usage + +To generate the infrastructure diagram, run the following command inside the `pipenv` shell: + +```sh +python live_core.py +``` + +This will create a diagram of the live production infrastructure, showcasing the relationships between different AWS components. + +### πŸ“¦ Dependencies + +This project relies on the following Python packages: + +- `diagrams` +- Other dependencies specified in `Pipfile` diff --git a/tools/dac/live_core.py b/tools/dac/live_core.py index da9f089..3b7f076 100644 --- a/tools/dac/live_core.py +++ b/tools/dac/live_core.py @@ -11,10 +11,10 @@ with Cluster("AWS Region"): with Cluster("VPC"): with Cluster("Public Subnet"): - bastion_host = EC2("Bastion Host") nat_gateway = NATGateway("NAT Gateway") with Cluster("Private Subnet"): + bastion_host = EC2("Bastion Host") lambda_func = Lambda("Lambda") rds = RDS("RDS Postgres") diff --git a/tools/dac/live_prod_infrastructure.png b/tools/dac/live_prod_infrastructure.png index c461c2d..bb10f7c 100644 Binary files a/tools/dac/live_prod_infrastructure.png and b/tools/dac/live_prod_infrastructure.png differ