← All Use Cases

Deploying a Domain Controller to Azure with an AI Agent

How an AI agent extended our on-premises Active Directory into the cloud — provisioning, networking, and validating a new domain controller with a human approving every credentialed step.

The goal

As a sysadmin managing on-prem Active Directory (two Windows Server domain controllers, already hybrid-synced to Microsoft Entra ID), we set out to test a practical question: could an AI agent handle real, end-to-end infrastructure deployment — not a scripted demo, but actual cloud provisioning, networking, and Active Directory work — while a human stayed in control of every sensitive step?

The project: deploy a new, additional domain controller in Azure that joins and replicates with our existing on-prem domain. Nothing on-prem was removed or replaced — this was extending our AD environment into the cloud, coordinated through an AI agent with terminal, SSH, and browser-automation access.

An isolated, no-uplink clone of one domain controller was created early on as an additional safety measure. In practice, most of the networking and troubleshooting work took place directly against the production environment — a real, live infrastructure test, with every privileged or potentially destructive action requiring explicit human execution rather than agent autonomy.

Result: a fully functioning, replicating domain controller running in Azure, verified with zero replication errors across all three domain controllers, in both directions.

The architecture

Component Details
AzureResource group with a VNet, network security groups locked to a single trusted IP, a VPN Gateway supporting both site-to-site and point-to-site connectivity, and a Windows Server 2022 VM
On-premExisting KVM host running the two production domain controllers, an isolated test network used for early validation, and strongSwan providing the IPsec tunnel
ConnectivityA site-to-site VPN tunnel carrying only traffic between the on-prem subnet and the Azure VNet, with every AD-relevant port (Kerberos, LDAP, DNS, RPC, SMB) explicitly allowed and everything else denied
On-Prem to Azure: Domain Controller Deployment Architecture On-Premises (KVM Host) Domain Controller 1 Existing, production Domain Controller 2 Existing, production Isolated Test Clone No uplink — safety boundary strongSwan — IPsec Tunnel Endpoint Microsoft Azure (VNet) VPN Gateway Site-to-Site + Point-to-Site Network Security Group Locked to single trusted IP New Domain Controller Windows Server 2022 VM Promoted & replicating Azure VPN Endpoint IPsec Tunnel Dashed lines: Active Directory replication traffic (Kerberos, LDAP, DNS, RPC) Isolated / never touched production directly End goal: new working domain controller

What the agent actually did

Provisioning went the way automation should: the AI agent handled Azure CLI work directly, built the VNet, subnets, NSGs, VPN Gateway, and VM, and caught several of its own mistakes along the way — an invalid package name, a misconfigured firewall rule, an Azure capacity wall on the first VM size requested. In each case it diagnosed the actual error text and adjusted rather than guessing blindly.

The real test, though, was what happened once the infrastructure existed but the new domain controller still couldn't properly talk to the rest of the domain. Getting from "VM exists" to "VM is a healthy, replicating domain controller" required finding and fixing three genuinely subtle network faults — the kind that don't show up in a checklist and don't have a single obvious cause.

Fault 1 — A tunnel that was "up" but carried no traffic

The VPN tunnel reported a healthy, connected state on both ends. But pings and connection tests across it failed 100% of the time. Rather than assume the tunnel itself was broken, the agent checked its actual encryption counters directly and found them frozen at zero — meaning no real traffic had ever crossed it, despite the healthy status.

Tracing further, it found the on-prem host's own firewall was silently rewriting the source address of outbound traffic before the tunnel's encryption policy could ever match it — so packets bound for Azure were losing the very identity the tunnel needed to recognize and encrypt them. The fix was a single, precisely ordered firewall exception. Verification wasn't just "no more errors" — the agent re-checked the same encryption counters and watched them move from zero to real traffic before calling it fixed.

