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.
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.
uniqueString
function combined with the resourceGroup().id
to ensure a unique name is generated for the storage account.spoke01Name
gives a name to the virtual network, while spoke01IpPrefix
defines the IP address range for the network and its subnets.The first step in our deployment is establishing a virtual network. This network will house our storage account and its associated resources.
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'
}
}
]
}
}
Bicepname: spoke01Name
: This parameter sets the name of your virtual network. Here, we’re using a variable spoke01Name
to dynamically assign it.location: location
: Specifies the Azure region where the network will be deployed.addressSpace
: Defines the IP address range for the network. In our case, it’s a /23 range starting from 10.20.0.0
.subnets
: Within the virtual network, we define a subnet. This subnet will later connect to the private endpoints of our storage account. Why It’s Important: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.
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'
}
}
Bicepname: storageAccountName
: This parameter assigns a unique name to your storage account, using the uniqueString
function to ensure uniqueness.location: location
: Specifies the Azure region for the storage account.tags
: Custom tags for easy identification and management of the storage account.sku
: Sets the storage account type. Here, Standard_RAGRS
indicates standard performance with read-access geo-redundant storage.properties
: This section is critical as it defines the security and access properties of the storage account.publicNetworkAccess: 'Disabled'
: Ensures the storage account is not accessible from the public internet, enhancing security.networkAcls
: Configures network-level access control, with the default action set to ‘Deny’, allowing access only from specified networks.supportsHttpsTrafficOnly: true
: Ensures all requests to the storage account use HTTPS for secure communication.encryption
: Defines the encryption settings for stored data.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.
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.
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'
}
BicepMicrosoft.Network/privateDnsZones
resource represents a private DNS zone for a specific storage service (Blob, Table, Queue, File).name
: The name of the DNS zone is dynamically generated using the storagesuffix
variable to create a unique DNS zone for each service.location: 'global'
: Indicates that these DNS zones are available globally within your Azure environment. Note that in most cases global is required for PrivateDnsZonesPrivate 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.
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).
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
Bicepname
: Names are generated dynamically, following a pattern that includes the storage account name and the service type (e.g., -blobpe
for Blob service).privateLinkServiceConnections
property within each endpoint specifies the connection to the storage account’s respective service.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.
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.
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
BICEPparent: sablobpe
: This indicates that the DNS settings are for the Blob service private endpoint.privateDnsZoneConfigs
: Configures the private DNS zone for the Blob service, ensuring that DNS resolution for the Blob service happens within the private scope.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.
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
resource storageQueue 'Microsoft.Storage/storageAccounts/queueServices@2023-01-01' = {
parent: storageAccountName_resource
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
// Similar resources for table services and containers
Bicepparent: storageAccountName_resource
: Specifies that these services are part of the earlier defined storage account.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.
Here is the finished product:
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: []
}
BicepThis 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.