Files
webhook-server/docs/service-account-and-ad.md
T
justin f00ee0cf3a v0.1.2: Config Checkpoints dialog, descriptions, daily auto-snapshot, docs (#3)
* Documentation: install/upgrade/uninstall guides + recipes incl. Zerto

Adds a docs/ folder under the repo root with full operator documentation
aimed at sysadmins (not webhook developers). The Zerto pre/post script
recipe is the canonical "why does this exist" walkthrough; the GitHub
HMAC, AD password reset, and UI-on-desktop recipes round out common
patterns.

Pages:
- README.md (index)
- concepts.md (5-minute "what is a webhook" explainer)
- installation.md (interactive + silent install)
- upgrading.md (single-click upgrade flow + edge cases)
- uninstalling.md (clean removal + wiping ProgramData)
- runas-modes.md (Service / InteractiveUser / SpecificUser decision flow)
- service-account-and-ad.md (gMSA setup, delegated rights)
- network-and-security.md (bind addresses, allowlists, HTTPS, secret storage)
- troubleshooting.md (symptom -> first check, common errors)
- recipes/zerto-pre-post-scripts.md (canonical use case)
- recipes/github-style-hmac.md (GitHub / Stripe-shaped webhooks)
- recipes/ad-password-reset.md (gMSA-backed self-service reset)
- recipes/ui-on-desktop.md (InteractiveUser pattern)

Top-level README.md restructured to point at docs/ as the source of
truth, dropping the duplicated installation snippets.

Installer ships docs/ alongside the binaries so they're available
offline at C:\Program Files\WebhookServer\docs\. GUI Help menu gains
a "Documentation" item that opens the docs site in a browser.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Config Checkpoints dialog + daily auto-checkpoint; drop installer GUI launch

Three fixes:

1. Config Checkpoints submenu replaced with a proper dialog. Lists
   checkpoints with timestamp/size/filename, has a "Take Checkpoint
   Now" button, and a "Roll Back" button that becomes enabled when a
   row is selected. The previous click-a-menu-entry-immediate-restore
   flow was too easy to fire by accident.

2. New CheckpointScheduler BackgroundService creates a checkpoint at
   midnight every day. Combined with the existing auto-on-save
   snapshots, this guarantees a daily rollback point even if the
   config wasn't edited that day. A new "create-checkpoint" admin op
   plus AdminPipeServer.CreateCheckpoint helper does the actual file
   copy; both manual (via the dialog) and the scheduler use it.

3. Installer: drop the post-install "Launch Webhook Server" wizard
   step. It tried to launch the GUI un-elevated, which fails because
   the GUI's manifest is requireAdministrator. The Start Menu shortcut
   handles elevation correctly, so the user can launch from there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Docs: replace AD-reset recipe with realistic Zerto failover walkthrough

The AD password reset endpoint was a poor fit for what people actually
need this server for. Replaced with a realistic Zerto post-failover
example that's much closer to the project's purpose:

- Update DNS A records for failed-over hostnames
- Wait for the VM to come up at the DR site
- PowerShell-remote into the VM and check / start critical services
- Notify Teams with the result

The flagship pattern is now: Zerto post-script (curl, fire-and-forget)
calls an Async webhook endpoint -> 202 in milliseconds -> Zerto's
failover sequence is never blocked. The server runs the actual work in
the background, with full output captured in the daily log.

A ready-to-use Zerto-side script ships at
scripts/examples/zerto-post-failover.ps1 - pure curl.exe (no
PowerShell modules), reads the bearer token from a file the ZVM
service account can read.

The installer now bundles scripts/examples/ alongside docs/ so the
example is also available locally at
C:\Program Files\WebhookServer\scripts\examples\.

Removed: docs/recipes/ad-password-reset.md.
Updated: docs/README.md, README.md, the recipe content itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Restore installer GUI launch (via shellexec) + checkpoint descriptions

Two follow-ups to the previous Config Checkpoints commit:

1. Bring back the post-install "Launch Webhook Server" checkbox in the
   installer. The previous attempt failed because Inno Setup's
   postinstall flag launches via CreateProcess after Setup exits,
   bypassing the GUI's requireAdministrator manifest. Adding the
   shellexec flag switches to ShellExecute, which DOES honor the
   manifest and triggers a clean UAC prompt - so the post-install
   GUI launch works as expected.

2. Each checkpoint now carries a description, stored in a sidecar
   .meta.json file next to the snapshot. Defaults:
     - Auto-on-save: "Before save"
     - Midnight scheduler: "Nightly auto-checkpoint"
     - Manual: opens a small dialog so the user can type a meaningful
       description (defaults to "Manual checkpoint" if blank)
   The dialog and pruning both clean up sidecars alongside snapshots.
   The Config Checkpoints grid grows a Description column between
   When and Size.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* v0.1.2: bump checkpoint retention 30 -> 90

Each checkpoint is a few KB of JSON plus a tiny sidecar; even at 90
entries on a config with hundreds of endpoints the on-disk footprint
is negligible (worst case ~20 MB). With daily auto-checkpoints plus
on-save snapshots, 30 entries could fill in a couple weeks of
moderate use; 90 gives a comfortable ~3-month window.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 10:49:09 -04:00

6.6 KiB

Service account & Active Directory

The service runs as LocalSystem out of the box. That's right for local-only scripts and read-only AD queries (LocalSystem authenticates to the network as the machine account, which Authenticated Users includes by default). It is wrong for hooks that need to modify AD — passwords, group memberships, computer objects.

This page covers the four real-world choices and how to switch.

The four options

Account Network identity When to use
LocalSystem (default) Computer account DOMAIN\MACHINE$ on a domain-joined host; nothing on a workgroup host Default. Local file ops, simple PowerShell, read-only AD queries. Most powerful local account — any hook running under it has full local rights.
LocalService None Don't. Cannot talk to a domain controller. Listed only to rule it out.
NetworkService Same machine account as LocalSystem Slightly less local privilege than LocalSystem, same network identity. Rarely the right pick.
Domain user (DOMAIN\svc-webhookserver) That user Use when hooks need write access to AD and you can't use a gMSA. You own password rotation.
gMSA (DOMAIN\svc-webhookserver$) That gMSA Recommended for AD-write workloads. AD generates and rotates the password automatically every 30 days. Requires domain functional level 2012+.

Switching the service account at install time

Pass -ServiceAccount to install-service.ps1 (or to the deploy / dev launcher):

# Domain user
& "C:\Program Files\WebhookServer\scripts\install-service.ps1" `
    -BinaryPath "C:\Program Files\WebhookServer\WebhookServer.Service.exe" `
    -ServiceAccount "CONTOSO\svc-webhookserver" -Password "..."

# gMSA - note trailing $ and no -Password
& "C:\Program Files\WebhookServer\scripts\install-service.ps1" `
    -BinaryPath "C:\Program Files\WebhookServer\WebhookServer.Service.exe" `
    -ServiceAccount 'CONTOSO\svc-webhookserver$'

Or do it manually with sc.exe if the service is already installed:

sc.exe stop WebhookServer
sc.exe config WebhookServer obj= 'CONTOSO\svc-webhookserver$'
sc.exe start WebhookServer

A gMSA is a Group Managed Service Account. Active Directory generates and stores its password and rotates it every 30 days; the host machine account retrieves the password as needed. You never see or store it. This is the cleanest pattern for production.

One-time domain setup

If your domain has never used gMSAs, create the KDS root key (only needed once per domain):

# from a Domain Admin PowerShell, on any DC
Add-KdsRootKey -EffectiveImmediately
# in production wait 10 hours for replication; in a lab, override:
# Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))

Create the gMSA

# from a DC, with AD PowerShell module loaded
New-ADServiceAccount -Name svc-webhookserver `
    -DNSHostName webhook01.contoso.local `
    -PrincipalsAllowedToRetrieveManagedPassword "DOMAIN\WebhookHosts"

PrincipalsAllowedToRetrieveManagedPassword is the security group containing the computer accounts allowed to use the gMSA. Add your webhook host(s) to that group:

Add-ADGroupMember -Identity 'WebhookHosts' -Members 'WEBHOOK01$'
# the host needs to reboot OR have its kerberos ticket flushed for the new group membership to apply

Install the gMSA on the host

On the webhook server machine itself:

# from elevated PowerShell, AD PowerShell module installed (RSAT)
Install-ADServiceAccount svc-webhookserver
Test-ADServiceAccount svc-webhookserver  # should return True

If Test-ADServiceAccount returns False, check:

  • Host is in the WebhookHosts group (or whoever's in PrincipalsAllowedToRetrieveManagedPassword)
  • Host has been rebooted since being added to the group
  • KDS root key has had time to propagate (10 hours by default)

Configure the service to use it

# from elevated PowerShell on the webhook host
sc.exe stop WebhookServer
sc.exe config WebhookServer obj= 'CONTOSO\svc-webhookserver$'
sc.exe start WebhookServer

Note the trailing $. There is no password parameter for gMSAs. The trailing $ is what tells the SCM "look up this account in AD as a managed service account, retrieve its password automatically."

Grant AD permissions

Give the gMSA only what it needs. For a typical "reset user passwords" workload:

# Delegate "Reset password and force change at next logon" on a specific OU
$ou = "OU=Standard Users,DC=contoso,DC=local"
dsacls $ou /I:S /G "CONTOSO\svc-webhookserver$:CA;Reset Password;user"
dsacls $ou /I:S /G "CONTOSO\svc-webhookserver$:WP;pwdLastSet;user"

…or use the GUI Delegation of Control wizard in Active Directory Users and Computers.

Domain user (fallback when gMSA isn't available)

# 1. Create the user (one time)
New-ADUser -Name "svc-webhookserver" -SamAccountName "svc-webhookserver" `
    -AccountPassword (Read-Host -AsSecureString "password") -Enabled $true `
    -PasswordNeverExpires $true -CannotChangePassword $true

# 2. Grant "Log on as a service" right on the host:
#    secpol.msc -> Local Policies -> User Rights Assignment -> Log on as a service
#    Add CONTOSO\svc-webhookserver

# 3. Configure the service:
sc.exe config WebhookServer obj= "CONTOSO\svc-webhookserver" password= "..."

You own password rotation. When you change the password in AD, also update the service via sc.exe config WebhookServer password= "newpw" and restart it.

What changes for hooks when you switch the service account

  • Service mode hooks now run as the new account. PowerShell whoami from inside a hook will show the new identity.
  • InteractiveUser hooks stop working if you switch off LocalSystem. Only SYSTEM can call WTSQueryUserToken. If you need both AD-write hooks and UI-on-desktop hooks, pick one of:
    • Keep service as LocalSystem and use SpecificUser mode for AD-write hooks
    • Switch service to a gMSA / domain user and drop UI hooks (or move them to a separate Webhook Server instance running as LocalSystem)
  • SpecificUser hooks continue to work regardless. They use a separate LogonUser token per call.

Verifying the switch worked

After changing the service account, restart the service and add a quick diagnostic endpoint:

slug:           whoami
auth:           none
executor:       Windows PowerShell
inline command: whoami; whoami /groups

Hit it and verify the output matches the account you configured. The first line should be domain\svc-webhookserver (or domain\machine$ for LocalSystem on a domain-joined host).