Bug 1 — Silent NAT Rewrite Breaking Tunnel Encryption BEFORE FIX Packet leaves host src: on-prem IP Firewall NAT rule rewrites source address Tunnel policy check source no longer matches Packet sent in clear Never encrypted. Dropped. Evidence: tunnel encryption counter stuck at 0 bytes / 0 packets despite "Connected" status AFTER FIX Packet leaves host src: on-prem IP New exception rule tunnel traffic: skip NAT Tunnel policy check source address matches Encrypted & delivered Tunnel carries real traffic Evidence: encryption counter moved to 5 packets / 420 bytes on first test Fix: a single firewall exception, placed above the existing address-rewrite rule

Fault 2 — Traffic worked one way, but not the other

With the tunnel actually carrying data, on-prem-to-Azure connections worked — but Azure-to-on-prem connections were explicitly rejected, not merely timing out. That distinction mattered: a timeout would suggest a routing gap, but an explicit rejection meant something was actively firing.

The agent traced the exact firewall rule evaluation order and found the inbound rule required a connection to already be "established" — a condition no brand-new connection can ever satisfy. The outbound-direction rule had no such restriction, which explained the asymmetry perfectly. This same bug reappeared once, after a routine reboot regenerated the firewall configuration with the broken rule instead of the fix — so the agent didn't just patch it again, it rewrote its own recovery script to verify the rule's position and correctness, not merely its presence, closing the gap for good.

Bug 2 — Asymmetric Firewall Rule Rejecting New Connections DIRECTION A: On-prem → Azure (worked) On-prem DC Firewall rule: no state check accepts any new connection Reaches Azure ✓ DIRECTION B: Azure → On-prem (rejected) Azure new DC Firewall rule: requires "already established" state Rejected ✗ new connection can never A brand-new connection can never be "already established" — the rule rejects every first packet Diagnostic clue: an explicit reject (not a timeout) meant a specific rule was firing, not a routing gap Fix: replace the state-restricted rule with an unconditional accept, matching Direction A's logic Recurred once after a reboot regenerated the broken rule — recovery script rewritten to verify rule position, not just presence

Fault 3 — The hardest: a silent packet-size mismatch

This was the hardest problem of the project. Small operations across the tunnel — DNS lookups, short authentication exchanges — worked perfectly. But the domain controller promotion process itself, which requires larger network exchanges, would hang for ten-plus minutes and fail with an unhelpful, generic Windows error.

The agent worked through this methodically: ruling out clock synchronization (a real but insufficient earlier fix), confirming the tunnel's actual usable packet size was smaller than either side assumed, and finally identifying that Windows was sending packets larger than the tunnel could carry — with the network's standard "please shrink your packets" signal being silently dropped rather than delivered. This is a notoriously difficult class of network fault precisely because it produces no useful error message at all.

The fix required a kernel-level rule rewriting oversized packets down to a safe size at the tunnel boundary. What stands out here is the verification discipline: the agent's first attempt to confirm the fix used an indirect signal, and it explicitly declined to accept that as proof. It then built a direct, unambiguous test — and in doing so caught a second, more subtle bug: the fix's own matching rule excluded exactly the packets that needed rewriting. It corrected that too, and only declared the issue closed after a counter-based test showed 41 out of 41 relevant packets crossing at the corrected size, with zero at the old, broken size.

Bug 3 — Path MTU Blackhole (the hardest bug) SMALL REQUESTS — worked fine DNS lookup Fits under tunnel's real usable packet size Delivered ✓ LARGE REQUESTS — silently failed DC promotion handshake Windows advertises a packet size larger than the tunnel can actually carry The standard "please shrink your packets" network signal never gets delivered back → Oversized packet vanishes. No error. No retry signal. Just silence. Result: a 10+ minute hang, then a generic, unhelpful Windows error Fix: kernel-level rule rewrites oversized packets down to a safe size at the tunnel boundary Verification: counter-based test, not an indirect signal 41 of 41 packets crossed at the corrected size — 0 at the old, broken size

Making it durable

Once all three faults were fixed, the agent noted — unprompted — that none of the fixes would survive a reboot, since they existed only in the running system's memory. It built a dedicated boot-time service to reapply them automatically, carefully avoiding a trap it identified in advance: naively enabling the system's default firewall service would have wiped out unrelated rules for other services running on the same host.

