Skip to main content

Rotating Active Directory and Windows credentials

How Qorionix changes a Windows local password and an AD service account password over WinRM, what it cannot confirm afterwards, and what breaks.

Written for whoever runs IT9 min readUpdated

Two rotation engines cover the Microsoft estate: windows_local for a local account on a Windows server, and ad_service_account for a domain service account in Active Directory. Both post a SOAP request to a WinRM endpoint. Neither can confirm afterwards that the new password works, and there is a larger problem with both that you should read before anything else.

These are also the two accounts in a typical small business estate that the most other things depend on, so the blast radius of getting it wrong is larger here than anywhere else in the module.

Prerequisites

Common to both engines:

  • WinRM must be enabled and reachable from Qorionix. These engines speak the WinRM SOAP protocol over HTTP or HTTPS. They do not use SMB, RPC or LDAP, and a firewall that permits RDP but not WinRM will not do.
  • WinRM must accept Basic authentication. Both engines authenticate with HTTP Basic, sending the account name and its current password in the request header. If your WinRM listener is configured to accept only Kerberos or Negotiate, rotation will fail at authentication.
  • The credential must be a password type attached to a target of the right platform: windows for a local account, active_directory for a domain account.
  • The current password in the vault must be the password the machine expects right now. The engine authenticates as the account before it changes anything. A stale vault entry produces an authentication failure, not a rotation.

The port mismatch that catches people

The windows platform defaults its target port to 5985, which is the conventional HTTP WinRM port. But when no endpoint is given in metadata, the engine builds one on 5986, the HTTPS port:

The endpoint each engine falls back to when metadata does not name one
windows_local        https://<hostname>:5986/wsman
ad_service_account   https://<domain_controller>:5986/wsman

Those two numbers disagree on purpose — the default target port is the common one, the fallback endpoint is the safe one — and the way to stop it mattering is to set winrm_endpoint explicitly in the target's metadata and not rely on either. Write out the whole URL, scheme and path included.

Windows local accounts (`windows_local`)

How it works, step by step:

  1. Qorionix generates 18 random bytes

    Cryptographically random, encoded into a password. You do not choose it and you will not see it.

  2. It authenticates to WinRM as the account being rotated

    HTTP Basic, using the current secret from the vault. The account changes its own password, so no separate administrator credential is needed — and no separate administrator credential is possible on this engine.

  3. It sends one command

    A hand-built SOAP request that runs powershell -Command "net user <account> '<new password>'" on the server. The client timeout is 20 seconds.

  4. It treats any HTTP response below 400 as success

    And stores the new password in the vault. There is no attempt to sign in again with the new password.

What to do about that, concretely. After the first rotation of any Windows local account, sign in with the stored credential by hand, or run a proxied RDP session to it, and confirm it works. If it does not, the vault holds a password the machine never accepted: reset the account at the console and update the vault entry. Once you have confirmed one rotation on a given server, subsequent ones are very likely to behave the same way — the failure mode is a rejected password policy or a broken command, and both are properties of the server that do not change between rotations.

There is a second reason to be careful here, and it is fair that you know it: this engine has no automated tests at all. No mock test, no live test. Every other engine in the module has at least one. That is a gap in our coverage rather than a statement about whether it works, and it is why the manual confirmation above is not optional advice.

Verbatim errors from the Windows local engine
windows_local: host and account are required
windows_local request: %w
windows_local do: %w
windows_local http %d: %s

windows_local do: wraps the network failure — no route, refused connection, TLS problem, timeout. windows_local http 401: means WinRM rejected the credential, usually a stale vault entry or Basic authentication being disabled on the listener. windows_local http 500: with a SOAP fault body usually means WinRM accepted the request and the command inside it failed.

Active Directory service accounts (`ad_service_account`)

Structurally the same approach, aimed at a domain controller rather than a member server. There is no LDAP dependency, and that is a deliberate choice: driving the change through PowerShell on the domain controller means the change follows exactly the path a Windows administrator would take by hand, with the directory's own validation applied, rather than Qorionix writing to the directory over a protocol of its own.

  • metadata["domain_controller"] names the domain controller to run the change on. If it is absent, the target's hostname is used. Name it explicitly — the hostname on an active_directory target is often the domain rather than a specific controller, and the change has to land on a machine.
  • metadata["winrm_endpoint"] overrides the endpoint. The fallback is https://<domain_controller>:5986/wsman.
  • The account being rotated needs the right to reset its own password through Set-ADAccountPassword. This is not the default for an ordinary service account and is the most common reason a first attempt fails.
  • The RSAT ActiveDirectory PowerShell module must be present on the domain controller. The command imports it. On a domain controller it usually is present; on a management jump box it often is not.
  • The client timeout is 25 seconds.

