def __init__(self, name: str, props: HubProps, opts: ResourceOptions = None): super().__init__('vdc:network:Hub', name, {}, opts) # set required vdc variables before calling functions vdc.resource_group_name = props.resource_group_name vdc.tags = props.tags vdc.self = self # calculate the subnets in the firewall_address_space fwz_nw = ip_network(props.firewall_address_space) fwz_sn = fwz_nw.subnets(new_prefix=25) # two /26 subnets required fwx_nw = next(fwz_sn) # for Azure Firewall and Management subnets fwz_sn = fwz_nw.address_exclude(fwx_nw) # consolidate remainder dmz_nw = next(fwz_sn) # largest remaining subnet for DMZ fwx_sn = fwx_nw.subnets(new_prefix=26) # split the /25 into two /26 fws_nw = next(fwx_sn) # AzureFirewallSubnet fwm_nw = next(fwx_sn) # AzureFirewallManagementSubnet # calculate the subnets in the hub_address_space hub_nw = ip_network(props.hub_address_space) if hub_nw.prefixlen < 20: # split evenly between subnets and hosts sub_diff = int((hub_nw.max_prefixlen - hub_nw.prefixlen) / 2) else: sub_diff = 25 - hub_nw.prefixlen # minimum /25 subnet subnets = hub_nw.subnets(prefixlen_diff=sub_diff) next_sn = next(subnets) # first subnet reserved for special uses first_sn = next_sn.subnets(new_prefix=26) # split it into /26 subnets gws_nw = next(first_sn) # GatewaySubnet /26 rem_nw = next(first_sn) # at least one more /26 subnet, perhaps more rem_sn = rem_nw.subnets(new_prefix=27) # only need /27 save the rest abs_nw = next(rem_sn) # AzureBastionSubnet /27 or greater # cast repeatedly referenced networks to strings dmz_ar = str(dmz_nw) gws_ar = str(gws_nw) # Azure Virtual Network to which spokes will be peered # separate address spaces to simplify custom routing hub = vdc.virtual_network( name, [ props.firewall_address_space, props.hub_address_space, ], ) # Azure will deploy gateways into this subnet hub_gw_sn = vdc.subnet_special( stem=f'{name}-gw', name='GatewaySubnet', # name required virtual_network_name=hub.name, address_prefix=gws_ar, ) # A perimeter network for Internet-facing services hub_dmz_sn = vdc.subnet_special( #ToDo add NSG stem=f'{name}-dmz', name='DMZ', # name not required but preferred virtual_network_name=hub.name, address_prefix=dmz_ar, ) # Azure will deploy the firewall into this subnet hub_fw_sn = vdc.subnet_special( stem=f'{name}-fw', name='AzureFirewallSubnet', # name required virtual_network_name=hub.name, address_prefix=str(fws_nw), ) # Azure requires this subnet in case of forced_tunnel hub_fwm_sn = vdc.subnet_special( stem=f'{name}-fwm', name='AzureFirewallManagementSubnet', # name required virtual_network_name=hub.name, address_prefix=str(fwm_nw), ) # Gateways and Firewall depends_on special subnets # to avoid contention in the Azure control plane # Azure Firewall hub_fw = vdc.firewall( stem=name, fw_sn_id=hub_fw_sn.id, fwm_sn_id=hub_fwm_sn.id, depends_on=[hub_dmz_sn, hub_fw_sn, hub_fwm_sn, hub_gw_sn], ) # VPN Gateway hub_vpn_gw = vdc.vpn_gateway( stem=name, subnet_id=hub_gw_sn.id, depends_on=[hub_dmz_sn, hub_fw_sn, hub_fwm_sn, hub_gw_sn], ) # ExpressRoute Gateway hub_er_gw = vdc.expressroute_gateway( stem=name, subnet_id=hub_gw_sn.id, depends_on=[hub_dmz_sn, hub_fw_sn, hub_fwm_sn, hub_gw_sn], ) # Azure Bastion subnet and host (optional) if props.azure_bastion: hub_ab_sn = vdc.subnet_special( #ToDo add NSG if required stem=f'{name}-ab', name='AzureBastionSubnet', # name required virtual_network_name=hub.name, address_prefix=str(abs_nw), depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], # avoid contention ) hub_ab = vdc.bastion_host( stem=name, 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')) # Route Table only to be associated with GatewaySubnet hub_gw_rt = vdc.route_table( stem=f'{name}-gw', disable_bgp_route_propagation=False, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], # avoid contention ) hub_gw_sn_rta = vdc.subnet_route_table( stem=f'{name}-gw', route_table_id=hub_gw_rt.id, subnet_id=hub_gw_sn.id, ) # Route Table only to be associated with DMZ subnet hub_dmz_rt = vdc.route_table( stem=f'{name}-dmz', disable_bgp_route_propagation=True, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], # avoid contention ) hub_dmz_sn_rta = vdc.subnet_route_table( stem=f'{name}-dmz', route_table_id=hub_dmz_rt.id, subnet_id=hub_dmz_sn.id, ) # Route Table only to be associated with hub shared services subnets hub_ss_rt = vdc.route_table( stem=f'{name}-ss', disable_bgp_route_propagation=True, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], # avoid contention ) # protect intra-GatewaySubnet traffic from being redirected vdc.route_to_virtual_network( stem=f'gw-gw', route_table_name=hub_gw_rt.name, address_prefix=gws_ar, ) # it is very important to ensure that there is never a route with an # address_prefix which covers the AzureFirewallSubnet. # partially or fully invalidate system routes to redirect traffic for route in [ (f'gw-dmz', hub_gw_rt.name, dmz_ar), (f'gw-hub', hub_gw_rt.name, props.hub_address_space), (f'dmz-dg', hub_dmz_rt.name, '0.0.0.0/0'), (f'dmz-dmz', hub_dmz_rt.name, dmz_ar), (f'dmz-hub', hub_dmz_rt.name, props.hub_address_space), (f'ss-dg', hub_ss_rt.name, '0.0.0.0/0'), (f'ss-dmz', hub_ss_rt.name, dmz_ar), (f'ss-gw', hub_ss_rt.name, gws_ar), ]: vdc.route_to_virtual_appliance( stem=route[0], route_table_name=route[1], address_prefix=route[2], next_hop_in_ip_address=hub_fw_ip, ) # VNet Peering between stacks using StackReference if props.peer: peer_stack = StackReference(props.reference) peer_hub_id = peer_stack.get_output('hub_id') # VNet Peering (Global) in one direction from stack to peer hub_hub = vdc.vnet_peering( stem=props.stack, virtual_network_name=hub.name, peer=props.peer, remote_virtual_network_id=peer_hub_id, allow_forwarded_traffic=True, allow_gateway_transit=False, # as both hubs have gateways ) # 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') for route in [ (f'dmz-{props.peer}-dmz', hub_dmz_rt.name, peer_dmz_ar), (f'dmz-{props.peer}-hub', hub_dmz_rt.name, peer_hub_as), (f'gw-{props.peer}-dmz', hub_gw_rt.name, peer_dmz_ar), (f'gw-{props.peer}-hub', hub_gw_rt.name, peer_hub_as), (f'ss-{props.peer}-dmz', hub_ss_rt.name, peer_dmz_ar), (f'ss-{props.peer}-hub', hub_ss_rt.name, peer_hub_as), ]: vdc.route_to_virtual_appliance( stem=route[0], route_table_name=route[1], address_prefix=route[2], next_hop_in_ip_address=peer_fw_ip, ) # shared services subnets starting with the second subnet for subnet in props.subnets: next_sn = next(subnets) hub_sn = vdc.subnet( #ToDo add NSG stem=f'{name}-{subnet[0]}', virtual_network_name=hub.name, address_prefix=str(next_sn), depends_on=[hub_ss_rt], # avoid contention ) hub_sn_rta = vdc.subnet_route_table( stem=f'{name}-{subnet[0]}', route_table_id=hub_ss_rt.id, subnet_id=hub_sn.id, ) # assign properties to hub including from child resources self.address_spaces = hub.address_spaces # informational 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.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.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 self.tags = props.tags # informational self.vpn_gw = hub_vpn_gw # needed prior to VNet Peering from spokes self.register_outputs({})
def __init__(self, name: str, props: SpokeProps, opts: ResourceOptions=None): super().__init__('vdc:network:Spoke', name, {}, opts) # set required vdc variables before calling functions vdc.resource_group_name = props.resource_group_name vdc.tags = props.tags vdc.self = self # calculate the subnets in spoke_address_space spoke_nw = ip_network(props.spoke_address_space) if spoke_nw.prefixlen < 24: # split evenly between subnets and hosts sub_diff = int((spoke_nw.max_prefixlen - spoke_nw.prefixlen) / 2) else: sub_diff = 27 - spoke_nw.prefixlen # minimum /27 subnet subnets = spoke_nw.subnets(prefixlen_diff=sub_diff) next_sn = next(subnets) # first subnet reserved for special uses first_sn = next_sn.subnets(new_prefix=27) # subdivide if possible abs_nw = next(first_sn) # AzureBastionSubnet /27 or greater # Azure Virtual Network to be peered to the hub spoke = vdc.virtual_network(name, [props.spoke_address_space]) # VNet Peering from the hub to spoke hub_spoke = vdc.vnet_peering( stem = props.hub.stem, virtual_network_name = props.hub.name, peer = name, remote_virtual_network_id = spoke.id, allow_gateway_transit = True, depends_on=[props.hub.er_gw, props.hub.vpn_gw], # avoid contention ) # VNet Peering from spoke to the hub spoke_hub = vdc.vnet_peering( stem = name, virtual_network_name = spoke.name, peer = props.hub.stem, remote_virtual_network_id = props.hub.id, allow_forwarded_traffic = True, use_remote_gateways = True, # requires at least one gateway 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( stem = f'{name}-ab', name = 'AzureBastionSubnet', virtual_network_name = spoke.name, address_prefix = str(abs_nw), depends_on = [hub_spoke, spoke_hub], # avoid contention ) spoke_ab = vdc.bastion_host( stem = name, subnet_id = spoke_ab_sn.id, ) # Route Table only to be associated with ordinary spoke subnets spoke_rt = vdc.route_table( stem = f'{name}', disable_bgp_route_propagation = True, depends_on = [hub_spoke, spoke_hub], # avoid contention ) # VNet Peering may not be specified as next_hop_type, so a separate # hub address space from the firewall is necessary to allow routes # from spokes to remain unchanged when hub subnets are added # it is very important to ensure that there is never a route with an # address_prefix which covers the AzureFirewallSubnet. # partially or fully invalidate system routes to redirect traffic for route in [ (f'dmz-{name}', props.hub.dmz_rt_name, props.spoke_address_space), (f'gw-{name}', props.hub.gw_rt_name, props.spoke_address_space), (f'ss-{name}', props.hub.ss_rt_name, props.spoke_address_space), (f'{name}-dg', spoke_rt.name, '0.0.0.0/0'), (f'{name}-dmz', spoke_rt.name, props.hub.dmz_ar), (f'{name}-hub', spoke_rt.name, props.hub.hub_as), ]: vdc.route_to_virtual_appliance( stem = route[0], route_table_name = route[1], address_prefix = route[2], next_hop_in_ip_address = props.hub.fw_ip, ) # ordinary spoke subnets starting with the second subnet for subnet in props.subnets: next_sn = next(subnets) spoke_sn = vdc.subnet( stem = f'{name}-{subnet[0]}', virtual_network_name = spoke.name, address_prefix = str(next_sn), depends_on = [spoke_rt], # avoid contention ) spoke_sn_rta = vdc.subnet_route_table( stem = f'{name}-{subnet[0]}', route_table_id = spoke_rt.id, subnet_id = spoke_sn.id, ) # assign properties to spoke including from child resources 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 self.stem = name self.tags = props.tags self.register_outputs({})
def __init__(self, name: str, props: SpokeProps, opts: ResourceOptions = None): super().__init__('vdc:network:Spoke', name, {}, opts) # set vdc defaults vdc.resource_group_name = props.resource_group_name vdc.tags = props.tags vdc.self = self # Azure Virtual Network to be peered to the hub spoke = vdc.virtual_network(name, [props.spoke_as]) # VNet Peering from the hub to spoke hub_spoke = vdc.vnet_peering( stem=props.hub.stem, virtual_network_name=props.hub.name, peer=name, remote_virtual_network_id=spoke.id, allow_gateway_transit=True, depends_on=[props.hub.er_gw, props.hub.vpn_gw], # avoid contention ) # VNet Peering from spoke to the hub spoke_hub = vdc.vnet_peering( stem=name, virtual_network_name=spoke.name, peer=props.hub.stem, remote_virtual_network_id=props.hub.id, allow_forwarded_traffic=True, use_remote_gateways=True, # requires at least one gateway depends_on=[props.hub.er_gw, props.hub.vpn_gw], ) # provisioning of optional subnet and routes depends_on VNet Peerings # to avoid contention in the Azure control plane # AzureBastionSubnet (optional) if props.sbs_ar: spoke_sbs_sn = vdc.subnet_special( stem=f'{name}-ab', name='AzureBastionSubnet', virtual_network_name=spoke.name, address_prefix=props.sbs_ar, depends_on=[hub_spoke, spoke_hub], ) # Route Table only to be associated with ordinary spoke subnets spoke_rt = vdc.route_table( stem=f'{name}', disable_bgp_route_propagation=True, depends_on=[hub_spoke, spoke_hub], ) # as VNet Peering may not be specified as next_hop_type, a separate # address space in the hub from the firewall allows routes from the # spoke to remain unchanged when subnets are added in the hub # it is very important to ensure that there is never a route with an # address_prefix which covers the AzureFirewallSubnet. #ToDo check AzureFirewallManagementSubnet requirements # partially or fully invalidate system routes to redirect traffic for route in [ (f'dmz-{name}', props.hub.dmz_rt_name, props.spoke_as), (f'gw-{name}', props.hub.gw_rt_name, props.spoke_as), (f'ss-{name}', props.hub.ss_rt_name, props.spoke_as), (f'{name}-dg', spoke_rt.name, '0.0.0.0/0'), (f'{name}-dmz', spoke_rt.name, props.hub.dmz_ar), (f'{name}-hub', spoke_rt.name, props.hub.hub_as), ]: vdc.route_to_virtual_appliance( stem=route[0], route_table_name=route[1], address_prefix=route[2], next_hop_in_ip_address=props.hub.fw_ip, ) # provisioning of subnets depends_on Route Table (VNet Peerings) # to avoid contention in the Azure control plane # ordinary spoke subnets subnet_range = props.spoke_ar for subnet in props.subnets: spoke_sn = vdc.subnet( stem=f'{name}-{subnet[0]}', virtual_network_name=spoke.name, address_prefix=subnet_range, depends_on=[spoke_rt], ) # associate all ordinary spoke subnets to Route Table spoke_sn_rta = vdc.subnet_route_table( stem=f'{name}-{subnet[0]}', route_table_id=spoke_rt.id, subnet_id=spoke_sn.id, ) subnet_range = vdc.subnet_next(props.spoke_as, subnet_range) # assign properties to spoke including from child resources self.address_spaces = spoke.address_spaces self.hub = props.hub.id self.id = spoke.id self.location = spoke.location self.name = spoke.name self.resource_group_name = props.resource_group_name self.subnets = spoke.subnets self.stem = name self.tags = props.tags self.register_outputs({})
def __init__(self, name: str, props: HubProps, opts: ResourceOptions=None): super().__init__('vdc:network:Hub', name, {}, opts) # retrieve configuration dmz_ar = props.config.require('dmz_ar') fwm_ar = props.config.get('fwm_ar') fws_ar = props.config.require('fws_ar') fwz_as = props.config.require('fwz_as') gws_ar = props.config.require('gws_ar') hbs_ar = props.config.get('hbs_ar') hub_ar = props.config.get('hub_ar') hub_as = props.config.require('hub_as') # set vdc defaults vdc.resource_group_name = props.resource_group.name vdc.location = props.resource_group.location vdc.tags = props.tags vdc.self = self # Azure Virtual Network to which spokes will be peered # separate address spaces to simplify custom routing hub = vdc.virtual_network(name, [fwz_as, hub_as]) # DMZ subnet hub_dmz_sn = vdc.subnet_special( #ToDo add NSG stem = f'{name}-dmz', name = 'DMZ', # name not required but preferred virtual_network_name = hub.name, address_prefix = dmz_ar, ) # AzureFirewallSubnet hub_fw_sn = vdc.subnet_special( stem = f'{name}-fw', name = 'AzureFirewallSubnet', # name required virtual_network_name = hub.name, address_prefix = fws_ar, ) # GatewaySubnet hub_gw_sn = vdc.subnet_special( stem = f'{name}-gw', name = 'GatewaySubnet', # name required virtual_network_name = hub.name, address_prefix = gws_ar, ) # provisioning of Gateways and Firewall depends_on subnets # to avoid contention in the Azure control plane # VPN Gateway hub_vpn_gw = vdc.vpn_gateway( stem = name, subnet_id = hub_gw_sn.id, depends_on=[hub_dmz_sn, hub_fw_sn, hub_gw_sn], ) # ExpressRoute Gateway hub_er_gw = vdc.expressroute_gateway( stem = name, subnet_id = hub_gw_sn.id, depends_on=[hub_dmz_sn, hub_fw_sn, hub_gw_sn], ) # Azure Firewall hub_fw = vdc.firewall( stem = name, subnet_id = hub_fw_sn.id, depends_on=[hub_dmz_sn, hub_fw_sn, hub_gw_sn], ) # provisioning of optional subnets depends_on Gateways and Firewall # to avoid contention in the Azure control plane # AzureBastionSubnet (optional) if hbs_ar: hub_ab_sn = vdc.subnet_special( #ToDo add NSG if required stem = f'{name}-ab', name = 'AzureBastionSubnet', # name required virtual_network_name = hub.name, address_prefix = hbs_ar, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # AzureFirewallManagementSubnet (optional) if fwm_ar: hub_fwm_sn = vdc.subnet_special( stem = f'{name}-fwm', name = 'AzureFirewallManagementSubnet', # name required virtual_network_name = hub.name, address_prefix = fwm_ar, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # 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') ) # provisioning of Route Tables depends_on Gateways and Firewall # to avoid contention in the Azure control plane # Route Table only to be associated with the GatewaySubnet hub_gw_rt = vdc.route_table( stem = f'{name}-gw', disable_bgp_route_propagation = False, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # associate GatewaySubnet with Route Table hub_gw_sn_rta = vdc.subnet_route_table( stem = f'{name}-gw', route_table_id = hub_gw_rt.id, subnet_id = hub_gw_sn.id, ) # Route Table only to be associated with DMZ subnet hub_dmz_rt = vdc.route_table( stem = f'{name}-dmz', disable_bgp_route_propagation = True, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # associate DMZ subnet with Route Table hub_dmz_sn_rta = vdc.subnet_route_table( stem = f'{name}-dmz', route_table_id = hub_dmz_rt.id, subnet_id = hub_dmz_sn.id, ) # Route Table only to be associated with ordinary subnets in hub hub_sn_rt = vdc.route_table( stem = f'{name}-sn', disable_bgp_route_propagation = True, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # protect intra-GatewaySubnet traffic from being redirected vdc.route_to_virtual_network( stem = f'gw-gw', route_table_name = hub_gw_rt.name, address_prefix = gws_ar, ) # partially or fully invalidate system routes to redirect traffic for route in [ (f'gw-dmz', hub_gw_rt.name, dmz_ar), (f'gw-hub', hub_gw_rt.name, hub_as), (f'dmz-dg', hub_dmz_rt.name, '0.0.0.0/0'), (f'dmz-dmz', hub_dmz_rt.name, dmz_ar), (f'dmz-hub', hub_dmz_rt.name, hub_as), (f'sn-dg', hub_sn_rt.name, '0.0.0.0/0'), (f'sn-dmz', hub_sn_rt.name, dmz_ar), (f'sn-gw', hub_sn_rt.name, gws_ar), ]: vdc.route_to_virtual_appliance( stem = route[0], route_table_name = route[1], address_prefix = route[2], next_hop_in_ip_address = hub_fw_ip, ) # VNet Peering between stacks using StackReference peer = props.config.get('peer') if peer: org = props.config.require('org') project = get_project() peer_stack = StackReference(f'{org}/{project}/{peer}') peer_hub_id = peer_stack.get_output('hub_id') peer_fw_ip = peer_stack.get_output('hub_fw_ip') peer_dmz_ar = peer_stack.get_output('dmz_ar') peer_hub_as = peer_stack.get_output('hub_as') # VNet Peering (Global) in one direction from stack to peer hub_hub = vdc.vnet_peering( stem = props.stack, virtual_network_name = hub.name, peer = peer, remote_virtual_network_id = peer_hub_id, allow_forwarded_traffic = True, allow_gateway_transit = False, # as both hubs have gateways ) # need to invalidate system routes created by Global VNet Peering for route in [ (f'dmz-{peer}-dmz', hub_dmz_rt.name, peer_dmz_ar), (f'dmz-{peer}-hub', hub_dmz_rt.name, peer_hub_as), (f'gw-{peer}-dmz', hub_gw_rt.name, peer_dmz_ar), (f'gw-{peer}-hub', hub_gw_rt.name, peer_hub_as), (f'sn-{peer}-dmz', hub_sn_rt.name, peer_dmz_ar), (f'sn-{peer}-hub', hub_sn_rt.name, peer_hub_as), ]: vdc.route_to_virtual_appliance( stem = route[0], route_table_name = route[1], address_prefix = route[2], next_hop_in_ip_address = peer_fw_ip, ) # provisioning of subnets depends_on Route Table (Gateways & Firewall) # to avoid contention in the Azure control plane # only one shared subnet is provisioned as an example, but many can be if hub_ar: #ToDo replace with loop hub_example_sn = vdc.subnet( #ToDo add NSG stem = f'{name}-example', virtual_network_name = hub.name, address_prefix = hub_ar, depends_on=[hub_sn_rt], ) # associate all hub shared services subnets to Route Table hub_example_sn_rta = vdc.subnet_route_table( stem = f'{name}-example', route_table_id = hub_sn_rt.id, subnet_id = hub_example_sn.id, ) combined_output = Output.all( hub_dmz_rt.name, hub_er_gw, hub_fw, hub_fw_ip, hub_gw_rt.name, hub.id, hub.name, hub_sn_rt.name, hub.subnets, hub_vpn_gw, ).apply self.hub_dmz_rt_name = hub_dmz_rt.name # used to add routes to spokes self.hub_er_gw = hub_er_gw # needed prior to VNet Peering from spokes self.hub_fw = hub_fw # needed prior to VNet Peering from spokes self.hub_fw_ip = hub_fw_ip # used to construct routes self.hub_gw_rt_name = hub_gw_rt.name # used to add routes to spokes self.hub_id = hub.id # exported and used for peering self.hub_name = hub.name # exported and used for peering self.hub_sn_rt_name = hub_sn_rt.name # used to add routes to spokes self.hub_subnets = hub.subnets # exported as informational self.hub_vpn_gw = hub_vpn_gw # needed prior to VNet Peering from spokes self.register_outputs({})
def __init__(self, name: str, props: SpokeProps, opts: ResourceOptions = None): super().__init__('vdc:network:Spoke', name, {}, opts) # retrieve configuration dmz_ar = props.config.require('dmz_ar') hub_as = props.config.require('hub_as') hub_stem = props.config.require('hub_stem') sbs_ar = props.config.get('sbs_ar') spoke_ar = props.config.get('spoke_ar') spoke_as = props.config.require('spoke_as') # set vdc defaults vdc.resource_group_name = props.resource_group.name vdc.location = props.resource_group.location vdc.tags = props.tags vdc.self = self # Azure Virtual Network to be peered to the hub spoke = vdc.virtual_network(name, [spoke_as]) # VNet Peering from the hub to spoke hub_spoke = vdc.vnet_peering( stem=hub_stem, virtual_network_name=props.hub.hub_name, peer=name, remote_virtual_network_id=spoke.id, allow_gateway_transit=True, ) # VNet Peering from spoke to the hub spoke_hub = vdc.vnet_peering( stem=name, virtual_network_name=spoke.name, peer=hub_stem, remote_virtual_network_id=props.hub.hub_id, allow_forwarded_traffic=True, use_remote_gateways=True, ) # provisioning of optional subnet and routes depends_on VNet Peerings # to avoid contention in the Azure control plane # AzureBastionSubnet (optional) if sbs_ar: spoke_sbs_sn = vdc.subnet_special( stem=f'{name}-ab', name='AzureBastionSubnet', virtual_network_name=spoke.name, address_prefix=sbs_ar, depends_on=[hub_spoke, spoke_hub], ) # Route Table only to be associated with ordinary spoke subnets spoke_sn_rt = vdc.route_table( stem=f'{name}-sn', disable_bgp_route_propagation=True, depends_on=[hub_spoke, spoke_hub], ) # provisioning of subnets depends_on VNet Peerings and Route Table # to avoid contention in the Azure control plane # only one spoke subnet is provisioned as an example, but many can be if spoke_ar: # replace with a loop spoke_example_sn = vdc.subnet( stem=f'{name}-example', virtual_network_name=spoke.name, address_prefix=spoke_ar, depends_on=[spoke_sn_rt], ) # associate all ordinary spoke subnets to Route Table spoke_example_sn_rta = vdc.subnet_route_table( stem=f'{name}-example', route_table_id=spoke_sn_rt.id, subnet_id=spoke_example_sn.id, ) # as VNet Peering may not be specified as next_hop_type, a separate # address space in the hub from the firewall allows routes from the # spoke to remain unchanged when subnets are added in the hub # it is very important to ensure that there is never a route with an # address_prefix which covers the AzureFirewallSubnet. #ToDo check AzureFirewallManagementSubnet requirements # partially or fully invalidate system routes to redirect traffic for route in [ (f'dmz-{name}', props.hub.hub_dmz_rt_name, spoke_as), (f'gw-{name}', props.hub.hub_gw_rt_name, spoke_as), (f'sn-{name}', props.hub.hub_sn_rt_name, spoke_as), (f'{name}-dg', spoke_sn_rt.name, '0.0.0.0/0'), (f'{name}-dmz', spoke_sn_rt.name, dmz_ar), (f'{name}-hub', spoke_sn_rt.name, hub_as), ]: vdc.route_to_virtual_appliance( stem=route[0], route_table_name=route[1], address_prefix=route[2], next_hop_in_ip_address=props.hub.hub_fw_ip, ) combined_output = Output.all(spoke.name, spoke.id, spoke.subnets).apply self.spoke_id = spoke.id # exported as informational self.spoke_name = spoke.name # exported as informational self.spoke_subnets = spoke.subnets # exported as informational self.register_outputs({})
def __init__(self, name: str, props: HubProps, opts: ResourceOptions = None): super().__init__('vdc:network:Hub', name, {}, opts) # set required vdc variables before calling functions vdc.location = props.location vdc.resource_group_name = props.resource_group_name vdc.s = props.separator vdc.self = self vdc.suffix = props.suffix vdc.tags = props.tags # calculate the subnets in the firewall_address_space fwz_nw = ip_network(props.firewall_address_space) fwz_sn = fwz_nw.subnets(new_prefix=25) # two /26 subnets required fwx_nw = next(fwz_sn) # for Azure Firewall and Management subnets fwz_sn = fwz_nw.address_exclude(fwx_nw) # consolidate remainder dmz_nw = next(fwz_sn) # largest remaining subnet for DMZ fwx_sn = fwx_nw.subnets(new_prefix=26) # split the /25 into two /26 fws_nw = next(fwx_sn) # AzureFirewallSubnet fwm_nw = next(fwx_sn) # AzureFirewallManagementSubnet # calculate the subnets in the hub_address_space hub_nw = ip_network(props.hub_address_space) if hub_nw.prefixlen < 20: # split evenly between subnets and hosts sub_diff = int((hub_nw.max_prefixlen - hub_nw.prefixlen) / 2) else: sub_diff = 25 - hub_nw.prefixlen # minimum /25 subnet subnets = hub_nw.subnets(prefixlen_diff=sub_diff) next_sn = next(subnets) # first subnet reserved for special uses first_sn = next_sn.subnets(new_prefix=26) # split it into /26 subnets gws_nw = next(first_sn) # GatewaySubnet /26 rem_nw = next(first_sn) # at least one more /26 subnet, perhaps more rem_sn = rem_nw.subnets(new_prefix=27) # only need /27 save the rest abs_nw = next(rem_sn) # AzureBastionSubnet /27 or greater # cast repeatedly referenced networks to strings dmz_ar = str(dmz_nw) gws_ar = str(gws_nw) # set the separator to be used in resource names s = props.separator # Azure Virtual Network to which spokes will be peered # separate address spaces to simplify custom routing hub = vdc.virtual_network(name, [ props.firewall_address_space, props.hub_address_space, ], ) # AzureFirewallManagementSubnet and Route Table # https://docs.microsoft.com/en-us/azure/firewall/forced-tunneling hub_fwm_rt = vdc.route_table( stem=f'{name}{s}fwm', disable_bgp_route_propagation=True, # required ) # only a default route to the Internet is permitted hub_fwm_dg = vdc.route_to_internet( stem=f'fwm{s}internet', route_table_name=hub_fwm_rt.name, ) hub_fwm_sn = vdc.subnet_special( stem=f'{name}{s}fwm', name='AzureFirewallManagementSubnet', # name required virtual_network_name=hub.name, address_prefix=str(fwm_nw), route_table_id=hub_fwm_rt.id, depends_on=[hub, hub_fwm_rt, hub_fwm_dg], ) # AzureFirewallSubnet and Route Table hub_fw_rt = vdc.route_table( stem=f'{name}{s}fw', disable_bgp_route_propagation=False, ) # default route either direct to Internet or forced tunnel # turn off SNAT if the next_hop_ip_address is public # https://docs.microsoft.com/en-us/azure/firewall/snat-private-range private_ranges = 'IANAPrivateRanges' if not props.forced_tunnel: hub_fw_dg = vdc.route_to_internet( stem=f'fw{s}internet', route_table_name=hub_fw_rt.name, ) else: hub_fw_dg = vdc.route_to_virtual_appliance( stem=f'fw{s}tunnel', route_table_name=hub_fw_rt.name, address_prefix='0.0.0.0/0', next_hop_ip_address=props.forced_tunnel, ) ft_ip = ip_address(props.forced_tunnel) if not ft_ip.is_private: private_ranges = '0.0.0.0/0' hub_fw_sn = vdc.subnet_special( stem=f'{name}{s}fw', name='AzureFirewallSubnet', # name required virtual_network_name=hub.name, address_prefix=str(fws_nw), route_table_id=hub_fw_rt.id, depends_on=[hub, hub_fw_rt, hub_fw_dg], ) # Azure Firewall hub_fw = vdc.firewall( stem=name, fw_sn_id=hub_fw_sn.id, fwm_sn_id=hub_fwm_sn.id, private_ranges=private_ranges, depends_on=[hub_fw_sn, hub_fwm_sn], ) # wait for the private ip address of the firewall to become available hub_fw_ip = hub_fw.ip_configurations.apply( lambda ipc: ipc[0].private_ip_address ) # It is very important to ensure that there is never a route with an # address_prefix which covers the AzureFirewallSubnet. # DMZ subnet and Route Table hub_dmz_rt = vdc.route_table( stem=f'{name}{s}dmz', disable_bgp_route_propagation=True, depends_on=[hub_fw], ) # default route from DMZ via the firewall hub_dmz_dg = vdc.route_to_virtual_appliance( stem=f'dmz{s}dg', route_table_name=hub_dmz_rt.name, address_prefix='0.0.0.0/0', next_hop_ip_address=hub_fw_ip, ) # redirect intra-DMZ traffic via the firewall hub_dmz_dmz = vdc.route_to_virtual_appliance( stem=f'dmz{s}dmz', route_table_name=hub_dmz_rt.name, address_prefix=dmz_ar, next_hop_ip_address=hub_fw_ip, ) # redirect traffic from DMZ to hub via the firewall hub_dmz_hub = vdc.route_to_virtual_appliance( stem=f'dmz{s}hub', route_table_name=hub_dmz_rt.name, address_prefix=props.hub_address_space, next_hop_ip_address=hub_fw_ip, ) hub_dmz_sn = vdc.subnet_special( # ToDo add NSG stem=f'{name}{s}dmz', name='DMZ', # name not required but preferred virtual_network_name=hub.name, address_prefix=dmz_ar, route_table_id=hub_dmz_rt.id, depends_on=[hub_dmz_rt, hub_dmz_dg, hub_dmz_dmz, hub_dmz_hub], ) # GatewaySubnet and Route Table hub_gw_rt = vdc.route_table( stem=f'{name}{s}gw', disable_bgp_route_propagation=False, depends_on=[hub_dmz_sn], ) # protect intra-GatewaySubnet traffic from being redirected: hub_gw_gw = vdc.route_to_virtual_network( stem=f'gw{s}gw', route_table_name=hub_gw_rt.name, address_prefix=gws_ar, ) # redirect traffic from gateways to DMZ via firewall hub_gw_dmz = vdc.route_to_virtual_appliance( stem=f'gw{s}dmz', route_table_name=hub_gw_rt.name, address_prefix=dmz_ar, next_hop_ip_address=hub_fw_ip, ) # redirect traffic from gateways to hub via firewall hub_gw_hub = vdc.route_to_virtual_appliance( stem=f'gw{s}hub', route_table_name=hub_gw_rt.name, address_prefix=props.hub_address_space, next_hop_ip_address=hub_fw_ip, ) hub_gw_sn = vdc.subnet_special( stem=f'{name}{s}gw', name='GatewaySubnet', # name required virtual_network_name=hub.name, address_prefix=gws_ar, route_table_id=hub_gw_rt.id, depends_on=[hub_gw_rt, hub_gw_gw, hub_gw_dmz, hub_gw_hub], ) # VPN Gateway hub_vpn_gw = vdc.vpn_gateway( stem=name, subnet_id=hub_gw_sn.id, depends_on=[hub_gw_sn], ) # ExpressRoute Gateway hub_er_gw = vdc.expressroute_gateway( stem=name, subnet_id=hub_gw_sn.id, depends_on=[hub_gw_sn], ) # Route Table to be associated with all hub shared services subnets hub_ss_rt = vdc.route_table( stem=f'{name}{s}ss', disable_bgp_route_propagation=True, depends_on=[hub_er_gw, hub_vpn_gw], ) # default route from hub via the firewall hub_ss_dg = vdc.route_to_virtual_appliance( stem=f'ss{s}dg', route_table_name=hub_ss_rt.name, address_prefix='0.0.0.0/0', next_hop_ip_address=hub_fw_ip, ) # redirect traffic from hub to DMZ via the firewall hub_ss_dmz = vdc.route_to_virtual_appliance( stem=f'ss{s}dmz', route_table_name=hub_ss_rt.name, address_prefix=dmz_ar, next_hop_ip_address=hub_fw_ip, ) # redirect traffic from hub to gateways via the firewall hub_ss_gw = vdc.route_to_virtual_appliance( stem=f'ss{s}gw', route_table_name=hub_ss_rt.name, address_prefix=gws_ar, next_hop_ip_address=hub_fw_ip, ) # shared services subnets starting with the second subnet for subnet in props.subnets: next_sn = next(subnets) hub_sn = vdc.subnet( # ToDo add NSG stem=f'{name}{s}{subnet[0]}', virtual_network_name=hub.name, address_prefix=str(next_sn), route_table_id=hub_ss_rt.id, depends_on=[hub_ss_rt, hub_ss_dg, hub_ss_dmz, hub_ss_gw], ) # Azure Bastion subnet and host (optional) if props.azure_bastion: hub_ab = vdc.bastion_host( stem=name, virtual_network_name=hub.name, address_prefix=str(abs_nw), depends_on=[hub_er_gw, hub_vpn_gw], ) # VNet Peering between stacks using StackReference (optional) if props.peer: peer_hub_id = props.reference.get_output('hub_id') # VNet Peering (Global) in one direction from stack to peer hub_hub = vdc.vnet_peering( stem=props.stack, virtual_network_name=hub.name, peer=props.peer, remote_virtual_network_id=peer_hub_id, allow_forwarded_traffic=True, allow_gateway_transit=False, # as both hubs have gateways ) # need to invalidate system routes created by VNet Peering 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{s}{props.peer}{s}dmz', hub_dmz_rt.name, peer_dmz_ar), (f'dmz{s}{props.peer}{s}hub', hub_dmz_rt.name, peer_hub_as), (f'gw{s}{props.peer}{s}dmz', hub_gw_rt.name, peer_dmz_ar), (f'gw{s}{props.peer}{s}hub', hub_gw_rt.name, peer_hub_as), (f'ss{s}{props.peer}{s}dmz', hub_ss_rt.name, peer_dmz_ar), (f'ss{s}{props.peer}{s}hub', hub_ss_rt.name, peer_hub_as), ]: vdc.route_to_virtual_appliance( stem=route[0], route_table_name=route[1], address_prefix=route[2], next_hop_ip_address=peer_fw_ip, ) # assign properties to hub including from child resources self.address_space = props.hub_address_space # used for routes to the hub 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.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 # 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 self.tags = props.tags # informational self.vpn_gw = hub_vpn_gw # needed prior to VNet Peering from spokes self.register_outputs({})
def __init__(self, name: str, props: HubProps, opts: ResourceOptions = None): super().__init__('vdc:network:Hub', name, {}, opts) # set vdc defaults vdc.resource_group_name = props.resource_group_name vdc.tags = props.tags vdc.self = self # Azure Virtual Network to which spokes will be peered # separate address spaces to simplify custom routing hub = vdc.virtual_network(name, [props.fwz_as, props.hub_as]) # DMZ subnet hub_dmz_sn = vdc.subnet_special( #ToDo add NSG stem=f'{name}-dmz', name='DMZ', # name not required but preferred virtual_network_name=hub.name, address_prefix=props.dmz_ar, ) # AzureFirewallSubnet hub_fw_sn = vdc.subnet_special( stem=f'{name}-fw', name='AzureFirewallSubnet', # name required virtual_network_name=hub.name, address_prefix=props.fws_ar, ) # GatewaySubnet hub_gw_sn = vdc.subnet_special( stem=f'{name}-gw', name='GatewaySubnet', # name required virtual_network_name=hub.name, address_prefix=props.gws_ar, ) # provisioning of Gateways and Firewall depends_on subnets # to avoid contention in the Azure control plane # VPN Gateway hub_vpn_gw = vdc.vpn_gateway( stem=name, subnet_id=hub_gw_sn.id, depends_on=[hub_dmz_sn, hub_fw_sn, hub_gw_sn], ) # ExpressRoute Gateway hub_er_gw = vdc.expressroute_gateway( stem=name, subnet_id=hub_gw_sn.id, depends_on=[hub_dmz_sn, hub_fw_sn, hub_gw_sn], ) # Azure Firewall hub_fw = vdc.firewall( stem=name, subnet_id=hub_fw_sn.id, depends_on=[hub_dmz_sn, hub_fw_sn, hub_gw_sn], ) # provisioning of optional subnets depends_on Gateways and Firewall # to avoid contention in the Azure control plane # AzureBastionSubnet (optional) if props.hbs_ar: hub_ab_sn = vdc.subnet_special( #ToDo add NSG if required stem=f'{name}-ab', name='AzureBastionSubnet', # name required virtual_network_name=hub.name, address_prefix=props.hbs_ar, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # AzureFirewallManagementSubnet (optional) if props.fwm_ar: hub_fwm_sn = vdc.subnet_special( stem=f'{name}-fwm', name='AzureFirewallManagementSubnet', # name required virtual_network_name=hub.name, address_prefix=props.fwm_ar, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # 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')) # provisioning of Route Tables depends_on Gateways and Firewall # to avoid contention in the Azure control plane # Route Table only to be associated with the GatewaySubnet hub_gw_rt = vdc.route_table( stem=f'{name}-gw', disable_bgp_route_propagation=False, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # associate GatewaySubnet with Route Table hub_gw_sn_rta = vdc.subnet_route_table( stem=f'{name}-gw', route_table_id=hub_gw_rt.id, subnet_id=hub_gw_sn.id, ) # Route Table only to be associated with DMZ subnet hub_dmz_rt = vdc.route_table( stem=f'{name}-dmz', disable_bgp_route_propagation=True, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # associate DMZ subnet with Route Table hub_dmz_sn_rta = vdc.subnet_route_table( stem=f'{name}-dmz', route_table_id=hub_dmz_rt.id, subnet_id=hub_dmz_sn.id, ) # Route Table only to be associated with shared services subnets in hub hub_ss_rt = vdc.route_table( stem=f'{name}-ss', disable_bgp_route_propagation=True, depends_on=[hub_er_gw, hub_fw, hub_vpn_gw], ) # protect intra-GatewaySubnet traffic from being redirected vdc.route_to_virtual_network( stem=f'gw-gw', route_table_name=hub_gw_rt.name, address_prefix=props.gws_ar, ) # partially or fully invalidate system routes to redirect traffic for route in [ (f'gw-dmz', hub_gw_rt.name, props.dmz_ar), (f'gw-hub', hub_gw_rt.name, props.hub_as), (f'dmz-dg', hub_dmz_rt.name, '0.0.0.0/0'), (f'dmz-dmz', hub_dmz_rt.name, props.dmz_ar), (f'dmz-hub', hub_dmz_rt.name, props.hub_as), (f'ss-dg', hub_ss_rt.name, '0.0.0.0/0'), (f'ss-dmz', hub_ss_rt.name, props.dmz_ar), (f'ss-gw', hub_ss_rt.name, props.gws_ar), ]: vdc.route_to_virtual_appliance( stem=route[0], route_table_name=route[1], address_prefix=route[2], next_hop_in_ip_address=hub_fw_ip, ) # VNet Peering between stacks using StackReference if props.peer: peer_stack = StackReference(props.ref) peer_hub_id = peer_stack.get_output('hub_id') # VNet Peering (Global) in one direction from stack to peer hub_hub = vdc.vnet_peering( stem=props.stack, virtual_network_name=hub.name, peer=props.peer, remote_virtual_network_id=peer_hub_id, allow_forwarded_traffic=True, allow_gateway_transit=False, # as both hubs have gateways ) # 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') for route in [ (f'dmz-{props.peer}-dmz', hub_dmz_rt.name, peer_dmz_ar), (f'dmz-{props.peer}-hub', hub_dmz_rt.name, peer_hub_as), (f'gw-{props.peer}-dmz', hub_gw_rt.name, peer_dmz_ar), (f'gw-{props.peer}-hub', hub_gw_rt.name, peer_hub_as), (f'ss-{props.peer}-dmz', hub_ss_rt.name, peer_dmz_ar), (f'ss-{props.peer}-hub', hub_ss_rt.name, peer_hub_as), ]: vdc.route_to_virtual_appliance( stem=route[0], route_table_name=route[1], address_prefix=route[2], next_hop_in_ip_address=peer_fw_ip, ) # provisioning of subnets depends_on Route Table (Gateways & Firewall) # to avoid contention in the Azure control plane # shared services subnets subnet_range = props.hub_ar for subnet in props.subnets: hub_sn = vdc.subnet( #ToDo add NSG stem=f'{name}-{subnet[0]}', virtual_network_name=hub.name, address_prefix=subnet_range, depends_on=[hub_ss_rt], ) # associate all hub shared services subnets to Route Table hub_sn_rta = vdc.subnet_route_table( stem=f'{name}-{subnet[0]}', route_table_id=hub_ss_rt.id, subnet_id=hub_sn.id, ) subnet_range = vdc.subnet_next(props.hub_as, subnet_range) # assign properties to hub including from child resources self.address_spaces = hub.address_spaces # informational self.dmz_ar = props.dmz_ar # used to construct 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 to construct routes to the hub self.gw_rt_name = hub_gw_rt.name # used to add routes to spokes self.hub_as = props.hub_as # used to construct 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.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 self.tags = props.tags # informational self.vpn_gw = hub_vpn_gw # needed prior to VNet Peering from spokes self.register_outputs({})