From 2a5932f669d7ea4b9faaf212e8a36385516d2148 Mon Sep 17 00:00:00 2001 From: Matthew Lemmond Date: Fri, 3 May 2024 05:48:45 -0400 Subject: [PATCH] feat: add authorization policy for spoke -> hub DNS access (#775) --- README.md | 6 ++- examples/existing_vpc/variables.tf | 1 - .../hub-spoke-delegated-resolver/README.md | 1 + examples/hub-spoke-delegated-resolver/main.tf | 3 ++ examples/hub-spoke-manual-resolver/main.tf | 2 + main.tf | 46 +++++++++++++++++++ variables.tf | 12 +++++ 7 files changed, 69 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index badeb234..0c1c1ec1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This module creates the following IBM Cloud® Virtual Private Cloud (VPC) net - Network ACLs: Create network ACLs with multiple rules. By default, VPC network ACLs can have no more than 25 rules. - VPN gateways: Create VPN gateways on your subnets by using the `vpn_gateways` variable. For more information about VPN gateways on VPC, see [About site-to-site VPN gateways](https://cloud.ibm.com/docs/vpc?topic=vpc-using-vpn) in the IBM Cloud docs. - VPN gateway connections: Add connections to a VPN gateway. -- Hub and spoke DNS-sharing model: Optionally create a hub or spoke VPC, with associated custom resolver and DNS resolution binding. See [About DNS sharing for VPE gateways](https://cloud.ibm.com/docs/vpc?topic=vpc-hub-spoke-model) in the IBM Cloud docs for details. +- Hub and spoke DNS-sharing model: Optionally create a hub or spoke VPC, with associated custom resolver and DNS resolution binding, as well as a service-to-service authorization policy which supports the hub and spoke VPCs to be in separate accounts. See [About DNS sharing for VPE gateways](https://cloud.ibm.com/docs/vpc?topic=vpc-hub-spoke-model) in the IBM Cloud docs for details. ![vpc-module](https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-landing-zone-vpc/main/.docs/vpc-module.png) @@ -117,6 +117,7 @@ To attach access management tags to resources in this module, you need the follo |------|------| | [ibm_dns_custom_resolver.custom_resolver_hub](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/dns_custom_resolver) | resource | | [ibm_iam_authorization_policy.policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | +| [ibm_iam_authorization_policy.vpc_dns_resolution_auth_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | | [ibm_is_flow_log.flow_logs](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_flow_log) | resource | | [ibm_is_network_acl.network_acl](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_network_acl) | resource | | [ibm_is_public_gateway.gateway](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_public_gateway) | resource | @@ -132,6 +133,7 @@ To attach access management tags to resources in this module, you need the follo | [ibm_is_vpc_routing_table_route.routing_table_routes](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_vpc_routing_table_route) | resource | | [ibm_resource_instance.dns_instance_hub](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/resource_instance) | resource | | [time_sleep.wait_for_authorization_policy](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [ibm_iam_account_settings.iam_account_settings](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/iam_account_settings) | data source | | [ibm_is_subnet.subnet](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/is_subnet) | data source | | [ibm_is_vpc.vpc](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/is_vpc) | data source | | [ibm_is_vpc_address_prefixes.get_address_prefixes](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/data-sources/is_vpc_address_prefixes) | data source | @@ -164,6 +166,7 @@ To attach access management tags to resources in this module, you need the follo | [existing\_storage\_bucket\_name](#input\_existing\_storage\_bucket\_name) | Name of the COS bucket to collect VPC flow logs | `string` | `null` | no | | [existing\_subnets](#input\_existing\_subnets) | The detail of the existing subnets and required mappings to other resources. Required if 'create\_subnets' is false. |
list(object({
id = string
public_gateway = optional(bool, false)
}))
| `[]` | no | | [existing\_vpc\_id](#input\_existing\_vpc\_id) | The ID of the existing vpc. Required if 'create\_vpc' is false. | `string` | `null` | no | +| [hub\_account\_id](#input\_hub\_account\_id) | ID of the hub account for DNS resolution, required if 'skip\_spoke\_auth\_policy' is false. | `string` | `null` | no | | [hub\_vpc\_crn](#input\_hub\_vpc\_crn) | Indicates the crn of the hub VPC for DNS resolution. See https://cloud.ibm.com/docs/vpc?topic=vpc-hub-spoke-model. Mutually exclusive with hub\_vpc\_id. | `string` | `null` | no | | [hub\_vpc\_id](#input\_hub\_vpc\_id) | Indicates the id of the hub VPC for DNS resolution. See https://cloud.ibm.com/docs/vpc?topic=vpc-hub-spoke-model. Mutually exclusive with hub\_vpc\_crn. | `string` | `null` | no | | [is\_flow\_log\_collector\_active](#input\_is\_flow\_log\_collector\_active) | Indicates whether the collector is active. If false, this collector is created in inactive mode. | `bool` | `true` | no | @@ -180,6 +183,7 @@ To attach access management tags to resources in this module, you need the follo | [routing\_table\_name](#input\_routing\_table\_name) | The name to give the provisioned routing tables. If not set, the module generates a name based on the `prefix` and `name` variables. | `string` | `null` | no | | [security\_group\_rules](#input\_security\_group\_rules) | A list of security group rules to be added to the default vpc security group (default empty) |
list(
object({
name = string
direction = string
remote = string
tcp = optional(
object({
port_max = optional(number)
port_min = optional(number)
})
)
udp = optional(
object({
port_max = optional(number)
port_min = optional(number)
})
)
icmp = optional(
object({
type = optional(number)
code = optional(number)
})
)
})
)
| `[]` | no | | [skip\_custom\_resolver\_hub\_creation](#input\_skip\_custom\_resolver\_hub\_creation) | Indicates whether to skip the configuration of a custom resolver in the hub VPC. Only relevant if enable\_hub is set to true. | `bool` | `false` | no | +| [skip\_spoke\_auth\_policy](#input\_skip\_spoke\_auth\_policy) | Set to true to skip the creation of an authorization policy between the DNS resolution spoke and hub, only enable this if a policy already exists between these two VPCs. See https://cloud.ibm.com/docs/vpc?topic=vpc-vpe-dns-sharing-s2s-auth&interface=ui for more details. | `bool` | `false` | no | | [subnets](#input\_subnets) | List of subnets for the vpc. For each item in each array, a subnet will be created. Items can be either CIDR blocks or total ipv4 addressess. Public gateways will be enabled only in zones where a gateway has been created |
object({
zone-1 = list(object({
name = string
cidr = string
public_gateway = optional(bool)
acl_name = string
no_addr_prefix = optional(bool, false) # do not automatically add address prefix for subnet, overrides other conditions if set to true
}))
zone-2 = optional(list(object({
name = string
cidr = string
public_gateway = optional(bool)
acl_name = string
no_addr_prefix = optional(bool, false) # do not automatically add address prefix for subnet, overrides other conditions if set to true
})))
zone-3 = optional(list(object({
name = string
cidr = string
public_gateway = optional(bool)
acl_name = string
no_addr_prefix = optional(bool, false) # do not automatically add address prefix for subnet, overrides other conditions if set to true
})))
})
|
{
"zone-1": [
{
"acl_name": "vpc-acl",
"cidr": "10.10.10.0/24",
"name": "subnet-a",
"no_addr_prefix": false,
"public_gateway": true
}
],
"zone-2": [
{
"acl_name": "vpc-acl",
"cidr": "10.20.10.0/24",
"name": "subnet-b",
"no_addr_prefix": false,
"public_gateway": true
}
],
"zone-3": [
{
"acl_name": "vpc-acl",
"cidr": "10.30.10.0/24",
"name": "subnet-c",
"no_addr_prefix": false,
"public_gateway": false
}
]
}
| no | | [tags](#input\_tags) | List of Tags for the resource created | `list(string)` | `null` | no | | [update\_delegated\_resolver](#input\_update\_delegated\_resolver) | If set to true, and if the vpc is configured to be a spoke for DNS resolution (enable\_hub\_vpc\_crn or enable\_hub\_vpc\_id set), then the spoke VPC resolver will be updated to a delegated resolver. | `bool` | `false` | no | diff --git a/examples/existing_vpc/variables.tf b/examples/existing_vpc/variables.tf index f2c5f7cd..4e46e0a4 100644 --- a/examples/existing_vpc/variables.tf +++ b/examples/existing_vpc/variables.tf @@ -32,5 +32,4 @@ variable "existing_resource_group_name" { variable "name" { description = "The string is used as a prefix for the naming of VPC resources." type = string - default = null } diff --git a/examples/hub-spoke-delegated-resolver/README.md b/examples/hub-spoke-delegated-resolver/README.md index 17cbb14b..3e4551b3 100644 --- a/examples/hub-spoke-delegated-resolver/README.md +++ b/examples/hub-spoke-delegated-resolver/README.md @@ -4,6 +4,7 @@ This example demonstrates how to deploy hub and spoke VPCs, inclusive of enablin - The 2 VPCs are connected through a transit gateway. - The hub VPC is configured with a custom resolver. - The spoke VPC is configured with a delegated DNS resolver. DNS requests are resolved by the hub VPC. +- An authorization policy for the DNS Binding Connector role is created to allow the spoke VPC to use the DNS resolution of the hub VPC, this also allows the hub and spoke VPCs to be in separate accounts. - A DNS resolution binding relationship is configured to enable the hub VPC to DNS resolve VPE in the spoke VPC. diff --git a/examples/hub-spoke-delegated-resolver/main.tf b/examples/hub-spoke-delegated-resolver/main.tf index 6bfbb16f..71fa0183 100644 --- a/examples/hub-spoke-delegated-resolver/main.tf +++ b/examples/hub-spoke-delegated-resolver/main.tf @@ -51,6 +51,8 @@ module "hub_vpc" { } +data "ibm_iam_account_settings" "iam_account_settings" {} + module "spoke_vpc" { source = "../../" resource_group_id = module.resource_group.resource_group_id @@ -58,6 +60,7 @@ module "spoke_vpc" { name = "spoke" prefix = "${var.prefix}-spoke" tags = var.resource_tags + hub_account_id = data.ibm_iam_account_settings.iam_account_settings.account_id hub_vpc_crn = module.hub_vpc.vpc_crn enable_hub_vpc_crn = true update_delegated_resolver = var.update_delegated_resolver diff --git a/examples/hub-spoke-manual-resolver/main.tf b/examples/hub-spoke-manual-resolver/main.tf index 0501aa61..a089e5f5 100644 --- a/examples/hub-spoke-manual-resolver/main.tf +++ b/examples/hub-spoke-manual-resolver/main.tf @@ -50,6 +50,7 @@ module "hub_vpc" { } } +data "ibm_iam_account_settings" "iam_account_settings" {} module "spoke_vpc" { source = "../../" @@ -58,6 +59,7 @@ module "spoke_vpc" { name = "spoke" prefix = "${var.prefix}-spoke" tags = var.resource_tags + hub_account_id = data.ibm_iam_account_settings.iam_account_settings.account_id hub_vpc_crn = module.hub_vpc.vpc_crn enable_hub_vpc_crn = true update_delegated_resolver = false diff --git a/main.tf b/main.tf index e534cab8..dcac73f1 100644 --- a/main.tf +++ b/main.tf @@ -37,6 +37,9 @@ locals { # tflint-ignore: terraform_unused_declarations validate_vpc_flow_logs_inputs = (var.enable_vpc_flow_logs) ? ((var.create_authorization_policy_vpc_to_cos) ? ((var.existing_cos_instance_guid != null && var.existing_storage_bucket_name != null) ? true : tobool("Please provide COS instance & bucket name to create flow logs collector.")) : ((var.existing_storage_bucket_name != null) ? true : tobool("Please provide COS bucket name to create flow logs collector"))) : false + + # tflint-ignore: terraform_unused_declarations + validate_skip_spoke_auth_policy_input = (var.hub_account_id == null && !var.skip_spoke_auth_policy && !var.enable_hub && (var.enable_hub_vpc_id || var.enable_hub_vpc_crn)) ? tobool("var.hub_account_id must be set when var.skip_spoke_auth_policy is False and either var.enable_hub_vpc_id or var.enable_hub_vpc_crn is true.") : true } ############################################################################## @@ -116,9 +119,50 @@ resource "ibm_is_vpc" "vpc" { # See https://cloud.ibm.com/docs/vpc?topic=vpc-hub-spoke-model for context ############################################################################## +# fetch this account ID +data "ibm_iam_account_settings" "iam_account_settings" {} + +# spoke -> hub auth policy based on https://cloud.ibm.com/docs/vpc?topic=vpc-vpe-dns-sharing-s2s-auth&interface=terraform +resource "ibm_iam_authorization_policy" "vpc_dns_resolution_auth_policy" { + count = (var.enable_hub == false && var.skip_spoke_auth_policy == false && (var.enable_hub_vpc_id || var.enable_hub_vpc_crn)) ? 1 : 0 + roles = ["DNS Binding Connector"] + # subject is the spoke + subject_attributes { + name = "accountId" + value = data.ibm_iam_account_settings.iam_account_settings.account_id + } + subject_attributes { + name = "serviceName" + value = "is" + } + subject_attributes { + name = "resourceType" + value = "vpc" + } + subject_attributes { + name = "resource" + value = local.vpc_id + } + # resource is the hub + resource_attributes { + name = "accountId" + value = var.hub_account_id + } + resource_attributes { + name = "serviceName" + value = "is" + } + resource_attributes { + name = "vpcId" + value = var.enable_hub_vpc_id ? var.hub_vpc_id : split(":", var.hub_vpc_crn)[9] + } +} + # Enable Hub to dns resolve in spoke VPC resource "ibm_is_vpc_dns_resolution_binding" "vpc_dns_resolution_binding_id" { count = (var.enable_hub == false && var.enable_hub_vpc_id) ? 1 : 0 + # Depends on required as the authorization policy cannot be directly referenced + depends_on = [ibm_iam_authorization_policy.vpc_dns_resolution_auth_policy] # Use var.dns_binding_name if not null, otherwise, use var.prefix and var.name combination. name = coalesce( @@ -133,6 +177,8 @@ resource "ibm_is_vpc_dns_resolution_binding" "vpc_dns_resolution_binding_id" { resource "ibm_is_vpc_dns_resolution_binding" "vpc_dns_resolution_binding_crn" { count = (var.enable_hub == false && var.enable_hub_vpc_crn) ? 1 : 0 + # Depends on required as the authorization policy cannot be directly referenced + depends_on = [ibm_iam_authorization_policy.vpc_dns_resolution_auth_policy] # Use var.dns_binding_name if not null, otherwise, use var.prefix and var.name combination. name = coalesce( diff --git a/variables.tf b/variables.tf index 260c0649..ae46c2c3 100644 --- a/variables.tf +++ b/variables.tf @@ -542,6 +542,18 @@ variable "enable_hub" { default = false } +variable "skip_spoke_auth_policy" { + description = "Set to true to skip the creation of an authorization policy between the DNS resolution spoke and hub, only enable this if a policy already exists between these two VPCs. See https://cloud.ibm.com/docs/vpc?topic=vpc-vpe-dns-sharing-s2s-auth&interface=ui for more details." + type = bool + default = false +} + +variable "hub_account_id" { + description = "ID of the hub account for DNS resolution, required if 'skip_spoke_auth_policy' is false." + type = string + default = null +} + variable "enable_hub_vpc_id" { description = "Indicates whether Hub VPC ID is passed." type = bool