Publish AD CS CRLs to Azure Blob Storage for Entra CBA Revocation Checks
Purpose
This playbook describes how to publish Certificate Revocation Lists (CRLs) from an on-prem AD CS Certification Authority to Azure Blob Storage, with HTTP read access for downstream consumers — most commonly Microsoft Entra Certificate-Based Authentication (CBA) revocation checks. The upload is automated from the CA server using AzCopy authenticated as an Entra ID Service Principal.
The playbook is organized in three parts:
- Create the Azure Storage Account and Blob container
- Create the App Registration and grant access
- Configure the CRL publishing automation
Prerequisites
- An Azure subscription with permission to create Storage Accounts and Entra ID App Registrations
- An on-prem AD CS CA with a working CRL publication process (
certutil -crlwrites CRLs toCertEnroll) - Administrative access to the CA server
- AzCopy installed on the CA server (Microsoft download — not redistributed by HYPR)
- A working configuration that requires an HTTP-accessible CDP — Entra CBA being the canonical case
Allowing anonymous HTTP read on the blob container disables encryption in transit for the CRL download. CRLs are public and signed, so this is often acceptable, but get security approval before enabling HTTP if your organization mandates HTTPS for all traffic.
Part 1 — Create the Azure Storage Account and Blob container
Step 1.1 — Create the Storage Account
- In the Azure Portal, search for Storage accounts and select Create.
- Choose your Subscription and Resource group.
- Enter a Storage account name (must be globally unique). The examples in this playbook use
hyprcrl. - Select a Region close to your users / services.
- Performance: Standard is appropriate for CRL hosting.
- Redundancy: pick LRS / ZRS based on availability needs.
Step 1.2 — Enable HTTP access on the Storage Account
- Open the Storage Account you just created.
- Go to Settings → Configuration.
- Set Secure transfer required to Disabled.
- Click Save.
The Storage Account will now accept both https:// and http:// endpoints.
Step 1.3 — Create a Blob container for the CRL files
- In the Storage Account, go to Data storage → Containers.
- Click + Container. The examples use
hyprcrlblob. - Set Public access level to Blob (anonymous read access for blobs only).
- Click Create.
Step 1.4 — Upload a file and verify HTTP download works
-
Open the container and click Upload.
-
Upload a test file (for example,
test.txt) or your CRL file (for example,biometric-AWSLDEVNET-1-CA.crl). -
Open the blob and copy its URL.
-
Edit the URL scheme from
https://tohttp://and confirm it downloads in a browser:http://hyprcrl.blob.core.windows.net/hyprcrlblob/biometric-AWSLDEVNET-1-CA.crl
Recommended hardening (compatible with anonymous HTTP reads)
- Use a dedicated container for CRLs; do not mix with sensitive blobs.
- Keep the container Public access level at Blob (not Container).
- Enable Storage Account logging and metrics if required by your operations team.
- Enable soft delete and / or versioning so you can recover from a bad CRL upload.
Part 1 troubleshooting
- 403 or 404 on HTTP download — confirm Secure transfer required is Disabled, the container Public access level is Blob, and the URL path uses the correct container + blob name.
- HTTP works but Entra revocation still fails — confirm the certificate's CDP contains the exact
http://blob URL, the correct CRL file name is being uploaded (both base CRL and, where applicable, delta CRL), and the CRL is current (ThisUpdate/NextUpdatevalid and the revoked-serials list reflects expected revocations).
Part 2 — Create the App Registration and grant access
AzCopy authenticates to the Storage Account as a Service Principal so the CA server can upload CRLs without interactive credentials.
Step 2.1 — Create the App Registration
- Sign in to the Azure Portal.
- Go to Microsoft Entra ID → App registrations → New registration.
- Configure:
- Name:
CRL-Publisher(or your preferred name) - Supported account types: Accounts in this organizational directory only
- Redirect URI: leave blank
- Name:
- Click Register.
Step 2.2 — Record the IDs
From the app's Overview page, copy these values — you will need them for the automation script:
- Application (client) ID (becomes
$AppID) - Directory (tenant) ID (becomes
$TenantID)
Step 2.3 — Create a Client Secret
- In the app, go to Certificates & secrets → Client secrets → New client secret.
- Choose an expiration per your policy (for example, 6–12 months).
- Click Add.
- Copy the secret Value immediately — Azure does not show it again.
Step 2.4 — Assign least-privilege access to the Storage Account
- Open your Storage Account (for example,
hyprcrl). - Open Access control (IAM).
- Click Add role assignment.
- Role:
Storage Blob Data Contributor. - Assign access to: User, group, or service principal.
- Select your app name (
CRL-Publisher). - Save.
Scope the role at the storage account (or container) level — not at subscription level — unless an environment-specific reason requires broader scope.
Step 2.5 — Install AzCopy on the CA server
-
Install AzCopy to a known path. The script defaults to
C:\Program Files\AzCopy\azcopy.exe. -
Verify it runs:
"C:\Program Files\AzCopy\azcopy.exe" --version
Step 2.6 — Store the client secret in a locked-down file
Storing the secret in a file with restricted ACLs is preferred over hard-coding it in the script.
-
Create the file on the CA server:
C:\PKI-Automation\crl_secret.txt -
Put the secret Value on a single line.
-
Lock the ACL so only SYSTEM and Administrators can read it:
icacls C:\PKI-Automation\crl_secret.txt /inheritance:r
icacls C:\PKI-Automation\crl_secret.txt /remove "Users" "Authenticated Users" "Everyone"
icacls C:\PKI-Automation\crl_secret.txt /grant "SYSTEM:R" "BUILTIN\Administrators:R"
icacls C:\PKI-Automation\crl_secret.txt
Step 2.7 — Validate authentication before scheduling
Run a simple AzCopy list command to confirm the identity can reach the container:
$env:AZCOPY_AUTO_LOGIN_TYPE = "SPN"
$env:AZCOPY_SPA_APPLICATION_ID = "<app-id>"
$env:AZCOPY_SPA_CLIENT_SECRET = (Get-Content "C:\PKI-Automation\crl_secret.txt" -Raw).Trim()
$env:AZCOPY_TENANT_ID = "<tenant-id>"
& "C:\Program Files\AzCopy\azcopy.exe" list "https://<storage-account>.blob.core.windows.net/<container>"
A successful listing confirms RBAC has propagated.
Part 3 — Configure the CRL publishing automation
Step 3.1 — Save and configure the script
Save the reference automation script below as Publish-CRL.ps1 on the CA host (a C:\PKI-Automation\ working directory is assumed by the Task Scheduler wrapper later in this playbook):
# =====================
# Publish-CRL.ps1
#
# Generates the CA's Certificate Revocation List (CRL) and uploads it to an
# Azure Blob Storage container via AzCopy. Intended to run on the AD CS CA
# host on a Task Scheduler schedule shorter than the CRL Publication Interval.
# =====================
# =====================
# CONFIG
# =====================
$ErrorActionPreference = "Stop"
# Storage Account and Container that host the published CRL.
$StorageAccount = "<your-storage-account>"
$Container = "<your-container>"
# Azure App Registration that has Storage Blob Data Contributor on the
# container above. For production, read $Secret from a locked-down file
# rather than hard-coding it (see Step 2.6 and the note below the table).
$TenantID = "<your-tenant-id>"
$AppID = "<your-app-id>"
$Secret = "<your-app-client-secret>"
# Local CA CRL output directory and the CA's CommonName-based filename
# prefix (matches "$CAName*.crl").
$CRLPath = "C:\Windows\System32\CertSrv\CertEnroll"
$CAName = "<your-ca-name>"
$AzCopyPath = "C:\Program Files\AzCopy\azcopy.exe"
$DestBase = "https://$StorageAccount.blob.core.windows.net/$Container"
# =====================
# SANITY CHECKS
# =====================
if (-not (Test-Path $AzCopyPath)) { throw "AzCopy not found at: $AzCopyPath" }
if (-not (Test-Path $CRLPath)) { throw "CRLPath not found: $CRLPath" }
# =====================
# AUTH
# =====================
$env:AZCOPY_AUTO_LOGIN_TYPE = "SPN"
$env:AZCOPY_SPA_APPLICATION_ID = $AppID
$env:AZCOPY_SPA_CLIENT_SECRET = $Secret
$env:AZCOPY_TENANT_ID = $TenantID
# =====================
# GENERATE CRL
# =====================
Write-Host "Generating CRL..."
certutil -crl | Out-Host
Start-Sleep -Seconds 3
# =====================
# UPLOAD ONLY REAL CA CRLs
# =====================
$files = Get-ChildItem $CRLPath -File -Filter "$CAName*.crl" |
Sort-Object LastWriteTime -Descending
if ($files.Count -eq 0) {
throw "No CRLs found matching '$CAName*.crl' in $CRLPath"
}
foreach ($file in $files) {
Write-Host "Uploading $($file.Name)..."
& $AzCopyPath copy `
"$($file.FullName)" `
"$DestBase/$($file.Name)" `
--from-to=LocalBlob `
--overwrite=true | Out-Host
if ($LASTEXITCODE -ne 0) {
throw "AzCopy failed uploading $($file.Name). Exit code: $LASTEXITCODE"
}
}
Write-Host "CRL Upload Complete"
Update the configuration block at the top of the script with your environment values:
| Variable | Value |
|---|---|
$StorageAccount | Your Storage Account name (e.g., hyprcrl) |
$Container | Your blob container name (e.g., hyprcrlblob) |
$TenantID | Tenant (Directory) ID from Step 2.2 |
$AppID | Application (client) ID from Step 2.2 |
$Secret | The client secret — for production, read from the locked-down file (Step 2.6) instead of hard-coding |
$CRLPath | Local path where the CA writes CRLs (default C:\Windows\System32\CertSrv\CertEnroll) |
$CAName | Filename prefix of your CA's CRL files (matches $CAName*.crl) |
$AzCopyPath | Path to azcopy.exe (default C:\Program Files\AzCopy\azcopy.exe) |
For production, replace the inline $Secret assignment with a read from the locked-down file:
$Secret = (Get-Content "C:\PKI-Automation\crl_secret.txt" -Raw).Trim()
Step 3.2 — What the script does
- Validates that AzCopy and the local CRL path exist.
- Sets the AzCopy Service Principal environment variables (
AZCOPY_AUTO_LOGIN_TYPE = SPN,AZCOPY_SPA_APPLICATION_ID,AZCOPY_SPA_CLIENT_SECRET,AZCOPY_TENANT_ID). - Runs
certutil -crlto regenerate the CA's CRLs inCertEnroll. - Lists all files matching
$CAName*.crlin$CRLPath, sorted byLastWriteTimedescending. - Uploads each matching CRL to
https://<StorageAccount>.blob.core.windows.net/<Container>/<filename>usingazcopy copy --overwrite=true.
The script throws on AzCopy non-zero exit and on missing prerequisites, so failures surface clearly in scheduled-task logs.
Step 3.3 — Schedule the job (SYSTEM) using a CMD wrapper
Create a CMD wrapper so Task Scheduler captures stdout / stderr consistently:
C:\PKI-Automation\RunPublishCRL.cmd
Wrapper content:
@echo off
setlocal
cd /d C:\PKI-Automation
"C:\Program Files\PowerShell\7\pwsh.exe" -NoProfile -NonInteractive -ExecutionPolicy Bypass ^
-File "C:\PKI-Automation\Publish-CRL.ps1" ^
>> C:\Windows\Temp\crl-task.log 2>&1
exit /b %errorlevel%
In Task Scheduler:
- Create Task (not Basic Task).
- General: Run as
SYSTEM, Run whether user is logged on or not, Run with highest privileges. - Triggers: schedule per your CRL refresh cadence (for example, daily at 02:00).
- Actions:
- Program / script:
C:\PKI-Automation\RunPublishCRL.cmd - Start in:
C:\PKI-Automation - Arguments: (none)
- Program / script:
- Settings: Stop the task if it runs longer than 30 minutes; If the task is already running, stop the existing instance.
Step 3.4 — Validate end-to-end
-
Run the scheduled task once on demand.
-
Inspect
C:\Windows\Temp\crl-task.logfor AzCopy output and theCRL Upload Completeline. -
From an external workstation, download the blob over HTTP:
http://<storage-account>.blob.core.windows.net/<container>/<CAName>.crl -
Confirm the file is your latest CRL (
ThisUpdate/NextUpdatetimestamps current, expected revoked serials present). -
From the system that consumes the CRL (Entra CBA, in the canonical case), trigger a revocation check and confirm it resolves against the blob URL.
Troubleshooting
- AzCopy fails with
invalid_client— wrongAppID/TenantID, expired client secret, or RBAC hasn't propagated yet. Re-check Step 2.2 IDs and Step 2.4 role assignment; wait a few minutes for RBAC to propagate. - Task returns
0x1with no logs — verify the Action path. Use a known-writable directory likeC:\Windows\Tempfor initial logging. - Entra CBA revocation still fails after a successful upload — confirm the issued certificate's CDP extension contains the exact
http://blob URL the CRL is published at, and that the file name in the URL matches$CAName*.crl.