This wasn't just claimed to work — a real, full reboot was performed, and every fix was confirmed present and correctly ordered afterward, with the tunnel automatically reconnecting and full connectivity restored without any manual intervention.

Where the human stayed in control

Three categories of action never left human hands, for the entire project:

Detailed Network Topology Addresses shown are representative of the actual scheme used; public IPs are illustrative placeholders On-Premises — KVM / libvirt Host Host WAN (behind NAT, port-forwarded): 203.0.113.10 Bridge: virbr0 — Production Network 192.168.122.0/24 · gateway 192.168.122.1 Domain Controller 1 192.168.122.2 — DNS, PDC Domain Controller 2 192.168.122.3 Ports required outbound/inbound: TCP+UDP 53, 88, 135, 389, 445, 636 · TCP 49152-65535 Bridge: virbr100 — Isolated Test Network 10.66.0.0/24 · no uplink to virbr0 or internet Used for pre-testing before any real DC was touched Host Firewall / NAT (nftables) NAT exclusion: 192.168.122.0/24 → 10.20.0.0/16 bypasses masquerade Forward accept: 10.20.0.0/16 → 192.168.122.0/24 (no state restriction) MSS clamp: TCP SYN packets rewritten to 1398 bytes at tunnel egress These three rules were the source of Bugs 1–3 · persisted via boot-time service strongSwan — IPsec Tunnel Endpoint IKEv2 · PSK auth · ESP encryption · auto-reconnect on boot Local Network Gateway (Azure-side object): represents 192.168.122.0/24 Microsoft Azure — Virtual Network Address space: 10.20.0.0/16 Subnet: GatewaySubnet — 10.20.255.0/27 VPN Gateway (VpnGw1AZ, zone-redundant) Public IP: 20.0.113.55 (placeholder) · IKEv2, S2S + P2S Subnet: snet-vm — 10.20.1.0/24 Network Security Group Inbound restricted to one trusted source IP New Domain Controller — 10.20.1.4 Windows Server 2022 · no public IP Tunnel selector (traffic-of-interest) 192.168.122.0/24 ⇄ 10.20.0.0/16 Only these ranges are encrypted end-to-end Point-to-Site (secondary path, optional) Client address pool: 172.16.0.0/25 · cert-based auth Site-to-Site IPsec Tunnel IKEv2 · ESP · UDP 500/4500 NAT-T Requires router port-forward for 500/4500

Verified outcome

repadmin /replsummary

Source DSA          largest delta    fails/total %%   error
 CONTO-DC                  28m:30s    0 /  10    0
 CONTO-DC2                 32m:59s    0 /  10    0
 vm-w2022-poc              23m:12s    0 /  10    0

Destination DSA     largest delta    fails/total %%   error
 CONTO-DC                  23m:12s    0 /  10    0
 CONTO-DC2                 21m:24s    0 /  10    0
 vm-w2022-poc              33m:00s    0 /  10    0

Zero replication failures, in both directions, across all three domain controllers — confirmed live, in an active session on the newly deployed Azure domain controller.

repadmin /replsummary output in an active RDP session on the newly deployed Azure domain controller
Azure Portal overview of the new domain controller VM (subscription ID redacted)
Azure resource dependency map for the deployment
Azure Portal’s auto-generated resource dependency view for the deployment’s resource group

What this actually demonstrates

The interesting result isn't that an AI agent can run a single well-documented promotion command — it's that, given real infrastructure access and a disciplined human-in-the-loop model, it performed genuinely hard systems debugging: correlating symptoms across firewall rule order, packet captures, and kernel networking behavior; distinguishing a real fix from one that only looked correct; and knowing when to stop and hand a step back to a human rather than push through.

Equally important is what it never did. Across dozens of privileged operations, the agent never held a production credential, never executed an irreversible domain change unsupervised, and — when a password was accidentally exposed mid-project — flagged it for rotation immediately rather than proceeding as if nothing had happened. That boundary wasn't added afterward. It was the operating model from the first message, and it held for the entire project without exception.