├── multi-account
├── spoke-account
│ ├── .header.md
│ ├── outputs.tf
│ ├── .terraform-docs.yaml
│ ├── providers.tf
│ ├── variables.tf
│ ├── README.md
│ └── main.tf
├── security-account
│ ├── .header.md
│ ├── outputs.tf
│ ├── .terraform-docs.yaml
│ ├── providers.tf
│ ├── variables.tf
│ ├── README.md
│ └── main.tf
├── networking-account
│ ├── .header.md
│ ├── outputs.tf
│ ├── .terraform-docs.yaml
│ ├── providers.tf
│ ├── variables.tf
│ ├── README.md
│ └── main.tf
├── modules
│ └── compute
│ │ ├── outputs.tf
│ │ ├── providers.tf
│ │ ├── variables.tf
│ │ └── main.tf
└── README.md
├── images
├── diagrams.pptx
├── multi_account.png
├── single_account_eastwest.png
├── single_account_centralizedegress.png
├── .$diagrams.drawio.bkp
└── diagrams.drawio
├── CODE_OF_CONDUCT.md
├── single-account
├── modules
│ ├── compute
│ │ ├── outputs.tf
│ │ ├── providers.tf
│ │ ├── variables.tf
│ │ └── main.tf
│ └── iam_kms
│ │ ├── providers.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── main.tf
├── east_west
│ ├── .terraform-docs.yaml
│ ├── providers.tf
│ ├── outputs.tf
│ ├── .header.md
│ ├── variables.tf
│ ├── policy.tf
│ ├── main.tf
│ └── README.md
├── centralized_egress
│ ├── .terraform-docs.yaml
│ ├── providers.tf
│ ├── outputs.tf
│ ├── .header.md
│ ├── variables.tf
│ ├── policy.tf
│ ├── README.md
│ └── main.tf
└── README.md
├── LICENSE
├── README.md
├── .gitignore
└── CONTRIBUTING.md
/multi-account/spoke-account/.header.md:
--------------------------------------------------------------------------------
1 | # Spoke AWS Account
--------------------------------------------------------------------------------
/multi-account/security-account/.header.md:
--------------------------------------------------------------------------------
1 | # Security AWS Account
--------------------------------------------------------------------------------
/multi-account/networking-account/.header.md:
--------------------------------------------------------------------------------
1 | # Networking AWS Account
--------------------------------------------------------------------------------
/images/diagrams.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/hub-and-spoke-with-inspection-vpc-terraform/HEAD/images/diagrams.pptx
--------------------------------------------------------------------------------
/images/multi_account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/hub-and-spoke-with-inspection-vpc-terraform/HEAD/images/multi_account.png
--------------------------------------------------------------------------------
/images/single_account_eastwest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/hub-and-spoke-with-inspection-vpc-terraform/HEAD/images/single_account_eastwest.png
--------------------------------------------------------------------------------
/images/single_account_centralizedegress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/hub-and-spoke-with-inspection-vpc-terraform/HEAD/images/single_account_centralizedegress.png
--------------------------------------------------------------------------------
/multi-account/spoke-account/outputs.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- spoke-account/outputs.tf ---
--------------------------------------------------------------------------------
/multi-account/security-account/outputs.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- security-account/outputs.tf ---
--------------------------------------------------------------------------------
/multi-account/networking-account/outputs.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- networking-account/outputs.tf ---
5 |
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Code of Conduct
2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
4 | opensource-codeofconduct@amazon.com with any additional questions or comments.
5 |
--------------------------------------------------------------------------------
/multi-account/modules/compute/outputs.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- multi-account/modules/compute/outputs.tf ---
5 |
6 | output "ec2_instances" {
7 | value = aws_instance.ec2_instance
8 | description = "List of instances created."
9 | }
--------------------------------------------------------------------------------
/single-account/modules/compute/outputs.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- single-account/modules/compute/outputs.tf ---
5 |
6 | output "ec2_instances" {
7 | value = aws_instance.ec2_instance
8 | description = "List of instances created."
9 | }
--------------------------------------------------------------------------------
/single-account/east_west/.terraform-docs.yaml:
--------------------------------------------------------------------------------
1 | formatter: markdown
2 | header-from: .header.md
3 | settings:
4 | anchor: true
5 | color: true
6 | default: true
7 | escape: true
8 | html: true
9 | indent: 2
10 | required: true
11 | sensitive: true
12 | type: true
13 |
14 | sort:
15 | enabled: true
16 | by: required
17 |
18 | output:
19 | file: README.md
20 | mode: replace
--------------------------------------------------------------------------------
/multi-account/spoke-account/.terraform-docs.yaml:
--------------------------------------------------------------------------------
1 | formatter: markdown
2 | header-from: .header.md
3 | settings:
4 | anchor: true
5 | color: true
6 | default: true
7 | escape: true
8 | html: true
9 | indent: 2
10 | required: true
11 | sensitive: true
12 | type: true
13 |
14 | sort:
15 | enabled: true
16 | by: required
17 |
18 | output:
19 | file: README.md
20 | mode: replace
--------------------------------------------------------------------------------
/multi-account/modules/compute/providers.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- multi-account/modules/compute/providers.tf ---
5 |
6 | terraform {
7 | required_version = ">= 1.3.0"
8 | required_providers {
9 | aws = {
10 | source = "hashicorp/aws"
11 | version = ">= 3.73.0"
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/multi-account/networking-account/.terraform-docs.yaml:
--------------------------------------------------------------------------------
1 | formatter: markdown
2 | header-from: .header.md
3 | settings:
4 | anchor: true
5 | color: true
6 | default: true
7 | escape: true
8 | html: true
9 | indent: 2
10 | required: true
11 | sensitive: true
12 | type: true
13 |
14 | sort:
15 | enabled: true
16 | by: required
17 |
18 | output:
19 | file: README.md
20 | mode: replace
--------------------------------------------------------------------------------
/multi-account/security-account/.terraform-docs.yaml:
--------------------------------------------------------------------------------
1 | formatter: markdown
2 | header-from: .header.md
3 | settings:
4 | anchor: true
5 | color: true
6 | default: true
7 | escape: true
8 | html: true
9 | indent: 2
10 | required: true
11 | sensitive: true
12 | type: true
13 |
14 | sort:
15 | enabled: true
16 | by: required
17 |
18 | output:
19 | file: README.md
20 | mode: replace
--------------------------------------------------------------------------------
/single-account/centralized_egress/.terraform-docs.yaml:
--------------------------------------------------------------------------------
1 | formatter: markdown
2 | header-from: .header.md
3 | settings:
4 | anchor: true
5 | color: true
6 | default: true
7 | escape: true
8 | html: true
9 | indent: 2
10 | required: true
11 | sensitive: true
12 | type: true
13 |
14 | sort:
15 | enabled: true
16 | by: required
17 |
18 | output:
19 | file: README.md
20 | mode: replace
--------------------------------------------------------------------------------
/single-account/modules/compute/providers.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- single-account/modules/compute/providers.tf ---
5 |
6 | terraform {
7 | required_version = ">= 1.3.0"
8 | required_providers {
9 | aws = {
10 | source = "hashicorp/aws"
11 | version = ">= 3.73.0"
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/single-account/modules/iam_kms/providers.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- single-account/modules/iam_kms/outputs.tf ---
5 |
6 | terraform {
7 | required_version = ">= 1.3.0"
8 | required_providers {
9 | aws = {
10 | source = "hashicorp/aws"
11 | version = ">= 3.73.0"
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/single-account/modules/iam_kms/variables.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- single-account/modules/iam_kms/outputs.tf ---
5 |
6 | variable "identifier" {
7 | type = string
8 | description = "Project identifier."
9 | }
10 |
11 | variable "aws_region" {
12 | type = string
13 | description = "AWS Region."
14 | }
--------------------------------------------------------------------------------
/single-account/modules/iam_kms/outputs.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- single-account/modules/iam_kms/outputs.tf ---
5 |
6 | output "vpc_flowlogs_role" {
7 | value = aws_iam_role.vpc_flowlogs_role.id
8 | description = "VPC Flow Logs IAM Role."
9 | }
10 |
11 | output "kms_key" {
12 | value = aws_kms_key.log_key.arn
13 | description = "KMS Key - to encrypt VPC Flow Logs."
14 | }
--------------------------------------------------------------------------------
/multi-account/spoke-account/providers.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- spoke-account/providers.tf ---
5 |
6 | terraform {
7 | required_version = ">= 1.3.0"
8 | required_providers {
9 | aws = {
10 | source = "hashicorp/aws"
11 | version = "= 5.16.2"
12 | }
13 | }
14 | }
15 |
16 | # Provider definition for Spoke AWS Account
17 | provider "aws" {
18 | region = var.aws_region
19 | }
--------------------------------------------------------------------------------
/multi-account/security-account/providers.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- security-account/providers.tf ---
5 |
6 | terraform {
7 | required_version = ">= 1.3.0"
8 | required_providers {
9 | aws = {
10 | source = "hashicorp/aws"
11 | version = "= 5.16.2"
12 | }
13 | }
14 | }
15 |
16 | # Provider definition for Security AWS Account
17 | provider "aws" {
18 | region = var.aws_region
19 | }
--------------------------------------------------------------------------------
/multi-account/networking-account/providers.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- networking-account/providers.tf ---
5 |
6 | terraform {
7 | required_version = ">= 1.3.0"
8 | required_providers {
9 | aws = {
10 | source = "hashicorp/aws"
11 | version = "= 5.16.2"
12 | }
13 | }
14 | }
15 |
16 | # Provider definition for Networking AWS Account
17 | provider "aws" {
18 | region = var.aws_region
19 | }
--------------------------------------------------------------------------------
/single-account/east_west/providers.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/east_west/providers.tf ---
5 |
6 | terraform {
7 | required_version = ">= 1.3.0"
8 | required_providers {
9 | aws = {
10 | source = "hashicorp/aws"
11 | version = "> 5.0.0"
12 | }
13 | }
14 | }
15 |
16 | # AWS Provider configuration
17 | provider "aws" {
18 | region = var.aws_region
19 | }
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/single-account/centralized_egress/providers.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/centralized_egress/providers.tf ---
5 |
6 | terraform {
7 | required_version = ">= 1.3.0"
8 | required_providers {
9 | aws = {
10 | source = "hashicorp/aws"
11 | version = "> 5.0.0"
12 | }
13 | }
14 | }
15 |
16 | # AWS Provider configuration
17 | provider "aws" {
18 | region = var.aws_region
19 | }
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/images/.$diagrams.drawio.bkp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/multi-account/modules/compute/variables.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- multi-account/modules/compute/variables.tf ---
5 |
6 | variable "identifier" {
7 | type = string
8 | description = "Project identifier."
9 | }
10 |
11 | variable "vpc_name" {
12 | type = string
13 | description = "Name of the VPC where the EC2 instance(s) are created."
14 | }
15 |
16 | variable "vpc" {
17 | type = any
18 | description = "VPC resources."
19 | }
20 |
21 | variable "vpc_information" {
22 | type = any
23 | description = "VPC information (defined in root variables.tf file)."
24 | }
--------------------------------------------------------------------------------
/single-account/modules/compute/variables.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- single-account/modules/compute/variables.tf ---
5 |
6 | variable "identifier" {
7 | type = string
8 | description = "Project identifier."
9 | }
10 |
11 | variable "vpc_name" {
12 | type = string
13 | description = "Name of the VPC where the EC2 instance(s) are created."
14 | }
15 |
16 | variable "vpc" {
17 | type = any
18 | description = "VPC resources."
19 | }
20 |
21 | variable "vpc_information" {
22 | type = any
23 | description = "VPC information (defined in root variables.tf file)."
24 | }
--------------------------------------------------------------------------------
/multi-account/security-account/variables.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- security-account/variables.tf ---
5 |
6 | variable "identifier" {
7 | type = string
8 | description = "Account Identifier."
9 |
10 | default = "security-account"
11 | }
12 |
13 | variable "aws_region" {
14 | type = string
15 | description = "AWS Region."
16 |
17 | default = "eu-west-1"
18 | }
19 |
20 | variable "network_supernet" {
21 | type = string
22 | description = "Network supernet."
23 |
24 | default = "10.0.0.0/16"
25 | }
26 |
27 | variable "secret_name" {
28 | type = string
29 | description = "AWS Secrets Manager secret name."
30 |
31 | default = "security-account-firewall-policy-arn"
32 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # AWS Hub and Spoke Architecture with an Inspection VPC - Terraform
3 |
4 | This repository contains terraform code to deploy a sample AWS Hub and Spoke architecture with an Inspection VPC using AWS Network Firewall. The resources deployed and the architectural pattern they follow is purely for demonstration/testing purposes.
5 |
6 | You will find two examples: the architecture built in a single AWS Account, and in a multi-Account environment.
7 |
8 | * [Single AWS Account](./single-account/)
9 |
10 | 
11 |
12 | 
13 |
14 | * [Multi-AWS Account](./multi-account/)
15 |
16 | 
17 |
18 | ## Security
19 |
20 | See [CONTRIBUTING](../CONTRIBUTING.md) for more information.
21 |
22 | ## License
23 |
24 | This library is licensed under the MIT-0 License. See the [LICENSE](../LICENSE) file.
--------------------------------------------------------------------------------
/multi-account/networking-account/variables.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- networking-account/variables.tf ---
5 |
6 | variable "identifier" {
7 | type = string
8 | description = "Account Identifier."
9 |
10 | default = "networking-account"
11 | }
12 |
13 | variable "aws_region" {
14 | type = string
15 | description = "AWS Region."
16 |
17 | default = "eu-west-1"
18 | }
19 |
20 | variable "secrets_names" {
21 | type = map(string)
22 | description = "AWS Secrets Manager secrets name."
23 |
24 | default = {
25 | security_account = "security-account-firewall-policy-arn"
26 | networking_account_tgw = "networking-account-transit-gateway-id"
27 | networking_account_ipam = "networking-account-ipam-pool-id"
28 | networking_account_attachments = "network-account-vpc-attachments"
29 | }
30 | }
31 |
32 | variable "security_account" {
33 | type = string
34 | description = "Security Account ID."
35 | }
--------------------------------------------------------------------------------
/single-account/east_west/outputs.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/east_west/outputs.tf ---
5 |
6 | output "vpcs" {
7 | description = "VPCs created."
8 | value = {
9 | spokes = { for k, v in module.spoke_vpcs : k => v.vpc_attributes.id }
10 | }
11 | }
12 |
13 | output "transit_gateway_id" {
14 | description = "AWS Transit Gateway ID."
15 | value = aws_ec2_transit_gateway.tgw.id
16 | }
17 |
18 | output "transit_gateway_route_tables" {
19 | description = "Transit Gateway Route Table."
20 | value = {
21 | inspection = aws_ec2_transit_gateway_route_table.tgw_route_table_inspection.id
22 | spoke = aws_ec2_transit_gateway_route_table.tgw_route_table_spoke.id
23 | }
24 | }
25 |
26 | output "instances" {
27 | description = "EC2 instances created."
28 | value = { for k, v in module.compute : k => v.ec2_instances.*.id }
29 | }
30 |
31 | output "network_firewall" {
32 | description = "AWS Network Firewall ID."
33 | value = module.network_firewall.aws_network_firewall.id
34 | }
35 |
--------------------------------------------------------------------------------
/single-account/centralized_egress/outputs.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/centralized_egress/outputs.tf ---
5 |
6 | output "vpcs" {
7 | description = "VPCs created."
8 | value = {
9 | spokes = { for k, v in module.spoke_vpcs : k => v.vpc_attributes.id }
10 | inspection = module.inspection_vpc.vpc_attributes.id
11 | }
12 | }
13 |
14 | output "transit_gateway_id" {
15 | description = "AWS Transit Gateway ID."
16 | value = aws_ec2_transit_gateway.tgw.id
17 | }
18 |
19 | output "transit_gateway_route_tables" {
20 | description = "Transit Gateway Route Table."
21 | value = {
22 | inspection = aws_ec2_transit_gateway_route_table.tgw_route_table_inspection.id
23 | spoke = aws_ec2_transit_gateway_route_table.tgw_route_table_spoke.id
24 | }
25 | }
26 |
27 | output "instances" {
28 | description = "EC2 instances created."
29 | value = { for k, v in module.compute : k => v.ec2_instances.*.id }
30 | }
31 |
32 | output "network_firewall" {
33 | description = "AWS Network Firewall ID."
34 | value = module.network_firewall.aws_network_firewall.id
35 | }
36 |
--------------------------------------------------------------------------------
/single-account/centralized_egress/.header.md:
--------------------------------------------------------------------------------
1 | # AWS Hub and Spoke Architecture with an Inspection VPC - Single AWS Account (Centralized Egress)
2 |
3 | ## Prerequisites
4 | * An AWS account with an IAM user with the appropriate permissions
5 | * Terraform installed
6 |
7 | ## Code Principles:
8 | * Writing DRY (Do No Repeat Yourself) code using a modular design pattern
9 |
10 | ## Usage
11 | * Clone the repository
12 | * Edit the variables.tf file in the project root directory. This file contains the information used to configure the Terraform code.
13 |
14 | **Note** EC2 instances, and AWS Network Firewall endpoints will be deployed in all the Availability Zones configured for each VPC. Keep this in mind when testing this environment from a cost perspective - for production environments, we recommend the use of at least 2 AZs for high-availability.
15 |
16 | ## Target Architecture
17 |
18 | 
19 |
20 | ## Deployment
21 |
22 | * `terraform init` to initialize the environment.
23 | * `terraform plan` to check the resources to create
24 | * `terraform apply` to build the architecture.
25 |
26 | ## Clean-up
27 |
28 | * `terraform destroy` will clean-up the resources created.
--------------------------------------------------------------------------------
/single-account/east_west/.header.md:
--------------------------------------------------------------------------------
1 | # AWS Hub and Spoke Architecture with an Inspection VPC - Single AWS Account (East-West)
2 |
3 | ## Prerequisites
4 | * An AWS account with an IAM user with the appropriate permissions
5 | * Terraform installed
6 |
7 | ## Code Principles:
8 | * Writing DRY (Do No Repeat Yourself) code using a modular design pattern
9 |
10 | ## Usage
11 | * Clone the repository
12 | * Edit the variables.tf file in the project root directory. This file contains the information used to configure the Terraform code.
13 |
14 | **Note** EC2 instances will be deployed in all the Availability Zones configured for each VPC, and AWS Network Firewall will be deployed in all the AWS Region's Availability Zones. Keep this in mind when testing this environment from a cost perspective - for production environments, we recommend the use of at least 2 AZs for high-availability.
15 |
16 | ## Target Architecture
17 |
18 | 
19 |
20 | ## Deployment
21 |
22 | * `terraform init` to initialize the environment.
23 | * `terraform plan` to check the resources to create
24 | * `terraform apply` to build the architecture.
25 |
26 | ## Clean-up
27 |
28 | * `terraform destroy` will clean-up the resources created.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/terraform
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=terraform
4 |
5 | ### Terraform ###
6 | # Local .terraform directories
7 | **/.terraform/*
8 |
9 | # .tfstate files
10 | *.tfstate
11 | *.tfstate.*
12 | *.lock.hcl
13 | # Crash log files
14 | crash.log
15 |
16 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as
17 | # password, private keys, and other secrets. These should not be part of version
18 | # control as they are data points which are potentially sensitive and subject
19 | # to change depending on the environment.
20 | #
21 | *.tfvars
22 |
23 | # Ignore override files as they are usually used to override resources locally and so
24 | # are not checked in
25 | override.tf
26 | override.tf.json
27 | *_override.tf
28 | *_override.tf.json
29 |
30 | # Include override files you do wish to add to version control using negated pattern
31 | # !example_override.tf
32 |
33 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
34 | # example: *tfplan*
35 |
36 | # Ignore CLI configuration files
37 | .terraformrc
38 | terraform.rc
39 |
40 | # End of https://www.toptal.com/developers/gitignore/api/terraform
41 |
42 | # macOs
43 | .DS_Store
44 |
45 | # Project
46 | keys/
47 |
48 | gcm-diagnose.log
49 |
50 |
--------------------------------------------------------------------------------
/multi-account/spoke-account/variables.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- spoke-account/variables.tf ---
5 |
6 | variable "identifier" {
7 | type = string
8 | description = "Account Identifier."
9 |
10 | default = "spoke-account"
11 | }
12 |
13 | variable "aws_region" {
14 | type = string
15 | description = "AWS Region."
16 |
17 | default = "eu-west-1"
18 | }
19 |
20 | variable "secrets_names" {
21 | type = map(string)
22 | description = "AWS Secrets Manager secrets name."
23 |
24 | default = {
25 | networking_account_tgw = "networking-account-transit-gateway-id"
26 | networking_account_ipam = "networking-account-ipam-pool-id"
27 | networking_account_attachments = "network-account-vpc-attachments"
28 | }
29 | }
30 |
31 | variable "vpcs" {
32 | type = any
33 | description = "VPC information."
34 | default = {
35 | vpc1 = {
36 | routing_domain = "prod"
37 | number_azs = 2
38 | instance_type = "t3.micro"
39 | }
40 | vpc2 = {
41 | routing_domain = "nonprod"
42 | number_azs = 2
43 | instance_type = "t3.micro"
44 | }
45 | }
46 | }
47 |
48 | variable "networking_account" {
49 | type = string
50 | description = "Networking Account ID."
51 | }
--------------------------------------------------------------------------------
/single-account/east_west/variables.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/east_west/variables.tf ---
5 |
6 | # Variables that define project configuration
7 | variable "aws_region" {
8 | description = "AWS Region."
9 | type = string
10 | default = "eu-west-1"
11 | }
12 |
13 | variable "identifier" {
14 | description = "Name of the project."
15 | type = string
16 | default = "singleaccount-eastwest"
17 | }
18 |
19 | # Spoke VPCs
20 | variable "spoke_vpcs" {
21 | description = "Spoke VPCs definition."
22 | type = any
23 |
24 | default = {
25 | "spoke-vpc-1" = {
26 | cidr_block = "10.0.0.0/24"
27 | workload_subnet_netmask = 28
28 | endpoint_subnet_netmask = 28
29 | tgw_subnet_netmask = 28
30 | number_azs = 2
31 | instance_type = "t2.micro"
32 |
33 | flow_log_config = {
34 | log_destination_type = "cloud-watch-logs"
35 | retention_in_days = 7
36 | }
37 | }
38 |
39 | "spoke-vpc-2" = {
40 | cidr_block = "10.0.1.0/24"
41 | workload_subnet_netmask = 28
42 | endpoint_subnet_netmask = 28
43 | tgw_subnet_netmask = 28
44 | number_azs = 2
45 | instance_type = "t2.micro"
46 |
47 | flow_log_config = {
48 | log_destination_type = "cloud-watch-logs"
49 | retention_in_days = 7
50 | }
51 | }
52 | }
53 | }
54 |
55 | # Inspection VPC
56 | variable "inspection_vpc" {
57 | description = "Inspection VPC definition."
58 | type = any
59 |
60 | default = {
61 | cidr_block = "10.129.0.0/24"
62 | public_subnet_netmask = 28
63 | private_subnet_netmask = 28
64 | tgw_subnet_netmask = 28
65 | number_azs = 2
66 | }
67 | }
--------------------------------------------------------------------------------
/single-account/centralized_egress/variables.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/centralized_egress/variables.tf ---
5 |
6 | # Variables that define project configuration
7 | variable "aws_region" {
8 | description = "AWS Region."
9 | type = string
10 | default = "eu-west-1"
11 | }
12 |
13 | variable "identifier" {
14 | description = "Name of the project."
15 | type = string
16 | default = "singleaccount-centralizedegress"
17 | }
18 |
19 | # Spoke VPCs
20 | variable "spoke_vpcs" {
21 | description = "Spoke VPCs definition."
22 | type = any
23 |
24 | default = {
25 | "spoke-vpc-1" = {
26 | cidr_block = "10.0.0.0/24"
27 | workload_subnet_netmask = 28
28 | endpoint_subnet_netmask = 28
29 | tgw_subnet_netmask = 28
30 | number_azs = 2
31 | instance_type = "t2.micro"
32 |
33 | flow_log_config = {
34 | log_destination_type = "cloud-watch-logs"
35 | retention_in_days = 7
36 | }
37 | }
38 |
39 | "spoke-vpc-2" = {
40 | cidr_block = "10.0.1.0/24"
41 | workload_subnet_netmask = 28
42 | endpoint_subnet_netmask = 28
43 | tgw_subnet_netmask = 28
44 | number_azs = 2
45 | instance_type = "t2.micro"
46 |
47 | flow_log_config = {
48 | log_destination_type = "cloud-watch-logs"
49 | retention_in_days = 7
50 | }
51 | }
52 | }
53 | }
54 |
55 | # Inspection VPC
56 | variable "inspection_vpc" {
57 | description = "Inspection VPC definition."
58 | type = any
59 |
60 | default = {
61 | cidr_block = "10.129.0.0/24"
62 | public_subnet_netmask = 28
63 | private_subnet_netmask = 28
64 | tgw_subnet_netmask = 28
65 | number_azs = 2
66 | }
67 | }
--------------------------------------------------------------------------------
/single-account/README.md:
--------------------------------------------------------------------------------
1 | # AWS Hub and Spoke Architecture with an Inspection VPC - Single AWS Account
2 |
3 | This repository contains terraform code to deploy a sample AWS Hub and Spoke architecture with an Inspection VPC using AWS Network Firewall. The resources deployed and the architectural pattern they follow is purely for demonstration/testing purposes. You can find two different examples:
4 |
5 | 1. [Centralized egress](./centralized_egress/), where there's a single Inspection VPC attached to the Transit Gateway with both AWS Network Firewall endpoints and NAT gateways. This pattern is also recommended when wanting to use the firewall both for egress and east-west traffic inspection.
6 |
7 | 
8 |
9 | 2. [East-West](./east_west/), where we use the [native TGW attachment](https://docs.aws.amazon.com/network-firewall/latest/developerguide/tgw-firewall.html) in Network Firewall to reduce the operational overhead creating a central Inspection VPC.
10 |
11 | 
12 |
13 | ## Infrastructure configuration
14 |
15 | ### AWS Network Firewall Policy
16 |
17 | The AWS Network Firewall Policy is defined in the *policy.tf* file in the network_firewall module directory. The policy configuration will vary depending the example:
18 |
19 | * All the SSH and RDP traffic is blocked by the Stateless engine.
20 | * The Stateful engine follows Strict Rule Ordering.
21 | * For **centralized egress**, all traffic is blocked except HTTPS traffic to *aws.amazon.com*.
22 | * For **east-west**, all traffic is blocked except ICMP traffic between the spoke VPCs attached to the Transit Gateway.
23 |
24 | ### VPC Flow Logs configuration
25 |
26 | This project configures both the alert and flow logs to respective AWS Cloudwatch Log Groups (both for the VPC Flow logs and AWS Network Firewall logs). In VPC Flow logs, you can also use Amazon S3. In Network Firewall, you can also use Amazon S3, or Amazon Kinesis Firehose.
27 |
28 | To follow best practices, all the logs are encrypted at rest using AWS KMS. The KMS key (alongside the IAM roles needed) is created using the *iam\_kms* module.
29 |
30 | ## Security
31 |
32 | See [CONTRIBUTING](../CONTRIBUTING.md) for more information.
33 |
34 | ## License
35 |
36 | This library is licensed under the MIT-0 License. See the [LICENSE](../LICENSE) file.
37 |
--------------------------------------------------------------------------------
/single-account/modules/iam_kms/main.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- single-account/modules/iam_kms/main.tf ---
5 |
6 | # DATA SOURCE: AWS CALLER IDENTITY - Used to get the Account ID
7 | data "aws_caller_identity" "current" {}
8 |
9 | # ---------- VPC FLOW LOG - IAM ROLE AND KMS KEY ----------
10 | # IAM Role
11 | resource "aws_iam_role" "vpc_flowlogs_role" {
12 | name = "vpc-flowlog-role-${var.identifier}"
13 | assume_role_policy = data.aws_iam_policy_document.policy_role_document.json
14 | }
15 |
16 | data "aws_iam_policy_document" "policy_role_document" {
17 | statement {
18 | sid = "1"
19 | actions = ["sts:AssumeRole"]
20 |
21 | principals {
22 | type = "Service"
23 | identifiers = ["vpc-flow-logs.amazonaws.com"]
24 | }
25 | }
26 | }
27 |
28 | # IAM Role Policy
29 | resource "aws_iam_role_policy" "vpc_flowlogs_role_policy" {
30 | name = "vpc-flowlog-role-policy-${var.identifier}"
31 | role = aws_iam_role.vpc_flowlogs_role.id
32 | policy = data.aws_iam_policy_document.policy_rolepolicy_document.json
33 | }
34 |
35 | data "aws_iam_policy_document" "policy_rolepolicy_document" {
36 | statement {
37 | sid = "2"
38 | actions = [
39 | "logs:CreateLogGroup",
40 | "logs:CreateLogStream",
41 | "logs:PutLogEvents",
42 | "logs:DescribeLogGroup",
43 | "logs:DescribeLogStreams"
44 | ]
45 | resources = ["arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"]
46 | }
47 | }
48 |
49 | # KMS Key
50 | resource "aws_kms_key" "log_key" {
51 | description = "KMS Logs Key"
52 | deletion_window_in_days = 7
53 | enable_key_rotation = true
54 | policy = data.aws_iam_policy_document.policy_kms_logs_document.json
55 |
56 | tags = {
57 | Name = "kms-key-${var.identifier}"
58 | }
59 | }
60 |
61 | # KMS Policy - it allows the use of the Key by the CloudWatch log groups created in this sample
62 | data "aws_iam_policy_document" "policy_kms_logs_document" {
63 | statement {
64 | sid = "Enable IAM User Permissions"
65 | actions = ["kms:*"]
66 | resources = ["arn:aws:kms:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"]
67 |
68 | principals {
69 | type = "AWS"
70 | identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
71 | }
72 | }
73 |
74 | statement {
75 | sid = "Enable KMS to be used by CloudWatch Logs"
76 | actions = [
77 | "kms:Encrypt*",
78 | "kms:Decrypt*",
79 | "kms:ReEncrypt*",
80 | "kms:GenerateDataKey*",
81 | "kms:Describe*"
82 | ]
83 | resources = ["arn:aws:kms:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"]
84 |
85 | principals {
86 | type = "Service"
87 | identifiers = ["logs.${var.aws_region}.amazonaws.com"]
88 | }
89 |
90 | condition {
91 | test = "ArnLike"
92 | variable = "kms:EncryptionContext:aws:logs:arn"
93 | values = ["arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:*"]
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/multi-account/spoke-account/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Spoke AWS Account
3 |
4 | ## Requirements
5 |
6 | | Name | Version |
7 | |------|---------|
8 | | [terraform](#requirement\_terraform) | >= 1.3.0 |
9 | | [aws](#requirement\_aws) | = 5.16.2 |
10 |
11 | ## Providers
12 |
13 | | Name | Version |
14 | |------|---------|
15 | | [aws](#provider\_aws) | = 5.16.2 |
16 |
17 | ## Modules
18 |
19 | | Name | Source | Version |
20 | |------|--------|---------|
21 | | [compute](#module\_compute) | ../modules/compute | n/a |
22 | | [vpcs](#module\_vpcs) | aws-ia/vpc/aws | 4.4.2 |
23 |
24 | ## Resources
25 |
26 | | Name | Type |
27 | |------|------|
28 | | [aws_secretsmanager_secret_version.vpc_information](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/secretsmanager_secret_version) | resource |
29 | | [aws_caller_identity.aws_spoke_account](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/caller_identity) | data source |
30 | | [aws_secretsmanager_secret.ipam_pool_id](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/secretsmanager_secret) | data source |
31 | | [aws_secretsmanager_secret.tgw_id](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/secretsmanager_secret) | data source |
32 | | [aws_secretsmanager_secret.vpc_information](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/secretsmanager_secret) | data source |
33 | | [aws_secretsmanager_secret_version.ipam_pool_id](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/secretsmanager_secret_version) | data source |
34 | | [aws_secretsmanager_secret_version.tgw_id](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/secretsmanager_secret_version) | data source |
35 |
36 | ## Inputs
37 |
38 | | Name | Description | Type | Default | Required |
39 | |------|-------------|------|---------|:--------:|
40 | | [networking\_account](#input\_networking\_account) | Networking Account ID. | `string` | n/a | yes |
41 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no |
42 | | [identifier](#input\_identifier) | Account Identifier. | `string` | `"spoke-account"` | no |
43 | | [secrets\_names](#input\_secrets\_names) | AWS Secrets Manager secrets name. | `map(string)` |
{
"networking_account_attachments": "network-account-vpc-attachments",
"networking_account_ipam": "networking-account-ipam-pool-id",
"networking_account_tgw": "networking-account-transit-gateway-id"
} | no |
44 | | [vpcs](#input\_vpcs) | VPC information. | `any` | {
"vpc1": {
"instance_type": "t3.micro",
"number_azs": 2,
"routing_domain": "prod"
},
"vpc2": {
"instance_type": "t3.micro",
"number_azs": 2,
"routing_domain": "nonprod"
}
} | no |
45 |
46 | ## Outputs
47 |
48 | No outputs.
49 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
4 | documentation, we greatly value feedback and contributions from our community.
5 |
6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
7 | information to effectively respond to your bug report or contribution.
8 |
9 |
10 | ## Reporting Bugs/Feature Requests
11 |
12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features.
13 |
14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
16 |
17 | * A reproducible test case or series of steps
18 | * The version of our code being used
19 | * Any modifications you've made relevant to the bug
20 | * Anything unusual about your environment or deployment
21 |
22 |
23 | ## Contributing via Pull Requests
24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
25 |
26 | 1. You are working against the latest source on the *main* branch.
27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
29 |
30 | To send us a pull request, please:
31 |
32 | 1. Fork the repository.
33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
34 | 3. Ensure local tests pass.
35 | 4. Commit to your fork using clear commit messages.
36 | 5. Send us a pull request, answering any default questions in the pull request interface.
37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
38 |
39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
41 |
42 |
43 | ## Finding contributions to work on
44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
45 |
46 |
47 | ## Code of Conduct
48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
50 | opensource-codeofconduct@amazon.com with any additional questions or comments.
51 |
52 |
53 | ## Security issue notifications
54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
55 |
56 |
57 | ## Licensing
58 |
59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
60 |
--------------------------------------------------------------------------------
/single-account/east_west/policy.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/east_west/policy.tf ---
5 |
6 | resource "aws_networkfirewall_firewall_policy" "anfw_policy" {
7 | name = "firewall-policy-${var.identifier}"
8 |
9 | firewall_policy {
10 | # Stateless configuration
11 | stateless_default_actions = ["aws:forward_to_sfe"]
12 | stateless_fragment_default_actions = ["aws:forward_to_sfe"]
13 |
14 | stateless_rule_group_reference {
15 | priority = 10
16 | resource_arn = aws_networkfirewall_rule_group.drop_remote.arn
17 | }
18 |
19 | # Stateful configuration
20 | stateful_engine_options {
21 | rule_order = "STRICT_ORDER"
22 | }
23 | stateful_default_actions = ["aws:drop_strict", "aws:alert_strict"]
24 | stateful_rule_group_reference {
25 | priority = 10
26 | resource_arn = aws_networkfirewall_rule_group.allow_icmp.arn
27 | }
28 | }
29 | }
30 |
31 | # Stateless Rule Group - Dropping any SSH connection
32 | resource "aws_networkfirewall_rule_group" "drop_remote" {
33 | capacity = 2
34 | name = "drop-remote-${var.identifier}"
35 | type = "STATELESS"
36 | rule_group {
37 | rules_source {
38 | stateless_rules_and_custom_actions {
39 | # Block SSH (port 22)
40 | stateless_rule {
41 | priority = 1
42 | rule_definition {
43 | actions = ["aws:drop"]
44 | match_attributes {
45 | protocols = [6]
46 | source {
47 | address_definition = "0.0.0.0/0"
48 | }
49 | destination {
50 | address_definition = "0.0.0.0/0"
51 | }
52 | destination_port {
53 | from_port = 22
54 | to_port = 22
55 | }
56 | }
57 | }
58 | }
59 |
60 | # Block RDP (port 3389)
61 | stateless_rule {
62 | priority = 2
63 | rule_definition {
64 | actions = ["aws:drop"]
65 | match_attributes {
66 | protocols = [6]
67 | source {
68 | address_definition = "0.0.0.0/0"
69 | }
70 | destination {
71 | address_definition = "0.0.0.0/0"
72 | }
73 | destination_port {
74 | from_port = 3389
75 | to_port = 3389
76 | }
77 | }
78 | }
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
85 | # Stateful Rule Group - Allowing ICMP traffic
86 | resource "aws_networkfirewall_rule_group" "allow_icmp" {
87 | capacity = 1
88 | name = "allow-icmp-${var.identifier}"
89 | type = "STATEFUL"
90 | rule_group {
91 | rule_variables {
92 | ip_sets {
93 | key = "SUPERNET"
94 | ip_set {
95 | definition = ["10.0.0.0/16"]
96 | }
97 | }
98 | }
99 | rules_source {
100 | rules_string = < $SUPERNET any (msg: "Allowing ICMP packets"; sid:2; rev:1;)
102 | EOF
103 | }
104 | stateful_rule_options {
105 | rule_order = "STRICT_ORDER"
106 | }
107 | }
108 | }
--------------------------------------------------------------------------------
/multi-account/modules/compute/main.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- multi-account/modules/compute/main.tf ---
5 |
6 | # ---------- EC2 INSTANCES ----------
7 | # Security Group
8 | resource "aws_security_group" "instance_sg" {
9 | name = "${var.vpc_name}-instance-security-group-${var.identifier}"
10 | description = "EC2 Instance Security Group"
11 | vpc_id = var.vpc.vpc_attributes.id
12 | }
13 |
14 | resource "aws_vpc_security_group_ingress_rule" "allowing_ingress_icmp" {
15 | security_group_id = aws_security_group.instance_sg.id
16 |
17 | from_port = -1
18 | to_port = -1
19 | ip_protocol = "icmp"
20 | cidr_ipv4 = "0.0.0.0/0"
21 | }
22 |
23 | resource "aws_vpc_security_group_ingress_rule" "allowing_ingress_eic" {
24 | security_group_id = aws_security_group.instance_sg.id
25 |
26 | from_port = 22
27 | to_port = 22
28 | ip_protocol = "tcp"
29 | referenced_security_group_id = aws_security_group.eic_sg.id
30 | }
31 |
32 | resource "aws_vpc_security_group_egress_rule" "allowing_egress_any" {
33 | security_group_id = aws_security_group.instance_sg.id
34 |
35 | ip_protocol = "-1"
36 | cidr_ipv4 = "0.0.0.0/0"
37 | }
38 |
39 | # Data resource to determine the latest Amazon Linux 2023 AMI
40 | data "aws_ami" "amazon_linux" {
41 | most_recent = true
42 | owners = ["amazon"]
43 |
44 | filter {
45 | name = "name"
46 | values = ["al2023-ami-2023.*-x86_64"]
47 | }
48 | }
49 |
50 | # EC2 instances
51 | resource "aws_instance" "ec2_instance" {
52 | count = var.vpc_information.number_azs
53 |
54 | ami = data.aws_ami.amazon_linux.id
55 | associate_public_ip_address = false
56 | instance_type = var.vpc_information.instance_type
57 | vpc_security_group_ids = [aws_security_group.instance_sg.id]
58 | subnet_id = values({ for k, v in var.vpc.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "private" })[count.index]
59 |
60 | metadata_options {
61 | http_endpoint = "enabled"
62 | http_tokens = "required"
63 | }
64 |
65 | root_block_device {
66 | encrypted = true
67 | }
68 |
69 | tags = {
70 | Name = "${var.vpc_name}-instance-${count.index + 1}-${var.identifier}"
71 | }
72 | }
73 |
74 | # ---------- EC2 INSTANCE CONNECT ----------
75 | # Security Group
76 | resource "aws_security_group" "eic_sg" {
77 | name = "${var.vpc_name}-eic-security-group-${var.identifier}"
78 | description = "EC2 Instance Connect Security Group"
79 | vpc_id = var.vpc.vpc_attributes.id
80 | }
81 |
82 | resource "aws_vpc_security_group_egress_rule" "allowing_egress_ec2_instances" {
83 | security_group_id = aws_security_group.eic_sg.id
84 |
85 | from_port = 22
86 | to_port = 22
87 | ip_protocol = "tcp"
88 | referenced_security_group_id = aws_security_group.instance_sg.id
89 | }
90 |
91 | # EC2 Instance Connect endpoint
92 | resource "aws_ec2_instance_connect_endpoint" "eic_endpoint" {
93 | subnet_id = values({ for k, v in var.vpc.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "endpoints" })[0]
94 | preserve_client_ip = false
95 | security_group_ids = [aws_security_group.eic_sg.id]
96 | }
--------------------------------------------------------------------------------
/multi-account/spoke-account/main.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- spoke-account/main.tf ---
5 |
6 | # ---------- AWS ORGANIZATIONS AND ACCOUNT INFORMATION ----------
7 | data "aws_caller_identity" "aws_spoke_account" {}
8 |
9 | # ---------- AMAZON VPCs ----------
10 | # We obtain the Transit Gateway ID and IPAM Pool ID information
11 | data "aws_secretsmanager_secret" "tgw_id" {
12 | arn = "arn:aws:secretsmanager:${var.aws_region}:${var.networking_account}:secret:${var.secrets_names.networking_account_tgw}"
13 | }
14 |
15 | data "aws_secretsmanager_secret_version" "tgw_id" {
16 | secret_id = data.aws_secretsmanager_secret.tgw_id.id
17 | }
18 |
19 | data "aws_secretsmanager_secret" "ipam_pool_id" {
20 | arn = "arn:aws:secretsmanager:${var.aws_region}:${var.networking_account}:secret:${var.secrets_names.networking_account_ipam}"
21 | }
22 |
23 | data "aws_secretsmanager_secret_version" "ipam_pool_id" {
24 | secret_id = data.aws_secretsmanager_secret.ipam_pool_id.id
25 | }
26 |
27 | # VPCs
28 | module "vpcs" {
29 | source = "aws-ia/vpc/aws"
30 | version = "4.4.2"
31 | for_each = var.vpcs
32 |
33 | name = each.key
34 | az_count = 3
35 | vpc_ipv4_ipam_pool_id = data.aws_secretsmanager_secret_version.ipam_pool_id.secret_string
36 | vpc_ipv4_netmask_length = 24
37 |
38 | transit_gateway_id = data.aws_secretsmanager_secret_version.tgw_id.secret_string
39 | transit_gateway_routes = {
40 | private = "0.0.0.0/0"
41 | }
42 |
43 | subnets = {
44 | endpoints = { netmask = 28 }
45 | private = { netmask = 28 }
46 | transit_gateway = {
47 | netmask = 28
48 |
49 | tags = { domain = var.vpcs[each.key].routing_domain }
50 | }
51 | }
52 |
53 | tags = {
54 | domain = each.value.routing_domain
55 | }
56 | }
57 |
58 | # ---------- INCLUDE VPC INFORMATION IN SECRET ----------
59 | # We retrieve the Secrets Manager secret created by the Central Account
60 | data "aws_secretsmanager_secret" "vpc_information" {
61 | arn = "arn:aws:secretsmanager:${var.aws_region}:${var.networking_account}:secret:${var.secrets_names.networking_account_attachments}"
62 | }
63 |
64 | # We generate the secret we want to pass - with the Spoke VPCs information
65 | locals {
66 | vpc_information = {
67 | spoke_account = {
68 | id = data.aws_caller_identity.aws_spoke_account.id
69 | number_spoke_vpcs = length(var.vpcs)
70 | vpc_information = { for k, v in module.vpcs : k => {
71 | vpc_id = v.vpc_attributes.id
72 | transit_gateway_attachment_id = v.transit_gateway_attachment_id
73 | routing_domain = var.vpcs[k].routing_domain
74 | } }
75 | }
76 | }
77 | }
78 |
79 | # We add the secret value to the secret
80 | resource "aws_secretsmanager_secret_version" "vpc_information" {
81 | secret_id = data.aws_secretsmanager_secret.vpc_information.id
82 | secret_string = jsonencode(local.vpc_information)
83 | }
84 |
85 | # ---------- EC2 INSTANCES ----------
86 | module "compute" {
87 | source = "../modules/compute"
88 | for_each = module.vpcs
89 |
90 | identifier = var.identifier
91 | vpc_name = each.key
92 | vpc = each.value
93 | vpc_information = var.vpcs[each.key]
94 | }
--------------------------------------------------------------------------------
/single-account/modules/compute/main.tf:
--------------------------------------------------------------------------------
1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | # SPDX-License-Identifier: MIT-0
3 |
4 | # --- single-account/modules/compute/main.tf ---
5 |
6 | # ---------- EC2 INSTANCES ----------
7 | # Security Group
8 | resource "aws_security_group" "instance_sg" {
9 | name = "${var.vpc_name}-instance-security-group-${var.identifier}"
10 | description = "EC2 Instance Security Group"
11 | vpc_id = var.vpc.vpc_attributes.id
12 | }
13 |
14 | resource "aws_vpc_security_group_ingress_rule" "allowing_ingress_icmp" {
15 | security_group_id = aws_security_group.instance_sg.id
16 |
17 | from_port = -1
18 | to_port = -1
19 | ip_protocol = "icmp"
20 | cidr_ipv4 = "0.0.0.0/0"
21 | }
22 |
23 | resource "aws_vpc_security_group_ingress_rule" "allowing_ingress_eic" {
24 | security_group_id = aws_security_group.instance_sg.id
25 |
26 | from_port = 22
27 | to_port = 22
28 | ip_protocol = "tcp"
29 | referenced_security_group_id = aws_security_group.eic_sg.id
30 | }
31 |
32 | resource "aws_vpc_security_group_egress_rule" "allowing_egress_any" {
33 | security_group_id = aws_security_group.instance_sg.id
34 |
35 | ip_protocol = "-1"
36 | cidr_ipv4 = "0.0.0.0/0"
37 | }
38 |
39 | # Data resource to determine the latest Amazon Linux 2023 AMI
40 | data "aws_ami" "amazon_linux" {
41 | most_recent = true
42 | owners = ["amazon"]
43 |
44 | filter {
45 | name = "name"
46 | values = ["al2023-ami-2023.*-x86_64"]
47 | }
48 | }
49 |
50 | # EC2 instances
51 | resource "aws_instance" "ec2_instance" {
52 | count = var.vpc_information.number_azs
53 |
54 | ami = data.aws_ami.amazon_linux.id
55 | associate_public_ip_address = false
56 | instance_type = var.vpc_information.instance_type
57 | vpc_security_group_ids = [aws_security_group.instance_sg.id]
58 | subnet_id = values({ for k, v in var.vpc.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "workload" })[count.index]
59 |
60 | metadata_options {
61 | http_endpoint = "enabled"
62 | http_tokens = "required"
63 | }
64 |
65 | root_block_device {
66 | encrypted = true
67 | }
68 |
69 | tags = {
70 | Name = "${var.vpc_name}-instance-${count.index + 1}-${var.identifier}"
71 | }
72 | }
73 |
74 | # ---------- EC2 INSTANCE CONNECT ----------
75 | # Security Group
76 | resource "aws_security_group" "eic_sg" {
77 | name = "${var.vpc_name}-eic-security-group-${var.identifier}"
78 | description = "EC2 Instance Connect Security Group"
79 | vpc_id = var.vpc.vpc_attributes.id
80 | }
81 |
82 | resource "aws_vpc_security_group_egress_rule" "allowing_egress_ec2_instances" {
83 | security_group_id = aws_security_group.eic_sg.id
84 |
85 | from_port = 22
86 | to_port = 22
87 | ip_protocol = "tcp"
88 | referenced_security_group_id = aws_security_group.instance_sg.id
89 | }
90 |
91 | # EC2 Instance Connect endpoint
92 | resource "aws_ec2_instance_connect_endpoint" "eic_endpoint" {
93 | subnet_id = values({ for k, v in var.vpc.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "endpoints" })[0]
94 | preserve_client_ip = false
95 | security_group_ids = [aws_security_group.eic_sg.id]
96 | }
--------------------------------------------------------------------------------
/single-account/centralized_egress/policy.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/centralized_egress/policy.tf ---
5 |
6 | resource "aws_networkfirewall_firewall_policy" "anfw_policy" {
7 | name = "firewall-policy-${var.identifier}"
8 |
9 | firewall_policy {
10 | # Stateless configuration
11 | stateless_default_actions = ["aws:forward_to_sfe"]
12 | stateless_fragment_default_actions = ["aws:forward_to_sfe"]
13 |
14 | stateless_rule_group_reference {
15 | priority = 10
16 | resource_arn = aws_networkfirewall_rule_group.drop_remote.arn
17 | }
18 |
19 | # Stateful configuration
20 | stateful_engine_options {
21 | rule_order = "STRICT_ORDER"
22 | }
23 | stateful_default_actions = ["aws:drop_established", "aws:alert_established"]
24 | stateful_rule_group_reference {
25 | priority = 10
26 | resource_arn = aws_networkfirewall_rule_group.allow_domains.arn
27 | }
28 | }
29 | }
30 |
31 | # Stateless Rule Group - Dropping any SSH connection
32 | resource "aws_networkfirewall_rule_group" "drop_remote" {
33 | capacity = 2
34 | name = "drop-remote-${var.identifier}"
35 | type = "STATELESS"
36 | rule_group {
37 | rules_source {
38 | stateless_rules_and_custom_actions {
39 | # Block SSH (port 22)
40 | stateless_rule {
41 | priority = 1
42 | rule_definition {
43 | actions = ["aws:drop"]
44 | match_attributes {
45 | protocols = [6]
46 | source {
47 | address_definition = "0.0.0.0/0"
48 | }
49 | destination {
50 | address_definition = "0.0.0.0/0"
51 | }
52 | destination_port {
53 | from_port = 22
54 | to_port = 22
55 | }
56 | }
57 | }
58 | }
59 |
60 | # Block RDP (port 3389)
61 | stateless_rule {
62 | priority = 2
63 | rule_definition {
64 | actions = ["aws:drop"]
65 | match_attributes {
66 | protocols = [6]
67 | source {
68 | address_definition = "0.0.0.0/0"
69 | }
70 | destination {
71 | address_definition = "0.0.0.0/0"
72 | }
73 | destination_port {
74 | from_port = 3389
75 | to_port = 3389
76 | }
77 | }
78 | }
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
85 | # Stateful Rule Group - Allowing access to .amazon.com (HTTPS)
86 | resource "aws_networkfirewall_rule_group" "allow_domains" {
87 | capacity = 100
88 | name = "allow-domains-${var.identifier}"
89 | type = "STATEFUL"
90 | rule_group {
91 | rule_variables {
92 | ip_sets {
93 | key = "HOME_NET"
94 | ip_set {
95 | definition = values({ for k, v in var.spoke_vpcs : k => v.cidr_block })
96 | }
97 | }
98 | }
99 | rules_source {
100 | rules_source_list {
101 | generated_rules_type = "ALLOWLIST"
102 | target_types = ["TLS_SNI"]
103 | targets = ["aws.amazon.com"]
104 | }
105 | }
106 | stateful_rule_options {
107 | rule_order = "STRICT_ORDER"
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/multi-account/security-account/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Security AWS Account
3 |
4 | ## Requirements
5 |
6 | | Name | Version |
7 | |------|---------|
8 | | [terraform](#requirement\_terraform) | >= 1.3.0 |
9 | | [aws](#requirement\_aws) | = 5.16.2 |
10 |
11 | ## Providers
12 |
13 | | Name | Version |
14 | |------|---------|
15 | | [aws](#provider\_aws) | = 5.16.2 |
16 |
17 | ## Modules
18 |
19 | No modules.
20 |
21 | ## Resources
22 |
23 | | Name | Type |
24 | |------|------|
25 | | [aws_kms_key.secrets_key](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/kms_key) | resource |
26 | | [aws_networkfirewall_firewall_policy.central_inspection_policy](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/networkfirewall_firewall_policy) | resource |
27 | | [aws_networkfirewall_rule_group.allow_domains](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/networkfirewall_rule_group) | resource |
28 | | [aws_networkfirewall_rule_group.allow_tcp](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/networkfirewall_rule_group) | resource |
29 | | [aws_networkfirewall_rule_group.drop_remote](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/networkfirewall_rule_group) | resource |
30 | | [aws_ram_principal_association.principal_association](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/ram_principal_association) | resource |
31 | | [aws_ram_resource_association.firewall_policy_share](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/ram_resource_association) | resource |
32 | | [aws_ram_resource_share.resource_share](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/ram_resource_share) | resource |
33 | | [aws_secretsmanager_secret.firewall_policy_arn](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/secretsmanager_secret) | resource |
34 | | [aws_secretsmanager_secret_version.firewall_policy_arn](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/secretsmanager_secret_version) | resource |
35 | | [aws_caller_identity.aws_security_account](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/caller_identity) | data source |
36 | | [aws_iam_policy_document.policy_kms_document](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/iam_policy_document) | data source |
37 | | [aws_iam_policy_document.secrets_resource_policy](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/iam_policy_document) | data source |
38 | | [aws_organizations_organization.org](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/organizations_organization) | data source |
39 |
40 | ## Inputs
41 |
42 | | Name | Description | Type | Default | Required |
43 | |------|-------------|------|---------|:--------:|
44 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no |
45 | | [identifier](#input\_identifier) | Account Identifier. | `string` | `"security-account"` | no |
46 | | [network\_supernet](#input\_network\_supernet) | Network supernet. | `string` | `"10.0.0.0/16"` | no |
47 | | [secret\_name](#input\_secret\_name) | AWS Secrets Manager secret name. | `string` | `"security-account-firewall-policy-arn"` | no |
48 |
49 | ## Outputs
50 |
51 | No outputs.
52 |
--------------------------------------------------------------------------------
/multi-account/networking-account/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Networking AWS Account
3 |
4 | ## Requirements
5 |
6 | | Name | Version |
7 | |------|---------|
8 | | [terraform](#requirement\_terraform) | >= 1.3.0 |
9 | | [aws](#requirement\_aws) | = 5.16.2 |
10 |
11 | ## Providers
12 |
13 | | Name | Version |
14 | |------|---------|
15 | | [aws](#provider\_aws) | = 5.16.2 |
16 |
17 | ## Modules
18 |
19 | | Name | Source | Version |
20 | |------|--------|---------|
21 | | [hubspoke](#module\_hubspoke) | aws-ia/network-hubandspoke/aws | 3.2.0 |
22 | | [ipam](#module\_ipam) | aws-ia/ipam/aws | 2.0.0 |
23 |
24 | ## Resources
25 |
26 | | Name | Type |
27 | |------|------|
28 | | [aws_kms_key.secrets_key](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/kms_key) | resource |
29 | | [aws_ram_principal_association.principal_association](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/ram_principal_association) | resource |
30 | | [aws_ram_resource_association.transit_gateway_share](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/ram_resource_association) | resource |
31 | | [aws_ram_resource_share.resource_share](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/ram_resource_share) | resource |
32 | | [aws_secretsmanager_secret.ipam_pool](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/secretsmanager_secret) | resource |
33 | | [aws_secretsmanager_secret.spoke_attachments](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/secretsmanager_secret) | resource |
34 | | [aws_secretsmanager_secret.transit_gateway](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/secretsmanager_secret) | resource |
35 | | [aws_secretsmanager_secret_version.ipam_pool](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/secretsmanager_secret_version) | resource |
36 | | [aws_secretsmanager_secret_version.transit_gateway](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/resources/secretsmanager_secret_version) | resource |
37 | | [aws_caller_identity.aws_networking_account](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/caller_identity) | data source |
38 | | [aws_iam_policy_document.policy_kms_document](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/iam_policy_document) | data source |
39 | | [aws_iam_policy_document.secrets_resource_policy_read](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/iam_policy_document) | data source |
40 | | [aws_iam_policy_document.secrets_resource_policy_write](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/iam_policy_document) | data source |
41 | | [aws_organizations_organization.org](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/organizations_organization) | data source |
42 | | [aws_secretsmanager_secret.firewall_policy_arn](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/secretsmanager_secret) | data source |
43 | | [aws_secretsmanager_secret_version.firewall_policy_arn](https://registry.terraform.io/providers/hashicorp/aws/5.16.2/docs/data-sources/secretsmanager_secret_version) | data source |
44 |
45 | ## Inputs
46 |
47 | | Name | Description | Type | Default | Required |
48 | |------|-------------|------|---------|:--------:|
49 | | [security\_account](#input\_security\_account) | Security Account ID. | `string` | n/a | yes |
50 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no |
51 | | [identifier](#input\_identifier) | Account Identifier. | `string` | `"networking-account"` | no |
52 | | [secrets\_names](#input\_secrets\_names) | AWS Secrets Manager secrets name. | `map(string)` | {
"networking_account_attachments": "network-account-vpc-attachments",
"networking_account_ipam": "networking-account-ipam-pool-id",
"networking_account_tgw": "networking-account-transit-gateway-id",
"security_account": "security-account-firewall-policy-arn"
} | no |
53 |
54 | ## Outputs
55 |
56 | No outputs.
57 |
--------------------------------------------------------------------------------
/single-account/east_west/main.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/east_west/main.tf ---
5 |
6 | data "aws_availability_zones" "azs" {
7 | state = "available"
8 | }
9 |
10 | # ---------- HUB AND SPOKE WITH CENTRAL INSPECTION (AWS NETWORK FIREWALL) ----------
11 | # AWS Transit Gateway
12 | resource "aws_ec2_transit_gateway" "tgw" {
13 | description = "Transit Gateway - ${var.identifier}"
14 | default_route_table_association = "disable"
15 | default_route_table_propagation = "disable"
16 |
17 | tags = { Name = "tgw-${var.identifier}" }
18 | }
19 |
20 | # AWS Network Firewall (native attachment)
21 | resource "aws_networkfirewall_firewall" "anfw" {
22 | name = "anfw-${var.identifier}"
23 | firewall_policy_arn = aws_networkfirewall_firewall_policy.anfw_policy.arn
24 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id
25 |
26 | dynamic "availability_zone_mapping" {
27 | for_each = { for index, v in toset(data.aws_availability_zones.azs.zone_ids) : index => v }
28 | iterator = az
29 |
30 | content {
31 | availability_zone_id = az.value
32 | }
33 | }
34 | }
35 |
36 | # ---------- SPOKE VPCs ----------
37 | # Amazon VPC Module - https://registry.terraform.io/modules/aws-ia/vpc/aws/latest
38 | module "spoke_vpcs" {
39 | for_each = var.spoke_vpcs
40 | source = "aws-ia/vpc/aws"
41 | version = "= 4.5.0"
42 |
43 | name = each.key
44 | cidr_block = each.value.cidr_block
45 | az_count = each.value.number_azs
46 |
47 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id
48 | transit_gateway_routes = {
49 | workload = "0.0.0.0/0"
50 | }
51 |
52 | subnets = {
53 | workload = { netmask = each.value.workload_subnet_netmask }
54 | endpoints = { netmask = each.value.endpoint_subnet_netmask }
55 | transit_gateway = {
56 | netmask = each.value.tgw_subnet_netmask
57 | transit_gateway_default_route_table_association = false
58 | transit_gateway_default_route_table_propagation = false
59 | }
60 | }
61 |
62 | vpc_flow_logs = {
63 | log_destination_type = each.value.flow_log_config.log_destination_type
64 | retention_in_days = each.value.flow_log_config.retention_in_days
65 | iam_role_arn = module.iam_kms.vpc_flowlogs_role
66 | kms_key_id = module.iam_kms.kms_key
67 | }
68 | }
69 |
70 | # ---------- TRANSIT GATEWAY ROUTING ----------
71 | # Spoke Route Table
72 | # Spoke VPCs associated
73 | # Static route: 0.0.0.0/0 --> Inspection VPC attachment
74 | resource "aws_ec2_transit_gateway_route_table" "tgw_route_table_spoke" {
75 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id
76 |
77 | tags = { Name = "spoke-rt-${var.identifier}" }
78 | }
79 |
80 | resource "aws_ec2_transit_gateway_route_table_association" "tgw_spoke_association" {
81 | for_each = var.spoke_vpcs
82 |
83 | transit_gateway_attachment_id = module.spoke_vpcs[each.key].transit_gateway_attachment_id
84 | transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table_spoke.id
85 | }
86 |
87 | resource "aws_ec2_transit_gateway_route" "tgw_route_default_to_inspection" {
88 | destination_cidr_block = "0.0.0.0/0"
89 | transit_gateway_attachment_id = aws_networkfirewall_firewall.anfw.firewall_status[0].transit_gateway_attachment_sync_states[0].attachment_id
90 | transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table_spoke.id
91 | }
92 |
93 | # Inspection Route Table
94 | # Inspection VPC associated
95 | # Spoke VPCs propagating
96 | resource "aws_ec2_transit_gateway_route_table" "tgw_route_table_inspection" {
97 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id
98 |
99 | tags = { Name = "inspection-rt-${var.identifier}" }
100 | }
101 |
102 | resource "aws_ec2_transit_gateway_route_table_association" "tgw_inspection_association" {
103 | transit_gateway_attachment_id = aws_networkfirewall_firewall.anfw.firewall_status[0].transit_gateway_attachment_sync_states[0].attachment_id
104 | transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table_inspection.id
105 | }
106 |
107 | resource "aws_ec2_transit_gateway_route_table_propagation" "tgw_spoke_propagation_inspection" {
108 | for_each = var.spoke_vpcs
109 |
110 | transit_gateway_attachment_id = module.spoke_vpcs[each.key].transit_gateway_attachment_id
111 | transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table_inspection.id
112 | }
113 |
114 | # ---------- EC2 INSTANCES & EC2 INSTANCE CONNECT ENDPOINT ----------
115 | module "compute" {
116 | source = "../modules/compute"
117 | for_each = module.spoke_vpcs
118 |
119 | identifier = var.identifier
120 | vpc_name = each.key
121 | vpc = each.value
122 | vpc_information = var.spoke_vpcs[each.key]
123 | }
124 |
125 | # ---------- IAM ROLE (SSM ACCESS & VPC FLOW LOGS) AND KMS KEY (VPC FLOW LOGS) ----------
126 | module "iam_kms" {
127 | source = "../modules/iam_kms"
128 |
129 | identifier = var.identifier
130 | aws_region = var.aws_region
131 | }
--------------------------------------------------------------------------------
/multi-account/README.md:
--------------------------------------------------------------------------------
1 | # AWS Hub and Spoke Architecture with an Inspection VPC - Multi-AWS Accounts
2 |
3 | This repository contains terraform code to deploy a sample AWS Hub and Spoke architecture with an Inspection VPC using AWS Network Firewall - in a multi-Account environment. The resources deployed and the architectural pattern they follow is purely for demonstration/testing purposes.
4 |
5 | ## Prerequisites
6 | * Three AWS Accounts with an IAM user with the appropriate permissions
7 | * The AWS Accounts should be part of the same AWS Organizations
8 | * Amazon VPC IPAM should be enabled in the AWS Organization
9 | * The *Networking Account* should be configured as the delegated Account to manage VPC IPAM.
10 | * Check the [VPC IPAM documentation](https://docs.aws.amazon.com/vpc/latest/ipam/enable-integ-ipam.html) for more information about the required configuration.
11 | * Terraform installed
12 |
13 | ## Code Principles:
14 | * Writing DRY (Do No Repeat Yourself) code using a modular design pattern
15 |
16 | ## Usage
17 | * Clone the repository
18 | * Edit the variables.tf file in each of the directories (*security-account*, *networking-account*, *spoke-account*). Each file contains the information used to configure the Terraform code in each AWS Account.
19 |
20 | **Note** EC2 instances, and AWS Network Firewall endpoints will be deployed in all the Availability Zones configured for each VPC. Keep this in mind when testing this environment from a cost perspective - for production environments, we recommend the use of at least 2 AZs for high-availability.
21 |
22 | ## Target Architecture
23 |
24 | 
25 |
26 | ## Deployment
27 |
28 | * `terraform init` in each folder to download Terraform provider and modules.
29 | * Step 1: Deploy the resources of the *Security Account*
30 | * Move to the specific folder - `cd security-account`
31 | * Deploy the resources using `terraform apply`
32 | * Step 2: Deploy the resources of the *Networking Account*
33 | * Move to the specific folder - `cd networking-account`
34 | * This folder needs the Account ID of the *Security Account* (var.security_account) to obtain the Secrets Manager secret containing the Network Firewall Policy ID. We recommend the use of a [.tfvars file](https://developer.hashicorp.com/terraform/language/values/variables#variable-definitions-tfvars-files:~:text=several%20different%20variables.-,Variable%20Definitions%20(.tfvars)%20Files,-To%20set%20lots) to pass this value.
35 | * Deploy the resources using `terraform apply`
36 | * The Transit Gateway routing (Spoke VPCs) will be deployed after we deploy the resources from the *Spoke Account*
37 | * Step 3: Deploy the resources of the *Spoke Account*
38 | * Move to the specific folder - `cd spoke-account`
39 | * This folder needs the Account ID of the *Networking Account* (var.networking_account) to obtain the Secrets Manager secrets containing the Transit Gateway ID and VPC IPAM pool ID. We recommend the use of a [.tfvars file](https://developer.hashicorp.com/terraform/language/values/variables#variable-definitions-tfvars-files:~:text=several%20different%20variables.-,Variable%20Definitions%20(.tfvars)%20Files,-To%20set%20lots) to pass this value.
40 | * Deploy the resources using `terraform apply`
41 | * Step 4: Creating the Spoke VPC attachments routing in the *Networking Account*
42 | * Move to the specific folder - `cd networking-account`
43 | * Uncomment lines 48 - 54 & 95 - 99 in **main.tf** file.
44 | * These lines will get the information of the Spoke VPC attachments - shared via Secrets Manager by the *Spoke Account* - and create the corresponding Transit Gateway resources (route tables, associations, propagations, static routes)
45 | * `terraform apply` will create the resources.
46 |
47 | ## Clean-up
48 |
49 | * Step 1: Remove the Transit Gateway routing configuration
50 | * Move to the specific folder - `cd networking-account`
51 | * Comment lines 48 - 54 & 95 - 99 in **main.tf** file.
52 | * Removing these resources (and data sources) will remove the Transit Gateway routing, allowing later to destroy the VPC attachments in the *Spoke Account*
53 | * `terraform apply` will destroy the resources
54 | * Step 2: Clean-up *Spoke Account*.
55 | * Move to the specific folder - `cd spoke-account`
56 | * `terraform destroy` will clean-up all the resources in the Account.
57 | * Step 3: Clean-up *Networking Account*
58 | * Move to the specific folder - `cd networking-account`
59 | * `terraform destroy` will clean-up all the resources in the Account.
60 | * Step 4: Clean-up *Security Account*
61 | * Move to the specific folder - `cd security-account`
62 | * `terraform destroy` will clean-up all the resources in the Account.
63 |
64 | ## AWS Network Firewall Policy
65 |
66 | The AWS Network Firewall Policy is defined in the *policy.tf* file in the network_firewall module directory. By default:
67 |
68 | * All the SSH and RDP traffic is blocked by the Stateless engine.
69 | * The Stateful engine follows Strict Rule Ordering, blocking all the traffic by default. The only rule group allow HTTPS traffic to any **.amazon.com* domain.
70 |
71 | ## Security
72 |
73 | See [CONTRIBUTING](../CONTRIBUTING.md) for more information.
74 |
75 | ## License
76 |
77 | This library is licensed under the MIT-0 License. See the [LICENSE](../LICENSE) file.
--------------------------------------------------------------------------------
/single-account/east_west/README.md:
--------------------------------------------------------------------------------
1 |
2 | # AWS Hub and Spoke Architecture with an Inspection VPC - Single AWS Account (East-West)
3 |
4 | ## Prerequisites
5 | * An AWS account with an IAM user with the appropriate permissions
6 | * Terraform installed
7 |
8 | ## Code Principles:
9 | * Writing DRY (Do No Repeat Yourself) code using a modular design pattern
10 |
11 | ## Usage
12 | * Clone the repository
13 | * Edit the variables.tf file in the project root directory. This file contains the information used to configure the Terraform code.
14 |
15 | **Note** EC2 instances will be deployed in all the Availability Zones configured for each VPC, and AWS Network Firewall will be deployed in all the AWS Region's Availability Zones. Keep this in mind when testing this environment from a cost perspective - for production environments, we recommend the use of at least 2 AZs for high-availability.
16 |
17 | ## Target Architecture
18 |
19 | 
20 |
21 | ## Deployment
22 |
23 | * `terraform init` to initialize the environment.
24 | * `terraform plan` to check the resources to create
25 | * `terraform apply` to build the architecture.
26 |
27 | ## Clean-up
28 |
29 | * `terraform destroy` will clean-up the resources created.
30 |
31 | ## Requirements
32 |
33 | | Name | Version |
34 | |------|---------|
35 | | [terraform](#requirement\_terraform) | >= 1.3.0 |
36 | | [aws](#requirement\_aws) | > 5.0.0 |
37 |
38 | ## Providers
39 |
40 | | Name | Version |
41 | |------|---------|
42 | | [aws](#provider\_aws) | > 5.0.0 |
43 |
44 | ## Modules
45 |
46 | | Name | Source | Version |
47 | |------|--------|---------|
48 | | [compute](#module\_compute) | ../modules/compute | n/a |
49 | | [iam\_kms](#module\_iam\_kms) | ../modules/iam_kms | n/a |
50 | | [spoke\_vpcs](#module\_spoke\_vpcs) | aws-ia/vpc/aws | = 4.5.0 |
51 |
52 | ## Resources
53 |
54 | | Name | Type |
55 | |------|------|
56 | | [aws_ec2_transit_gateway.tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway) | resource |
57 | | [aws_ec2_transit_gateway_route.tgw_route_default_to_inspection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route) | resource |
58 | | [aws_ec2_transit_gateway_route_table.tgw_route_table_inspection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table) | resource |
59 | | [aws_ec2_transit_gateway_route_table.tgw_route_table_spoke](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table) | resource |
60 | | [aws_ec2_transit_gateway_route_table_association.tgw_inspection_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_association) | resource |
61 | | [aws_ec2_transit_gateway_route_table_association.tgw_spoke_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_association) | resource |
62 | | [aws_ec2_transit_gateway_route_table_propagation.tgw_spoke_propagation_inspection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource |
63 | | [aws_networkfirewall_firewall.anfw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkfirewall_firewall) | resource |
64 | | [aws_networkfirewall_firewall_policy.anfw_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkfirewall_firewall_policy) | resource |
65 | | [aws_networkfirewall_rule_group.allow_icmp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkfirewall_rule_group) | resource |
66 | | [aws_networkfirewall_rule_group.drop_remote](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkfirewall_rule_group) | resource |
67 | | [aws_availability_zones.azs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
68 |
69 | ## Inputs
70 |
71 | | Name | Description | Type | Default | Required |
72 | |------|-------------|------|---------|:--------:|
73 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no |
74 | | [identifier](#input\_identifier) | Name of the project. | `string` | `"singleaccount-eastwest"` | no |
75 | | [inspection\_vpc](#input\_inspection\_vpc) | Inspection VPC definition. | `any` | {
"cidr_block": "10.129.0.0/24",
"number_azs": 2,
"private_subnet_netmask": 28,
"public_subnet_netmask": 28,
"tgw_subnet_netmask": 28
} | no |
76 | | [spoke\_vpcs](#input\_spoke\_vpcs) | Spoke VPCs definition. | `any` | {
"spoke-vpc-1": {
"cidr_block": "10.0.0.0/24",
"endpoint_subnet_netmask": 28,
"flow_log_config": {
"log_destination_type": "cloud-watch-logs",
"retention_in_days": 7
},
"instance_type": "t2.micro",
"number_azs": 2,
"tgw_subnet_netmask": 28,
"workload_subnet_netmask": 28
},
"spoke-vpc-2": {
"cidr_block": "10.0.1.0/24",
"endpoint_subnet_netmask": 28,
"flow_log_config": {
"log_destination_type": "cloud-watch-logs",
"retention_in_days": 7
},
"instance_type": "t2.micro",
"number_azs": 2,
"tgw_subnet_netmask": 28,
"workload_subnet_netmask": 28
}
} | no |
77 |
78 | ## Outputs
79 |
80 | No outputs.
81 |
--------------------------------------------------------------------------------
/single-account/centralized_egress/README.md:
--------------------------------------------------------------------------------
1 |
2 | # AWS Hub and Spoke Architecture with an Inspection VPC - Single AWS Account (Centralized Egress)
3 |
4 | ## Prerequisites
5 | * An AWS account with an IAM user with the appropriate permissions
6 | * Terraform installed
7 |
8 | ## Code Principles:
9 | * Writing DRY (Do No Repeat Yourself) code using a modular design pattern
10 |
11 | ## Usage
12 | * Clone the repository
13 | * Edit the variables.tf file in the project root directory. This file contains the information used to configure the Terraform code.
14 |
15 | **Note** EC2 instances, and AWS Network Firewall endpoints will be deployed in all the Availability Zones configured for each VPC. Keep this in mind when testing this environment from a cost perspective - for production environments, we recommend the use of at least 2 AZs for high-availability.
16 |
17 | ## Target Architecture
18 |
19 | 
20 |
21 | ## Deployment
22 |
23 | * `terraform init` to initialize the environment.
24 | * `terraform plan` to check the resources to create
25 | * `terraform apply` to build the architecture.
26 |
27 | ## Clean-up
28 |
29 | * `terraform destroy` will clean-up the resources created.
30 |
31 | ## Requirements
32 |
33 | | Name | Version |
34 | |------|---------|
35 | | [terraform](#requirement\_terraform) | >= 1.3.0 |
36 | | [aws](#requirement\_aws) | > 5.0.0 |
37 |
38 | ## Providers
39 |
40 | | Name | Version |
41 | |------|---------|
42 | | [aws](#provider\_aws) | > 5.0.0 |
43 |
44 | ## Modules
45 |
46 | | Name | Source | Version |
47 | |------|--------|---------|
48 | | [compute](#module\_compute) | ../modules/compute | n/a |
49 | | [iam\_kms](#module\_iam\_kms) | ../modules/iam_kms | n/a |
50 | | [inspection\_vpc](#module\_inspection\_vpc) | aws-ia/vpc/aws | 4.5.0 |
51 | | [network\_firewall](#module\_network\_firewall) | aws-ia/networkfirewall/aws | 1.0.2 |
52 | | [spoke\_vpcs](#module\_spoke\_vpcs) | aws-ia/vpc/aws | = 4.5.0 |
53 |
54 | ## Resources
55 |
56 | | Name | Type |
57 | |------|------|
58 | | [aws_ec2_transit_gateway.tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway) | resource |
59 | | [aws_ec2_transit_gateway_route.tgw_route_default_to_inspection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route) | resource |
60 | | [aws_ec2_transit_gateway_route_table.tgw_route_table_inspection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table) | resource |
61 | | [aws_ec2_transit_gateway_route_table.tgw_route_table_spoke](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table) | resource |
62 | | [aws_ec2_transit_gateway_route_table_association.tgw_inspection_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_association) | resource |
63 | | [aws_ec2_transit_gateway_route_table_association.tgw_spoke_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_association) | resource |
64 | | [aws_ec2_transit_gateway_route_table_propagation.tgw_spoke_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource |
65 | | [aws_ec2_transit_gateway_route_table_propagation.tgw_spoke_propagation_inspection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource |
66 | | [aws_networkfirewall_firewall_policy.anfw_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkfirewall_firewall_policy) | resource |
67 | | [aws_networkfirewall_rule_group.allow_domains](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkfirewall_rule_group) | resource |
68 | | [aws_networkfirewall_rule_group.drop_remote](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkfirewall_rule_group) | resource |
69 |
70 | ## Inputs
71 |
72 | | Name | Description | Type | Default | Required |
73 | |------|-------------|------|---------|:--------:|
74 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no |
75 | | [identifier](#input\_identifier) | Name of the project. | `string` | `"singleaccount-centralizedegress"` | no |
76 | | [inspection\_vpc](#input\_inspection\_vpc) | Inspection VPC definition. | `any` | {
"cidr_block": "10.129.0.0/24",
"number_azs": 2,
"private_subnet_netmask": 28,
"public_subnet_netmask": 28,
"tgw_subnet_netmask": 28
} | no |
77 | | [spoke\_vpcs](#input\_spoke\_vpcs) | Spoke VPCs definition. | `any` | {
"spoke-vpc-1": {
"cidr_block": "10.0.0.0/24",
"endpoint_subnet_netmask": 28,
"flow_log_config": {
"log_destination_type": "cloud-watch-logs",
"retention_in_days": 7
},
"instance_type": "t2.micro",
"number_azs": 2,
"tgw_subnet_netmask": 28,
"workload_subnet_netmask": 28
},
"spoke-vpc-2": {
"cidr_block": "10.0.1.0/24",
"endpoint_subnet_netmask": 28,
"flow_log_config": {
"log_destination_type": "cloud-watch-logs",
"retention_in_days": 7
},
"instance_type": "t2.micro",
"number_azs": 2,
"tgw_subnet_netmask": 28,
"workload_subnet_netmask": 28
}
} | no |
78 |
79 | ## Outputs
80 |
81 | | Name | Description |
82 | |------|-------------|
83 | | [instances](#output\_instances) | EC2 instances created. |
84 | | [network\_firewall](#output\_network\_firewall) | AWS Network Firewall ID. |
85 | | [transit\_gateway\_id](#output\_transit\_gateway\_id) | AWS Transit Gateway ID. |
86 | | [transit\_gateway\_route\_tables](#output\_transit\_gateway\_route\_tables) | Transit Gateway Route Table. |
87 | | [vpcs](#output\_vpcs) | VPCs created. |
88 |
--------------------------------------------------------------------------------
/single-account/centralized_egress/main.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- single-account/centralized_egress/main.tf ---
5 |
6 | # ---------- HUB AND SPOKE WITH CENTRAL INSPECTION (AWS NETWORK FIREWALL) ----------
7 | # AWS Transit Gateway
8 | resource "aws_ec2_transit_gateway" "tgw" {
9 | description = "Transit Gateway - ${var.identifier}"
10 | default_route_table_association = "disable"
11 | default_route_table_propagation = "disable"
12 |
13 | tags = { Name = "tgw-${var.identifier}" }
14 | }
15 |
16 |
17 | # Inspection VPC - https://registry.terraform.io/modules/aws-ia/vpc/aws/latest
18 | module "inspection_vpc" {
19 | source = "aws-ia/vpc/aws"
20 | version = "4.5.0"
21 |
22 | name = "inspection-vpc-${var.identifier}"
23 | cidr_block = "100.64.0.0/24"
24 | az_count = 2
25 |
26 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id
27 | transit_gateway_routes = { endpoints = "10.0.0.0/16" }
28 |
29 | subnets = {
30 | public = {
31 | netmask = var.inspection_vpc.public_subnet_netmask
32 | nat_gateway_configuration = "all_azs"
33 | }
34 | endpoints = {
35 | netmask = var.inspection_vpc.private_subnet_netmask
36 | connect_to_public_natgw = true
37 | }
38 | transit_gateway = {
39 | netmask = var.inspection_vpc.tgw_subnet_netmask
40 | transit_gateway_default_route_table_association = false
41 | transit_gateway_default_route_table_propagation = false
42 | transit_gateway_appliance_mode_support = "enable"
43 | }
44 | }
45 | }
46 |
47 | # AWS Network Firewall (and related routing) - https://registry.terraform.io/modules/aws-ia/networkfirewall/aws/latest
48 | module "network_firewall" {
49 | source = "aws-ia/networkfirewall/aws"
50 | version = "1.0.2"
51 |
52 | network_firewall_name = "anfw-${var.identifier}"
53 | network_firewall_description = "AWS Network Firewall - ${var.identifier}"
54 | network_firewall_policy = aws_networkfirewall_firewall_policy.anfw_policy.arn
55 |
56 | vpc_id = module.inspection_vpc.vpc_attributes.id
57 | number_azs = var.inspection_vpc.number_azs
58 | vpc_subnets = { for k, v in module.inspection_vpc.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "endpoints" }
59 |
60 | routing_configuration = {
61 | centralized_inspection_with_egress = {
62 | connectivity_subnet_route_tables = { for k, v in module.inspection_vpc.rt_attributes_by_type_by_az.transit_gateway : k => v.id }
63 | public_subnet_route_tables = { for k, v in module.inspection_vpc.rt_attributes_by_type_by_az.public : k => v.id }
64 | network_cidr_blocks = ["10.0.0.0/16"]
65 | }
66 | }
67 | }
68 |
69 | # ---------- SPOKE VPCs ----------
70 | # Amazon VPC Module - https://registry.terraform.io/modules/aws-ia/vpc/aws/latest
71 | module "spoke_vpcs" {
72 | for_each = var.spoke_vpcs
73 | source = "aws-ia/vpc/aws"
74 | version = "= 4.5.0"
75 |
76 | name = each.key
77 | cidr_block = each.value.cidr_block
78 | az_count = each.value.number_azs
79 |
80 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id
81 | transit_gateway_routes = {
82 | workload = "0.0.0.0/0"
83 | }
84 |
85 | subnets = {
86 | workload = { netmask = each.value.workload_subnet_netmask }
87 | endpoints = { netmask = each.value.endpoint_subnet_netmask }
88 | transit_gateway = {
89 | netmask = each.value.tgw_subnet_netmask
90 | transit_gateway_default_route_table_association = false
91 | transit_gateway_default_route_table_propagation = false
92 | }
93 | }
94 |
95 | vpc_flow_logs = {
96 | log_destination_type = each.value.flow_log_config.log_destination_type
97 | retention_in_days = each.value.flow_log_config.retention_in_days
98 | iam_role_arn = module.iam_kms.vpc_flowlogs_role
99 | kms_key_id = module.iam_kms.kms_key
100 | }
101 | }
102 |
103 | # ---------- TRANSIT GATEWAY ROUTING ----------
104 | # Spoke Route Table
105 | # Spoke VPCs associated and propagating
106 | # Static route: 0.0.0.0/0 --> Inspection VPC attachment
107 | resource "aws_ec2_transit_gateway_route_table" "tgw_route_table_spoke" {
108 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id
109 |
110 | tags = { Name = "spoke-rt-${var.identifier}" }
111 | }
112 |
113 | resource "aws_ec2_transit_gateway_route_table_association" "tgw_spoke_association" {
114 | for_each = var.spoke_vpcs
115 |
116 | transit_gateway_attachment_id = module.spoke_vpcs[each.key].transit_gateway_attachment_id
117 | transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table_spoke.id
118 | }
119 |
120 | resource "aws_ec2_transit_gateway_route_table_propagation" "tgw_spoke_propagation" {
121 | for_each = var.spoke_vpcs
122 |
123 | transit_gateway_attachment_id = module.spoke_vpcs[each.key].transit_gateway_attachment_id
124 | transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table_spoke.id
125 | }
126 |
127 | resource "aws_ec2_transit_gateway_route" "tgw_route_default_to_inspection" {
128 | destination_cidr_block = "0.0.0.0/0"
129 | transit_gateway_attachment_id = module.inspection_vpc.transit_gateway_attachment_id
130 | transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table_spoke.id
131 | }
132 |
133 | # Inspection Route Table
134 | # Inspection VPC associated
135 | # Spoke VPCs propagating
136 | resource "aws_ec2_transit_gateway_route_table" "tgw_route_table_inspection" {
137 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id
138 |
139 | tags = { Name = "inspection-rt-${var.identifier}" }
140 | }
141 |
142 | resource "aws_ec2_transit_gateway_route_table_association" "tgw_inspection_association" {
143 | transit_gateway_attachment_id = module.inspection_vpc.transit_gateway_attachment_id
144 | transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table_inspection.id
145 | }
146 |
147 | resource "aws_ec2_transit_gateway_route_table_propagation" "tgw_spoke_propagation_inspection" {
148 | for_each = var.spoke_vpcs
149 |
150 | transit_gateway_attachment_id = module.spoke_vpcs[each.key].transit_gateway_attachment_id
151 | transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table_inspection.id
152 | }
153 |
154 | # ---------- EC2 INSTANCES & EC2 INSTANCE CONNECT ENDPOINT ----------
155 | module "compute" {
156 | source = "../modules/compute"
157 | for_each = module.spoke_vpcs
158 |
159 | identifier = var.identifier
160 | vpc_name = each.key
161 | vpc = each.value
162 | vpc_information = var.spoke_vpcs[each.key]
163 | }
164 |
165 | # ---------- IAM ROLE (SSM ACCESS & VPC FLOW LOGS) AND KMS KEY (VPC FLOW LOGS) ----------
166 | module "iam_kms" {
167 | source = "../modules/iam_kms"
168 |
169 | identifier = var.identifier
170 | aws_region = var.aws_region
171 | }
--------------------------------------------------------------------------------
/multi-account/security-account/main.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- security-account/main.tf ---
5 |
6 | # ---------- AWS ORGANIZATIONS AND ACCOUNT INFORMATION ----------
7 | data "aws_caller_identity" "aws_security_account" {}
8 | data "aws_organizations_organization" "org" {}
9 |
10 | # ---------- NETWORK FIREWALL POLICY ----------
11 | # Firewall policy
12 | resource "aws_networkfirewall_firewall_policy" "central_inspection_policy" {
13 | name = "central-firewall-policy-${var.identifier}"
14 |
15 | firewall_policy {
16 | # Stateless configuration
17 | stateless_default_actions = ["aws:forward_to_sfe"]
18 | stateless_fragment_default_actions = ["aws:forward_to_sfe"]
19 |
20 | stateless_rule_group_reference {
21 | priority = 10
22 | resource_arn = aws_networkfirewall_rule_group.drop_remote.arn
23 | }
24 |
25 | # Stateful configuration
26 | stateful_engine_options {
27 | rule_order = "STRICT_ORDER"
28 | }
29 | stateful_default_actions = ["aws:drop_strict", "aws:alert_strict"]
30 | stateful_rule_group_reference {
31 | priority = 10
32 | resource_arn = aws_networkfirewall_rule_group.allow_tcp.arn
33 | }
34 | stateful_rule_group_reference {
35 | priority = 20
36 | resource_arn = aws_networkfirewall_rule_group.allow_domains.arn
37 | }
38 | }
39 | }
40 |
41 | # Stateless Rule Group - Dropping SSH traffic
42 | resource "aws_networkfirewall_rule_group" "drop_remote" {
43 | capacity = 2
44 | name = "drop-remote-${var.identifier}"
45 | type = "STATELESS"
46 | rule_group {
47 | rules_source {
48 | stateless_rules_and_custom_actions {
49 |
50 | stateless_rule {
51 | priority = 1
52 | rule_definition {
53 | actions = ["aws:drop"]
54 | match_attributes {
55 | protocols = [6]
56 | source {
57 | address_definition = "0.0.0.0/0"
58 | }
59 | source_port {
60 | from_port = 0
61 | to_port = 65535
62 | }
63 | destination {
64 | address_definition = "0.0.0.0/0"
65 | }
66 | destination_port {
67 | from_port = 22
68 | to_port = 22
69 | }
70 | }
71 | }
72 | }
73 | }
74 | }
75 | }
76 | }
77 |
78 | # Stateful Rule Group - Allowing TCP traffic to port 443 (HTTPS)
79 | resource "aws_networkfirewall_rule_group" "allow_tcp" {
80 | capacity = 1
81 | name = "allow-tcp-${var.identifier}"
82 | type = "STATEFUL"
83 | rule_group {
84 | rule_variables {
85 | ip_sets {
86 | key = "NETWORK"
87 | ip_set {
88 | definition = [var.network_supernet]
89 | }
90 | }
91 | }
92 | rules_source {
93 | rules_string = < $EXTERNAL_NET 443 (msg:"Allowing TCP in port 443"; flow:not_established; sid:892123; rev:1;)
95 | EOF
96 | }
97 | stateful_rule_options {
98 | rule_order = "STRICT_ORDER"
99 | }
100 | }
101 | }
102 |
103 |
104 | # Stateful Rule Group - Allowing access to .amazon.com (HTTPS)
105 | resource "aws_networkfirewall_rule_group" "allow_domains" {
106 | capacity = 100
107 | name = "allow-domains-${var.identifier}"
108 | type = "STATEFUL"
109 | rule_group {
110 | rule_variables {
111 | ip_sets {
112 | key = "HOME_NET"
113 | ip_set {
114 | definition = [var.network_supernet]
115 | }
116 | }
117 | }
118 | rules_source {
119 | rules_source_list {
120 | generated_rules_type = "ALLOWLIST"
121 | target_types = ["TLS_SNI"]
122 | targets = [".amazon.com"]
123 | }
124 | }
125 | stateful_rule_options {
126 | rule_order = "STRICT_ORDER"
127 | }
128 | }
129 | }
130 |
131 | # ---------- AWS RAM SHARE ----------
132 | # Resource Share
133 | resource "aws_ram_resource_share" "resource_share" {
134 | name = "Security Account Resource Share"
135 | allow_external_principals = false
136 | }
137 |
138 | # Principal Association
139 | resource "aws_ram_principal_association" "principal_association" {
140 | principal = data.aws_organizations_organization.org.arn
141 | resource_share_arn = aws_ram_resource_share.resource_share.arn
142 | }
143 |
144 | # Resource Association - AWS Transit Gateway
145 | resource "aws_ram_resource_association" "firewall_policy_share" {
146 | resource_arn = aws_networkfirewall_firewall_policy.central_inspection_policy.arn
147 | resource_share_arn = aws_ram_resource_share.resource_share.arn
148 | }
149 |
150 | # ---------- AWS SECRETS MANAGER ----------
151 | # Firewall Policy ARN Secret
152 | resource "aws_secretsmanager_secret" "firewall_policy_arn" {
153 | name = var.secret_name
154 | description = "Firewall Policy ARN (Security Account)."
155 | kms_key_id = aws_kms_key.secrets_key.arn
156 | policy = data.aws_iam_policy_document.secrets_resource_policy.json
157 | recovery_window_in_days = 0
158 | }
159 |
160 | resource "aws_secretsmanager_secret_version" "firewall_policy_arn" {
161 | secret_id = aws_secretsmanager_secret.firewall_policy_arn.id
162 | secret_string = aws_networkfirewall_firewall_policy.central_inspection_policy.arn
163 | }
164 |
165 | # Secrets resource policy - Allowing the AWS Organization to read the secret
166 | data "aws_iam_policy_document" "secrets_resource_policy" {
167 | statement {
168 | actions = [
169 | "secretsmanager:GetSecretValue",
170 | "secretsmanager:DescribeSecret",
171 | "secretsmanager:GetResourcePolicy"
172 | ]
173 | resources = ["*"]
174 |
175 | principals {
176 | type = "AWS"
177 | identifiers = ["*"]
178 | }
179 |
180 | condition {
181 | test = "StringEquals"
182 | variable = "aws:PrincipalOrgID"
183 |
184 | values = ["${data.aws_organizations_organization.org.id}"]
185 | }
186 | }
187 | }
188 |
189 | # KMS Key to encrypt the secrets
190 | resource "aws_kms_key" "secrets_key" {
191 | description = "KMS Secrets Key - Security Account."
192 | deletion_window_in_days = 7
193 | enable_key_rotation = true
194 | policy = data.aws_iam_policy_document.policy_kms_document.json
195 |
196 | tags = {
197 | Name = "kms-key-${var.identifier}"
198 | }
199 | }
200 |
201 | # KMS Policy
202 | data "aws_iam_policy_document" "policy_kms_document" {
203 | statement {
204 | sid = "Enable AWS Secrets Manager secrets decryption."
205 | effect = "Allow"
206 | actions = [
207 | "kms:Decrypt",
208 | "kms:Encrypt",
209 | "kms:*"
210 | ]
211 | resources = ["*"]
212 |
213 | principals {
214 | type = "AWS"
215 | identifiers = ["*"]
216 | }
217 |
218 | condition {
219 | test = "StringEquals"
220 | variable = "kms:ViaService"
221 |
222 | values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
223 | }
224 | condition {
225 | test = "StringLike"
226 | variable = "kms:EncryptionContext:SecretARN"
227 |
228 | values = ["arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.aws_security_account.id}:secret:*"]
229 | }
230 | condition {
231 | test = "StringEquals"
232 | variable = "aws:PrincipalOrgID"
233 |
234 | values = ["${data.aws_organizations_organization.org.id}"]
235 | }
236 | }
237 |
238 | statement {
239 | sid = "Enable IAM User Permissions"
240 | actions = ["kms:*"]
241 | resources = ["arn:aws:kms:${var.aws_region}:${data.aws_caller_identity.aws_security_account.id}:*"]
242 |
243 | principals {
244 | type = "AWS"
245 | identifiers = ["arn:aws:iam::${data.aws_caller_identity.aws_security_account.id}:root"]
246 | }
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/multi-account/networking-account/main.tf:
--------------------------------------------------------------------------------
1 | /* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 | SPDX-License-Identifier: MIT-0 */
3 |
4 | # --- networking-account/main.tf ---
5 |
6 | # ---------- AWS ORGANIZATIONS AND ACCOUNT INFORMATION ----------
7 | data "aws_caller_identity" "aws_networking_account" {}
8 | data "aws_organizations_organization" "org" {}
9 |
10 | # ---------- AMAZON VPC IPAM ----------
11 | module "ipam" {
12 | source = "aws-ia/ipam/aws"
13 | version = "2.0.0"
14 |
15 | top_cidr = ["10.0.0.0/8"]
16 | address_family = "ipv4"
17 | create_ipam = true
18 | top_name = "Organization IPAM"
19 |
20 | pool_configurations = {
21 | ireland = {
22 | name = "ireland"
23 | description = "Ireland (eu-west-1) Region"
24 | netmask_length = 16
25 | locale = var.aws_region
26 |
27 | sub_pools = {
28 | spoke = {
29 | name = "spoke-accounts"
30 | netmask_length = 16
31 | ram_share_principals = [data.aws_organizations_organization.org.arn]
32 | }
33 | }
34 | }
35 | }
36 | }
37 |
38 | # ---------- HUB AND SPOKE ARCHITECTURE (WITH INSPECTION) --------
39 | # Obtaining Firewall Policy ARN from Secrets Manager secret (shared by Security Account)
40 | data "aws_secretsmanager_secret" "firewall_policy_arn" {
41 | arn = "arn:aws:secretsmanager:${var.aws_region}:${var.security_account}:secret:${var.secrets_names.security_account}"
42 | }
43 |
44 | data "aws_secretsmanager_secret_version" "firewall_policy_arn" {
45 | secret_id = data.aws_secretsmanager_secret.firewall_policy_arn.id
46 | }
47 |
48 | # data "aws_secretsmanager_secret_version" "spoke_attachments" {
49 | # secret_id = aws_secretsmanager_secret.spoke_attachments.id
50 | # }
51 |
52 | # locals {
53 | # spoke_vpc_information = jsondecode(data.aws_secretsmanager_secret_version.spoke_attachments.secret_string)["spoke_account"]
54 | # }
55 |
56 | # Hub and Spoke architecture
57 | module "hubspoke" {
58 | source = "aws-ia/network-hubandspoke/aws"
59 | version = "3.2.0"
60 |
61 | identifier = "hubspoke-${var.identifier}"
62 | transit_gateway_attributes = {
63 | name = "tgw-${var.identifier}"
64 | description = "Transit Gateway - ${var.identifier}"
65 | amazon_side_asn = 65050
66 | auto_accept_shared_attachments = "enable"
67 | }
68 |
69 | network_definition = {
70 | type = "CIDR"
71 | value = "10.0.0.0/16"
72 | }
73 |
74 | central_vpcs = {
75 | inspection = {
76 | name = "inspection-vpc-${var.aws_region}"
77 | cidr_block = "100.64.0.0/24"
78 | az_count = 2
79 | inspection_flow = "north-south"
80 |
81 | aws_network_firewall = {
82 | name = "anfw-${var.identifier}"
83 | description = "AWS Network Firewall - ${var.identifier}"
84 | policy_arn = data.aws_secretsmanager_secret_version.firewall_policy_arn.secret_string
85 | }
86 |
87 | subnets = {
88 | public = { netmask = 28 }
89 | endpoints = { netmask = 28 }
90 | transit_gateway = { netmask = 28 }
91 | }
92 | }
93 | }
94 |
95 | # spoke_vpcs = {
96 | # routing_domains = ["prod", "nonprod"]
97 | # number_vpcs = local.spoke_vpc_information.number_spoke_vpcs
98 | # vpc_information = local.spoke_vpc_information.vpc_information
99 | # }
100 |
101 | tags = {
102 | team = "networking"
103 | }
104 | }
105 |
106 | # ---------- AWS RAM (TRANSIT GATEWAY) ----------
107 | # Resource Share
108 | resource "aws_ram_resource_share" "resource_share" {
109 | name = "Transit Gateway Resource Share"
110 | allow_external_principals = false
111 | }
112 |
113 | # Principal Association
114 | resource "aws_ram_principal_association" "principal_association" {
115 | principal = data.aws_organizations_organization.org.arn
116 | resource_share_arn = aws_ram_resource_share.resource_share.arn
117 | }
118 |
119 | # Resource Association - AWS Transit Gateway
120 | resource "aws_ram_resource_association" "transit_gateway_share" {
121 | resource_arn = module.hubspoke.transit_gateway.arn
122 | resource_share_arn = aws_ram_resource_share.resource_share.arn
123 | }
124 |
125 | # ---------- AWS SECRETS MANAGER ----------
126 | # Transit Gateway Secret
127 | resource "aws_secretsmanager_secret" "transit_gateway" {
128 | name = var.secrets_names.networking_account_tgw
129 | description = "Transit Gateway (Networking Account)."
130 | kms_key_id = aws_kms_key.secrets_key.arn
131 | policy = data.aws_iam_policy_document.secrets_resource_policy_read.json
132 | recovery_window_in_days = 0
133 | }
134 |
135 | resource "aws_secretsmanager_secret_version" "transit_gateway" {
136 | secret_id = aws_secretsmanager_secret.transit_gateway.id
137 | secret_string = module.hubspoke.transit_gateway.id
138 | }
139 |
140 | # Spoke VPC Attachment information
141 | resource "aws_secretsmanager_secret" "spoke_attachments" {
142 | name = var.secrets_names.networking_account_attachments
143 | description = "VPC Attachments - Spoke Accounts."
144 | kms_key_id = aws_kms_key.secrets_key.arn
145 | policy = data.aws_iam_policy_document.secrets_resource_policy_write.json
146 | recovery_window_in_days = 0
147 | }
148 |
149 | # IPAM Pool Secret
150 | resource "aws_secretsmanager_secret" "ipam_pool" {
151 | name = var.secrets_names.networking_account_ipam
152 | description = "IPAM Pool (Networking Account)."
153 | kms_key_id = aws_kms_key.secrets_key.arn
154 | policy = data.aws_iam_policy_document.secrets_resource_policy_read.json
155 | recovery_window_in_days = 0
156 | }
157 |
158 | resource "aws_secretsmanager_secret_version" "ipam_pool" {
159 | secret_id = aws_secretsmanager_secret.ipam_pool.id
160 | secret_string = module.ipam.pools_level_2["ireland/spoke"].id
161 | }
162 |
163 | # Secrets resource policy - Allowing the AWS Organization to read the secret
164 | data "aws_iam_policy_document" "secrets_resource_policy_read" {
165 | statement {
166 | actions = [
167 | "secretsmanager:GetSecretValue",
168 | "secretsmanager:DescribeSecret",
169 | "secretsmanager:GetResourcePolicy"
170 | ]
171 | resources = ["*"]
172 |
173 | principals {
174 | type = "AWS"
175 | identifiers = ["*"]
176 | }
177 |
178 | condition {
179 | test = "StringEquals"
180 | variable = "aws:PrincipalOrgID"
181 |
182 | values = ["${data.aws_organizations_organization.org.id}"]
183 | }
184 | }
185 | }
186 |
187 | # Secrets resource policy - Allowing the Spoke AWS Account to write the secret
188 | data "aws_iam_policy_document" "secrets_resource_policy_write" {
189 | statement {
190 | actions = [
191 | "secretsmanager:GetSecretValue",
192 | "secretsmanager:PutSecretValue",
193 | "secretsmanager:DescribeSecret",
194 | "secretsmanager:GetResourcePolicy"
195 | ]
196 | resources = ["*"]
197 |
198 | principals {
199 | type = "AWS"
200 | identifiers = ["*"]
201 | }
202 |
203 | condition {
204 | test = "StringEquals"
205 | variable = "aws:PrincipalOrgID"
206 |
207 | values = ["${data.aws_organizations_organization.org.id}"]
208 | }
209 | }
210 | }
211 |
212 | # KMS Key to encrypt the secrets
213 | resource "aws_kms_key" "secrets_key" {
214 | description = "KMS Secrets Key - Security Account."
215 | deletion_window_in_days = 7
216 | enable_key_rotation = true
217 | policy = data.aws_iam_policy_document.policy_kms_document.json
218 |
219 | tags = {
220 | Name = "kms-key-${var.identifier}"
221 | }
222 | }
223 |
224 | # KMS Policy
225 | data "aws_iam_policy_document" "policy_kms_document" {
226 | statement {
227 | sid = "Enable AWS Secrets Manager secrets decryption."
228 | effect = "Allow"
229 | actions = [
230 | "kms:Decrypt",
231 | "kms:Encrypt",
232 | "kms:*"
233 | ]
234 | resources = ["*"]
235 |
236 | principals {
237 | type = "AWS"
238 | identifiers = ["*"]
239 | }
240 |
241 | condition {
242 | test = "StringEquals"
243 | variable = "kms:ViaService"
244 |
245 | values = ["secretsmanager.${var.aws_region}.amazonaws.com"]
246 | }
247 | condition {
248 | test = "StringLike"
249 | variable = "kms:EncryptionContext:SecretARN"
250 |
251 | values = ["arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.aws_networking_account.id}:secret:*"]
252 | }
253 | condition {
254 | test = "StringEquals"
255 | variable = "aws:PrincipalOrgID"
256 |
257 | values = ["${data.aws_organizations_organization.org.id}"]
258 | }
259 | }
260 |
261 | statement {
262 | sid = "Enable IAM User Permissions"
263 | actions = ["kms:*"]
264 | resources = ["arn:aws:kms:${var.aws_region}:${data.aws_caller_identity.aws_networking_account.id}:*"]
265 |
266 | principals {
267 | type = "AWS"
268 | identifiers = ["arn:aws:iam::${data.aws_caller_identity.aws_networking_account.id}:root"]
269 | }
270 | }
271 | }
--------------------------------------------------------------------------------
/images/diagrams.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
--------------------------------------------------------------------------------