Releases: glennbyron1/CAC-program
v1.2 — Azure VPN end-to-end; same YubiKey unlocks AD AND cloud
Headline — Same physical YubiKey now unlocks AD AND Azure VPN
One slot 9a cert, two authentication contexts. Kerberos PKINIT (Event 4768 Pre-Auth Type 16) authenticates AD logon on-prem; EAP-TLS authenticates Azure Point-to-Site VPN in the cloud. The credential never leaves the hardware token. Both authentications validate the same chain to the same internal Lab-CA. One possession factor (the card), one knowledge factor (the PIN), two clouds — without ever provisioning a parallel "VPN credential."
This is the "hybrid trust" claim federal Zero-Trust roles look for. Not "I built two things"; one credential that works in two clouds because both trust the same internal CA.
📄 Build guide: Architecture/Azure-VPN-Guide.md (ARCH-ICAM-013)
Maps to NIST SP 800-53 IA-2, IA-2(11), IA-5(11), AC-17, SC-8, SC-12, SC-17, CM-3, AU-6 · DoD ZTRA Identity + Devices + Networks pillars · NIST SP 800-207 Zero Trust Architecture.
What's new
Phase 9 Azure VPN — built end-to-end
A complete deploy → test → teardown cycle of Azure Point-to-Site VPN with certificate authentication from the lab's internal CA:
- Cost guardrail ($20/month budget alert wired before any resource was created)
- VNet
10.20.0.0/16with the requiredGatewaySubnet10.20.255.0/27 - VpnGw1 VPN Gateway with Standard Static public IP (~40 min deploy)
- Lab-CA root cert exported from Lab-DC01 via PowerShell Direct, Base64-uploaded to Azure as the P2S trust anchor
- jdoe's YubiKey-resident smart card cert (slot 9a, the same one used for AD logon since v1.1) authenticated the EAP-TLS exchange
- P2S client assigned
172.16.0.2from the VPN address pool; tunnel up - Teardown via
Remove-AzResourceGroup -Force -AsJobin the same session — total cost ~$0.40 in gateway hours
Slot 5 of Demo-Walkthrough closed — all 8 slots now captured
Screenshots/05-vpn-azure-eap-cert-auth-no-password.png— headline: jdoe@lab.local connected tovnet-cac-lab-phase9over EAP-TLS, duration counter running, no password promptScreenshots/05b-vpn-ipconfig-172-16-0-2.png—ipconfigshowing the PPP adapter with the P2S-assigned IPScreenshots/05c-vpn-caption-azure-p2s-eap-tls.png— configuration caption documenting the auth mechanism
PKI architecture discovery — designed two-tier, deployed single-tier
Honest portfolio finding captured in the Azure VPN guide:
- The lab's design doc describes a two-tier PKI (offline Root CA signs Issuing CA on Lab-DC01)
- Lab-DC01's Trusted Root store shows
CN=LAB-CAas self-signed — Subject == Issuer - The separate
CN=Lab Root CAcert exists (10-year, valid 2026 → 2036) but hasbasicConstraints CA:TRUE, pathlen:0— per RFC 5280, this means the Root can only sign end-entity certs, NOT sub-CAs. So it could not have signed LAB-CA as an intermediate. - The deployed PKI is operationally single-tier with LAB-CA as the trust anchor
Captured as a "designed vs deployed" delta in the build guide, not hidden. Future remediation queued.
Install pitfall + working fix path
Three install paths were attempted before one worked. The lesson ships with the build guide so anyone walking the same path doesn't lose an hour:
| Path | Result |
|---|---|
VpnClientSetupAmd64.exe → Run as administrator |
Blocked by scforceoption=1 GPO elevation prompt for CardIssuer VSC PIN; even with creds entered, installer left system phonebook at 0 bytes |
| Azure VPN Client app from Microsoft Store + XML import with thumbprint hardcoded | Cert picker doesn't enumerate YubiKey-resident certs; Save validation fails with "client certificate must include an issuer" even with <hash> and <issuer> both populated |
VpnProfileSetup.ps1 from elevated PowerShell + Tunnel type Automatic |
✅ Works — the script bypasses the GUI elevation block; Automatic tunnel type gives Windows IKEv2/SSTP/L2TP fallback |
Three Azure VPN docs consolidated to one canonical guide
The original "starter" Azure-VPN-Lab-Guide.md, the build doc Phase9-Azure-VPN-Build-Guide.md, and the roadmap design CAC_PIV_Phase9_Azure_VPN_ConditionalAccess.md are merged into one Architecture/Azure-VPN-Guide.md. Sits parallel to the existing Architecture/WatchGuard-IKEv2-VPN-Guide.md for on-prem.
The new guide covers: CGNAT framing, cost discipline, architecture diagram with trust chain, full 11-step build sequence, lessons learned, PKI discovery, control mapping (NIST + Zero Trust), future phases 9.1/9.3/9.4/9.5/9.6 (designed not built), and honest framing.
What's NOT in v1.2 — designed but not built
Documented in the build guide so the "what's shipped vs what's designed" line is honest:
- 9.1 Entra ID baseline — M365 Developer tenant + synthetic test users + MFA enrollment
- 9.3 Conditional Access — the actual DoD pattern, where "VPN = you're in" becomes "policy decides on every connection"
- 9.4 Device compliance signal — Intune compliance policy + Entra device registration
- 9.5 Visibility & decisioning — Entra sign-in logs to Log Analytics + basic detections
These will land in a future v1.3 or as their own incremental tags. The Phase 9 design doc is preserved inside the consolidated build guide so the full vision stays visible.
Infrastructure changes
Documentation consolidation
3 source docs deleted: Architecture/Azure-VPN-Lab-Guide.md, Architecture/Phase9-Azure-VPN-Build-Guide.md, Architecture/Roadmap/CAC_PIV_Phase9_Azure_VPN_ConditionalAccess.md
7 cross-references updated across Demo-Walkthrough.md, CSET-Assessment-Guide.md, SSP-Template.md, and TODO.md. Zero broken refs after consolidation.
Screenshots catalog rewritten
Screenshots/README.md reorganized into 5 tiers (Demo-Walkthrough headline shots, Lessons-Learned discovery evidence, session evidence, FIDO2 credential evidence, portfolio reference). Phase 9 slot 5 captures added to the catalog.
For reviewers — start here
| Artifact | Why it matters |
|---|---|
Architecture/Azure-VPN-Guide.md |
The headline v1.2 deliverable. Complete Azure VPN P2S build with Lab-CA + YubiKey cert auth. Includes the install pitfall lesson and the PKI discovery. |
Demo-Walkthrough.md Step 5 |
The slot 5 marquee shot — same YubiKey unlocks AD AND Azure VPN, no password anywhere |
Lab-Kit/Reference/RUNBOOK-YubiKey-Enrollment.md |
The runbook that produced jdoe's smart card cert; same cert authenticates v1.2's Azure VPN |
Architecture/Lessons-Learned/2026-06-16-Silent-VSC-Fallback-Discovery.md |
The v1.1 acceptance check pattern this build inherits — certutil -scinfo as reader-level verification |
Acknowledgments
The "same physical card, two clouds" pattern is a real federal-IT design pattern, not original to this lab. What this lab demonstrates is the buildable proof of it in a homelab with commodity hardware (one YubiKey, free Azure for Students subscription, internal CA). The deploy → test → teardown cost discipline is itself a learnable artifact for cloud-governance interview answers.
v1.1 — YubiKey end-to-end + Silent VSC Fallback discovery
Headline finding — Silent TPM Virtual Smart Card Fallback Discovery
During v1.1 enrollment testing we discovered that smart card enrollment can silently land on a TPM-backed Virtual Smart Card instead of the intended physical token, with no error surfaced anywhere in the GUI. Logon works, audit events fire, the user is happy — but the credential is on the wrong factor. A hardware-factor authentication design silently demotes to a software-factor design.
The discovery doc captures the failure mode in full: environment, what was observed, why it happened, how certutil -scinfo exposes it, a detection-script sketch, and four compensating controls. The most important compensating control is procedural — the operator runbook now mandates a four-point acceptance check on every enrollment.
📄 Architecture/Lessons-Learned/2026-06-16-Silent-VSC-Fallback-Discovery.md
Maps to NIST SP 800-53 IA-2(11), IA-5(11), CM-6, AU-6 and CISA ZTMM Identity pillar.
What's new
YubiKey enrollment validated end-to-end
jdoe enrolled on a physical YubiKey 5 NFC via New-YubiKeyToken.ps1 + Enroll-on-Behalf, with the Yubico Smart Card Minidriver, against the lab's internal Issuing CA. Logs into WO02 with card + PIN under full SmartcardLogonRequired=True enforcement. Event 4768 Pre-Auth Type 16 (PKINIT) on the DC confirms the protocol-level handshake.
Slots 1, 1b, 1d, and 4 of the Demo-Walkthrough captured and embedded:
- Slot 1 —
01-lockscreen-smartcard-only.png— lock screen with smart-card-only logon - Slot 1b —
01b-certutil-scinfo-yubikey-chain-validates.png— cert chain validates on physical reader - Slot 1d —
01d-certutil-scinfo-yubikey-cert-context-jane-doe.png— Subject + UPN binding evidence - Slot 4 —
04-session-lock-on-card-removal.png— ~2-second session lock on card removal
Hirsch uTrust FIDO2 FIPS — declared NO-GO for PIV (n=2 cards)
Factory 3DES management key rejected on both cards (OpenSC admin_mode failed -1201). The vendor minidriver assumes an already-personalized card; it does not personalize one. FIDO2 applet on the same cards works fine — but for PIV / AD smart-card logon, the card is a dead end without the vendor's per-card management key + personalization software.
This is now the lab's procurement-evaluation criterion for any new card form factor: can the card be brought to a known-management-key state using off-the-shelf tooling (ykman, OpenSC, GIDS minidriver), or does it require vendor-specific software + per-card management key?
Enrollment kit — five new operator artifacts
In Lab-Kit/03-DomainController/:
New-LabUser.ps1— AD user creation with OU resolver + numbered menuNew-TokenEnrollment.ps1— RA + Issuer ceremony script (SCRIPT-ICAM-011, NIST SP 800-53 AC-5)New-YubiKeyToken.ps1— YubiKey PIV provisioning (SCRIPT-ICAM-016)Deploy-ScriptsToDC.ps1— SMB-based deploy of the three above to Lab-DC01
In Lab-Kit/Reference/:
RUNBOOK-YubiKey-Enrollment.md(RUNBOOK-ICAM-001) — operator runbook for the scripted pathMANUAL-Enrollment-Walkthrough.md(RUNBOOK-ICAM-002) — GUI-driven walkthrough using the real-world "copy a peer in ADUC" pattern. Companion piece showing what the scripts automate, click-by-click.
The two runbooks are explicitly cross-referenced. Anyone reading both gets a complete picture of the workflow regardless of whether their environment uses scripted automation or GUI-driven user management.
Card-Test-Matrix.md filled in
PIV and FIDO2 rows populated for both tested cards (YubiKey 5 NFC, Hirsch uTrust FIDO2 FIPS). Six observations documenting the YubiKey path that works, the Yubico minidriver requirement, the mgmt-key sequencing rule, the Hirsch NO-GO finding, the silent VSC fallback callout, and the procurement-evaluation criterion for future cards.
Framing rewritten as ongoing testing rather than complete — the matrix grows as new card form factors are evaluated.
Documentation expansions
Architecture/Lessons-Learned/2026-06-16-CAC-Enrollment-Session.md— full forensic session log (scrubbed) tracing the v1.1 enrollment journey with 10 documented issues and fixes- 24 v1.1 screenshots staged in
Screenshots/with named slot filenames; one redacted to hide YubiKey serial + AES-256 management key Screenshots/README.mdcatalog rewritten to reflect five-tier structure (headline / lessons-learned / session evidence / FIDO2 / portfolio reference)
Infrastructure improvements
Scrub-Repo.ps1 — two bug fixes, caught before any file was touched
Both bugs were caught by the -WhatIf preview during the v1.1 pre-push cycle, not by a real run. They're shipped publicly as documentation:
-
_*meta-key filter — when documentation keys like_README,_README_REBUILTare added to.scrub-patterns.local.jsonto explain the file inline, the scrubber must skip them. Previously, it would treat them as substitution patterns and corrupt every file containing the literal string_README. Now matchesScan-LocalRepo.ps1's existing behavior. -
Skip gitignored local-only scanner files —
Scan-LocalRepo.ps1andSCAN-README.mdare gitignored local tools that contain real values BY DESIGN (they document the scrub patterns). The scrubber must not rewrite them, since that would degrade the local documentation used during pre-push scans.
Scrub pattern coverage extended
- New literal patterns: YubiKey AES-256 management key, Hyper-V host name, YubiKey serial
- New preemptive tripwire patterns for off-repo business context that should never appear in the public repo (commercial-build entity names, vendor-relationship contacts)
- Four
_README_*documentation keys explain the file's organization, rebuild history, and tripwire policy
Deferred to a future tag
- Slot 5 — VPN EAP-TLS capture — depends on Phase 9 (Azure VPN Gateway) or Phase 9B (OPNsense on-prem appliance). Honest framing: the credential story closes when the VPN build lands, not before.
- Real AAGUID values via
ykman fido infoand equivalent for Hirsch — webauthn.io anonymizes them; device-side CLI captures pending - Optional Ansible STIG hardening pass — would push SCAP scores up but the delta from hardening is the portfolio value, not the absolute number
- Additional card form factors — GIDS smart cards and other vendors queued
For reviewers — start here
| Artifact | Why it matters |
|---|---|
Architecture/Lessons-Learned/2026-06-16-Silent-VSC-Fallback-Discovery.md |
The headline DevSecOps finding. Demonstrates ability to catch a high-severity silent failure mode that vendor and Microsoft documentation does not adequately cover, and to codify the fix as a procedural control. |
Lab-Kit/Reference/RUNBOOK-YubiKey-Enrollment.md + MANUAL-Enrollment-Walkthrough.md |
Two runbooks — scripted and GUI-driven — for the same workflow. Shows operator-engineering discipline: automation for compliance environments, manual procedure for small-shop reality. |
Lab-Kit/Reference/Card-Test-Matrix.md |
Hardware-evaluation methodology applied across multiple form factors with both ✅ and ❌ outcomes. Procurement-question criterion stated explicitly. |
Demo-Walkthrough.md |
Step-by-step live demo with slots 1, 1b, 1d, 3, 4, 6, 7, 8 captured. NIST control mapping table at the bottom. |
Diff from v1.0: 46 changed files, 19 new files, 27 modified files.
Acknowledgments: The Silent VSC Fallback failure mode was discovered during normal lab operations, not from any vendor or industry source. The methodology to detect it (certutil -scinfo as a reader-level acceptance check) is now baked into the operator runbook and the four-point verification at the end of every enrollment.
First stable milestone of the CAC/PIV ICAM portfolio.
What's in v1.0
- Two-tier PKI — Offline Root CA + Enterprise Issuing CA on Lab-DC01
- Smart card MFA end-to-end — VM + physical endpoint (WO02). User
LAB\labtechenrolled with TPM Virtual Smart Card; smart card logon confirmed working via RDP - NIST IA-2(11) protocol evidence — Event 4768 captured with Pre-Auth Type 16 (PKINIT). See
Demo-Walkthrough.mdStep 3 - SCAP compliance baseline — three hosts scanned with SCC 5.10.2: DC01 44.95% → 42.66%, WS01 42.20% → 42.20%, WO02 37.00% (Win 11 STIG, MAC-1 Classified). Full HTML / XCCDF / OVAL / CKL artifacts staged in
Compliance-Reports/ - Phase 8 Zero Trust extension — 21 PowerShell scripts in
Lab-Kit/07-ZeroTrust/(8 working implementations + 13 product-dependent scaffolds) covering tiered admin model, Authentication Policy Silos, device certs, Kerberos lifetimes, microsegmentation, ZT validation; companionDemo-Walkthrough-ZT.md - RMF artifact package — SAR, POAM, SSP, Annual-STIG-Rescan-SOP populated with real numbers
- DevSecOps scaffolding — GitHub Actions (CodeQL, gitleaks, secret scan, PSScriptAnalyzer), pre-commit hook, local sensitive-pattern scanner,
Scrub-Repo.ps1workflow
Deferred to v1.1 (card-blocked)
- Demo screenshots — lock screen smart-card-only, session lock on card removal, VPN connected (waiting on YubiKey 5 NFC + Hirsch uTrust FIDO2 cards)
- VPN EAP-TLS test from WO02
Card-Test-Matrix.mdwith PIV / FIDO2 / reset workflow comparisons
Frameworks
NIST SP 800-53 Rev 5 (AC-2/3/5/6/11/12/17, IA-2/2(11)/3/5, SC-7/8, AU-2/6/12, CA-7, SI-4), NIST SP 800-207, CISA Zero Trust Maturity Model v2.0, FIPS 201-3, DISA STIG.