Azure DNS is a simple hosting service Microsoft offers in Azure. While it does not have TLD registration built in, it does serve and manage namespaces pointed to it. This can be used for a variety of things including App Service domains. Let's point a fresh domain to Azure DNS then use it on an App Service presently running in Azure.
Setup
The first step you need to do is to setup an Azure DNS zone.
then enter the Domain you are adding
and once created, you'll be presented with a page that shows the NS you will need to add at your registrar.
Updating Registrar
Next we change Gandi.net to use Azure DNS hosts we see above
Using Azure DNS: Appling a CNAME to Azurewebsites endpoints
Say you have an App Service or Azure Function and would like to point your own domain name to it.
Part of adding a custom domain to an App is to verify domain ownership:
Back on Azure DNS side, we add a record set for our app
And to verify, we use the TXT entry from the above Domain Ownership image.
Now we can see the listing as such:
This can take up to 48h to verify. However, when i tested, i found it was immediately able to validate (likely since Microsoft checks Azure DNS first):
So now that we validated, click add custom domain to finish.
Unfortunately if we want to get valid TLS we have to upgrade to a nonshared tier (not D1 or F1) AND pay $70 or more for an App Service cert:
That said, we can see that we have standard HTTP now working on an azure function using Azure DNS:
But Isaac, you say, I don't want to spend $70 a year to buy a cert for a free tier function. I get it, i do... So let's use LetsEncrpt to get us a free one
Getting Free Certs
Download certbot or install. Here i'll install wiht homebrew
$ brew install certbot
Error: homebrew-core is a shallow clone. To `brew update` first run:
git -C "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core" fetch --unshallow
This restriction has been made on GitHub's request because updating shallow
clones is an extremely expensive operation due to the tree layout and traffic of
Homebrew/homebrew-core. We don't do this for you automatically to avoid
repeatedly performing an expensive unshallow operation in CI systems (which
should instead be fixed to not use shallow clones). Sorry for the inconvenience!
==> Downloading https://homebrew.bintray.com/bottles/augeas-1.12.0.catalina.bott
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/00a45b8b446df0a95c2c4
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/dialog-1.3-20201126.catalin
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/6fc24c87e6cf32e7702c0
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/certbot-1.10.1.catalina.bot
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/ffce387c91071c6480790
######################################################################## 100.0%
==> Installing dependencies for certbot: augeas and dialog
==> Installing certbot dependency: augeas
==> Pouring augeas-1.12.0.catalina.bottle.tar.gz
==> Caveats
Lenses have been installed to:
/usr/local/share/augeas/lenses/dist
==> Summary
🍺 /usr/local/Cellar/augeas/1.12.0: 473 files, 3.5MB
==> Installing certbot dependency: dialog
==> Pouring dialog-1.3-20201126.catalina.bottle.tar.gz
🍺 /usr/local/Cellar/dialog/1.3-20201126: 14 files, 919KB
==> Installing certbot
==> Pouring certbot-1.10.1.catalina.bottle.tar.gz
🍺 /usr/local/Cellar/certbot/1.10.1: 1,599 files, 15MB
==> `brew cleanup` has not been run in 30 days, running now...
Removing: /Users/johnsi10/Library/Caches/Homebrew/azure-cli--2.16.0.catalina.bottle.tar.gz... (25.7MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/ffmpeg--4.3.1_4.catalina.bottle.tar.gz... (20.7MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/glib--2.66.2_1.catalina.bottle.tar.gz... (4.5MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/go--1.15.5.catalina.bottle.tar.gz... (153.9MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/gobject-introspection--1.66.1_1.catalina.bottle.tar.gz... (1.8MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/libass--0.15.0.catalina.bottle.tar.gz... (216.6KB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/libbluray--1.2.1.catalina.bottle.tar.gz... (1MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/libtiff--4.1.0_1.catalina.bottle.tar.gz... (1.1MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/oci-cli--2.16.1.catalina.bottle.tar.gz... (10.9MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/python@3.8--3.8.6_1.catalina.bottle.tar.gz... (16.8MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/python@3.8--3.8.6_2.catalina.bottle.tar.gz... (16.8MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/python@3.9--3.9.0_5.catalina.bottle.tar.gz... (17.9MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/python@3.9--3.9.0_1.catalina.bottle.tar.gz... (17.9MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/rke--1.2.3.catalina.bottle.tar.gz... (10.1MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/sqlite--3.34.0.catalina.bottle.tar.gz... (2.0MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/thefuck--3.30_1.catalina.bottle.tar.gz... (2.3MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/x264--r3027_1.catalina.bottle.tar.gz... (2.3MB)
Removing: /Users/johnsi10/Library/Caches/Homebrew/x265--3.4_1.catalina.bottle.tar.gz... (5MB)
Removing: /Users/johnsi10/Library/Logs/Homebrew/terraform... (119B)
==> Caveats
==> augeas
Lenses have been installed to:
/usr/local/share/augeas/lenses/dist
Verification
$ certbot --version
certbot 1.10.1
Run certbot in manual mode with DNS challenge
$ sudo certbot certonly --manual -d tpkfn.tpk.pw --preferred-challenges dns
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Requesting a certificate for tpkfn.tpk.pw
Performing the following challenges:
dns-01 challenge for tpkfn.tpk.pw
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.tpkfn.tpk.pw with the following value:
1L4fqN5JXLjwX4zlLbRzy8ewvh14XmdOsZaR_H-mo2Q
Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
Then create in Azure DNS:
Then hit enter in certbot to continue
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/tpkfn.tpk.pw/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/tpkfn.tpk.pw/privkey.pem
Your cert will expire on 2021-07-15. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
And now we have a valid SSL cert:
$ sudo ls /etc/letsencrypt/live/tpkfn.tpk.pw/
README cert.pem chain.pem fullchain.pem privkey.pem
Now we can create a password protected PFX to upload
$ sudo openssl pkcs12 -export -out tpkfn.pfx -inkey /etc/letsencrypt/live/tpkfn.tpk.pw/privkey.pem -in /etc/letsencrypt/live/tpkfn.tpk.pw/cert.pem -certfile /etc/letsencrypt/live/tpkfn.tpk.pw/chain.pem -password pass:tpkftw8869
I just made up a password of tpkftw8869.. Any string will do
$ ls -l *.pfx
-rw-r--r-- 1 root staff 4085 Apr 16 06:49 tpkfn.pfx
Appling the Cert
Back in Azure, in the Function App, Settings/Custom Domains, lets add a binding on the "not secure" area
Upload a pfx file
We then browse and pick the file, and also set the same password (tpkftw8869)
On the next screen, just choose the values in the dropdowns (should just be one)
And click add binding...
We now see that is secured (at least for a year)
Verification
And what's more, i can see this was holding to my low/free tier dynamic plan EastUSLinuxDynamicPlan (Y1: 0)
The whole lot of which is effectively free
This Y1 plan has some limitations:
$ az appservice plan show -n EastUSLinuxDynamicPlan -g theprincessking
{
"freeOfferExpirationTime": null,
"geoRegion": "East US",
"hostingEnvironmentProfile": null,
"hyperV": false,
"id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/theprincessking/providers/Microsoft.Web/serverfarms/EastUSLinuxDynamicPlan",
"isSpot": false,
"isXenon": false,
"kind": "functionapp",
"location": "East US",
"maximumElasticWorkerCount": 1,
"maximumNumberOfWorkers": 0,
"name": "EastUSLinuxDynamicPlan",
"numberOfSites": 1,
"perSiteScaling": false,
"provisioningState": "Succeeded",
"reserved": true,
"resourceGroup": "theprincessking",
"sku": {
"capabilities": null,
"capacity": 0,
"family": "Y",
"locations": null,
"name": "Y1",
"size": "Y1",
"skuCapacity": null,
"tier": "Dynamic"
},
"spotExpirationTime": null,
"status": "Ready",
"subscription": "d955c0ba-13dc-44cf-a29a-8fed74cbb22d",
"tags": null,
"targetWorkerCount": 0,
"targetWorkerSizeId": 0,
"type": "Microsoft.Web/serverfarms",
"workerTierName": null
}
Since Y1 is not listed when creating an app service plan, i had to do some looking to see that it's the Dynamic plan associated to the consumption model (pay for what you use)
Summary
Here we setup Azure DNS then used it to point a subdomain at a simple App Service plan serverless app. In order to secure it, cheaply, we used certbot locally to get a free LetsEncrypt SSL cert and applied as a binding to the app enabling properly signed SSL traffic on our DNS to route to our app.
It was pointed out to my by a colleague that Azure does have an "App Service Domain" option now to purchase a new TLD through Azure. However, based on the Terms of Service, this is really a marketplace option hosted by godaddy