2026-10-16 07:40:29 -06:00
|
|
|
# Samba AD DC for CI
|
2026-10-15 23:08:47 -06:00
|
|
|
|
2026-10-16 07:40:29 -06:00
|
|
|
A throwaway Active Directory domain controller for CI and local end-to-end runs. It lets
|
|
|
|
|
sign-in run over real LDAPS against a real directory without touching PESCO's AD.
|
|
|
|
|
It is only ever started by `deploy/compose.ci.yaml`. Nothing here is used by the test or
|
|
|
|
|
production stacks.
|
2026-10-15 23:08:47 -06:00
|
|
|
|
2026-10-16 07:40:29 -06:00
|
|
|
## What it is
|
2026-10-15 23:08:47 -06:00
|
|
|
|
2026-10-16 07:40:29 -06:00
|
|
|
- Domain `PSTEST.LOCAL`, NetBIOS `PSTEST`, base `DC=pstest,DC=local`, host `dc1.pstest.local`.
|
|
|
|
|
- Debian bookworm with the distribution's Samba. Built from `Dockerfile` in this folder.
|
|
|
|
|
- No published port. The app reaches it on the internal `backend` network as
|
|
|
|
|
`ldaps://dc1.pstest.local:636`. Port 389 also answers, for STARTTLS tests.
|
|
|
|
|
- Simple binds work only over TLS (`ldap server require strong auth = allow_sasl_over_tls`).
|
|
|
|
|
A plain LDAP simple bind is refused, as on PESCO's DCs.
|
2026-10-15 23:08:47 -06:00
|
|
|
|
2026-10-16 07:40:29 -06:00
|
|
|
## Files
|
|
|
|
|
|
|
|
|
|
| File | Job |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `Dockerfile` | Debian bookworm-slim, samba, ldb-tools, openssl, tini. |
|
|
|
|
|
| `entrypoint.sh` | Provisions on first start, makes the TLS certificates, seeds once, refreshes `/export`, runs samba in the foreground. |
|
|
|
|
|
| `seed.sh` | OU tree, service account, groups, test users, `users.json`. |
|
|
|
|
|
| `healthcheck.sh` | Healthy once seeded, exported, and LDAPS verifies against the exported CA. |
|
|
|
|
|
| `groups.txt` | The 50 group names, a copy of `packetstation ad-groups --list`. A server test keeps them equal. |
|
|
|
|
|
|
|
|
|
|
## First start
|
|
|
|
|
|
|
|
|
|
1. `samba-tool domain provision` with `--use-rfc2307 --dns-backend=NONE`.
|
|
|
|
|
The Administrator password comes from the compose secret `samba_admin_password`.
|
|
|
|
|
Setting the sysvol NT ACLs needs `security.*` xattrs, so the service runs with
|
|
|
|
|
`cap_add: [SYS_ADMIN]` (verified on docker-1 with Samba 4.17; without it the provision
|
|
|
|
|
step fails with access denied).
|
|
|
|
|
2. openssl makes a CA (`PSTEST CI CA`) and a server certificate for `dc1.pstest.local` and
|
|
|
|
|
`dc1`, valid 10 years, under `/var/lib/samba/private/tls/`. smb.conf points at them.
|
|
|
|
|
3. The password policy is relaxed: no complexity, no age, no history.
|
|
|
|
|
4. `seed.sh` runs once against the local `sam.ldb`, before samba starts.
|
|
|
|
|
5. smb.conf is kept on the data volume (`/var/lib/samba/etc/smb.conf`), so a recreated
|
|
|
|
|
container starts from the same domain.
|
|
|
|
|
|
|
|
|
|
Provisioning and seeding take about one to two minutes. The healthcheck allows 120 seconds of
|
|
|
|
|
start period and 30 retries after that.
|
|
|
|
|
|
|
|
|
|
Later starts skip provisioning and seeding but always refresh the export.
|
|
|
|
|
|
|
|
|
|
## What seed.sh creates
|
|
|
|
|
|
|
|
|
|
- `OU=PESCO_FARMINGTON` with `OU=Groups`, `OU=Groups` -> `OU=PacketStation`, and `OU=Users`.
|
|
|
|
|
`OU=Admin` -> `OU=System_Accounts`.
|
|
|
|
|
- `CN=svc-packetstation,OU=System_Accounts,OU=Admin,DC=pstest,DC=local`. Its password is the
|
|
|
|
|
`ldap_bind_password` compose secret, the same one the app reads. It never expires.
|
|
|
|
|
- In `OU=PacketStation`: the 50 groups from `groups.txt` except `PS-14-Electrical-Lead`
|
|
|
|
|
(missing on purpose), plus `PS-Extra-Legacy` (extra on purpose). Global security groups.
|
|
|
|
|
- In `OU=Groups`: `Shop-Welders-Nested`, a member of `PS-10-WeldAssembly-Worker`.
|
|
|
|
|
- In `OU=Users`: the 17 test users from the phase 1 contract. CN is the account name.
|
|
|
|
|
employeeID and displayName are set with `ldbmodify`. `nested.ned` is only in the nested
|
|
|
|
|
group. `disabled.dan` is disabled. `noemp.nora` has no employeeID. `dup.dave` and
|
|
|
|
|
`dup.dina` share 9001. `norole.nick` is in no group.
|
|
|
|
|
|
|
|
|
|
Every user gets a new random password on each seed. Nothing secret is committed.
|
|
|
|
|
|
|
|
|
|
## The export volume
|
|
|
|
|
|
|
|
|
|
The DC writes to the `ci-dc-export` volume, mounted at `/export`:
|
|
|
|
|
|
|
|
|
|
- `ca.pem`: the CI CA. migrate, app and worker mount the volume at `/etc/ssl/pstest`, and
|
|
|
|
|
`ci.env` sets `LDAP_CA_BUNDLE=/etc/ssl/pstest/ca.pem`.
|
|
|
|
|
- `users.json`: the test accounts, mode 0444.
|
|
|
|
|
`{"domain": "pstest.local", "users": {"<sam>": {"password", "emp_no", "display_name"}}}`.
|
|
|
|
|
`emp_no` is `null` for `noemp.nora`.
|
|
|
|
|
- `seeded`: the time of the seed. The healthcheck waits for it.
|
|
|
|
|
|
|
|
|
|
The Playwright service mounts the same volume read-only and reads `E2E_DC_USERS_FILE`.
|
|
|
|
|
|
|
|
|
|
## Running the CI stack on docker-1
|
|
|
|
|
|
|
|
|
|
From the repository root:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
bash deploy/ci/make-secrets.sh
|
|
|
|
|
CI="docker compose -p packetstation-ci --env-file deploy/ci/ci.env -f deploy/compose.yaml -f deploy/compose.ci.yaml"
|
|
|
|
|
$CI up -d --build --wait --wait-timeout 300
|
|
|
|
|
$CI --profile e2e run --rm playwright # report in tests/e2e/playwright-report
|
|
|
|
|
$CI logs samba-dc # provisioning and seeding log, no secrets
|
|
|
|
|
$CI --profile e2e down -v
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
To run the server's DC-backed tests from docker-1 against this DC, copy the export out:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
mkdir -p tests/fixtures/local/dc
|
|
|
|
|
docker run --rm -v packetstation-ci_ci-dc-export:/export:ro -v "$PWD/tests/fixtures/local/dc:/out" \
|
|
|
|
|
debian:bookworm-slim cp /export/ca.pem /export/users.json /out/
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`tests/fixtures/local/` is ignored by git. The DC publishes no port, so those tests must run in
|
|
|
|
|
a container on the stack's `backend` network (for example `$CI run --rm --entrypoint pytest app`
|
|
|
|
|
with the tests mounted), with `TEST_LDAP_URL=ldaps://dc1.pstest.local:636`,
|
|
|
|
|
`TEST_DC_USERS_FILE` and the other `TEST_LDAP_*` values set.
|
|
|
|
|
|
|
|
|
|
## What CI does today
|
|
|
|
|
|
|
|
|
|
- The `e2e` job in `.gitea/workflows/ci.yml` builds this DC with the stack, runs the
|
|
|
|
|
Playwright scenarios in `tests/e2e/`, uploads the report and tears down with `down -v`.
|
|
|
|
|
- The `server` job does not start the DC. Its DC-backed tests skip because `TEST_LDAP_URL`
|
|
|
|
|
is unset. Follow-up: run them against the DC, either as a job service or inside the e2e stack.
|
|
|
|
|
|
|
|
|
|
## Running the server suite against the DC by hand
|
|
|
|
|
|
|
|
|
|
On docker-1, with the CI stack up, run the suite from a container on the stack's `backend`
|
|
|
|
|
network (it is internal, so bake pytest into a derived image first):
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
printf "FROM packetstation:ci
|
|
|
|
|
USER root
|
|
|
|
|
RUN pip install -q pytest==9.1.1 anyio==4.15.1
|
|
|
|
|
" | docker build -q -t packetstation:ci-test -
|
|
|
|
|
docker run --rm --user root --network packetstation-ci_backend -v "$PWD/tests:/work/tests:ro" -v "$PWD/pyproject.toml:/work/pyproject.toml:ro" -v "$PWD/server:/work/server:ro" -v "$PWD/deploy/samba/groups.txt:/work/deploy/samba/groups.txt:ro" -v packetstation-ci_ci-dc-export:/export:ro -v "$PWD/deploy/ci/ci-secrets:/secrets:ro" -w /work -e STACK=ci -e TEST_LDAP_URL=ldaps://dc1.pstest.local:636 -e TEST_LDAP_BASE_DN=DC=pstest,DC=local -e TEST_LDAP_BIND_DN=CN=svc-packetstation,OU=System_Accounts,OU=Admin,DC=pstest,DC=local -e TEST_LDAP_PASSWORD_FILE=/secrets/ldap_bind_password -e TEST_LDAP_GROUP_BASE_DN=OU=PacketStation,OU=Groups,OU=PESCO_FARMINGTON,DC=pstest,DC=local -e TEST_LDAP_CA_BUNDLE=/export/ca.pem -e TEST_DC_USERS_FILE=/export/users.json -e TEST_PG_SUPERUSER_URL=postgresql://postgres:PW@some-other-postgres:5432/postgres --entrypoint python packetstation:ci-test -m pytest -q
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Point `TEST_PG_SUPERUSER_URL` at a throwaway PostgreSQL, never at the stack's own. The
|
|
|
|
|
`pg_settings` fixture resets the `ps_app` and `ps_migrate` passwords on the server it is given,
|
|
|
|
|
which breaks the running app and worker (seen on docker-1 on 2026-10-16; `down -v` and `up`
|
|
|
|
|
repair it). Omit the variable to run only the DC tests that need no database.
|
|
|
|
|
|
|
|
|
|
## Open question 15
|
|
|
|
|
|
|
|
|
|
Whether the Gitea runner can run this container. Status: verified on docker-1 (Docker 29,
|
|
|
|
|
Ubuntu 24.04) on 2026-10-16; not yet tried on the Gitea runner.
|
|
|
|
|
|
|
|
|
|
What it needs:
|
|
|
|
|
|
|
|
|
|
- A Docker daemon that can run a root container with `cap_add: [SYS_ADMIN]`. Provisioning
|
|
|
|
|
sets NT ACLs on sysvol, which needs `security.*` xattrs; without the capability Samba 4.17
|
|
|
|
|
fails with access denied (tried on docker-1). The volume's filesystem must support xattrs
|
|
|
|
|
and POSIX ACLs (ext4 and xfs under `/var/lib/docker/volumes` do). If provisioning fails,
|
|
|
|
|
the provision log tail is printed in `$CI logs samba-dc`.
|
|
|
|
|
- Debian bookworm's `samba-ad-provision` package, which the Dockerfile installs (the schema
|
|
|
|
|
files live there, not in `samba`).
|
|
|
|
|
- Bind mounts that resolve. The base stack bind-mounts `deploy/ci/ci-secrets/` (secrets),
|
|
|
|
|
`deploy/postgres/initdb` and `tests/e2e`. If act_runner runs the job inside a container that
|
|
|
|
|
talks to the host daemon through its socket, those paths must exist on the host at the same
|
|
|
|
|
place. If they do not, use a runner on docker-1 in host mode, or one whose workdir is
|
|
|
|
|
mounted at the same path on the host.
|
|
|
|
|
- Outbound HTTPS from the playwright container to the npm registry and from the builder to
|
|
|
|
|
the Debian mirrors.
|