Skip to content

Add Repair-AcmeDnsCredential recovery tool for DPAPI scope mismatch#7

Merged
andrewyager merged 5 commits into
mainfrom
fix/acmedns-localmachine-dpapi-recovery
Apr 30, 2026
Merged

Add Repair-AcmeDnsCredential recovery tool for DPAPI scope mismatch#7
andrewyager merged 5 commits into
mainfrom
fix/acmedns-localmachine-dpapi-recovery

Conversation

@andrewyager

@andrewyager andrewyager commented Apr 30, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds scripts/Recovery/Repair-AcmeDnsCredential.ps1, a self-contained recovery tool that re-encrypts an existing acme-dns credential file under DataProtectionScope.LocalMachine so the SYSTEM-context win-acme renewal task can decrypt it.
  • Auto-detects the toolkit installation, idempotently patches the local Get-AcmeDnsCredential.ps1 to recognise the new DPAPI-LocalMachine StorageMethod, and backs up both files before writing.
  • Verifies the round-trip and re-decrypts via the patched Get-AcmeDnsCredential.ps1 before declaring success.

Why

Register-AcmeDns.ps1 encrypts the acme-dns password using DPAPI in CurrentUser scope (line 195), but Install-Prerequisites.ps1 creates the renewal scheduled task running as SYSTEM (line 382). SYSTEM cannot decrypt CurrentUser-scoped DPAPI blobs, so every automatic renewal fails silently until the certificate expires. We hit this in production today — first renewal cycle since deployment, cert expired before the scheduled task could renew it.

This is a recovery tool to unstick existing deployments. A follow-up PR will change Register-AcmeDns.ps1 to write DPAPI-LocalMachine by default for new registrations.

Test plan

  • PSScriptAnalyzer clean (one false-positive suppression with justification)
  • Patched Get-AcmeDnsCredential.ps1 parses cleanly via [Parser]::ParseFile
  • -WhatIf dry run on test server resolves toolkit + credential paths and round-trips DPAPI
  • Live repair on test server: patch applied, credential re-encrypted, verification succeeded
  • Triggered scheduled task as SYSTEM after repair — renewal succeeded, new Let's Encrypt cert obtained, NTDS rebound, LDAPS test passed

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

Copilot AI review requested due to automatic review settings April 30, 2026 00:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a recovery workflow to fix existing WinCertManager acme-dns credential files that were encrypted with DPAPI CurrentUser scope (interactive registration) but later need to be decrypted by the SYSTEM-run renewal scheduled task.

Changes:

  • Introduces a self-contained recovery script that re-encrypts an existing acme-dns credential password using DPAPI LocalMachine scope and updates the credential JSON accordingly.
  • Implements an idempotent local patch mechanism to extend Get-AcmeDnsCredential.ps1 with support for a new DPAPI-LocalMachine StorageMethod, including backups and verification.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

andrewyager and others added 2 commits April 30, 2026 10:59
Register-AcmeDns.ps1 encrypts the acme-dns password using DPAPI in
CurrentUser scope, but Install-Prerequisites.ps1 creates the renewal
scheduled task running as SYSTEM. SYSTEM cannot decrypt CurrentUser
DPAPI blobs, so every automatic renewal fails until the certificate
expires.

This recovery tool re-encrypts an existing credential file under
DataProtectionScope.LocalMachine so any account on the host (including
SYSTEM) can decrypt it. The script auto-detects the toolkit, patches
the local Get-AcmeDnsCredential.ps1 to recognise the new
"DPAPI-LocalMachine" StorageMethod, backs up both files, and verifies
the round-trip end-to-end.

Verified end-to-end on a Domain Controller affected by this bug: the
repair tool was applied, then the win-acme renewal scheduled task
running as SYSTEM successfully renewed the certificate.

A toolkit-level fix making LocalMachine scope the default for new
registrations will follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 'Renewals Silently Failing (Legacy DPAPI Scope)' subsection to
the README troubleshooting block describing the symptom (renewal task
exit code 0xFFFFFFFF, decrypt failure in win-acme logs, StorageMethod
"DPAPI" in the credential JSON) and the recovery procedure.

The procedure downloads the script from raw.githubusercontent.com,
explicitly opens it for review before execution, and prompts for the
original acme-dns password. Also points operators at the signed
release ZIP for verified deployment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@andrewyager andrewyager force-pushed the fix/acmedns-localmachine-dpapi-recovery branch from 1003783 to 2d9add0 Compare April 30, 2026 00:59
andrewyager and others added 2 commits April 30, 2026 11:13
Makes -Domain optional. When omitted, the script enumerates the
credential store directory (default
%ProgramData%\WinCertManager\Config\acme-dns) and uses the single
credential file present. If zero or multiple files exist, the script
errors with a clear message listing the candidates.

Motivation: enables running the recovery as a single unattended job
from an RMM platform without per-host customisation. The new
Resolve-DomainFromCredentialStore helper excludes .meta.json files
(Credential Manager metadata) so it interacts cleanly with both
storage methods.

Verified on a host where the credential is already migrated:
auto-detect picks the correct file, the rest of the workflow proceeds
unchanged, and -WhatIf prevents writes.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
Comment thread scripts/Recovery/Repair-AcmeDnsCredential.ps1
Comment thread scripts/Recovery/Repair-AcmeDnsCredential.ps1 Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@andrewyager andrewyager merged commit c304282 into main Apr 30, 2026
1 check passed
@andrewyager andrewyager deleted the fix/acmedns-localmachine-dpapi-recovery branch April 30, 2026 02:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants