MDM (device compliance)
lindir’s MDM layer provides compliance policies, custom discovery scripts, retire and lock remote actions, and OIDC Conditional Access via device assertions — a calm way to know a host’s posture before it opens an application.
What you get
| Capability | Behavior |
|---|---|
| Allowed distro/version | Policy allowedDistros[{family,minVersion,maxVersion}] |
| Disk encryption | requireEncryption — dm-crypt/LUKS on root (mapper/dm-) |
| VPN posture | requireVpn — host reports mesh/VPN up (see seeded fleet-vpn below) |
| Local password mins | Compare /etc/security/pwquality.conf (and PAM hints) to policy mins |
| Custom scripts | Admin POSIX scripts → JSON stdout; customAssert on policy |
| Lock | Queue loginctl lock-sessions on next sync |
| Retire | Clear enrollment secret, mark host retired, agent clears local secret + energy drop-ins; VPN removeHost when a provider is configured |
| Conditional Access | OIDC clients with requireCompliantDevice need a short-lived device assertion |
Remote actions today: lock sessions and retire enrollment. Compliance can gate OIDC clients that require a compliant device.
Seeded baseline: fleet-vpn
On first start the server ensures an MDM policy named fleet-vpn with {"requireVpn":true} and keeps it assigned to every non-retired host (including each new enroll). A host reports noncompliant until the agent sees the mesh/VPN as up (Tailscale/Headscale/WireGuard/Pangolin as configured). LAN-only fleets that use vpn.provider: "null" should unassign or edit that policy if VPN posture is not required.
Admin UI Compliance → Policies lists it like any other policy. You may tighten rules or add labels; deleting the seed is possible but enroll will recreate the baseline assignment path unless you change server defaults.
Flow
- Admin creates a compliance policy and assigns host and/or label (or relies on seeded
fleet-vpn). - Optional: create a custom script; set
rules.customScriptId+customAssert. - Host
lindir-client synccollects signals, runs scripts,POST /v1/client/mdm/compliance-report. - Server evaluates →
mdm_host_compliance(compliant/noncompliant/unknown). - Audit action
mdm.compliance.evaluatedis recorded only when status or reasons change (unchanged posture on every sync does not spam Audit). - For SSO apps that require a device: run
lindir-client mdm-broker(listens on127.0.0.1:1290); OIDC login page fetches/assertion.
Admin API (Bearer)
GET|POST /v1/mdm/policies,PUT|DELETE /v1/mdm/policies/{id}GET /v1/mdm/policies/{id}/targetsPOST /v1/mdm/assign/unassign—{ policyId, hostIds[], labelIds[] }GET|POST /v1/mdm/scripts,DELETE /v1/mdm/scripts/{id}GET /v1/mdm/compliance,GET /v1/mdm/compliance/{hostId}GET /v1/mdm/actionsPOST /v1/mdm/lock/retire—{ hostId }
Client API (Host auth)
GET /v1/client/mdm/scriptsPOST /v1/client/mdm/compliance-report— signals JSONGET /v1/client/mdm/actions/POST …/actions/{id}/resultPOST /v1/client/mdm/assertion→{ assertion, expiresIn }
CLI
lindir-admin mdm policy create --name laptop-baseline
lindir-admin mdm policies
# assign via API / Admin UI Compliance → Policies
lindir-admin mdm compliance
lindir-admin mdm lock --host-id HOST_UUID
lindir-admin mdm retire --host-id HOST_UUID
On the host:
sudo lindir-client sync
sudo lindir-client mdm-broker # for OIDC apps that require compliant device
Admin UI
Compliance → Policies, Scripts, Devices. Platform → OIDC → Require compliant device.
Security notes
- Retire clears the enrollment secret and rejects Host auth (
host_unauthorizedon sync). It also callsvpn.removeHostwhen a mesh provider is configured. - Re-enroll after retire: finish the retire action (host sync claims it) before enrolling again under the same hostname. If you enroll while a retire is still pending or the old secret was cleared mid-flight, the new secret can be wiped — sync then returns
host_unauthorized. Run enroll a second time after retire has completed; use--skip-vpnif the device is already on the mesh. - Device assertions are HS256 JWTs (~5 min) signed with a server secret derived from bootstrap token — protect bootstrap credentials.
- Localhost broker binds
127.0.0.1only and sendsAccess-Control-Allow-Origin: *for the OIDC page origin; do not expose that port remotely. - Custom compliance scripts run as root during sync — same trust model as fleet scripts (OWASP Controls).
See also Fleet, Client, VPN, Admin Web UI.
