diff --git a/.header.md b/.header.md index 4a6dca6..d2567fc 100644 --- a/.header.md +++ b/.header.md @@ -1,5 +1,9 @@ # Terraform Module for Amazon VPC IP Address Manager on AWS +Note: For information regarding the 2.0 upgrade see our [upgrade guide](https://github.com/aws-ia/terraform-aws-ipam/blob/main/docs/UPGRADE-GUIDE-2.0.md). + +This module helps deploy AWS IPAM including IPAM Pools, Provisioned CIDRs, and can help with sharing those pools via AWS RAM. + Built to accommodate a wide range of use cases, this Terraform module can deploy both simple and complex Amazon Virtual Private Cloud (Amazon VPC) IP Address Manager (IPAM) configurations. It supports both symmetrically nested, multi-Region deployments (most common IPAM designs) as well as [asymmetically nested deployments](https://github.com/aws-ia/terraform-aws-ipam/blob/main/images/asymmetrical_example.png). Refer to the [examples/](https://github.com/aws-ia/terraform-aws-ipam/blob/main/examples) directory in this GitHub repository for examples. @@ -60,7 +64,6 @@ variable "pool_config" { allocation_resource_tags = optional(map(string)) tags = optional(map(string)) - cidr_authorization_context = optional(map(string)) sub_pools = optional(any) }) diff --git a/.terraform-docs.yaml b/.terraform-docs.yaml index 1e310cc..e2fb9cd 100644 --- a/.terraform-docs.yaml +++ b/.terraform-docs.yaml @@ -7,6 +7,7 @@ settings: escape: true html: true indent: 2 + lockfile: false required: true sensitive: true type: true diff --git a/README.md b/README.md index 7b9aaac..619e23f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # Terraform Module for Amazon VPC IP Address Manager on AWS +Note: For information regarding the 2.0 upgrade see our [upgrade guide](https://github.com/aws-ia/terraform-aws-ipam/blob/main/docs/UPGRADE-GUIDE-2.0.md). + +This module helps deploy AWS IPAM including IPAM Pools, Provisioned CIDRs, and can help with sharing those pools via AWS RAM. + Built to accommodate a wide range of use cases, this Terraform module can deploy both simple and complex Amazon Virtual Private Cloud (Amazon VPC) IP Address Manager (IPAM) configurations. It supports both symmetrically nested, multi-Region deployments (most common IPAM designs) as well as [asymmetically nested deployments](https://github.com/aws-ia/terraform-aws-ipam/blob/main/images/asymmetrical_example.png). Refer to the [examples/](https://github.com/aws-ia/terraform-aws-ipam/blob/main/examples) directory in this GitHub repository for examples. @@ -61,7 +65,6 @@ variable "pool_config" { allocation_resource_tags = optional(map(string)) tags = optional(map(string)) - cidr_authorization_context = optional(map(string)) sub_pools = optional(any) }) @@ -151,7 +154,7 @@ The IPAM `operating_region` variable must be set for the primary Region in your | [pool\_configurations](#input\_pool\_configurations) | A multi-level, nested map describing nested IPAM pools. Can nest up to three levels with the top level being outside the `pool_configurations` in vars prefixed `top_`. If arugument descriptions are omitted, you can find them in the [official documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipam_pool#argument-reference).

- `ram_share_principals` = (optional, list(string)) of valid organization principals to create ram shares to.
- `name` = (optional, string) name to give the pool, the key of your map in var.pool\_configurations will be used if omitted.
- `description` = (optional, string) description to give the pool, the key of your map in var.pool\_configurations will be used if omitted.
- `cidr` = (optional, list(string)) list of CIDRs to provision into pool. Conflicts with `netmask_length`.
- `netmask_length` = (optional, number) netmask length to request provisioned into pool. Conflicts with `cidr`.

