azure-security-lab-terraform
Deploy and configure the Azure Security Lab - a hands-on Terraform environment with hub-spoke networking, Azure Firewall, NSGs, Key Vault, and security monitoring for learning Azure security controls.
How do I install this agent skill?
npx skills add https://github.com/reason-machines/security-skills --skill azure-security-lab-terraformIs this agent skill safe to install?
- Gen Agent Trust Hubwarn
The skill automates the deployment of an Azure Security Lab by cloning a third-party GitHub repository and executing it with Terraform. While intended for security training, this pattern involves executing remote code from an unverified source and processing security logs that may contain external data.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
Azure Security Lab Terraform Skill
Skill by ara.so — Security Skills collection.
This skill enables AI agents to deploy, configure, and troubleshoot the Azure Security Lab - a compact Terraform project that creates a hub-spoke network architecture with Azure Firewall Basic, NSGs, route tables, Key Vault, Log Analytics, and Azure Policy for hands-on Azure security learning.
What This Project Does
The Azure Security Lab deploys a focused security testing environment:
- Hub-spoke topology: Hub VNet (10.20.0.0/16) with Azure Firewall, test spoke (10.23.0.0/16) with untrusted client, protected spoke (10.22.0.0/16) with private workload
- Network security: Azure Firewall Basic for east-west traffic inspection, NSGs for subnet-level controls, route tables forcing traffic through firewall
- Compute: Windows test VM with optional restricted RDP, Windows IIS web VM without public IP
- Security features: Key Vault for secrets, Log Analytics for diagnostics, Azure Policy for posture auditing, optional Bastion for private access
- Cost awareness: Main cost is Azure Firewall Basic (~0.40 USD/hour), designed for short-term learning deployments
Installation & Prerequisites
Requirements
- Terraform >= 1.9.0
- Azure CLI
- Azure subscription with Contributor rights
- Optional: Entra ID permissions for test user creation
Initial Setup
# Clone the repository
git clone https://github.com/Jamonygr/Azure-Security-Lab.git
cd Azure-Security-Lab
# Login to Azure
az login
az account set --subscription "<subscription-id>"
# Initialize Terraform
terraform init
Configuration Files
The project uses two main configuration approaches:
- terraform.tfvars - Root-level variables
- environments/lab.tfvars - Environment-specific overrides
# Create your configuration from example
cp terraform.tfvars.example terraform.tfvars
Key Configuration Variables
Essential Variables
# terraform.tfvars
# Your public IP for RDP access (REQUIRED)
admin_source_ip_cidr = "203.0.113.42/32"
# Core deployment toggles
deploy_firewall = true
firewall_sku_tier = "Basic"
force_spoke_egress_to_firewall = false
# Access method
enable_test_client_public_ip = true
deploy_bastion = false
# Optional features
deploy_keyvault = true
deploy_log_analytics = true
deploy_policy = true
deploy_budget = true
# Advanced features (disabled by default)
deploy_entra_test_users = false
deploy_conditional_access = false
deploy_sentinel = false
# Resource naming
environment = "lab"
location = "westeurope"
prefix = "azseclab"
Network Architecture
# Address space configuration
hub_vnet_address_space = ["10.20.0.0/16"]
test_vnet_address_space = ["10.23.0.0/16"]
protected_vnet_address_space = ["10.22.0.0/16"]
# Subnet assignments
firewall_subnet_prefix = "10.20.1.0/24"
firewall_mgmt_subnet_prefix = "10.20.3.0/24"
bastion_subnet_prefix = "10.20.2.0/26"
test_client_subnet_prefix = "10.23.1.0/24"
protected_web_subnet_prefix = "10.22.1.0/24"
Core Terraform Commands
Standard Deployment Workflow
# Plan deployment (review changes)
terraform plan -var-file="terraform.tfvars"
# Apply with confirmation
terraform apply -var-file="terraform.tfvars"
# Apply without confirmation (CI/CD)
terraform apply -var-file="terraform.tfvars" -auto-approve
# Destroy all resources
terraform destroy -var-file="terraform.tfvars"
Using Environment Files
# Deploy with environment-specific config
terraform apply -var-file="environments/lab.tfvars"
# Plan specific environment
terraform plan -var-file="environments/prod.tfvars"
Targeted Operations
# Deploy only firewall module
terraform apply -target=module.firewall
# Destroy only test spoke
terraform destroy -target=module.test_spoke
# Refresh state without changes
terraform refresh
Retrieving Lab Access Information
Get Outputs
# Show all outputs
terraform output
# Get specific output
terraform output test_client
terraform output protected_web
# Get sensitive password (raw format)
terraform output -raw windows_admin_password
# Get firewall IPs (when deployed)
terraform output firewall_private_ip
terraform output firewall_public_ip
Output Structure
# Example outputs
test_client = {
private_ip = "10.23.1.4"
public_ip = "20.73.45.67" # when enable_test_client_public_ip = true
rdp_command = "mstsc /v:20.73.45.67"
}
protected_web = {
private_ip = "10.22.1.4"
test_url = "http://10.22.1.4"
}
windows_admin_username = "azureadmin"
windows_admin_password = <sensitive>
Common Deployment Patterns
Pattern 1: Default Security Lab (Firewall + Public RDP)
# terraform.tfvars
admin_source_ip_cidr = "YOUR_IP/32"
deploy_firewall = true
firewall_sku_tier = "Basic"
enable_test_client_public_ip = true
deploy_bastion = false
deploy_keyvault = true
deploy_log_analytics = true
deploy_policy = true
terraform apply -var-file="terraform.tfvars"
# Get RDP access
terraform output test_client
# RDP to test client from your IP
mstsc /v:<test_client_public_ip>
Pattern 2: Private Access with Bastion
# terraform.tfvars
admin_source_ip_cidr = "0.0.0.0/0" # Not used with Bastion
deploy_firewall = true
enable_test_client_public_ip = false
deploy_bastion = true
terraform apply -var-file="terraform.tfvars"
# Access via Azure Portal Bastion
# Navigate to VM → Connect → Bastion
Pattern 3: Minimal Lab Without Firewall
# terraform.tfvars
admin_source_ip_cidr = "YOUR_IP/32"
deploy_firewall = false
enable_test_client_public_ip = true
deploy_bastion = false
# Lower cost, no firewall inspection
terraform apply -var-file="terraform.tfvars"
# Test direct spoke-to-spoke connectivity
# NSGs still enforce security
Pattern 4: Identity Lab with Entra Users
# terraform.tfvars
deploy_entra_test_users = true
entra_domain_name = "contoso.onmicrosoft.com"
# Requires these permissions:
# - User.ReadWrite.All
# - Group.ReadWrite.All
# Grant permissions in Azure Portal first
az ad app permission grant --id <app-id> --api 00000003-0000-0000-c000-000000000000
terraform apply -var-file="terraform.tfvars"
# Created users:
# - sec-admin@contoso.onmicrosoft.com
# - sec-analyst@contoso.onmicrosoft.com
# - lab-user@contoso.onmicrosoft.com
# - break-glass@contoso.onmicrosoft.com
Pattern 5: SOC Lab with Sentinel
# terraform.tfvars
deploy_sentinel = true
deploy_log_analytics = true
deploy_firewall = true
terraform apply -var-file="terraform.tfvars"
# Query firewall logs in Log Analytics
# Navigate to workspace → Logs
Testing Lab Scenarios
Scenario 1: Verify Firewall East-West Routing
# 1. RDP to test client
mstsc /v:<test_client_public_ip>
# 2. Inside test client PowerShell:
$protectedIP = "10.22.1.4"
$firewallIP = "10.20.1.4"
# Test HTTP (should succeed - firewall allows web)
curl http://$protectedIP
# Test RDP (should fail - firewall denies admin protocols)
Test-NetConnection -ComputerName $protectedIP -Port 3389
# Check routing
Get-NetRoute | Where-Object {$_.DestinationPrefix -eq "10.22.0.0/16"}
# Should show NextHop = firewall IP when force_spoke_egress_to_firewall = true
Scenario 2: Key Vault Secret Retrieval
# Get Key Vault name from output
KV_NAME=$(terraform output -raw keyvault_name)
# Retrieve Windows admin password
az keyvault secret show --vault-name $KV_NAME --name windows-admin-password --query value -o tsv
# List all lab secrets
az keyvault secret list --vault-name $KV_NAME --query "[].name" -o table
Scenario 3: Firewall Log Analysis
// In Log Analytics workspace
// Show all firewall deny events
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK"
| where Category in ("AzureFirewallNetworkRule", "AZFWNetworkRule")
| where msg_s has "Deny"
| project TimeGenerated, msg_s, Protocol, SourceIP, DestinationIP, DestinationPort
| order by TimeGenerated desc
| take 50
// Show allowed web traffic
AzureDiagnostics
| where Category == "AzureFirewallNetworkRule"
| where msg_s has "Allow"
| where DestinationPort in ("80", "443")
| summarize Count = count() by SourceIP, DestinationIP, DestinationPort
Scenario 4: NSG Flow Verification
# Check NSG rules from Azure CLI
PROTECTED_NSG_NAME="<prefix>-protected-web-nsg"
RG_NAME="<prefix>-protected-rg"
az network nsg rule list \
--resource-group $RG_NAME \
--nsg-name $PROTECTED_NSG_NAME \
--query "[].{Name:name, Priority:priority, Access:access, Protocol:protocol, Direction:direction}" \
--output table
Module Structure
The project uses reusable modules:
# Example module usage from main.tf
module "hub_network" {
source = "./modules/hub-network"
resource_group_name = azurerm_resource_group.hub.name
location = var.location
vnet_address_space = var.hub_vnet_address_space
deploy_bastion = var.deploy_bastion
bastion_subnet_prefix = var.bastion_subnet_prefix
tags = local.common_tags
}
module "firewall" {
source = "./modules/firewall"
count = var.deploy_firewall ? 1 : 0
resource_group_name = azurerm_resource_group.hub.name
location = var.location
firewall_subnet_id = module.hub_network.firewall_subnet_id
firewall_mgmt_subnet_id = module.hub_network.firewall_mgmt_subnet_id
sku_tier = var.firewall_sku_tier
test_spoke_cidr = var.test_vnet_address_space[0]
protected_spoke_cidr = var.protected_vnet_address_space[0]
tags = local.common_tags
}
module "test_spoke" {
source = "./modules/test-spoke"
resource_group_name = azurerm_resource_group.test.name
location = var.location
vnet_address_space = var.test_vnet_address_space
admin_username = var.windows_admin_username
admin_password = random_password.windows_admin.result
admin_source_ip_cidr = var.admin_source_ip_cidr
enable_public_ip = var.enable_test_client_public_ip
firewall_private_ip = var.deploy_firewall ? module.firewall[0].private_ip : null
force_firewall_routing = var.force_spoke_egress_to_firewall
tags = local.common_tags
}
Troubleshooting Guide
Issue: Terraform Init Fails
# Clear backend state
rm -rf .terraform/
rm .terraform.lock.hcl
# Re-initialize
terraform init -upgrade
Issue: Asymmetric Routing (RDP Fails After Apply)
Symptom: Cannot RDP to test client after enabling firewall routing
Cause: force_spoke_egress_to_firewall = true with public RDP causes asymmetric routing
Solution:
# Option 1: Use public RDP without forced routing
force_spoke_egress_to_firewall = false
enable_test_client_public_ip = true
# Option 2: Use Bastion for private access
force_spoke_egress_to_firewall = true
enable_test_client_public_ip = false
deploy_bastion = true
Issue: Cannot Reach Protected Web from Test Client
# 1. Check route table
az network route-table route list \
--resource-group <prefix>-test-rg \
--route-table-name <prefix>-test-rt \
--output table
# 2. Verify firewall rules
az network firewall policy rule-collection-group list \
--policy-name <prefix>-fw-policy \
--resource-group <prefix>-hub-rg
# 3. Check NSG on protected subnet
az network nsg rule list \
--resource-group <prefix>-protected-rg \
--nsg-name <prefix>-protected-web-nsg \
--include-default \
--output table
# 4. Test from test client:
Test-NetConnection -ComputerName 10.22.1.4 -Port 80
Issue: Key Vault Access Denied
# Check current user identity
az account show --query user.name -o tsv
# Grant yourself access
KV_NAME=$(terraform output -raw keyvault_name)
USER_ID=$(az ad signed-in-user show --query id -o tsv)
az keyvault set-policy \
--name $KV_NAME \
--object-id $USER_ID \
--secret-permissions get list
Issue: Entra User Creation Fails
Error: "Insufficient privileges to complete the operation"
Solution:
# Check Graph API permissions
az ad app permission list --id <app-id>
# Required permissions:
# - User.ReadWrite.All
# - Group.ReadWrite.All
# Grant admin consent in Azure Portal:
# Azure AD → App registrations → Your app → API permissions → Grant admin consent
Issue: Budget Alerts Not Received
# Verify budget configuration
deploy_budget = true
budget_amount = 500
# Check contact role in subscription
az account show --query user.assignedRoles
# Must have "Owner" role for budget alerts
Issue: Firewall Logs Not Appearing
# Verify diagnostic settings
az monitor diagnostic-settings list \
--resource <firewall-resource-id> \
--query "[].{Name:name, Workspace:workspaceId}" \
--output table
# Check Log Analytics workspace
az monitor log-analytics workspace show \
--resource-group <prefix>-security-rg \
--workspace-name <prefix>-law
# Allow 5-15 minutes for initial log ingestion
Cost Control Strategies
Estimate Before Deploy
# Use Azure Pricing Calculator
# Main costs:
# - Azure Firewall Basic: ~0.40 USD/hour (~292 USD/month)
# - Azure Bastion: ~0.19 USD/hour (~140 USD/month)
# - VMs: 2x B2s ~0.04 USD/hour each (~60 USD/month)
# - Storage, networking: ~10-20 USD/month
# Total: ~400-500 USD/month with firewall and Bastion
Deploy Time-Limited Lab
# Deploy for testing
terraform apply -var-file="terraform.tfvars"
# Run validation tests
./scripts/validate-lab.sh
# Destroy immediately after
terraform destroy -var-file="terraform.tfvars" -auto-approve
Minimal Cost Configuration
# Lowest cost setup
deploy_firewall = false
deploy_bastion = false
enable_test_client_public_ip = true
vm_size = "Standard_B1s" # Smallest VM
# Cost: ~50-80 USD/month
GitHub Actions Integration
The project includes CI validation:
# .github/workflows/terraform-validate.yml
name: Terraform Validate
on:
pull_request:
branches: [ main ]
push:
branches: [ main ]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.0
- name: Terraform Init
run: terraform init -backend=false
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Validate
run: terraform validate
Security Best Practices
1. Restrict Admin Access
# Always use /32 for admin source
admin_source_ip_cidr = "203.0.113.42/32" # Your IP only
# Never use:
# admin_source_ip_cidr = "0.0.0.0/0" # Insecure!
2. Use Environment Variables for Sensitive Data
# Export Azure credentials
export ARM_SUBSCRIPTION_ID="<subscription-id>"
export ARM_CLIENT_ID="<client-id>"
export ARM_CLIENT_SECRET="<client-secret>"
export ARM_TENANT_ID="<tenant-id>"
# Terraform will use these automatically
terraform apply
3. Enable All Security Features
deploy_keyvault = true # Store secrets securely
deploy_log_analytics = true # Enable logging
deploy_policy = true # Audit posture
deploy_budget = true # Cost alerts
4. Review Firewall Rules Before Deploy
# Check modules/firewall/main.tf for rule logic
# Understand what traffic is allowed/denied
# Customize for your lab scenarios
Wiki Reference
The project includes extensive wiki documentation:
- Get Started:
wiki/get-started/deploy.md,wiki/get-started/final-validation.md - Architecture:
wiki/architecture/overview.md,wiki/architecture/security-controls.md - Scenarios:
wiki/scenarios/firewall-east-west.md,wiki/scenarios/key-vault-secrets.md - Reference:
wiki/reference/variables.md,wiki/reference/security-test-matrix.md - Runbooks:
wiki/runbooks/apply-test-destroy.md,wiki/runbooks/final-check.md
Quick Reference Commands
# Complete deployment workflow
terraform init
terraform plan -var-file="terraform.tfvars"
terraform apply -var-file="terraform.tfvars"
terraform output
terraform destroy -var-file="terraform.tfvars"
# Get lab access
terraform output test_client
terraform output -raw windows_admin_password
# Query firewall logs
az monitor log-analytics query \
--workspace <workspace-id> \
--analytics-query "AzureDiagnostics | where Category == 'AzureFirewallNetworkRule' | take 10"
# Check Key Vault secrets
az keyvault secret list --vault-name <kv-name>
# Validate NSG rules
az network nsg rule list --resource-group <rg> --nsg-name <nsg>
This skill provides comprehensive coverage for deploying, configuring, and troubleshooting the Azure Security Lab with Terraform.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/reason-machines/security-skills/azure-security-lab-terraform">View azure-security-lab-terraform on skillZs</a>