Summary

I recently stumbled upon Is it on AWS? and its accompanying backstory. It sounded like a fascinating way to create a small, focused website. I thought it would be a positive learning experience to create a similar site with Microsoft Azure.

Preparing our checklist of IPs

As stated earlier, Microsoft provides a weekly list of IP ranges for its Azure datacenters in an XML document, broken down first by region and then by IP range in CIDR notation. Not the easiest format to work with. This in itself would be a great opportunity to employ a function to obtain, convert and apply the latest IP ranges each week - but this is not essential for now, so a simple LINQPad script is sufficient.

I’ve used a uint to store the lower and upper-bound thresholds for a range, which is enough for these 32-bit IPv4 addresses - but that would need to be improved when and if we are to support 128-bit IPv6 addresses.

Also of note is that there doesn’t seem to be any native .NET libraries for working with CIDR notation, so I’ve included the IPNetwork2 library to assist.

The result is a single JSON file containing a single flat array of ranges like so:

Building the function

Back in the portal again, it’s time to create the function that will actually serve our website. Starting with the HttpTrigger-CSharp template and an Anonymous authorization level, I’ve created a function named Content. For an initial request of the webpage, it does roughly the following:

Reads a local index.html file;

Creates an initial HTTP response;

Sets the content of the response to the content of our HTML file; and

Returns our response.

On this initial page an end user can specify a single URL, hostname or IP to check. This time our function does the following:

Reads a local index.html file;

Creates an initial HTTP response;

Performs a DNS lookup for the relevant IP address (if a URL or hostname is specified);

Exposing the function

Now that our function is complete, we need a way of exposing it to the world. Let’s start by making it accessible on the default isitinthecloud.azurewebsites.net we’ve been provided.

By default our function is accessible at /api/Content, but what we really want is to serve requests on the root domain. Removing the /api component can be done by editing the host.json file contained within the wwwroot folder of our App Service. I did this by using the App Service Editor.

{"http":{"routePrefix":""}}

Getting rid of the /Content portion is not as straightforward. By default there is a “Your Function App is up and running” placeholder at the root of our site. It can apparently be disabled with a AzureWebJobsDisableHomepage flag in our app settings, but this didn’t seem to do this trick; neither did changing the route of the function to accept just an optional, dummy parameter.

To solve this I opted in to the new Azure Functions Proxies functionality (currently in preview). Creating a function proxy with a * route and backend URL http://isitonazure.azurewebsites.net/Content allows us to expose our site the way we want. Here is our resulting proxies.json and website:

A custom domain is much nicer than the default we’re provided. Rather than detailing this process in its entirity, you can do what I did and follow the documentation provided. Without too much trouble we arrive at:

Applying HTTPS

Our site is now up and running and we could stop here - but it’s 2017 and it’s never been easier to obtain an SSL certificate. Let’s Encrypt to the rescue!

While Azure (sadly) doesn’t have any native one-click setup for obtaining a Let’s Encrypt cert, they appear to be content with a community-provided solution. Once again, rather than stepping through the process in its entirity I recommend following an existing tutorial. I used this blog by Shaun Luttin (which itself is a set of high-level notes from Troy Hunt and the official documentation). This worked equally well for our Function App right up until the point of actually obtaining our certificate, at which point we run in to a problem.

Let’s Encrypt needs to verify the authenticity of our request by exposing a series of files at the /.well-known/acme-challenge/ path of our site. But our Function App isn’t set up to serve just any arbitrary file. I expect this could be solved with a temporary site to serve the files, but I wanted to try solving it with another function instead. A second function with a route template of .well-known/acme-challenge/{challengeId} and the following code does the trick:

To make this accessible I had to temporarily disable the earlier proxy function while the certificate was obtained. I also disabled this function immediately afterwards, as in its current form I suspect it’s open to Path Traversal attacks.

Conclusion

Our site is now complete! There appears to be a warm-up time before the site is accessible after any period of activity, and I’d love to eliminate or minimise this if possible. Likewise there’s an odd ms-ext-routing error that pops up from time to time which requires a page refresh to resolve.

I can already think of a number of other changes or improvements that could be made to the site, such as:

Continuous deployment - given that this restricts the editing of functions in the portal, I’m hesitant to do this until testability is improved;

IPv6 support for Azure; and

Support for checking other cloud hosting providers.

Developing this site has certainly helped me understand the potential of an offering like this. I would consider using functions again in future once the workflow behind developing, testing and deploying the functions is a bit more flexible - for instance, I’d love to use Visual Studio Code on any O/S rather than full-blown Visual Studio.