A complete lab environment for testing and learning Azure DNS Security Policies with comprehensive monitoring capabilities. This lab demonstrates how to deploy and configure DNS security policies to block malicious domains using Azure CLI automation in GitHub Codespaces.
This lab creates a complete Azure environment with:
- Virtual Network with an Ubuntu 22.04 LTS virtual machine (no public IP - serial console access)
- Azure DNS Security Policy linked to the virtual network
- DNS Domain List with malicious domains (
malicious.contoso.com.,exploit.adatum.com.) - DNS Security Rules to block specific domains with blockpolicy.azuredns.invalid response
- Network Security Group for internal access only
- Log Analytics Workspace for DNS query monitoring and diagnostics
- Diagnostic Settings configured to capture all DNS security events
┌─────────────────────────────────────────────────────────────┐
│ Azure Subscription │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Resource Group │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────────────┐ │ │
│ │ │ Virtual Network │ │ DNS Security Policy │ │ │
│ │ │ (10.0.0.0/16) │◄───┤ - Domain List │ │ │
│ │ │ │ │ - Security Rules │ │ │
│ │ │ ┌─────────────┐│ │ - VNet Link │ │ │
│ │ │ │ Subnet ││ │ - Diagnostic Settings │ │ │
│ │ │ │(10.0.1.0/24)││ └─────────────┬───────────┘ │ │
│ │ │ │ ││ │ │ │
│ │ │ │ ┌────────┐ ││ ┌─────────────┼───────────┐ │ │
│ │ │ │ │Ubuntu │ ││ │ NSG │ │ │ │
│ │ │ │ │VM │◄┼┼────┤ - Internal │Access │ │ │
│ │ │ │ └────────┘ ││ │ - No Public│IP │ │ │
│ │ │ └─────────────┘│ └─────────────┘ │ │ │
│ │ └─────────────────┘ │ │ │
│ │ ▲ │ │ │
│ │ Azure Portal Serial Console │ │ │
│ │ │ │ │
│ │ ┌─────────────────────────────────────────────────┘ │ │
│ │ │ Log Analytics Workspace │ │
│ │ │ - DNS Query Logs │ │
│ │ │ - Diagnostic Data │ │
│ │ └───────────────────────────────────────────────────┘ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
✅ NONE! This lab is designed to run in GitHub Codespaces with no additional setup required.
The Codespaces devcontainer includes:
- Azure CLI (latest version)
- jq JSON processor
- All required VS Code extensions
Click the "Code" button and select "Open with Codespaces" to launch the lab environment.
Edit the answers.json file and add your Azure subscription ID:
{
"subscriptionId": "YOUR-SUBSCRIPTION-ID-HERE"
}Run the deployment script:
./deploy-lab.shNote: If you get "Permission denied", run
chmod +x *.shfirst, or usebash deploy-lab.sh
The script will:
- Prompt for Azure authentication via device code
- Request a secure password for the VM
- Deploy all Azure resources
- Configure DNS security policies
- Set up monitoring and diagnostics
After deployment, access your VM via the Azure Portal:
- Go to Azure Portal
- Navigate to Virtual Machines
- Select your VM in the resource group
- Click "Serial console" in the left menu
- Login with the credentials you provided
Test DNS blocking from the VM:
# Test blocked domains (should return blockpolicy.azuredns.invalid)
dig malicious.contoso.com
dig exploit.adatum.com
# Test allowed domains (should resolve normally)
dig google.com
dig microsoft.com
# For more detailed output, use:
dig malicious.contoso.com +short
dig @8.8.8.8 google.com # Test with external DNS for comparisonExpected Results:
- Blocked domains: Should return
blockpolicy.azuredns.invalid - Allowed domains: Should return IP addresses normally
View DNS logs in Log Analytics:
- Go to your resource group in Azure Portal
- Open the Log Analytics workspace (
law-dns-security-lab) - Click "Logs" and run KQL queries
When finished:
./remove-lab.shThe lab includes a Log Analytics workspace that automatically collects DNS query logs from the DNS Security Policy. This provides visibility into:
- All DNS queries passing through the security policy
- Blocked queries and the domains that triggered blocks
- Query patterns and frequency analysis
- Security events for monitoring malicious activity
- Navigate to your DNS security policy in the Azure Portal
- Under Monitoring, select Diagnostic settings
- Select your Log Analytics workspace (
law-dns-security-lab) - Click Logs to open the query interface
Based on the official DNSQueryLogs table schema, here are accurate queries for analyzing DNS security events:
// View all DNS queries from the last hour
DNSQueryLogs
| where TimeGenerated > ago(1h)
| project TimeGenerated, QueryName, SourceIpAddress, ResponseCode, ResolverPolicyRuleAction
| order by TimeGenerated desc
// View blocked DNS queries only
DNSQueryLogs
| where ResolverPolicyRuleAction == "Deny"
| project TimeGenerated, QueryName, SourceIpAddress, ResolverPolicyRuleAction, ResolverPolicyDomainListId
| order by TimeGenerated desc
// Count queries by domain name
DNSQueryLogs
| summarize QueryCount = count() by QueryName
| order by QueryCount desc
// Analyze query patterns by source IP and policy action
DNSQueryLogs
| summarize AllowedQueries = countif(ResolverPolicyRuleAction == "Allow"),
BlockedQueries = countif(ResolverPolicyRuleAction == "Deny"),
NoMatchQueries = countif(ResolverPolicyRuleAction == "NoMatch")
by SourceIpAddress
| order by BlockedQueries desc
// View queries from specific virtual network
DNSQueryLogs
| where VirtualNetworkId contains "vnet-dns-security-lab"
| project TimeGenerated, QueryName, SourceIpAddress, QueryType, ResponseCode
| limit 100
// DNS query analysis by hour with policy actions
DNSQueryLogs
| summarize AllowedCount = countif(ResolverPolicyRuleAction == "Allow"),
BlockedCount = countif(ResolverPolicyRuleAction == "Deny"),
NoMatchCount = countif(ResolverPolicyRuleAction == "NoMatch")
by bin(TimeGenerated, 1h)
| order by TimeGenerated desc
// Security-focused query: Show all blocked malicious domains
DNSQueryLogs
| where ResolverPolicyRuleAction == "Deny"
| where QueryName contains "malicious" or QueryName contains "exploit"
| project TimeGenerated, QueryName, SourceIpAddress, ResponseCode
| order by TimeGenerated desc- 0: NOERROR (successful query)
- 3: NXDOMAIN (domain does not exist)
TimeGenerated: Timestamp when the log was createdQueryName: Domain being queried (e.g., "malicious.contoso.com")SourceIpAddress: IP address that made the DNS queryResolverPolicyRuleAction: Policy action taken ("Allow", "Deny", "Alert", "NoMatch") - use this to identify blocked queriesResolverPolicyId: ID of the security policy that processed the queryVirtualNetworkId: ID of the virtual network where query originated
The DNS security policy automatically generates diagnostic logs for all DNS queries processed. These logs are essential for:
- Security monitoring: Tracking blocked malicious domains
- Traffic analysis: Understanding DNS query patterns
- Compliance: Maintaining audit trails of DNS filtering actions
- Troubleshooting: Diagnosing DNS resolution issues
- Immediate Analysis: Logs typically appear within 1-2 minutes
- Retention: Default Log Analytics retention (30-730 days configurable)
- Alerting: Create custom alerts based on blocked query thresholds
- Dashboards: Build visual dashboards for DNS security insights
// Security Alert: High volume of blocked queries from single source
DNSQueryLogs
| where TimeGenerated > ago(1h)
| where ResolverPolicyRuleAction == "Deny"
| summarize BlockedCount = count() by SourceIpAddress
| where BlockedCount > 10
| order by BlockedCount desc
// Trend Analysis: DNS query volume over time
DNSQueryLogs
| where TimeGenerated > ago(24h)
| summarize TotalQueries = count(),
BlockedQueries = countif(ResolverPolicyRuleAction == "Deny"),
AllowedQueries = countif(ResolverPolicyRuleAction == "Allow"),
NoMatchQueries = countif(ResolverPolicyRuleAction == "NoMatch")
by bin(TimeGenerated, 1h)
| extend BlockedPercentage = round((BlockedQueries * 100.0) / TotalQueries, 2)
| project TimeGenerated, TotalQueries, BlockedQueries, AllowedQueries, NoMatchQueries, BlockedPercentage
// Forensic Analysis: Detailed view of specific domain queries
DNSQueryLogs
| where QueryName contains "malicious.contoso.com"
| project TimeGenerated, SourceIpAddress, QueryType, ResponseCode,
ResolverPolicyRuleAction, QueryResponseTime
| order by TimeGenerated desc
// Performance Monitoring: Query response times
DNSQueryLogs
| where TimeGenerated > ago(1h)
| summarize AvgResponseTime = avg(QueryResponseTime),
MaxResponseTime = max(QueryResponseTime),
QueryCount = count()
by ResolverPolicyRuleAction
| order by AvgResponseTime descSet up proactive monitoring with Azure Monitor alerts:
// Alert query: Detect potential DNS tunneling attempts
DNSQueryLogs
| where TimeGenerated > ago(5m)
| where ResolverPolicyRuleAction == "Deny"
| summarize BlockedCount = count() by SourceIpAddress
| where BlockedCount > 5Alert Configuration:
- Frequency: Every 5 minutes
- Threshold: More than 5 blocked queries from single IP
- Action: Email notification or webhook
- Power BI: Connect Log Analytics for advanced visualizations
- Azure Sentinel: Integrate for security information and event management (SIEM)
- REST API: Programmatic access to DNS logs
- Export: Regular data export to storage accounts
The lab creates a DNS security policy with the following configuration:
- Policy Name:
dns-security-policy-lab - Action: Block
- Response Code: blockpolicy.azuredns.invalid
- Priority: 100
- State: Enabled
- Blocked Domains:
malicious.contoso.com.(note the trailing dot)exploit.adatum.com.(note the trailing dot)
- Virtual Network:
vnet-dns-security-lab(10.0.0.0/16) - Subnet:
subnet-internal(10.0.1.0/24) - VM: Ubuntu 22.04 LTS, Standard_B1s
- Access: Serial console only (no public IP)
- Log Analytics Workspace:
law-dns-security-lab - Diagnostic Settings: Configured to capture DNS query logs
- Data Retention: Default Log Analytics retention policy
- Deploy the lab environment
- Connect to VM via serial console
- Test DNS blocking with these commands:
# Install dig if not present
sudo apt update && sudo apt install dnsutils -y
# Test blocked domains (should return blockpolicy.azuredns.invalid)
dig malicious.contoso.com
# Expected: blockpolicy.azuredns.invalid
dig exploit.adatum.com
# Expected: blockpolicy.azuredns.invalid
# Test allowed domains (should resolve normally)
dig google.com
# Expected: status: NOERROR, IP address returned
# Verbose testing for detailed output
dig malicious.contoso.com +short
# Expected: No output (blocked)
dig google.com +short
# Expected: IP address like 142.250.191.14- Verify results in Log Analytics (queries appear within 1-2 minutes)
- Add new domains to the block list
- Create additional security rules
- Test different response codes
- Modify rule priorities
- Log Analytics Integration: The lab automatically configures diagnostic settings to send DNS query logs to Log Analytics
- Monitor DNS Queries: View all DNS queries and blocked attempts in the Log Analytics workspace
- Analyze Security Events: Use KQL queries to analyze blocked vs. allowed queries
- Set Up Alerts: Configure Azure Monitor alerts for suspicious DNS activity patterns
Before deployment, you can validate your environment:
./validate-environment.shThis checks for:
- Azure CLI installation and authentication
- Required tools (jq, etc.)
- Subscription access permissions
AzDnsSecurityPolicyLab/
├── README.md # This documentation
├── FILE_OVERVIEW.md # Detailed file descriptions
├── answers.json # Configuration file (update with your subscription)
├── answers.json.template # Template for configuration
├── deploy-lab.sh # Main deployment script
├── remove-lab.sh # Lab cleanup script
├── validate-environment.sh # Pre-deployment validation
├── test-dns-policy.sh # DNS testing instructions
└── .devcontainer/ # GitHub Codespaces configuration
└── devcontainer.json # Container setup and tools
- VM has no public IP address
- Access only via Azure Portal serial console
- Network Security Group allows internal traffic only
- No SSH keys or direct network access required
- Blocks malicious domains at the DNS level
- Returns blockpolicy.azuredns.invalid response for blocked queries
- Linked to virtual network for automatic protection
- Configurable priority and response types
- All DNS queries logged to Log Analytics
- Blocked attempts tracked and analyzed
- KQL queries for security analysis
- Integration with Azure Monitor for alerts
"Permission denied" when running scripts
- This happens when shell scripts don't have execute permissions
- Fix with:
chmod +x *.sh(should be automatic in Codespaces) - If still having issues, run:
bash deploy-lab.shinstead of./deploy-lab.sh
"No subscription found"
- Ensure you've updated
answers.jsonwith your subscription ID - Run
az account listto verify your subscriptions
"VM password requirements"
- Password must be 12-123 characters
- Must contain uppercase, lowercase, numbers, and special characters
"DNS queries not being blocked"
- Wait 2-3 minutes after deployment for DNS propagation
- Ensure domains have trailing dots (malicious.contoso.com.)
- Check VM is using Azure DNS (should be automatic)
- Test with:
dig malicious.contoso.com(should return blockpolicy.azuredns.invalid) - Verify policy is linked: Check virtual network links in Azure Portal
"Cannot access VM"
- Use Azure Portal serial console only
- VM has no public IP by design
- Login with username/password provided during deployment
"dig command not found"
- Install dig if needed:
sudo apt update && sudo apt install dnsutils - Alternative: Use
host malicious.contoso.com(usually pre-installed)
- Check the FILE_OVERVIEW.md for detailed file descriptions
- Review deployment logs for specific error messages
- Ensure all prerequisites are met in your Azure subscription
- Use the validation script to check your environment
- Azure DNS Security Policies Documentation
- Azure DNS Resolver Documentation
- Azure Monitor and Log Analytics
- KQL Query Language Reference
This lab is designed for educational purposes. Feel free to modify the scripts and configuration to suit your learning needs. Key areas for customization:
- Add additional blocked domains
- Modify network topology
- Extend monitoring capabilities
- Add automated testing scenarios
- This lab creates billable Azure resources
- Remember to clean up resources when done (
./remove-lab.sh) - VM access is via serial console only - no SSH/RDP
- DNS changes may take 2-3 minutes to propagate
