LAPS: local administrator passwords¶
Steward lets a delegated helpdesk operator look up a computer in the OUs they manage and reveal its LAPS password (the local administrator password that Windows LAPS rotates on each machine), once, with a ticket and a step-up, audited. It never shows the password of a domain controller or a Tier 0 server, and the console process never holds the account that can read it.
LAPS runs in the password service, so it needs the helpdesk.
What operators can do¶
| Page / action | Permission (delegation) | What happens |
|---|---|---|
| Computers page: search or list, read-only | computers.read |
Name, DNS name, OS, last sign-in (lastLogonTimestamp, up to ~14 days late by design), enabled, LAPS expiry. Never a password. |
| Computer detail > Show LAPS password | computers.laps.read |
Ticket (always), step-up (always), confirmation; the password service reads it with the LAPS account; shown once with a copy button, hidden after 30 seconds. |
| Computer detail > Expire now | computers.laps.expire |
Sets msLAPS-PasswordExpirationTime to now; the device sets a new password at its next policy cycle (typically within the hour, or at once with Invoke-LapsPolicyProcessing). |
Like every directory action, the three are granted per delegation (scope = where the computers live), and every request is decided on fresh facts:
- Never manageable, whatever the role (Super administrator included): domain controllers, read-only DCs, machines trusted
for unconstrained delegation or for protocol transition, any computer that is a member (even nested, or through its primary
group) of a sensitive group (put your Tier 0 servers in one),
adminCount=1, any computer below a Tier 0/1 OU, and anything that is not a computer account. A missing signal is a refusal. These computers are not even listed. - Protected computers: a computer that is a member of a protected (VIP) group (Configuration > Protection) can be listed
with
computers.read, but revealing or expiring its password needsvip.managein the same delegation. To protect a set of machines, create a group (for exampleGG-Protected-Computers), put the computer accounts in it and add it to the protected groups. computers.laps.readandcomputers.laps.expirerequirecomputers.readin the same delegation (the role editor refuses otherwise:computers_read_required).
Set it up¶
1. Windows LAPS on the domain¶
Windows LAPS is built into Windows Server 2019+ and Windows 10/11 (April 2023 update). On a domain without its schema, a
Schema Admin runs, on a DC or a machine with the LAPS PowerShell module:
Import-Module LAPS
Update-LapsADSchema # adds the msLAPS-* attributes (forest-wide, irreversible like any schema change)
Set-LapsADComputerSelfPermission -Identity "OU=Workstations,OU=Corp,DC=corp,DC=example,DC=ch" # lets the computers write their own password
Then a GPO (Computer Configuration > Administrative Templates > System > LAPS) with Configure password backup directory =
Active Directory, and Enable password encryption = Disabled for the OUs the helpdesk serves (see
encrypted passwords). Check on a client: Invoke-LapsPolicyProcessing, then
Get-LapsADPassword -Identity PC01 -AsPlainText as a domain admin.
2. A dedicated LAPS account¶
Create a plain user, for example svc-steward-laps, member of no administrative group, and never the read, write or
password-reset account (the installer and the delegation script refuse that).
3. Give its password to the password service only¶
The guided install asks for it after the helpdesk ("Set up the LAPS identity now?", then the UPN and the password, typed hidden). With options (the helpdesk, and so the password-reset account, is required):
sudo install -m 600 /dev/null /root/laps.txt && sudo vi /root/laps.txt # the password, one line
sudo deploy/install.sh --name acme --hostname steward.acme-client.ch --helpdesk \
--laps-upn svc-steward-laps@corp.example.ch --laps-password-file /root/laps.txt
sudo shred -u /root/laps.txt
The script stores the password in <instance>/secrets/ad-laps-read (0600, owned by the container user) and writes the .env;
re-runs keep it. The reset account and its password are kept from the first helpdesk install.
Without the installer: write the password to a file (for example secrets/ad-laps-read, chmod 600), set in the instance's
.env, then docker compose up -d:
STEWARD_LAPS_UPN=svc-steward-laps@corp.example.ch
STEWARD_LAPS_PASSWORD_FILE=./secrets/ad-laps-read
deploy/compose.yml passes the UPN to the console (Helpdesk:LapsIdentityUpn: Configuration page, diagnostics, export) and the
UPN plus the password file to steward-pwd only (PasswordService:LapsIdentity). The console never sees the password, and it
cannot be entered in the web UI (Configuration shows the account read-only).
4. Grant the delegations¶
In Delegations, give computers.read, computers.laps.read and, if wanted, computers.laps.expire on the OUs that hold the
workstations.
5. Apply the AD rights¶
Configuration > Diagnostics > Export AD delegation, then a domain administrator applies it (Delegation model and AD rights). For each LAPS delegation it grants the LAPS account, inherited by computer objects only:
- Read property + Control access on
msLAPS-Password(andms-Mcs-AdmPwdwhen the legacy schema exists); - for
computers.laps.expire: Write property onmsLAPS-PasswordExpirationTime(andms-Mcs-AdmPwdExpirationTime).
This is the same right as Set-LapsADReadPasswordPermission (Windows LAPS module), restricted to the clear-text attribute. The
script refuses any other LAPS combination (another account, another object class, msLAPS-EncryptedPassword, any other
attribute).
6. Check the diagnostics¶
Configuration > Diagnostics, LAPS section:
| Check | Meaning |
|---|---|
laps_schema_windows / laps_schema_legacy_only / laps_schema_missing |
Which LAPS schema the directory has |
laps_identity_upn_missing |
STEWARD_LAPS_UPN is not set on the console |
laps_identity_not_configured / laps_bind_failed / laps_identity_ok |
The password service holds the account / it signs in |
laps_service_unreachable |
The console cannot reach the password service |
laps_read_rights_effective / laps_read_rights_missing |
On a sample LAPS-managed computer of each scope, AD returns the password attribute to the LAPS account. Checked with an attribute-names-only read: diagnostics never fetch the password |
laps_encrypted_only |
The sample stores only an encrypted password (not supported) |
laps_expire_rights_present / laps_expire_rights_missing |
The LAPS account may write the expiration time (allowedAttributesEffective) |
no_sample_computer |
No LAPS-managed standard computer in that scope yet: rights cannot be verified |
laps_needs_password_service |
A delegation names a LAPS action but the helpdesk (password service) is off |
What protects a LAPS read¶
- A ticket is always required (400
ticket_required), even when the instance does not require tickets for other changes. - One audited decision (
authz.decision,computers.laps.read) on fresh facts read by GUID. - A step-up is always required, even when the instance step-up mode is
none: an AD operator proves it with an authenticator code if enrolled, otherwise their AD password (auditedauth.reauth); in the modesauthenticatorandpasswordit is the usual step-up. Teleport operators: their Teleport session is the step-up, as for every change (Authentication:Teleport:StepUp: Denyrefuses them). When refused, the API answers 403strict_reauth_requiredwith the proof to ask for. - An explicit confirmation, then the console asks the password service over the signed internal link. The service checks
again: computer under a managed OU, at the DN the decision was made on, classified standard, at most 5 reads or expiries per
computer per 10 minutes (
PasswordService:MaxLapsReadsPerTarget,PasswordService:RateWindow). - The LAPS account reads
msLAPS-Password(Windows LAPS), elsems-Mcs-AdmPwd(legacy LAPS, read-only support). AD itself is the last check: without the Control access right the confidential attribute is simply not returned (the console then says "no readable LAPS password"). - The password goes back to the console encrypted for it (AES-256-GCM with the link key, bound to the computer and to the
direction), then to the browser once. It is never stored, logged or audited: the audit row
computers.laps.readrecords who, which computer, the ticket, the outcome and the source (source:windows/source:legacy). The password service logslaps_operationlines (operation, outcome, computer, operator, correlation id), never the password. - In the browser the password is never cached (no query cache, no storage, no URL), is shown with a copy button and hidden after 30 seconds; closing the dialog clears it.
Encrypted passwords are not supported¶
Windows LAPS can encrypt the password in AD (msLAPS-EncryptedPassword, DPAPI-NG, decryptable only by an authorized principal
on a domain-joined Windows machine). Steward does not decrypt it and never grants the right to read it: a computer that stores
only an encrypted password answers 409 laps_encrypted_unsupported, and the diagnostics warn (laps_encrypted_only). Use
clear-text mode for the OUs the helpdesk serves (ADPasswordEncryptionEnabled = False in the LAPS policy), or keep those
computers out of Steward.
API¶
| Endpoint | Notes |
|---|---|
GET /api/computers?q=&ou= |
computers.read; without q it lists the operator's scope (bounded, truncated when cut) |
GET /api/computers/{id} |
computers.read; 404 when not readable (no existence oracle); actions = display hints |
POST /api/computers/{id}/laps { confirm, ticket } |
200 { account, password, updatedUtc, expiresUtc, source }; 403 strict_reauth_required; 409 laps_no_password / laps_encrypted_unsupported / moved; 429 rate_limited; 503 laps_not_configured / laps_unavailable |
POST /api/computers/{id}/laps/expire { confirm, ticket } |
200 { expired: true }; 409 laps_not_managed; 403 ad_denied |
The LAPS endpoints exist only when the password service is deployed (Helpdesk:Enabled), like the other helpdesk endpoints.