Posted: Sunday, December 24, 2023

Word Count: 2223

Reading Time: 10 minutes


Azure Storage Accounts are foundational in any Azure infrastructure, offering scalable and secure data storage solutions. Incorporating private endpoints connects these storage accounts to a virtual network, thus bolstering security by restricting public internet access.

Tools Requirements

Resources Created

Detailed Explanation of the Code

The Params and Vars

Parameters in an Bicep template act as placeholders that are filled in with values when the template is deployed. They allow for customization and reusability of the template. In our storage account deployment template, several parameters play key roles.

Key Parameters of the Template
  1. storageAccountName
    • Purpose: This parameter defines the name of the storage account to be created.
    • Usage: It uses the uniqueString function combined with the resourceGroup().id to ensure a unique name is generated for the storage account.
  2. location
    • Purpose: Specifies the Azure region where resources will be deployed.
    • Usage: It is crucial for deploying resources in a region that meets your latency, availability, and compliance requirements.
  3. spoke01Name and spoke01IpPrefix
    • Purpose: These parameters are used in setting up the virtual network.
    • Usage: spoke01Name gives a name to the virtual network, while spoke01IpPrefix defines the IP address range for the network and its subnets.
  4. subnetNamePrefix
    • Purpose: Used to create a consistent naming convention for subnets within the virtual network.
    • Usage: It’s a concatenation of the virtual network name and ‘sn’ to denote a subnet.
  5. storagesuffix
    • Purpose: A suffix used in naming private DNS zones.
    • Usage: Helps in creating distinct names for private DNS zones for each Azure storage service (Blob, File, Queue, Table).
Why Parameterization is Important
Tips for Using Parameters Effectively
Creating the Virtual Network

The first step in our deployment is establishing a virtual network. This network will house our storage account and its associated resources.

Code Snippet – Virtual Network
   resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-05-01' = {
     name: spoke01Name
     location: location
     properties: {
       addressSpace: {
         addressPrefixes: [
           '${spoke01IpPrefix}0.0/23'
         ]
       }
       subnets: [
         {
           name: '${subnetNamePrefix}01'
           properties: {
             addressPrefix: '${spoke01IpPrefix}0.0/24'
           }
         }
       ]
     }
   }
Bicep
Explanation:

Configuring the Storage Account

Once the virtual network is established, the next crucial step is setting up the storage account. This is where your data will reside, so ensuring its configuration aligns with best security practices is key.

Code Snippet – Storage Account
resource storageAccountName_resource 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  tags: {
    'Created By': 'Cyril Figgis'
    'Used For ': 'Phrasing'
    'ms-resource-usage': 'azure-cloud-shell'
  }
  sku: {
    name: 'Standard_RAGRS'
  }
  kind: 'StorageV2'
  properties: {
    dnsEndpointType: 'Standard'
    defaultToOAuthAuthentication: false
    publicNetworkAccess: 'Disabled'
    allowCrossTenantReplication: false
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
    allowSharedKeyAccess: true
    networkAcls: {
      bypass: 'AzureServices'
      virtualNetworkRules: []
      ipRules: []
      defaultAction: 'Deny'
    }
    supportsHttpsTrafficOnly: true
    encryption: {
      requireInfrastructureEncryption: false
      services: {
        file: {
          keyType: 'Account'
          enabled: true
        }
        blob: {
          keyType: 'Account'
          enabled: true
        }
      }
      keySource: 'Microsoft.Storage'
    }
    accessTier: 'Hot'
  }
}
Bicep
Explanation:
Why It’s Important:

Configuring the storage account with these settings ensures that your data is not only securely stored but also compliant with many industry-standard security practices. The emphasis on encryption and restricted access aligns with best practices for data security in the cloud.

Continuing with the explanation of the Bicep template, let’s focus on the next crucial components: setting up private DNS zones and private endpoints for the storage account.


Establishing Private DNS Zones

