Configure Azure Function App for root domain redirection with SSL support
Learn how to redirect custom domain traffic for HTTP and HTTPS (with free valid certificate) to another domain with Azure Functions.
Learn how to redirect custom domain traffic for HTTP and HTTPS (with free valid certificate) to another domain with Azure Functions.
This is a multi part article with the following parts:
- Part 1 - Static site generators
- Part 2 - Setup Azure Storage Account for static websites
- Part 3 - Setup Azure DNS for static websites
- Part 4 - Configure Azure CDN for static websites
- Part 5 - Configure Azure Function App for root domain redirection (You are here)
In this part we will implement an Azure Function to redirect traffic from the root of our staticwebsite.de domain to the www.staticwebsite.de
domain. Then we will use Azure Function proxies and the Let's Encrypt extension in combination with a PowerShell function to get a free SSL certificate.
Introduction
As explained in part 4, it is currently not possible to add root domains like staticwebsite.de
as a custom domain to Azure CDN. So we need a way to forward traffic to the www subdomain and still provide SSL support to satisfy the requirements for hsts preloading.
I had the following ideas:
- Use the rule engine of Azure CDN in Verizon Premium tier to send 301 replies - Does not work, as there is currently no way to add root domains to Azure CDN. None of the known workarounds work anymore.
- Use a function App with consumption plan to return a 301 HTTP status code - Would work
- Use function App with app service plan - Would work.
- App Service with smallest App service plan with SSL support - Would work
I chose the Function App, as it is the cheapest variant and I wanted to test and implement let's encrypt with a PowerShell Function for a long time.
Setup Azure Function App
First we setup the Azure Function App with PowerShell as language worker.
Redirection function
Redirector function
When the Function App is ready, we create a new function called "redirector" where the redirection logic will be implemented. We need a HTTP trigger and a HTTP reply as an output so we just use the HTTP trigger template. We need to create a HTTP reply packet with code 301 for "Moved Permanently" and add the location parameter to the response header. The location parameter tells the browser, where the website has moved to and will directly follow that path.
using namespace System.Net
param($Request, $TriggerMetadata)
$path = $Request.Headers['x-original-url']
$status = [HttpStatusCode]::MOVED
$headers = @{Location = "https://www.staticwebsite.de$path"}
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = $status
Headers = $headers
Body = ''
})
Map custom domain
Regarding to the documentation, it is necessary to add a CNAME for our custom domain. As explained in part 3, this is not possible for root domain (it's why we are doing this in the first place 😉), but with a little trickery we can use another validation method to add our custom domain. If we add our custom domain, the function app IP is shown in the portal. We have to create a A record for @
pointing to this IP. After clicking validate, the portal will tell us that only CNAME is supported for custom domain validation, but will show us the necessary TXT record, if we choose TXT based validation from the dropdown.
DNS entry | type | value |
---|---|---|
www.staticwebsite.de | CNAME | staticwebsitede.azureedge.net |
@ | A | (FunctionAppIp) |
@ | TXT | (FunctionAppUrl) |
The trick is to click validate again and then switch back to CNAME. Now the custom domain has successful been added to the function app.
Add custom domain
Proxy configuration
Function proxy setup
To redirect all traffic from the root domain including everything after the forward slash e.g. http://staticwebsite.de/article01
, we need to create a function proxy that redirects all traffic to our function app. By using the placeholder path, we can later use that part in the code to parse the origin url and append it to the www subdomain. In our example it would redirect to https://www.staticwebsite.de/article01
.
SSL with Let's Encrypt
Our redirection function works as expected, but we have just taken care of the HTTP protocol. We don't want our users to navigate to https://staticwebsite.de and get a certificate error, so we need to add a SSL certificate to our custom domain. We will use the excellent Let's Encrypt extension by sjkp for this.
Install Let's Encrypt extension
To install the Let's Encrypt extension, we navigate to extension, click add and choose the Let's Encrypt extension. Wait for the installation and restart the function app afterwards.
Let's Encrypt extension installation
We verify the successful installation by browsing the Let's Encrypt extension in the portal by navigating to "site extensions -> Azure Let's Encrypt -> Browse". The website should look like this:
Let's Encrypt reply function
We will create a new function in our app to reply to the ACME challenge and thereby validate the ownership of our domain.
There is a functions v1 example for C# in the docs for the Let's Encrypt extension that I used as a template for the following PowerShell function:
using namespace System.Net
param($Request, $TriggerMetadata)
Write-Host "PowerShell HTTP trigger function processed a request."
$code = $Request.Params.rest
$content = [System.IO.File]::ReadAllText("D:\home\site\wwwroot\.well-known\acme-challenge\$code");
$status = [HttpStatusCode]::OK
$body = $content
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = $status
Body = $body
})
Lets Encrypt configuration
Next we need to define a route for the so called "HTTP-01" ACME challenge. As it is a fixed defined path, we have to route traffic to the path http://{YOUR_DOMAIN}/.well-known/acme-challenge/{TOKEN}
directly to our reply function. Read more about how letsencrypt validation works from the Challenge Types Documentation.
So let's create a proxy called "letsencryptAcme" for the redirection with the token parameter captured in the rest
variable. We use this variable in line 7 of our PowerShell function.
Request and install certificate
The last thing we need is an Azure Service Principal with permissions on the resourcegroup where the function app and the App Service plan are located. The process is explained by Microsoft in great detail HERE. Now we have prepared everything for the Let's Encrypt extension to be able to request and install a certificate for our custom domain. To open the web frontend of the Let's Encrypt extension, we navigate to "Site Extensions -> Azure Let's Encrypt -> Browse". We are now presented with a form where we have to insert all the details of our service principal as well as the function app name. If you don't want to type your service principal credentials each time you request or renew a certificate, just check the "Update Application Settings and Virtual Directory (if needed)" checkbox. The settings will then be saved to the application settings of the function app.
Request and install the SSL certificate
The next step is to press next, choose a custom domain and hit the "Request and Install certificate" button. After waiting about a minute, we are presented with our brand new or renewed SSL certificate in the summary.
Summary
We can test the redirection by opening the "Developer Tools" in Firefox, Chrome or Edge and visit https://staticwebsite.de
.
The Response Headers look like this:
HTTP/1.1 301 Moved Permanently
Content-Type: text/plain; charset=utf-8
Location: https://www.staticwebsite.de/
Date: Fri, 26 Jul 2019 19:51:55 GMT
Content-Length: 0
The SSL certificate is acknowledged as valid and has the SHA1 thumbprint from the extensions summary page:
{
"Connection:": {
"Protocol version:": "TLSv1.2",
"Cipher suite:": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"Key Exchange Group:": "P256",
"Signature Scheme:": "RSA-PKCS1-SHA1"
},
"Host staticwebsite.de:": {
"HTTP Strict Transport Security:": "Disabled",
"Public Key Pinning:": "Disabled"
},
"Certificate:": {
"Issued To": {
"Common Name (CN):": "staticwebsite.de",
"Organization (O):": "<Not Available>",
"Organizational Unit (OU):": "<Not Available>"
},
"Issued By": {
"Common Name (CN):": "Let's Encrypt Authority X3",
"Organization (O):": "Let's Encrypt",
"Organizational Unit (OU):": "<Not Available>"
},
"Period of Validity": {
"Begins On:": "Friday, July 26, 2019",
"Expires On:": "Thursday, October 24, 2019"
},
"Fingerprints": {
"SHA-256 Fingerprint:": "56:E1:8A:33:F0:BF:6D:39:E4:BC:88:1E:F3:76:3B:BF:31:49:1A:B6:23:37:E7:59:70:A2:C2:03:81:51:B4:9E",
"SHA1 Fingerprint:": "50:D8:18:90:28:C6:85:28:51:54:AA:B0:9D:56:1F:FE:35:3D:4E:71"
},
"Transparency:": "<Not Available>"
}
}
We now have a function app that listens on HTTP and HTTPS with a valid renewable free SSL certificate that redirects all our traffic to our www subdomain. Feel free to experiment and extend the redirection for your needs!
Configure Azure CDN for static websites
Azure CDN is a great service to add functionality to your website. In this post we will setup a Azure CDN resource, add a custom domain, activate free SSL and take a look at the CDN rules engine.
Template Chooser
In many Office 365 projects, SharePoint migrations or document structure planning, the question often araises, where to store templates so that every user has access to them and, more importantly, always uses the latest version.