Posted: Saturday, February 3, 2024
Word Count: 1447
Reading Time: 7 minutes
Any organization requires their DNS records, private and personal, somewhere, why not leverage Azure DNS. The lab below, if you choose to leverage it, provides base bicep code that you can leverage to deploy Azure Public DNS.
This lab is relatively low cost and should only set you back a buck if the resources hang out for a while. As long as you do not surpass 10 million DNS queries in a billing cycle you shouldn’t experience a dramatic increase in your Azure spend.
Resource | Monthly Cost |
---|---|
Price per Zone | $0.50 |
Price Per Million Queries | $0.40 |
Monthly Lab Estimate | $0.90 |
Repo Location | GitHub Repo |
Azure | az deployment sub create –location centralus –template-file ./main.bicep –parameters ./vars.bicepparam |
This lab leverages 7 modules to deploy the solution:
Module | Description |
---|---|
resourceGroupModule.bicep | Creates the resource group that will host all resources |
dnsZoneModule.bicep | Creates DNS Zones that will host the records created by the other modules. |
aRecordModule.bicep | Creates all host (A) records across all DNS zones |
cnameRecordModule.bicep | Creates all canonical name (CNAME) records across all DNS zones |
mxRecordModule.bicep | Creates all mail exchanger (MX) records across all DNS zones |
srvRecordModule.bicep | Creates all service (SRV) records across all DNS zones |
txtRecordModule.bicep | Creates all service (TXT) records across all DNS zones |
There’s nothing really overly complicated regarding this module. It creates simply creates resource groups.
targetScope = 'subscription'
param rgName string
param location string
resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = {
name: rgName
location: location
}
output id string = resourceGroup.id
BICEPThe module below is solely responsible for creating. There is an output designated in the module, but in this exercise it’s not leveraged. The variables are fed into the module from main.tf using an array found in the vars.bicepparam file.
@description('The name of the DNS zone to be created. Must have at least 2 segments, e.g. hostname.org')
param zoneName string
resource zone 'Microsoft.Network/dnsZones@2018-05-01' = {
name: zoneName
location: 'global'
}
output nameServers array = zone.properties.nameServers
BICEPThe record modules are tasked to create the records in the DNS zones. Similar to the DNS zones module, the variables are fed into the module from main.tf using an array found in the vars.bicepparam file. The modules are created to create a specific record type, but across all DNS zones. The variable array determines the ttl, name, values and zone name.
param recordName string
param zoneName string
param ipv4Address string
param ttl int = 3600
resource zone 'Microsoft.Network/dnsZones@2018-05-01' existing = {
name: zoneName
}
resource record 'Microsoft.Network/dnsZones/A@2018-05-01' = {
parent: zone
name: recordName
properties: {
TTL: ttl
ARecords: [
{
ipv4Address: ipv4Address
}
]
}
}
output id string = record.id
output fqdn string = record.properties.fqdn
Bits and That Technology Blogparam recordName string
param zoneName string
param cname string
param ttl int = 3600
resource zone 'Microsoft.Network/dnsZones@2018-05-01' existing = {
name: zoneName
}
resource record 'Microsoft.Network/dnsZones/CNAME@2018-05-01' = {
parent: zone
name: recordName
properties: {
TTL: ttl
CNAMERecord: {
cname: cname
}
}
}
output id string = record.id
output fqdn string = record.properties.fqdn
Bits and That Technology Blogparam recordName string
param zoneName string
param mxRecords array
param ttl int
resource zone 'Microsoft.Network/dnsZones@2018-05-01' existing = {
name: zoneName
}
resource mxrecord 'Microsoft.Network/dnsZones/MX@2023-07-01-preview' = {
name: recordName
parent: zone
properties: {
TTL: ttl
MXRecords: mxRecords
}
}
output id string = mxrecord.id
output fqdn string = mxrecord.properties.fqdn
Bits and That Technology Blogparam recordName string
param zoneName string
param txtRecords array
param ttl int
resource zone 'Microsoft.Network/dnsZones@2018-05-01' existing = {
name: zoneName
}
resource txtrecord 'Microsoft.Network/dnsZones/TXT@2023-07-01-preview' = {
name: recordName
parent: zone
properties: {
TTL: ttl
TXTRecords: txtRecords
}
}
output id string = txtrecord.id
output fqdn string = txtrecord.properties.fqdn
Bits and That Technology Blogparam recordName string
param zoneName string
param txtRecords array
param ttl int
resource zone 'Microsoft.Network/dnsZones@2018-05-01' existing = {
name: zoneName
}
resource txtrecord 'Microsoft.Network/dnsZones/TXT@2023-07-01-preview' = {
name: recordName
parent: zone
properties: {
TTL: ttl
TXTRecords: txtRecords
}
}
output id string = txtrecord.id
output fqdn string = txtrecord.properties.fqdn
Bits and That Technology BlogThe variable file below controls the deployment of the DNS zones and records. With the exception of the resource group module, there is an array for every module listed above. Each record is enclosed in brackets and it’s a simple matter of copying what’s between the brackets an d modifying appropriately. The zone name value is determined by the order they are listed in the dnsZones parameter starting with 0. For example, to reference mypublicwish-1.com, you would reference dnsZones[0].name.
In each parameter, I personally added comments to group the records related to each dns zone. This isn’t a requirement, but it makes it easier on us humans.
using 'main.bicep'
////////////
// DNS Zones
/////////////
param dnsZones = [
{
name: 'mypublicwish-1.com'
}
{
name: 'mypublicwish-2.com'
}
]
//////////////
// A REcords
/////////////
param aRecords = [
// dnsZones[0].name = mypublicwish-1.com
{
key: 'a1'
ip: '1.1.1.1'
name: 'hosta'
zoneName: dnsZones[0].name
ttl: 60
}
{
key: 'a2'
ip: '2.2.2.2'
name: 'hostb'
zoneName: dnsZones[0].name
ttl: 60
}
// dnsZones[1].name = mypublicwish-1.com
{
key: 'a3'
ip: '3.3.3.3'
name: 'hostb2'
zoneName: dnsZones[1].name
ttl: 60
}
]
//////////
// CNAMES
//////////
param cNameRecords = [
// dnsZones[0].name = mypublicwish-1.com
{
key: 'c1'
name: '419k0hzlooiuoj3b0lkjlkjg0w541wjjydf53txx1hjlgdk'
cname: 'dcv1.digitallycertly.com'
zoneName: dnsZones[0].name
ttl: 60
}
{
key: 'c2'
cname: 'verify.cuboidspace.com'
name: '790kyaq7jlulk3g98dqypd16jlkjlkb3bvgb2htmbh9'
zoneName: dnsZones[0].name
ttl: 60
}
// dnsZones[1].name = mypublicwish-1.com
{
key: 'c3'
cname: 'www.google.com'
name: 'www'
zoneName: dnsZones[1].name
ttl: 60
}
]
//////////////
// MX Records
/////////////
param mxRecords = [
// dnsZones[0].name = mypublicwish-1.com
{
key: 'mx1'
recordName: '@'
zoneName: dnsZones[0].name
ttl: 60
values: [
{
preference: 113
exchange: 'mxa.hypnospp.org'
}
{
preference: 140
exchange: 'mxb.hypnospq.org'
}
]
}
// dnsZones[0].name = mypublicwish-1.com
{
key: 'mx2'
recordName: '@'
zoneName: dnsZones[1].name
ttl: 60
values: [
{
preference: 113
exchange: 'mxa.hypnosnb.org'
}
{
preference: 140
exchange: 'mxb.hypnosnc.org'
}
]
}
]
//////////////
// txtRecords
//////////////
param txtRecords = [
// dnsZones[0].name = mypublicwish-1.com
{
key: 'txt1'
recordName: '@'
zoneName: dnsZones[0].name
ttl: 60
targets: [
{
value: ['v=spf1 include:spf.protection.outlook.com include:usb._netblocks.mimecast.com include:nw026.com include:nw027.com include:nw028.com include:emailus.freshservice.com include:mg-spf.greenhouse.io include:rp.oracleemaildelivery.com ~all']
}
{
value: ['uc=ucJupER9Uyg']
}
]
}
// dnsZones[0].name = mypublicwish-1.com
{
key: 'txt2'
recordName: '@'
zoneName: dnsZones[1].name
ttl: 60
targets: [
{
value: ['elppa-domain-verification=MGNcyKAUTC0rrZ']
}
{
value: ['MOOZ_verify_649uu9bgwXh7NLKibBgouYcfD5ieT']
}
]
}
]
////////////////
// SRV Records
////////////////
param srvRecords = [
// dnsZones[0].name = mypublicwish-1.com
{
key: 'srv1'
recordName: '_starssip._tcp.umbrellacorp.com'
zoneName: dnsZones[0].name
ttl: 60
values: [
{
priority: 100
weight: 10
port: 443
target: 'sipfed.online.umbrellacorp.com'
}
]
}
{
key: 'srv2'
recordName: '_motleyfool._tcp.umbrellacorp.com'
zoneName: dnsZones[0].name
ttl: 60
values: [
{
priority: 100
weight: 10
port: 443
target: 'motelusmodules.online.uccreelsoft.com'
}
]
}
// dnsZones[0].name = mypublicwish-1.com
{
key: 'srv3'
recordName: '__starssip._tls.extenzaLife.com'
zoneName: dnsZones[1].name
ttl: 60
values: [
{
priority: 100
weight: 10
port: 443
target: 'sipdir.online.extenzaLife.com'
}
{
priority: 105
weight: 13
port: 443
target: 'sipdir.online.extenzaLife2.com'
}
]
}
]
Bits and That Technology BlogThe main.bicep file puts everything together, the variables, the modules and any relevant outputs; however in this case, there are none. The dns and record modules leverage a for statement to loop the relevant values. Additionally, dependOn has been added to each module to ensure that zones are created after the zones. Since the loop does not create an implicit dependency, this is a requirement.
targetScope = 'subscription'
param aRecords array
param mxRecords array
param txtRecords array
param cNameRecords array
param srvRecords array
param dnsZones array
module PubDnsRg 'Modules/resourceGroupModule.bicep' = {
name: 'pubDNStest'
params: {
location: 'Central US'
rgName: 'pubDNStest'
}
}
module dnsZone 'Modules/dnsZoneModule.bicep' = [for zone in dnsZones: {
name: zone.name
scope: resourceGroup(PubDnsRg.name)
params: {
zoneName: zone.name
}
}]
module aRecord 'Modules/aRecordModule.bicep' = [for record in aRecords: {
name: record.name
scope: resourceGroup(PubDnsRg.name)
params: {
recordName: record.name
zoneName: record.zoneName
ipv4Address: record.ip
ttl: record.ttl
}
dependsOn: [
dnsZone
]
}]
module cNameRecord 'Modules/cnameRecordModule.bicep' = [for record in cNameRecords: {
name: record.name
scope: resourceGroup(PubDnsRg.name)
params: {
recordName: record.name
zoneName: record.zoneName
cname: record.target
ttl: record.ttl
}
dependsOn: [
dnsZone
]
}]
module mxRecord 'Modules/mxRecordModule.bicep' = [for (record, index) in mxRecords: {
name: record.key
scope: resourceGroup(PubDnsRg.name)
params: {
ttl: record.ttl
mxRecords: record.values
recordName: record.recordName
zoneName: record.zonename
}
dependsOn: [
dnsZone
]
}]
module txtRecord 'Modules/txtRecordModule.bicep' = [for (record, index) in txtRecords: {
name: record.key
scope: resourceGroup(PubDnsRg.name)
params: {
ttl: record.ttl
txtRecords: record.targets
recordName: record.recordName
zoneName: record.zonename
}
dependsOn: [
dnsZone
]
}]
module srvRecord 'Modules/srvRecordModule.bicep' = [for (record, index) in srvRecords: {
name: record.key
scope: resourceGroup(PubDnsRg.name)
params: {
ttl: record.ttl
srvRecords: record.values
recordName: record.recordName
zoneName: record.zonename
}
dependsOn: [
dnsZone
]
}]
Bits and That Technology BlogI decided to leverage the .bicepparam format for the variables in this lab instead of the traditional JSON file. This is a newer format. If you run into an issue deploying this lab, you may need to update AZ CLI to the latest version.
Leave a Reply