Private DNS zones in Azure provide a reliable and secure DNS service for your virtual network resources. Here, we set up DNS zones for Blob, Table, Queue, and File services associated with the storage account.

Code Snippet – Private DNS Zones
resource blobPrivDNS 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.blob.${storagesuffix}'
  location: 'global'
}


resource tablePrivDNS 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.table.${storagesuffix}'
  location: 'global'
}


resource queuePrivDNS 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.queue.${storagesuffix}'
  location: 'global'
}

resource filePrivDNS 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.file.${storagesuffix}'
  location: 'global'
}
Bicep
Explanation:
Why It’s Important:

Private DNS zones are essential for resolving the names of your Azure services in a secure manner. They play a crucial role in ensuring that your storage account communicates securely within your Azure environment, especially when public network access is disabled.

Configuring Private Endpoints

Private endpoints are a foundational aspect of securing your Azure services. They ensure that your services are accessible only within your Azure Virtual Network (vNet).

Code Snippet – Private Endpoints
resource sablobpe 'Microsoft.Network/privateEndpoints@2023-05-01' = {
  name: '${storageAccountName}-blobpe'
  location: location
  properties:{
    subnet: {
      id: virtualNetwork.properties.subnets[0].id
    }
    privateLinkServiceConnections: [
    {
      name: '${storageAccountName}-blobpe-conn'
      properties: {
        privateLinkServiceId: storageAccountName_resource.id
        groupIds:[
          'blob'
        ]
        privateLinkServiceConnectionState: {
          status: 'Approved'
          actionsRequired: 'None'
        }
      }
    }
    ]
  }
}

// Similar resources for queue, table, and file service endpoints
Bicep
Explanation:
Why It’s Important:

By using private endpoints, you ensure that these services are accessible only from within the VNet. This significantly enhances security by eliminating exposure to the public internet.


Continuing further, let’s now focus on the setup of service-specific resources and their integration with the private endpoints.


Integrating Services with Private Endpoints

After setting up private DNS zones and private endpoints, the next step involves linking these services to their respective private endpoints. This is crucial for ensuring that the storage services are securely accessible within the virtual network.

Code Snippet – Privae DNS Zone Groups
resource privateEndpoints_DNS_blob 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = {
  parent: sablobpe
  properties: {
    privateDnsZoneConfigs: [
      {
        name: blobPrivDNS.name
        properties: {
          privateDnsZoneId: blobPrivDNS.id
        }
      }
    ]
  }
}
...
// Similar resources for queue, table, and file service endpoints
BICEP
Explanation:
Why It’s Important:

This configuration ensures that when your Azure services try to communicate with, say, the Blob service, they use the private DNS zone, thus keeping the traffic within the virtual network. It’s a key step in enforcing network-level isolation and security for your services.


Configuring Additional Storage Resources

Finally, the Bicep template configures additional storage resources like queue services, table services, and containers. These resources are essential for the functional and operational aspects of your storage account

Code Snippet – Creating Storage Resources
resource storageQueue 'Microsoft.Storage/storageAccounts/queueServices@2023-01-01' = {
  parent: storageAccountName_resource
  name: 'default'
  properties: {
    cors: {
      corsRules: []
    }
  }
}

// Similar resources for table services and containers
Bicep
Explanation:
Why It’s Important:

While private endpoints and DNS zones establish security, these additional resources ensure that your storage account has the necessary capabilities for storage operations like queuing, table storage, and blob containers.


Final Product

Here is the finished product:

Code Snippet – Full Code
param storageAccountName string = 'storage${uniqueString(resourceGroup().id)}'
param location string = 'centralus'
param spoke01Name string = 'spoke01'
param spoke01IpPrefix string = '10.20.'
var subnetNamePrefix = '${spoke01Name}sn'
param storagesuffix string = environment().suffixes.storage

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-05-01' = {
  name: spoke01Name
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [
        '${spoke01IpPrefix}0.0/23'
      ]
    }
    subnets: [
      {
        name: '${subnetNamePrefix}01'
        properties: {
          addressPrefix: '${spoke01IpPrefix}0.0/24'
        }
      }
    ]
  }
}

