Active Directory domain with GPO enforcement and a minimal Kubernetes cluster — simulating an on-prem enterprise environment for IAM, policy management, and container orchestration practice.
Active DirectoryKubernetesGPOkubeadmWindows Server
// What I Built
01Active Directory domain — Windows Server DC, OUs structured by department, GPOs for password policy and desktop lockdowns.
02Domain-joined workstations — Windows VMs joined to the domain, policy applied at login, users managed via AD Users & Computers.
03Kubernetes cluster (kubeadm) — minimal single control-plane, Flannel CNI, namespace isolation, basic workload scheduling.
04Attack & defend simulations — brute force, lateral movement, and privilege escalation exercises against the AD environment.
// Terminal
$ kubectl get nodes
k8s-ctrl Ready control-plane
k8s-wkr1 Ready worker
---
PS> Get-ADUser -Filter * | Select Name
Administrator, g.sakora, svc_backup
// Challenges & Learnings
Challenges
AD DNS IssuesDomain joins failing because client DNS pointed to ISP, not the DC. Fixed via DNS forwarding on the DC.
K8s CNI ConflictsCNI plugin conflicts during kubeadm init — resolved by pre-pulling images and setting the correct pod CIDR.
Learnings
AD & Enterprise SecurityBuilding from scratch revealed how much of Windows security sits on AD — GPOs, Kerberos, and delegation.
Kubernetes InternalsManual bootstrapping gave far deeper understanding of control plane components than managed K8s ever would.
Entire VM lifecycle managed as code — Terraform with the libvirt provider declaratively provisions KVM VMs, injects cloud-init config at boot, and tears down cleanly for repeatable lab resets.
Terraformlibvirtcloud-initKVM/QEMUvirsh
// What I Built
01Terraform + libvirt provider — declarative VM definitions: vCPU, RAM, disk image, bridge attachment, and cloud-init volume.
02Cloud-init provisioning — hostname, SSH keys, user accounts, and packages injected at first boot without manual steps.
03Modular VM definitions — reusable Terraform modules for different VM roles (DC, workstation, SIEM) with variable-driven config.
04Snapshot-based lab resets — destroy and reprovision the full lab in under two minutes from a clean baseline.
// Code
resource "libvirt_domain" "lab_vm" {
name = "worker-01"
vcpu = 2
memory = 2048
network_interface {
bridge = "br-lab"
}
cloudinit = libvirt_cloudinit_disk.ci.id
}
// Challenges & Learnings
Challenges
Cloud-init Race ConditionsNetwork not ready before cloud-init ran — fixed with systemd dependency ordering in the unit file.
libvirt Provider QuirksVolume and domain resources needed explicit dependency ordering — depends_on required in several places.
Learnings
IaC for Home LabsTreating VMs as code means fully reproducible environments — destroy and reprovision the entire lab in seconds.
cloud-init InternalsUnderstanding user-data, meta-data, and network-config gave much more control over first-boot state.