├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── images
├── building-enforce-network-security.drawio
└── full-architecture.png
├── modules
├── retrieve_parameters
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ └── variables.tf
└── share_parameter
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ └── variables.tf
├── network
├── .header.md
├── .terraform-docs.yaml
├── README.md
├── automation.py
├── cloudwan_policy.tf
├── main.tf
├── modules
│ ├── automation
│ │ ├── automation.zip
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── providers.tf
│ │ └── variables.tf
│ ├── firewall_policy
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── providers.tf
│ │ └── variables.tf
│ └── vpc_endpoints
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── providers.tf
│ │ └── variables.tf
├── outputs.tf
├── providers.tf
└── variables.tf
└── spoke
├── .header.md
├── .terraform-docs.yaml
├── README.md
├── main.tf
├── modules
└── compute
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ └── variables.tf
├── outputs.tf
├── providers.tf
└── variables.tf
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/terraform
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=terraform
3 |
4 | ### Terraform ###
5 | # Local .terraform directories
6 | **/.terraform/*
7 |
8 | # .tfstate files
9 | *.tfstate
10 | *.tfstate.*
11 | *.lock.hcl
12 | # Crash log files
13 | crash.log
14 |
15 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as
16 | # password, private keys, and other secrets. These should not be part of version
17 | # control as they are data points which are potentially sensitive and subject
18 | # to change depending on the environment.
19 | #
20 | *.tfvars
21 |
22 | # Ignore override files as they are usually used to override resources locally and so
23 | # are not checked in
24 | override.tf
25 | override.tf.json
26 | *_override.tf
27 | *_override.tf.json
28 |
29 | # Include override files you do wish to add to version control using negated pattern
30 | # !example_override.tf
31 |
32 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
33 | # example: *tfplan*
34 |
35 | # Ignore CLI configuration files
36 | .terraformrc
37 | terraform.rc
38 |
39 | # End of https://www.toptal.com/developers/gitignore/api/terraform
40 |
41 | # macOs
42 | .DS_Store
43 |
44 | # Project
45 | keys/
46 |
47 | gcm-diagnose.log
48 |
49 | **/client_vpn_config/*
50 |
51 | *.bkp
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT No Attribution
2 |
3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so.
10 |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 |
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Build and enforce network security in multi-Account environments
2 |
3 | In this repository, you will leverage [Amazon GuardDuty](https://aws.amazon.com/guardduty/) findings to automate actions in global networks leveraging [AWS Cloud WAN](https://aws.amazon.com/cloud-wan/). The Infrastructure-as-Code framework used is **Terraform**. The main idea is to build an automation that updates Cloud WAN VPC attachment tags to move the VPC to a different security level depending the GuardDuty finding severity.
4 |
5 | 
6 |
7 | This example requires the use of 2 AWS Accounts: one for the central Networking, Security and automation resources; and another one for the spoke VPCs. The following resources are created in 3 AWS Regions (**us-east-1**, **us-east-2**, and **eu-west-1**):
8 |
9 | **Networking Account**
10 |
11 | * AWS Network Manager global network and AWS Cloud WAN core network. The core network policy builds the following segments:
12 | * `hub` segment is the default segment for any VPC connecting the network. VPCs can communicate between each other without restrictions, there's access to a Shared Services VPC, and egress traffic is sent to a central egress & inspection VPC.
13 | * `inspected` segment. Traffic between VPCs is sent to a central inspection VPC (also used for egress traffic). There's access to a Shared Services VPC.
14 | * `onlyshared` segment. Isolated segment, only access to Shared Services VPC.
15 | * `blocked` segment. Isolated segment, VPCs in this segment don't have any communication outside their own VPC.
16 | * Automation:
17 | * [Amazon EventBridge](https://aws.amazon.com/eventbridge/) rule invoking a Lambda function from GuardDuty findings.
18 | * [AWS Lambda](https://aws.amazon.com/pm/lambda/) function changing VPC Cloud WAN attachment tags depending the GuardDuty finding obtained.
19 | * [Amazon VPC IPAM](https://docs.aws.amazon.com/vpc/latest/ipam/what-it-is-ipam.html) to provide CIDR blocks to the VPCs created in the spoke AWS Account.
20 | * In each AWS Region, two central VPCs are created:
21 | * Inspection & Egress VPC, with [AWS Network Firewall](https://aws.amazon.com/network-firewall/) as inspection layer.
22 | * Shared Services VPC, with central [Amazon S3](https://aws.amazon.com/pm/serv-s3/) VPC endpoint access.
23 | * [Amazon Route 53 Profiles](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/profiles.html) to share [Private Hosted Zones](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html) with the S3 VPC endpoint resolution.
24 | * [AWS Systems Manager Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) is used to share information between AWS Accounts - for example, IPAM pool ID or Core Network ARN.
25 |
26 | **Spoke Account**
27 |
28 | * In each AWS Region used:
29 | * Amazon VPC connected to Cloud WAN, and using an IPAM pool to obtain a CIDR block.
30 | * [Amazon EC2](https://aws.amazon.com/ec2/) instances in each Availability Zone - to test connectivity.
31 | * [EC2 Instance Connect endpoints](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-using-eice.html) to connect privately to the EC2 instances.
32 |
33 | ## Pre-requisites
34 |
35 | * When using several AWS Accounts, make sure you use different AWS credentials when initializing the provider in each folder.
36 | * This repository does not configure Amazon GuardDuty. Check the [documentation](https://docs.aws.amazon.com/guardduty/latest/ug/guardduty_settingup.html) to understand how to enable it in your AWS Accounts (if not done already).
37 | * Terraform installed.
38 |
39 | ## Code Principles
40 |
41 | * Writing DRY (Do No Repeat Yourself) code using a modular design pattern.
42 |
43 | ## Usage
44 |
45 | * Clone the repository
46 |
47 | ```
48 | git clone https://github.com/aws-samples/build-enforce-network-security-multi-account-environments.git
49 | ```
50 |
51 | * Edit the *variables.tf* file in each folder to configure the environment:
52 | * AWS Regions to use.
53 | * Amazon GuardDuty findings to test.
54 | * EC2 Instance types.
55 | * To share parameters between AWS Accounts, you will need to provide the Account ID of the corresponding Account in each folder. We recommend the use of tha *tfvars* file.
56 |
57 | ## Deployment
58 |
59 | * **Step 1**: Networking Account resources
60 |
61 | ```
62 | cd network/
63 | terraform apply
64 | ```
65 |
66 | * **Step 2**: Spoke Account resources
67 |
68 | ```
69 | cd spoke/
70 | terraform apply
71 | ```
72 |
73 | ## Cleanup
74 |
75 | * **Step 1**: Spoke Account resources
76 |
77 | ```
78 | cd spoke/
79 | terraform apply
80 | ```
81 |
82 | * **Step 2**: Networking Account resources
83 |
84 | ```
85 | cd network/
86 | terraform apply
87 | ```
88 |
89 | ## Security
90 |
91 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
92 |
93 | ## License
94 |
95 | This library is licensed under the MIT-0 License. See the LICENSE file.
96 |
97 |
--------------------------------------------------------------------------------
/images/building-enforce-network-security.drawio:
--------------------------------------------------------------------------------
1 |
{| no | 84 | | [guarduty\_finding\_names](#input\_guarduty\_finding\_names) | List of GuardDuty Finding names to filter in EventBridge. | `list(string)` |
"ireland": "eu-west-1",
"nvirginia": "us-east-1",
"ohio": "us-east-2"
}
[| no | 85 | | [identifier](#input\_identifier) | Project Identifier, used as identifer when creating resources. | `string` | `"nis342"` | no | 86 | 87 | ## Outputs 88 | 89 | No outputs. 90 | -------------------------------------------------------------------------------- /network/automation.py: -------------------------------------------------------------------------------- 1 | import json 2 | import boto3 3 | import os 4 | from botocore.exceptions import ClientError 5 | 6 | cloudwan_client = boto3.client('networkmanager') 7 | cn_id = os.environ['CORE_NETWORK_ID'] 8 | 9 | def lambda_handler(event, context): 10 | 11 | print("Received event:", event) 12 | 13 | if 'InputTemplate' in event and isinstance(event['InputTemplate'], str): 14 | try: 15 | event_data = json.loads(event['InputTemplate']) 16 | except json.JSONDecodeError as e: 17 | print(f"Error decoding nested JSON: {str(e)}") 18 | return { 19 | 'statusCode': 400, 20 | 'body': 'Invalid nested JSON input' 21 | } 22 | else: 23 | event_data = event 24 | 25 | finding_type = event_data.get('Finding_Type', 'No Data received') 26 | finding_description = event_data.get('Finding_description', 'No Data received') 27 | instance_id = event_data.get('instanceId', 'No Data received') 28 | region = event_data.get('region', 'No Data received') 29 | severity = float(event_data.get('severity', 'No Data received')) 30 | vpc_id = event_data.get('vpcId', 'No Data received') 31 | account_id = context.invoked_function_arn.split(":")[4] 32 | 33 | try: 34 | response = cloudwan_client.list_attachments( 35 | CoreNetworkId=cn_id, 36 | EdgeLocation=region 37 | ) 38 | print("CloudWan Response:", json.dumps(response, default=str)) 39 | 40 | AttachId = None 41 | 42 | for attachment in response.get('Attachments', []): 43 | if vpc_id in attachment.get('ResourceArn', ''): 44 | AttachId = attachment['AttachmentId'] 45 | break 46 | 47 | if AttachId: 48 | resource_arn = f"arn:aws:networkmanager::{account_id}:attachment/{AttachId}" 49 | print(f"Constructed ResourceArn: {resource_arn}") 50 | 51 | # Determine tags based on severity 52 | if severity <= 3.9: 53 | tags = [{'Key': 'domain', 'Value': 'inspected'}] 54 | elif severity <= 6.9: 55 | tags = [{'Key': 'domain', 'Value': 'onlyshared'}] 56 | elif severity <= 8.9: 57 | tags = [{'Key': 'domain', 'Value': 'blocked'}] 58 | 59 | tag_response = cloudwan_client.tag_resource( 60 | ResourceArn=resource_arn, 61 | Tags=tags 62 | ) 63 | 64 | print("Tagging has been placed correctly on:", resource_arn) 65 | print("Tags:", tags) 66 | 67 | else: 68 | print("No matching AttachmentId found for the provided vpc_id") 69 | tag_response = {} 70 | 71 | except ClientError as e: 72 | print(f"An error occurred: {str(e)}") 73 | return { 74 | 'statusCode': 500, 75 | 'body': json.dumps({'error': e.response['Error']['Message']}) 76 | } 77 | 78 | return { 79 | 'statusCode': 200, 80 | 'body': json.dumps({ 81 | 'Finding Type': finding_type, 82 | 'Finding Description': finding_description, 83 | 'Instance ID': instance_id, 84 | 'Region': region, 85 | 'Severity': severity, 86 | 'VPC ID': vpc_id, 87 | 'Matching AttachmentId': AttachId if AttachId else "Not Found", 88 | 'Tagging Response': tag_response 89 | }, default=str) 90 | } -------------------------------------------------------------------------------- /network/cloudwan_policy.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/cloudwan_policy.tf ---------- 2 | 3 | # Data source: Region's Prefix Lists 4 | data "aws_ec2_managed_prefix_list" "nvirginia_prefix_list" { 5 | provider = aws.awsnvirginia 6 | 7 | id = module.nvirginia_retrieve_parameters.parameter.prefix_list_id 8 | } 9 | 10 | data "aws_ec2_managed_prefix_list" "ohio_prefix_list" { 11 | provider = aws.awsohio 12 | 13 | id = module.ohio_retrieve_parameters.parameter.prefix_list_id 14 | } 15 | 16 | data "aws_ec2_managed_prefix_list" "ireland_prefix_list" { 17 | provider = aws.awsireland 18 | 19 | id = module.ireland_retrieve_parameters.parameter.prefix_list_id 20 | } 21 | 22 | locals { 23 | # We get the list of AWS Region codes from var.aws_regions 24 | region_codes = values({ for k, v in var.aws_regions : k => v }) 25 | # We get the list of AWS Region names from var.aws_regions 26 | region_names = keys({ for k, v in var.aws_regions : k => v }) 27 | # List of routing domains 28 | routing_domains = ["hub", "inspected", "onlyshared", "blocked", "shared"] 29 | 30 | # Information about the CIDR blocks and Inspection VPC attachments of each AWS Region 31 | region_information = { 32 | ireland = { 33 | cidr_blocks = [for entry in data.aws_ec2_managed_prefix_list.ireland_prefix_list.entries : entry.cidr] 34 | inspection_vpc_attachment = module.ireland_central.central_vpcs.inspection.core_network_attachment.id 35 | } 36 | nvirginia = { 37 | cidr_blocks = [for entry in data.aws_ec2_managed_prefix_list.nvirginia_prefix_list.entries : entry.cidr] 38 | inspection_vpc_attachment = module.nvirginia_central.central_vpcs.inspection.core_network_attachment.id 39 | } 40 | ohio = { 41 | cidr_blocks = [for entry in data.aws_ec2_managed_prefix_list.ohio_prefix_list.entries : entry.cidr] 42 | inspection_vpc_attachment = module.ohio_central.central_vpcs.inspection.core_network_attachment.id 43 | } 44 | } 45 | 46 | # We create a list of maps with the following format: 47 | # - inspection --> inspection segment to create the static routes 48 | # - destination --> destination AWS Region, to add the destination CIDRs + Inspection VPC of that Region 49 | region_combination = flatten( 50 | [for region1 in local.region_names : 51 | [for region2 in local.region_names : 52 | { 53 | inspection = region1 54 | destination = region2 55 | } 56 | if region1 != region2 57 | ] 58 | ] 59 | ) 60 | } 61 | 62 | # AWS Cloud WAN Core Network Policy - Single Segment 63 | data "aws_networkmanager_core_network_policy_document" "core_network_policy" { 64 | core_network_configuration { 65 | vpn_ecmp_support = false 66 | asn_ranges = ["64520-65525"] 67 | 68 | dynamic "edge_locations" { 69 | for_each = local.region_codes 70 | iterator = region 71 | 72 | content { 73 | location = region.value 74 | } 75 | } 76 | } 77 | 78 | # We generate one segment per routing domain 79 | dynamic "segments" { 80 | for_each = local.routing_domains 81 | iterator = domain 82 | 83 | content { 84 | name = domain.value 85 | require_attachment_acceptance = false 86 | isolate_attachments = domain.value == "hub" ? false : true 87 | deny_filter = [for r in local.region_names : "inspection${r}"] 88 | } 89 | } 90 | 91 | # We create 1 inspection segment per AWS Region 92 | dynamic "segments" { 93 | for_each = local.region_names 94 | iterator = region 95 | 96 | content { 97 | name = "inspection${region.value}" 98 | require_attachment_acceptance = false 99 | isolate_attachments = true 100 | } 101 | } 102 | 103 | # HUB & INSPECTED SEGMENTS: default (0.0.0.0/0) to Inspection VPCs - egress traffic 104 | dynamic "segment_actions" { 105 | for_each = ["hub", "inspected"] 106 | iterator = domain 107 | 108 | content { 109 | action = "create-route" 110 | segment = domain.value 111 | destination_cidr_blocks = ["0.0.0.0/0"] 112 | destinations = values({ for k, v in local.region_information : k => v.inspection_vpc_attachment }) 113 | } 114 | } 115 | 116 | # HUB & INSPECTED SEGMENTS: we share the segment routes with the inspection segments 117 | dynamic "segment_actions" { 118 | for_each = ["hub", "inspected"] 119 | iterator = domain 120 | 121 | content { 122 | action = "share" 123 | mode = "attachment-route" 124 | segment = domain.value 125 | share_with = [for r in local.region_names : "inspection${r}"] 126 | } 127 | } 128 | 129 | # SHARED SEGMENT: sharing with all segments except blocked 130 | dynamic "segment_actions" { 131 | for_each = ["hub", "inspected", "onlyshared"] 132 | iterator = domain 133 | 134 | content { 135 | action = "share" 136 | mode = "attachment-route" 137 | segment = domain.value 138 | share_with = ["shared"] 139 | } 140 | } 141 | 142 | # Create of static routes - per AWS Region, we need to point those VPCs CIDRs to pass through the local Inspection VPC in the other inspection segments 143 | # For example, N. Virginia CIDRs to Inspection VPC in N.Virginia --> inspectionireland & inspectionsydney 144 | dynamic "segment_actions" { 145 | for_each = local.region_combination 146 | iterator = combination 147 | 148 | content { 149 | action = "create-route" 150 | segment = "inspection${combination.value.inspection}" 151 | destination_cidr_blocks = local.region_information[combination.value.destination].cidr_blocks 152 | destinations = [local.region_information[combination.value.destination].inspection_vpc_attachment] 153 | } 154 | } 155 | 156 | # Attachment policies 157 | attachment_policies { 158 | rule_number = 100 159 | condition_logic = "or" 160 | 161 | conditions { 162 | type = "tag-exists" 163 | key = "domain" 164 | } 165 | 166 | action { 167 | association_method = "tag" 168 | tag_value_of_key = "domain" 169 | } 170 | } 171 | 172 | attachment_policies { 173 | rule_number = 200 174 | condition_logic = "or" 175 | 176 | conditions { 177 | type = "attachment-type" 178 | operator = "equals" 179 | value = "vpc" 180 | } 181 | 182 | action { 183 | association_method = "constant" 184 | segment = "hub" 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /network/main.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/main.tf ---------- 2 | data "aws_organizations_organization" "org" {} 3 | data "aws_caller_identity" "current" {} 4 | 5 | locals { 6 | parameters = ["prefix_list_id"] 7 | } 8 | 9 | data "archive_file" "cwan_automation_package" { 10 | type = "zip" 11 | source_file = "./automation.py" 12 | output_path = "./modules/automation/automation.zip" 13 | } 14 | 15 | # ---------- AUTOMATION (GUARDUTY FINDINGS TO CLOUDWAN ATTACHMENT TAG CHANGE) ---------- 16 | # North Virginia 17 | module "nvirginia_automation" { 18 | providers = { aws = aws.awsnvirginia } 19 | source = "./modules/automation" 20 | 21 | source_code_hash = data.archive_file.cwan_automation_package.output_base64sha256 22 | lambda_role_arn = aws_iam_role.automation_lambda_role.arn 23 | core_network_id = aws_networkmanager_core_network.core_network.id 24 | guardduty_finding_names = var.guarduty_finding_names 25 | } 26 | 27 | # Ireland 28 | module "ireland_automation" { 29 | providers = { aws = aws.awsireland } 30 | source = "./modules/automation" 31 | 32 | source_code_hash = data.archive_file.cwan_automation_package.output_base64sha256 33 | lambda_role_arn = aws_iam_role.automation_lambda_role.arn 34 | core_network_id = aws_networkmanager_core_network.core_network.id 35 | guardduty_finding_names = var.guarduty_finding_names 36 | } 37 | 38 | # Ohio 39 | module "ohio_automation" { 40 | providers = { aws = aws.awsohio } 41 | source = "./modules/automation" 42 | 43 | source_code_hash = data.archive_file.cwan_automation_package.output_base64sha256 44 | lambda_role_arn = aws_iam_role.automation_lambda_role.arn 45 | core_network_id = aws_networkmanager_core_network.core_network.id 46 | guardduty_finding_names = var.guarduty_finding_names 47 | } 48 | 49 | # IAM Role 50 | resource "aws_iam_role" "automation_lambda_role" { 51 | provider = aws.awsnvirginia 52 | 53 | name = "automation-lambda-role" 54 | path = "/" 55 | 56 | assume_role_policy = data.aws_iam_policy_document.automation_lambda_assume_role_policy.json 57 | } 58 | 59 | resource "aws_iam_policy" "automation_lambda_policy" { 60 | name = "automation-lambda-policy" 61 | description = "Automation - AWS Lambda policy" 62 | policy = data.aws_iam_policy_document.automation_lambda_actions.json 63 | } 64 | 65 | resource "aws_iam_role_policy_attachment" "automation_lambda_policy_attachment" { 66 | role = aws_iam_role.automation_lambda_role.name 67 | policy_arn = aws_iam_policy.automation_lambda_policy.arn 68 | } 69 | 70 | data "aws_iam_policy_document" "automation_lambda_assume_role_policy" { 71 | statement { 72 | effect = "Allow" 73 | actions = ["sts:AssumeRole"] 74 | principals { 75 | type = "Service" 76 | identifiers = ["lambda.amazonaws.com"] 77 | } 78 | } 79 | } 80 | 81 | data "aws_iam_policy_document" "automation_lambda_actions" { 82 | statement { 83 | effect = "Allow" 84 | actions = [ 85 | "logs:CreateLogGroup", 86 | "logs:CreateLogStream", 87 | "logs:PutLogEvents" 88 | ] 89 | resources = ["arn:aws:logs:*:${data.aws_caller_identity.current.id}:*"] 90 | } 91 | 92 | statement { 93 | effect = "Allow" 94 | actions = [ 95 | "networkmanager:DescribeGlobalNetworks", 96 | "networkmanager:ListCoreNetworks", 97 | "networkmanager:ListCoreNetworkPolicyVersions", 98 | "networkmanager:ListAttachments", 99 | "networkmanager:GetCoreNetworkPolicy", 100 | "networkmanager:GetNetworkResources", 101 | "networkmanager:GetResourcePolicy", 102 | "networkmanager:ListTagsForResource", 103 | "networkmanager:GetVpcAttachment", 104 | "networkmanager:AcceptAttachment", 105 | "networkmanager:PutResourcePolicy", 106 | "networkmanager:UpdateCoreNetwork", 107 | "networkmanager:UpdateVpcAttachment", 108 | "networkmanager:TagResource", 109 | "networkmanager:UntagResource", 110 | "ec2:DescribeRegions" 111 | ] 112 | resources = ["*"] 113 | } 114 | } 115 | 116 | # ---------- AWS CLOUD WAN RESOURCES ---------- 117 | # Global Network 118 | resource "aws_networkmanager_global_network" "global_network" { 119 | provider = aws.awsnvirginia 120 | 121 | description = "Global Network - ${var.identifier}" 122 | 123 | tags = { 124 | Name = "Global Network - ${var.identifier}" 125 | } 126 | } 127 | 128 | # Core Network 129 | resource "aws_networkmanager_core_network" "core_network" { 130 | provider = aws.awsnvirginia 131 | 132 | description = "Core Network - ${var.identifier}" 133 | global_network_id = aws_networkmanager_global_network.global_network.id 134 | 135 | create_base_policy = true 136 | base_policy_regions = values({ for k, v in var.aws_regions : k => v }) 137 | 138 | tags = { 139 | Name = "Core Network - ${var.identifier}" 140 | } 141 | } 142 | 143 | # Core Network Policy Attachment 144 | resource "aws_networkmanager_core_network_policy_attachment" "core_network_policy_attachment" { 145 | provider = aws.awsnvirginia 146 | 147 | core_network_id = aws_networkmanager_core_network.core_network.id 148 | policy_document = jsonencode(jsondecode(data.aws_networkmanager_core_network_policy_document.core_network_policy.json)) 149 | } 150 | 151 | # Resource Share 152 | resource "aws_ram_resource_share" "cwan_resource_share" { 153 | provider = aws.awsnvirginia 154 | 155 | name = "AWS Cloud WAN - Core Network" 156 | allow_external_principals = false 157 | } 158 | 159 | resource "aws_ram_principal_association" "cwan_principal_association" { 160 | provider = aws.awsnvirginia 161 | 162 | principal = data.aws_organizations_organization.org.arn 163 | resource_share_arn = aws_ram_resource_share.cwan_resource_share.arn 164 | } 165 | 166 | resource "aws_ram_resource_association" "cwan_resource_association" { 167 | provider = aws.awsnvirginia 168 | 169 | resource_arn = aws_networkmanager_core_network.core_network.arn 170 | resource_share_arn = aws_ram_resource_share.cwan_resource_share.arn 171 | } 172 | 173 | # ---------- AMAZON VPC IPAM ---------- 174 | module "ipam" { 175 | providers = { aws = aws.awsnvirginia } 176 | source = "aws-ia/ipam/aws" 177 | version = "2.0.0" 178 | 179 | top_cidr = ["10.0.0.0/8"] 180 | address_family = "ipv4" 181 | create_ipam = true 182 | top_name = "Organization IPAM" 183 | 184 | pool_configurations = { 185 | nvirginia = { 186 | name = "nvirginia" 187 | description = "N. Virginia (us-east-1) Region" 188 | netmask_length = 10 189 | locale = var.aws_regions.nvirginia 190 | 191 | sub_pools = { 192 | central = { 193 | name = "nvirginia-central" 194 | netmask_length = 11 195 | } 196 | spoke = { 197 | name = "nvirginia-spoke" 198 | netmask_length = 11 199 | ram_share_principals = [data.aws_organizations_organization.org.arn] 200 | } 201 | } 202 | } 203 | ohio = { 204 | name = "ohio" 205 | description = "Ohio (us-east-2) Region" 206 | netmask_length = 10 207 | locale = var.aws_regions.ohio 208 | 209 | sub_pools = { 210 | central = { 211 | name = "ohio-central" 212 | netmask_length = 11 213 | } 214 | spoke = { 215 | name = "ohio-spoke" 216 | netmask_length = 11 217 | ram_share_principals = [data.aws_organizations_organization.org.arn] 218 | } 219 | } 220 | } 221 | ireland = { 222 | name = "ireland" 223 | description = "Ireland (us-east-1) Region" 224 | netmask_length = 10 225 | locale = var.aws_regions.ireland 226 | 227 | sub_pools = { 228 | central = { 229 | name = "ireland-central" 230 | netmask_length = 11 231 | } 232 | spoke = { 233 | name = "ireland-spoke" 234 | netmask_length = 11 235 | ram_share_principals = [data.aws_organizations_organization.org.arn] 236 | } 237 | } 238 | } 239 | } 240 | } 241 | 242 | # ---------- NORTH VIRGINIA ---------- 243 | module "nvirginia_central" { 244 | source = "aws-ia/cloudwan/aws" 245 | version = "3.2.0" 246 | providers = { aws = aws.awsnvirginia } 247 | 248 | core_network_arn = aws_networkmanager_core_network.core_network.arn 249 | 250 | ipv4_network_definition = "10.0.0.0/8" 251 | central_vpcs = { 252 | inspection = { 253 | type = "egress_with_inspection" 254 | vpc_ipv4_ipam_pool_id = module.ipam.pools_level_2["nvirginia/central"].id 255 | vpc_ipv4_netmask_length = 24 256 | az_count = 2 257 | 258 | subnets = { 259 | public = { netmask = 28 } 260 | endpoints = { netmask = 28 } 261 | core_network = { 262 | netmask = 28 263 | 264 | tags = { domain = "inspectionnvirginia" } 265 | } 266 | } 267 | } 268 | shared_services = { 269 | type = "shared_services" 270 | vpc_ipv4_ipam_pool_id = module.ipam.pools_level_2["nvirginia/central"].id 271 | vpc_ipv4_netmask_length = 24 272 | az_count = 2 273 | 274 | subnets = { 275 | endpoints = { netmask = 28 } 276 | core_network = { 277 | netmask = 28 278 | 279 | tags = { domain = "shared" } 280 | } 281 | } 282 | } 283 | } 284 | 285 | aws_network_firewall = { 286 | inspection = { 287 | name = "anfw-nvirginia" 288 | description = "AWS Network Firewall - us-east-1" 289 | policy_arn = module.nvirginia_firewall_policy.firewall_policy_arn 290 | } 291 | } 292 | } 293 | 294 | module "nvirginia_firewall_policy" { 295 | providers = { aws = aws.awsnvirginia } 296 | source = "./modules/firewall_policy" 297 | 298 | identifier = var.identifier 299 | } 300 | 301 | module "nvirginia_vpc_endpoints" { 302 | providers = { 303 | aws = aws.awsnvirginia 304 | awscc = awscc.awsccnvirginia 305 | } 306 | source = "./modules/vpc_endpoints" 307 | 308 | service_endpoints = ["s3"] 309 | vpc_information = module.nvirginia_central.central_vpcs["shared_services"] 310 | } 311 | 312 | resource "aws_ram_resource_share" "nvirginia_dns_resource_share" { 313 | provider = aws.awsnvirginia 314 | 315 | name = "DNS Resolution - Amazon Route 53 Profiles" 316 | allow_external_principals = false 317 | } 318 | 319 | resource "aws_ram_principal_association" "nvirginia_dns_principal_association" { 320 | provider = aws.awsnvirginia 321 | 322 | principal = data.aws_organizations_organization.org.arn 323 | resource_share_arn = aws_ram_resource_share.nvirginia_dns_resource_share.arn 324 | } 325 | 326 | resource "aws_ram_resource_association" "nvirginia_profile_resource_association" { 327 | provider = aws.awsnvirginia 328 | 329 | resource_arn = module.nvirginia_vpc_endpoints.r53_profile 330 | resource_share_arn = aws_ram_resource_share.nvirginia_dns_resource_share.arn 331 | } 332 | 333 | module "nvirginia_share_parameter" { 334 | providers = { aws = aws.awsnvirginia } 335 | source = "../modules/share_parameter" 336 | 337 | ram_share_name = "Networking Account - N. Virginia" 338 | parameters = { 339 | core_network = aws_networkmanager_core_network.core_network.arn 340 | ipam_pool_id = module.ipam.pools_level_2["nvirginia/spoke"].id 341 | r53_profile = split("/", module.nvirginia_vpc_endpoints.r53_profile)[1] 342 | } 343 | } 344 | 345 | module "nvirginia_retrieve_parameters" { 346 | providers = { aws = aws.awsnvirginia } 347 | source = "../modules/retrieve_parameters" 348 | 349 | account_id = var.spoke_account_id 350 | parameters = local.parameters 351 | } 352 | 353 | # ---------- OHIO ---------- 354 | module "ohio_central" { 355 | source = "aws-ia/cloudwan/aws" 356 | version = "3.2.0" 357 | providers = { aws = aws.awsohio } 358 | 359 | core_network_arn = aws_networkmanager_core_network.core_network.arn 360 | 361 | ipv4_network_definition = "10.0.0.0/8" 362 | central_vpcs = { 363 | inspection = { 364 | type = "egress_with_inspection" 365 | vpc_ipv4_ipam_pool_id = module.ipam.pools_level_2["ohio/central"].id 366 | vpc_ipv4_netmask_length = 24 367 | az_count = 2 368 | 369 | subnets = { 370 | public = { netmask = 28 } 371 | endpoints = { netmask = 28 } 372 | core_network = { 373 | netmask = 28 374 | 375 | tags = { domain = "inspectionohio" } 376 | } 377 | } 378 | } 379 | shared_services = { 380 | type = "shared_services" 381 | vpc_ipv4_ipam_pool_id = module.ipam.pools_level_2["ohio/central"].id 382 | vpc_ipv4_netmask_length = 24 383 | az_count = 2 384 | 385 | subnets = { 386 | endpoints = { netmask = 28 } 387 | core_network = { 388 | netmask = 28 389 | 390 | tags = { domain = "shared" } 391 | } 392 | } 393 | } 394 | } 395 | 396 | aws_network_firewall = { 397 | inspection = { 398 | name = "anfw-ohio" 399 | description = "AWS Network Firewall - us-east-2" 400 | policy_arn = module.ohio_firewall_policy.firewall_policy_arn 401 | } 402 | } 403 | } 404 | 405 | module "ohio_firewall_policy" { 406 | providers = { aws = aws.awsohio } 407 | source = "./modules/firewall_policy" 408 | 409 | identifier = var.identifier 410 | } 411 | 412 | module "ohio_vpc_endpoints" { 413 | providers = { 414 | aws = aws.awsohio 415 | awscc = awscc.awsccohio 416 | } 417 | source = "./modules/vpc_endpoints" 418 | 419 | service_endpoints = ["s3"] 420 | vpc_information = module.ohio_central.central_vpcs["shared_services"] 421 | } 422 | 423 | resource "aws_ram_resource_share" "ohio_dns_resource_share" { 424 | provider = aws.awsohio 425 | 426 | name = "DNS Resolution - Amazon Route 53 Profiles" 427 | allow_external_principals = false 428 | } 429 | 430 | resource "aws_ram_principal_association" "ohio_dns_principal_association" { 431 | provider = aws.awsohio 432 | 433 | principal = data.aws_organizations_organization.org.arn 434 | resource_share_arn = aws_ram_resource_share.ohio_dns_resource_share.arn 435 | } 436 | 437 | resource "aws_ram_resource_association" "ohio_profile_resource_association" { 438 | provider = aws.awsohio 439 | 440 | resource_arn = module.ohio_vpc_endpoints.r53_profile 441 | resource_share_arn = aws_ram_resource_share.ohio_dns_resource_share.arn 442 | } 443 | 444 | module "ohio_share_parameter" { 445 | providers = { aws = aws.awsohio } 446 | source = "../modules/share_parameter" 447 | 448 | ram_share_name = "Networking Account - Ohio" 449 | parameters = { 450 | core_network = aws_networkmanager_core_network.core_network.arn 451 | ipam_pool_id = module.ipam.pools_level_2["ohio/spoke"].id 452 | r53_profile = split("/", module.ohio_vpc_endpoints.r53_profile)[1] 453 | } 454 | } 455 | 456 | module "ohio_retrieve_parameters" { 457 | providers = { aws = aws.awsohio } 458 | source = "../modules/retrieve_parameters" 459 | 460 | account_id = var.spoke_account_id 461 | parameters = local.parameters 462 | } 463 | 464 | # ---------- IRELAND ---------- 465 | module "ireland_central" { 466 | source = "aws-ia/cloudwan/aws" 467 | version = "3.2.0" 468 | providers = { aws = aws.awsireland } 469 | 470 | core_network_arn = aws_networkmanager_core_network.core_network.arn 471 | 472 | ipv4_network_definition = "10.0.0.0/8" 473 | central_vpcs = { 474 | inspection = { 475 | type = "egress_with_inspection" 476 | vpc_ipv4_ipam_pool_id = module.ipam.pools_level_2["ireland/central"].id 477 | vpc_ipv4_netmask_length = 24 478 | az_count = 2 479 | 480 | subnets = { 481 | public = { netmask = 28 } 482 | endpoints = { netmask = 28 } 483 | core_network = { 484 | netmask = 28 485 | 486 | tags = { domain = "inspectionireland" } 487 | } 488 | } 489 | } 490 | shared_services = { 491 | type = "shared_services" 492 | vpc_ipv4_ipam_pool_id = module.ipam.pools_level_2["ireland/central"].id 493 | vpc_ipv4_netmask_length = 24 494 | az_count = 2 495 | 496 | subnets = { 497 | endpoints = { netmask = 28 } 498 | core_network = { 499 | netmask = 28 500 | 501 | tags = { domain = "shared" } 502 | } 503 | } 504 | } 505 | } 506 | 507 | aws_network_firewall = { 508 | inspection = { 509 | name = "anfw-ireland" 510 | description = "AWS Network Firewall - eu-west-1" 511 | policy_arn = module.ireland_firewall_policy.firewall_policy_arn 512 | } 513 | } 514 | } 515 | 516 | module "ireland_firewall_policy" { 517 | providers = { aws = aws.awsireland } 518 | source = "./modules/firewall_policy" 519 | 520 | identifier = var.identifier 521 | } 522 | 523 | module "ireland_vpc_endpoints" { 524 | providers = { 525 | aws = aws.awsireland 526 | awscc = awscc.awsccireland 527 | } 528 | source = "./modules/vpc_endpoints" 529 | 530 | service_endpoints = ["s3"] 531 | vpc_information = module.ireland_central.central_vpcs["shared_services"] 532 | } 533 | 534 | resource "aws_ram_resource_share" "ireland_dns_resource_share" { 535 | provider = aws.awsireland 536 | 537 | name = "DNS Resolution - Amazon Route 53 Profiles" 538 | allow_external_principals = false 539 | } 540 | 541 | resource "aws_ram_principal_association" "ireland_dns_principal_association" { 542 | provider = aws.awsireland 543 | 544 | principal = data.aws_organizations_organization.org.arn 545 | resource_share_arn = aws_ram_resource_share.ireland_dns_resource_share.arn 546 | } 547 | 548 | resource "aws_ram_resource_association" "ireland_profile_resource_association" { 549 | provider = aws.awsireland 550 | 551 | resource_arn = module.ireland_vpc_endpoints.r53_profile 552 | resource_share_arn = aws_ram_resource_share.ireland_dns_resource_share.arn 553 | } 554 | 555 | module "ireland_share_parameter" { 556 | providers = { aws = aws.awsireland } 557 | source = "../modules/share_parameter" 558 | 559 | ram_share_name = "Networking Account - Ireland" 560 | parameters = { 561 | core_network = aws_networkmanager_core_network.core_network.arn 562 | ipam_pool_id = module.ipam.pools_level_2["ireland/spoke"].id 563 | r53_profile = split("/", module.ireland_vpc_endpoints.r53_profile)[1] 564 | } 565 | } 566 | 567 | module "ireland_retrieve_parameters" { 568 | providers = { aws = aws.awsireland } 569 | source = "../modules/retrieve_parameters" 570 | 571 | account_id = var.spoke_account_id 572 | parameters = local.parameters 573 | } -------------------------------------------------------------------------------- /network/modules/automation/automation.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/build-enforce-network-security-multi-account-environments/0a6f3ca892ac0e79a19d7997a7e897a98a88cfe2/network/modules/automation/automation.zip -------------------------------------------------------------------------------- /network/modules/automation/main.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/automation/main.tf ---------- 2 | data "aws_region" "region" {} 3 | 4 | # ---------- AMAZON EVENTBRIDGE ---------- 5 | resource "aws_cloudwatch_event_rule" "event_rule" { 6 | name = "guardduty-event-rule-${data.aws_region.region.name}" 7 | description = "Capture Amazon GuardDuty findings." 8 | 9 | event_pattern = jsonencode({ 10 | source = ["aws.guardduty"] 11 | detail = { 12 | type = var.guardduty_finding_names 13 | } 14 | }) 15 | } 16 | 17 | resource "aws_cloudwatch_event_target" "lambda_target" { 18 | rule = aws_cloudwatch_event_rule.event_rule.id 19 | target_id = "${data.aws_region.region.name}-lambda" 20 | arn = aws_lambda_function.lambda_function.arn 21 | 22 | input_transformer { 23 | input_paths = { 24 | Finding_ID = "$.detail.id", 25 | Finding_Type = "$.detail.type", 26 | Finding_description = "$.detail.description", 27 | instanceId = "$.detail.resource.instanceDetails.instanceId", 28 | region = "$.detail.region", 29 | severity = "$.detail.severity", 30 | vpcId = "$.detail.resource.instanceDetails.networkInterfaces[0].vpcId", 31 | } 32 | input_template = <
"UnauthorizedAccess:EC2/MaliciousIPCaller.Custom",
"CryptoCurrency:EC2/BitcoinTool.B!DNS",
"Execution:Runtime/SuspiciousTool"
]
{| no | 80 | | [identifier](#input\_identifier) | Project Identifier, used as identifer when creating resources. | `string` | `"nis342"` | no | 81 | | [vpcs](#input\_vpcs) | Information about the VPCs to create. | `any` |
"ireland": "eu-west-1",
"nvirginia": "us-east-1",
"ohio": "us-east-2"
}
{| no | 82 | 83 | ## Outputs 84 | 85 | No outputs. 86 | -------------------------------------------------------------------------------- /spoke/main.tf: -------------------------------------------------------------------------------- 1 | # ---------- spoke/main.tf ---------- 2 | data "aws_organizations_organization" "org" {} 3 | data "aws_region" "region" {} 4 | data "aws_caller_identity" "current" {} 5 | 6 | locals { 7 | parameters = ["core_network", "ipam_pool_id", "r53_profile"] 8 | } 9 | 10 | # ---------- N. VIRGINIA ---------- 11 | module "nvirginia_vpcs" { 12 | providers = { aws = aws.awsnvirginia } 13 | source = "aws-ia/vpc/aws" 14 | version = "4.4.2" 15 | for_each = var.vpcs.nvirginia 16 | 17 | name = each.key 18 | vpc_ipv4_ipam_pool_id = module.nvirginia_retrieve_parameters.parameter.ipam_pool_id 19 | vpc_ipv4_netmask_length = 24 20 | az_count = 2 21 | 22 | core_network = { 23 | id = split("/", module.nvirginia_retrieve_parameters.parameter.core_network)[1] 24 | arn = module.nvirginia_retrieve_parameters.parameter.core_network 25 | } 26 | core_network_routes = { 27 | workload = "0.0.0.0/0" 28 | } 29 | 30 | subnets = { 31 | workload = { netmask = 28 } 32 | endpoints = { netmask = 28 } 33 | core_network = { netmask = 28 } 34 | } 35 | } 36 | 37 | # Retrieve Parameters: Core Network ARN & IPAM Pool ID 38 | module "nvirginia_retrieve_parameters" { 39 | providers = { aws = aws.awsnvirginia } 40 | source = "../modules/retrieve_parameters" 41 | 42 | account_id = var.networking_account_id 43 | parameters = local.parameters 44 | } 45 | 46 | # Compute: EC2 Instances & EC2 Instance Connect endpoint 47 | module "nvirginia_compute" { 48 | providers = { aws = aws.awsnvirginia } 49 | source = "./modules/compute" 50 | for_each = module.nvirginia_vpcs 51 | 52 | identifier = var.identifier 53 | vpc_name = each.key 54 | vpc = each.value 55 | vpc_information = var.vpcs.nvirginia[each.key] 56 | ec2_iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.id 57 | } 58 | 59 | # Associating central Route 53 Profile (from Networking Account) 60 | resource "awscc_route53profiles_profile_association" "nvirginia_r53_profile_association" { 61 | for_each = module.nvirginia_vpcs 62 | provider = awscc.awsccnvirginia 63 | 64 | name = "r53_profile_association" 65 | profile_id = module.nvirginia_retrieve_parameters.parameter.r53_profile 66 | resource_id = each.value.vpc_attributes.id 67 | } 68 | 69 | # Managed Prefix List 70 | resource "aws_ec2_managed_prefix_list" "nvirginia_prefix_list" { 71 | provider = aws.awsnvirginia 72 | 73 | name = "nvirginia-vpcs" 74 | address_family = "IPv4" 75 | max_entries = length(var.vpcs.nvirginia) 76 | } 77 | 78 | data "aws_vpc" "nvirginia_vpcs" { 79 | provider = aws.awsnvirginia 80 | for_each = module.nvirginia_vpcs 81 | 82 | id = each.value.vpc_attributes.id 83 | } 84 | 85 | resource "aws_ec2_managed_prefix_list_entry" "nvirginia_prefix_list_entry" { 86 | provider = aws.awsnvirginia 87 | for_each = data.aws_vpc.nvirginia_vpcs 88 | 89 | cidr = each.value.cidr_block_associations[0].cidr_block 90 | description = "${each.key}-nvirginia" 91 | prefix_list_id = aws_ec2_managed_prefix_list.nvirginia_prefix_list.id 92 | } 93 | 94 | # Resource Share: Prefix List 95 | resource "aws_ram_resource_share" "nvirginia_pl_resource_share" { 96 | provider = aws.awsnvirginia 97 | 98 | name = "VPCs Prefix List - N. Virginia" 99 | allow_external_principals = false 100 | } 101 | 102 | resource "aws_ram_principal_association" "nvirginia_pl_principal_association" { 103 | provider = aws.awsnvirginia 104 | 105 | principal = data.aws_organizations_organization.org.arn 106 | resource_share_arn = aws_ram_resource_share.nvirginia_pl_resource_share.arn 107 | } 108 | 109 | resource "aws_ram_resource_association" "nvirginia_pl_resource_association" { 110 | provider = aws.awsnvirginia 111 | 112 | resource_arn = aws_ec2_managed_prefix_list.nvirginia_prefix_list.arn 113 | resource_share_arn = aws_ram_resource_share.nvirginia_pl_resource_share.arn 114 | } 115 | 116 | # Share Parameters: Prefix List ID 117 | module "nvirginia_share_parameters" { 118 | providers = { aws = aws.awsnvirginia } 119 | source = "../modules/share_parameter" 120 | 121 | ram_share_name = "Prefix List - N. Virginia" 122 | parameters = { 123 | prefix_list_id = aws_ec2_managed_prefix_list.nvirginia_prefix_list.id 124 | } 125 | } 126 | 127 | # ---------- OHIO ---------- 128 | module "ohio_vpcs" { 129 | providers = { aws = aws.awsohio } 130 | source = "aws-ia/vpc/aws" 131 | version = "4.4.2" 132 | for_each = var.vpcs.ohio 133 | 134 | name = each.key 135 | vpc_ipv4_ipam_pool_id = module.ohio_retrieve_parameters.parameter.ipam_pool_id 136 | vpc_ipv4_netmask_length = 24 137 | az_count = 2 138 | 139 | core_network = { 140 | id = split("/", module.ohio_retrieve_parameters.parameter.core_network)[1] 141 | arn = module.ohio_retrieve_parameters.parameter.core_network 142 | } 143 | core_network_routes = { 144 | workload = "0.0.0.0/0" 145 | } 146 | 147 | subnets = { 148 | workload = { netmask = 28 } 149 | endpoints = { netmask = 28 } 150 | core_network = { netmask = 28 } 151 | } 152 | } 153 | 154 | # Retrieve Parameters: Core Network ARN & IPAM Pool ID 155 | module "ohio_retrieve_parameters" { 156 | providers = { aws = aws.awsohio } 157 | source = "../modules/retrieve_parameters" 158 | 159 | account_id = var.networking_account_id 160 | parameters = local.parameters 161 | } 162 | 163 | # Compute: EC2 Instances & EC2 Instance Connect endpoint 164 | module "ohio_compute" { 165 | providers = { aws = aws.awsohio } 166 | source = "./modules/compute" 167 | for_each = module.ohio_vpcs 168 | 169 | identifier = var.identifier 170 | vpc_name = each.key 171 | vpc = each.value 172 | vpc_information = var.vpcs.ohio[each.key] 173 | ec2_iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.id 174 | } 175 | 176 | # Associating central Route 53 Profile (from Networking Account) 177 | resource "awscc_route53profiles_profile_association" "ohio_r53_profile_association" { 178 | for_each = module.ohio_vpcs 179 | provider = awscc.awsccohio 180 | 181 | name = "r53_profile_association" 182 | profile_id = module.ohio_retrieve_parameters.parameter.r53_profile 183 | resource_id = each.value.vpc_attributes.id 184 | } 185 | 186 | # Managed Prefix List 187 | resource "aws_ec2_managed_prefix_list" "ohio_prefix_list" { 188 | provider = aws.awsohio 189 | 190 | name = "ohio-vpcs" 191 | address_family = "IPv4" 192 | max_entries = length(var.vpcs.ohio) 193 | } 194 | 195 | data "aws_vpc" "ohio_vpcs" { 196 | provider = aws.awsohio 197 | for_each = module.ohio_vpcs 198 | 199 | id = each.value.vpc_attributes.id 200 | } 201 | 202 | resource "aws_ec2_managed_prefix_list_entry" "ohio_prefix_list_entry" { 203 | provider = aws.awsohio 204 | for_each = data.aws_vpc.ohio_vpcs 205 | 206 | cidr = each.value.cidr_block_associations[0].cidr_block 207 | description = "${each.key}-ohio" 208 | prefix_list_id = aws_ec2_managed_prefix_list.ohio_prefix_list.id 209 | } 210 | 211 | # Resource Share: Prefix List 212 | resource "aws_ram_resource_share" "ohio_pl_resource_share" { 213 | provider = aws.awsohio 214 | 215 | name = "VPCs Prefix List - Ohio" 216 | allow_external_principals = false 217 | } 218 | 219 | resource "aws_ram_principal_association" "ohio_pl_principal_association" { 220 | provider = aws.awsohio 221 | 222 | principal = data.aws_organizations_organization.org.arn 223 | resource_share_arn = aws_ram_resource_share.ohio_pl_resource_share.arn 224 | } 225 | 226 | resource "aws_ram_resource_association" "ohio_pl_resource_association" { 227 | provider = aws.awsohio 228 | 229 | resource_arn = aws_ec2_managed_prefix_list.ohio_prefix_list.arn 230 | resource_share_arn = aws_ram_resource_share.ohio_pl_resource_share.arn 231 | } 232 | 233 | # Share Parameters: Prefix List ID 234 | module "ohio_share_parameters" { 235 | providers = { aws = aws.awsohio } 236 | source = "../modules/share_parameter" 237 | 238 | ram_share_name = "Prefix List - Ohio" 239 | parameters = { 240 | prefix_list_id = aws_ec2_managed_prefix_list.ohio_prefix_list.id 241 | } 242 | } 243 | 244 | # ---------- IRELAND ---------- 245 | module "ireland_vpcs" { 246 | providers = { aws = aws.awsireland } 247 | source = "aws-ia/vpc/aws" 248 | version = "4.4.2" 249 | for_each = var.vpcs.ireland 250 | 251 | name = each.key 252 | vpc_ipv4_ipam_pool_id = module.ireland_retrieve_parameters.parameter.ipam_pool_id 253 | vpc_ipv4_netmask_length = 24 254 | az_count = 2 255 | 256 | core_network = { 257 | id = split("/", module.ireland_retrieve_parameters.parameter.core_network)[1] 258 | arn = module.ireland_retrieve_parameters.parameter.core_network 259 | } 260 | core_network_routes = { 261 | workload = "0.0.0.0/0" 262 | } 263 | 264 | subnets = { 265 | workload = { netmask = 28 } 266 | endpoints = { netmask = 28 } 267 | core_network = { netmask = 28 } 268 | } 269 | } 270 | 271 | # Retrieve Parameters: Core Network ARN & IPAM Pool ID 272 | module "ireland_retrieve_parameters" { 273 | providers = { aws = aws.awsireland } 274 | source = "../modules/retrieve_parameters" 275 | 276 | account_id = var.networking_account_id 277 | parameters = local.parameters 278 | } 279 | 280 | # Compute: EC2 Instances & EC2 Instance Connect endpoint 281 | module "ireland_compute" { 282 | providers = { aws = aws.awsireland } 283 | source = "./modules/compute" 284 | for_each = module.ireland_vpcs 285 | 286 | identifier = var.identifier 287 | vpc_name = each.key 288 | vpc = each.value 289 | vpc_information = var.vpcs.ireland[each.key] 290 | ec2_iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.id 291 | } 292 | 293 | # Associating central Route 53 Profile (from Networking Account) 294 | resource "awscc_route53profiles_profile_association" "ireland_r53_profile_association" { 295 | for_each = module.ireland_vpcs 296 | provider = awscc.awsccireland 297 | 298 | name = "r53_profile_association" 299 | profile_id = module.ireland_retrieve_parameters.parameter.r53_profile 300 | resource_id = each.value.vpc_attributes.id 301 | } 302 | 303 | # Managed Prefix List 304 | resource "aws_ec2_managed_prefix_list" "ireland_prefix_list" { 305 | provider = aws.awsireland 306 | 307 | name = "ireland-vpcs" 308 | address_family = "IPv4" 309 | max_entries = length(var.vpcs.ireland) 310 | } 311 | 312 | data "aws_vpc" "ireland_vpcs" { 313 | provider = aws.awsireland 314 | for_each = module.ireland_vpcs 315 | 316 | id = each.value.vpc_attributes.id 317 | } 318 | 319 | resource "aws_ec2_managed_prefix_list_entry" "ireland_prefix_list_entry" { 320 | provider = aws.awsireland 321 | for_each = data.aws_vpc.ireland_vpcs 322 | 323 | cidr = each.value.cidr_block_associations[0].cidr_block 324 | description = "${each.key}-ireland" 325 | prefix_list_id = aws_ec2_managed_prefix_list.ireland_prefix_list.id 326 | } 327 | 328 | # Resource Share: Prefix List 329 | resource "aws_ram_resource_share" "ireland_pl_resource_share" { 330 | provider = aws.awsireland 331 | 332 | name = "VPCs Prefix List - Ireland" 333 | allow_external_principals = false 334 | } 335 | 336 | resource "aws_ram_principal_association" "ireland_pl_principal_association" { 337 | provider = aws.awsireland 338 | 339 | principal = data.aws_organizations_organization.org.arn 340 | resource_share_arn = aws_ram_resource_share.ireland_pl_resource_share.arn 341 | } 342 | 343 | resource "aws_ram_resource_association" "ireland_pl_resource_association" { 344 | provider = aws.awsireland 345 | 346 | resource_arn = aws_ec2_managed_prefix_list.ireland_prefix_list.arn 347 | resource_share_arn = aws_ram_resource_share.ireland_pl_resource_share.arn 348 | } 349 | 350 | # Share Parameters: Prefix List ID 351 | module "ireland_share_parameters" { 352 | providers = { aws = aws.awsireland } 353 | source = "../modules/share_parameter" 354 | 355 | ram_share_name = "Prefix List - Ireland" 356 | parameters = { 357 | prefix_list_id = aws_ec2_managed_prefix_list.ireland_prefix_list.id 358 | } 359 | } 360 | 361 | # ---------- IAM ROLE (EC2 S3 read-only access) ---------- 362 | # IAM instance profile 363 | resource "aws_iam_instance_profile" "ec2_instance_profile" { 364 | provider = aws.awsnvirginia 365 | 366 | name = "ec2_instance_profile" 367 | role = aws_iam_role.role_ec2.id 368 | } 369 | 370 | # IAM role 371 | resource "aws_iam_role" "role_ec2" { 372 | provider = aws.awsnvirginia 373 | 374 | name = "ec2_ssm_role" 375 | path = "/" 376 | assume_role_policy = data.aws_iam_policy_document.policy_document.json 377 | } 378 | 379 | data "aws_iam_policy_document" "policy_document" { 380 | statement { 381 | sid = "1" 382 | actions = ["sts:AssumeRole"] 383 | 384 | principals { 385 | type = "Service" 386 | identifiers = ["ec2.amazonaws.com"] 387 | } 388 | 389 | } 390 | } 391 | 392 | # Policies Attachment to Role 393 | resource "aws_iam_policy_attachment" "s3_readonly_policy_attachment" { 394 | provider = aws.awsnvirginia 395 | 396 | name = "s3_readonly_policy_attachment" 397 | roles = [aws_iam_role.role_ec2.id] 398 | policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" 399 | } -------------------------------------------------------------------------------- /spoke/modules/compute/main.tf: -------------------------------------------------------------------------------- 1 | # --- spoke/modules/compute/main.tf --- 2 | 3 | # ---------- EC2 INSTANCES ---------- 4 | # Security Group 5 | resource "aws_security_group" "instance_sg" { 6 | name = "${var.vpc_name}-instance-security-group-${var.identifier}" 7 | description = "EC2 Instance Security Group" 8 | vpc_id = var.vpc.vpc_attributes.id 9 | } 10 | 11 | resource "aws_vpc_security_group_ingress_rule" "allowing_ingress_icmp" { 12 | security_group_id = aws_security_group.instance_sg.id 13 | 14 | from_port = -1 15 | to_port = -1 16 | ip_protocol = "icmp" 17 | cidr_ipv4 = "0.0.0.0/0" 18 | } 19 | 20 | resource "aws_vpc_security_group_ingress_rule" "allowing_ingress_eic" { 21 | security_group_id = aws_security_group.instance_sg.id 22 | 23 | from_port = 22 24 | to_port = 22 25 | ip_protocol = "tcp" 26 | referenced_security_group_id = aws_security_group.eic_sg.id 27 | } 28 | 29 | resource "aws_vpc_security_group_egress_rule" "allowing_egress_any" { 30 | security_group_id = aws_security_group.instance_sg.id 31 | 32 | ip_protocol = "-1" 33 | cidr_ipv4 = "0.0.0.0/0" 34 | } 35 | 36 | # Data resource to determine the latest Amazon Linux 2023 AMI 37 | data "aws_ami" "amazon_linux" { 38 | most_recent = true 39 | owners = ["amazon"] 40 | 41 | filter { 42 | name = "name" 43 | values = ["al2023-ami-2023.*-x86_64"] 44 | } 45 | } 46 | 47 | # EC2 instances 48 | resource "aws_instance" "ec2_instance" { 49 | count = var.vpc_information.number_azs 50 | 51 | ami = data.aws_ami.amazon_linux.id 52 | associate_public_ip_address = false 53 | instance_type = var.vpc_information.instance_type 54 | vpc_security_group_ids = [aws_security_group.instance_sg.id] 55 | 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] 56 | iam_instance_profile = var.ec2_iam_instance_profile 57 | 58 | metadata_options { 59 | http_endpoint = "enabled" 60 | http_tokens = "required" 61 | } 62 | 63 | root_block_device { 64 | encrypted = true 65 | } 66 | 67 | tags = { 68 | Name = "${var.vpc_name}-instance-${count.index + 1}-${var.identifier}" 69 | } 70 | } 71 | 72 | # ---------- EC2 INSTANCE CONNECT ---------- 73 | # Security Group 74 | resource "aws_security_group" "eic_sg" { 75 | name = "${var.vpc_name}-eic-security-group-${var.identifier}" 76 | description = "EC2 Instance Connect Security Group" 77 | vpc_id = var.vpc.vpc_attributes.id 78 | } 79 | 80 | resource "aws_vpc_security_group_egress_rule" "allowing_egress_ec2_instances" { 81 | security_group_id = aws_security_group.eic_sg.id 82 | 83 | from_port = 22 84 | to_port = 22 85 | ip_protocol = "tcp" 86 | referenced_security_group_id = aws_security_group.instance_sg.id 87 | } 88 | 89 | # EC2 Instance Connect endpoint 90 | resource "aws_ec2_instance_connect_endpoint" "eic_endpoint" { 91 | 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] 92 | preserve_client_ip = false 93 | security_group_ids = [aws_security_group.eic_sg.id] 94 | } -------------------------------------------------------------------------------- /spoke/modules/compute/outputs.tf: -------------------------------------------------------------------------------- 1 | # --- spoke/modules/compute/outputs.tf --- 2 | 3 | output "ec2_instances" { 4 | value = aws_instance.ec2_instance 5 | description = "List of instances created." 6 | } -------------------------------------------------------------------------------- /spoke/modules/compute/providers.tf: -------------------------------------------------------------------------------- 1 | # --- spoke/modules/compute/providers.tf --- 2 | 3 | terraform { 4 | required_version = ">= 1.3.0" 5 | required_providers { 6 | aws = { 7 | source = "hashicorp/aws" 8 | version = ">= 3.73.0" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /spoke/modules/compute/variables.tf: -------------------------------------------------------------------------------- 1 | # --- spoke/modules/compute/variables.tf --- 2 | 3 | variable "identifier" { 4 | type = string 5 | description = "Project identifier." 6 | } 7 | 8 | variable "vpc_name" { 9 | type = string 10 | description = "Name of the VPC where the EC2 instance(s) are created." 11 | } 12 | 13 | variable "vpc" { 14 | type = any 15 | description = "VPC resources." 16 | } 17 | 18 | variable "vpc_information" { 19 | type = any 20 | description = "VPC information (defined in root variables.tf file)." 21 | } 22 | 23 | variable "ec2_iam_instance_profile" { 24 | type = string 25 | description = "EC2 instance profile to attach to the EC2 instance(s)" 26 | } -------------------------------------------------------------------------------- /spoke/outputs.tf: -------------------------------------------------------------------------------- 1 | # ---------- spoke/outputs.tf ---------- -------------------------------------------------------------------------------- /spoke/providers.tf: -------------------------------------------------------------------------------- 1 | # ---------- spoke/providers.tf ---------- 2 | 3 | terraform { 4 | required_version = ">= 1.3.0" 5 | required_providers { 6 | aws = { 7 | source = "hashicorp/aws" 8 | version = ">= 5.0.0" 9 | } 10 | awscc = { 11 | source = "hashicorp/awscc" 12 | version = "= 0.78.0" 13 | } 14 | } 15 | 16 | backend "s3" { 17 | bucket = "nis342-spoke-tfstate" 18 | key = "spoke" 19 | region = "eu-west-1" 20 | dynamodb_table = "nis342-spoke-tfstate" 21 | } 22 | } 23 | 24 | provider "aws" { 25 | alias = "awsnvirginia" 26 | region = var.aws_regions.nvirginia 27 | } 28 | 29 | provider "aws" { 30 | alias = "awsireland" 31 | region = var.aws_regions.ireland 32 | } 33 | 34 | provider "aws" { 35 | alias = "awsohio" 36 | region = var.aws_regions.ohio 37 | } 38 | 39 | provider "awscc" { 40 | alias = "awsccnvirginia" 41 | region = var.aws_regions.nvirginia 42 | } 43 | 44 | provider "awscc" { 45 | alias = "awsccireland" 46 | region = var.aws_regions.ireland 47 | } 48 | 49 | provider "awscc" { 50 | alias = "awsccohio" 51 | region = var.aws_regions.ohio 52 | } -------------------------------------------------------------------------------- /spoke/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------- spoke/variables.tf ---------- 2 | 3 | # Project Identifier 4 | variable "identifier" { 5 | type = string 6 | description = "Project Identifier, used as identifer when creating resources." 7 | default = "nis342" 8 | } 9 | 10 | # AWS Regions 11 | variable "aws_regions" { 12 | type = map(string) 13 | description = "AWS Regions to create the environment." 14 | default = { 15 | ireland = "eu-west-1" 16 | nvirginia = "us-east-1" 17 | ohio = "us-east-2" 18 | } 19 | } 20 | 21 | # Networking AWS Account ID 22 | variable "networking_account_id" { 23 | type = string 24 | description = "Networking AWS Account ID." 25 | } 26 | 27 | # VPCs' definition 28 | variable "vpcs" { 29 | type = any 30 | description = "Information about the VPCs to create." 31 | 32 | default = { 33 | nvirginia = { 34 | vpc1 = { 35 | number_azs = 2 36 | instance_type = "t2.micro" 37 | } 38 | } 39 | 40 | ohio = { 41 | vpc1 = { 42 | number_azs = 2 43 | instance_type = "t2.micro" 44 | } 45 | } 46 | 47 | ireland = { 48 | vpc1 = { 49 | number_azs = 2 50 | instance_type = "t2.micro" 51 | } 52 | } 53 | } 54 | } --------------------------------------------------------------------------------
"ireland": {
"vpc1": {
"instance_type": "t2.micro",
"number_azs": 2
}
},
"nvirginia": {
"vpc1": {
"instance_type": "t2.micro",
"number_azs": 2
}
},
"ohio": {
"vpc1": {
"instance_type": "t2.micro",
"number_azs": 2
}
}
}