resource storageAccountName_resource 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  tags: {
    'Created By': 'Cyril Figgis'
    'Used For ': 'Phrasing'
    'ms-resource-usage': 'azure-cloud-shell'
  }
  sku: {
    name: 'Standard_RAGRS'
  }
  kind: 'StorageV2'
  properties: {
    dnsEndpointType: 'Standard'
    defaultToOAuthAuthentication: false
    publicNetworkAccess: 'Disabled'
    allowCrossTenantReplication: false
    minimumTlsVersion: 'TLS1_2'
    allowBlobPublicAccess: false
    allowSharedKeyAccess: true
    networkAcls: {
      bypass: 'AzureServices'
      virtualNetworkRules: []
      ipRules: []
      defaultAction: 'Deny'
    }
    supportsHttpsTrafficOnly: true
    encryption: {
      requireInfrastructureEncryption: false
      services: {
        file: {
          keyType: 'Account'
          enabled: true
        }
        blob: {
          keyType: 'Account'
          enabled: true
        }
      }
      keySource: 'Microsoft.Storage'
    }
    accessTier: 'Hot'
  }
}

resource blobPrivDNS 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.blob.${storagesuffix}'
  location: 'global'
}


resource tablePrivDNS 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.table.${storagesuffix}'
  location: 'global'
}


resource queuePrivDNS 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.queue.${storagesuffix}'
  location: 'global'
}

resource filePrivDNS 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.file.${storagesuffix}'
  location: 'global'
}


resource storageAccountName_default 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
  parent: storageAccountName_resource
  name: 'default'
  properties: {
    changeFeed: {
      enabled: false
    }
    restorePolicy: {
      enabled: false
    }
    containerDeleteRetentionPolicy: {
      enabled: true
      days: 7
    }
    cors: {
      corsRules: []
    }
    deleteRetentionPolicy: {
      allowPermanentDelete: false
      enabled: true
      days: 7
    }
    isVersioningEnabled: false
  }
}

resource Microsoft_Storage_storageAccounts_fileServices_storageAccountName_default 'Microsoft.Storage/storageAccounts/fileServices@2023-01-01' = {
  parent: storageAccountName_resource
  name: 'default'
  properties: {
    protocolSettings: {
      smb: {}
    }
    cors: {
      corsRules: []
    }
    shareDeleteRetentionPolicy: {
      enabled: true
      days: 7
    }
  }
}
resource sablobpe 'Microsoft.Network/privateEndpoints@2023-05-01' = {
  name: '${storageAccountName}-blobpe'
  location: location
  properties:{
    subnet: {
      id: virtualNetwork.properties.subnets[0].id
    }
    privateLinkServiceConnections: [
    {
      name: '${storageAccountName}-blobpe-conn'
      properties: {
        privateLinkServiceId: storageAccountName_resource.id
        groupIds:[
          'blob'
        ]
        privateLinkServiceConnectionState: {
          status: 'Approved'
          actionsRequired: 'None'
        }
      }
    }
    ]
  }
}


resource privateEndpoints_DNS_blob 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = {
  name: 'default'
  parent: sablobpe
  properties: {
    privateDnsZoneConfigs: [
      {
        name: blobPrivDNS.name
        properties: {
          privateDnsZoneId: blobPrivDNS.id
        }
      }
    ]
  }
  dependsOn: [
  ]
}

resource saqueuepe 'Microsoft.Network/privateEndpoints@2023-05-01' = {
  name: '${storageAccountName}-queuepe'
  location: location
  properties:{
    subnet: {
      id: virtualNetwork.properties.subnets[0].id
    }
    privateLinkServiceConnections: [
    {
      name: '${storageAccountName}-queuepe-conn'
      properties: {
        privateLinkServiceId: storageAccountName_resource.id
        groupIds:[
          'queue'
        ]
        privateLinkServiceConnectionState: {
          status: 'Approved'
          actionsRequired: 'None'
        }
      }
    }
    ]
  }
}

