General

Hello everyone! In today’s blog post we examine the code that will be used to deploy our infrastructure.

Bicep is being used for the IaC; all the templates/modules are available in the bicep/ folder.

Nikolaos Antoniou’s Azure Naming Bicep module has been used to apply the desired naming convention.

We are not going to delve into every single one of the modules; instead, we are going to focus on some important and noteworthy aspects.

WebApp

Code
resource webapp 'Microsoft.Web/sites@2022-03-01' = {
  name: name
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    httpsOnly: true
    virtualNetworkSubnetId: subnet_id
    vnetRouteAllEnabled: true
    vnetImagePullEnabled: true
    vnetContentShareEnabled: true
    siteConfig: {
      acrUseManagedIdentityCreds: true
      alwaysOn: always_on
      appSettings: [
        {
          name: 'WEBSITES_ENABLE_APP_SERVICE_STORAGE'
          value: 'false'
        }
        {
          name: 'WEBSITE_PULL_IMAGE_OVER_VNET'
          value: 'true'
        }
        {
          name: 'WEBSITE_VNET_ROUTE_ALL'
          value: 'true'
        }
        {
          name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
          value: app_insights_key
        }
        {
          name: 'WORDPRESS_DB_NAME'
          value: wordpress_db_name
        }
        {
          name: 'WORDPRESS_DB_HOST'
          value: mysql_host_name
        }
        {
          name: 'WORDPRESS_DB_USER'
          value: mysql_admin_username
        }
        {
          name: 'WORDPRESS_DB_PASSWORD'
          value: '@Microsoft.KeyVault(SecretUri=https://${kv_name}.vault.azure.net/secrets/${mysql_admin_password_secret_name}/)'
        }
        {
          name: 'MYSQL_SSL_CA'
          value: '/home/site/wwwroot/bin/DigiCertGlobalRootCA.crt.pem'
        }
        {
          name: 'WORDPRESS_CONFIG_EXTRA'
          value: 'define( \'MYSQL_CLIENT_FLAGS\', MYSQLI_CLIENT_SSL | MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT ); define(\'MYSQL_SSL_CA\', getenv(\'MYSQL_SSL_CA\') );'
        }
      ]
      linuxFxVersion: 'DOCKER|${registry_name}.azurecr.io/${image_name}:latest'
    }
    serverFarmId: app_service_plan_id
  }
}

Here we need to be careful in enabling some options.

First, setting vnetRouteAllEnabled to true assures that the WebApp’s outbound traffic will flow through the virtual network.

Next, both the vnetImagePullEnabled and the acrUseManagedIdentityCreds options must be set to true. By doing this, we allow our WebApp to pull the WordPress image from the ACR using its system-assigned identity.

Lastly, we need to create the necessary configuration settings regarding the database:

  • WORDPRESS_DB_NAME, WORDPRESS_DB_HOST, WORDPRESS_DB_USER
  • WORDPRESS_DB_PASSWORD (keyvault reference using system-assigned identity)
  • MYSQL_SSL_CA is the path to the DB certificate (the steps to download and store the certificate will be in Part 3)
  • WORDPRESS_CONFIG_EXTRA with some extra options in order to use the certificate and enforce SSL

MySQL Server

Code
var private_dns_zone_name = '${name}.private.mysql.database.azure.com'

resource private_dns_zone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: private_dns_zone_name
  location: 'global'
}

resource private_dns_zone_vnet_link 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  parent: private_dns_zone
  name: 'private-dns-vnet-link-${name}'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: vnet_id
    }
  }
}

resource mysql 'Microsoft.DBforMySQL/flexibleServers@2021-12-01-preview' = {
  name: name
  location: location
  sku: {
    name: sku_name
    tier: sku_tier
  }
  properties: {
    administratorLogin: admin_username
    administratorLoginPassword: admin_password
    version: version
    backup: {
      backupRetentionDays: backup_retention_days
      geoRedundantBackup: geo_redundant_backup
    }
    network: {
      delegatedSubnetResourceId: subnet_id
      privateDnsZoneResourceId: private_dns_zone.id
    }
  }
  resource database 'databases' = {
    name: database_name
    properties: {
      charset: database_charset
      collation: database_collation
    }
  }
  dependsOn: [
    private_dns_zone_vnet_link
  ]
}

We create a private DNS zone and link it to the vnet.

Afterwards, we create the flexible server, which has a delegated subnet (vnet integration).

Because the application expects a pre-existing database with the name wordpress, we create it.

Private DNS Zone and Private Endpoint for the ACR

Code
var private_dns_zone_name = 'privatelink.azurecr.io'

resource private_dns_zone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: private_dns_zone_name
  location: 'global'
}

resource private_dns_zone_vnet_link 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  parent: private_dns_zone
  name: 'private-dns-vnet-link-${name}'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: vnet_id
    }
  }
}

resource pep_cr 'Microsoft.Network/privateEndpoints@2022-01-01' = {
  name: pep_name
  location: pep_location
  properties: {
    privateLinkServiceConnections: [
      {
        name: pep_name
        properties: {
          groupIds: [
            'registry'
          ]
          privateLinkServiceId: cr.id
        }
      }
    ]
    subnet: {
      id: pep_subnet_id
    }
  }
}

resource private_dns_zone_group 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-01-01' = {
  parent: pep_cr
  name: 'registry-private-dns-zone-group'
  properties: {
    privateDnsZoneConfigs: [
      {
        name: 'registry-private-dns-zone-config'
        properties: {
          privateDnsZoneId: private_dns_zone.id
        }
      }
    ]
  }
}

Firstly, we create a private DNS zone and then link it to the vnet.

Then we create the private endpoint and create the appropriate zone group in the newly created DNS zone.

I strongly suggest that you use the parent element in order to avoid resolve errors.

Identity

Code
resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' = {
  name: name
  location: location
}

output identity_resource_id string = identity.id
output identity_client_id string = identity.properties.clientId
output identity_principal_id string = identity.properties.principalId

The important thing here is to export all the values that are necessary to perform a role assignment later on.

Role assignment

Code
@allowed([
  'Owner'
  'Contributor'
  'Reader'
  'AcrPush'
  'AcrPull'
  'NetworkContributor'
])
@description('Built-in role to assign')
param built_in_role_type string

var role = {
  Owner: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
  Contributor: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c'
  Reader: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/acdd72a7-3385-48ef-bd42-f606fba81ae7'

  NetworkContributor: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7'

  AcrPush: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/8311e382-0749-4cb8-b61a-304f252e45ec'
  AcrPull: '/subscriptions/${subscription().subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/7f951dda-4ed3-4680-a7ca-43fe172d538d'
}

resource role_assignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(subscription().id, principal_id, role[built_in_role_type])
  properties: {
    principalId: principal_id
    roleDefinitionId: role[built_in_role_type]
  }
}

Here, we only allow a certain list of roles to be assigned.

We use the friendly role name and then reference the role id using the variable role.

Summary

That about sums up the code modules. In the following part, we will deploy our infrastructure and execute some post-configuration tasks.

Next part:

Previous parts:

Related repository: WordPress-on-Azure

Tags: ,

Updated:

Leave a comment