- `locale` = (optional, string) locale to set for pool.
- `auto_import` = (optional, string)
- `tags` = (optional, map(string))
- `allocation_default_netmask_length` = (optional, string)
- `allocation_max_netmask_length` = (optional, string)
- `allocation_min_netmask_length` = (optional, string)
- `allocation_resource_tags` = (optional, map(string))

The following arguments are available but only relevant for public ips
- `cidr_authorization_context` = (optional, map(string)) Details found in [official documentation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipam_pool_cidr#cidr_authorization_context).
- `aws_service` = (optional, string)
- `publicly_advertisable` = (optional, bool)

- `sub_pools` = (nested repeats of pool\_configuration object above) | `any` | `{}` | no | | [tags](#input\_tags) | Tags to add to the aws\_vpc\_ipam resource. | `any` | `{}` | no | | [top\_auto\_import](#input\_top\_auto\_import) | `auto_import` setting for top-level pool. | `bool` | `null` | no | -| [top\_cidr\_authorization\_context](#input\_top\_cidr\_authorization\_context) | A signed document that proves that you are authorized to bring the specified IP address range to Amazon using BYOIP. Document is not stored in the state file. For more information, refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipam_pool_cidr#cidr_authorization_context. | `any` | `null` | no | +| [top\_cidr\_authorization\_contexts](#input\_top\_cidr\_authorization\_contexts) | CIDR must match a CIDR defined in `var.top_cidr`. A list of signed documents that proves that you are authorized to bring the specified IP address range to Amazon using BYOIP. Document is not stored in the state file. For more information, refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipam_pool_cidr#cidr_authorization_context. |
list(object({
cidr = string
message = string
signature = string
}))
| `[]` | no | | [top\_description](#input\_top\_description) | Description of top-level pool. | `string` | `""` | no | | [top\_name](#input\_top\_name) | Name of top-level pool. | `string` | `null` | no | | [top\_netmask\_length](#input\_top\_netmask\_length) | Top-level netmask length to request. Not possible to use for IPv4. Only possible to use with amazon provided ipv6. | `number` | `null` | no | diff --git a/docs/UPGRADE-GUIDE-2.0.md b/docs/UPGRADE-GUIDE-2.0.md new file mode 100644 index 0000000..361ba36 --- /dev/null +++ b/docs/UPGRADE-GUIDE-2.0.md @@ -0,0 +1,34 @@ +# Upgrade from v1 to v2 + +**NOTE: If you are not using public IPs there are no changes required to upgrade to v2.** + +In order to support importing multiple public IPs into AWS IPAM, we have updated the variable `top_cidr_authorization_context`. This variable has been renamed to `top_cidr_authorization_contexts` (notice the `s`) which has a strict structure for to inform provision public cidrs into the top level pool. + + +## Upgrade Guide + +### HCL upgrade + +Previously you could only specify the context for [1 public ip](https://github.com/aws-ia/terraform-aws-ipam/blob/991dcf02fd2175bd3a6b10a4ee61b01cf89f813d/examples/single_scope_ipv6/main.tf#L15C1-L18C4). This should now be updated to a list of maps that includes the corresponding cidr. See example below + + +#### Before + +```hcl + top_cidr_authorization_context = { + message = var.cidr_authorization_context_message + signature = var.cidr_authorization_context_signature + } +``` + +#### After + +```hcl + top_cidr_authorization_contexts = [{ + cidr = var.cidr_authorization_context_cidr + message = var.cidr_authorization_context_message + signature = var.cidr_authorization_context_signature + }] +``` + +**IMPORTANT: Each `top_cidr_authorization_contexts[#].cidr` must have a corresponding matching reference in the `top_cidr` list.** diff --git a/examples/multiple_scopes/README.md b/examples/multiple_scopes/README.md new file mode 100644 index 0000000..a492dcd --- /dev/null +++ b/examples/multiple_scopes/README.md @@ -0,0 +1,54 @@ + +## Multiple Scopes + +There are several reasons you may want to populate multiple IPAM scopes: + +- Public & Private scope +- IPv4 + IPv6 +- Overlapping IPv4 ranges + +This example shows you how to build scopes for 2 overlapping IPv4 ranges that you want IPAM to manage. You do this by: + +1. invoke module to build IPAM + ipv4 pool\_configuration +2. create a new private scope on the IPAM built in step 1 +3. invoke module with `create_ipam = false` and pass in the new scope id created + +For IPv4 + IPv6, skip step 2. Reference the `public_default_scope_id` from the ipam in step 1 instead of creating a new scope. + +![Multiple Scopes](../../images/multiple\_ipv4\_scopes.png "Multiple Scopes") + +## Requirements + +| Name | Version | +|------|---------| +| [aws](#requirement\_aws) | = 4.2 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | = 4.2 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [ipv4\_scope](#module\_ipv4\_scope) | ../.. | n/a | +| [overlapping\_cidr\_second\_ipv4\_scope](#module\_overlapping\_cidr\_second\_ipv4\_scope) | ../.. | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_vpc_ipam_scope.scope_for_overlapping_cidr](https://registry.terraform.io/providers/hashicorp/aws/4.2/docs/resources/vpc_ipam_scope) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cidr](#input\_cidr) | n/a | `string` | `"10.0.0.0/8"` | no | + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/examples/single_scope_ipv6/README.md b/examples/single_scope_ipv6/README.md new file mode 100644 index 0000000..3740c6e --- /dev/null +++ b/examples/single_scope_ipv6/README.md @@ -0,0 +1,38 @@ + +## IPv6 Basic Deployment + +The example shows you how to build an IPAM and populate the public scope with IPv6. + +![IPv6 Pool structure](../../images/ipv6\_example.png "Region Separated Pools") + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [ipv6\_basic](#module\_ipv6\_basic) | ../.. | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cidr\_authorization\_context\_cidr](#input\_cidr\_authorization\_context\_cidr) | CIDR Authorization Context CIDR. MUST MATCH a cidr in var.ipv6\_cidr | `any` | n/a | yes | +| [cidr\_authorization\_context\_message](#input\_cidr\_authorization\_context\_message) | CIDR Authorization Context Message. | `any` | n/a | yes | +| [cidr\_authorization\_context\_signature](#input\_cidr\_authorization\_context\_signature) | CIDR Authorization Context Signature. | `any` | n/a | yes | +| [ipv6\_cidr](#input\_ipv6\_cidr) | Top CIDR IPv6. | `any` | n/a | yes | + +## Outputs + +No outputs. + \ No newline at end of file diff --git a/examples/single_scope_ipv6/main.tf b/examples/single_scope_ipv6/main.tf index 9c5a867..561b626 100644 --- a/examples/single_scope_ipv6/main.tf +++ b/examples/single_scope_ipv6/main.tf @@ -12,10 +12,11 @@ module "ipv6_basic" { address_family = "ipv6" ipam_scope_type = "public" - top_cidr_authorization_context = { + top_cidr_authorization_contexts = [{ + cidr = var.cidr_authorization_context_cidr message = var.cidr_authorization_context_message signature = var.cidr_authorization_context_signature - } + }] pool_configurations = { us-east-1 = { diff --git a/examples/single_scope_ipv6/variables.tf b/examples/single_scope_ipv6/variables.tf index ef6126d..2fae0dd 100644 --- a/examples/single_scope_ipv6/variables.tf +++ b/examples/single_scope_ipv6/variables.tf @@ -9,3 +9,7 @@ variable "cidr_authorization_context_message" { variable "cidr_authorization_context_signature" { description = "CIDR Authorization Context Signature." } + +variable "cidr_authorization_context_cidr" { + description = "CIDR Authorization Context CIDR. MUST MATCH a cidr in var.ipv6_cidr" +} diff --git a/main.tf b/main.tf index c788beb..63a9cde 100644 --- a/main.tf +++ b/main.tf @@ -46,14 +46,16 @@ module "level_zero" { ipam_scope_id = local.scope_id source_ipam_pool_id = null + cidr_authorization_contexts = var.top_cidr_authorization_contexts + pool_config = { - cidr = var.top_cidr - ram_share_principals = var.top_ram_share_principals - auto_import = var.top_auto_import - description = var.top_description - cidr_authorization_context = var.top_cidr_authorization_context - name = var.top_name - netmask_length = var.top_netmask_length + cidr = var.top_cidr + ram_share_principals = var.top_ram_share_principals + auto_import = var.top_auto_import + description = var.top_description + + name = var.top_name + netmask_length = var.top_netmask_length } } diff --git a/modules/sub_pool/README.md b/modules/sub_pool/README.md index 682c0a4..cda9bd7 100644 --- a/modules/sub_pool/README.md +++ b/modules/sub_pool/README.md @@ -32,8 +32,9 @@ No modules. |------|-------------|------|---------|:--------:| | [address\_family](#input\_address\_family) | IPv4/6 address family. | `string` | n/a | yes | | [ipam\_scope\_id](#input\_ipam\_scope\_id) | IPAM Scope ID to attach the pool to. | `string` | n/a | yes | -| [pool\_config](#input\_pool\_config) | Configuration of the Pool you want to deploy. All aws\_vpc\_ipam\_pool arguments are available as well as ram\_share\_principals list and sub\_pools map (up to 3 levels). |
object({
cidr = optional(list(string))
ram_share_principals = optional(list(string))

locale = optional(string)
allocation_default_netmask_length = optional(string)
allocation_max_netmask_length = optional(string)
allocation_min_netmask_length = optional(string)
auto_import = optional(string)
aws_service = optional(string)
description = optional(string)
name = optional(string)
netmask_length = optional(number)
publicly_advertisable = optional(bool)

allocation_resource_tags = optional(map(string))
tags = optional(map(string))
cidr_authorization_context = optional(map(string))

sub_pools = optional(any)
})
| n/a | yes | +| [pool\_config](#input\_pool\_config) | Configuration of the Pool you want to deploy. All aws\_vpc\_ipam\_pool arguments are available as well as ram\_share\_principals list and sub\_pools map (up to 3 levels). |
object({
cidr = optional(list(string))
ram_share_principals = optional(list(string))

locale = optional(string)
allocation_default_netmask_length = optional(string)
allocation_max_netmask_length = optional(string)
allocation_min_netmask_length = optional(string)
auto_import = optional(string)
aws_service = optional(string)
description = optional(string)
name = optional(string)
netmask_length = optional(number)
publicly_advertisable = optional(bool)

allocation_resource_tags = optional(map(string))
tags = optional(map(string))

sub_pools = optional(any)
})
| n/a | yes | | [source\_ipam\_pool\_id](#input\_source\_ipam\_pool\_id) | IPAM parent pool ID to attach the pool to. | `string` | n/a | yes | +| [cidr\_authorization\_contexts](#input\_cidr\_authorization\_contexts) | A list of signed documents that proves that you are authorized to bring the specified IP address range to Amazon using BYOIP. Document is not stored in the state file. For more information, refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipam_pool_cidr#cidr_authorization_context. |
list(object({
cidr = string
message = string
signature = string
}))
| `[]` | no | | [implied\_description](#input\_implied\_description) | Description is implied from the pool tree name / unless specified on the pool\_config. | `string` | `null` | no | | [implied\_locale](#input\_implied\_locale) | Locale is implied from a parent pool even if another is specified. Its not possible to set child pools to different locales. | `string` | `"None"` | no | | [implied\_name](#input\_implied\_name) | Name is implied from the pool tree name / unless specified on the pool\_config. | `string` | `null` | no | diff --git a/modules/sub_pool/main.tf b/modules/sub_pool/main.tf index 0767856..672757e 100644 --- a/modules/sub_pool/main.tf +++ b/modules/sub_pool/main.tf @@ -8,6 +8,12 @@ locals { ram_share_enabled = try(length(var.pool_config.ram_share_principals), 0) > 0 pool_cidrs = var.pool_config.cidr == null ? [var.pool_config.netmask_length] : var.pool_config.cidr + cidr_authorization_contexts = { + for k, v in var.cidr_authorization_contexts : v.cidr => { + message = v.message, + signature = v.signature + } + } } resource "aws_vpc_ipam_pool" "sub" { @@ -36,10 +42,10 @@ resource "aws_vpc_ipam_pool_cidr" "sub" { netmask_length = length(regexall("/", each.key)) == 0 ? each.key : null dynamic "cidr_authorization_context" { - for_each = var.pool_config.cidr_authorization_context == null ? [] : [1] + for_each = length(var.cidr_authorization_contexts) == 0 ? [] : [1] content { - message = var.pool_config.cidr_authorization_context.message - signature = var.pool_config.cidr_authorization_context.signature + message = local.cidr_authorization_contexts[each.key].message + signature = local.cidr_authorization_contexts[each.key].signature } } } diff --git a/modules/sub_pool/variables.tf b/modules/sub_pool/variables.tf index 983a963..7e0b8af 100644 --- a/modules/sub_pool/variables.tf +++ b/modules/sub_pool/variables.tf @@ -15,9 +15,8 @@ variable "pool_config" { netmask_length = optional(number) publicly_advertisable = optional(bool) - allocation_resource_tags = optional(map(string)) - tags = optional(map(string)) - cidr_authorization_context = optional(map(string)) + allocation_resource_tags = optional(map(string)) + tags = optional(map(string)) sub_pools = optional(any) }) @@ -31,6 +30,17 @@ variable "pool_config" { } } +variable "cidr_authorization_contexts" { + description = "A list of signed documents that proves that you are authorized to bring the specified IP address range to Amazon using BYOIP. Document is not stored in the state file. For more information, refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipam_pool_cidr#cidr_authorization_context." + type = list(object({ + cidr = string + message = string + signature = string + })) + default = [] +} + + variable "implied_locale" { description = "Locale is implied from a parent pool even if another is specified. Its not possible to set child pools to different locales." type = string diff --git a/test/examples_single_scope_ipv6_test.go b/test/examples_single_scope_ipv6_test.go index 2da934a..db7ca3a 100644 --- a/test/examples_single_scope_ipv6_test.go +++ b/test/examples_single_scope_ipv6_test.go @@ -28,6 +28,7 @@ func TestExamplesIPv6(t *testing.T) { TerraformDir: "../examples/single_scope_ipv6", Vars: map[string]interface{}{ "ipv6_cidr": _ipv6_cidr, + "cidr_authorization_context_cidr": _ipv6_cidr, "cidr_authorization_context_message": _message, "cidr_authorization_context_signature": _signature, }, diff --git a/variables.tf b/variables.tf index b95a554..5ac0492 100644 --- a/variables.tf +++ b/variables.tf @@ -62,12 +62,17 @@ variable "top_name" { default = null } -variable "top_cidr_authorization_context" { - description = "A signed document that proves that you are authorized to bring the specified IP address range to Amazon using BYOIP. Document is not stored in the state file. For more information, refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipam_pool_cidr#cidr_authorization_context." - type = any - default = null +variable "top_cidr_authorization_contexts" { + description = "CIDR must match a CIDR defined in `var.top_cidr`. A list of signed documents that proves that you are authorized to bring the specified IP address range to Amazon using BYOIP. Document is not stored in the state file. For more information, refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipam_pool_cidr#cidr_authorization_context." + type = list(object({ + cidr = string + message = string + signature = string + })) + default = [] } + variable "address_family" { description = "IPv4/6 address family." type = string