The change itself generates 20 random bytes and runs, over WinRM:

The command executed on the domain controller
Import-Module ActiveDirectory; Set-ADAccountPassword -Identity <account>
  -Reset -NewPassword (ConvertTo-SecureString "<new password>" -AsPlainText -Force)

Note -Reset. The password is reset rather than changed with knowledge of the old one, which means the account's own minimum-password-age policy does not block the operation — but the domain's complexity and history policies still apply, and a rejection by either is the failure this engine is least able to report clearly, for the same status-code reason as the Windows engine.

This engine has no automated tests either. Confirm the first rotation of each service account by hand.

Verbatim errors from the Active Directory engine
ad_service_account: account is required
ad_service_account: domain_controller or host required
ad request: %w
ad do: %w
ad http %d: %s

What the connection test can and cannot tell you here

Neither platform has a real verifier, so a connection test on a windows or active_directory target is a TCP probe. It tells you something is listening on the port. It does not tell you the password is right. The result says so in those terms rather than showing a green tick:

%s is listening on port %d. Qorionix can rotate this credential but cannot sign in to check it beforehand, so it will be stored unverified.

And on the credential itself, after a rotation, the summary reads Last rotated %s; next %s. followed by Qorionix changed the password but could not sign in with the new one to confirm it. That sentence is accurate on these two platforms and you should read it as the caution it is.

What breaks: services that cached the old password

This is the part that costs people a night. A Windows password is not only used by people typing it. It is stored, by Windows, in several places that no rotation engine can see or update.

Where a Windows or domain password gets cached, and what happens when it changes underneath.
What holds a copyWhat happens on rotation
A Windows service configured to log on as the accountKeeps using the saved password. It carries on running until it is restarted or the machine reboots — then it fails to start, with event 7000 or 7038 in the system log.
A scheduled task with stored credentialsFails at its next scheduled run. Often silently, because nobody is watching a nightly task that has always worked.
An IIS application pool identityContinues on the existing worker process, then fails when the pool recycles — which by default happens on a timer, so the failure arrives hours later with no obvious cause.
A mapped drive, a saved credential in Credential Manager, an RDP saved passwordFails at next use, and repeatedly retries.
Another server's configuration file, an application's connection string, a scriptFails at next use. Qorionix has no way to know these exist.

The practical procedure, and it is boring on purpose:

  1. Establish what uses the account before you rotate it

    Search your servers for services and scheduled tasks running as it. If the answer is 'we do not know', that is itself a finding, and rotating is not the next step — enumerating is. The privileged account discovery scan can help with the inventory: Discovering privileged accounts.

  2. Prefer accounts nothing depends on

    A named administrator account used by a person for interactive work is an excellent rotation candidate: nothing caches it, and the person does not need to know it because they will reach the machine through a proxied session. A service account that runs three things is a project rather than a task.

  3. Rotate outside working hours the first time

    Not because rotation is slow — it takes seconds — but because if something was caching the password, you want to be there when it stops.

  4. Confirm the new password by hand

    Sign in with the stored credential, or open a proxied RDP session. On these two platforms this is the only proof available that the rotation did what it reported.

  5. Where an application genuinely needs a password, consider not rotating that account

    An honest answer. A service account whose password is in three configuration files is not made safer by a rotation that breaks it and gets switched off in week two. Store it in the vault, restrict who can reach it, leave the interval off, and fix the architecture when you can.

What to do next

  1. Read the rotation overview if you have not — particularly the sections on there being no retry and on how a failure is recorded.
  2. Set up a proxied RDP session so your administrators never need the Windows password in the first place.
  3. Start with Linux instead if you have the option — that engine proves its own work.

Was this article wrong?

If a procedure here does not match what you see, or a limit we described has changed, tell us and we will fix the page. Email us about this article, or see how to get help if you need an answer rather than a correction.

Everything in privileged access