Application Gateway and Network Security Groups
Basically everything is set up now. All I need to do now is create the application gateway and attach my SSL certificate to it so people can access the web app by going to cybauer.com. Once I get the application gateway set up I am going to set up network security groups for each subnet so I can lockdown the network access to everything and make sure only approved connections are getting through.
Application Gateway
There are two ways to attach certificates to the application gateway. The first is to store the certificate in a key vault and give the application gateway a user assigned managed identity with a role that allows it to grab the cert. The other way is to just attach the cert directly to the application gateway with the cert and its password. I am going to attach it directly with the password.
from pulumi_azure_native import network, managedidentity, authorization
import pulumi_azure as azure
import pulumi
import pulumi_std as std
import os
# Load the Pulumi configuration
config = pulumi.Config()
# Get configuration values
subscription_id = config.require("subscriptionId")
#kv_id not needed if attaching cert to app gateway.
def create_gateway(rg_name, app_gateway_sub_id, container_app_fqdn, kv_id, vnet):
# Create a Public IP Address
public_ip = network.PublicIPAddress("appGatewayPublicIP",
resource_group_name=rg_name,
public_ip_allocation_method=network.IPAllocationMethod.STATIC,
sku=network.PublicIPAddressSkuArgs(
name="Standard"
))
certificate_path = "certificate5.pfx"
certificate_password = os.environ.get("CERT_PASS")
# Define names for various Application Gateway components
backend_address_pool_name = vnet.name.apply(lambda name: f"{name}-beap")
frontend_port_name_443 = vnet.name.apply(lambda name: f"{name}-feport")
frontend_ip_configuration_name = vnet.name.apply(lambda name: f"{name}-feip")
http_setting_name = vnet.name.apply(lambda name: f"{name}-be-htst")
listener_name = vnet.name.apply(lambda name: f"{name}-httplstn")
request_routing_rule_name = vnet.name.apply(lambda name: f"{name}-rqrt")
Ok let’s go over these parts first. The first thing I need with this application gateway is a public IP. I am going to need to create a DNS record later so this IP address will resolve to cybauer.com. The second is the cert variables. I give the path and the password and the later the app gateway class will use these to attach the cert. The last piece from above is the names for the app gateway components. When creating the app gateway these are all of the components needed to make it work and you need to reference these components. I found it easiest to just create the names for them above so I can just pass them as variables each instead of spelling them out each time I needed them. It also just made it easier to keep track of everything.
Now let’s create the gateway.
# Create an Application Gateway
app_gateway = azure.network.ApplicationGateway("AppGateway",
resource_group_name=rg_name,
#identity=azure.network.ApplicationGatewayIdentityArgs(
#type="UserAssigned",
#identity_ids=[app_managed_identity.id.apply(lambda id: id.replace("resourcegroups", "resourceGroups"))]
sku=azure.network.ApplicationGatewaySkuArgs(
name="Standard_v2",
tier="Standard_v2",
capacity=2,
),
gateway_ip_configurations=[azure.network.ApplicationGatewayGatewayIpConfigurationArgs(
name="my-gateway-ip-configuration",
subnet_id = app_gateway_sub_id.id
)],
frontend_ports=[azure.network.ApplicationGatewayFrontendPortArgs(
name=frontend_port_name_443,
port=443,
)],
frontend_ip_configurations=[azure.network.ApplicationGatewayFrontendIpConfigurationArgs(
name=frontend_ip_configuration_name,
public_ip_address_id=public_ip.id,
)],
backend_address_pools=[azure.network.ApplicationGatewayBackendAddressPoolArgs(
name=backend_address_pool_name,
fqdns=[container_app_fqdn]
)],
backend_http_settings=[azure.network.ApplicationGatewayBackendHttpSettingArgs(
name=http_setting_name,
cookie_based_affinity="Disabled",
port=443,
protocol="Https",
request_timeout=60,
pick_host_name_from_backend_address=True
)],
ssl_certificates=[azure.network.ApplicationGatewaySslCertificateArgs(
name="appgatewaycert",
data=std.filebase64("certificate5.pfx").result,
password=certificate_password
)],
http_listeners=[azure.network.ApplicationGatewayHttpListenerArgs(
name=listener_name,
frontend_ip_configuration_name=frontend_ip_configuration_name,
frontend_port_name=frontend_port_name_443,
protocol="Https",
ssl_certificate_name="appgatewaycert"
)],
request_routing_rules=[azure.network.ApplicationGatewayRequestRoutingRuleArgs(
name=request_routing_rule_name,
rule_type="Basic",
http_listener_name=listener_name,
backend_address_pool_name=backend_address_pool_name,
backend_http_settings_name=http_setting_name,
priority=1,
)],
probes=[azure.network.ApplicationGatewayProbeArgs(
name="agacaprobe",
protocol="Https",
pick_host_name_from_backend_http_settings=True,
interval=30,
timeout=30,
path="/",
unhealthy_threshold=7
)]
)
# Export the public IP address of the Application Gateway
return public_ip, app_gateway
Let’s go through each component and what it is for. I start by using the same resource group name and want to use the Standard_V2 sku for the gateway. I give it a capacity of 1 because I know this site will not get much traffic. If I had more traffic, I would need to make this bigger or just use auto scaling.
I want it to be inside app gateway subnet I made so I pass the subnet id to the gateway configuration. Since connections are going to be coming in through HTTPS, I set the port as 443. I then set the frontend IP as the public IP I created above this.
For the backend I only have one app in the pool, so I use the container apps fully qualified domain name. I use the function argument for this. I set the backend settings to port 443 and I set it to pick the hostname from the backend address. This will automatically select the hostname for me.
The next thing is to create the SSL certificate. I use the cert data and the password to create it.
The last three components are the http listener, routing rule, and the custom probe. For the listener I use the frontend configuration name so it listens for that public IP. Port 443 is used so any connections coming into that IP on port 443 will be heard and passed along to the backend pool. Then I just use the SSL certificate name to set the certificate on the listener.
The routing rule is what is used to pass the traffic to the application. I give it the listener just created, the backend address pool with my apps FQDN in it, and the backend settings. If there were multiple rules you could adjust the priority but since I only have one I set it as priority 1.
The probe is just used to check and see if the app is online. It will send an Https request every 30 seconds to check and see if it is online.
I now need a DNS zone and record so my people can use my domain name to connect to my app.
def create_dns(rg_name, public_ip):
dns_zone = network.Zone("cybauerDnsZone",
resource_group_name=rg_name,
zone_name="cybauer.com",
location="Global"
)
a_record = network.RecordSet("cybauerARecord",
resource_group_name=rg_name,
relative_record_set_name="@", # Or empty string for the root domain
zone_name=dns_zone.name,
record_type="A",
ttl=300, # Time to live in seconds
a_records=[network.ARecordArgs(
ipv4_address = public_ip.ip_address
)]
)
return dns_zone, a_record
Now let’s put it all together .
from pulumi_azure_native import network, managedidentity, authorization
import pulumi_azure as azure
import pulumi
import pulumi_std as std
import os
# Load the Pulumi configuration
config = pulumi.Config()
# Get configuration values
subscription_id = config.require("subscriptionId")
def create_gateway(rg_name, app_gateway_sub_id, container_app_fqdn, kv_id, vnet):
"""
# Create Managed Identity for the Application Gateway
app_managed_identity = managedidentity.UserAssignedIdentity("AppGatewayIdentity",
resource_group_name=rg_name
)
pulumi.export("gateway_MI_id", app_managed_identity.id)
pulumi.export("app_subnet_id", app_gateway_sub_id)
# Assign the key vault role to the Managed Identity
cybauer_key_role_assignment = authorization.RoleAssignment(
"certcybauerkey_roleAssignment",
principal_id=app_managed_identity.principal_id,
principal_type=authorization.PrincipalType.SERVICE_PRINCIPAL,
role_definition_id=f"/subscriptions/{subscription_id}/providers/Microsoft.Authorization/roleDefinitions/db79e9a7-68ee-4b58-9aeb-b90e7c24fcba",
scope=kv_id
)
cybauer_key_role_assignment = authorization.RoleAssignment(
"secretcyaberkey_roleAssignment",
principal_id=app_managed_identity.principal_id,
principal_type=authorization.PrincipalType.SERVICE_PRINCIPAL,
role_definition_id="/subscriptions/3b8667c6-8f75-42ea-b301-bf27c9db8674/providers/Microsoft.Authorization/roleDefinitions/4633458b-17de-408a-b874-0445c86b69e6",
scope=kv_id
)
user_assigned_identities = app_managed_identity.id.apply(lambda id: {id: {}})
"""
# Create a Public IP Address
public_ip = network.PublicIPAddress("appGatewayPublicIP",
resource_group_name=rg_name,
public_ip_allocation_method=network.IPAllocationMethod.STATIC,
sku=network.PublicIPAddressSkuArgs(
name="Standard"
))
certificate_path = "certificate5.pfx"
certificate_password = os.environ.get("CERT_PASS")
certificate_password = "^SaG5tDF^6tYj%q09mvJ@6KSfV@5L"
# Define names for various Application Gateway components
backend_address_pool_name = vnet.name.apply(lambda name: f"{name}-beap")
frontend_port_name_443 = vnet.name.apply(lambda name: f"{name}-feport")
frontend_ip_configuration_name = vnet.name.apply(lambda name: f"{name}-feip")
http_setting_name = vnet.name.apply(lambda name: f"{name}-be-htst")
listener_name = vnet.name.apply(lambda name: f"{name}-httplstn")
request_routing_rule_name = vnet.name.apply(lambda name: f"{name}-rqrt")
# Create an Application Gateway
app_gateway = azure.network.ApplicationGateway("AppGateway",
resource_group_name=rg_name,
#identity=azure.network.ApplicationGatewayIdentityArgs(
#type="UserAssigned",
#identity_ids=[app_managed_identity.id.apply(lambda id: id.replace("resourcegroups", "resourceGroups"))]
sku=azure.network.ApplicationGatewaySkuArgs(
name="Standard_v2",
tier="Standard_v2",
capacity=1,
),
gateway_ip_configurations=[azure.network.ApplicationGatewayGatewayIpConfigurationArgs(
name="my-gateway-ip-configuration",
subnet_id = app_gateway_sub_id.id
)],
frontend_ports=[azure.network.ApplicationGatewayFrontendPortArgs(
name=frontend_port_name_443,
port=443,
)],
frontend_ip_configurations=[azure.network.ApplicationGatewayFrontendIpConfigurationArgs(
name=frontend_ip_configuration_name,
public_ip_address_id=public_ip.id,
)],
backend_address_pools=[azure.network.ApplicationGatewayBackendAddressPoolArgs(
name=backend_address_pool_name,
fqdns=[container_app_fqdn]
)],
backend_http_settings=[azure.network.ApplicationGatewayBackendHttpSettingArgs(
name=http_setting_name,
cookie_based_affinity="Disabled",
port=443,
protocol="Https",
request_timeout=60,
pick_host_name_from_backend_address=True
)],
ssl_certificates=[azure.network.ApplicationGatewaySslCertificateArgs(
name="appgatewaycert",
data=std.filebase64("certificate5.pfx").result,
password="^SaG5tDF^6tYj%q09mvJ@6KSfV@5L"
)],
http_listeners=[azure.network.ApplicationGatewayHttpListenerArgs(
name=listener_name,
frontend_ip_configuration_name=frontend_ip_configuration_name,
frontend_port_name=frontend_port_name_443,
protocol="Https",
ssl_certificate_name="appgatewaycert"
)],
request_routing_rules=[azure.network.ApplicationGatewayRequestRoutingRuleArgs(
name=request_routing_rule_name,
rule_type="Basic",
http_listener_name=listener_name,
backend_address_pool_name=backend_address_pool_name,
backend_http_settings_name=http_setting_name,
priority=1,
)],
probes=[azure.network.ApplicationGatewayProbeArgs(
name="agacaprobe",
protocol="Https",
pick_host_name_from_backend_http_settings=True,
interval=30,
timeout=30,
path="/",
unhealthy_threshold=7
)]
)
# Export the public IP address of the Application Gateway
return public_ip, app_gateway
def create_dns(rg_name, public_ip):
dns_zone = network.Zone("cybauerDnsZone",
resource_group_name=rg_name,
zone_name="cybauer.com",
location="Global"
)
a_record = network.RecordSet("cybauerARecord",
resource_group_name=rg_name,
relative_record_set_name="@", # Or empty string for the root domain
zone_name=dns_zone.name,
record_type="A",
ttl=300, # Time to live in seconds
a_records=[network.ARecordArgs(
ipv4_address = public_ip.ip_address
)]
)
return dns_zone, a_record
Here is what is used in the main.py file.
public_ip, app_gateway = create_gateway(rg_name=cybauer_rg.name, app_gateway_sub_id=app_gateway_subnet, container_app_fqdn=fqdn,
kv_id=key_vault.id, vnet=vnet)
dns_zone, a_record = create_dns(rg_name=cybauer_rg.name, public_ip=public_ip)
Network Security Groups (NSGs)
Now I need to create the NSG and all of the rules for each one. Microsoft gives documentation that I will need for each NSG. I am going to start with the application gateway. According to Microsoft I am going to need 4 allow rules on the NSG. I need to allow traffic to the app gateway subnet, allow to the public frontend IP, allow to the Gateway Manager on ports 65200-65535, and allow traffic from the Azure Load Balancer. Here is the link to page with instructions https://learn.microsoft.com/en-us/azure/application-gateway/configuration-infrastructure .
Let’s create it now.
def create_nsg(public_ip, rg_name, ag_sub_id, containerapp_sub_id, postgres_sub_id, cont_env_ip):
containerapp_sub = "10.0.0.0/23"
ag_sub = "10.0.2.0/24"
postgres_sub = "10.0.3.0/24"
ag_nsg = network.NetworkSecurityGroup(
"ag_nsg",
network_security_group_name="AG-nsg",
resource_group_name=rg_name,
security_rules=[
network.SecurityRuleArgs(
name="AllowClientTraffic",
priority=100,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["80", "443"], # Add other listener ports as needed
source_address_prefix="*",
destination_address_prefix=ag_sub # Subnet IP Prefix
),
network.SecurityRuleArgs(
name="AllowPublicPrivateFrontendIPs",
priority=200,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["443"], # Add other listener ports as needed
source_address_prefix="*",
destination_address_prefix=public_ip # Replace with actual IP
),
network.SecurityRuleArgs(
name="AllowInfrastructurePorts",
priority=300,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["65200-65535"], # V2 SKU ports
source_address_prefix="GatewayManager",
destination_address_prefix="*"
),
network.SecurityRuleArgs(
name="AllowAzureLoadBalancerProbes",
priority=400,
direction="Inbound",
access="Allow",
protocol="*",
source_port_range="*",
destination_port_range="*",
source_address_prefix="AzureLoadBalancer",
destination_address_prefix="*"
),
]
)
Now I need check out what I need for the container app. Because there are too many rules, I am just going to use a couple photos to show what I am going to need. Here is the link where these photos were taken https://learn.microsoft.com/en-us/azure/container-apps/firewall-integration?tabs=consumption-only
Outbound
Here are all of the rules I created from these instructions.
container_app_nsg = network.NetworkSecurityGroup(
"ContainerApp-nsg",
network_security_group_name="ContainerApp-nsg",
resource_group_name=rg_name,
security_rules=[
network.SecurityRuleArgs(
name="AllowAppGwToContainerAppSubnet",
priority=100,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["80", "443"],
source_address_prefix=ag_sub,
destination_address_prefix=containerapp_sub
),
network.SecurityRuleArgs(
name="AllowAppGwToContainerAppStaticIP",
priority=110,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["80", "443"],
source_address_prefix=ag_sub,
destination_address_prefix=cont_env_ip
),
network.SecurityRuleArgs(
name="AllowAzureLoadBalancerProbes",
priority=120,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["30000-32767"],
source_address_prefix="AzureLoadBalancer",
destination_address_prefix=containerapp_sub
),
network.SecurityRuleArgs(
name="AllowInternalTraffic",
priority=130,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="*",
source_address_prefix=containerapp_sub,
destination_address_prefix=containerapp_sub
),
network.SecurityRuleArgs(
name="AllowToMCR",
priority=200,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=containerapp_sub,
destination_address_prefix="MicrosoftContainerRegistry"
),
network.SecurityRuleArgs(
name="AllowToAzureFrontDoor",
priority=210,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureFrontDoor.FirstParty"
),
network.SecurityRuleArgs(
name="AllowToAzureCloudUDP",
priority=220,
direction="Outbound",
access="Allow",
protocol="Udp",
source_port_range="*",
destination_port_range="1194",
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureCloud.eastus"
),
network.SecurityRuleArgs(
name="AllowToAzureCloudTCP",
priority=230,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="9000",
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureCloud.eastus"
),
network.SecurityRuleArgs(
name="AllowToAzureCloudGeneral",
priority=240,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureCloud"
),
network.SecurityRuleArgs(
name="AllowToNTP",
priority=250,
direction="Outbound",
access="Allow",
protocol="Udp",
source_port_range="*",
destination_port_range="123",
source_address_prefix=containerapp_sub,
destination_address_prefix="*"
),
network.SecurityRuleArgs(
name="AllowInternalSubnet",
priority=260,
direction="Outbound",
access="Allow",
protocol="*",
source_port_range="*",
destination_port_range="*",
source_address_prefix=containerapp_sub,
destination_address_prefix=containerapp_sub
),
network.SecurityRuleArgs(
name="AllowToAzureDNS",
priority=270,
direction="Outbound",
access="Allow",
protocol="*",
source_port_range="*",
destination_port_range="53",
source_address_prefix=containerapp_sub,
destination_address_prefix="168.63.129.16"
),
network.SecurityRuleArgs(
name="AllowToContainerRegistry",
priority=280,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["443", "80"],
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureContainerRegistry"
),
network.SecurityRuleArgs(
name="AllowToAzureStorage",
priority=290,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=containerapp_sub,
destination_address_prefix="Storage.eastus"
)
]
)
Now l need to create the NSG for the PostgreSQL server. Here are the rules I need. Here is the link to this page https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-networking-private
Now here are the rules I created off of these instructions.
# Define NSG for the PostgreSQL Subnet
postgres_db_nsg = network.NetworkSecurityGroup(
"PostGres-nsg",
network_security_group_name="PostGres-nsg",
resource_group_name=rg_name,
security_rules=[
network.SecurityRuleArgs(
name="AllowInboundPostgres",
priority=100,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="5432",
source_address_prefix=postgres_sub,
destination_address_prefix=postgres_sub
),
network.SecurityRuleArgs(
name="AllowInboundFromContainerApp",
priority=110,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="5432",
source_address_prefix=containerapp_sub,
destination_address_prefix=postgres_sub
),
network.SecurityRuleArgs(
name="AllowOutboundAzureStorageRegion",
priority=120,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=postgres_sub,
destination_address_prefix="Storage"
),
]
)
With all of the NSGs now created with their rules, I need to associate the NSGs to their respective subnet.
ag_association = azure.network.SubnetNetworkSecurityGroupAssociation(
"app_gateway_nsg_association",
subnet_id=ag_sub_id,
network_security_group_id=ag_nsg.id
)
postgres_association = azure.network.SubnetNetworkSecurityGroupAssociation(
"postgres_nsg_association",
subnet_id=postgres_sub_id,
network_security_group_id=postgres_db_nsg.id
)
container_app_association = azure.network.SubnetNetworkSecurityGroupAssociation(
"containerapp_nsg_association",
subnet_id=containerapp_sub_id,
network_security_group_id=container_app_nsg.id
)
Now I can put it all together.
from pulumi_azure_native import network
import pulumi
import pulumi_azure as azure
def create_nsg(public_ip, rg_name, ag_sub_id, containerapp_sub_id, postgres_sub_id, cont_env_ip):
containerapp_sub = "10.0.0.0/23"
ag_sub = "10.0.2.0/24"
postgres_sub = "10.0.3.0/24"
ag_nsg = network.NetworkSecurityGroup(
"ag_nsg",
network_security_group_name="AG-nsg",
resource_group_name=rg_name,
security_rules=[
network.SecurityRuleArgs(
name="AllowClientTraffic",
priority=100,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["80", "443"], # Add other listener ports as needed
source_address_prefix="*",
destination_address_prefix=ag_sub # Subnet IP Prefix
),
network.SecurityRuleArgs(
name="AllowPublicPrivateFrontendIPs",
priority=200,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["443"], # Add other listener ports as needed
source_address_prefix="*",
destination_address_prefix=public_ip # Replace with actual IP
),
network.SecurityRuleArgs(
name="AllowInfrastructurePorts",
priority=300,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["65200-65535"], # V2 SKU ports
source_address_prefix="GatewayManager",
destination_address_prefix="*"
),
network.SecurityRuleArgs(
name="AllowAzureLoadBalancerProbes",
priority=400,
direction="Inbound",
access="Allow",
protocol="*",
source_port_range="*",
destination_port_range="*",
source_address_prefix="AzureLoadBalancer",
destination_address_prefix="*"
),
]
)
container_app_nsg = network.NetworkSecurityGroup(
"ContainerApp-nsg",
network_security_group_name="ContainerApp-nsg",
resource_group_name=rg_name,
security_rules=[
network.SecurityRuleArgs(
name="AllowAppGwToContainerAppSubnet",
priority=100,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["80", "443"],
source_address_prefix=ag_sub,
destination_address_prefix=containerapp_sub
),
network.SecurityRuleArgs(
name="AllowAppGwToContainerAppStaticIP",
priority=110,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["80", "443"],
source_address_prefix=ag_sub,
destination_address_prefix=cont_env_ip
),
network.SecurityRuleArgs(
name="AllowAzureLoadBalancerProbes",
priority=120,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["30000-32767"],
source_address_prefix="AzureLoadBalancer",
destination_address_prefix=containerapp_sub
),
network.SecurityRuleArgs(
name="AllowInternalTraffic",
priority=130,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="*",
source_address_prefix=containerapp_sub,
destination_address_prefix=containerapp_sub
),
network.SecurityRuleArgs(
name="AllowToMCR",
priority=200,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=containerapp_sub,
destination_address_prefix="MicrosoftContainerRegistry"
),
network.SecurityRuleArgs(
name="AllowToAzureFrontDoor",
priority=210,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureFrontDoor.FirstParty"
),
network.SecurityRuleArgs(
name="AllowToAzureCloudUDP",
priority=220,
direction="Outbound",
access="Allow",
protocol="Udp",
source_port_range="*",
destination_port_range="1194",
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureCloud.eastus"
),
network.SecurityRuleArgs(
name="AllowToAzureCloudTCP",
priority=230,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="9000",
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureCloud.eastus"
),
network.SecurityRuleArgs(
name="AllowToAzureCloudGeneral",
priority=240,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureCloud"
),
network.SecurityRuleArgs(
name="AllowToNTP",
priority=250,
direction="Outbound",
access="Allow",
protocol="Udp",
source_port_range="*",
destination_port_range="123",
source_address_prefix=containerapp_sub,
destination_address_prefix="*"
),
network.SecurityRuleArgs(
name="AllowInternalSubnet",
priority=260,
direction="Outbound",
access="Allow",
protocol="*",
source_port_range="*",
destination_port_range="*",
source_address_prefix=containerapp_sub,
destination_address_prefix=containerapp_sub
),
network.SecurityRuleArgs(
name="AllowToAzureDNS",
priority=270,
direction="Outbound",
access="Allow",
protocol="*",
source_port_range="*",
destination_port_range="53",
source_address_prefix=containerapp_sub,
destination_address_prefix="168.63.129.16"
),
network.SecurityRuleArgs(
name="AllowToContainerRegistry",
priority=280,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_ranges=["443", "80"],
source_address_prefix=containerapp_sub,
destination_address_prefix="AzureContainerRegistry"
),
network.SecurityRuleArgs(
name="AllowToAzureStorage",
priority=290,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=containerapp_sub,
destination_address_prefix="Storage"
)
]
)
# Define NSG for the PostgreSQL Subnet
postgres_db_nsg = network.NetworkSecurityGroup(
"PostGres-nsg",
network_security_group_name="PostGres-nsg",
resource_group_name=rg_name,
security_rules=[
network.SecurityRuleArgs(
name="AllowInboundPostgres",
priority=100,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="5432",
source_address_prefix=postgres_sub,
destination_address_prefix=postgres_sub
),
network.SecurityRuleArgs(
name="AllowInboundFromContainerApp",
priority=110,
direction="Inbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="5432",
source_address_prefix=containerapp_sub,
destination_address_prefix=postgres_sub
),
network.SecurityRuleArgs(
name="AllowOutboundAzureStorageRegion",
priority=120,
direction="Outbound",
access="Allow",
protocol="Tcp",
source_port_range="*",
destination_port_range="443",
source_address_prefix=postgres_sub,
destination_address_prefix="Storage"
),
]
)
ag_association = azure.network.SubnetNetworkSecurityGroupAssociation(
"app_gateway_nsg_association",
subnet_id=ag_sub_id,
network_security_group_id=ag_nsg.id
)
postgres_association = azure.network.SubnetNetworkSecurityGroupAssociation(
"postgres_nsg_association",
subnet_id=postgres_sub_id,
network_security_group_id=postgres_db_nsg.id
)
container_app_association = azure.network.SubnetNetworkSecurityGroupAssociation(
"containerapp_nsg_association",
subnet_id=containerapp_sub_id,
network_security_group_id=container_app_nsg.id
)
pulumi.export("ag_nsg_id", ag_nsg.id)
pulumi.export("container_app_nsg_id", container_app_nsg.id)
pulumi.export("postgres_db_nsg_id", postgres_db_nsg.id)
Here is what the main.py file is going to look like now that everything has been created.
main.py
from resource_group import create_resourcegroup
from network import create_network
from key_vault import create_kv
from storage_account import create_sa
from post_gresql import create_postgres
from container_registry import create_reg
from container_app import create_identity, create_app, create_app_env, app_create_dns
from app_gateway import create_gateway, create_dns
from nsg import create_nsg
cybauer_rg = create_resourcegroup()
vnet, postgres_subnet, container_apps_subnet, private_dns_zone, subnet_dependencies = create_network(rg_name=cybauer_rg.name)
account, blob_service_properties_resource, static_container, media_container, sas_token = create_sa(rg_name=cybauer_rg.name, container_apps_sub_id=container_apps_subnet.id)
key_vault = create_kv(rg_name=cybauer_rg.name, container_subnet_id=container_apps_subnet.id, depends_on=subnet_dependencies)
password, postgres_server, cybauer_db, postgres_password_secret, postgres_fqdn_secret = create_postgres(rg_name=cybauer_rg.name,
postgres_subnet_id=postgres_subnet.id, key_vault_name=key_vault.name, priv_dns_zone=private_dns_zone)
registry, credentials, cybaer_image = create_reg(rg_name=cybauer_rg.name)
managed_identity, captcha_key_role_assignment, cybauer_key_role_assignment, sa_role_assignment, acr_pull_role_assignmnet = create_identity(rg_name=cybauer_rg.name, kv_id=key_vault.id, sa_account_id=account.id, registry_id=registry.id)
app_env1 = create_app_env(rg_name=cybauer_rg.name, container_subnet_id=container_apps_subnet.id)
container_app, container_app_fqdn_secret, fqdn, dns_zone, a_record, txt_record, certificate = create_app(rg_name=cybauer_rg.name, app_env=app_env1, managed_identity=managed_identity,
registry=registry, credential=credentials, image=cybaer_image, kv_name=key_vault.name, depends_on=postgres_server,
sas_token=sas_token, static_ip=app_env1.static_ip)
app_private_dns_zone, arecord, atrecord, dns_zone_link = app_create_dns(rg_name=cybauer_rg.name, app_env=app_env1, vnet=vnet)
public_ip, app_gateway = create_gateway(rg_name=cybauer_rg.name, app_gateway_sub_id=app_gateway_subnet, container_app_fqdn=fqdn, kv_id=key_vault.id, vnet=vnet)
dns_zone, a_record = create_dns(rg_name=cybauer_rg.name, public_ip=public_ip)
create_nsg(public_ip, rg_name, ag_sub_id, containerapp_sub_id, postgres_sub_id, cont_env_ip)
With this main.py file now completed, I can just run pulumi up and it should all get deployed automatically. If you are reading this post that means it worked and my app has been deployed successfully.
Series Conclusion
This is the end of the Azure Pulumi Infrastructure series. This was a lot of fun and a great way for me to learn more about the process of creating and deploying a web app inside Azure. I really think Pululmi is a great tool to use, and I honestly probably wasn’t even using it completely correct. I just wanted to pick it up and see how it worked but from reading the docs on their site, it is extremely powerful and can be used in really unique ways. For example, I don’t even think I used a for loop in this entire series to create any resources. I really didn’t have to because I didn’t have that much infrastructure to create, but if I did, using for loops would be something I could have used to save a lot of time. So, keep that in mind this was really just me getting my feet wet and I am by no means a Pulumi expert.
I don’t really know what I am going to do next. I didn’t get to go through and cover building the Django app at all so I might make a series of that where I go over the entire build process, but I don’t know yet. I think the next project I might cover is getting these two sets up code, infrastructure and application, and putting them into GitHub repositories and deploying them using pipelines. I could probably put some fun stuff in the pipelines like approvals and automatic security scans. But again, I don’t know yet so we will see. Thanks for reading this, hopefully you enjoyed it and maybe even learned something.