As part of my search to provide outbound Deny on an Azure NSG with whitelisted FQDN entries, I started looking at Azure Functions.
The idea is that I would have an Automation runbook on a schedule, which called my function for a variety of domain names, receiving the resolved IP addresses in return. These would then be compared against outbound NSG rules, and if the resolved IP differs from what is in the NSG, it would update it.
In reality there isn’t much need for this, since you can do the DNS resolution right in the runbook with this:
$currentIpAddress = [system.net.dns]::GetHostByName("$fqdn").AddressList.IPAddressToString |
There are other limitations with this idea as well:
- for a globally-managed DNS behind some type of CDN or round-robin mechanism, its possible that IP resolution would continually be different. Take “smtp.office365.com” for example.
- There isn’t a way to manage wildcard whitelists – “*.windowsupdate.com” isn’t something you can resolve to individual IP addresses.
All that being said, I still used this as a learning opportunity for my first function.
To begin, in the Azure Portal I went to the “App Services” blade, clicked “Add”, and searched for Function:
During creation I accepted most of the defaults, and was left with a v2 Function App and the initial “HttpTriggerCSharp1” function.
I am by no means a programmer, and certainly not familiar with C# from ASP.net Core as evidenced by my previous post. With that in mind, here is the contents of my function that I ended up with:
#r "Newtonsoft.Json" using System.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using Newtonsoft.Json; using System.Text; public static async Task<string[]> Run(HttpRequest req, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); string name = req.Query["name"]; string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); name = name ?? data?.name; List collectedIP = new List(); IPAddress[] ipaddresses = null; if (name != null){ try { // Putting this in the Try because if it errored out I wanted to see that // as a defined message rather than failure of the function ipaddresses = Dns.GetHostAddresses(name); } catch (Exception) { log.LogInformation("Did not resolve IP from: " + name); collectedIP.Add("Did not resolve"); } if (ipaddresses != null) { // Knowing that multiple IPs could be returned for a record, used a ForEach foreach (IPAddress ip in ipaddresses) { log.LogInformation("Resolved " + name + " to " + ip.ToString()); // Add the resolved IP to a string list collectedIP.Add(ip.ToString()); log.LogInformation("Added IP to list"); } log.LogInformation("End of If Ipaddresses isn't null"); } log.LogInformation("End of If Name isn't null"); } else { //return a string log.LogInformation("No IP passed In"); collectedIP.Add("No IP passed in"); } log.LogInformation("Ready to return value"); // Return the string list as an array to the calling entity return collectedIP.ToArray(); } |
Now I run this function in Test mode, with a Query parameter as “name”:
$IPList = invoke-webrequest 'https://functionappname.azurewebsites.net/api/HttpTriggerCSharp1?code=<privatekey>&name=www.microsoft.com' |