Skip to content

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.

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.

Clone the repository and create your .env from the template:

Terminal window
git clone https://git.supported.systems/bingham/samba-domain-controller.git
cd samba-domain-controller
cp .env.example .env
$EDITOR .env

The three values that matter on first boot are the realm, the NetBIOS name, and the admin password:

Terminal window
SAMBA_REALM=ad.supported.systems # a subdomain you control, never your bare public domain
SAMBA_DOMAIN=AD # NetBIOS short name, <= 15 chars, no dots
SAMBA_ADMIN_PASSWORD=ChangeMe123! # must meet AD complexity rules
SAMBA_HOST_IP=10.22.22.22 # the LAN IP the DC advertises

Every variable is explained in the .env reference.

Terminal window
make up # creates the 'samba' network, builds the image, starts dc1
make logs # watch the first-boot provision

The 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:

Terminal window
make health # lists the Samba service tasks once it is up

Open a shell inside the DC and get a Kerberos ticket as the Administrator:

Terminal window
make shell
kinit Administrator # enter SAMBA_ADMIN_PASSWORD
klist # should show a TGT for the realm
smbclient -L localhost -U Administrator
exit

If klist shows a ticket, your domain is alive. domain up

Populate the directory with users, each with an email address, a phone number, and a fax number:

Terminal window
make seed
make list-users
make show-user U=tommy.tutone # note the memorable fax number

make seed is idempotent. It also wires up a home-directory share for each user. Confirm a home works end to end:

Terminal window
make test-home U=marty.mcfly # writes then reads a file over SMB

Now add the organisational structure and the clinics. First the OU tree and the manager reporting chain, then the security groups, then the clinics:

Terminal window
make org # OU tree + manager / direct-reports chain
make groups # base security groups + nesting + gated shares
make clinics # ER clinical staff + clinic groups + the PHI boundary

make 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.

This is the payoff. The hospital’s HIPAA boundary is enforced by group membership, and you can watch it work:

Terminal window
make test-clinic-access

You should see:

  • john.carter (Cardiology) writes \\dc1\cardiology and \\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.

A working domain controller, a populated directory, a hospital file-share layout, and a proof that the access control behaves. From here:

The host’s own resolver often already binds 53. On a systemd host:

Terminal window
sudo ss -lunp 'sport = :53' # see what holds it
# set DNSStubListener=no in /etc/systemd/resolved.conf, then:
sudo systemctl restart systemd-resolved

Then make up again. The full explanation is in the repository README under “Port 53 conflict”.