Building Azure VPN with Terraform: Zero-Trust Authentication and GitOps

I needed VPN access to Azure resources across multiple subscriptions. The requirement was simple: secure access without managing additional credentials, no PSKs floating around, and everything infrastructure-as-code. Here’s how I built it. The Problem The Azure environment spans many subscriptions with overlapping IP ranges - a legacy of growth. We had: SQL Managed Instances requiring private connectivity Public SQL databases needing IP whitelisting Development teams needing ad-hoc access Zero appetite for managing VPN credentials separately from Azure AD Traditional VPN solutions would require: ...

November 21, 2025 · 7 min · Zac Lohrenz

Implementing Group-Based License Management in Microsoft 365

The Challenge of License Management at Scale Managing Microsoft 365 licenses individually becomes unwieldy as organizations grow. Each time an employee joins, changes roles, or leaves, IT must manually update licenses. Group-based licensing solves this by automatically assigning licenses based on group membership. Prerequisites Azure AD Premium P1 or P2 license Global Administrator or License Administrator role PowerShell modules: AzureAD or Microsoft.Graph Step 1: Audit Current License Assignments First, identify all licenses currently assigned individually: ...

October 31, 2025 · 4 min · Me

Why Claude Skills Beat MCP for Production Integration Work

The Problem: MCP’s Architectural Overhead MCP (Model Context Protocol) sounds compelling on paper: a universal protocol for connecting AI models to external data sources. In practice, it introduces significant operational complexity that Claude Skills sidesteps entirely. Core Architectural Differences MCP: Client-Server Architecture MCP requires running separate server processes: { "mcpServers": { "database": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-postgres"], "env": { "POSTGRES_CONNECTION_STRING": "postgresql://user:pass@host:5432/db" } } } } This means: Additional processes to monitor More failure points Version dependency hell Network latency for every operation Authentication complexity multiplied Skills: Direct API Integration Skills execute directly within Claude’s runtime: ...

October 17, 2025 · 5 min · Me

Migrating from Azure Cache for Redis to Azure Managed Redis

Understanding the Azure Redis Migration Microsoft recently announced the retirement of Basic, Standard, and Premium tiers of Azure Cache for Redis, with a hard deadline of September 30, 2028. This affects many production workloads, and planning your migration strategy now will save headaches later. Key Dates to Remember October 1, 2026: No new instances of Azure Cache for Redis can be created September 30, 2028: Migration deadline October 1, 2028: All remaining instances will be disabled Pre-Migration Assessment Inventory Your Redis Instances # List all Redis caches across subscriptions az redis list --query "[].{name:name, tier:sku.name, capacity:sku.capacity, location:location}" --output table # Get detailed configuration for specific instance az redis show --name <redis-name> --resource-group <rg-name> Analyze Current Usage Patterns # Export Redis metrics for capacity planning az monitor metrics list \ --resource <redis-resource-id> \ --metric-names "connectedclients" "usedmemory" "serverLoad" "operationsPerSecond" \ --start-time 2024-01-01T00:00:00Z \ --end-time 2024-01-31T23:59:59Z \ --interval PT1H \ --output json > redis-metrics.json Migration Strategy 1. Create Azure Managed Redis Instance # Create new Managed Redis instance az redis enterprise create \ --name <managed-redis-name> \ --resource-group <rg-name> \ --location <location> \ --sku Enterprise_E10 \ --capacity 2 # Create database within the instance az redis enterprise database create \ --cluster-name <managed-redis-name> \ --resource-group <rg-name> \ --client-protocol Encrypted \ --clustering-policy EnterpriseCluster \ --eviction-policy NoEviction \ --modules name=RedisJSON,name=RedisTimeSeries 2. Data Migration Approaches Option A: Redis MIGRATE Command (Small Datasets) import redis import time # Connect to source and target source = redis.Redis( host='source-redis.redis.cache.windows.net', port=6379, password='source_password', ssl=True ) target = redis.Redis( host='target-redis.region.redisenterprise.cache.azure.net', port=10000, password='target_password', ssl=True ) # Migrate keys for key in source.scan_iter(count=100): ttl = source.ttl(key) value = source.dump(key) if value: target.restore(key, ttl if ttl > 0 else 0, value) # Rate limiting to avoid overwhelming the system time.sleep(0.001) Option B: Redis Replication (Large Datasets) # Export data from source redis-cli -h source-redis.redis.cache.windows.net \ -p 6379 \ -a <password> \ --rdb dump.rdb \ --scan # Import to target using redis-cli redis-cli -h target-redis.region.redisenterprise.cache.azure.net \ -p 10000 \ -a <password> \ --pipe < dump.rdb 3. Application Configuration Updates # Update your application configuration # Old configuration redis: host: old-redis.redis.cache.windows.net port: 6379 ssl: true password: ${REDIS_PASSWORD} # New configuration redis: host: new-redis.region.redisenterprise.cache.azure.net port: 10000 ssl: true password: ${MANAGED_REDIS_PASSWORD} # Additional Managed Redis specific settings cluster_enabled: true read_replicas: - host: replica1.region.redisenterprise.cache.azure.net - host: replica2.region.redisenterprise.cache.azure.net Testing and Validation Performance Testing Script import redis import time import statistics def benchmark_redis_instance(connection_params, test_name): r = redis.Redis(**connection_params) # Test SET operations set_times = [] for i in range(1000): start = time.time() r.set(f'test_key_{i}', 'x' * 1024) # 1KB payload set_times.append(time.time() - start) # Test GET operations get_times = [] for i in range(1000): start = time.time() r.get(f'test_key_{i}') get_times.append(time.time() - start) print(f"\ {test_name} Results:") print(f"SET - Avg: {statistics.mean(set_times)*1000:.2f}ms, " f"P99: {sorted(set_times)[int(len(set_times)*0.99)]*1000:.2f}ms") print(f"GET - Avg: {statistics.mean(get_times)*1000:.2f}ms, " f"P99: {sorted(get_times)[int(len(get_times)*0.99)]*1000:.2f}ms") # Benchmark both instances benchmark_redis_instance(old_redis_config, "Azure Cache for Redis") benchmark_redis_instance(new_redis_config, "Azure Managed Redis") Cutover Strategy Blue-Green Deployment Approach # Step 1: Deploy application with dual-write capability # Application writes to both old and new Redis # Step 2: Verify data consistency redis-cli -h old-redis.redis.cache.windows.net --scan --pattern '*' | wc -l redis-cli -h new-redis.redisenterprise.cache.azure.net --scan --pattern '*' | wc -l # Step 3: Switch reads to new Redis (canary deployment) # Monitor error rates and latencies # Step 4: Complete cutover # Update DNS/load balancer to point to new Redis Post-Migration Monitoring # Set up alerts for the new Managed Redis instance az monitor metrics alert create \ --name high-memory-usage \ --resource <managed-redis-id> \ --condition "avg usedmemorypercentage > 80" \ --window-size 5m \ --evaluation-frequency 1m az monitor metrics alert create \ --name high-cpu-usage \ --resource <managed-redis-id> \ --condition "avg cpuusagepercentage > 75" \ --window-size 5m \ --evaluation-frequency 1m Rollback Plan Always maintain your rollback capability: ...

October 10, 2025 · 4 min · Me

Implementing Azure AD Privileged Identity Management (PIM)

Why Implement PIM? Privileged Identity Management (PIM) is one of the most effective security controls available in Azure AD. Traditional role assignments grant permanent, standing privileges that create significant security risks: Expanded Attack Surface: Compromised accounts with permanent privileges give attackers immediate access Compliance Gaps: Audit requirements often mandate Just-In-Time (JIT) access for privileged operations Privilege Creep: Over time, users accumulate unnecessary permanent role assignments PIM transforms these permanent assignments into time-bound, audited, and justified access - dramatically reducing your organization’s risk profile. ...

September 26, 2025 · 11 min · Me