├── .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 | ![Architecture diagram](./images/full-architecture.png) 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 | 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 | -------------------------------------------------------------------------------- /images/full-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/build-enforce-network-security-multi-account-environments/0a6f3ca892ac0e79a19d7997a7e897a98a88cfe2/images/full-architecture.png -------------------------------------------------------------------------------- /modules/retrieve_parameters/main.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/retreive_parameters/main.tf ---------- 2 | 3 | locals { 4 | parameters = { for p in var.parameters : p => var.account_id } 5 | } 6 | 7 | # Obtain AWS Region 8 | data "aws_region" "current" {} 9 | 10 | # Retrieving parameters 11 | data "aws_ssm_parameter" "parameter" { 12 | for_each = local.parameters 13 | 14 | name = "arn:aws:ssm:${data.aws_region.current.name}:${each.value}:parameter/${each.key}" 15 | } -------------------------------------------------------------------------------- /modules/retrieve_parameters/outputs.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/retreive_parameters/outputs.tf ---------- 2 | 3 | output "parameter" { 4 | description = "Parameter value." 5 | value = { for k, v in data.aws_ssm_parameter.parameter : k => v.value } 6 | } -------------------------------------------------------------------------------- /modules/retrieve_parameters/providers.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/retreive_parameters/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 | } 11 | } -------------------------------------------------------------------------------- /modules/retrieve_parameters/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/retreive_parameters/variables.tf ---------- 2 | 3 | variable "parameters" { 4 | description = "List of parameters to retrieve." 5 | type = list(string) 6 | } 7 | 8 | variable "account_id" { 9 | description = "AWS Account ID (that shared the parameters)." 10 | type = string 11 | } -------------------------------------------------------------------------------- /modules/share_parameter/main.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/share_parameter/main.tf ---------- 2 | 3 | # Obtaining AWS Organization ID 4 | data "aws_organizations_organization" "org" {} 5 | 6 | # Resource Share 7 | resource "aws_ram_resource_share" "resource_share" { 8 | name = var.ram_share_name 9 | allow_external_principals = false 10 | } 11 | 12 | resource "aws_ram_principal_association" "principal_association" { 13 | principal = data.aws_organizations_organization.org.arn 14 | resource_share_arn = aws_ram_resource_share.resource_share.arn 15 | } 16 | 17 | # Creation of the SSM Parameter Store Parameters 18 | resource "aws_ssm_parameter" "parameter" { 19 | for_each = var.parameters 20 | 21 | name = each.key 22 | tier = "Advanced" 23 | type = "String" 24 | value = each.value 25 | } 26 | 27 | # Sharing Parameters via RAM 28 | resource "aws_ram_resource_association" "resource_association" { 29 | for_each = var.parameters 30 | 31 | resource_arn = aws_ssm_parameter.parameter[each.key].arn 32 | resource_share_arn = aws_ram_resource_share.resource_share.arn 33 | } -------------------------------------------------------------------------------- /modules/share_parameter/outputs.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/build-enforce-network-security-multi-account-environments/0a6f3ca892ac0e79a19d7997a7e897a98a88cfe2/modules/share_parameter/outputs.tf -------------------------------------------------------------------------------- /modules/share_parameter/providers.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/share_parameter/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 | } 11 | } -------------------------------------------------------------------------------- /modules/share_parameter/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/share_parameter/variables.tf ---------- 2 | 3 | variable "ram_share_name" { 4 | description = "RAM Share Name." 5 | type = string 6 | } 7 | 8 | variable "parameters" { 9 | description = "List of parameters to share." 10 | type = map(string) 11 | } -------------------------------------------------------------------------------- /network/.header.md: -------------------------------------------------------------------------------- 1 | ## Networking AWS Account -------------------------------------------------------------------------------- /network/.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 -------------------------------------------------------------------------------- /network/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.0.0 | 10 | | [awscc](#requirement\_awscc) | = 0.78.0 | 11 | 12 | ## Providers 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | [archive](#provider\_archive) | n/a | 17 | | [aws](#provider\_aws) | >= 5.0.0 | 18 | | [aws.awsireland](#provider\_aws.awsireland) | >= 5.0.0 | 19 | | [aws.awsnvirginia](#provider\_aws.awsnvirginia) | >= 5.0.0 | 20 | | [aws.awsohio](#provider\_aws.awsohio) | >= 5.0.0 | 21 | 22 | ## Modules 23 | 24 | | Name | Source | Version | 25 | |------|--------|---------| 26 | | [ipam](#module\_ipam) | aws-ia/ipam/aws | 2.0.0 | 27 | | [ireland\_automation](#module\_ireland\_automation) | ./modules/automation | n/a | 28 | | [ireland\_central](#module\_ireland\_central) | aws-ia/cloudwan/aws | 3.2.0 | 29 | | [ireland\_firewall\_policy](#module\_ireland\_firewall\_policy) | ./modules/firewall_policy | n/a | 30 | | [ireland\_retrieve\_parameters](#module\_ireland\_retrieve\_parameters) | ../modules/retrieve_parameters | n/a | 31 | | [ireland\_share\_parameter](#module\_ireland\_share\_parameter) | ../modules/share_parameter | n/a | 32 | | [ireland\_vpc\_endpoints](#module\_ireland\_vpc\_endpoints) | ./modules/vpc_endpoints | n/a | 33 | | [nvirginia\_automation](#module\_nvirginia\_automation) | ./modules/automation | n/a | 34 | | [nvirginia\_central](#module\_nvirginia\_central) | aws-ia/cloudwan/aws | 3.2.0 | 35 | | [nvirginia\_firewall\_policy](#module\_nvirginia\_firewall\_policy) | ./modules/firewall_policy | n/a | 36 | | [nvirginia\_retrieve\_parameters](#module\_nvirginia\_retrieve\_parameters) | ../modules/retrieve_parameters | n/a | 37 | | [nvirginia\_share\_parameter](#module\_nvirginia\_share\_parameter) | ../modules/share_parameter | n/a | 38 | | [nvirginia\_vpc\_endpoints](#module\_nvirginia\_vpc\_endpoints) | ./modules/vpc_endpoints | n/a | 39 | | [ohio\_automation](#module\_ohio\_automation) | ./modules/automation | n/a | 40 | | [ohio\_central](#module\_ohio\_central) | aws-ia/cloudwan/aws | 3.2.0 | 41 | | [ohio\_firewall\_policy](#module\_ohio\_firewall\_policy) | ./modules/firewall_policy | n/a | 42 | | [ohio\_retrieve\_parameters](#module\_ohio\_retrieve\_parameters) | ../modules/retrieve_parameters | n/a | 43 | | [ohio\_share\_parameter](#module\_ohio\_share\_parameter) | ../modules/share_parameter | n/a | 44 | | [ohio\_vpc\_endpoints](#module\_ohio\_vpc\_endpoints) | ./modules/vpc_endpoints | n/a | 45 | 46 | ## Resources 47 | 48 | | Name | Type | 49 | |------|------| 50 | | [aws_iam_policy.automation_lambda_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | 51 | | [aws_iam_role.automation_lambda_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 52 | | [aws_iam_role_policy_attachment.automation_lambda_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | 53 | | [aws_networkmanager_core_network.core_network](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkmanager_core_network) | resource | 54 | | [aws_networkmanager_core_network_policy_attachment.core_network_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkmanager_core_network_policy_attachment) | resource | 55 | | [aws_networkmanager_global_network.global_network](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkmanager_global_network) | resource | 56 | | [aws_ram_principal_association.cwan_principal_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_principal_association) | resource | 57 | | [aws_ram_principal_association.ireland_dns_principal_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_principal_association) | resource | 58 | | [aws_ram_principal_association.nvirginia_dns_principal_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_principal_association) | resource | 59 | | [aws_ram_principal_association.ohio_dns_principal_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_principal_association) | resource | 60 | | [aws_ram_resource_association.cwan_resource_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | 61 | | [aws_ram_resource_association.ireland_profile_resource_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | 62 | | [aws_ram_resource_association.nvirginia_profile_resource_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | 63 | | [aws_ram_resource_association.ohio_profile_resource_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | 64 | | [aws_ram_resource_share.cwan_resource_share](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share) | resource | 65 | | [aws_ram_resource_share.ireland_dns_resource_share](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share) | resource | 66 | | [aws_ram_resource_share.nvirginia_dns_resource_share](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share) | resource | 67 | | [aws_ram_resource_share.ohio_dns_resource_share](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share) | resource | 68 | | [archive_file.cwan_automation_package](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | 69 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 70 | | [aws_ec2_managed_prefix_list.ireland_prefix_list](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_managed_prefix_list) | data source | 71 | | [aws_ec2_managed_prefix_list.nvirginia_prefix_list](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_managed_prefix_list) | data source | 72 | | [aws_ec2_managed_prefix_list.ohio_prefix_list](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_managed_prefix_list) | data source | 73 | | [aws_iam_policy_document.automation_lambda_actions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 74 | | [aws_iam_policy_document.automation_lambda_assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 75 | | [aws_networkmanager_core_network_policy_document.core_network_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/networkmanager_core_network_policy_document) | data source | 76 | | [aws_organizations_organization.org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organization) | data source | 77 | 78 | ## Inputs 79 | 80 | | Name | Description | Type | Default | Required | 81 | |------|-------------|------|---------|:--------:| 82 | | [spoke\_account\_id](#input\_spoke\_account\_id) | Spoke AWS Account ID. | `string` | n/a | yes | 83 | | [aws\_regions](#input\_aws\_regions) | AWS Regions to create the environment. | `map(string)` |
{
"ireland": "eu-west-1",
"nvirginia": "us-east-1",
"ohio": "us-east-2"
}
| no | 84 | | [guarduty\_finding\_names](#input\_guarduty\_finding\_names) | List of GuardDuty Finding names to filter in EventBridge. | `list(string)` |
[
"UnauthorizedAccess:EC2/MaliciousIPCaller.Custom",
"CryptoCurrency:EC2/BitcoinTool.B!DNS",
"Execution:Runtime/SuspiciousTool"
]
| 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 = <\", \"Finding_Type\": \"\", \"Finding_description\": \"\", \"instanceId\": \"\", \"region\": \"\", \"severity\": \"\", \"vpcId\": \"\" }" 35 | } 36 | EOF 37 | } 38 | } 39 | 40 | resource "aws_lambda_permission" "eventbridge_lambda_permission" { 41 | statement_id = "AllowExecutionFromEventBridge" 42 | action = "lambda:InvokeFunction" 43 | function_name = aws_lambda_function.lambda_function.function_name 44 | principal = "events.amazonaws.com" 45 | source_arn = aws_cloudwatch_event_rule.event_rule.arn 46 | } 47 | 48 | # ---------- AWS LAMBDA FUNCTION ---------- 49 | resource "aws_lambda_function" "lambda_function" { 50 | function_name = "${data.aws_region.region.name}-automation-function" 51 | filename = "${path.module}/automation.zip" 52 | source_code_hash = var.source_code_hash 53 | 54 | role = var.lambda_role_arn 55 | runtime = "python3.12" 56 | handler = "automation.lambda_handler" 57 | timeout = 60 58 | memory_size = 128 59 | 60 | environment { 61 | variables = { CORE_NETWORK_ID = var.core_network_id } 62 | } 63 | } -------------------------------------------------------------------------------- /network/modules/automation/outputs.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/automation/outputs.tf ---------- 2 | -------------------------------------------------------------------------------- /network/modules/automation/providers.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/automation/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 | } 11 | } -------------------------------------------------------------------------------- /network/modules/automation/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/automation/variables.tf ---------- 2 | 3 | variable "source_code_hash" { 4 | type = any 5 | description = "AWS Lambda Function's source code hash." 6 | } 7 | 8 | variable "lambda_role_arn" { 9 | type = string 10 | description = "AWS Lambda Function's role ARN." 11 | } 12 | 13 | variable "core_network_id" { 14 | type = string 15 | description = "Core Network ID." 16 | } 17 | 18 | variable "guardduty_finding_names" { 19 | type = list(string) 20 | description = "List of GuardDuty Finding names to filter in EventBridge." 21 | } -------------------------------------------------------------------------------- /network/modules/firewall_policy/main.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/firewall_policy/main.tf ---------- 2 | data "aws_region" "current" {} 3 | 4 | resource "aws_networkfirewall_firewall_policy" "anfw_policy" { 5 | name = "firewall-policy-${var.identifier}-${data.aws_region.current.name}" 6 | 7 | firewall_policy { 8 | # Stateless configuration 9 | stateless_default_actions = ["aws:forward_to_sfe"] 10 | stateless_fragment_default_actions = ["aws:forward_to_sfe"] 11 | 12 | stateless_rule_group_reference { 13 | priority = 10 14 | resource_arn = aws_networkfirewall_rule_group.drop_remote.arn 15 | } 16 | 17 | # Stateful configuration 18 | stateful_engine_options { 19 | rule_order = "STRICT_ORDER" 20 | } 21 | stateful_default_actions = ["aws:drop_strict", "aws:alert_strict"] 22 | stateful_rule_group_reference { 23 | priority = 10 24 | resource_arn = aws_networkfirewall_rule_group.allow_icmp.arn 25 | } 26 | } 27 | } 28 | 29 | # Stateless Rule Group - Dropping any SSH or RDP connection 30 | resource "aws_networkfirewall_rule_group" "drop_remote" { 31 | capacity = 2 32 | name = "drop-remote-${var.identifier}-${data.aws_region.current.name}" 33 | type = "STATELESS" 34 | rule_group { 35 | rules_source { 36 | stateless_rules_and_custom_actions { 37 | 38 | stateless_rule { 39 | priority = 1 40 | rule_definition { 41 | actions = ["aws:drop"] 42 | match_attributes { 43 | protocols = [6] 44 | source { 45 | address_definition = "0.0.0.0/0" 46 | } 47 | source_port { 48 | from_port = 22 49 | to_port = 22 50 | } 51 | destination { 52 | address_definition = "0.0.0.0/0" 53 | } 54 | destination_port { 55 | from_port = 22 56 | to_port = 22 57 | } 58 | } 59 | } 60 | } 61 | 62 | stateless_rule { 63 | priority = 2 64 | rule_definition { 65 | actions = ["aws:drop"] 66 | match_attributes { 67 | protocols = [27] 68 | source { 69 | address_definition = "0.0.0.0/0" 70 | } 71 | destination { 72 | address_definition = "0.0.0.0/0" 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | # Stateful Rule Group - Allowing ICMP traffic 83 | resource "aws_networkfirewall_rule_group" "allow_icmp" { 84 | capacity = 100 85 | name = "allow-icmp-${var.identifier}-${data.aws_region.current.name}" 86 | type = "STATEFUL" 87 | rule_group { 88 | rules_source { 89 | rules_string = < any any (msg: "Allowing ICMP packets"; sid:2; rev:1;) 91 | EOF 92 | } 93 | stateful_rule_options { 94 | rule_order = "STRICT_ORDER" 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /network/modules/firewall_policy/outputs.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/firewall_policy/outputs.tf ---------- 2 | 3 | output "firewall_policy_arn" { 4 | value = aws_networkfirewall_firewall_policy.anfw_policy.arn 5 | } -------------------------------------------------------------------------------- /network/modules/firewall_policy/providers.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/firewall_policy/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 | } 11 | } -------------------------------------------------------------------------------- /network/modules/firewall_policy/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/firewall_policy/variables.tf ---------- 2 | 3 | variable "identifier" { 4 | type = string 5 | description = "Project identifier." 6 | } -------------------------------------------------------------------------------- /network/modules/vpc_endpoints/main.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/vpc_endpoints/main.tf ---------- 2 | data "aws_region" "region" {} 3 | 4 | # VPC endpoints 5 | resource "aws_vpc_endpoint" "endpoint" { 6 | for_each = { for i, endpoint in var.service_endpoints : endpoint => i } 7 | 8 | vpc_id = var.vpc_information.vpc_attributes.id 9 | service_name = "com.amazonaws.${data.aws_region.region.name}.${each.key}" 10 | vpc_endpoint_type = "Interface" 11 | subnet_ids = values({ for k, v in var.vpc_information.private_subnet_attributes_by_az : split("/", k)[1] => v.id if split("/", k)[0] == "endpoints" }) 12 | security_group_ids = [aws_security_group.vpc_endpoint_sg.id] 13 | private_dns_enabled = false 14 | } 15 | 16 | # Security Group 17 | resource "aws_security_group" "vpc_endpoint_sg" { 18 | name = "vpc_endpoint-security-group-${data.aws_region.region.name}" 19 | description = "VPC endpoint Security Group" 20 | vpc_id = var.vpc_information.vpc_attributes.id 21 | } 22 | 23 | resource "aws_vpc_security_group_ingress_rule" "allowing_ingress_https" { 24 | security_group_id = aws_security_group.vpc_endpoint_sg.id 25 | 26 | from_port = 443 27 | to_port = 443 28 | ip_protocol = "tcp" 29 | cidr_ipv4 = "0.0.0.0/0" 30 | } 31 | 32 | resource "aws_vpc_security_group_egress_rule" "allowing_egress_any" { 33 | security_group_id = aws_security_group.vpc_endpoint_sg.id 34 | 35 | ip_protocol = "-1" 36 | cidr_ipv4 = "0.0.0.0/0" 37 | } 38 | 39 | # Route 53 Profile 40 | resource "awscc_route53profiles_profile" "r53_profile" { 41 | name = "${data.aws_region.region.name}-r53-profile" 42 | } 43 | 44 | # PHZs associated to R53 profile 45 | resource "awscc_route53profiles_profile_resource_association" "r53_profile_resource_association" { 46 | for_each = { for i, endpoint in var.service_endpoints : endpoint => i } 47 | 48 | name = "${each.key}-phz" 49 | profile_id = awscc_route53profiles_profile.r53_profile.id 50 | resource_arn = aws_route53_zone.private_hosted_zone[each.key].arn 51 | } 52 | 53 | # Private Hosted Zones 54 | resource "aws_route53_zone" "private_hosted_zone" { 55 | for_each = { for i, endpoint in var.service_endpoints : endpoint => i } 56 | 57 | name = "${each.key}.${data.aws_region.region.name}.amazonaws.com" 58 | 59 | vpc { 60 | vpc_id = var.vpc_information.vpc_attributes.id 61 | } 62 | } 63 | 64 | # DNS Records (CNAME) 65 | resource "aws_route53_record" "endpoint_record" { 66 | for_each = { for i, endpoint in var.service_endpoints : endpoint => i } 67 | 68 | zone_id = aws_route53_zone.private_hosted_zone[each.key].id 69 | name = "" 70 | type = "A" 71 | 72 | alias { 73 | name = aws_vpc_endpoint.endpoint[each.key].dns_entry[0].dns_name 74 | zone_id = aws_vpc_endpoint.endpoint[each.key].dns_entry[0].hosted_zone_id 75 | evaluate_target_health = true 76 | } 77 | } 78 | 79 | resource "aws_route53_record" "s3_endpoint_record" { 80 | for_each = { 81 | for i, endpoint in var.service_endpoints : endpoint => i 82 | if endpoint == "s3" 83 | } 84 | 85 | zone_id = aws_route53_zone.private_hosted_zone[each.key].id 86 | name = "*" 87 | type = "A" 88 | 89 | alias { 90 | name = aws_vpc_endpoint.endpoint[each.key].dns_entry[0].dns_name 91 | zone_id = aws_vpc_endpoint.endpoint[each.key].dns_entry[0].hosted_zone_id 92 | evaluate_target_health = true 93 | } 94 | } -------------------------------------------------------------------------------- /network/modules/vpc_endpoints/outputs.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/vpc_endpoints/outputs.tf ---------- 2 | 3 | output "r53_profile" { 4 | description = "Route 53 Profile ARN." 5 | value = awscc_route53profiles_profile.r53_profile.arn 6 | } -------------------------------------------------------------------------------- /network/modules/vpc_endpoints/providers.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/vpc_endpoints/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 | } -------------------------------------------------------------------------------- /network/modules/vpc_endpoints/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/modules/vpc_endpoints/variables.tf ---------- 2 | 3 | variable "service_endpoints" { 4 | type = list(string) 5 | description = "List of AWS services to create VPC endpoints." 6 | } 7 | 8 | variable "vpc_information" { 9 | type = any 10 | description = "VPC information." 11 | } -------------------------------------------------------------------------------- /network/outputs.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/outputs.tf ---------- -------------------------------------------------------------------------------- /network/providers.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/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-network-tfstate" 18 | key = "network" 19 | region = "eu-west-1" 20 | dynamodb_table = "nis342-network-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 | } -------------------------------------------------------------------------------- /network/variables.tf: -------------------------------------------------------------------------------- 1 | # ---------- network/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 | variable "guarduty_finding_names" { 22 | type = list(string) 23 | description = "List of GuardDuty Finding names to filter in EventBridge." 24 | default = [ 25 | "UnauthorizedAccess:EC2/MaliciousIPCaller.Custom", 26 | "CryptoCurrency:EC2/BitcoinTool.B!DNS", 27 | "Execution:Runtime/SuspiciousTool" 28 | ] 29 | } 30 | 31 | # Spoke AWS Account ID 32 | variable "spoke_account_id" { 33 | type = string 34 | description = "Spoke AWS Account ID." 35 | } 36 | 37 | 38 | -------------------------------------------------------------------------------- /spoke/.header.md: -------------------------------------------------------------------------------- 1 | ## Spoke AWS Account -------------------------------------------------------------------------------- /spoke/.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 -------------------------------------------------------------------------------- /spoke/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.0.0 | 10 | | [awscc](#requirement\_awscc) | = 0.78.0 | 11 | 12 | ## Providers 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | [aws](#provider\_aws) | >= 5.0.0 | 17 | | [aws.awsireland](#provider\_aws.awsireland) | >= 5.0.0 | 18 | | [aws.awsnvirginia](#provider\_aws.awsnvirginia) | >= 5.0.0 | 19 | | [aws.awsohio](#provider\_aws.awsohio) | >= 5.0.0 | 20 | | [awscc.awsccireland](#provider\_awscc.awsccireland) | = 0.78.0 | 21 | | [awscc.awsccnvirginia](#provider\_awscc.awsccnvirginia) | = 0.78.0 | 22 | | [awscc.awsccohio](#provider\_awscc.awsccohio) | = 0.78.0 | 23 | 24 | ## Modules 25 | 26 | | Name | Source | Version | 27 | |------|--------|---------| 28 | | [ireland\_compute](#module\_ireland\_compute) | ./modules/compute | n/a | 29 | | [ireland\_retrieve\_parameters](#module\_ireland\_retrieve\_parameters) | ../modules/retrieve_parameters | n/a | 30 | | [ireland\_share\_parameters](#module\_ireland\_share\_parameters) | ../modules/share_parameter | n/a | 31 | | [ireland\_vpcs](#module\_ireland\_vpcs) | aws-ia/vpc/aws | 4.4.2 | 32 | | [nvirginia\_compute](#module\_nvirginia\_compute) | ./modules/compute | n/a | 33 | | [nvirginia\_retrieve\_parameters](#module\_nvirginia\_retrieve\_parameters) | ../modules/retrieve_parameters | n/a | 34 | | [nvirginia\_share\_parameters](#module\_nvirginia\_share\_parameters) | ../modules/share_parameter | n/a | 35 | | [nvirginia\_vpcs](#module\_nvirginia\_vpcs) | aws-ia/vpc/aws | 4.4.2 | 36 | | [ohio\_compute](#module\_ohio\_compute) | ./modules/compute | n/a | 37 | | [ohio\_retrieve\_parameters](#module\_ohio\_retrieve\_parameters) | ../modules/retrieve_parameters | n/a | 38 | | [ohio\_share\_parameters](#module\_ohio\_share\_parameters) | ../modules/share_parameter | n/a | 39 | | [ohio\_vpcs](#module\_ohio\_vpcs) | aws-ia/vpc/aws | 4.4.2 | 40 | 41 | ## Resources 42 | 43 | | Name | Type | 44 | |------|------| 45 | | [aws_ec2_managed_prefix_list.ireland_prefix_list](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_managed_prefix_list) | resource | 46 | | [aws_ec2_managed_prefix_list.nvirginia_prefix_list](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_managed_prefix_list) | resource | 47 | | [aws_ec2_managed_prefix_list.ohio_prefix_list](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_managed_prefix_list) | resource | 48 | | [aws_ec2_managed_prefix_list_entry.ireland_prefix_list_entry](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_managed_prefix_list_entry) | resource | 49 | | [aws_ec2_managed_prefix_list_entry.nvirginia_prefix_list_entry](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_managed_prefix_list_entry) | resource | 50 | | [aws_ec2_managed_prefix_list_entry.ohio_prefix_list_entry](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_managed_prefix_list_entry) | resource | 51 | | [aws_iam_instance_profile.ec2_instance_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | 52 | | [aws_iam_policy_attachment.s3_readonly_policy_attachment](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy_attachment) | resource | 53 | | [aws_iam_role.role_ec2](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | 54 | | [aws_ram_principal_association.ireland_pl_principal_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_principal_association) | resource | 55 | | [aws_ram_principal_association.nvirginia_pl_principal_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_principal_association) | resource | 56 | | [aws_ram_principal_association.ohio_pl_principal_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_principal_association) | resource | 57 | | [aws_ram_resource_association.ireland_pl_resource_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | 58 | | [aws_ram_resource_association.nvirginia_pl_resource_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | 59 | | [aws_ram_resource_association.ohio_pl_resource_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | 60 | | [aws_ram_resource_share.ireland_pl_resource_share](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share) | resource | 61 | | [aws_ram_resource_share.nvirginia_pl_resource_share](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share) | resource | 62 | | [aws_ram_resource_share.ohio_pl_resource_share](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share) | resource | 63 | | [awscc_route53profiles_profile_association.ireland_r53_profile_association](https://registry.terraform.io/providers/hashicorp/awscc/0.78.0/docs/resources/route53profiles_profile_association) | resource | 64 | | [awscc_route53profiles_profile_association.nvirginia_r53_profile_association](https://registry.terraform.io/providers/hashicorp/awscc/0.78.0/docs/resources/route53profiles_profile_association) | resource | 65 | | [awscc_route53profiles_profile_association.ohio_r53_profile_association](https://registry.terraform.io/providers/hashicorp/awscc/0.78.0/docs/resources/route53profiles_profile_association) | resource | 66 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | 67 | | [aws_iam_policy_document.policy_document](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | 68 | | [aws_organizations_organization.org](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organization) | data source | 69 | | [aws_region.region](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | 70 | | [aws_vpc.ireland_vpcs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | 71 | | [aws_vpc.nvirginia_vpcs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | 72 | | [aws_vpc.ohio_vpcs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | 73 | 74 | ## Inputs 75 | 76 | | Name | Description | Type | Default | Required | 77 | |------|-------------|------|---------|:--------:| 78 | | [networking\_account\_id](#input\_networking\_account\_id) | Networking AWS Account ID. | `string` | n/a | yes | 79 | | [aws\_regions](#input\_aws\_regions) | AWS Regions to create the environment. | `map(string)` |
{
"ireland": "eu-west-1",
"nvirginia": "us-east-1",
"ohio": "us-east-2"
}
| 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": {
"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
}
}
}
| 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 | } --------------------------------------------------------------------------------