Skip to content

Commit

Permalink
feat: add authorization policy for spoke -> hub DNS access (#775)
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthewLemmond committed May 3, 2024
1 parent 365c23b commit 2a5932f
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 2 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 |
Expand All @@ -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 |
Expand Down Expand Up @@ -164,6 +166,7 @@ To attach access management tags to resources in this module, you need the follo
| <a name="input_existing_storage_bucket_name"></a> [existing\_storage\_bucket\_name](#input\_existing\_storage\_bucket\_name) | Name of the COS bucket to collect VPC flow logs | `string` | `null` | no |
| <a name="input_existing_subnets"></a> [existing\_subnets](#input\_existing\_subnets) | The detail of the existing subnets and required mappings to other resources. Required if 'create\_subnets' is false. | <pre>list(object({<br> id = string<br> public_gateway = optional(bool, false)<br> }))</pre> | `[]` | no |
| <a name="input_existing_vpc_id"></a> [existing\_vpc\_id](#input\_existing\_vpc\_id) | The ID of the existing vpc. Required if 'create\_vpc' is false. | `string` | `null` | no |
| <a name="input_hub_account_id"></a> [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 |
| <a name="input_hub_vpc_crn"></a> [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 |
| <a name="input_hub_vpc_id"></a> [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 |
| <a name="input_is_flow_log_collector_active"></a> [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 |
Expand All @@ -180,6 +183,7 @@ To attach access management tags to resources in this module, you need the follo
| <a name="input_routing_table_name"></a> [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 |
| <a name="input_security_group_rules"></a> [security\_group\_rules](#input\_security\_group\_rules) | A list of security group rules to be added to the default vpc security group (default empty) | <pre>list(<br> object({<br> name = string<br> direction = string<br> remote = string<br> tcp = optional(<br> object({<br> port_max = optional(number)<br> port_min = optional(number)<br> })<br> )<br> udp = optional(<br> object({<br> port_max = optional(number)<br> port_min = optional(number)<br> })<br> )<br> icmp = optional(<br> object({<br> type = optional(number)<br> code = optional(number)<br> })<br> )<br> })<br> )</pre> | `[]` | no |
| <a name="input_skip_custom_resolver_hub_creation"></a> [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 |
| <a name="input_skip_spoke_auth_policy"></a> [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 |
| <a name="input_subnets"></a> [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 | <pre>object({<br> zone-1 = list(object({<br> name = string<br> cidr = string<br> public_gateway = optional(bool)<br> acl_name = string<br> no_addr_prefix = optional(bool, false) # do not automatically add address prefix for subnet, overrides other conditions if set to true<br> }))<br> zone-2 = optional(list(object({<br> name = string<br> cidr = string<br> public_gateway = optional(bool)<br> acl_name = string<br> no_addr_prefix = optional(bool, false) # do not automatically add address prefix for subnet, overrides other conditions if set to true<br> })))<br> zone-3 = optional(list(object({<br> name = string<br> cidr = string<br> public_gateway = optional(bool)<br> acl_name = string<br> no_addr_prefix = optional(bool, false) # do not automatically add address prefix for subnet, overrides other conditions if set to true<br> })))<br> })</pre> | <pre>{<br> "zone-1": [<br> {<br> "acl_name": "vpc-acl",<br> "cidr": "10.10.10.0/24",<br> "name": "subnet-a",<br> "no_addr_prefix": false,<br> "public_gateway": true<br> }<br> ],<br> "zone-2": [<br> {<br> "acl_name": "vpc-acl",<br> "cidr": "10.20.10.0/24",<br> "name": "subnet-b",<br> "no_addr_prefix": false,<br> "public_gateway": true<br> }<br> ],<br> "zone-3": [<br> {<br> "acl_name": "vpc-acl",<br> "cidr": "10.30.10.0/24",<br> "name": "subnet-c",<br> "no_addr_prefix": false,<br> "public_gateway": false<br> }<br> ]<br>}</pre> | no |
| <a name="input_tags"></a> [tags](#input\_tags) | List of Tags for the resource created | `list(string)` | `null` | no |
| <a name="input_update_delegated_resolver"></a> [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 |
Expand Down
1 change: 0 additions & 1 deletion examples/existing_vpc/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions examples/hub-spoke-delegated-resolver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
3 changes: 3 additions & 0 deletions examples/hub-spoke-delegated-resolver/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,16 @@ module "hub_vpc" {
}


data "ibm_iam_account_settings" "iam_account_settings" {}

module "spoke_vpc" {
source = "../../"
resource_group_id = module.resource_group.resource_group_id
region = var.region
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
Expand Down
2 changes: 2 additions & 0 deletions examples/hub-spoke-manual-resolver/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ module "hub_vpc" {
}
}

data "ibm_iam_account_settings" "iam_account_settings" {}

module "spoke_vpc" {
source = "../../"
Expand All @@ -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
Expand Down
46 changes: 46 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

##############################################################################
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down
12 changes: 12 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2a5932f

Please sign in to comment.