Skip to content

Commit

Permalink
add routes to peered spokes (pulumi#747)
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesianberry committed Jul 20, 2020
1 parent 17cfd25 commit 9cb78d2
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 23 deletions.
2 changes: 2 additions & 0 deletions azure-py-virtual-data-center/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
*.pyc
.venv/
.vscode/
venv/
__pycache__
4 changes: 2 additions & 2 deletions azure-py-virtual-data-center/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
12 changes: 9 additions & 3 deletions azure-py-virtual-data-center/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@
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
Expand All @@ -47,7 +50,10 @@
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
Expand All @@ -65,10 +71,10 @@
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)
14 changes: 11 additions & 3 deletions azure-py-virtual-data-center/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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."""
Expand Down Expand Up @@ -30,13 +30,21 @@ def __init__(self, keys: [str], message: str):

# 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

Expand Down
63 changes: 51 additions & 12 deletions azure-py-virtual-data-center/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(
firewall_address_space: str,
hub_address_space: str,
peer: str,
reference: str,
reference: StackReference,
resource_group_name: str,
stack: str,
subnets: [str, str, str],
Expand Down Expand Up @@ -143,10 +143,6 @@ def __init__(self, name: str, props: HubProps, opts: ResourceOptions=None):
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')
Expand Down Expand Up @@ -176,6 +172,49 @@ def __init__(self, name: str, props: HubProps, opts: ResourceOptions=None):
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',
Expand Down Expand Up @@ -213,8 +252,7 @@ def __init__(self, name: str, props: HubProps, opts: ResourceOptions=None):

# 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(
Expand All @@ -227,9 +265,9 @@ def __init__(self, name: str, props: HubProps, opts: ResourceOptions=None):
)

# 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),
Expand Down Expand Up @@ -262,20 +300,21 @@ def __init__(self, name: str, props: HubProps, opts: ResourceOptions=None):
)

# 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
self.location = hub.location # informational
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
Expand Down
24 changes: 21 additions & 3 deletions azure-py-virtual-data-center/spoke.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
from ipaddress import ip_network
from pulumi import ComponentResource, ResourceOptions
from pulumi import ComponentResource, ResourceOptions, StackReference
from hub import Hub
import vdc

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
Expand Down Expand Up @@ -65,6 +71,18 @@ def __init__(self, name: str, props: SpokeProps,
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(
Expand Down Expand Up @@ -125,13 +143,13 @@ def __init__(self, name: str, props: SpokeProps,
)

# 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({})
11 changes: 11 additions & 0 deletions azure-py-virtual-data-center/vdc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 9cb78d2

Please sign in to comment.