resource privateEndpoints_DNS_queue 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = {
  name: 'default'
  parent: saqueuepe
  properties: {
    privateDnsZoneConfigs: [
      {
        name: queuePrivDNS.name
        properties: {
          privateDnsZoneId:  queuePrivDNS.id
        }
      }
    ]
  }
  dependsOn: [
  ]
}

resource satablepe 'Microsoft.Network/privateEndpoints@2023-05-01' = {
  name: '${storageAccountName}-tablepe'
  location: location
  properties:{
    subnet: {
      id: virtualNetwork.properties.subnets[0].id
    }
    privateLinkServiceConnections: [
    {
      name: '${storageAccountName}-tablepe-conn'
      properties: {
        privateLinkServiceId: storageAccountName_resource.id
        groupIds:[
          'table'
        ]
        privateLinkServiceConnectionState: {
          status: 'Approved'
          actionsRequired: 'None'
        }
      }
    }
    ]
  }
}

resource privateEndpoints_DNS_table 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = {
  name: 'default'
  parent: satablepe
  properties: {
    privateDnsZoneConfigs: [
      {
        name: tablePrivDNS.name
        properties: {
          privateDnsZoneId:  tablePrivDNS.id
        }
      }
    ]
  }
  dependsOn: [
  ]
}

resource safilepe 'Microsoft.Network/privateEndpoints@2023-05-01' = {
  name: '${storageAccountName}-filepe'
  location: location
  properties:{
    subnet: {
      id: virtualNetwork.properties.subnets[0].id
    }
    privateLinkServiceConnections: [
    {
      name: '${storageAccountName}-filepe-conn'
      properties: {
        privateLinkServiceId: storageAccountName_resource.id
        groupIds:[
          'file'
        ]
        privateLinkServiceConnectionState: {
          status: 'Approved'
          actionsRequired: 'None'
        }
      }
    }
    ]
  }
}

resource privateEndpoints_DNS_file 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = {
  name: 'default'
  parent: safilepe
  properties: {
    privateDnsZoneConfigs: [
      {
        name:  filePrivDNS.name
        properties: {
          privateDnsZoneId: filePrivDNS.id
        }
      }
    ]
  }
  dependsOn: [
  ]
}

resource storageQueue 'Microsoft.Storage/storageAccounts/queueServices@2023-01-01' = {
  parent: storageAccountName_resource
  name: 'default'
  properties: {
    cors: {
      corsRules: []
    }
  }
}

resource storageTable 'Microsoft.Storage/storageAccounts/tableServices@2023-01-01' = {
  parent: storageAccountName_resource
  name: 'default'
  properties: {
    cors: {
      corsRules: []
    }
  }
}

resource storageBlobWebJobsHosts 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = {
  parent: storageAccountName_default
  name: 'azure-webjobs-hosts'
  properties: {
    immutableStorageWithVersioning: {
      enabled: false
    }
    defaultEncryptionScope: '$account-encryption-key'
    denyEncryptionScopeOverride: false
    publicAccess: 'None'
  }
  dependsOn: []
}

resource storageBlobWebJobsSecrets 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = {
  parent: storageAccountName_default
  name: 'azure-webjobs-secrets'
  properties: {
    immutableStorageWithVersioning: {
      enabled: false
    }
    defaultEncryptionScope: '$account-encryption-key'
    denyEncryptionScopeOverride: false
    publicAccess: 'None'
  }
  dependsOn: []
}
Bicep

This setup exemplifies a secure and efficient approach to managing Azure storage resources. By understanding each part of the Bicep template, you can tailor this approach to fit the specific needs of your infrastructure.

Next Steps