From 7e2cf0aa9a7cc96fb7e2b59f7bfbb06b1d505247 Mon Sep 17 00:00:00 2001
From: Irena Reedy <205901210+irenareedy@users.noreply.github.com>
Date: Fri, 30 Jan 2026 08:41:42 -0800
Subject: [PATCH] =?UTF-8?q?Create=20enable-okta-verify-on-windows-using-a-?=
=?UTF-8?q?scep-configuration-profi=E2=80=A6=20(#38716)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
…le.md
Adam Baali's article
---------
Co-authored-by: Adam Baali <45665341+AdamBaali@users.noreply.github.com>
Co-authored-by: Brock Walters <153771548+nonpunctual@users.noreply.github.com>
---
...dows-using-a-scep-configuration-profile.md | 223 ++++++++++++++++++
1 file changed, 223 insertions(+)
create mode 100644 articles/enable-okta-verify-on-windows-using-a-scep-configuration-profile.md
diff --git a/articles/enable-okta-verify-on-windows-using-a-scep-configuration-profile.md b/articles/enable-okta-verify-on-windows-using-a-scep-configuration-profile.md
new file mode 100644
index 0000000000..d8acd329c8
--- /dev/null
+++ b/articles/enable-okta-verify-on-windows-using-a-scep-configuration-profile.md
@@ -0,0 +1,223 @@
+# Enable Okta Verify on Windows using a SCEP configuration profile
+
+## Introduction
+
+This guide explains how to enable [Okta Verify](https://help.okta.com/en-us/content/topics/mobile/okta-verify-overview.htm) on Windows using a SCEP client certificate delivered by the Windows **ClientCertificateInstall** CSP and then applied using an **Exec** command. This pattern is useful when your MDM payload cannot send **Add or Replace** nodes together with an **Exec** in one transaction.
+
+You will deploy the SCEP profile first, then call **Enroll** via Exec to request the client certificate.
+
+**Files**
+* [Profile XML](https://github.com/fleetdm/fleet/blob/main/docs/solutions/windows/configuration-profiles/install%20Okta%20attestation%20certificate%20-%20%5BBundle%5D.xml)
+* [Powershell script](https://github.com/fleetdm/fleet/blob/main/docs/solutions/windows/scripts/trigger%20scep%20enrollment.ps1)
+
+---
+
+## Order at a glance
+
+1. Get your CA **thumbprint**, choose **{yourCertName}**, and locate your SCEP **URL** and **Challenge**.
+2. Create Fleet **secrets** for URL, Challenge, CA thumbprint, and API token.
+3. Use the Fleet repo XML CSP profile and replace only the required placeholders.
+4. Deploy the profile to devices.
+5. Update the **Exec** script to use the same `{yourCertName}` and your secrets, then run it.
+6. Verify the certificate is installed.
+7. Plan and automate **renewal**.
+
+---
+
+## Prerequisites
+
+* Windows devices enrolled to Fleet MDM
+* Okta SCEP endpoint with a static challenge
+* Root CA certificate thumbprint for the SCEP issuing CA
+* Fleet API token stored as a secret
+* Optional GitOps workflow if you manage Fleet configuration as code
+
+---
+
+## Step 1. Collect your values
+
+### 1.1 Get the CA thumbprint
+
+**Windows PowerShell**
+```powershell
+Get-FileHash -Path "C:\Path\To\ca.cer" -Algorithm SHA256 | Select-Object -ExpandProperty Hash
+```
+
+**macOS or Linux**
+```bash
+openssl x509 -in ~/Downloads/ca.cer -noout -fingerprint -sha256
+# Output looks like:
+# SHA256 Fingerprint=E2:18:D7:A7:B0:DF:ED:79:B2:05:73:BA:79:CB:14:B1:FE:EA:D2:7B
+# Remove the colons:
+# E218D7A7B0DFED79B20573BA79CB14B1FEEAD27B
+```
+
+Use the hex string without colons or spaces in the secret you will create below.
+
+### 1.2 Choose your SCEP node name
+
+Pick a simple value for `{yourCertName}`, for example `OKTA` or `OKTAVERIFY`. You will use this exact value:
+* in the XML profile path `.../SCEP/{yourCertName}/Install/...`
+* in the Exec path `.../SCEP/{yourCertName}/Install/Enroll`
+
+### 1.3 Get your SCEP URL and Challenge
+
+* `{yourScepUrl}` is your Okta SCEP endpoint.
+* `{yourScepChallenge}` is your static SCEP challenge. This profile expects **plain text**. Avoid special characters that can break XML or transport. Recommended: letters, numbers, underscore. If your challenge contains characters such as `! @ # $ % ^ & * ( )`, rotate to a simpler value.
+
+**Quick checklist**
+* {yourCertName} chosen
+* {yourScepUrl} confirmed
+* {yourScepChallenge} validated (plain text, simple characters)
+* {yourScepCAThumbprint} ready (no colons, no spaces)
+
+---
+
+## Step 2. Create Fleet secrets
+
+Follow Fleet’s guide: https://fleetdm.com/guides/secrets-in-scripts-and-configuration-profiles
+
+Create these secrets in Fleet (Controls → Variables) or via GitOps:
+
+| Secret name | Value you collected |
+|---|---|
+| `FLEET_SECRET_OKTA_SCEP_URL` | `{yourScepUrl}` |
+| `FLEET_SECRET_OKTA_SCEP_CHALLENGE` | `{yourScepChallenge}` (plain text, simple characters) |
+| `FLEET_SECRET_OKTA_CA_THUMBPRINT` | SHA256 thumbprint with no colons, no spaces
+| `FLEET_SECRET_API` | Fleet API token used by the Exec script |
+
+Optional convenience secret:
+* `FLEET_SECRET_OKTA_CERT_NAME` set to `{yourCertName}`
+
+**Security notes**
+* Fleet does not hide the secret in script results. Don't print/echo your secrets to the console output.
+
+---
+
+## Step 3. Use Fleet’s XML CSP profile
+
+Source file in the Fleet repo:
+```
+docs/solutions/Windows/configuration-profiles/install Okta attestation certificate - [Bundle].xml
+```
+
+Only change the following placeholders:
+
+* `{yourCertName}` set to the SCEP node name you chose in Step 1.2
+* `{yourScepUrl}` replaced with `$FLEET_SECRET_OKTA_SCEP_URL`
+* `{yourScepChallenge}` replaced with `$FLEET_SECRET_OKTA_SCEP_CHALLENGE` (plain text, simple characters)
+* `{yourScepCAThumbprint}` replaced with `$FLEET_SECRET_OKTA_CA_THUMBPRINT` (no colons, no spaces)
+
+**Important**
+Use the same `{yourCertName}` in both the profile path and the Exec path. If they differ, the Exec will 404.
+
+### Replace just these lines in the profile
+
+```xml
+
+-
+
+ ./Device/Vendor/MSFT/ClientCertificateInstall/SCEP/{yourCertName}/Install/ServerURL
+
+ chr
+ $FLEET_SECRET_OKTA_SCEP_URL
+
+
+
+-
+
+ ./Device/Vendor/MSFT/ClientCertificateInstall/SCEP/{yourCertName}/Install/Challenge
+
+ chr
+ $FLEET_SECRET_OKTA_SCEP_CHALLENGE
+
+
+
+-
+
+ ./Device/Vendor/MSFT/ClientCertificateInstall/SCEP/{yourCertName}/Install/CAThumbprint
+
+ chr
+ $FLEET_SECRET_OKTA_CA_THUMBPRINT
+
+```
+
+Keep the other defaults from the file (KeyLength 2048, KeyUsage 160, HashAlgorithm `SHA-1`, SubjectName `CN=$FLEET_VAR_HOST_UUID managementAttestation`, EKUMapping, RetryCount, RetryDelay).
+
+Deploy the profile to your Windows hosts using Fleet.
+
+---
+
+## Step 4. Update the Exec script and run Enroll
+
+Script location in repo:
+`docs/solutions/Windows/scripts/trigger-scep-enrollment.ps1`
+
+Your Exec must target the same `{yourCertName}` as in the profile. Example path:
+```
+./Device/Vendor/MSFT/ClientCertificateInstall/SCEP/{yourCertName}/Install/Enroll
+```
+
+Update your PowerShell script to read the API token from the secret, set your node name, and build the correct LocURI.
+
+```powershell
+# ----- USER SETTINGS -----
+# Add your secrets in Fleet (Controls > Variables) or via GitOps.
+# The variable named "API" becomes FLEET_SECRET_API
+# Full guidance: https://fleetdm.com/guides/secrets-in-scripts-and-configuration-profiles
+
+$NODE_NAME = "OKTA" # must match {yourCertName} in the XML
+$FLEET_API = "$FLEET_SECRET_API" # injected by Fleet
+
+$locUri = "./Device/Vendor/MSFT/ClientCertificateInstall/SCEP/$NODE_NAME/Install/Enroll"
+# ...construct and send the Exec command body referencing $locUri...
+```
+
+Run the script from Fleet so secrets inject automatically.
+
+---
+
+## Step 5. Verify enrollment
+
+**PowerShell**
+```powershell
+Get-ChildItem Cert:\LocalMachine\My |
+ Where-Object { $_.Subject -like "*managementAttestation*" } |
+ Format-List Subject, Thumbprint, NotAfter
+```
+
+**GUI**
+* Open `certlm.msc`
+* Personal > Certificates
+* Confirm a certificate whose Subject contains `managementAttestation`
+
+---
+
+## Step 6. Renewal
+
+* Automated workflow. Use a Fleet query to find certificates expiring within 30 days and trigger the Exec command for those hosts.
+
+Find certs expiring within 30 days:
+```TODO!
+```
+
+---
+
+## Troubleshooting
+
+* Exec returns 404: node name mismatch. Ensure `{yourCertName}` in XML equals `$NODE_NAME` in the script.
+* Enrollment fails immediately: check `ServerURL`, `CAThumbprint` format, and that the device can reach the SCEP URL.
+* Challenge rejected: try a simpler plain text challenge, or base64 encode and update the XML ``.
+* Nothing in `Cert:\LocalMachine\My`: review Device Management logs
+ ```powershell
+ Get-WinEvent -LogName Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin -MaxEvents 50
+ ```
+
+---
+
+
+
+
+
+
+