The world of database security and protection can be a difficult path to tread at times. I often find myself having to adopt a “tin-foil hat” approach, obsessing over the smallest potential vulnerability that a database could be compromised with. This thought process can be considered easy compared with any protective steps that need to be implemented in practice, as these can often prove to be mind-bogglingly convoluted. This is one of the reasons why I like working with Microsoft Azure and features such as Azure SQL Database Firewall Rules. They present a familiar means of securing your databases to specific IP address endpoints and are not inordinately complex in how they need to be approached; just provide a name, Start/End IP range and hey presto! Your client/ application can communicate with your database. The nicest thing about them is that the feature is enabled by default, meaning you don’t have to worry about designing and implementing a solution to restrict your database from unauthorised access at the outset.
As alluded to above, Database Firewall Rules are added via T-SQL code (unlike Server Rules, which can be specified via the Azure portal), using syntax that most SQL developers should feel comfortable using. If you traditionally prefer to design and build your databases using a Visual Studio SQL Database project, however, you may encounter a problem when looking to add a Database Firewall rule to your project. There is no dedicated template item that can be used to add this to the database. In this eventuality, you would have to look at setting up a Post-Deployment Script or Pre-Deployment Script to handle the creation of any requisite rules you require. Yet this can present the following problems:
- Visual Studio will be unable to provide you with the basic syntax to create the rules.
- Related to the above, Intellisense support will be limited, so you may struggle to identify errors in your code until it is deployed.
- When deploying changes out to your database, the project will be unable to successfully detect (and remove) any rules that are deleted from your project.
The last one could prove to be particularly cumbersome if you are tightly managing the security of your Azure SQL database. Putting aside the obvious risk of someone forgetting to remove a rule as part of a deployment process, you would then have to manually remove the rules by connecting to your database and executing the following T-SQL statement:
EXECUTE sp_delete_database_firewall_rule 'MyDBFirewallRule'
Not the end of the world by any stretch, but if you are using Visual Studio as your deployment method for managing changes to your database, then having to do this step seems a little counter-intuitive. Fortunately, with a bit of creative thinking and utilisation of more complex T-SQL functionality, we can get around the issue by developing a script that carries out the following steps in order:
- Retrieve a list of all current Database Firewall Rules.
- Iterate through the list of rules and remove them all from the database.
- Proceed to re-create the required Database Firewall Rules from scratch
The second step involves the use of a T-SQL function that I have traditionally steered away from using - Cursors. This is not because they are bad in any way but because a) I have previously struggled to understand how they work and b) have never found a good scenario in which they could be used in. The best way of understanding them is to put on your C# hat for a few moments and consider the following code snippet:
string[] array = new string[] { "Test1", "Test2", "Test3" };
foreach(string s in array)
{
Console.WriteLine(s);
}
To summarise how the above works, we take our collection of values - Test1, Test2 and Test3 - and carry out a particular action against each; in this case, print out their value into the console. This, in a nutshell, is how Cursors work, and you have a great deal of versatility on what action you take during each iteration of the “loop”.
With a clear understanding of how Cursors work. the below script that accomplishes the aims set out above should hopefully be a lot clearer:
DECLARE @FirewallRule NVARCHAR(128)
DECLARE REMOVEFWRULES_CURSOR CURSOR
LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
SELECT DISTINCT [name]
FROM sys.database_firewall_rules
OPEN REMOVEFWRULES_CURSOR
FETCH NEXT FROM REMOVEFWRULES_CURSOR INTO @FirewallRule
WHILE @@FETCH_STATUS = 0
BEGIN
EXECUTE sp_delete_database_firewall_rule @FirewallRule
PRINT 'Firewall rule ' + @FirewallRule + ' has been successfully deleted.'
FETCH NEXT FROM REMOVEFWRULES_CURSOR INTO @FirewallRule
END
CLOSE REMOVEFWRULES_CURSOR
DEALLOCATE REMOVEFWRULES_CURSOR
GO
EXECUTE sp_set_database_firewall_rule @name = N'MyDBFirewallRule1',
@start_ip_address = '1.2.3.4', @end_ip_address = '1.2.3.4';
EXECUTE sp_set_database_firewall_rule @name = N'MyDBFirewallRule2',
@start_ip_address = '1.2.3.4', @end_ip_address = '1.2.3.4';
To integrate as part of your existing database project, add a new Post-Deployment Script file and modify the above to reflect your requirements. As the name indicates, the script will run after all other aspects of your solution deployment has been completed. Now, the key caveat to bear in mind with this solution is that, during deployment, there will be a brief period of time where all Database Firewall Rules are removed from the database. This could potentially prevent any current database connections from dropping or failing to connect altogether. You should take care when using the above code snippet within a production environment and I would recommend you look at an alternative solution if your application/system cannot tolerate even a second of downtime.