add routes to peered spokes
MODIFY
azure-py-virtual-data-center/.gitignore
azure-py-virtual-data-center/.gitignore
*.pyc
+.venv/
+.vscode/
venv/
__pycache__

MODIFY
azure-py-virtual-data-center/README.md
azure-py-virtual-data-center/README.md
@@ -50,7 +50,7 @@ After cloning this repo, `cd` into the `azure-py-virtual-data-center` directory
Optional:
```bash
$ pulumi config set azure_bastion "true"
- $ pulumi config set forced_tunnel "true"
+ $ pulumi config set forced_tunnel "10.0.100.1"
```
1. Deploy the `prod` stack with the `pulumi up` command. This may take up to an hour to provision all the Azure resources specified, including gateways, firewall and bastion hosts:
@@ -175,7 +175,7 @@ After cloning this repo, `cd` into the `azure-py-virtual-data-center` directory
Optional:
```bash
$ pulumi config set azure_bastion "true"
- $ pulumi config set forced_tunnel "true"
+ $ pulumi config set forced_tunnel "10.0.200.1"
```
1. Deploy the `dr` stack with the `pulumi up` command. Once again, this may take up to an hour to provision all the Azure resources specified, including gateways, firewall and bastion hosts:

MODIFY
azure-py-virtual-data-center/__main__.py
azure-py-virtual-data-center/__main__.py
@@ -32,7 +32,10 @@ hub = Hub('hub', # stem of child resource names (<4 chars)
spoke1 = Spoke('s01', # stem of child resource names (<6 chars)
SpokeProps(
azure_bastion = config.azure_bastion,
+ fw_rt_name = hub.fw_rt_name,
hub = hub,
+ peer = config.peer,
+ reference = config.reference,
resource_group_name = resource_group_name,
spoke_address_space = str(next(config.stack_sn)),
subnets = [ # extra columns for future NSGs
@@ -47,7 +50,10 @@ spoke1 = Spoke('s01', # stem of child resource names (<6 chars)
spoke2 = Spoke('s02', # stem of child resource names (<6 chars)
SpokeProps(
azure_bastion = config.azure_bastion,
+ fw_rt_name = hub.fw_rt_name,
hub = hub,
+ peer = config.peer,
+ reference = config.reference,
resource_group_name = resource_group_name,
spoke_address_space = str(next(config.stack_sn)),
subnets = [ # extra columns for future NSGs
@@ -65,10 +71,10 @@ export('fw_ip', hub.fw_ip) # required for stack peering
export('hub_as', hub.hub_as) # required for stack peering
export('hub_id', hub.id) # required for stack peering
export('hub_name', hub.name)
-export('hub_subnets', hub.subnets)
+export('hub_address_spaces', hub.address_spaces)
export('s01_id', spoke1.id)
export('s01_name', spoke1.name)
-export('s01_subnets', spoke1.subnets)
+export('s01_address_spaces', spoke1.address_spaces)
export('s02_id', spoke2.id)
export('s02_name', spoke2.name)
-export('s02_subnets', spoke2.subnets)
+export('s02_address_spaces', spoke2.address_spaces)

MODIFY
azure-py-virtual-data-center/config.py
azure-py-virtual-data-center/config.py
-from ipaddress import ip_network
-from pulumi import Config, get_stack, get_project
+from ipaddress import ip_address, ip_network
+from pulumi import Config, get_stack, get_project, StackReference
class Error(Exception):
"""Base class for exceptions in this module."""
@@ -30,13 +30,21 @@ azure_bastion = config.get_bool('azure_bastion')
# Azure Firewall to route all Internet-bound traffic to designated next hop
forced_tunnel = config.get_bool('forced_tunnel')
+# turn off SNAT (private_ranges not yet available on Azure API?)
+# https://docs.microsoft.com/en-us/azure/firewall/snat-private-range
+if forced_tunnel:
+ ft_ip = ip_address(forced_tunnel)
+ if ft_ip.is_private:
+ private_ranges = 'IANAPrivateRanges'
+ else:
+ private_ranges = '0.0.0.0./0'
# another stack in the same project and organization may be peered
peer = config.get('peer')
if peer:
org = config.require('org')
project = get_project()
- reference = f'{org}/{project}/{peer}'
+ reference = StackReference(f'{org}/{project}/{peer}')
else:
reference = None

MODIFY
azure-py-virtual-data-center/hub.py
azure-py-virtual-data-center/hub.py
@@ -10,7 +10,7 @@ class HubProps:
firewall_address_space: str,
hub_address_space: str,
peer: str,
- reference: str,
+ reference: StackReference,
resource_group_name: str,
stack: str,
subnets: [str, str, str],
@@ -143,10 +143,6 @@ class Hub(ComponentResource):
subnet_id = hub_ab_sn.id,
)
- #ToDo requires Azure API version 2019-11-01 or later
- #if props.forced_tunnel:
- # https://docs.microsoft.com/en-us/azure/firewall/forced-tunneling
-
# work around https://github.com/pulumi/pulumi/issues/4040
hub_fw_ip = hub_fw.ip_configurations.apply(
lambda ipc: ipc[0].get('private_ip_address')
@@ -176,6 +172,49 @@ class Hub(ComponentResource):
subnet_id = hub_dmz_sn.id,
)
+ #ToDo forced_tunnel requires Azure API version 2019-11-01 or later
+ # https://docs.microsoft.com/en-us/azure/firewall/forced-tunneling
+
+ # Route Table only to be associated with AzureFirewallSubnet
+ hub_fw_rt = vdc.route_table(
+ stem = f'{name}-fw',
+ disable_bgp_route_propagation = False,
+ depends_on = [hub_er_gw, hub_fw, hub_vpn_gw], # avoid contention
+ ) # for routes to peered spokes and Internet (including forced_tunnel)
+ if props.forced_tunnel:
+ vdc.route_to_virtual_appliance(
+ stem = f'fw-tunnel',
+ route_table_name = hub_fw_rt.name,
+ address_prefix = '0.0.0.0/0',
+ next_hop_in_ip_address = props.forced_tunnel,
+ )
+ else:
+ vdc.route_to_internet(
+ stem = f'fw-internet',
+ route_table_name = hub_fw_rt.name,
+ )
+ hub_fw_sn_rta = vdc.subnet_route_table(
+ stem = f'{name}-fw',
+ route_table_id = hub_fw_rt.id,
+ subnet_id = hub_fw_sn.id,
+ )
+
+ # Route Table only to be associated with AzureFirewallManagementSubnet
+ hub_fwm_rt = vdc.route_table(
+ stem = f'{name}-fwm',
+ disable_bgp_route_propagation = True,
+ depends_on = [hub_er_gw, hub_fw, hub_vpn_gw], # avoid contention
+ )
+ vdc.route_to_internet(
+ stem = f'fwm-internet',
+ route_table_name = hub_fwm_rt.name,
+ )
+ hub_fwm_sn_rta = vdc.subnet_route_table(
+ stem = f'{name}-fwm',
+ route_table_id = hub_fwm_rt.id,
+ subnet_id = hub_fwm_sn.id,
+ )
+
# Route Table only to be associated with hub shared services subnets
hub_ss_rt = vdc.route_table(
stem = f'{name}-ss',
@@ -213,8 +252,7 @@ class Hub(ComponentResource):
# VNet Peering between stacks using StackReference
if props.peer:
- peer_stack = StackReference(props.reference)
- peer_hub_id = peer_stack.get_output('hub_id')
+ peer_hub_id = props.reference.get_output('hub_id')
# VNet Peering (Global) in one direction from stack to peer
hub_hub = vdc.vnet_peering(
@@ -227,9 +265,9 @@ class Hub(ComponentResource):
)
# need to invalidate system routes created by Global VNet Peering
- peer_dmz_ar = peer_stack.get_output('dmz_ar')
- peer_fw_ip = peer_stack.get_output('fw_ip')
- peer_hub_as = peer_stack.get_output('hub_as')
+ peer_dmz_ar = props.reference.get_output('dmz_ar')
+ peer_fw_ip = props.reference.get_output('fw_ip')
+ peer_hub_as = props.reference.get_output('hub_as')
for route in [
(f'dmz-{props.peer}-dmz', hub_dmz_rt.name, peer_dmz_ar),
@@ -262,12 +300,13 @@ class Hub(ComponentResource):
)
# assign properties to hub including from child resources
- self.address_spaces = hub.address_spaces # informational
+ self.address_spaces = hub.address_spaces # exported
self.dmz_ar = dmz_ar # used for routes to the hub
self.dmz_rt_name = hub_dmz_rt.name # used to add routes to spokes
self.er_gw = hub_er_gw # needed prior to VNet Peering from spokes
self.fw = hub_fw # needed prior to VNet Peering from spokes
self.fw_ip = hub_fw_ip # used for routes to the hub
+ self.fw_rt_name = hub_fw_rt.name # used for route to the peered spokes
self.gw_rt_name = hub_gw_rt.name # used to add routes to spokes
self.hub_as = props.hub_address_space # used for routes to the hub
self.id = hub.id # exported and used for stack and spoke peering
@@ -275,7 +314,7 @@ class Hub(ComponentResource):
self.name = hub.name # exported and used for spoke peering
self.peer = props.peer # informational
self.resource_group_name = props.resource_group_name # informational
- self.subnets = hub.subnets # exported as informational
+ self.subnets = hub.subnets # informational
self.stack = props.stack # informational
self.stem = name # used for VNet Peering from spokes
self.ss_rt_name = hub_ss_rt.name # used to add routes to spokes

MODIFY
azure-py-virtual-data-center/spoke.py
azure-py-virtual-data-center/spoke.py
from ipaddress import ip_network
-from pulumi import ComponentResource, ResourceOptions
+from pulumi import ComponentResource, ResourceOptions, StackReference
from hub import Hub
import vdc
@@ -7,14 +7,20 @@ class SpokeProps:
def __init__(
self,
azure_bastion: bool,
+ fw_rt_name: str,
hub: Hub,
+ peer: str,
+ reference: StackReference,
resource_group_name: str,
spoke_address_space: str,
subnets: [str, str, str],
tags: [str, str],
):
self.azure_bastion = azure_bastion
+ self.fw_rt_name = fw_rt_name
self.hub = hub
+ self.peer = peer
+ self.reference = reference
self.resource_group_name = resource_group_name
self.spoke_address_space = spoke_address_space
self.subnets = subnets
@@ -65,6 +71,18 @@ class Spoke(ComponentResource):
depends_on=[props.hub.er_gw, props.hub.vpn_gw],
)
+ # add routes to spokes in peered stack
+ if props.peer:
+ peer_fw_ip = props.reference.get_output('fw_ip')
+ peer_spoke_as = props.reference.get_output(f'{name}_address_spaces')
+ for address_prefix in peer_spoke_as:
+ vdc.route_to_virtual_appliance(
+ stem = f'fw-{props.peer}-{name}',
+ route_table_name = props.fw_rt_name,
+ address_prefix = address_prefix,
+ next_hop_in_ip_address = peer_fw_ip,
+ ) # only one address_space per spoke at present...
+
# Azure Bastion subnet and host (optional)
if props.azure_bastion:
spoke_ab_sn = vdc.subnet_special(
@@ -125,13 +143,13 @@ class Spoke(ComponentResource):
)
# assign properties to spoke including from child resources
- self.address_spaces = spoke.address_spaces
+ self.address_spaces = spoke.address_spaces #exported
self.hub = props.hub.id
self.id = spoke.id # exported
self.location = spoke.location
self.name = spoke.name # exported
self.resource_group_name = props.resource_group_name
- self.subnets = spoke.subnets # exported
+ self.subnets = spoke.subnets
self.stem = name
self.tags = props.tags
self.register_outputs({})

MODIFY
azure-py-virtual-data-center/vdc.py
azure-py-virtual-data-center/vdc.py
@@ -122,6 +122,17 @@ def route_table(stem, disable_bgp_route_propagation=None, depends_on=None):
)
return rt
+def route_to_internet(stem, route_table_name):
+ r_i = network.Route(
+ f'{stem}-r-',
+ resource_group_name = resource_group_name,
+ address_prefix = '0.0.0.0/0',
+ next_hop_type = 'Internet',
+ route_table_name = route_table_name,
+ opts = ResourceOptions(parent=self),
+ )
+ return r_i
+
def route_to_virtual_appliance(
stem,
route_table_name,

add routes to peered spokes (#747)