Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add firewall to Pulumi #1375

Merged
Merged
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
8196002
:sparkles: Add firewall to SHM
jemrobinson Nov 25, 2022
4123b45
:sparkles: Add SHM route table
jemrobinson Jan 27, 2023
6b8b1a0
:closed_lock_with_key: Add domain admin password to backend keyvault
jemrobinson Feb 1, 2023
1f8fe3e
:sparkles: Add route table to direct traffic through the firewall. Ad…
jemrobinson Feb 1, 2023
b6a53ad
:bug: Allow route table to ignore added routes, preventing them from …
jemrobinson Feb 2, 2023
f80f22a
:rotating_light: Mark ResourceOptions as Optional
jemrobinson Feb 3, 2023
f98594c
:sparkles: Add NAT rule to RDP to domain controller via firewall
jemrobinson Feb 3, 2023
bfdeaec
:sparkles: Add subnet functionality to AzureIPv4Range
jemrobinson Feb 6, 2023
7e56241
:rotating_light: Fix typing issues
jemrobinson Feb 6, 2023
aec7ed8
:recycle: Move subnet definitions out of SHM virtual network
jemrobinson Feb 6, 2023
9157820
:rotating_light: Fix typing
jemrobinson Feb 15, 2023
f6b0950
:recycle: Standardise subnet handling across SHM and SRE
jemrobinson Feb 15, 2023
e173382
:bug: Allow PowershellDSC to reboot the VM during configuration
jemrobinson Feb 15, 2023
47ab234
:sparkles: Add public DNS address for domain controller (to be replac…
jemrobinson Feb 15, 2023
fdf35e6
:bug: Fix secret overwriting when value is unchanged
jemrobinson Feb 15, 2023
fe1a9b5
:recycle: Refactor backend resource names
jemrobinson Feb 15, 2023
904c58e
:wrench: Allow raw.githubusercontent.com from domain controller for s…
jemrobinson Feb 15, 2023
32ee60a
:bug: Ensure that SHM DNS zone is delegated to SRE zone before trying…
jemrobinson Feb 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
♻️ Move subnet definitions out of SHM virtual network
  • Loading branch information
jemrobinson committed Feb 15, 2023

Verified

This commit was signed with the committer’s verified signature.
renovate-bot Mend Renovate
commit aec7ed8d9953f2495228c3d5d7b142833fd7e1c9
30 changes: 15 additions & 15 deletions data_safe_haven/pulumi/components/shm_domain_controllers.py
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ def __init__(
password_domain_searcher: Input[str],
public_ip_range_admins: Input[Sequence[str]],
private_ip_address: Input[str],
subnet_name: Input[str],
subnet_identity: Input[network.Subnet],
subscription_name: Input[str],
virtual_network_name: Input[str],
virtual_network_resource_group_name: Input[str],
@@ -61,7 +61,7 @@ def __init__(
self.password_domain_searcher = password_domain_searcher
self.public_ip_range_admins = public_ip_range_admins
self.private_ip_address = private_ip_address
self.subnet_name = subnet_name
self.subnet_name = Output.from_input(subnet_identity).apply(lambda s: s.name)
self.subscription_name = subscription_name
# Note that usernames have a maximum of 20 characters
self.username_domain_admin = "dshdomainadmin"
@@ -130,19 +130,19 @@ def __init__(
configuration_name=dsc_configuration_name,
dsc_description="DSC for Data Safe Haven primary domain controller",
dsc_file=dsc_reader,
dsc_parameters={
"AzureADConnectPassword": props.password_domain_azuread_connect,
"AzureADConnectUsername": props.username_domain_azuread_connect,
"DomainAdministratorPassword": props.password_domain_admin,
"DomainAdministratorUsername": props.username_domain_admin,
"DomainComputerManagerPassword": props.password_domain_computer_manager,
"DomainComputerManagerUsername": props.username_domain_computer_manager,
"DomainName": props.domain_fqdn,
"DomainNetBios": props.domain_netbios_name,
"DomainRootDn": props.domain_root_dn,
"LDAPSearcherPassword": props.password_domain_searcher,
"LDAPSearcherUsername": props.username_domain_searcher,
},
dsc_parameters=Output.all(
AzureADConnectPassword=props.password_domain_azuread_connect,
AzureADConnectUsername=props.username_domain_azuread_connect,
DomainAdministratorPassword=props.password_domain_admin,
DomainAdministratorUsername=props.username_domain_admin,
DomainComputerManagerPassword=props.password_domain_computer_manager,
DomainComputerManagerUsername=props.username_domain_computer_manager,
DomainName=props.domain_fqdn,
DomainNetBios=props.domain_netbios_name,
DomainRootDn=props.domain_root_dn,
LDAPSearcherPassword=props.password_domain_searcher,
LDAPSearcherUsername=props.username_domain_searcher,
),
dsc_required_modules=props.automation_account_modules,
location=props.location,
subscription_name=props.subscription_name,
20 changes: 11 additions & 9 deletions data_safe_haven/pulumi/components/shm_firewall.py
Original file line number Diff line number Diff line change
@@ -19,21 +19,23 @@ def __init__(
location: Input[str],
resource_group_name: Input[str],
route_table_name: Input[str],
subnet_firewall: Input[network.GetSubnetResult],
subnet_identity_iprange: Input[AzureIPv4Range],
subnet_update_servers_iprange: Input[AzureIPv4Range],
subnet_firewall: Input[network.Subnet],
subnet_identity: Input[network.Subnet],
subnet_update_servers: Input[network.Subnet],
):
self.domain_controller_private_ip = domain_controller_private_ip
self.location = location
self.resource_group_name = resource_group_name
self.route_table_name = route_table_name
self.subnet_firewall = Output.from_input(subnet_firewall)
self.subnet_identity_iprange = Output.from_input(subnet_identity_iprange).apply(
lambda ip_range: str(ip_range)
self.subnet_firewall_id = Output.from_input(subnet_firewall).apply(
lambda s: s.id
)
self.subnet_identity_iprange = Output.from_input(
subnet_identity
).address_prefix.apply(lambda prefix: str(prefix) if prefix else "")
self.subnet_update_servers_iprange = Output.from_input(
subnet_update_servers_iprange
).apply(lambda ip_range: str(ip_range))
subnet_update_servers
).address_prefix.apply(lambda prefix: str(prefix) if prefix else "")


class SHMFirewallComponent(ComponentResource):
@@ -1048,7 +1050,7 @@ def __init__(
network.AzureFirewallIPConfigurationArgs(
name="FirewallIpConfiguration",
public_ip_address=network.SubResourceArgs(id=public_ip.id),
subnet=network.SubResourceArgs(id=props.subnet_firewall.id),
subnet=network.SubResourceArgs(id=props.subnet_firewall_id),
)
],
location=props.location,
6 changes: 4 additions & 2 deletions data_safe_haven/pulumi/components/shm_monitoring.py
Original file line number Diff line number Diff line change
@@ -14,11 +14,13 @@ def __init__(
self,
dns_resource_group_name: Input[str],
location: Input[str],
subnet_monitoring_id: Input[str],
subnet_monitoring: Input[network.Subnet],
):
self.dns_resource_group_name = dns_resource_group_name
self.location = location
self.subnet_monitoring_id = subnet_monitoring_id
self.subnet_monitoring_id = Output.from_input(subnet_monitoring).apply(
lambda s: s.id
)


class SHMMonitoringComponent(ComponentResource):
195 changes: 81 additions & 114 deletions data_safe_haven/pulumi/components/shm_networking.py
Original file line number Diff line number Diff line change
@@ -19,44 +19,11 @@ def __init__(
location: Input[str],
public_ip_range_admins: Input[Sequence[str]],
record_domain_verification: Input[str],
ip_range_vnet: Optional[Input[Sequence[str]]],
ip_range_firewall: Optional[Input[Sequence[str]]],
ip_range_vpn_gateway: Optional[Input[Sequence[str]]],
ip_range_monitoring: Optional[Input[Sequence[str]]],
ip_range_update_servers: Optional[Input[Sequence[str]]],
ip_range_identity: Optional[Input[Sequence[str]]],
):
self.fqdn = fqdn
self.ip_range_firewall = (
ip_range_firewall if ip_range_firewall else ["10.0.0.0", "10.0.0.63"]
) # must be at least /26 in size
self.ip_range_identity = (
ip_range_identity if ip_range_identity else ["10.0.0.192", "10.0.0.223"]
)
self.ip_range_monitoring = (
ip_range_monitoring if ip_range_monitoring else ["10.0.0.128", "10.0.0.159"]
)
self.ip_range_update_servers = (
ip_range_update_servers
if ip_range_update_servers
else ["10.0.0.160", "10.0.0.191"]
)
self.ip_range_vnet = (
ip_range_vnet if ip_range_vnet else ["10.0.0.0", "10.0.255.255"]
)
self.ip_range_vpn_gateway = (
ip_range_vpn_gateway
if ip_range_vpn_gateway
else ["10.0.0.64", "10.0.0.127"]
)
self.location = location
self.public_ip_range_admins = public_ip_range_admins
self.record_domain_verification = record_domain_verification
self.subnet_firewall_name = "AzureFirewallSubnet" # This name is forced by https://docs.microsoft.com/en-us/azure/firewall/tutorial-firewall-deploy-portal
self.subnet_identity_name = "IdentitySubnet"
self.subnet_monitoring_name = "MonitoringSubnet"
self.subnet_update_servers_name = "UpdateServersSubnet"
self.subnet_vpn_gateway_name = "GatewaySubnet" # This name is forced by https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-vpn-faq#do-i-need-a-gatewaysubnet


class SHMNetworkingComponent(ComponentResource):
@@ -81,43 +48,33 @@ def __init__(
)

# Set address prefixes from ranges
virtual_network_iprange = Output.from_input(props.ip_range_vnet).apply(
lambda iprange: AzureIPv4Range(*iprange)
)
subnet_firewall_iprange = Output.from_input(props.ip_range_firewall).apply(
lambda iprange: AzureIPv4Range(*iprange)
)
subnet_vpn_gateway_iprange = Output.from_input(
props.ip_range_vpn_gateway
).apply(lambda iprange: AzureIPv4Range(*iprange))
subnet_monitoring_iprange = Output.from_input(props.ip_range_monitoring).apply(
lambda iprange: AzureIPv4Range(*iprange)
)
subnet_update_servers_iprange = Output.from_input(
props.ip_range_update_servers
).apply(lambda iprange: AzureIPv4Range(*iprange))
subnet_identity_iprange = Output.from_input(props.ip_range_identity).apply(
lambda iprange: AzureIPv4Range(*iprange)
)
virtual_network_iprange = AzureIPv4Range("10.0.0.0", "10.0.255.255")
# Firewall subnet must be at least /26 in size
subnet_firewall_iprange = virtual_network_iprange.next_subnet(64)
# VPN gateway subnet must be at least /29 in size
subnet_vpn_gateway_iprange = virtual_network_iprange.next_subnet(64)
subnet_monitoring_iprange = virtual_network_iprange.next_subnet(32)
subnet_update_servers_iprange = virtual_network_iprange.next_subnet(32)
subnet_identity_iprange = virtual_network_iprange.next_subnet(32)

# Define NSGs
nsg_monitoring = network.NetworkSecurityGroup(
f"{self._name}_nsg_monitoring",
network_security_group_name=f"nsg-{stack_name}-monitoring",
network_security_group_name=f"{stack_name}-nsg-monitoring",
resource_group_name=resource_group.name,
security_rules=[],
opts=child_opts,
)
nsg_update_servers = network.NetworkSecurityGroup(
f"{self._name}_nsg_update_servers",
network_security_group_name=f"nsg-{stack_name}-update-servers",
network_security_group_name=f"{stack_name}-nsg-update-servers",
resource_group_name=resource_group.name,
security_rules=[],
opts=child_opts,
)
nsg_identity = network.NetworkSecurityGroup(
f"{self._name}_nsg_identity",
network_security_group_name=f"nsg-{stack_name}-identity",
network_security_group_name=f"{stack_name}-nsg-identity",
resource_group_name=resource_group.name,
security_rules=[
network.SecurityRuleArgs(
@@ -170,55 +127,82 @@ def __init__(
opts=ResourceOptions.merge(
ResourceOptions(
ignore_changes=["routes"]
), # allow routes to be added in other modules
), # allow routes to be created outside this definition
child_opts,
),
)

# Define the virtual network with inline subnets
# Define the virtual network and its subnets
virtual_network = network.VirtualNetwork(
f"{self._name}_virtual_network",
address_space=network.AddressSpaceArgs(
address_prefixes=[str(virtual_network_iprange)],
),
resource_group_name=resource_group.name,
subnets=[ # Note that we need to define subnets inline or they will be destroyed/recreated on a new run
network.SubnetArgs(
address_prefix=str(subnet_firewall_iprange),
name=props.subnet_firewall_name,
network_security_group=None, # the firewall subnet must NOT have an NSG
),
network.SubnetArgs(
address_prefix=str(subnet_vpn_gateway_iprange),
name=props.subnet_vpn_gateway_name,
network_security_group=None, # the VPN gateway subnet must NOT have an NSG
),
network.SubnetArgs(
address_prefix=str(subnet_monitoring_iprange),
name=props.subnet_monitoring_name,
network_security_group=network.NetworkSecurityGroupArgs(
id=nsg_monitoring.id
),
route_table=network.RouteTableArgs(id=route_table.id),
),
network.SubnetArgs(
address_prefix=str(subnet_update_servers_iprange),
name=props.subnet_update_servers_name,
network_security_group=network.NetworkSecurityGroupArgs(
id=nsg_update_servers.id
),
route_table=network.RouteTableArgs(id=route_table.id),
),
network.SubnetArgs(
address_prefix=str(subnet_identity_iprange),
name=props.subnet_identity_name,
network_security_group=network.NetworkSecurityGroupArgs(
id=nsg_identity.id
),
route_table=network.RouteTableArgs(id=route_table.id),
),
],
virtual_network_name=f"vnet-{stack_name}",
subnets=[],
virtual_network_name=f"{stack_name}-vnet",
opts=ResourceOptions.merge(
ResourceOptions(
ignore_changes=["subnets"]
), # allow subnets to be created outside this definition
child_opts,
),
)
# AzureFirewall subnet
subnet_firewall = network.Subnet(
f"{self._name}_subnet_firewall",
address_prefix=str(subnet_firewall_iprange),
network_security_group=None, # the firewall subnet must NOT have an NSG
resource_group_name=resource_group.name,
subnet_name="AzureFirewallSubnet", # this name is forced by https://docs.microsoft.com/en-us/azure/firewall/tutorial-firewall-deploy-portal
virtual_network_name=virtual_network.name,
opts=child_opts,
)
# VPN gateway subnet
subnet_vpn_gateway = network.Subnet(
f"{self._name}_subnet_vpn_gateway",
address_prefix=str(subnet_vpn_gateway_iprange),
network_security_group=None, # the VPN gateway subnet must NOT have an NSG
resource_group_name=resource_group.name,
subnet_name="GatewaySubnet", # this name is forced by https://docs.microsoft.com/en-us/azure/vpn-gateway/vpn-gateway-vpn-faq#do-i-need-a-gatewaysubnet
virtual_network_name=virtual_network.name,
opts=child_opts,
)
# Monitoring subnet
subnet_monitoring = network.Subnet(
f"{self._name}_subnet_monitoring",
address_prefix=str(subnet_monitoring_iprange),
network_security_group=network.NetworkSecurityGroupArgs(
id=nsg_monitoring.id
),
resource_group_name=resource_group.name,
route_table=network.RouteTableArgs(id=route_table.id),
subnet_name="MonitoringSubnet",
virtual_network_name=virtual_network.name,
opts=child_opts,
)
# Update servers subnet
subnet_update_servers = network.Subnet(
f"{self._name}_subnet_update_servers",
address_prefix=str(subnet_update_servers_iprange),
network_security_group=network.NetworkSecurityGroupArgs(
id=nsg_update_servers.id
),
resource_group_name=resource_group.name,
route_table=network.RouteTableArgs(id=route_table.id),
subnet_name="UpdateServersSubnet",
virtual_network_name=virtual_network.name,
opts=child_opts,
)
# Identity subnet
subnet_identity = network.Subnet(
f"{self._name}_subnet_identity",
address_prefix=str(subnet_identity_iprange),
network_security_group=network.NetworkSecurityGroupArgs(id=nsg_identity.id),
resource_group_name=resource_group.name,
route_table=network.RouteTableArgs(id=route_table.id),
subnet_name="IdentitySubnet",
virtual_network_name=virtual_network.name,
opts=child_opts,
)

@@ -286,33 +270,16 @@ def __init__(
virtual_network_link_name=f"link-to-vnet-{stack_name}",
)

# Extract subnets
subnet_firewall = network.get_subnet_output(
subnet_name=props.subnet_firewall_name,
resource_group_name=resource_group.name,
virtual_network_name=virtual_network.name,
)

# Register outputs
self.dns_zone_nameservers = dns_zone.name_servers
self.domain_controller_private_ip = str(subnet_identity_iprange.available()[0])
self.resource_group_name = Output.from_input(resource_group.name)
self.route_table = route_table
self.subnet_firewall = subnet_firewall
self.subnet_firewall_name = Output.from_input(props.subnet_firewall_name)
self.subnet_identity_iprange = subnet_identity_iprange
self.subnet_identity_name = Output.from_input(props.subnet_identity_name)
self.subnet_monitoring_name = Output.from_input(props.subnet_monitoring_name)
self.subnet_monitoring = network.get_subnet(
resource_group_name=resource_group.name,
subnet_name=props.subnet_monitoring_name,
virtual_network_name=virtual_network.name,
)
self.subnet_update_servers_iprange = subnet_update_servers_iprange
self.subnet_update_servers_name = Output.from_input(
props.subnet_update_servers_name
)
self.subnet_vpn_gateway_name = Output.from_input(props.subnet_vpn_gateway_name)
self.subnet_identity = subnet_identity
self.subnet_monitoring = subnet_monitoring
self.subnet_update_servers = subnet_update_servers
self.subnet_vpn_gateway = subnet_vpn_gateway
self.virtual_network = virtual_network

# Register exports
8 changes: 4 additions & 4 deletions data_safe_haven/pulumi/declarative_shm.py
Original file line number Diff line number Diff line change
@@ -58,8 +58,8 @@ def run(self):
resource_group_name=networking.resource_group_name,
route_table_name=networking.route_table.name,
subnet_firewall=networking.subnet_firewall,
subnet_identity_iprange=networking.subnet_identity_iprange,
subnet_update_servers_iprange=networking.subnet_update_servers_iprange,
subnet_identity=networking.subnet_identity,
subnet_update_servers=networking.subnet_update_servers,
),
)

@@ -83,7 +83,7 @@ def run(self):
SHMMonitoringProps(
dns_resource_group_name=networking.resource_group_name,
location=self.cfg.azure.location,
subnet_monitoring_id=networking.subnet_monitoring.id,
subnet_monitoring=networking.subnet_monitoring,
),
)

@@ -115,7 +115,7 @@ def run(self):
),
public_ip_range_admins=self.cfg.shm.admin_ip_addresses,
private_ip_address=networking.domain_controller_private_ip,
subnet_name=networking.subnet_identity_name,
subnet_identity=networking.subnet_identity,
subscription_name=self.cfg.subscription_name,
virtual_network_name=networking.virtual_network.name,
virtual_network_resource_group_name=networking.resource_group_name,