Stand up the lab
By the end of this tutorial you will have a running Active Directory domain, a cast of users, the General Hospital clinic structure, and proof that the access control actually works. Plan for about ten minutes, most of it waiting for the first-boot provision.
Before you start
Section titled “Before you start”You need a Linux host with Docker and the Compose plugin, and free TCP/UDP port
53. A domain controller has to own DNS, and most Linux desktops already run a
resolver on 53. If make up fails at startup, read Port 53 conflict
at the bottom of this page first.
1. Configure the domain
Section titled “1. Configure the domain”Clone the repository and create your .env from the template:
git clone https://git.supported.systems/bingham/samba-domain-controller.gitcd samba-domain-controllercp .env.example .env$EDITOR .envThe three values that matter on first boot are the realm, the NetBIOS name, and the admin password:
SAMBA_REALM=ad.supported.systems # a subdomain you control, never your bare public domainSAMBA_DOMAIN=AD # NetBIOS short name, <= 15 chars, no dotsSAMBA_ADMIN_PASSWORD=ChangeMe123! # must meet AD complexity rulesSAMBA_HOST_IP=10.22.22.22 # the LAN IP the DC advertisesEvery variable is explained in the .env reference.
2. Start the domain controller
Section titled “2. Start the domain controller”make up # creates the 'samba' network, builds the image, starts dc1make logs # watch the first-boot provisionThe first boot provisions the whole domain, which takes a minute or two. Watch for the line that says provisioning completed, then check the service is healthy:
make health # lists the Samba service tasks once it is up3. Prove Kerberos and LDAP work
Section titled “3. Prove Kerberos and LDAP work”Open a shell inside the DC and get a Kerberos ticket as the Administrator:
make shellkinit Administrator # enter SAMBA_ADMIN_PASSWORDklist # should show a TGT for the realmsmbclient -L localhost -U AdministratorexitIf klist shows a ticket, your domain is alive. domain up
4. Seed the cast
Section titled “4. Seed the cast”Populate the directory with users, each with an email address, a phone number, and a fax number:
make seedmake list-usersmake show-user U=tommy.tutone # note the memorable fax numbermake seed is idempotent. It also wires up a home-directory share for each user.
Confirm a home works end to end:
make test-home U=marty.mcfly # writes then reads a file over SMB5. Build the hospital
Section titled “5. Build the hospital”Now add the organisational structure and the clinics. First the OU tree and the manager reporting chain, then the security groups, then the clinics:
make org # OU tree + manager / direct-reports chainmake groups # base security groups + nesting + gated sharesmake clinics # ER clinical staff + clinic groups + the PHI boundarymake clinics seeds the ER cast into OU=Clinical, creates one security group
per clinic, and builds the nested Clinicians and GH-Staff meta-groups that
gate the patient-data share. See Build the clinics for
what each piece does.
6. Prove the access control
Section titled “6. Prove the access control”This is the payoff. The hospital’s HIPAA boundary is enforced by group membership, and you can watch it work:
make test-clinic-accessYou should see:
john.carter(Cardiology) writes\\dc1\cardiologyand\\dc1\phi, but is denied\\dc1\billing.madonna(Billing) writes\\dc1\billing, is denied\\dc1\phi, and can read but not write\\dc1\all-staff.ellen.ripley(Administration) writes\\dc1\all-staff.
That john.carter reaches phi while madonna cannot, with neither of them
named on the share, is the whole point. The HIPAA nesting
page explains exactly why.
What you have now
Section titled “What you have now”A working domain controller, a populated directory, a hospital file-share layout, and a proof that the access control behaves. From here:
- Add the print server and printer emulation.
- Add a read-only replica.
- Tear down and rebuild any time:
make downkeeps the domain,make upbrings it back,make nukewipes it for a clean start.
If startup fails on port 53
Section titled “If startup fails on port 53”The host’s own resolver often already binds 53. On a systemd host:
sudo ss -lunp 'sport = :53' # see what holds it# set DNSStubListener=no in /etc/systemd/resolved.conf, then:sudo systemctl restart systemd-resolvedThen make up again. The full explanation is in the repository README under
“Port 53 conflict”.