├── .gitignore ├── .hooks ├── .pre-commit-config.yaml └── requirements.txt ├── LICENSE ├── Makefile ├── README.md ├── data.tf ├── examples ├── custom │ ├── README.md │ ├── main.tf │ └── output.tf ├── nacl_default │ ├── README.md │ ├── main.tf │ └── output.tf ├── nacl_subnet │ ├── README.md │ ├── main.tf │ └── output.tf ├── nat_gateway_az │ ├── README.md │ ├── main.tf │ └── output.tf ├── nat_gateway_az_existing_eip │ ├── README.md │ ├── main.tf │ └── output.tf ├── nat_gateway_single │ ├── README.md │ ├── main.tf │ └── output.tf ├── nat_gateway_subnet │ ├── README.md │ ├── main.tf │ └── output.tf ├── output_private_subnet_by_type │ ├── README.md │ ├── main.tf │ └── output.tf └── simple │ ├── README.md │ ├── main.tf │ └── output.tf ├── img └── logo_192x192.png ├── locals.tf ├── main.tf ├── outputs.tf ├── subnets_private.tf ├── subnets_pub.tf ├── variables.tf ├── versions.tf └── vpc.tf /.gitignore: -------------------------------------------------------------------------------- 1 | **/.terragrunt-cache/ 2 | **/.terraform.lock.hcl 3 | venv 4 | **.swp 5 | **.idea 6 | **.terraform 7 | **.__pycache__ 8 | 9 | terraform/environments/**/** 10 | -------------------------------------------------------------------------------- /.hooks/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v3.2.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-added-large-files 8 | - repo: https://github.com/psf/black 9 | rev: 21.7b0 10 | hooks: 11 | - id: black 12 | language_version: python3 13 | - repo: https://github.com/antonbabenko/pre-commit-terraform 14 | rev: v1.50.0 15 | hooks: 16 | - id: terraform_fmt 17 | -------------------------------------------------------------------------------- /.hooks/requirements.txt: -------------------------------------------------------------------------------- 1 | pre-commit==2.13.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Viktar Mikalayeu 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | lint: 2 | pre-commit run --all-files -c .hooks/.pre-commit-config.yaml 3 | 4 | install_git_hooks: 5 | $(VENV_BIN_PATH)/pre-commit install 6 | @echo 'Pre-commit hooks installed' 7 | 8 | venv: 9 | virtualenv -p python3 -q $(VENV_PATH) 10 | $(VENV_BIN_PATH)/pip install --default-timeout 60 -r .hooks/requirements.txt 11 | @echo 'Virtualenv created' 12 | 13 | clean: 14 | @rm -fr $(VENV_PATH) 15 | @echo 'Removed virtualenv' 16 | 17 | dev: venv install_git_hooks 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform AWS VPC Module 2 |

3 | SRE Learning Platform 4 |

