Azure .NET SDK - Custom Rules for Web Application Firewall

How to view, create and manage custom rules for an azure web application firewall using the .NET SDK

·

4 min read

A repeating question is when is the correct time to automate some piece of functionality. We start by manually making infrastructure changes directly in Azure, but as teams and systems grow we need to shift to something more automated and maintainable.

Azure gives multiple options. Often the first one considered is the Azure Rest Api. But Microsoft also have an Azure .NET SDK which is designed to make it easy to interact with Azure services from .NET applications. The biggest challenge I have found with the SDK so far is that I know how to manually do what I want, but I can't find the correct way to do it in the SDK due to lack of documentation.

The specific example I would like to examine focuses on managing custom rules on a Web Application Firewall. Microsoft do give some examples that use the REST API but at the time of writing I can find no equivalent information on how to do this with the SDK. Seeing as they have made a .NET SDK available we should strive to use it to make our lives a little easier (in theory).

Scenario

We have code that can identify malicious bots that are hitting our application. We know there's a bot named evilbot and that all requests from this evilbot are coming from 198.168.5.0. To protect our systems, we want to block all requests coming from this IP address in the web application firewall. Of course we can do this manually, but as more bots might appear an automated solution might be preferable.

The code examples given will be using hardcoded values for clarity. You should tweak them to match your system's coding practices which hopefully include things like using dependency injection and key vaults etc.

Get the Web Application Firewall Resource

The first thing we must do is retrieve the web application resource allowing us to examine the existing rules and make the changes we want.

using Azure;
using Azure.Core;
using Azure.Identity;
using Azure.ResourceManager;
using Azure.ResourceManager.Network;

// you can get your resource id in azure by navigating to your web application firewall > Overview > JSON View
string resourceId = "my-resource-id";

var armClient = new ArmClient(new DefaultAzureCredential());
WebApplicationFirewallPolicyResource policy = 
armClient.GetWebApplicationFirewallPolicyResource(new ResourceIdentifier(resourceId));
Response<WebApplicationFirewallPolicyResource> response = await policy.GetAsync();
WebApplicationFirewallPolicyData data = response.Value.Data

The ArmClient is used to interact with the Azure Resource Management API. We won't get into the different ways to authenticate here as that is confusing enough at the best of times, and Microsoft have actually documented it quite extensively here.

Create New Custom Rule

We have no existing custom rule in place, so we need to setup a new custom rule initially. Once the rule is in place, we can update it with new IP addresses as those become apparent.

Probably the best way to get an idea for how the different classes work is to examine the contents of some existing rules you have manually set up in the data.CustomRules property.

var matchVariables = new List<MatchVariable>
{
    new(WebApplicationFirewallMatchVariable.RemoteAddr)
};

// Match condition to say we want to create a rule based on IpAddress of requests
var matchCondition = new List<MatchCondition>
{
    new MatchCondition (
      matchVariables, 
      WebApplicationFirewallOperator.IPMatch, 
      new List<string>(){ newIpAddressToBlock })
};

// Priority of 1 ensures this rule takes precedent over other rules. 
// If we want this to not be the case, we need to alter the priority accordingly.
int priority = 1;

// Create the rule. When a request matches the match condition it will be blocked.
var rule = new WebApplicationFirewallCustomRule(
    priority,
    WebApplicationFirewallRuleType.MatchRule,
    matchCondition ,
    WebApplicationFirewallAction.Block)
{
    Name = "evilBotRule"
};

// Add the new rule to the WAF and save in azure
data.CustomRules.Add(newRule);
await policy.UpdateAsync(WaitUntil.Started, data);

Check Existing Custom Rules

Now we have a custom rule in place, we want to continue to block this evilBot everytime it changes IP. So we want to check if the IP address identified from the bot is already blocked or not.

// Get the existing rule
WebApplicationFirewallCustomRule existingRule = data.CustomRules.First(a => a.Name == "evilBotRule");
// Get the match condition that is blocking on Ip
MatchCondition matchCondition = existingRule.MatchConditions.First(a => a.Operator == WebApplicationFirewallOperator.IPMatch));

// Get ips that are already blocked
IEnumerable<string> ipsAlreadyBlocked = matchCondition.MatchValues;

Update Existing Custom Rule

Finally, we have confirmed that evilBot is using a new IP address, so we now want to block it to protect our application without manually having to check when the bot changes IP addresses.

foreach(string ip in newIpsToBlock)
{
    if(!ipsAlreadyBlocked.Contains(ip))
    {
      // Add new ip
      matchCondition.MatchValues.AddRange(ip);
    }
}

// Save changes to azure
policy.UpdateAsync(WaitUntil.Started, data)