Posted: Saturday, January 27, 2024
Word Count: 3046
Reading Time: 14 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 terraform 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 |
Terraform Commands | Terraform Init Terraform Plan -out=PubDNSLab Terraform Apply -out=PubDNSLab |
Variables and Locals that are not relevant to the creation of DNS Records or zones are stored here. This file sets, resource location, subscription ID, and a few tags for testing purposes.
## Vars
variable "location" {
default = "centralus"
}
variable "department" {
default = "awesome"
}
variable "environment" {
default = "nonprod"
}
variable "subscription_id" {
default = "SUBSCRIPTION_ID_HERE"
}
## Locals
data "external" "me" {
program = ["az", "account", "show", "--query", "user"]
}
locals {
dns_resource_group_name = "my-publicdns-lab"
Owner = lookup(data.external.me.result, "name")
localuser = split("@", local.Owner)
tags = {
Environment = var.environment
Createdby = lookup(data.external.me.result, "name")
LastModifiedDate = formatdate("DD MMM YYYY hh:mm ZZZ", timestamp())
Department = var.department
}
}
Bits and That Technology BlogTerraform providers are plugins that Terraform uses to interact with remote systems. Each provider offers a collection of resource types and data sources that Terraform can manage. Providers are responsible for understanding API interactions and exposing resources. They are usually developed by vendors of the service or by the Terraform community.
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0.2"
}
}
required_version = ">= 1.1.0"
}
provider "azurerm" {
subscription_id = var.subscription_id
features {}
}
Bits and That Technology BlogAs you would expect the modules are configured in a similar fashion. Variables, Resources and outputs are all consolidated into a single tf file. The resources are leveraging a for_each statement to loop through an array of variables to create the related resources. Additionally, you will notice a key has been added to each variable file.
This module is foundational to everything else , as it creates the resource group that will host everything in this lab.
## Variables
variable "location" {}
variable "DnsResourceGroup" {}
variable "tags" {}
## Resources
resource "azurerm_resource_group" "public_dns_zone" {
name = var.DnsResourceGroup
location = var.location
tags = var.tags
}
## Outputs
output "name" {
value = azurerm_resource_group.public_dns_zone.name
}
output "id" {
value = azurerm_resource_group.public_dns_zone.id
}
output "location" {
value = azurerm_resource_group.public_dns_zone.location
}
output "tags" {
value = azurerm_resource_group.public_dns_zone.tags
}
Bits and That Technology BlogThe DNS zone module is leveraged to create all required DNS zones in the azure subscription. In this lab, we create three public DNS zones: massivedynamics.com, umbrellacorp.com and initech.com. The zones are fed into an output array which is referenced by the modules that create the corresponding records in each zone.
variable resource_group_name {}
variable "dnszones" {
type = list(object({
key = string
name = string
}))
}
resource "azurerm_dns_zone" "zones" {
for_each = { for idx, record in var.dnszones : idx => record }
name = each.value.name
resource_group_name = var.resource_group_name
}
output "ids" {
value = { for key, zone in azurerm_dns_zone.zones : key => zone.id }
}
output "all_zone_names" {
value = { for key, zone in azurerm_dns_zone.zones : key => zone.name }
}
Bits and That Technology Blog#Zones
locals {
dnszones = [
{
key = "000"
name = "massivedynamics.com"
},
{
key = "001"
name = "umbrellacorp.com"
},
{
key = "002"
name = "initech.com"
},
# Add more dns zones as needed
]
}
Bits and That Technology BlogEvery module in this section is tasked with creating records in the zones listed above. There are modules for A, CNAME, MX, SRV and TXT records. Each modules created that related records for all zones.
This module and variable files in this section will create all host (a) records across each DNS zone.
## Variables
variable "resource_group_name" {}
variable "host_records" {
type = list(object({
key = string
a_record = string
zone = string
ttl = number
values = list(string)
}))
}
## Resources
resource "azurerm_dns_a_record" "arecords" {
for_each = { for record in var.host_records: record.key => record }
name = each.value.a_record
zone_name = each.value.zone
resource_group_name = var.resource_group_name
ttl = each.value.ttl
records = each.value.values
}
## Output
output "name" {
value = {
for key, record in azurerm_dns_a_record.arecords: key => record.name}
}
output "zone_name" {
value = {
for key, record in azurerm_dns_a_record.arecords: key => record.zone_name}
}
output "resource_group_names" {
value = {
for key, record in azurerm_dns_a_record.arecords: key => record.resource_group_name
}
}
output "ttls" {
value = {
for key, record in azurerm_dns_a_record.arecords: key => record.ttl
}
}
output "ttls" {
value = {
for key, record in azurerm_dns_a_record.arecords: key => record.ttl
}
}
Bits and That Technology Blog#############
# A RECORDS
#############
locals {
arecords_list = [
# massivedynamics.com - 0
{
key = "${module.public_zones.all_zone_names[0]}-0"
a_record = "www"
values = ["3.0.0.3"]
zone = module.public_zones.all_zone_names[0]
ttl = 3600
},
{
key = "${module.public_zones.all_zone_names[0]}-1"
a_record = "www2"
values = ["3.0.0.3"]
zone = module.public_zones.all_zone_names[0]
ttl = 3600
},
# umbrellacorp.com - 1
{
key = "${module.public_zones.all_zone_names[1]}-0"
a_record = "www"
values = ["2.0.0.2"]
zone = module.public_zones.all_zone_names[1]
ttl = 3600
},
#Initech.com - 2
{
key = "${module.public_zones.all_zone_names[2]}-0"
a_record = "www"
values = ["1.0.0.1"]
zone = module.public_zones.all_zone_names[2]
ttl = 3600
},
# Add more record sets as needed
]
}
JavaScriptThe module and variable files in this section will create all canonical name (CNAME) records across each DNS zone.
## Variables
variable "resource_group_name" {}
variable "cname_records" {
type = list(object({
key = string
cname_record = string
zone = string
ttl = number
values = list(string)
}))
}
## Resources
resource "azurerm_dns_cname_record" "cnames" {
name = each.value.cname_record
zone_name = var.zone_name
resource_group_name = var.resource_group_name
ttl = var.ttl
record = var.values
}
## Outputs
output "name" {
value = {
for key, record in azurerm_dns_a_record.cnames: key => record.name}
}
}
output "zone_name" {
value = {
for key, record in azurerm_dns_a_record.cnames: key => record.zone_name}
}
output "resource_group_names" {
value = {
for key, record in azurerm_dns_a_record.cnames: key => record.resource_group_name
}
}
output "ttls" {
value = {
for key, record in azurerm_dns_a_record.cnames: key => record.ttl
}
}
Bits and That Technology Blog################
# CNAMES
################
locals {
cnamerecords_list = [
# massivedynamics.com - 0
{
key = "${module.public_zones.all_zone_names[0]}-1"
cname_record = "autodiscover"
values = "autodiscover.massivelogistic.com"
zone = module.public_zones.all_zone_names[0]
ttl = 180
},
# umbrellacorp.com - 1
{
key = "${module.public_zones.all_zone_names[1]}-2"
cname_record = "cdnverify.zealots"
values = "cdnverify.umbrellacorp.cerberus.net"
zone = module.public_zones.all_zone_names[1]
ttl = 180
},
# Initech.com -2
{
key = "${module.public_zones.all_zone_names[2]}-1"
cname_record = "f6hkKeVcvvQ1CHVwca6kNWG8Gp1"
values = "dcv.Initechcert.com"
zone = module.public_zones.all_zone_names[2]
ttl = 3600
},
# Add more record sets as needed
]
}
Bits and That Technology BlogThe module and variable files in this section will create all mail exchanger (MX) records across each DNS zone.
## Variables
variable "resource_group_name" {
type = string
}
variable "mx_records" {
type = list(object({
key = string
mxrecord = string
zone = string
ttl = number
preferences = list(number)
values = list(string)
}))
}
## Resources
resource "azurerm_dns_mx_record" "mx_record" {
for_each = { for record in var.mx_records :
record.key => record
}
name = each.value.mxrecord
zone_name = each.value.zone
resource_group_name = var.resource_group_name
ttl = each.value.ttl
dynamic "record" {
for_each = [for i in range(length(each.value.preferences)) : {
preference = each.value.preferences[i]
value = each.value.values[i]
}]
content {
preference = record.value.preference
exchange = record.value.value
}
}
}
## Outputs
output "name" {
value = {
for key, record in azurerm_dns_mx_record.mx_record: key => record.name}
}
output "zone_name" {
value = {
for key, record in azurerm_dns_mx_record.mx_record: key => record.zone_name}
}
output "resource_group_names" {
value = {
for key, record in azurerm_dns_mx_record.mx_record: key => record.resource_group_name
}
}
output "ttls" {
value = {
for key, record in azurerm_dns_mx_record.mx_record: key => record.ttl
}
}
Bits and That Technology Blog##############
# MX Records
##############
locals {
mxrecords_list = [
#massivedynamics Care - 0
{
key = "${module.public_zones.all_zone_names[0]}-00"
mxrecord = "@"
preferences = [100, 101]
values = ["usb-smtp-inbound-1.massivedynamics.com", "usb-smtp-inbound-2.massivedynamics.com"]
zone = module.public_zones.all_zone_names[0]
ttl = 60
},
# umbrellacorp.com -1
{
key = "${module.public_zones.all_zone_names[0]}-00"
mxrecord = "@"
preferences = [100, 200]
values = ["usb-smtp-inbound-1.nemesis.com", "usb-smtp-inbound-2.nemesis.com"]
zone = module.public_zones.all_zone_names[1]
ttl = 60
},
{
key = "${module.public_zones.all_zone_names[0]}-01"
mxrecord = "gh-mail"
preferences = [113, 140]
values = ["mxa.hypnos.org", "mxb.hypnos.org"]
zone = module.public_zones.all_zone_names[1]
ttl = 60
},
# Initech.com - 2
{
key = "${module.public_zones.all_zone_names[0]}-00"
mxrecord = "@"
preferences = [200, 300]
values = ["usb-smtp-inbound-1.Initech.com", "usb-smtp-inbound-2.Initech.com"]
zone = module.public_zones.all_zone_names[2]
ttl = 60
},
# Add more MX record sets as needed
]
}
Bits and That Technology BlogThe module and variable files in this section will create all text (TXT) records across each DNS zone.
## Variables
variable "resource_group_name" {}
variable "txt_records" {
type = list(object({
key = string
txtrecord = string
zone = string
ttl = number
values = list(string)
}))
}
## Resources
resource "azurerm_dns_txt_record" "txt_records" {
for_each = { for idx, record in var.txt_records : idx => record }
name = each.value.txtrecord
zone_name = each.value.zone
resource_group_name = var.resource_group_name
ttl = 300
dynamic "record" {
for_each = each.value.values
content {
value = record.value
}
}
}
## Outputs
output "name" {
value = {
for key, record in azurerm_dns_txt_record.txt_records: key => record.name}
}
output "zone_name" {
value = {
for key, record in azurerm_dns_txt_record.txt_records: key => record.zone_name}
}
output "resource_group_names" {
value = {
for key, record in azurerm_dns_txt_record.txt_records: key => record.resource_group_name
}
}
output "ttls" {
value = {
for key, record in azurerm_dns_txt_record.txt_records: key => record.ttl
}
}
Bits and That Technology Blog
locals {
# massivedyamics.com - 0
txtrecords = [
{
key = "${module.public_zones.all_zone_names[0]}-0"
txtrecord = "@"
values = [
"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",
"uc=ucJupER9Uyg",
"elppa-domain-verification=MGNcyKAUTC0rrZ",
"MOOZ_verify_649uu9bgwXh7NLKibBgouYcfD5ieT",
"mdfoundation-domain-verification=JTi2sDRuraVFuwF7fxrzFA15zgQDqX0nTFY9x8Qo450qKtRAFhiy36LifU3Z8spksK"
]
zone = module.public_zones.all_zone_names[0]
ttl = 3600
},
{
key = "${module.public_zones.all_zone_names[0]}-1"
txtrecord = "_dmarc"
values = ["v=DMARC1; p=none; rua=mailto:dmarc_agg@massivelogistic.com; ruf=mailto:dmarc_for@massivelogistic.com; fo=1"]
zone = module.public_zones.all_zone_names[0]
ttl = 3600
},
# umbrellacorp.com - 1
{
key = "${module.public_zones.all_zone_names[1]}-0"
txtrecord = "9eqtue6uxumx0efgxrslq._domainkey"
values = ["v=DKIM1;p=DDCyJiiTwoF3B3QLpcWYoXwynBKiC4CkTz5XjLG0rLH6xVhqKUVam0up5cXHyEMNWeruvGaPVeTN4AH4NV1j54TP0eXr1XNyvRER",
"cQEPEA1LAJbLVTpiAbuBWZXKLeEC3yKWNcA0fAT46suFaWBWJFVjHfx4c9XgnUbDjBVXRm0o9uNnrpJXGZwuG3rCUY51ueRcqYs9"]
zone = module.public_zones.all_zone_names[1]
ttl = 3600
},
{
key = "${module.public_zones.all_zone_names[1]}-1"
txtrecord = "@"
values = ["tvVjmvFbDEsDt3PaGmQUH3q7UEQjCbo", "neptune-site-verification=QWn6gJx5P1hcgJXekCczasmw2pMX6NQCZEUu6Fnu5RLbkNJb-r6z3vJaQ",
"md=ucTvcNne0tU", "v=spf1 include:nyx.org include:spf.protection.garrador.com include:usb._netblocks.mimecast.com include:umbrella-com.spf.smtp258.com ~all",
"bandersnatch_verification_token=C24FE082863254F899D681B27847735825", "pluto-site-verification=b875ca0b846dc584f79a94ee1f990d63",
"v=DMARC1; p=quarantine; rua=mailto:dmarc_agg@cockroach.com; ruf=mailto:dmarc_for@cockroach.com; fo=1"
]
zone = module.public_zones.all_zone_names[1]
ttl = 3600
},
{
key = "${module.public_zones.all_zone_names[1]}-2"
txtrecord = "@"
values = ["tvVjmvFbDEsDt3PaGmQUH3q7UEQjCbo", "neptune-site-verification=QWn6gJx5P1hcgJXekCczasmw2pMX6NQCZEUu6Fnu5RLbkNJb-r6z3vJaQ",
"uc=ucTvcNne0tU", "v=spf1 include:nyx.org include:spf.protection.garrador.com include:usb._netblocks.mimecast.com include:umbrella-com.spf.smtp258.com ~all",
"bandersnatch_verification_token=C24FE08286376254F899D68881B27847735825", "pluto-site-verification=b875ca0areiib846dgc584f79a94ee1f990d63",
"v=DMARC1; p=quarantine; rua=mailto:dmarc_agg@cockroach.com; ruf=mailto:dmarc_for@cockroach.com; fo=1"
]
zone = module.public_zones.all_zone_names[1]
ttl = 3600
},
# Initech -2
{
key = "${module.public_zones.all_zone_names[2]}-0"
txtrecord = "_dmarc"
values = ["v=DMARC1; p=quarantine; rua=mailto:dmarc_agg@Initech.com; ruf=mailto:dmarc_for@Initech.com; fo=1"]
zone = module.public_zones.all_zone_names[2]
ttl = 3600
},
# add additional TXT Records Below
]
}
Bits and That Technology BlogThe module and variable files in this section will create all service (SRV) records across each DNS zone.
## Variables
variable "resource_group_name" {
type = string
}
variable "srv_records" {
type = list(object({
key = string
srvrecord = string
zone = string
ttl = number
values = list(string)
priority = list(string)
weight = list(string)
ports = list(string)
}))
}
## Resources
resource "azurerm_dns_srv_record" "srvrecords" {
for_each = { for record in var.srv_records :
record.key => record
}
name = each.value.srvrecord
zone_name = each.value.zone
resource_group_name = var.resource_group_name
ttl = each.value.ttl
dynamic "record" {
for_each = [for i in range(length(each.value.values)) : {
priority = each.value.priority[i]
weight = each.value.weight[i]
target = each.value.values[i]
ports = each.value.ports[i]
}]
content {
priority = record.value.priority
weight = record.value.weight
target = record.value.target
port = record.value.ports
}
}
}
## Outputs
output "name" {
value = azurerm_dns_srv_record.srvrecords
}
output "zone_name" {
value = azurerm_dns_srv_record.srvrecords
}
output "resource_group_name" {
value = azurerm_dns_srv_record.srvrecords
}
output "ttl" {
value = azurerm_dns_srv_record.srvrecords
}
Bits and That Technology Blog##############
# SRV RECORDS
##############
locals {
srv_recordsets = [
# Massive Dynamics - 0
{
key = "${module.public_zones.all_zone_names[0]}-01"
srvrecord = "__starssip._tls.extenzaLife.com"
priority = [100]
weight = [1]
ports = [443]
values = ["sipdir.online.extenzaLife.com"]
zone = module.public_zones.all_zone_names[0]
ttl = 60
},
{
key = "${module.public_zones.all_zone_names[0]}-02"
srvrecord = "__starssip._tls.fleming-monroe.com"
priority = [100]
weight = [1]
ports = [443]
values = ["sipdir.online.fleming-monroe.com"]
zone = module.public_zones.all_zone_names[0]
ttl = 60
},
# Umbrella Corp - 1
{
key = "${module.public_zones.all_zone_names[1]}-01"
srvrecord = "_starssip._tcp.umbrellacorp.com"
priority = [100]
weight = [1]
ports = [5061]
values = ["sipfed.online.umbrellacorp.com"]
zone = module.public_zones.all_zone_names[1]
ttl = 1800
},
{
key = "${module.public_zones.all_zone_names[1]}-02"
srvrecord = "__starssip._tls.umbrellacorp.com"
priority = [100]
weight = [1]
ports = [443]
values = ["sipdir.online.umbrellacorp.com"]
zone = module.public_zones.all_zone_names[1]
ttl = 1800
},
]
}
Bits and That Technology BlogAll of the files above culminate into the main.tf file below. Each module is associated with the relevant locals array.
### Resource Group Creations
module "dns_resource_group" {
source = "./Modules/resourceGroup"
DnsResourceGroup = local.dns_resource_group_name
location = var.location
tags = local.tags
}
### Zone Creations
module "public_zones" {
source = "./Modules/dnsZones"
resource_group_name = module.dns_resource_group.name
dnszones = local.dnszones
}
### Record Creations
module "Arecords" {
source = "./Modules/Records/A"
resource_group_name = module.dns_resource_group.name
host_records = local.a_records_list
}
module "CNAMErecords" {
source = "./Modules/Records/CNAME"
resource_group_name = module.dns_resource_group.name
cname_records = local.cname_records_list
}
module "MXrecords" {
source = "./Modules/Records/MX"
resource_group_name = module.dns_resource_group.name
mx_records = local.mxrecords_list
depends_on = [module.public_zones]
}
module "Srvrecords" {
source = "./Modules/Records/SRV"
resource_group_name = module.dns_resource_group.name
srv_records = local.srv_recordsets
depends_on = [module.public_zones]
}
module "Txtrecords" {
source = "./Modules/Records/TXT"
resource_group_name = module.dns_resource_group.name
txt_records = local.txtrecords
depends_on = [module.public_zones]
}
Bits and That Technology BlogAll done! You are encouraged to augment as you see fit. Also, don’t forget to clean up your subscription one you’re done tinkering. Although, $0.90 shouldn’t break the bank, every little bit helps.
Leave a Reply