Key Vault and PostgreSQL Server
In this post I will create the key vault to hold the secrets I need for my Django app connections. In the settings.py file of my app I use the function I created in it to grab the secret values from secrets in a key vault names cybauer-vault. I need to make sure to name the key vault the same in Pulumi and I also need to make sure I give the secrets the same name as the ones I have in settings.py.
After creating the key vault I will set up the PostgreSQL server and the cybauer database that goes with it. I want the server to be integrated with my VNET so I will link it to the delegated subnet and the private DNS zone I created previously. After the server and database a created I need to grab the fully qualified domain name and the password and create secrets in the key vault I just created.
Key Vault
I want the key vault created but I want it to be created after the container subnet is created. The reason for that is I need put a firewall rule on the key vault so only the container subnet can access it. As I said with the storage account in the previous post this would best to use a private endpoint with it, but because this is simple project and no important data is flowing through this app, I will use networking rules instead. While testing I noticed that the key vault would try and get created before the VNET and then it would fail because because the networking rule needs the container app subnet id. Because if this I am going to a depends on so the key vault creation waits for the subnet.
def create_kv(rg_name, container_subnet_id, depends_on=None):
tenant_id = ""
key_vault = keyvault.Vault("KeyVault",
pulumi.ResourceOptions(depends_on=depends_on),
resource_group_name = rg_name,
vault_name="cybauer-vault",
properties=keyvault.VaultPropertiesArgs(
tenant_id = tenant_id,
enable_rbac_authorization = True,
sku=keyvault.SkuArgs(
family="A",
name="standard",
),
network_acls = keyvault.NetworkRuleSetArgs(
default_action="Deny",
virtual_network_rules=[
keyvault.VirtualNetworkRuleArgs(
id = container_subnet_id
)
])
)
)
return key_vault
I again make the default rule action deny and then pass the subnet id of the subnet I want to allow traffic from. I also enabled RBAC authorization so an identity with an authorized role is needed to access this key vault and access policies do not work.
Now that the key vault is created lets go add the function to the main file.
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
cybauer_rg = create_resourcegroup()
vnet, postgres_subnet, container_apps_subnet, app_gateway_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)
In part 2 of the series in the create_network function I return a list of the subnets for that function and you can see in the main file I name it subnet_dependencies. I then pass the dependencies to the depends_on argument in the create_kv function. This way Pulumi knows it needs those subnets created before it can create the key vault.
PostgreSQL Server and Database
Now that the key vault is created I can create the server and database and create secrets from the creation of them.
def create_postgres(rg_name, postgres_subnet_id, key_vault_name, priv_dns_zone):
password = random.RandomPassword("password",
length=16,
special=True,
override_special="!#$%&*()-_=+[]{}<>:?")
postgres_server = postgresql.Server(
server_name="cybauer-postgresql",
resource_name="cybauer-postgresql",
resource_group_name = rg_name,
administrator_login = "dbadmin",
administrator_login_password = password.result,
storage=postgresql.StorageArgs(storage_size_gb=32),
backup=postgresql.BackupArgs(
backup_retention_days = 14, geo_redundant_backup="Disabled"
),
sku=postgresql.SkuArgs(tier="Burstable", name="Standard_B1ms"),
availability_zone = "1",
version = "16",
network = postgresql.NetworkArgs(
delegated_subnet_resource_id=postgres_subnet_id,
private_dns_zone_arm_resource_id=priv_dns_zone.id
)
cybauer_db = postgresql.Database(
database_name = "cybauer",
resource_name = "cybauer",
resource_group_name = rg_name,
server_name = postgres_server.name
)
The first thing this function does is create a 16 character complex password with the random Pulumi class. It then creates the PostgreSQL server and passes the created password to the administrator_login_password. It gives the username dbadmin which is the same username I have in the settings.py Django file. It also uses the postgres_subnet_id as the delegated subnet resource id and the priv_dns_zone id as the private dns zone resource id. This is why earlier in the networking I had to create the delegation and the private dns zone for the postgres subnet. I then create the database with the name cybauer and pass the server created above as the server name.
Now that I have this all set up I need to create secrets to store in the key vault so the Django app can grab them dynamically when it starts up.
postgres_fqdn_secret = keyvault.Secret(
"postgresFQDNSecret",
secret_name = "dbhost",
vault_name = key_vault_name,
resource_group_name = rg_name,
properties = keyvault.SecretPropertiesArgs(
value = postgres_server.fully_qualified_domain_name
)
)
postgres_password_secret = keyvault.Secret(
"postgrespassSecret",
secret_name = "dbpassword",
vault_name = key_vault_name,
resource_group_name = rg_name,
properties = keyvault.SecretPropertiesArgs(
value = password.result
)
)
I create the host and password secret using the fully qualified domain name and the same password the server used above this. I use the key vault name that was passed as an argument to the function. Now to put it all together:
from pulumi_azure_native import dbforpostgresql as postgresql
from pulumi_azure_native import keyvault, network
import pulumi
import pulumi_random as random
def create_postgres(rg_name, postgres_subnet_id, key_vault_name, priv_dns_zone):
password = random.RandomPassword("password",
length=16,
special=True,
override_special="!#$%&*()-_=+[]{}<>:?")
postgres_server = postgresql.Server(
server_name="cybauer-postgresql",
resource_name="cybauer-postgresql",
resource_group_name = rg_name,
administrator_login = "dbadmin",
administrator_login_password = password.result,
storage=postgresql.StorageArgs(storage_size_gb=32),
backup=postgresql.BackupArgs(
backup_retention_days = 14, geo_redundant_backup="Disabled"
),
sku=postgresql.SkuArgs(tier="Burstable", name="Standard_B1ms"),
availability_zone = "1",
version = "16",
network = postgresql.NetworkArgs(
delegated_subnet_resource_id=postgres_subnet_id,
private_dns_zone_arm_resource_id=priv_dns_zone.id
)
)
cybauer_db = postgresql.Database(
database_name = "cybauer",
resource_name = "cybauer",
resource_group_name = rg_name,
server_name = postgres_server.name
)
# Store the PostgreSQL FQDN in the Key Vault
postgres_fqdn_secret = keyvault.Secret(
"postgresFQDNSecret",
secret_name = "dbhost",
vault_name = key_vault_name,
resource_group_name = rg_name,
properties = keyvault.SecretPropertiesArgs(
value = postgres_server.fully_qualified_domain_name
)
)
postgres_password_secret = keyvault.Secret(
"postgrespassSecret",
secret_name = "dbpassword",
vault_name = key_vault_name,
resource_group_name = rg_name,
properties = keyvault.SecretPropertiesArgs(
value = password.result
)
)
return password, postgres_server, cybauer_db, postgres_password_secret, postgres_fqdn_secret
Here is the main file using this function
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
cybauer_rg = create_resourcegroup()
vnet, postgres_subnet, container_apps_subnet, app_gateway_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)
The function is using the subnets, private dns zone, and key vault that were created before this as arguments.
Conclusion
That is it for part 3 of the Pulumi infrastructure series. I got the key vault and database all set up and now I just need a way for the app to access the key vault so it can retrieve those secrets and connect to the database. I will cover that in the next post when I create the container registry, container app environment, and the container app. I will need to create a user assigned managed identity and assign it roles so the container app can access the two key vaults and the container registry.