5 | 6 | ## Overview 7 | 8 | This Terraform module creates an Amazon Virtual Private Cloud (VPC) with both public and private subnets, route tables, Network ACLs (NACLs), and NAT Gateways. It provides an extensible and customizable setup for scalable networking in AWS. 9 | 10 | ## Features 11 | - **Creation of a VPC** with specified CIDR blocks, tags, and NACL settings. 12 | - **Dynamic creation of public and private subnets** with automatic retrieval of their IDs. 13 | - **Subnet creation** with the ability to assign them to specific Availability Zones (AZ) or AZ IDs. 14 | - **Independent tagging** for each subnet, allowing for individual resource labeling. 15 | - **Flexible network management**: ability to add new subnets or remove existing ones without impacting other subnets. 16 | - Support for **three NAT Gateway scenarios** (by default, **AZ NAT Gateway** is used): 17 | - **AZ**: One NAT Gateway is created for each AZ specified in private subnets. 18 | - **SINGLE**: One NAT Gateway is created for the entire VPC. 19 | - **SUBNET**: One NAT Gateway per subnet. 20 | - **NONE**: No NAT Gateway is created for the subnet, and no routes for 0.0.0.0/0 are configured. 21 | - Each subnet can be configured with any NAT Gateway type, and all types can coexist within a single VPC. 22 | - **Custom DHCP options** to fine-tune network configurations. 23 | - **Output** of all created resources, including subnet IDs grouped by type, AZ, and AZ ID. 24 | 25 | 26 | 27 | 28 | Table Of Contents 29 | ----------------- 30 | 31 | * [Quick Start](#quick-start) 32 | * [Input Variables](#input-Variables) 33 | * [VPC](#vpc) 34 | * [Subnets](#subnets) 35 | * [Public Subnets](#public-subnets) 36 | * [Private Subnets](#private-subnets) 37 | * [Other](#other) 38 | * [Output](#output) 39 | * [Examples](#examples) 40 | * [Contribution](#contribution) 41 | * [License and Usage Agreement](#license-and-usage-agreement) 42 | * [Contacts](#contacts) 43 | 44 | 45 | 46 | ## Quick Start 47 | 48 | ```hcl 49 | provider "aws" { 50 | region = "eu-north-1" 51 | } 52 | 53 | 54 | module "vpc" { 55 | source = "ViktorUJ/vpc/aws" 56 | tags_default = { 57 | "Owner" = "DevOps Team" 58 | "Terraform" = "true" 59 | "cost_center" = "1111" 60 | } 61 | vpc = { 62 | name = "test-vpc" 63 | cidr = "10.10.0.0/16" 64 | } 65 | 66 | subnets = { 67 | public = { 68 | "pub1" = { 69 | name = "public-subnet-1" 70 | cidr = "10.10.1.0/24" 71 | az = "eu-north-1c" 72 | } 73 | "pub2" = { 74 | name = "public-subnet-2" 75 | cidr = "10.10.2.0/24" 76 | az = "eu-north-1a" 77 | } 78 | 79 | } 80 | private = { 81 | 82 | "private1" = { 83 | name = "private-subnet-1" 84 | cidr = "10.10.11.0/24" 85 | az = "eu-north-1a" 86 | tags = { "cost_center" = "1234" } 87 | 88 | } 89 | "private2" = { 90 | name = "private-subnet-2" 91 | cidr = "10.10.12.0/24" 92 | az = "eu-north-1c" 93 | tags = { "cost_center" = "5678" } 94 | } 95 | } 96 | } 97 | } 98 | 99 | output "vpc_id" { 100 | value = module.vpc.vpc_raw.id 101 | } 102 | 103 | output "vpc_cidr_block" { 104 | value = module.vpc.vpc_raw.cidr_block 105 | } 106 | 107 | 108 | output "private_subnets_by_type" { 109 | value = module.vpc.private_subnets_by_type 110 | } 111 | output "public_subnets_by_type" { 112 | value = module.vpc.public_subnets_by_type 113 | } 114 | ``` 115 | 116 | ## Input Variables 117 | 118 | ### VPC 119 | 120 | | Variable | Type | Required | Default | Description | 121 | |--------------------------------------------|-----------------|----------|------------------------------|-----------------------------------------------------------------------------------------------------| 122 | | **`vpc.name`** | `string` | Yes | N/A | The name of the VPC. | 123 | | **`vpc.cidr`** | `string` | Yes | N/A | The CIDR block for the VPC. | 124 | | **`vpc.secondary_cidr_blocks`** | `list(string)` | No | `[]` | Additional CIDR blocks to associate with the VPC. | 125 | | **`vpc.tags`** | `map(string)` | No | `{}` | Tags to assign to the VPC. | 126 | | **`vpc.instance_tenancy`** | `string` | No | `default` | Instance tenancy option for instances in the VPC. Options: `default`, `dedicated`. | 127 | | **`vpc.enable_dns_support`** | `bool` | No | `true` | Enable DNS support in the VPC. | 128 | | **`vpc.enable_dns_hostnames`** | `bool` | No | `false` | Enable DNS hostnames in the VPC. | 129 | | **`vpc.nacl_default`** | `map(object)` | No | `{}` | Default NACL rules for the VPC. | 130 | | **`vpc.dhcp_options`** | `object` | No | `{}` | DHCP options set for the VPC. | 131 | 132 | ### Subnets 133 | 134 | #### Public Subnets 135 | 136 | | Variable | Type | Required | Default | Description | 137 | |-----------------------------------------|-----------------|----------|------------------------------|----------------------------------------------------------------------------| 138 | | **`subnets.public`** | `map(object)` | No | N/A | Public subnets | 139 | | **`subnets.public.name`** | `string` | Yes | N/A | Name of the public subnet. | 140 | | **`subnets.public.cidr`** | `string` | Yes | N/A | The CIDR block for the public subnet. | 141 | | **`subnets.public.az`** | `string` | Yes | N/A | Availability Zone or Availability Zone ID for the public subnet. | 142 | | **`subnets.public.tags`** | `map(string)` | No | `{}` | Tags to assign to the public subnet. | 143 | | **`subnets.public.type`** | `string` | No | `"public"` | A custom type label for the public subnet (e.g., `web`, `app`). | 144 | | **`subnets.public.assign_ipv6_address_on_creation`** | `bool` | No | `false` | Assign IPv6 addresses on creation. | 145 | | **`subnets.public.customer_owned_ipv4_pool`** | `string` | No | `""` | ID of the customer-owned IPv4 address pool to use for the subnet. | 146 | | **`subnets.public.enable_dns64`** | `bool` | No | `false` | Enable DNS64 for NAT64 in the public subnet. | 147 | | **`subnets.public.enable_resource_name_dns_aaaa_record_on_launch`** | `bool` | No | `false` | Enable DNS AAAA records for resources in the public subnet. | 148 | | **`subnets.public.enable_resource_name_dns_a_record_on_launch`** | `bool` | No | `false` | Enable DNS A records for resources in the public subnet. | 149 | | **`subnets.public.ipv6_cidr_block`** | `string` | No | `""` | The IPv6 CIDR block for the public subnet. | 150 | | **`subnets.public.ipv6_native`** | `bool` | No | `false` | Enable native IPv6 addressing. | 151 | | **`subnets.public.map_customer_owned_ip_on_launch`** | `bool` | No | `false` | Map customer-owned IP addresses on launch. | 152 | | **`subnets.public.map_public_ip_on_launch`** | `bool` | No | `true` | Map public IP addresses on launch. | 153 | | **`subnets.public.outpost_arn`** | `string` | No | `""` | ARN of the Outpost for the public subnet. | 154 | | **`subnets.public.private_dns_hostname_type_on_launch`** | `string` | No | `ip-name` | The type of DNS hostnames to assign on launch. ip-name or resource-name . | 155 | | **`subnets.public.nacl`** | `map(object)` | No | `{}` | Network ACL (NACL) configuration for the public subnet. | 156 | | **`subnets.public.nacl.egress`** | `string` | No | N/A | Egress rule for NACL (allow or deny). | 157 | | **`subnets.public.nacl.rule_number`** | `string` | No | N/A | Rule number for the NACL entry. | 158 | | **`subnets.public.nacl.rule_action`** | `string` | No | N/A | Rule action for the NACL entry (allow or deny). | 159 | | **`subnets.public.nacl.from_port`** | `string` | No | `""` | From port for NACL rule (if applicable). | 160 | | **`subnets.public.nacl.to_port`** | `string` | No | `""` | To port for NACL rule (if applicable). | 161 | | **`subnets.public.nacl.icmp_code`** | `string` | No | `""` | ICMP code for NACL rule (if applicable). | 162 | | **`subnets.public.nacl.icmp_type`** | `string` | No | `""` | ICMP type for NACL rule (if applicable). | 163 | | **`subnets.public.nacl.protocol`** | `string` | Yes | N/A | Protocol for the NACL rule (e.g., TCP, UDP, ICMP, or `-1` for all). | 164 | | **`subnets.public.nacl.cidr_block`** | `string` | No | `""` | CIDR block for the NACL rule (if applicable). | 165 | | **`subnets.public.nacl.ipv6_cidr_block`** | `string` | No | `""` | IPv6 CIDR block for the NACL rule (if applicable). | 166 | 167 | #### Private Subnets 168 | 169 | | Variable | Type | Required | Default | Description | 170 | |-----------------------------------------------------------|-----------------|----------|------------------------------|-----------------------------------------------------------------------------------------------------| 171 | | **`subnets.private`** | `map(object)` | No | N/A | Private subnets. | 172 | | **`subnets.private.name`** | `string` | Yes | N/A | Name of the private subnet. | 173 | | **`subnets.private.cidr`** | `string` | Yes | N/A | The CIDR block for the private subnet. | 174 | | **`subnets.private.az`** | `string` | Yes | N/A | Availability Zone or Availability Zone ID for the private subnet. | 175 | | **`subnets.private.tags`** | `map(string)` | No | `{}` | Tags to assign to the private subnet. | 176 | | **`subnets.private.type`** | `string` | No | `"private"` | A custom type label for the private subnet (e.g., `app`, `db`). | 177 | | **`subnets.private.nat_gateway`** | `string` | No | `"AZ"` | NAT Gateway configuration: AZ, SINGLE, SUBNET, DEFAULT, or NONE. | 178 | | **`subnets.private.assign_ipv6_address_on_creation`** | `bool` | No | `false` | Assign IPv6 addresses on creation. | 179 | | **`subnets.private.customer_owned_ipv4_pool`** | `string` | No | `""` | ID of the customer-owned IPv4 address pool to use for the subnet. | 180 | | **`subnets.private.enable_dns64`** | `bool` | No | `false` | Enable DNS64 for NAT64 in the private subnet. | 181 | | **`subnets.private.enable_resource_name_dns_aaaa_record_on_launch`** | `bool` | No | `false` | Enable DNS AAAA records for resources in the private subnet. | 182 | | **`subnets.private.enable_resource_name_dns_a_record_on_launch`** | `bool` | No | `false` | Enable DNS A records for resources in the private subnet. | 183 | | **`subnets.private.ipv6_cidr_block`** | `string` | No | `""` | The IPv6 CIDR block for the private subnet. | 184 | | **`subnets.private.ipv6_native`** | `bool` | No | `false` | Enable native IPv6 addressing. | 185 | | **`subnets.private.map_customer_owned_ip_on_launch`** | `bool` | No | `false` | Map customer-owned IP addresses on launch. | 186 | | **`subnets.private.map_public_ip_on_launch`** | `bool` | No | `true` | Map public IP addresses on launch. | 187 | | **`subnets.private.outpost_arn`** | `string` | No | `""` | ARN of the Outpost for the private subnet. | 188 | | **`subnets.private.private_dns_hostname_type_on_launch`** | `string` | No | `"ip-name"` | The type of DNS hostnames to assign on launch for the private subnet. ip-name or resource-name | 189 | | **`subnets.private.nacl`** | `map(object)` | No | `{}` | Network ACL (NACL) configuration for the private subnet. | 190 | | **`subnets.private.nacl.egress`** | `string` | Yes | N/A | Egress rule for NACL (allow or deny). | 191 | | **`subnets.private.nacl.rule_number`** | `string` | Yes | N/A | Rule number for the NACL entry. | 192 | | **`subnets.private.nacl.rule_action`** | `string` | Yes | N/A | Rule action for the NACL entry (allow or deny). | 193 | | **`subnets.private.nacl.from_port`** | `string` | No | `""` | From port for NACL rule (if applicable). | 194 | | **`subnets.private.nacl.to_port`** | `string` | No | `""` | To port for NACL rule (if applicable). | 195 | | **`subnets.private.nacl.icmp_code`** | `string` | No | `""` | ICMP code for NACL rule (if applicable). | 196 | | **`subnets.private.nacl.icmp_type`** | `string` | No | `""` | ICMP type for NACL rule (if applicable). | 197 | | **`subnets.private.nacl.protocol`** | `string` | Yes | N/A | Protocol for the NACL rule (e.g., TCP, UDP, ICMP, or `-1` for all protocols). | 198 | | **`subnets.private.nacl.cidr_block`** | `string` | No | `""` | CIDR block for the NACL rule (if applicable). | 199 | | **`subnets.private.nacl.ipv6_cidr_block`** | `string` | No | `""` | IPv6 CIDR block for the NACL rule (if applicable). | 200 | 201 | ### Other 202 | 203 | | Variable | Type | Required | Default | Description | 204 | |--------------------------------------------|-----------------|----------|------------------------------|-----------------------------------------------------------------------------------------------------| 205 | | **`tags_default`** | `map(string)` | No | `{}` | (Optional) Default tags to assign across all resources created by the module. | 206 | | **`existing_eip_ids_az`** | `map(string)` | No | `{}` | (Optional) Map of Availability Zones (AZ) to existing Elastic IP (EIP) IDs for NAT Gateways. | 207 | 208 | ## Output 209 | 210 | | Variable | Type | Description | 211 | |--------------------------------------------|----------------|----------------------------------------------------------------------------------------| 212 | | **`tags_default`** | `map(string)` | Default tags assigned to all resources. | 213 | | **`subnets_var`** | `map(object)` | The input subnets object for testing. | 214 | | **`vpc_var`** | `object` | The input VPC object. | 215 | | **`vpc_raw`** | `map(object)` | The AWS VPC resource. | 216 | | **`az_mapping`** | `map(object)` | Mapping of Availability Zones (AZ) to their corresponding IDs. | 217 | | **`normalized_public_subnets_all`** | `map(object)` | Normalized list of all public subnets. | 218 | | **`subnets_public_raw`** | `map(object)` | The AWS public subnets resource. | 219 | | **`public_subnets_by_type`** | `map(object)` | Public subnets grouped by type. | 220 | | **`public_subnets_by_az`** | `map(object)` | Public subnets grouped by Availability Zone. | 221 | | **`public_subnets_by_az_id`** | `map(object)` | Public subnets grouped by Availability Zone ID. | 222 | | **`normalized_private_subnets_all`** | `map(object)` | Normalized list of all private subnets. | 223 | | **`subnets_private_raw`** | `map(object)` | The AWS private subnets resource. | 224 | | **`private_subnets_by_type`** | `map(object)` | Private subnets grouped by type. | 225 | | **`private_subnets_by_az`** | `map(object)` | Private subnets grouped by Availability Zone. | 226 | | **`private_subnets_by_az_id`** | `map(object)` | Private subnets grouped by Availability Zone ID. | 227 | | **`nacl_default_rules_raw`** | `map(object)` | Default NACL rules. | 228 | | **`public_nacl_raw`** | `map(object)` | Public Network ACL resource. | 229 | | **`public_nacl_rules_raw`** | `map(object)` | Rules for the public Network ACL. | 230 | | **`private_nacl_raw`** | `map(object)` | Private Network ACL resource. | 231 | | **`private_nacl_rules_raw`** | `map(object)` | Rules for the private Network ACL. | 232 | | **`nat_gateway_single_raw`** | `map(object)` | The NAT gateway for the entire VPC. | 233 | | **`nat_gateway_subnet_raw`** | `map(object)` | The NAT gateway for each subnet. | 234 | | **`nat_gateway_az_raw`** | `map(object)` | The NAT gateway for each Availability Zone. | 235 | | **`route_table_private_raw`** | `map(object)` | Private route table. | 236 | | **`route_table_public_raw`** | `map(object)` | Public route table. | 237 | 238 | ## Examples 239 | 240 | You can find additional examples in the [examples](./examples) directory: 241 | 242 | | Example | Description | 243 | |---------------------------------------------------|----------------------------------------------------------------------------| 244 | | [simple](./examples/simple) | A basic VPC setup with public and private subnets. | 245 | | [custom](./examples/custom) | Example showcasing custom configurations in the VPC ,Subnets , NACL, etc . | 246 | | [nacl_default](./examples/nacl_default) | Demonstrates default Network ACL (NACL) configurations. | 247 | | [nacl_subnet](./examples/nacl_subnet) | Configuring Network ACLs for individual subnets. | 248 | | [nat_gateway_az](./examples/nat_gateway_az) | NAT Gateway setup with one NAT Gateway per Availability Zone (AZ). | 249 | | [nat_gateway_single](./examples/nat_gateway_single)| Using a single NAT Gateway for the entire VPC. | 250 | | [nat_gateway_subnet](./examples/nat_gateway_subnet)| Setting up one NAT Gateway per subnet. | 251 | | [output_private_subnet_by_type](./examples/output_private_subnet_by_type)| Outputting private subnets grouped by type. | 252 | 253 | 254 | ## Contribution 255 | If you want to be part of the project development team, get in touch with [us](#contacts). We are always happy to welcome new members to our development team 256 | 257 | 258 | If you want to say **thank you** or/and support the active development of **module** : 259 | - [Star](https://github.com/ViktorUJ/terraform-aws-vpc) the **terraform-aws-vpc** on Github 260 | - Feel free to write articles about the project on [dev.to](https://dev.to/), [medium](https://medium.com/), [hackernoon](https://hackernoon.com) or on your personal blog and share your experiences. 261 | 262 | 263 | ## License and Usage Agreement 264 | - [Apache License 2.0](LICENSE) 265 | 266 | ## Contacts 267 | 268 | If you encounter any issues or have questions about the project, you can reach out to: 269 | 270 | [![email](https://badgen.net/badge/icon/email?icon=email&label)](mailto:viktoruj@gmail.com) [![Telegram](https://badgen.net/badge/icon/telegram?icon=telegram&label)](https://t.me/viktor_uj) [![LinkedI](https://badgen.net/badge/icon/linkedin?icon=linkedin&label)](https://www.linkedin.com/in/viktar-mikalayeu-mns) 271 | -------------------------------------------------------------------------------- /data.tf: -------------------------------------------------------------------------------- 1 | data "aws_availability_zones" "available" {} 2 | -------------------------------------------------------------------------------- /examples/custom/README.md: -------------------------------------------------------------------------------- 1 | # Example showcasing custom configurations in the VPC ,Subnets , NACL, etc . 2 | -------------------------------------------------------------------------------- /examples/custom/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-north-1" 3 | } 4 | 5 | 6 | module "vpc" { 7 | source = "ViktorUJ/vpc/aws" 8 | tags_default = { 9 | "Owner" = "DevOps Team" 10 | "Terraform" = "true" 11 | "cost_center" = "1111" 12 | } 13 | vpc = { 14 | name = "test-vpc" 15 | cidr = "10.10.0.0/16" 16 | secondary_cidr_blocks = ["10.2.0.0/16", "10.3.0.0/16"] 17 | tags = { "cost_center" = "444" } 18 | dhcp_options = { 19 | ipv6_address_preferred_lease_time = "2147483647" 20 | } 21 | nacl_default = { 22 | test = { 23 | egress = "true" 24 | rule_number = "99" 25 | rule_action = "allow" 26 | protocol = "tcp" 27 | cidr_block = "0.0.0.0/0" 28 | 29 | 30 | } 31 | test2 = { 32 | egress = "false" 33 | rule_number = "99" 34 | rule_action = "allow" 35 | protocol = "tcp" 36 | cidr_block = "0.0.0.0/0" 37 | 38 | 39 | } 40 | } 41 | } 42 | 43 | subnets = { 44 | public = { 45 | "pub1" = { 46 | name = "public-subnet-1" 47 | cidr = "10.10.1.0/24" 48 | az = "eun1-az1" 49 | type = "qa-test" 50 | tags = { "cost_center" = "5555" } 51 | private_dns_hostname_type_on_launch = "resource-name" 52 | nacl = { 53 | test = { 54 | egress = "true" 55 | rule_number = "99" 56 | rule_action = "allow" 57 | protocol = "tcp" 58 | cidr_block = "0.0.0.0/0" 59 | } 60 | test2 = { 61 | egress = "false" 62 | rule_number = "99" 63 | rule_action = "allow" 64 | protocol = "tcp" 65 | cidr_block = "0.0.0.0/0" 66 | } 67 | allow_all_inbound = { 68 | egress = "false" 69 | rule_number = "9999" 70 | rule_action = "allow" 71 | from_port = "0" 72 | to_port = "0" 73 | protocol = "-1" 74 | cidr_block = "0.0.0.0/0" 75 | } 76 | allow_all_outbound = { 77 | egress = "true" 78 | rule_number = "8888" 79 | rule_action = "allow" 80 | from_port = "0" 81 | to_port = "0" 82 | protocol = "-1" 83 | cidr_block = "0.0.0.0/0" 84 | } 85 | 86 | 87 | } 88 | } 89 | "pub2" = { 90 | name = "public-subnet-2" 91 | cidr = "10.10.2.0/24" 92 | az = "eun1-az3" 93 | type = "Devops" 94 | nat_gateway = "DEFAULT" 95 | tags = { "cost_center" = "1234" } 96 | 97 | } 98 | 99 | } 100 | private = { 101 | "private_no_nat" = { 102 | name = "private-subnet-1" 103 | cidr = "10.10.33.0/24" 104 | az = "eun1-az1" 105 | type = "qa-test" 106 | tags = { "cost_center" = "5555" } 107 | private_dns_hostname_type_on_launch = "resource-name" 108 | nat_gateway = "NONE" 109 | nacl = { 110 | test = { 111 | egress = "true" 112 | rule_number = "99" 113 | rule_action = "allow" 114 | protocol = "tcp" 115 | cidr_block = "0.0.0.0/0" 116 | } 117 | test2 = { 118 | egress = "false" 119 | rule_number = "99" 120 | rule_action = "allow" 121 | protocol = "tcp" 122 | cidr_block = "0.0.0.0/0" 123 | } 124 | } 125 | } 126 | 127 | "private1" = { 128 | name = "private-subnet-1" 129 | cidr = "10.10.11.0/24" 130 | az = "eun1-az1" 131 | type = "qa-test" 132 | tags = { "cost_center" = "5555" } 133 | private_dns_hostname_type_on_launch = "resource-name" 134 | nat_gateway = "SINGLE" 135 | nacl = { 136 | test = { 137 | egress = "true" 138 | rule_number = "99" 139 | rule_action = "allow" 140 | protocol = "tcp" 141 | cidr_block = "0.0.0.0/0" 142 | } 143 | test2 = { 144 | egress = "false" 145 | rule_number = "99" 146 | rule_action = "allow" 147 | protocol = "tcp" 148 | cidr_block = "0.0.0.0/0" 149 | } 150 | } 151 | 152 | 153 | } 154 | "private2" = { 155 | name = "private-subnet-2" 156 | cidr = "10.10.12.0/24" 157 | az = "eun1-az3" 158 | type = "Devops" 159 | tags = { "cost_center" = "1234" } 160 | nat_gateway = "SINGLE" 161 | 162 | } 163 | "private3" = { 164 | name = "private-subnet-3" 165 | cidr = "10.10.13.0/24" 166 | az = "eu-north-1a" 167 | type = "Devops" 168 | tags = { "cost_center" = "1234" } 169 | nat_gateway = "SINGLE" 170 | 171 | } 172 | "private4" = { 173 | name = "private-subnet-4" 174 | cidr = "10.10.14.0/24" 175 | az = "eun1-az3" 176 | type = "Devops" 177 | tags = { "cost_center" = "1234" } 178 | nat_gateway = "SINGLE" 179 | 180 | } 181 | 182 | "k8s1" = { 183 | name = "private-k8s-1" 184 | cidr = "10.10.15.0/24" 185 | az = "eun1-az3" 186 | type = "k8s" 187 | tags = { "cost_center" = "1234" } 188 | nat_gateway = "SINGLE" 189 | 190 | } 191 | "k8s2" = { 192 | name = "private-k8s-2" 193 | cidr = "10.10.16.0/24" 194 | az = "eun1-az3" 195 | type = "k8s" 196 | tags = { "cost_center" = "1234" } 197 | nat_gateway = "SINGLE" 198 | 199 | 200 | } 201 | 202 | } 203 | 204 | 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /examples/custom/output.tf: -------------------------------------------------------------------------------- 1 | # inputs 2 | output "tags_default" { 3 | description = "Default tags" 4 | value = module.vpc.tags_default 5 | } 6 | 7 | output "subnets_var" { 8 | description = "for test" 9 | value = module.vpc.subnets_var 10 | } 11 | output "vpc_var" { 12 | value = module.vpc.vpc_var 13 | } 14 | 15 | # vpc 16 | output "vpc_raw" { 17 | value = module.vpc.vpc_raw 18 | } 19 | 20 | 21 | 22 | # debug 23 | output "az_mapping" { 24 | value = module.vpc.az_mapping 25 | } 26 | 27 | 28 | #subnets public 29 | 30 | output "normalized_public_subnets_all" { 31 | value = module.vpc.normalized_public_subnets_all 32 | } 33 | 34 | output "subnets_public_raw" { 35 | value = module.vpc.subnets_public_raw 36 | } 37 | output "public_subnets_by_type" { 38 | value = module.vpc.public_subnets_by_type 39 | } 40 | 41 | output "public_subnets_by_az" { 42 | value = module.vpc.public_subnets_by_az 43 | } 44 | 45 | output "public_subnets_by_az_id" { 46 | value = module.vpc.public_subnets_by_az_id 47 | } 48 | #subnets private 49 | output "normalized_private_subnets_all" { 50 | value = module.vpc.normalized_private_subnets_all 51 | } 52 | 53 | output "subnets_private_raw" { 54 | value = module.vpc.subnets_private_raw 55 | } 56 | 57 | output "private_subnets_by_type" { 58 | value = module.vpc.private_subnets_by_type 59 | } 60 | 61 | output "private_subnets_by_az" { 62 | value = module.vpc.private_subnets_by_az 63 | } 64 | 65 | output "private_subnets_by_az_id" { 66 | value = module.vpc.private_subnets_by_az_id 67 | } 68 | 69 | # NACL 70 | output "public_nacl_raw" { 71 | value = module.vpc.public_nacl_raw 72 | 73 | } 74 | output "public_nacl_rules_raw" { 75 | value = module.vpc.public_nacl_rules_raw 76 | } 77 | 78 | output "private_nacl_raw" { 79 | value = module.vpc.private_nacl_raw 80 | } 81 | output "private_nacl_rules_raw" { 82 | value = module.vpc.private_nacl_rules_raw 83 | } 84 | 85 | # NAT Gateway 86 | 87 | output "nat_gateway_single_raw" { 88 | value = module.vpc.nat_gateway_single_raw 89 | } 90 | 91 | output "nat_gateway_subnet_raw" { 92 | value = module.vpc.nat_gateway_subnet_raw 93 | } 94 | 95 | output "nat_gateway_az_raw" { 96 | value = module.vpc.nat_gateway_az_raw 97 | } 98 | 99 | # Route Table 100 | output "route_table_private_raw" { 101 | value = module.vpc.route_table_private_raw 102 | } 103 | 104 | output "route_table_public_raw" { 105 | value = module.vpc.route_table_public_raw 106 | } 107 | 108 | 109 | # EXAMPLES 110 | 111 | output "vpc_id" { 112 | value = module.vpc.vpc_raw.id 113 | } 114 | 115 | output "vpc_cidr_block" { 116 | value = module.vpc.vpc_raw.cidr_block 117 | } 118 | 119 | output "k8s_private_subnets" { 120 | description = "K8s private subnets" 121 | value = module.vpc.private_subnets_by_type.k8s.ids 122 | } 123 | 124 | output "private_subnets_az_id" { 125 | description = "privare subnets in AZ iD eun1-az3" 126 | value = module.vpc.private_subnets_by_az_id.eun1-az3 127 | } 128 | 129 | output "private_subnets_az" { 130 | description = "privare subnets in AZ eu-north-1a" 131 | value = module.vpc.private_subnets_by_az.eu-north-1a 132 | } 133 | -------------------------------------------------------------------------------- /examples/nacl_default/README.md: -------------------------------------------------------------------------------- 1 | # Demonstrates default Network ACL (NACL) configurations. 2 | -------------------------------------------------------------------------------- /examples/nacl_default/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-north-1" 3 | } 4 | 5 | 6 | module "vpc" { 7 | source = "ViktorUJ/vpc/aws" 8 | tags_default = { 9 | "Owner" = "DevOps Team" 10 | "Terraform" = "true" 11 | "cost_center" = "1111" 12 | } 13 | vpc = { 14 | name = "test-vpc" 15 | cidr = "10.10.0.0/16" 16 | nacl_default = { 17 | test = { 18 | egress = "true" 19 | rule_number = "99" 20 | rule_action = "allow" 21 | protocol = "tcp" 22 | cidr_block = "0.0.0.0/0" 23 | 24 | 25 | } 26 | test2 = { 27 | egress = "false" 28 | rule_number = "99" 29 | rule_action = "allow" 30 | protocol = "tcp" 31 | cidr_block = "0.0.0.0/0" 32 | 33 | 34 | } 35 | } 36 | } 37 | 38 | subnets = { 39 | public = {} 40 | private = {} 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/nacl_default/output.tf: -------------------------------------------------------------------------------- 1 | output "vpc_raw" { 2 | value = module.vpc.vpc_raw 3 | } 4 | 5 | output "nacl_default_rules_raw" { 6 | value = module.vpc.nacl_default_rules_raw 7 | } 8 | -------------------------------------------------------------------------------- /examples/nacl_subnet/README.md: -------------------------------------------------------------------------------- 1 | # Configuring Network ACLs for individual subnets. 2 | -------------------------------------------------------------------------------- /examples/nacl_subnet/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-north-1" 3 | } 4 | 5 | 6 | module "vpc" { 7 | source = "ViktorUJ/vpc/aws" 8 | tags_default = { 9 | "Owner" = "DevOps Team" 10 | "Terraform" = "true" 11 | "cost_center" = "1111" 12 | } 13 | vpc = { 14 | name = "test-vpc" 15 | cidr = "10.10.0.0/16" 16 | } 17 | 18 | subnets = { 19 | public = { 20 | "pub1" = { 21 | name = "public-subnet-1" 22 | cidr = "10.10.1.0/24" 23 | az = "eu-north-1a" 24 | } 25 | "pub2" = { 26 | name = "public-subnet-2" 27 | cidr = "10.10.2.0/24" 28 | az = "eu-north-1a" 29 | } 30 | 31 | } 32 | private = { 33 | 34 | "private1" = { 35 | name = "private-subnet-1" 36 | cidr = "10.10.11.0/24" 37 | az = "eu-north-1a" 38 | nacl = { 39 | test = { 40 | egress = "true" 41 | rule_number = "99" 42 | rule_action = "allow" 43 | protocol = "tcp" 44 | cidr_block = "0.0.0.0/0" 45 | } 46 | test2 = { 47 | egress = "false" 48 | rule_number = "99" 49 | rule_action = "allow" 50 | protocol = "tcp" 51 | cidr_block = "0.0.0.0/0" 52 | } 53 | allow_all_inbound = { 54 | egress = "false" 55 | rule_number = "9999" 56 | rule_action = "allow" 57 | from_port = "0" 58 | to_port = "0" 59 | protocol = "-1" 60 | cidr_block = "0.0.0.0/0" 61 | } 62 | allow_all_outbound = { 63 | egress = "true" 64 | rule_number = "8888" 65 | rule_action = "allow" 66 | from_port = "0" 67 | to_port = "0" 68 | protocol = "-1" 69 | cidr_block = "0.0.0.0/0" 70 | } 71 | 72 | 73 | } 74 | 75 | } 76 | "private2" = { 77 | name = "private-subnet-2" 78 | cidr = "10.10.12.0/24" 79 | az = "eu-north-1a" 80 | nacl = { 81 | test = { 82 | egress = "true" 83 | rule_number = "99" 84 | rule_action = "allow" 85 | protocol = "tcp" 86 | cidr_block = "0.0.0.0/0" 87 | } 88 | ssh = { 89 | egress = "false" 90 | rule_number = "88" 91 | rule_action = "allow" 92 | protocol = "tcp" 93 | from_port = "22" 94 | to_port = "22" 95 | cidr_block = "0.0.0.0/0" 96 | 97 | } 98 | allow_all_inbound = { 99 | egress = "false" 100 | rule_number = "9999" 101 | rule_action = "allow" 102 | from_port = "0" 103 | to_port = "0" 104 | protocol = "-1" 105 | cidr_block = "0.0.0.0/0" 106 | } 107 | allow_all_outbound = { 108 | egress = "true" 109 | rule_number = "8888" 110 | rule_action = "allow" 111 | from_port = "0" 112 | to_port = "0" 113 | protocol = "-1" 114 | cidr_block = "0.0.0.0/0" 115 | } 116 | 117 | 118 | } 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /examples/nacl_subnet/output.tf: -------------------------------------------------------------------------------- 1 | output "private_nacl_raw" { 2 | value = module.vpc.private_nacl_raw 3 | } 4 | 5 | output "private_nacl_rules_raw" { 6 | value = module.vpc.private_nacl_rules_raw 7 | } 8 | -------------------------------------------------------------------------------- /examples/nat_gateway_az/README.md: -------------------------------------------------------------------------------- 1 | # NAT Gateway setup with one NAT Gateway per Availability Zone (AZ). 2 | -------------------------------------------------------------------------------- /examples/nat_gateway_az/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-north-1" 3 | } 4 | 5 | 6 | module "vpc" { 7 | source = "ViktorUJ/vpc/aws" 8 | tags_default = { 9 | "Owner" = "DevOps Team" 10 | "Terraform" = "true" 11 | "cost_center" = "1111" 12 | } 13 | vpc = { 14 | name = "test-vpc" 15 | cidr = "10.10.0.0/16" 16 | } 17 | 18 | subnets = { 19 | public = { 20 | "pub1" = { 21 | name = "public-subnet-1" 22 | cidr = "10.10.1.0/24" 23 | az = "eu-north-1a" 24 | } 25 | "pub2" = { 26 | name = "public-subnet-2" 27 | cidr = "10.10.2.0/24" 28 | az = "eu-north-1b" 29 | } 30 | 31 | } 32 | private = { 33 | 34 | "private1" = { 35 | name = "private-subnet-1" 36 | cidr = "10.10.11.0/24" 37 | az = "eu-north-1a" 38 | nat_gateway = "AZ" # it is default . you can remove it 39 | 40 | } 41 | "private2" = { 42 | name = "private-subnet-2" 43 | cidr = "10.10.12.0/24" 44 | az = "eu-north-1a" 45 | # nat_gateway = "AZ" # it is default . you can remove it 46 | } 47 | "private3" = { 48 | name = "private-subnet-3" 49 | cidr = "10.10.13.0/24" 50 | az = "eu-north-1b" 51 | # nat_gateway = "AZ" # it is default . you can remove it 52 | } 53 | "private4" = { 54 | name = "private-subnet-4" 55 | cidr = "10.10.14.0/24" 56 | az = "eu-north-1b" 57 | # nat_gateway = "AZ" # it is default . you can remove it 58 | } 59 | 60 | 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/nat_gateway_az/output.tf: -------------------------------------------------------------------------------- 1 | output "nat_gateway_az_raw" { 2 | value = module.vpc.nat_gateway_az_raw 3 | } 4 | -------------------------------------------------------------------------------- /examples/nat_gateway_az_existing_eip/README.md: -------------------------------------------------------------------------------- 1 | # NAT Gateway setup with one NAT Gateway per Availability Zone (AZ), 2 | # and an existing Elastic IP (EIP) for each NAT Gateway. 3 | -------------------------------------------------------------------------------- /examples/nat_gateway_az_existing_eip/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "us-west-1" 3 | } 4 | 5 | module "vpc" { 6 | source = "ViktorUJ/vpc/aws" 7 | # Using existing EIP IDs here 8 | existing_eip_ids_az = { 9 | "us-west-1a" = "vpc-0a1b2c3d4e5f6g7h8" 10 | "us-east-1b" = "vpc-0a1b2c3d4e5f6g7h8" 11 | } 12 | vpc = { 13 | name = "main" 14 | cidr = "10.2.0.0/16" 15 | secondary_cidr_blocks = ["100.64.0.0/16"] 16 | instance_tenancy = "default" 17 | enable_dns_support = true 18 | enable_dns_hostnames = false 19 | tags_default = { 20 | "Environment" = "Dev" 21 | "Name" = "EKS-VPC" 22 | "Owner" = "DevOps" 23 | } 24 | dhcp_options = { 25 | domain_name = "" 26 | domain_name_servers = [] 27 | ntp_servers = [] 28 | netbios_name_servers = [] 29 | netbios_node_type = "" 30 | } 31 | } 32 | 33 | subnets = { 34 | public = { 35 | public1 = { 36 | name = "public-1" 37 | cidr = "10.2.2.0/24" 38 | az = "us-west-1a" 39 | tags = { "Name" = "public-1" } 40 | type = "public" 41 | assign_ipv6_address_on_creation = false 42 | customer_owned_ipv4_pool = "" 43 | enable_dns64 = false 44 | enable_resource_name_dns_aaaa_record_on_launch = false 45 | enable_resource_name_dns_a_record_on_launch = false 46 | ipv6_native = false 47 | map_customer_owned_ip_on_launch = false 48 | map_public_ip_on_launch = true 49 | outpost_arn = "" 50 | private_dns_hostname_type_on_launch = "ip-name" 51 | nacl = { 52 | default_ingress = { 53 | egress = false 54 | rule_number = 100 55 | rule_action = "allow" 56 | from_port = 0 57 | to_port = 0 58 | protocol = "-1" 59 | cidr_block = "0.0.0.0/0" 60 | } 61 | default_egress = { 62 | egress = true 63 | rule_number = 100 64 | rule_action = "allow" 65 | from_port = 0 66 | to_port = 0 67 | protocol = "-1" 68 | cidr_block = "0.0.0.0/0" 69 | } 70 | } 71 | } 72 | public2 = { 73 | name = "public-2" 74 | cidr = "10.2.3.0/24" 75 | az = "us-west-1b" 76 | tags = { "Name" = "public-2" } 77 | type = "public" 78 | assign_ipv6_address_on_creation = false 79 | customer_owned_ipv4_pool = "" 80 | enable_dns64 = false 81 | enable_resource_name_dns_aaaa_record_on_launch = false 82 | enable_resource_name_dns_a_record_on_launch = false 83 | ipv6_native = false 84 | map_customer_owned_ip_on_launch = false 85 | map_public_ip_on_launch = true 86 | outpost_arn = "" 87 | private_dns_hostname_type_on_launch = "ip-name" 88 | nacl = { 89 | default_ingress = { 90 | egress = false 91 | rule_number = 100 92 | rule_action = "allow" 93 | from_port = 0 94 | to_port = 0 95 | protocol = "-1" 96 | cidr_block = "0.0.0.0/0" 97 | } 98 | default_egress = { 99 | egress = true 100 | rule_number = 100 101 | rule_action = "allow" 102 | from_port = 0 103 | to_port = 0 104 | protocol = "-1" 105 | cidr_block = "0.0.0.0/0" 106 | } 107 | } 108 | } 109 | } 110 | private = { 111 | eks1 = { 112 | name = "eks-control-plane-1" 113 | cidr = "10.2.0.0/24" 114 | az = "us-west-1a" 115 | tags = {} 116 | type = "private" 117 | assign_ipv6_address_on_creation = false 118 | customer_owned_ipv4_pool = "" 119 | enable_dns64 = false 120 | enable_resource_name_dns_aaaa_record_on_launch = false 121 | enable_resource_name_dns_a_record_on_launch = false 122 | ipv6_native = false 123 | map_customer_owned_ip_on_launch = false 124 | map_public_ip_on_launch = false 125 | outpost_arn = "" 126 | private_dns_hostname_type_on_launch = "ip-name" 127 | nacl = { 128 | eks_default_ingress = { 129 | egress = false 130 | rule_number = 100 131 | rule_action = "allow" 132 | from_port = 0 133 | to_port = 0 134 | protocol = "-1" 135 | cidr_block = "0.0.0.0/0" 136 | } 137 | eks_default_egress = { 138 | egress = true 139 | rule_number = 100 140 | rule_action = "allow" 141 | from_port = 0 142 | to_port = 0 143 | protocol = "-1" 144 | cidr_block = "0.0.0.0/0" 145 | } 146 | } 147 | } 148 | eks2 = { 149 | name = "elk-control-plane-2" 150 | cidr = "10.2.1.0/24" 151 | az = "us-west-1b" 152 | tags = {} 153 | type = "private" 154 | assign_ipv6_address_on_creation = false 155 | customer_owned_ipv4_pool = "" 156 | enable_dns64 = false 157 | enable_resource_name_dns_aaaa_record_on_launch = false 158 | enable_resource_name_dns_a_record_on_launch = false 159 | ipv6_native = false 160 | map_customer_owned_ip_on_launch = false 161 | map_public_ip_on_launch = false 162 | outpost_arn = "" 163 | private_dns_hostname_type_on_launch = "ip-name" 164 | nacl = { 165 | eks_default_ingress = { 166 | egress = false 167 | rule_number = 100 168 | rule_action = "allow" 169 | from_port = 0 170 | to_port = 0 171 | protocol = "-1" 172 | cidr_block = "0.0.0.0/0" 173 | } 174 | eks_default_egress = { 175 | egress = true 176 | rule_number = 100 177 | rule_action = "allow" 178 | from_port = 0 179 | to_port = 0 180 | protocol = "-1" 181 | cidr_block = "0.0.0.0/0" 182 | } 183 | } 184 | } 185 | } 186 | } 187 | 188 | tags_default = { 189 | "Environment" = "Dev" 190 | "Name" = "EKS-VPC" 191 | "Owner" = "DevOps" 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /examples/nat_gateway_az_existing_eip/output.tf: -------------------------------------------------------------------------------- 1 | output "nat_gateway_az_raw" { 2 | value = module.vpc.nat_gateway_az_raw 3 | } 4 | -------------------------------------------------------------------------------- /examples/nat_gateway_single/README.md: -------------------------------------------------------------------------------- 1 | # Using a single NAT Gateway for the entire VPC. 2 | -------------------------------------------------------------------------------- /examples/nat_gateway_single/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-north-1" 3 | } 4 | 5 | 6 | module "vpc" { 7 | source = "ViktorUJ/vpc/aws" 8 | tags_default = { 9 | "Owner" = "DevOps Team" 10 | "Terraform" = "true" 11 | "cost_center" = "1111" 12 | } 13 | vpc = { 14 | name = "test-vpc" 15 | cidr = "10.10.0.0/16" 16 | } 17 | 18 | subnets = { 19 | public = { 20 | "pub1" = { 21 | name = "public-subnet-1" 22 | cidr = "10.10.1.0/24" 23 | az = "eu-north-1a" 24 | nat_gateway = "DEFAULT" 25 | } 26 | "pub2" = { 27 | name = "public-subnet-2" 28 | cidr = "10.10.2.0/24" 29 | az = "eu-north-1a" 30 | } 31 | 32 | } 33 | private = { 34 | 35 | "private1" = { 36 | name = "private-subnet-1" 37 | cidr = "10.10.11.0/24" 38 | az = "eu-north-1a" 39 | nat_gateway = "SINGLE" 40 | 41 | } 42 | "private2" = { 43 | name = "private-subnet-2" 44 | cidr = "10.10.12.0/24" 45 | az = "eu-north-1a" 46 | nat_gateway = "SINGLE" 47 | } 48 | "private3" = { 49 | name = "private-subnet-3" 50 | cidr = "10.10.13.0/24" 51 | az = "eu-north-1b" 52 | nat_gateway = "SINGLE" 53 | } 54 | "private4" = { 55 | name = "private-subnet-4" 56 | cidr = "10.10.14.0/24" 57 | az = "eu-north-1b" 58 | nat_gateway = "SINGLE" 59 | } 60 | 61 | 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/nat_gateway_single/output.tf: -------------------------------------------------------------------------------- 1 | output "nat_gateway_single_raw" { 2 | value = module.vpc.nat_gateway_single_raw 3 | } 4 | -------------------------------------------------------------------------------- /examples/nat_gateway_subnet/README.md: -------------------------------------------------------------------------------- 1 | # Setting up one NAT Gateway per subnet. 2 | -------------------------------------------------------------------------------- /examples/nat_gateway_subnet/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-north-1" 3 | } 4 | 5 | 6 | module "vpc" { 7 | source = "ViktorUJ/vpc/aws" 8 | tags_default = { 9 | "Owner" = "DevOps Team" 10 | "Terraform" = "true" 11 | "cost_center" = "1111" 12 | } 13 | vpc = { 14 | name = "test-vpc" 15 | cidr = "10.10.0.0/16" 16 | } 17 | 18 | subnets = { 19 | public = { 20 | "pub1" = { 21 | name = "public-subnet-1" 22 | cidr = "10.10.1.0/24" 23 | az = "eu-north-1a" 24 | } 25 | "pub2" = { 26 | name = "public-subnet-2" 27 | cidr = "10.10.2.0/24" 28 | az = "eu-north-1b" 29 | } 30 | 31 | } 32 | private = { 33 | 34 | "private1" = { 35 | name = "private-subnet-1" 36 | cidr = "10.10.11.0/24" 37 | az = "eu-north-1a" 38 | nat_gateway = "SUBNET" 39 | 40 | } 41 | "private2" = { 42 | name = "private-subnet-2" 43 | cidr = "10.10.12.0/24" 44 | az = "eu-north-1a" 45 | nat_gateway = "SUBNET" 46 | } 47 | "private3" = { 48 | name = "private-subnet-3" 49 | cidr = "10.10.13.0/24" 50 | az = "eu-north-1b" 51 | nat_gateway = "SUBNET" 52 | } 53 | "private4" = { 54 | name = "private-subnet-4" 55 | cidr = "10.10.14.0/24" 56 | az = "eu-north-1b" 57 | nat_gateway = "NONE" # this subnet will not have NAT Gateway 58 | } 59 | 60 | 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/nat_gateway_subnet/output.tf: -------------------------------------------------------------------------------- 1 | output "nat_gateway_subnet_raw" { 2 | value = module.vpc.nat_gateway_subnet_raw 3 | } 4 | -------------------------------------------------------------------------------- /examples/output_private_subnet_by_type/README.md: -------------------------------------------------------------------------------- 1 | # Outputting private subnets grouped by type. 2 | -------------------------------------------------------------------------------- /examples/output_private_subnet_by_type/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-north-1" 3 | } 4 | 5 | 6 | module "vpc" { 7 | source = "ViktorUJ/vpc/aws" 8 | tags_default = { 9 | "Owner" = "DevOps Team" 10 | "Terraform" = "true" 11 | "cost_center" = "1111" 12 | } 13 | vpc = { 14 | name = "test-vpc" 15 | cidr = "10.10.0.0/16" 16 | } 17 | 18 | subnets = { 19 | public = { 20 | "pub1" = { 21 | name = "public-subnet-1" 22 | cidr = "10.10.1.0/24" 23 | az = "eu-north-1a" 24 | } 25 | "pub2" = { 26 | name = "public-subnet-2" 27 | cidr = "10.10.2.0/24" 28 | az = "eu-north-1a" 29 | } 30 | 31 | } 32 | private = { 33 | 34 | "rds1" = { 35 | name = "rds-subnet-1" 36 | cidr = "10.10.11.0/24" 37 | az = "eu-north-1b" 38 | type = "rds" 39 | 40 | } 41 | "rds2" = { 42 | name = "rds-subnet-2" 43 | cidr = "10.10.12.0/24" 44 | az = "eu-north-1a" 45 | type = "rds" 46 | } 47 | "k8s1" = { 48 | name = "rds-subnet-1" 49 | cidr = "10.10.13.0/24" 50 | az = "eu-north-1a" 51 | type = "k8s" 52 | } 53 | 54 | "k8s2" = { 55 | name = "rds-subnet-2" 56 | cidr = "10.10.14.0/24" 57 | az = "eu-north-1a" 58 | type = "k8s" 59 | } 60 | "app1" = { 61 | name = "app-subnet-1" 62 | cidr = "10.10.16.0/24" 63 | az = "eu-north-1a" 64 | type = "app" 65 | } 66 | "app2" = { 67 | name = "app-subnet-2" 68 | cidr = "10.10.15.0/24" 69 | az = "eu-north-1a" 70 | type = "app" 71 | } 72 | 73 | 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/output_private_subnet_by_type/output.tf: -------------------------------------------------------------------------------- 1 | output "private_subnets_by_type" { 2 | value = module.vpc.private_subnets_by_type 3 | } 4 | 5 | output "k8s_subnets_id" { 6 | value = module.vpc.private_subnets_by_type.k8s.ids 7 | } 8 | 9 | output "rds_subnets_id" { 10 | value = module.vpc.private_subnets_by_type.rds.ids 11 | } 12 | 13 | output "app_subnets_id" { 14 | value = module.vpc.private_subnets_by_type.app.ids 15 | } 16 | -------------------------------------------------------------------------------- /examples/simple/README.md: -------------------------------------------------------------------------------- 1 | # A basic VPC setup with public and private subnets. 2 | -------------------------------------------------------------------------------- /examples/simple/main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "eu-north-1" 3 | } 4 | 5 | 6 | module "vpc" { 7 | source = "ViktorUJ/vpc/aws" 8 | tags_default = { 9 | "Owner" = "DevOps Team" 10 | "Terraform" = "true" 11 | "cost_center" = "1111" 12 | } 13 | vpc = { 14 | name = "test-vpc" 15 | cidr = "10.10.0.0/16" 16 | } 17 | 18 | subnets = { 19 | public = { 20 | "pub1" = { 21 | name = "public-subnet-1" 22 | cidr = "10.10.1.0/24" 23 | az = "eu-north-1a" 24 | } 25 | "pub2" = { 26 | name = "public-subnet-2" 27 | cidr = "10.10.2.0/24" 28 | az = "eu-north-1a" 29 | } 30 | 31 | } 32 | private = { 33 | 34 | "private1" = { 35 | name = "private-subnet-1" 36 | cidr = "10.10.11.0/24" 37 | az = "eu-north-1a" 38 | 39 | } 40 | "private2" = { 41 | name = "private-subnet-2" 42 | cidr = "10.10.12.0/24" 43 | az = "eu-north-1a" 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/simple/output.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | value = module.vpc.vpc_raw.id 3 | } 4 | 5 | output "vpc_cidr_block" { 6 | value = module.vpc.vpc_raw.cidr_block 7 | } 8 | 9 | 10 | output "private_subnets_by_type" { 11 | value = module.vpc.private_subnets_by_type 12 | } 13 | output "public_subnets_by_type" { 14 | value = module.vpc.public_subnets_by_type 15 | } 16 | -------------------------------------------------------------------------------- /img/logo_192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ViktorUJ/terraform-aws-vpc/cacfa28e67c743b45a5699989a6e79192416848b/img/logo_192x192.png -------------------------------------------------------------------------------- /locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | az_mapping = { 3 | for idx, az in data.aws_availability_zones.available.names : az => 4 | data.aws_availability_zones.available.zone_ids[idx] 5 | } 6 | 7 | az_id_to_az = { for az, az_id in local.az_mapping : az_id => az } 8 | 9 | normalized_public_subnets_all = { 10 | for k, v in var.subnets.public : k => merge(v, { 11 | az = lookup(local.az_id_to_az, v.az, v.az) # modify AZ ID to AZ 12 | }) 13 | } 14 | 15 | normalized_private_subnets_all = { 16 | for k, v in var.subnets.private : k => merge(v, { 17 | az = lookup(local.az_id_to_az, v.az, v.az) # modify AZ ID to AZ 18 | }) 19 | } 20 | 21 | # group by type and create a list of identifiers 22 | private_subnets_by_type = { 23 | for type in distinct([for k, v in local.normalized_private_subnets_all : v.type]) : type => { 24 | ids = [for k, v in local.normalized_private_subnets_all : aws_subnet.private[k].id if v.type == type] 25 | keys = [for k, v in local.normalized_private_subnets_all : k if v.type == type] 26 | } 27 | } 28 | 29 | public_subnets_by_type = { 30 | for type in distinct([for k, v in var.subnets.public : v.type]) : type => { 31 | ids = [for k, v in local.normalized_public_subnets_all : aws_subnet.public[k].id if v.type == type] 32 | keys = [for k, v in local.normalized_public_subnets_all : k if v.type == type] 33 | } 34 | } 35 | 36 | private_subnets_by_az_output = { 37 | for az in distinct([for subnet in local.normalized_private_subnets_all : subnet.az]) : az => [ 38 | for subnet_key, subnet in local.normalized_private_subnets_all : aws_subnet.private[subnet_key].id 39 | if subnet.az == az 40 | ] 41 | } 42 | 43 | public_subnets_by_az_output = { 44 | for az in distinct([for subnet in local.normalized_public_subnets_all : subnet.az]) : az => [ 45 | for subnet_key, subnet in local.normalized_public_subnets_all : aws_subnet.public[subnet_key].id 46 | if subnet.az == az 47 | ] 48 | } 49 | 50 | 51 | private_subnets_by_az_id = { 52 | for az_id in distinct([for subnet in local.normalized_private_subnets_all : lookup(local.az_mapping, subnet.az)]) : az_id => [ 53 | for subnet_key, subnet in local.normalized_private_subnets_all : aws_subnet.private[subnet_key].id 54 | if lookup(local.az_mapping, subnet.az) == az_id 55 | ] 56 | } 57 | 58 | public_subnets_by_az_id = { 59 | for az_id in distinct([for subnet in local.normalized_public_subnets_all : lookup(local.az_mapping, subnet.az)]) : az_id => [ 60 | for subnet_key, subnet in local.normalized_public_subnets_all : aws_subnet.public[subnet_key].id 61 | if lookup(local.az_mapping, subnet.az) == az_id 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ViktorUJ/terraform-aws-vpc/cacfa28e67c743b45a5699989a6e79192416848b/main.tf -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | # inputs 2 | output "tags_default" { 3 | description = "Default tags" 4 | value = var.tags_default 5 | } 6 | 7 | output "subnets_var" { 8 | description = "for test" 9 | value = var.subnets 10 | } 11 | output "vpc_var" { 12 | value = var.vpc 13 | } 14 | 15 | # vpc 16 | output "vpc_raw" { 17 | value = try(aws_vpc.default, null) 18 | } 19 | 20 | 21 | # debug 22 | output "az_mapping" { 23 | value = local.az_mapping 24 | } 25 | 26 | 27 | #subnets public 28 | 29 | output "normalized_public_subnets_all" { 30 | value = local.normalized_public_subnets_all 31 | } 32 | 33 | output "subnets_public_raw" { 34 | value = try(aws_subnet.public, null) 35 | } 36 | output "public_subnets_by_type" { 37 | value = local.public_subnets_by_type 38 | } 39 | 40 | output "public_subnets_by_az" { 41 | value = local.public_subnets_by_az_output 42 | } 43 | 44 | output "public_subnets_by_az_id" { 45 | value = local.public_subnets_by_az_id 46 | } 47 | #subnets private 48 | output "normalized_private_subnets_all" { 49 | value = local.normalized_private_subnets_all 50 | } 51 | 52 | output "subnets_private_raw" { 53 | value = try(aws_subnet.private, null) 54 | } 55 | 56 | output "private_subnets_by_type" { 57 | value = local.private_subnets_by_type 58 | } 59 | 60 | output "private_subnets_by_az" { 61 | value = local.private_subnets_by_az_output 62 | } 63 | 64 | output "private_subnets_by_az_id" { 65 | value = local.private_subnets_by_az_id 66 | } 67 | 68 | # NACL 69 | output "nacl_default_rules_raw" { 70 | value = aws_network_acl_rule.default 71 | } 72 | output "public_nacl_raw" { 73 | value = try(aws_network_acl.public, null) 74 | 75 | } 76 | output "public_nacl_rules_raw" { 77 | value = aws_network_acl_rule.public_rules 78 | } 79 | 80 | 81 | output "private_nacl_raw" { 82 | value = try(aws_network_acl.private, null) 83 | } 84 | output "private_nacl_rules_raw" { 85 | value = aws_network_acl_rule.private_rules 86 | } 87 | 88 | # NAT Gateway 89 | 90 | output "nat_gateway_single_raw" { 91 | value = try(aws_nat_gateway.SINGLE_nat_gateway, null) 92 | } 93 | 94 | output "nat_gateway_subnet_raw" { 95 | value = try(aws_nat_gateway.SUBNET_nat_gateway, null) 96 | } 97 | 98 | output "nat_gateway_az_raw" { 99 | value = try(aws_nat_gateway.az_nat_gateway, null) 100 | } 101 | 102 | # Route Table 103 | output "route_table_private_raw" { 104 | value = try(aws_route_table.private, null) 105 | } 106 | 107 | output "route_table_public_raw" { 108 | value = try(aws_route_table.public, null) 109 | } 110 | -------------------------------------------------------------------------------- /subnets_private.tf: -------------------------------------------------------------------------------- 1 | resource "aws_subnet" "private" { 2 | vpc_id = aws_vpc.default.id 3 | for_each = local.normalized_private_subnets_all 4 | map_public_ip_on_launch = "false" 5 | cidr_block = each.value.cidr 6 | 7 | assign_ipv6_address_on_creation = each.value.assign_ipv6_address_on_creation 8 | customer_owned_ipv4_pool = each.value.customer_owned_ipv4_pool != "" ? each.value.customer_owned_ipv4_pool : null 9 | enable_dns64 = each.value.enable_dns64 10 | enable_resource_name_dns_aaaa_record_on_launch = each.value.enable_resource_name_dns_aaaa_record_on_launch 11 | enable_resource_name_dns_a_record_on_launch = each.value.enable_resource_name_dns_a_record_on_launch 12 | ipv6_cidr_block = each.value.ipv6_cidr_block != "" ? each.value.ipv6_cidr_block : null 13 | ipv6_native = each.value.ipv6_native 14 | map_customer_owned_ip_on_launch = each.value.map_customer_owned_ip_on_launch ? each.value.map_customer_owned_ip_on_launch : null 15 | outpost_arn = each.value.outpost_arn != "" ? each.value.outpost_arn : null 16 | private_dns_hostname_type_on_launch = each.value.private_dns_hostname_type_on_launch != "" ? each.value.private_dns_hostname_type_on_launch : null 17 | 18 | 19 | 20 | availability_zone = each.value.az 21 | 22 | tags = merge(var.tags_default, { "Name" = each.value.name }, { "type" = each.value.type }, { "subnet_key" = each.key }, { "access_type" = "private" }, each.value.tags) 23 | } 24 | 25 | 26 | 27 | 28 | resource "aws_route_table" "private" { 29 | for_each = local.normalized_private_subnets_all 30 | vpc_id = aws_vpc.default.id 31 | tags = merge(var.tags_default, { "Name" = each.value.name }, { "type" = each.value.type }, { "subnet_key" = each.key }, { "access_type" = "private" }, each.value.tags) 32 | } 33 | 34 | resource "aws_route_table_association" "private" { 35 | for_each = local.normalized_private_subnets_all 36 | route_table_id = aws_route_table.private["${each.key}"].id 37 | subnet_id = aws_subnet.private["${each.key}"].id 38 | } 39 | 40 | # < Az NAT Gateway 41 | locals { 42 | normalized_private_subnets_AZ = { 43 | for k, v in var.subnets.private : k => merge(v, { 44 | az = lookup(local.az_id_to_az, v.az, v.az) # Преобразуем AZ ID в AZ, если это необходимо 45 | }) 46 | if v.nat_gateway == "AZ" # Фильтруем только те подсети, где nat_gateway = "AZ" 47 | } 48 | 49 | 50 | 51 | private_subnets_by_az = { 52 | for az in distinct([for s in local.normalized_private_subnets_AZ : s.az]) : 53 | az => { 54 | ids = [for k, s in local.normalized_private_subnets_AZ : aws_subnet.private[k].id if s.az == az] 55 | keys = [for k, s in local.normalized_private_subnets_AZ : k if s.az == az] 56 | } 57 | } 58 | } 59 | 60 | resource "aws_nat_gateway" "az_nat_gateway" { 61 | for_each = local.private_subnets_by_az 62 | 63 | allocation_id = length(keys(var.existing_eip_ids_az)) == 0 ? aws_eip.az_nat_gateway_eip[each.key].id : var.existing_eip_ids_az[each.key] 64 | subnet_id = local.public_subnets_by_az_output[each.key][0] 65 | tags = merge(var.tags_default, { "Name" = "az_nat_gateway-${each.key}" }) 66 | } 67 | 68 | resource "aws_eip" "az_nat_gateway_eip" { 69 | for_each = length(keys(var.existing_eip_ids_az)) == 0 ? { 70 | for az, data in local.private_subnets_by_az : az => az 71 | } : {} 72 | domain = "vpc" 73 | tags = merge(var.tags_default, { "Name" = "az_nat_gateway-${each.key}" }) 74 | } 75 | 76 | locals { 77 | flat_private_subnet_keys = flatten([ 78 | for az, data in local.private_subnets_by_az : [ 79 | for key in data.keys : { 80 | key = key 81 | az = az 82 | id = "${az}-${key}" 83 | } 84 | ] 85 | ]) 86 | 87 | routes_map_private_subnet_az = { 88 | for entry in local.flat_private_subnet_keys : entry.id => { 89 | key = entry.key 90 | az = entry.az 91 | } 92 | } 93 | } 94 | 95 | resource "aws_route" "private_route_az" { 96 | 97 | for_each = local.routes_map_private_subnet_az 98 | 99 | route_table_id = aws_route_table.private[each.value.key].id 100 | destination_cidr_block = "0.0.0.0/0" 101 | nat_gateway_id = aws_nat_gateway.az_nat_gateway[each.value.az].id 102 | } 103 | 104 | 105 | 106 | 107 | # Az NAT Gateway > 108 | 109 | 110 | 111 | # < SUBNET NAT Gateway 112 | 113 | locals { 114 | normalized_private_subnets_SUBNET = { 115 | for k, v in var.subnets.private : k => merge(v, { 116 | az = lookup(local.az_id_to_az, v.az, v.az) # Преобразуем AZ ID в AZ, если это необходимо 117 | }) 118 | if v.nat_gateway == "SUBNET" # Фильтруем только те подсети, где nat_gateway = "SUBNET" 119 | } 120 | 121 | 122 | } 123 | 124 | 125 | resource "aws_eip" "SUBNET_nat_gateway_eip" { 126 | for_each = local.normalized_private_subnets_SUBNET 127 | tags = merge(var.tags_default, { "Name" = "SUBNET_nat_gateway-${each.key}" }) 128 | domain = "vpc" 129 | } 130 | 131 | resource "aws_nat_gateway" "SUBNET_nat_gateway" { 132 | for_each = local.normalized_private_subnets_SUBNET 133 | 134 | allocation_id = aws_eip.SUBNET_nat_gateway_eip[each.key].id 135 | subnet_id = local.public_subnets_by_az_output[each.value.az][0] 136 | tags = merge(var.tags_default, { "Name" = "SUBNET_nat_gateway-${each.key}" }) 137 | } 138 | 139 | 140 | resource "aws_route" "private_route_SUBNET" { 141 | 142 | for_each = local.normalized_private_subnets_SUBNET 143 | route_table_id = aws_route_table.private[each.key].id 144 | destination_cidr_block = "0.0.0.0/0" 145 | nat_gateway_id = aws_nat_gateway.SUBNET_nat_gateway[each.key].id 146 | } 147 | 148 | 149 | # SUBNET NAT Gateway > 150 | 151 | 152 | # < SINGLE NAT Gateway 153 | 154 | locals { 155 | normalized_private_subnets_SINGLE = { 156 | for k, v in var.subnets.private : k => merge(v, { 157 | az = lookup(local.az_id_to_az, v.az, v.az) # Преобразуем AZ ID в AZ, если это необходимо 158 | }) 159 | if v.nat_gateway == "SINGLE" # Фильтруем только те подсети, где nat_gateway = "SUBNET" 160 | } 161 | 162 | normalized_public_subnets_DEFAULT = { 163 | for k, v in var.subnets.public : k => merge(v, { 164 | az = lookup(local.az_id_to_az, v.az, v.az) # Преобразуем AZ ID в AZ, если это необходимо 165 | }) 166 | if v.nat_gateway == "DEFAULT" # Фильтруем только те подсети, где nat_gateway = "DEFAULT" 167 | } 168 | normalized_public_subnets_DEFAULT_keys = keys(local.normalized_public_subnets_DEFAULT) 169 | normalized_public_subnets_DEFAULT_first_subnet_key = length(local.normalized_public_subnets_DEFAULT_keys) > 0 ? local.normalized_public_subnets_DEFAULT_keys[0] : null 170 | 171 | normalized_public_subnets_DEFAULT_selected = { 172 | for k, v in local.normalized_public_subnets_DEFAULT : 173 | k => v if k == local.normalized_public_subnets_DEFAULT_first_subnet_key 174 | } 175 | } 176 | 177 | 178 | resource "aws_eip" "SINGLE_nat_gateway_eip" { 179 | for_each = local.normalized_public_subnets_DEFAULT_selected 180 | tags = merge(var.tags_default, { "Name" = "SINGLE_nat_gateway-${each.key}" }) 181 | domain = "vpc" 182 | } 183 | 184 | 185 | 186 | resource "aws_nat_gateway" "SINGLE_nat_gateway" { 187 | for_each = local.normalized_public_subnets_DEFAULT_selected 188 | 189 | allocation_id = aws_eip.SINGLE_nat_gateway_eip[each.key].id 190 | subnet_id = aws_subnet.public[each.key].id 191 | tags = merge(var.tags_default, { "Name" = "SINGLE_nat_gateway-${each.key}" }) 192 | } 193 | 194 | 195 | resource "aws_route" "private_route_SINGLE" { 196 | 197 | for_each = local.normalized_private_subnets_SINGLE 198 | route_table_id = aws_route_table.private[each.key].id 199 | destination_cidr_block = "0.0.0.0/0" 200 | nat_gateway_id = aws_nat_gateway.SINGLE_nat_gateway["${local.normalized_public_subnets_DEFAULT_first_subnet_key}"].id 201 | } 202 | 203 | # SINGLE NAT Gateway > 204 | 205 | 206 | # NACL 207 | 208 | # Local variable to flatten all NACL rules for private subnets 209 | locals { 210 | private_nacl_rules = flatten([ 211 | for subnet_key, subnet in var.subnets.private : [ 212 | for rule_key, rule in subnet.nacl : { 213 | subnet_key = subnet_key 214 | rule_key = rule_key 215 | rule = rule 216 | } 217 | ] 218 | ]) 219 | } 220 | 221 | # Create a Network ACL for each private subnet if nacl is defined 222 | resource "aws_network_acl" "private" { 223 | for_each = { 224 | for subnet_key, subnet in var.subnets.private : 225 | subnet_key => subnet 226 | if length(subnet.nacl) > 0 227 | } 228 | 229 | vpc_id = aws_vpc.default.id 230 | 231 | tags = merge(var.tags_default, { 232 | "Name" = "${each.value.name}-nacl" 233 | }) 234 | } 235 | 236 | # Create Network ACL rules for each private subnet's NACL 237 | resource "aws_network_acl_rule" "private_rules" { 238 | for_each = { 239 | for rule in local.private_nacl_rules : 240 | "${rule.subnet_key}-${rule.rule_key}-${rule.rule.rule_number}" => rule 241 | if length(var.subnets.private[rule.subnet_key].nacl) > 0 242 | } 243 | 244 | network_acl_id = aws_network_acl.private[each.value.subnet_key].id 245 | rule_number = each.value.rule.rule_number 246 | egress = each.value.rule.egress == "true" ? true : false 247 | protocol = each.value.rule.protocol 248 | rule_action = each.value.rule.rule_action 249 | cidr_block = each.value.rule.cidr_block != "" ? each.value.rule.cidr_block : null 250 | from_port = each.value.rule.from_port != "" ? tonumber(each.value.rule.from_port) : null 251 | to_port = each.value.rule.to_port != "" ? tonumber(each.value.rule.to_port) : null 252 | icmp_code = each.value.rule.icmp_code != "" ? tonumber(each.value.rule.icmp_code) : null 253 | icmp_type = each.value.rule.icmp_type != "" ? tonumber(each.value.rule.icmp_type) : null 254 | ipv6_cidr_block = each.value.rule.ipv6_cidr_block != "" ? each.value.rule.ipv6_cidr_block : null 255 | } 256 | 257 | # Associate the NACL with each private subnet if NACL is defined 258 | resource "aws_network_acl_association" "private_association" { 259 | for_each = { 260 | for subnet_key, subnet in var.subnets.private : 261 | subnet_key => subnet 262 | if length(subnet.nacl) > 0 263 | } 264 | 265 | subnet_id = aws_subnet.private[each.key].id 266 | network_acl_id = aws_network_acl.private[each.key].id 267 | } 268 | -------------------------------------------------------------------------------- /subnets_pub.tf: -------------------------------------------------------------------------------- 1 | resource "aws_subnet" "public" { 2 | vpc_id = aws_vpc.default.id 3 | for_each = local.normalized_public_subnets_all 4 | map_public_ip_on_launch = each.value.map_public_ip_on_launch 5 | cidr_block = each.value.cidr 6 | 7 | assign_ipv6_address_on_creation = each.value.assign_ipv6_address_on_creation 8 | customer_owned_ipv4_pool = each.value.customer_owned_ipv4_pool != "" ? each.value.customer_owned_ipv4_pool : null 9 | enable_dns64 = each.value.enable_dns64 10 | enable_resource_name_dns_aaaa_record_on_launch = each.value.enable_resource_name_dns_aaaa_record_on_launch 11 | enable_resource_name_dns_a_record_on_launch = each.value.enable_resource_name_dns_a_record_on_launch 12 | ipv6_cidr_block = each.value.ipv6_cidr_block != "" ? each.value.ipv6_cidr_block : null 13 | ipv6_native = each.value.ipv6_native 14 | map_customer_owned_ip_on_launch = each.value.map_customer_owned_ip_on_launch ? each.value.map_customer_owned_ip_on_launch : null 15 | outpost_arn = each.value.outpost_arn != "" ? each.value.outpost_arn : null 16 | private_dns_hostname_type_on_launch = each.value.private_dns_hostname_type_on_launch != "" ? each.value.private_dns_hostname_type_on_launch : null 17 | 18 | 19 | availability_zone_id = length(regexall("^[a-z]{2}-", each.value.az)) == 0 ? each.value.az : null 20 | availability_zone = length(regexall("^[a-z]{2}-", each.value.az)) > 0 ? each.value.az : null 21 | 22 | tags = merge(var.tags_default, { "Name" = each.value.name }, { "type" = each.value.type }, { "subnet_key" = each.key }, { "access_type" = "public" }, each.value.tags) 23 | } 24 | 25 | resource "aws_route_table" "public" { 26 | vpc_id = aws_vpc.default.id 27 | tags = merge(var.tags_default, { "access_type" = "public" }) 28 | } 29 | 30 | resource "aws_route" "public" { 31 | route_table_id = aws_route_table.public.id 32 | destination_cidr_block = "0.0.0.0/0" 33 | gateway_id = aws_internet_gateway.default.id 34 | 35 | timeouts { 36 | create = "5m" 37 | } 38 | } 39 | 40 | 41 | resource "aws_route_table_association" "pub" { 42 | for_each = var.subnets.public 43 | route_table_id = aws_route_table.public.id 44 | subnet_id = aws_subnet.public["${each.key}"].id 45 | } 46 | 47 | 48 | 49 | # Local variable to flatten all NACL rules for public subnets 50 | locals { 51 | public_nacl_rules = flatten([ 52 | for subnet_key, subnet in local.normalized_public_subnets_all : [ 53 | for rule_key, rule in subnet.nacl : { 54 | subnet_key = subnet_key 55 | rule_key = rule_key 56 | rule = rule 57 | } 58 | if length(subnet.nacl) > 0 59 | ] 60 | ]) 61 | } 62 | 63 | # Create a Network ACL for each public subnet if nacl is defined and contains rules 64 | resource "aws_network_acl" "public" { 65 | for_each = { 66 | for subnet_key, subnet in local.normalized_public_subnets_all : 67 | subnet_key => subnet 68 | if length(subnet.nacl) > 0 69 | } 70 | 71 | vpc_id = aws_vpc.default.id 72 | 73 | tags = merge(var.tags_default, { 74 | "Name" = "${each.value.name}-nacl" 75 | }) 76 | } 77 | 78 | # Create Network ACL rules for each public subnet's NACL 79 | resource "aws_network_acl_rule" "public_rules" { 80 | for_each = { 81 | for rule in local.public_nacl_rules : 82 | "${rule.subnet_key}-${rule.rule_key}-${rule.rule.rule_number}" => rule 83 | if length(var.subnets.public[rule.subnet_key].nacl) > 0 84 | } 85 | 86 | network_acl_id = aws_network_acl.public[each.value.subnet_key].id 87 | rule_number = each.value.rule.rule_number 88 | egress = each.value.rule.egress == "true" ? true : false 89 | protocol = each.value.rule.protocol 90 | rule_action = each.value.rule.rule_action 91 | cidr_block = each.value.rule.cidr_block != "" ? each.value.rule.cidr_block : null 92 | from_port = each.value.rule.from_port != "" ? tonumber(each.value.rule.from_port) : null 93 | to_port = each.value.rule.to_port != "" ? tonumber(each.value.rule.to_port) : null 94 | icmp_code = each.value.rule.icmp_code != "" ? tonumber(each.value.rule.icmp_code) : null 95 | icmp_type = each.value.rule.icmp_type != "" ? tonumber(each.value.rule.icmp_type) : null 96 | ipv6_cidr_block = each.value.rule.ipv6_cidr_block != "" ? each.value.rule.ipv6_cidr_block : null 97 | } 98 | 99 | # Associate the NACL with each public subnet if NACL is defined and contains rules 100 | resource "aws_network_acl_association" "public_association" { 101 | for_each = { 102 | for subnet_key, subnet in local.normalized_public_subnets_all : 103 | subnet_key => subnet 104 | if length(subnet.nacl) > 0 105 | } 106 | 107 | subnet_id = aws_subnet.public[each.key].id 108 | network_acl_id = aws_network_acl.public[each.key].id 109 | } 110 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "vpc" { 2 | type = object({ 3 | name = string 4 | cidr = string 5 | secondary_cidr_blocks = optional(list(string), []) 6 | tags = optional(map(string), {}) 7 | instance_tenancy = optional(string, "default") # default, dedicated 8 | enable_dns_support = optional(bool, true) # true, false 9 | enable_dns_hostnames = optional(bool, true) # true, false 10 | nacl_default = optional(map(object({ 11 | egress = string # true, false 12 | rule_number = string # ACL entries are processed in ascending order by rule number 13 | rule_action = string # allow | deny 14 | from_port = optional(string, "") 15 | to_port = optional(string, "") 16 | icmp_code = optional(string, "") 17 | # (Optional) ICMP protocol: The ICMP type. Required if specifying ICMP for the protocolE.g., -1 18 | icmp_type = optional(string, "") 19 | # (Optional) ICMP protocol: The ICMP code. Required if specifying ICMP for the protocolE.g., -1 20 | protocol = string # A value of -1 means all protocols , tcp - 6 , 21 | cidr_block = optional(string, "") 22 | # The network range to allow or deny, in CIDR notation (for example 172.16.0.0/24 ). 23 | ipv6_cidr_block = optional(string, "") 24 | 25 | })), {}) 26 | dhcp_options = optional(object({ 27 | domain_name = optional(string, "") 28 | domain_name_servers = optional(list(string), []) 29 | ntp_servers = optional(list(string), []) 30 | netbios_name_servers = optional(list(string), []) 31 | netbios_node_type = optional(string, "") # 1, 2, 4, 8 . default 2 . http://www.ietf.org/rfc/rfc2132.txt 32 | ipv6_address_preferred_lease_time = optional(string, "140") # 140 .. 2147483647 seconds . default 140 33 | tags = optional(map(string), {}) 34 | }), {}) 35 | }) 36 | 37 | # Validation for CIDR format 38 | validation { 39 | condition = can(cidrsubnet(var.vpc.cidr, 0, 0)) 40 | error_message = "Invalid CIDR block format for VPC. CIDR block must be a valid subnet, e.g., 10.0.0.0/16." 41 | } 42 | 43 | # Validation for netbios_node_type field 44 | validation { 45 | condition = var.vpc.dhcp_options.netbios_node_type == "" || contains([ 46 | "1", "2", "4", "8" 47 | ], var.vpc.dhcp_options.netbios_node_type) 48 | error_message = "Invalid value for netbios_node_type. Must be one of: 1, 2, 4, 8." 49 | } 50 | 51 | # Validation for ipv6_address_preferred_lease_time 52 | validation { 53 | condition = ( 54 | var.vpc.dhcp_options.ipv6_address_preferred_lease_time == "" || 55 | ( 56 | length(var.vpc.dhcp_options.ipv6_address_preferred_lease_time) > 0 && 57 | can(tonumber(var.vpc.dhcp_options.ipv6_address_preferred_lease_time)) && 58 | tonumber(var.vpc.dhcp_options.ipv6_address_preferred_lease_time) >= 140 && 59 | tonumber(var.vpc.dhcp_options.ipv6_address_preferred_lease_time) <= 2147483647 60 | ) 61 | ) 62 | error_message = "Invalid value for ipv6_address_preferred_lease_time. Must be a number between 140 and 2147483647 seconds." 63 | } 64 | } 65 | 66 | variable "tags_default" { 67 | type = map(string) 68 | default = {} 69 | } 70 | 71 | 72 | variable "subnets" { 73 | type = object({ 74 | public = optional(map(object({ 75 | name = string 76 | cidr = string 77 | az = string # Availability Zone or Availability Zone ID 78 | tags = optional(map(string), {}) 79 | type = optional(string, "public") # any sort key for grouping . example , DB , WEB , APP , etc 80 | 81 | assign_ipv6_address_on_creation = optional(bool, false) 82 | customer_owned_ipv4_pool = optional(string, "") 83 | enable_dns64 = optional(bool, false) 84 | enable_resource_name_dns_aaaa_record_on_launch = optional(bool, false) 85 | enable_resource_name_dns_a_record_on_launch = optional(bool, true) 86 | ipv6_cidr_block = optional(string, "") 87 | ipv6_native = optional(bool, false) 88 | map_customer_owned_ip_on_launch = optional(bool, false) 89 | map_public_ip_on_launch = optional(bool, true) 90 | outpost_arn = optional(string, "") 91 | private_dns_hostname_type_on_launch = optional(string, "ip-name") # The type of hostnames to assign to instances in the subnet at launch. For IPv6-only subnets, an instance DNS name must be based on the instance ID. For dual-stack and IPv4-only subnets, you can specify whether DNS names use the instance IPv4 address or the instance ID . Valid values: ip-name, resource-name. 92 | nat_gateway = optional(string, "") # DEFAULT - default nat gateway for all AZ with SINGLE value 93 | nacl = optional(map(object({ 94 | egress = string # true, false 95 | rule_number = string # ACL entries are processed in ascending order by rule number 96 | rule_action = string # allow | deny 97 | from_port = optional(string, "") 98 | to_port = optional(string, "") 99 | icmp_code = optional(string, "") # (Optional) ICMP protocol: The ICMP type. Required if specifying ICMP for the protocolE.g., -1 100 | icmp_type = optional(string, "") # (Optional) ICMP protocol: The ICMP code. Required if specifying ICMP for the protocolE.g., -1 101 | protocol = string # A value of -1 means all protocols , tcp - 6 , 102 | cidr_block = optional(string, "") # The network range to allow or deny, in CIDR notation (for example 172.16.0.0/24 ). 103 | ipv6_cidr_block = optional(string, "") 104 | 105 | })), {}) 106 | 107 | }))) 108 | private = optional(map(object({ 109 | name = string 110 | cidr = string 111 | az = string # Availability Zone or Availability Zone ID 112 | tags = optional(map(string), {}) 113 | type = optional(string, "private") # any sort key for grouping . example , DB , WEB , APP , etc 114 | nat_gateway = optional(string, "AZ") # AZ - nat gateway for each AZ , SINGLE - single nat gateway for all AZ (for this option you need to set nat_gateway=DEFAULT in one of the public networks) ,SUBNET - dedicate nat gateway for each subnet with SUBNET type , NONE - no nat gateway 115 | assign_ipv6_address_on_creation = optional(bool, false) 116 | customer_owned_ipv4_pool = optional(string, "") 117 | enable_dns64 = optional(bool, false) 118 | enable_resource_name_dns_aaaa_record_on_launch = optional(bool, false) 119 | enable_resource_name_dns_a_record_on_launch = optional(bool, false) 120 | ipv6_cidr_block = optional(string, "") 121 | ipv6_native = optional(bool, false) 122 | map_customer_owned_ip_on_launch = optional(bool, false) 123 | map_public_ip_on_launch = optional(bool, true) 124 | outpost_arn = optional(string, "") 125 | private_dns_hostname_type_on_launch = optional(string, "ip-name") # The type of hostnames to assign to instances in the subnet at launch. For IPv6-only subnets, an instance DNS name must be based on the instance ID. For dual-stack and IPv4-only subnets, you can specify whether DNS names use the instance IPv4 address or the instance ID . Valid values: ip-name, resource-name. 126 | nacl = optional(map(object({ 127 | egress = string # true, false 128 | rule_number = string # ACL entries are processed in ascending order by rule number 129 | rule_action = string # allow | deny 130 | from_port = optional(string, "") 131 | to_port = optional(string, "") 132 | icmp_code = optional(string, "") # (Optional) ICMP protocol: The ICMP type. Required if specifying ICMP for the protocolE.g., -1 133 | icmp_type = optional(string, "") # (Optional) ICMP protocol: The ICMP code. Required if specifying ICMP for the protocolE.g., -1 134 | protocol = string # A value of -1 means all protocols , tcp - 6 , 135 | cidr_block = optional(string, "") # The network range to allow or deny, in CIDR notation (for example 172.16.0.0/24 ). 136 | ipv6_cidr_block = optional(string, "") 137 | 138 | })), {}) 139 | 140 | }))) 141 | }) 142 | default = { 143 | public = {} 144 | private = {} 145 | } 146 | 147 | validation { 148 | condition = alltrue([ 149 | for _, subnet in coalesce(var.subnets.private, {}) : contains(["AZ", "SINGLE", "DEFAULT", "SUBNET", "NONE"], subnet.nat_gateway) 150 | ]) 151 | error_message = "nat_gateway must be one of: AZ, SINGLE, DEFAULT, SUBNET, NONE." 152 | } 153 | 154 | validation { 155 | condition = alltrue([ 156 | for _, subnet in coalesce(var.subnets.private, {}) : can(cidrsubnet(subnet.cidr, 0, 0)) 157 | ]) 158 | error_message = "Invalid CIDR block format. CIDR block must be a valid subnet, e.g., 10.10.16.0/24." 159 | } 160 | 161 | validation { 162 | condition = alltrue([ 163 | for _, subnet in coalesce(var.subnets.private, {}) : contains(["ip-name", "resource-name"], subnet.private_dns_hostname_type_on_launch) 164 | ]) 165 | error_message = "Invalid value for private_dns_hostname_type_on_launch. Must be one of: ip-name, resource-name." 166 | } 167 | 168 | validation { 169 | condition = alltrue([ 170 | for _, subnet in coalesce(var.subnets.public, {}) : can(cidrsubnet(subnet.cidr, 0, 0)) 171 | ]) 172 | error_message = "Invalid CIDR block format. CIDR block must be a valid subnet, e.g., 10.10.16.0/24." 173 | } 174 | 175 | validation { 176 | condition = alltrue([ 177 | for _, subnet in coalesce(var.subnets.public, {}) : contains(["ip-name", "resource-name"], subnet.private_dns_hostname_type_on_launch) 178 | ]) 179 | error_message = "Invalid value for private_dns_hostname_type_on_launch. Must be one of: ip-name, resource-name." 180 | } 181 | } 182 | 183 | variable "existing_eip_ids_az" { 184 | description = "A map of existing Elastic IPs IDs to associate with the NAT Gateways where key is the AZ, value is the EIP ID" 185 | type = map(string) 186 | default = {} 187 | 188 | validation { 189 | condition = alltrue([ 190 | for az, eip in var.existing_eip_ids_az : can(regex("^eipalloc-[0-9a-fA-F]{17}$", eip)) 191 | ]) 192 | error_message = "Each value in existing_eip_ids_az must be a valid Elastic IP ID (eipalloc-xxxxxxxxxxxxxxxxx)." 193 | } 194 | 195 | validation { 196 | condition = alltrue([ 197 | for az, eip in var.existing_eip_ids_az : can(regex("^[a-z]{2}-[a-z]+-[0-9]{1}[a-z]$", az)) 198 | ]) 199 | error_message = "Each key in existing_eip_ids_az must be a valid Availability Zone (e.g., eu-west-1a)." 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.0" 3 | 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.46" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /vpc.tf: -------------------------------------------------------------------------------- 1 | resource "aws_vpc" "default" { 2 | cidr_block = var.vpc.cidr 3 | enable_dns_support = var.vpc.enable_dns_support 4 | enable_dns_hostnames = var.vpc.enable_dns_hostnames 5 | tags = merge(var.tags_default, { "Name" = var.vpc.name }, var.vpc.tags) 6 | } 7 | 8 | resource "aws_internet_gateway" "default" { 9 | vpc_id = aws_vpc.default.id 10 | tags = merge(var.tags_default, { "Name" = var.vpc.name }, var.vpc.tags) 11 | } 12 | 13 | resource "aws_vpc_ipv4_cidr_block_association" "default" { 14 | for_each = toset(var.vpc.secondary_cidr_blocks) 15 | vpc_id = aws_vpc.default.id 16 | cidr_block = each.value 17 | } 18 | 19 | resource "aws_network_acl_rule" "default" { 20 | for_each = var.vpc.nacl_default 21 | network_acl_id = aws_vpc.default.default_network_acl_id 22 | rule_number = each.value.rule_number 23 | egress = each.value.egress 24 | protocol = each.value.protocol 25 | rule_action = each.value.rule_action 26 | cidr_block = each.value.cidr_block != "" ? each.value.cidr_block : null 27 | from_port = each.value.from_port != "" ? each.value.from_port : null 28 | to_port = each.value.to_port != "" ? each.value.to_port : null 29 | ipv6_cidr_block = each.value.ipv6_cidr_block != "" ? each.value.ipv6_cidr_block : null 30 | } 31 | 32 | 33 | locals { 34 | # Check if any DHCP option is defined (for strings and lists) 35 | dhcp_options_defined = ( 36 | var.vpc.dhcp_options.domain_name != "" || 37 | length(var.vpc.dhcp_options.domain_name_servers) > 0 || 38 | length(var.vpc.dhcp_options.ntp_servers) > 0 || 39 | length(var.vpc.dhcp_options.netbios_name_servers) > 0 || 40 | var.vpc.dhcp_options.netbios_node_type != "" || 41 | var.vpc.dhcp_options.ipv6_address_preferred_lease_time != "140" 42 | ) 43 | } 44 | 45 | resource "aws_vpc_dhcp_options" "default" { 46 | for_each = local.dhcp_options_defined ? toset(["enable"]) : toset([]) 47 | domain_name = var.vpc.dhcp_options.domain_name != "" ? var.vpc.dhcp_options.domain_name : null 48 | domain_name_servers = length(var.vpc.dhcp_options.domain_name_servers) > 0 ? var.vpc.dhcp_options.domain_name_servers : null 49 | ntp_servers = length(var.vpc.dhcp_options.ntp_servers) > 0 ? var.vpc.dhcp_options.ntp_servers : null 50 | netbios_name_servers = length(var.vpc.dhcp_options.netbios_name_servers) > 0 ? var.vpc.dhcp_options.netbios_name_servers : null 51 | netbios_node_type = var.vpc.dhcp_options.netbios_node_type != "" ? var.vpc.dhcp_options.netbios_node_type : null 52 | ipv6_address_preferred_lease_time = var.vpc.dhcp_options.ipv6_address_preferred_lease_time != "140" ? var.vpc.dhcp_options.ipv6_address_preferred_lease_time : null 53 | tags = merge(var.tags_default, { "Name" = var.vpc.name }, var.vpc.tags) 54 | } 55 | 56 | resource "aws_vpc_dhcp_options_association" "default" { 57 | for_each = local.dhcp_options_defined ? toset(["enable"]) : toset([]) 58 | vpc_id = aws_vpc.default.id 59 | dhcp_options_id = aws_vpc_dhcp_options.default[each.key].id 60 | } 61 | --------------------------------------------------------------------------------