I deployed a managed Azure SQL Database via the az CLI with server-level firewall rules and least-privilege contained users. Scripting the provisioning made a secured PaaS database repeatable and reviewable instead of click-driven.

Objective & Context

Azure SQL is a managed PaaS database; security depends on firewall scoping and login least privilege, not host hardening. This lab provisions a logical server and database, restricts network access, and creates non-admin application users, aligning to CIS Azure data recommendations.

Learning Objectives

  • Provision a logical server and database with the az CLI.
  • Restrict network access with server firewall rules.
  • Create contained, least-privilege database users.
  • Choose a service tier appropriate to the workload.
  • Script provisioning so it is repeatable and reviewable.

Environment & Prerequisites

Prerequisites

  • An Azure subscription with the az CLI authenticated (az login)
  • A resource group and a naming convention decided upfront
  • Your client public IP for the firewall allow rule
  • Assumed skill: basic CLI usage and SQL GRANT concepts

Step-by-Step Execution

1. Create the logical server and database

az sql server create -g rg -l canadacentral -n tyf-sql --admin-user adm --admin-password '****' && az sql db create -g rg -s tyf-sql -n appdb --service-objective S0

Expected output:

Database appdb provisioned (S0).

What this teaches: Scripted provisioning makes the database repeatable and reviewable; the S0 tier is a sane starting point you scale after measuring. --location is a required parameter; canadacentral keeps the data in Canada for residency requirements. The command also fails without an authenticated CLI, so run az login first on a fresh machine.

2. Restrict network access

az sql server firewall-rule create -g rg -s tyf-sql -n client --start-ip-address 203.0.113.10 --end-ip-address 203.0.113.10

Expected output:

(firewall rule created)

What this teaches: A single-IP rule is the minimal viable network posture; 'allow Azure services' and 0.0.0.0/0 are conveniences with real attack surface.

3. Create a least-privilege user

CREATE USER app WITH PASSWORD='****'; ALTER ROLE db_datareader ADD MEMBER app;
Command(s) completed successfully.

Expected output:

(user app created and added to db_datareader)

What this teaches: Contained users live in the database, not the server, so credentials move with the database and the app gets read-only by default. Run this T-SQL connected to appdb (for example sqlcmd -S tyf-sql.database.windows.net -d appdb), not to master; a contained user created in master does not exist in appdb.

4. Test connectivity as the app user

sqlcmd -S tyf-sql.database.windows.net,1433 -d appdb -U app -Q "SELECT TOP (1) name FROM sys.tables;"

Expected output:

name
----
orders
(1 row affected)

What this teaches: Azure SQL listens on TCP 1433, and the comma syntax passes the port to sqlcmd. sqlcmd prompts for the password interactively, so the secret never appears in shell history. A successful query from the allowed IP proves the firewall rule and the contained login both work; run the same command from a non-allowed IP to watch it time out.

How It Works

How It Works

Azure SQL Database is PaaS: Microsoft runs the engine, patching, and HA, while you manage schema, users, and firewall rules. The logical server is an administrative container; the database is the billable, tunable unit inside it.

Network security is layered: server firewall rules filter by IP before authentication is even attempted. Deny-by-default with explicit allow rules shrinks the reachable surface to exactly your clients, and private endpoints remove public reachability entirely.

Contained database users authenticate at the database level rather than the server level. That decouples credentials from the logical server, so the database (and its users) can move between servers without recreating logins, and each app gets only the roles it needs.

Validation & Testing

Connect from the allowed IP with the app login and confirm read access works while writes are denied; connect from a non-allowed IP and confirm it is blocked. Pass criteria: firewall enforces source IP and the app user holds only granted roles.

Advanced: Troubleshooting
  • Cannot connect: add the client IP firewall rule and confirm "Allow Azure services" only if intended.
  • Over-privileged login: avoid using the server admin for apps; create scoped users.
  • Throttling: the service objective (DTU/vCore) may be undersized for the workload.

Troubleshooting

SymptomLikely causeFix
connection timeout from the clientthe client IP is not in any firewall ruleadd the current public IP; verify with az sql server firewall-rule list
login failed for usercontained user created in the wrong database or password mismatchconnect to the target database (not master) and recreate the user carefully
az sql commands not foundthe sql extension or an old CLI versionrun az upgrade and ensure the sql command group is available
S0 throttles under loadDTU limit hit during a burstcheck sys.dm_db_resource_stats, then scale to S1/S2 or vCore based on the bottleneck
'allow Azure services' questioned in reviewit opens the server to all of Azure, not just your resourcesreplace with private endpoint or explicit IPs and document the exception

Key Results

  • Provisioned a secured Azure SQL database from a repeatable CLI script.
  • Restricted network access to a single explicit source IP.
  • Granted the app login read-only, removing admin over-privilege.
  • Made the deployment reviewable and reproducible versus portal clicks.

Key Takeaways

  • Script provisioning with the az CLI: repeatable beats click-ops.
  • Firewall rules default-deny; allow only known client IPs.
  • Contained users decouple credentials from the logical server.
  • Grant least privilege: db_datareader first, expand deliberately.
  • Pick the tier from measured resource stats, not guesses.

Practice Challenges

Challenge 1: Private endpoint

Goal: Replace the public firewall rule with a private endpoint in your VNet and prove public connectivity fails.

Hint: Create the private endpoint, approve it, then test connection from outside the VNet.

Challenge 2: Failover drill

Goal: Configure an auto-failover group and simulate a failover, measuring the reconnection behaviour.

Hint: Use a secondary in another region; watch the app reconnect to the new primary.