WRKLOG-2026-06-08
Summary
Sunday. Massive productivity session. CLAUDE.md review and correction (7 fixes). 13-week PeopleSoft timesheet backfill (520 hours, Mar 09 – Jun 05) with scalable partial architecture. Three P1 investigations launched: spam bypass (SCL 9 SFV:SKA override — user safe sender list), Intune hybrid domain join cert auth failure (forget/rejoin pattern), and rsyslog centralized collector (CHLXSYSLOG01 Debian 13, first login, ASA syslog config). Fixed build-antora-page.sh nested include resolution — PDFs went from 172K to 1.8MB with full content. rsyslog-ops command reference created in d001 with ISO 8601 + MSWinEventLog parsing. API rate limits reference created. Project audit revealed P0/P1/P2 trackers 47 days stale.
URGENT - All Domains
Carryover Backlog (CRITICAL)
| Task | Details | Origin | Days | Status |
|---|---|---|---|---|
MSCHAPv2 Migration Report |
Report due. 6-sheet Standard Report (exec summary, trend, waves, device detail, stale, policy match). Sheet 6 added 05-14: policy match by protocol for removal planning + anonymous identity validation. Migration window 2026-05-04 to 2026-05-30. ~6,227 devices, 5 waves. |
2026-04-17 |
57 |
P0 - DUE — run report this week |
Abnormal Security — ✅ COMPLETE |
CR-2026-05-07-abnormal-read-write. CAB approved 2026-05-12. Implemented successfully 2026-05-13. Read/write enabled for pilot group. Post-deployment validation pending. |
2026-05-07 |
37 |
✅ IMPLEMENTED — post-validation pending |
SIEM QRadar → Sentinel Migration |
Lead role. Monad console error RESOLVED 2026-05-12 — secrets configured in CHLA production tenant. ISE secure syslog integration in progress — cert imported, remote logging target configured, streaming errors under investigation. Blocking: DCR not created (Rule ID + Stream Name). Azure private network policy unresolved. Victor + Mauricio action. |
2026-04-10 |
64 |
P0 - ACTIVE — ISE syslog + DCR blocking |
Monad Pipeline Evaluation |
Sentinel output connector. Console error resolved. 3 of 6 values configured. Remaining: Endpoint URL (have it), Rule ID + Stream Name (need DCR). ISE Remote Logging Target configured 2026-05-18 — TLS cert imported, secure syslog target created. Streaming errors in Monad console under investigation. |
2026-03-11 |
94 |
P0 - ACTIVE — ISE integration in progress |
Guest Redirect ACL |
Guest redirect ACL work needed. Related to Mandiant remediation findings. |
2026-05-12 |
32 |
P0 - TODO |
ISE Patch 10 (CVE-2026-20147 CVSS 9.9) |
ISE 3.2 Patch 10. Supersedes Patch 9. 61 days on a CVSS 9.9 — schedule maintenance window. Write CR if needed. |
2026-03-12 |
93 |
P0 - OVERDUE — schedule immediately |
k3s NAT verification |
NAT rule 170 for 10.42.0.0/16 pod network - test internet connectivity. 64 days — test this week or defer to Q3. |
2026-03-09 |
96 |
P0 - BLOCKING — TRIAGE: schedule or defer |
Wazuh indexer recovery |
Restart pod after NAT confirmed working - SIEM visibility blocked. Blocked by k3s NAT — cannot proceed until above resolved. |
2026-03-09 |
96 |
P0 - Blocked by k3s |
Strongline Gateway VLAN fix |
8 devices in wrong identity group (David Rukiza assigned) |
2026-03-16 |
89 |
P0 - TODO |
TCP Clocks deployment |
ISE identity group validation, query outputs, comms with team. Active d001 data Apr 22-23. |
2026-04-22 |
52 |
P0 - ACTIVE |
IoT Dr. Kim — recurring |
Sleep study devices (Apr 15-16), watches recurrence (Apr 22). 5 incident versions in d001. Validate iPSK enrollment. |
2026-04-15 |
59 |
P0 - RECURRING |
Murus Portae (WAF) — Phase 0 |
FMC cert expired, ACP returns zero rules. d001: zone map, architecture D2, FMC API reference, ops script. |
2026-04-16 |
58 |
P0 - INVESTIGATING |
Vocera EAP-TLS Supplicant Fix |
~10 phones failing 802.1X, missing supplicant config. 61 days — schedule with clinical engineering team. |
2026-03-12 |
93 |
P1 - TODO — schedule |
ISE MnT Messaging Service |
Enable "Use ISE Messaging Service for UDP syslogs delivery". 61 days — low risk, schedule with ISE Patch 10 maintenance window. |
2026-03-12 |
93 |
P2 - BUNDLE with Patch 10 |
| Professional backlog remains critical. Check Days column for priorities. |
BLOCKERS — Fix Immediately
| Task | Details | Origin | Days | Impact |
|---|---|---|---|---|
Z Fold 7 Termux |
gopass and SSH not working |
2026-03-10 |
58 |
BLOCKER — Cannot access passwords on mobile |
gopass v3 organization |
Inconsistent structure, poor key-value usage |
2026-03-20 |
48 |
Inefficient password management, no aggregation |
Git history scrub — sensitive personal terms |
Plaintext references to personal legal matters in committed worklogs (WRKLOG-2026-03-14, WRKLOG-2026-04-18). Forward-fixed but old commits still contain strings. Requires |
2026-04-22 |
15 |
SECURITY — sensitive terms in public git history |
Runbook: Git History Scrub (d000 Personal Terms)
Problem: Two committed worklogs contained plaintext references to personal legal matters. The files have been edited (forward-fix), but git history retains the original text in prior commits.
Affected commits: Any commit touching these files:
# Identify affected commits
git log --oneline -- \
docs/modules/ROOT/pages/2026/03/WRKLOG-2026-03-14.adoc \
docs/modules/ROOT/pages/2026/04/WRKLOG-2026-04-18.adoc
Scrub procedure:
# 1. BEFORE: Full backup of the repo
cp -a ~/atelier/_bibliotheca/domus-captures ~/atelier/_bibliotheca/domus-captures.bak
# 2. Install git-filter-repo (if not present)
# Arch: pacman -S git-filter-repo
# pip: pip install git-filter-repo
# 3. Create expressions file for replacement
cat > /tmp/scrub-expressions.txt << 'EXPR'
regex:(?i)divorce==[REDACTED]
regex:(?i)dissolutio(?!n\.adoc\.age)==[REDACTED-LEGAL]
regex:(?i)iliana==[REDACTED-NAME]
regex:(?i)angulo-arreola==[REDACTED-NAME]
regex:legal-divorce-notes\.age==legal-notes.age
regex:1099-NEC-iliana==1099-NEC
EXPR
# 4. Verify before (dry run — count matches in history)
git log -p --all -S 'divorce' -- '*.adoc' | grep -c 'divorce' || echo "0 matches"
git log -p --all -S 'iliana' -- '*.adoc' | grep -c 'iliana' || echo "0 matches"
# 5. Run filter-repo (DESTRUCTIVE — rewrites all commit hashes)
git filter-repo --replace-text /tmp/scrub-expressions.txt --force
# 6. Verify after
git log -p --all -S 'divorce' -- '*.adoc' | grep -c 'divorce' || echo "0 matches — CLEAN"
git log -p --all -S 'iliana' -- '*.adoc' | grep -c 'iliana' || echo "0 matches — CLEAN"
# 7. Re-add remotes (filter-repo removes them)
git remote add origin git@github.com:<user>/domus-captures.git
# Add any other remotes (Gitea, etc.)
# 8. Force-push to all remotes (DESTRUCTIVE — overwrites remote history)
git remote | xargs -I{} git push {} main --force
# 9. Clean up
rm /tmp/scrub-expressions.txt
rm -rf ~/atelier/_bibliotheca/domus-captures.bak # only after verifying
Post-scrub checklist:
-
Backup created before running
-
git filter-repoinstalled -
Expressions file reviewed — no false positives (e.g., Don Quijote "Angulo el Malo" is in
segunda-parte/texto/texto-011.adoc— the regex targetsangulo-arreolaspecifically to avoid this) -
Dry-run counts match expectations
-
Filter-repo executed
-
Post-scrub verification shows 0 matches
-
Remotes re-added
-
Force-pushed to all remotes
-
Cloudflare Pages rebuild verified
-
Local clones on other machines re-cloned or
git fetch --all && git reset --hard origin/main -
Backup removed
URGENT - Requires Immediate Action
| Item | Details | Deadline | Status | Impact |
|---|---|---|---|---|
Housing Search |
Granada Hills area - apartments/rooms |
TBD |
In Progress |
Quality of life, commute |
2025 Tax — IRS Transcript Review |
MFJ filed 2026-04-22. Pull IRS Return Transcript to verify contents. Consult attorney re: Form 8857 (Innocent Spouse Relief). Details in encrypted case file. |
Before attorney meeting |
In Progress |
Financial — liability exposure. See encrypted D000 case file. |
Rack Relocation |
Physical move of server rack. CR written: CR-2026-04-18 (pending in infra-ops). Borg backup completed. VM XML dumps, switch save, shutdown/startup procedure documented. |
TBD |
Pending |
Infrastructure downtime — all services offline during move |
D000 Legal Planning |
Encrypted D000 case file. Open: |
Before Jan 2029 |
Active — escalating |
Life transition — see case file for details |
Credit Report Review |
Pull reports from all 3 bureaus via annualcreditreport.com. Verify no unknown joint accounts or debts. Credentials in gopass: |
TBD |
In Progress |
Financial discovery — FL-142 preparation |
Gopass Security Audit |
Rotate passwords on shared/known accounts. Add 2FA backup codes to |
TBD |
Pending |
Digital security — pre-filing preparation |
Subscription Audit |
Download 3 months bank/CC statements (Chase, NFCU, USAA). Identify all recurring charges. Cancel unnecessary. Document active subscriptions for FL-150. |
TBD |
Pending |
Financial — expense documentation |
401(k) Enrollment |
Enroll in CHLA 401(k) immediately. Post-separation contributions are 100% separate property. Reduces gross income for support calculations. Max 2026: $23,500/yr. |
In progress (started 5/4) |
In Progress |
Financial — support calculation + retirement |
URGENT — Performance Review Certifications
| Certification | Provider | Deadline | Status | Impact |
|---|---|---|---|---|
CISSP |
ISC² — Certified Information Systems Security Professional |
July 12, 2026 |
ACTIVE — Week 2 of 10 (Project) |
Required for performance review. 10-week accelerated plan. |
RHCSA 9 |
Red Hat Certified System Administrator |
Q3 2026 |
ACTIVE — 21-phase curriculum (Project) |
After CISSP. Required for performance review. |
| CISSP: 41 days remaining (exam July 12). Domain 1 study in progress. Schedule exam today (06-01). |
Early Morning - 5:30am
Regex Training (CRITICAL CARRYOVER)
-
Session 3 - Character classes, word boundaries
-
Practice drills from regex-mastery curriculum
-
Status: 52 days carried over (since 2026-03-16) — CRITICAL
| Regex training continues to slip. This is the foundation for all CLI mastery. |
Daily Notes
Encrypted File Placement (2026-06-08 session captures)
| File | Location | Content |
|---|---|---|
|
|
P1 Intune hybrid join cert investigation — client/GPO/SCEP/PKCS/ISE commands |
|
|
CHLXSYSLOG01 operational command reference — health checks, live dashboards, parsing |
|
|
First login session capture — system recon, disk, network, listeners |
|
|
Full session: Monad API discovery, ASA logging config (FP-2140 + 5515), rsyslog Debian server |
decrypt-file data/d001/investigations/2026-06-08-intune-cert-auth/intune-cert-commands-2026-06-08.adoc.age
decrypt-file data/d001/projects/siem-qradar-to-sentinel/scripts/rsyslog-commands.adoc.age
decrypt-file data/d001/projects/siem-qradar-to-sentinel/scripts/rsyslog-commands-test.adoc.age
decrypt-file data/d001/projects/siem-qradar-to-sentinel/syslog-2026-06-08.adoc.age
Triage Status
| Item | Status | Destination |
|---|---|---|
⚡ CHARGE TIME IN PEOPLESOFT — Monday critical |
❌ not started |
PeopleSoft → submit hours before EOD |
Install BYOD P12 cert on ZFold7 (9C:83:06:CE:89:46) |
🟡 cert issued, needs P12 bundle transfer + install on device |
|
Fix dsec embedded quotes in vault unseal keys |
❌ not started |
dsec edit d000 dev/vault — remove wrapping double quotes |
Rejoin AD from ISE GUI |
❌ not started |
Phase 6 — DOMUS_AD join point |
Fix NAS SSH config (adminerosado → evanusmodestus + IdentitiesOnly) |
❌ not started |
~/.ssh/config line 245 |
Fix WLC SSH config (add PubkeyAuthentication no) |
❌ not started |
~/.ssh/config — add Host 9800-wlc-01 block |
Add NTP rule to vyos-02 (same as vyos-01 rule 45) |
❌ not started |
configure → set firewall ipv4 name MGMT_LOCAL rule 45 |
Add kvm-01 NAS mounts to /etc/fstab |
❌ not started |
10.50.1.70:/volume1/isos → /mnt/nas/isos |
Secondary NAS mount found unsynced — resync failed |
❌ blocked |
Investigate mount/NFS state, attempt manual resync |
Generalize build-antora-page.sh for HTML output |
❌ not started |
scripts/build-antora-page.sh |
Explore AsciiDoc CSV tables ( |
❌ not started |
codex/documentation/asciidoc.adoc or discoveries/ |
Printer unreachable — iPSK auth + VLAN filtering |
❌ blocked |
Depends on ISE rotation completing |
CLAUDE.md review — updated (this session) |
✅ done |
STD-024, file naming, skeleton, Makefile, invariants fixed |
P1: Spam bypass — SCL 9 delivered to inbox (SFV:SKA) |
🟡 investigation file created, commands ready |
spam-bypass-investigation.adoc — run Phase 1 on work machine |
P1: Intune hybrid join cert auth failure |
🟡 investigation file created, bulk ISE queries ready |
intune-cert-investigation.adoc — Anthony Martinez assisting |
rsyslog CHLXSYSLOG01 — first login, ASA syslog config |
🟡 server online, Windows logs arriving, ASA logs starting |
rsyslog-asa-troubleshooting.adoc + d001 rsyslog-ops.adoc.age |
PeopleSoft 13-week timesheet backfill |
✅ done |
13 partials in peoplesoft-codes/, wired to PRJ page |
build-antora-page.sh nested include fix |
✅ done |
Recursive resolution, PDFs 172K → 2.2MB |
API rate limits reference |
✅ done |
d001/api/max-api-calls.adoc.age |
Project audit (CHLA + personal) |
✅ done |
P0/P1/P2 47 days stale, 6 missing projects, refresh needed |
Linux Remediation — reuse Shahab model for enterprise scale |
🟡 inventory found, summary built |
d001/linux-research/ + domus-ise-linux + enterprise-linux-802.1x + scripts/ |
NEW: iTrack → Jira (Atlassian) migration |
🟡 announced — team must become proficient |
New project — needs codes.adoc entry, d001 scaffolding |
Ad-Hoc Requests
Ad-Hoc Requests
Capture walk-ups, Teams pings, and unplanned work here.
Linux Remediation — Enterprise Deployment Model
Executive Summary
The enterprise Linux remediation effort can leverage a proven 6-phase deployment model already validated with Dr. Shahab Asgharzadeh’s workstation (Spatial Biology & Genomics Core). The workflow, scripts, ISE policies, and troubleshooting runbooks are complete and documented across 4 repositories. This is not a greenfield build — it’s a scale-out of a working implementation.
Proven Deployment: Dr. Shahab (Jan 2026)
| Field | Value |
|---|---|
Subject |
Dr. Shahab Asgharzadeh — TSRI SBG |
Date |
2026-01-26 |
Deployment Record |
|
Auth Method |
EAP-TLS (certificate-based 802.1X) |
AD Integration |
SSSD domain join |
Cert Source |
Enterprise CA via cert enrollment |
Result |
Successful — wired 802.1X, AD login, dACL enforcement |
The 6-Phase Model
| Phase | Description | Deliverable | Status |
|---|---|---|---|
0: Prerequisites |
OS baseline, package install, network access |
|
✅ Proven |
1: AD Validation |
Verify AD reachability, DNS, Kerberos, LDAP |
|
✅ Proven |
2: Domain Join |
SSSD configuration, realm join, PAM integration |
|
✅ Proven |
3: Certificate Ops |
CA trust import, client cert enrollment, chain validation |
|
✅ Proven |
4: 802.1X Config |
NetworkManager nmcli EAP-TLS profile, wired and/or wireless |
|
✅ Proven |
5: Security Hardening |
UFW, dACL enforcement, privilege separation |
|
✅ Proven |
6: Validation |
End-to-end auth test, ISE verification, DataConnect confirmation |
|
✅ Proven |
Supporting Assets — Already Built
Scripts (ready to use)
| Script | Purpose |
|---|---|
|
Batch onboard MACs to ISE identity groups from file |
|
Validate MAC addresses against ISE (ERS + DataConnect) |
|
Create single endpoint in ISE via ERS API |
|
Query endpoint details from ISE |
|
Move endpoint between identity groups |
|
List all ISE endpoint identity groups |
|
Discover ISE policy sets and auth rules |
SQL Queries (DataConnect — inventory & validation)
| Query | Purpose |
|---|---|
|
All Linux endpoints — count, profile, auth status |
|
ISE profiler-detected Linux devices |
|
Linux devices with RADIUS auth history |
|
Unidentified Linux endpoints (remediation targets) |
Documentation Repositories
| Repository | Content | Size |
|---|---|---|
|
Full NetworkManager wired/wireless 802.1X config, cert enrollment, troubleshooting |
12 pages (34KB+ each) |
|
Deployment checklist, CLI reference, validation runbook, prerequisites |
4 deployment pages + runbooks |
|
nmcli reference, AD domain join runbook, cert operations |
General Linux ops |
|
Shahab-specific playbook, deployment guide, status tracking, team docs |
30+ files (encrypted) |
ISE Team Documentation (encrypted in d001)
| File | Content |
|---|---|
|
ISE policy set creation — authN/authZ rules for Linux |
|
Client-side config handoff for endpoint team |
|
ISE-side validation steps |
|
Switch port config, dACL, monitor mode → closed mode |
|
IOS-XE switch port templates for 802.1X |
|
Project communication history |
|
ISE-side troubleshooting (Live Log, policy trace) |
|
Rollback procedures for failed deployments |
Appendices (encrypted in d001)
| File | Content |
|---|---|
|
Pre-deployment checklist per device |
|
CLI quick reference for field engineers |
|
Config file locations and templates |
|
Risk register — known failure modes and mitigations |
|
Emergency disconnect / bypass procedures |
|
Client-side rollback (remove 802.1X, restore open access) |
|
EAP-TLS handshake failures, cert chain issues |
|
AD/SSSD login failures post-domain-join |
|
dACL not applied, wrong VLAN, access denied |
Scaling Strategy
| Step | Action |
|---|---|
1. Inventory |
Run |
2. Prioritize |
Group by department, criticality, network zone. Start with research labs (Shahab model fits directly) |
3. Batch onboard |
|
4. Deploy per phase |
Follow 6-phase model per device. Field engineer uses |
5. Validate |
|
6. Harden |
Switch from monitor mode to closed mode per switch port. |
Known Blocker
nmcli certificate "password required" prompt — documented during Shahab deployment. Workaround exists in endpoint/phase-4-dot1x-config.adoc. This is the P0 item that’s been open 72+ days. Must be resolved before scaling beyond individual deployments.
Access Commands
d001 open linux-research
dsource d001 dev/network/ise
dc-run-sql data/d001/projects/linux-research/sql/linux-inventory-summary.sql
# After decrypting
bat data/d001/projects/linux-research/linux-eaptls-deployment-guide.adoc
Investigation: Intune Auto-Enroll — Cert-Based WiFi Auth Failure
Incident Summary
| Field | Value |
|---|---|
Date Reported |
2026-06-08 |
Symptom |
Intune auto-enrolled devices fail WiFi auth. Forget + rejoin fixes it. |
Scope |
Hybrid Azure AD joined devices (GPO + Intune) |
Blamed System |
ISE (unconfirmed — ISE does NOT push certs, it validates them) |
Working Hypothesis |
Cert delivery timing or GPO/Intune WiFi profile conflict |
Key Fact |
Forget + rejoin fixes it → ISE policy is correct. The client is presenting the wrong cert or no cert. |
Talking Points
-
ISE does NOT push certificates to endpoints — it validates what the supplicant presents
-
"Forget + rejoin fixes it" proves ISE policy works — same device, same policy, passes after reconnect
-
Hybrid domain join = TWO cert sources (GPO AD CS + Intune SCEP/PKCS) — supplicant must choose correctly
-
Root cause is on the client side: cert store, WiFi profile config, or GPO/Intune conflict
Part 1: Client-Side Investigation (run on affected device)
Phase 1: Certificate Store Audit
Machine Personal Store
Get-ChildItem Cert:\LocalMachine\My | Format-Table Subject, Issuer, NotBefore, NotAfter, Thumbprint, HasPrivateKey -AutoSize
Get-ChildItem Cert:\LocalMachine\My | Where-Object {
$_.EnhancedKeyUsageList.ObjectId -contains "1.3.6.1.5.5.7.3.2"
} | Format-List Subject, Issuer, NotBefore, NotAfter, Thumbprint, SerialNumber, @{N='EKU';E={($_.EnhancedKeyUsageList.FriendlyName) -join ", "}}, @{N='Template';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Certificate Template Information'}).Format(0)}}
Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.NotAfter -lt (Get-Date) } | Format-Table Subject, Issuer, NotAfter, Thumbprint -AutoSize
User Personal Store
Get-ChildItem Cert:\CurrentUser\My | Format-Table Subject, Issuer, NotBefore, NotAfter, Thumbprint, HasPrivateKey -AutoSize
Get-ChildItem Cert:\CurrentUser\My | Where-Object {
$_.EnhancedKeyUsageList.ObjectId -contains "1.3.6.1.5.5.7.3.2"
} | Format-List Subject, Issuer, NotBefore, NotAfter, Thumbprint, SerialNumber, @{N='EKU';E={($_.EnhancedKeyUsageList.FriendlyName) -join ", "}}
GPO vs Intune Cert — Which CA Issued Each?
@("Cert:\CurrentUser\My", "Cert:\LocalMachine\My") | ForEach-Object {
$store = $_
Get-ChildItem $store | Where-Object {
$_.EnhancedKeyUsageList.ObjectId -contains "1.3.6.1.5.5.7.3.2"
} | Select-Object @{N='Store';E={$store}}, Subject, Issuer, NotAfter, @{N='DaysLeft';E={($_.NotAfter - (Get-Date)).Days}}, Thumbprint
} | Format-Table -AutoSize
Get-ChildItem Cert:\LocalMachine\My | Where-Object {
$_.EnhancedKeyUsageList.ObjectId -contains "1.3.6.1.5.5.7.3.2"
} | ForEach-Object {
$chain = New-Object Security.Cryptography.X509Certificates.X509Chain
$built = $chain.Build($_)
[PSCustomObject]@{
Subject = $_.Subject
Issuer = $_.Issuer
ChainValid = $built
ChainStatus = if($built){"OK"}else{($chain.ChainStatus.StatusInformation) -join "; "}
ChainLength = $chain.ChainElements.Count
}
} | Format-Table -AutoSize
Trusted Root & Intermediate CA Stores
Get-ChildItem Cert:\LocalMachine\Root | Where-Object {
$_.Subject -match "CHLA|Intune|SCEP|ISE|DigiCert|Enterprise"
} | Format-Table Subject, NotAfter, Thumbprint -AutoSize
Get-ChildItem Cert:\LocalMachine\CA | Where-Object {
$_.Subject -match "CHLA|Intune|SCEP|ISE|Enterprise"
} | Format-Table Subject, Issuer, NotAfter, Thumbprint -AutoSize
Phase 2: WiFi Profile Inspection (netsh — no GUI)
List and Inspect
netsh wlan show profiles
netsh wlan show profile name="CHLA-Staff" key=clear
netsh wlan show profile name="CHLA_Remote" key=clear
netsh wlan show interfaces
foreach ($p in "CHLA_Staff","CHLA-Remote") {
netsh wlan export profile name="$p" folder=$env:TEMP key=clear > $null
$f = Get-ChildItem $env:TEMP "Wi-Fi-$p*.xml" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
yq -o=json '.' $f.FullName |
jq '
def norm:
walk(
if type == "object" then
with_entries(select(.key | startswith("+@") | not)) |
if has("+content") and (keys | length == 1)
then .["+content"]
else .
end
else .
end
);
norm
'
Remove-Item $f.FullName
}
{
"+p_xml": "version=\"1.0\"",
"WLANProfile": {
"name": "CHLA_Staff",
"SSIDConfig": {
"SSID": {
"hex": "43484C415F5374616666",
"name": "CHLA_Staff"
},
"nonBroadcast": "false"
},
"connectionType": "ESS",
"connectionMode": "auto",
"autoSwitch": "true",
"MSM": {
"security": {
"authEncryption": {
"authentication": "WPA2",
"encryption": "AES",
"useOneX": "true"
},
"PMKCacheMode": "enabled",
"PMKCacheTTL": "720",
"PMKCacheSize": "128",
"preAuthThrottle": "3",
"OneX": {
"heldPeriod": "1",
"authPeriod": "30",
"startPeriod": "5",
"maxStart": "5",
"maxAuthFailures": "5",
"authMode": "machineOrUser",
"EAPConfig": {
"EapHostConfig": {
"EapMethod": {
"Type": "55",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "311"
},
"Config": {
"EapTeap": {
"ServerValidation": {
"TrustedRootCAHash": [
"32 eb 83 1e da 15 9 8f fc fb 8f 30 33 4e bf 71 ac 43 ac db fe a8 48 59 d4 ef 74 f8 1 0 4f b4",
"d0 38 9b 27 d7 26 97 7f 2f c0 8a 7e af 71 8a bb 5b 9a a6 ac 2f fb 8e e8 68 a8 17 58 5e 29 35 f",
"cb 3c cb b7 60 31 e5 e0 13 8f 8d d3 9a 23 f9 de 47 ff c3 5e 43 c1 14 4c ea 27 d4 6a 5a b1 cb 5f",
"c0 6e 30 7f 7c fc 1d 32 fa 72 a4 c0 33 c8 7b 90 1 9a f2 16 f0 77 5d 64 97 8a 2e ca 6c 8a 23 e"
],
"DisablePrompt": "false",
"DownloadTrustedServerRoot": "false"
},
"Phase2Authentication": {
"InnerMethodConfig": [
{
"EapHostConfig": {
"EapMethod": {
"Type": "13",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "0"
},
"Config": {
"Eap": {
"Type": "13",
"EapType": {
"CredentialsSource": {
"CertificateStore": {
"SimpleCertSelection": "true"
}
},
"ServerValidation": {
"DisableUserPromptForServerValidation": "false",
"ServerNames": null,
"TrustedRootCA": [
"1d 6d 28 88 a1 8f 32 96 77 b6 0f a8 31 74 74 c1 69 bc e7 95",
"71 59 9a 14 19 84 7d b0 6b b5 99 36 17 44 0c 06 4e a8 c3 23",
"df 3c 24 f9 bf d6 66 76 1b 26 80 73 fe 06 d1 cc 8d 4f 82 a4",
"8b 3c 5b 9b 86 7d 4b e4 6d 1c b5 a0 1d 45 d6 7d c8 e9 40 82"
]
},
"DifferentUsername": "false",
"PerformServerValidation": "true",
"AcceptServerName": "false",
"TLSExtensions": {
"FilteringInfo": {
"ClientAuthEKUList": {}
}
}
}
}
}
}
},
{
"EapHostConfig": {
"EapMethod": {
"Type": "13",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "0"
},
"Config": {
"Eap": {
"Type": "13",
"EapType": {
"CredentialsSource": {
"CertificateStore": {
"SimpleCertSelection": "true"
}
},
"ServerValidation": {
"DisableUserPromptForServerValidation": "false",
"ServerNames": null,
"TrustedRootCA": [
"1d 6d 28 88 a1 8f 32 96 77 b6 0f a8 31 74 74 c1 69 bc e7 95",
"71 59 9a 14 19 84 7d b0 6b b5 99 36 17 44 0c 06 4e a8 c3 23",
"df 3c 24 f9 bf d6 66 76 1b 26 80 73 fe 06 d1 cc 8d 4f 82 a4",
"8b 3c 5b 9b 86 7d 4b e4 6d 1c b5 a0 1d 45 d6 7d c8 e9 40 82"
]
},
"DifferentUsername": "false",
"PerformServerValidation": "true",
"AcceptServerName": "false",
"TLSExtensions": {
"FilteringInfo": {
"ClientAuthEKUList": {}
}
}
}
}
}
}
}
]
},
"Phase1Identity": {
"IdentityPrivacy": "true",
"AnonymousIdentity": "anonymous"
}
}
}
}
}
}
}
}
}
}
{
"+p_xml": "version=\"1.0\"",
"WLANProfile": {
"name": "CHLA-Remote",
"SSIDConfig": {
"SSID": {
"hex": "43484C412D52656D6F7465",
"name": "CHLA-Remote"
},
"nonBroadcast": "false"
},
"connectionType": "ESS",
"connectionMode": "auto",
"autoSwitch": "true",
"MSM": {
"security": {
"authEncryption": {
"authentication": "WPA2",
"encryption": "AES",
"useOneX": "true"
},
"PMKCacheMode": "enabled",
"PMKCacheTTL": "720",
"PMKCacheSize": "128",
"preAuthThrottle": "3",
"OneX": {
"cacheUserData": "false",
"heldPeriod": "1",
"authPeriod": "30",
"startPeriod": "5",
"maxStart": "5",
"maxAuthFailures": "5",
"authMode": "machineOrUser",
"EAPConfig": {
"EapHostConfig": {
"EapMethod": {
"Type": "55",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "311"
},
"Config": {
"EapTeap": {
"ServerValidation": {
"TrustedRootCAHash": [
"32 eb 83 1e da 15 9 8f fc fb 8f 30 33 4e bf 71 ac 43 ac db fe a8 48 59 d4 ef 74 f8 1 0 4f b4",
"d0 38 9b 27 d7 26 97 7f 2f c0 8a 7e af 71 8a bb 5b 9a a6 ac 2f fb 8e e8 68 a8 17 58 5e 29 35 f",
"cb 3c cb b7 60 31 e5 e0 13 8f 8d d3 9a 23 f9 de 47 ff c3 5e 43 c1 14 4c ea 27 d4 6a 5a b1 cb 5f",
"c0 6e 30 7f 7c fc 1d 32 fa 72 a4 c0 33 c8 7b 90 1 9a f2 16 f0 77 5d 64 97 8a 2e ca 6c 8a 23 e"
],
"DisablePrompt": "false",
"DownloadTrustedServerRoot": "false"
},
"Phase2Authentication": {
"InnerMethodConfig": [
{
"EapHostConfig": {
"EapMethod": {
"Type": "13",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "0"
},
"Config": {
"Eap": {
"Type": "13",
"EapType": {
"CredentialsSource": {
"CertificateStore": {
"SimpleCertSelection": "true"
}
},
"ServerValidation": {
"DisableUserPromptForServerValidation": "false",
"ServerNames": null,
"TrustedRootCA": [
"1d 6d 28 88 a1 8f 32 96 77 b6 0f a8 31 74 74 c1 69 bc e7 95",
"71 59 9a 14 19 84 7d b0 6b b5 99 36 17 44 0c 06 4e a8 c3 23",
"df 3c 24 f9 bf d6 66 76 1b 26 80 73 fe 06 d1 cc 8d 4f 82 a4",
"8b 3c 5b 9b 86 7d 4b e4 6d 1c b5 a0 1d 45 d6 7d c8 e9 40 82"
]
},
"DifferentUsername": "false",
"PerformServerValidation": "true",
"AcceptServerName": "false",
"TLSExtensions": {
"FilteringInfo": {
"ClientAuthEKUList": {}
}
}
}
}
}
}
},
{
"EapHostConfig": {
"EapMethod": {
"Type": "13",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "0"
},
"Config": {
"Eap": {
"Type": "13",
"EapType": {
"CredentialsSource": {
"CertificateStore": {
"SimpleCertSelection": "true"
}
},
"ServerValidation": {
"DisableUserPromptForServerValidation": "false",
"ServerNames": null,
"TrustedRootCA": [
"1d 6d 28 88 a1 8f 32 96 77 b6 0f a8 31 74 74 c1 69 bc e7 95",
"71 59 9a 14 19 84 7d b0 6b b5 99 36 17 44 0c 06 4e a8 c3 23",
"df 3c 24 f9 bf d6 66 76 1b 26 80 73 fe 06 d1 cc 8d 4f 82 a4",
"8b 3c 5b 9b 86 7d 4b e4 6d 1c b5 a0 1d 45 d6 7d c8 e9 40 82"
]
},
"DifferentUsername": "false",
"PerformServerValidation": "true",
"AcceptServerName": "false",
"TLSExtensions": {
"FilteringInfo": {
"ClientAuthEKUList": {}
}
}
}
}
}
}
}
]
},
"Phase1Identity": {
"IdentityPrivacy": "true",
"AnonymousIdentity": "anonymous"
}
}
}
}
}
}
}
}
}
$profiles = foreach ($p in "CHLA_Staff","CHLA-Remote") {
netsh wlan export profile name="$p" folder=$env:TEMP key=clear > $null
$f = Get-ChildItem $env:TEMP "Wi-Fi-$p*.xml" | sort LastWriteTime -desc | select -f 1
yq -o=json '.' $f.FullName
Remove-Item $f.FullName
}
$profiles | jq -s '
def norm:
walk(
if type == "object" then
with_entries(select(.key | startswith("+@") | not)) |
if has("+content") and (keys | length == 1)
then .["+content"]
else .
end
else .
end
);
map(norm | .WLANProfile)
' | Set-Clipboard
[
{
"name": "CHLA_Staff",
"SSIDConfig": {
"SSID": {
"hex": "43484C415F5374616666",
"name": "CHLA_Staff"
},
"nonBroadcast": "false"
},
"connectionType": "ESS",
"connectionMode": "auto",
"autoSwitch": "true",
"MSM": {
"security": {
"authEncryption": {
"authentication": "WPA2",
"encryption": "AES",
"useOneX": "true"
},
"PMKCacheMode": "enabled",
"PMKCacheTTL": "720",
"PMKCacheSize": "128",
"preAuthThrottle": "3",
"OneX": {
"heldPeriod": "1",
"authPeriod": "30",
"startPeriod": "5",
"maxStart": "5",
"maxAuthFailures": "5",
"authMode": "machineOrUser",
"EAPConfig": {
"EapHostConfig": {
"EapMethod": {
"Type": "55",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "311"
},
"Config": {
"EapTeap": {
"ServerValidation": {
"TrustedRootCAHash": [
"32 eb 83 1e da 15 9 8f fc fb 8f 30 33 4e bf 71 ac 43 ac db fe a8 48 59 d4 ef 74 f8 1 0 4f b4",
"d0 38 9b 27 d7 26 97 7f 2f c0 8a 7e af 71 8a bb 5b 9a a6 ac 2f fb 8e e8 68 a8 17 58 5e 29 35 f",
"cb 3c cb b7 60 31 e5 e0 13 8f 8d d3 9a 23 f9 de 47 ff c3 5e 43 c1 14 4c ea 27 d4 6a 5a b1 cb 5f",
"c0 6e 30 7f 7c fc 1d 32 fa 72 a4 c0 33 c8 7b 90 1 9a f2 16 f0 77 5d 64 97 8a 2e ca 6c 8a 23 e"
],
"DisablePrompt": "false",
"DownloadTrustedServerRoot": "false"
},
"Phase2Authentication": {
"InnerMethodConfig": [
{
"EapHostConfig": {
"EapMethod": {
"Type": "13",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "0"
},
"Config": {
"Eap": {
"Type": "13",
"EapType": {
"CredentialsSource": {
"CertificateStore": {
"SimpleCertSelection": "true"
}
},
"ServerValidation": {
"DisableUserPromptForServerValidation": "false",
"ServerNames": null,
"TrustedRootCA": [
"1d 6d 28 88 a1 8f 32 96 77 b6 0f a8 31 74 74 c1 69 bc e7 95",
"71 59 9a 14 19 84 7d b0 6b b5 99 36 17 44 0c 06 4e a8 c3 23",
"df 3c 24 f9 bf d6 66 76 1b 26 80 73 fe 06 d1 cc 8d 4f 82 a4",
"8b 3c 5b 9b 86 7d 4b e4 6d 1c b5 a0 1d 45 d6 7d c8 e9 40 82"
]
},
"DifferentUsername": "false",
"PerformServerValidation": "true",
"AcceptServerName": "false",
"TLSExtensions": {
"FilteringInfo": {
"ClientAuthEKUList": {}
}
}
}
}
}
}
},
{
"EapHostConfig": {
"EapMethod": {
"Type": "13",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "0"
},
"Config": {
"Eap": {
"Type": "13",
"EapType": {
"CredentialsSource": {
"CertificateStore": {
"SimpleCertSelection": "true"
}
},
"ServerValidation": {
"DisableUserPromptForServerValidation": "false",
"ServerNames": null,
"TrustedRootCA": [
"1d 6d 28 88 a1 8f 32 96 77 b6 0f a8 31 74 74 c1 69 bc e7 95",
"71 59 9a 14 19 84 7d b0 6b b5 99 36 17 44 0c 06 4e a8 c3 23",
"df 3c 24 f9 bf d6 66 76 1b 26 80 73 fe 06 d1 cc 8d 4f 82 a4",
"8b 3c 5b 9b 86 7d 4b e4 6d 1c b5 a0 1d 45 d6 7d c8 e9 40 82"
]
},
"DifferentUsername": "false",
"PerformServerValidation": "true",
"AcceptServerName": "false",
"TLSExtensions": {
"FilteringInfo": {
"ClientAuthEKUList": {}
}
}
}
}
}
}
}
]
},
"Phase1Identity": {
"IdentityPrivacy": "true",
"AnonymousIdentity": "anonymous"
}
}
}
}
}
}
}
}
},
{
"name": "CHLA-Remote",
"SSIDConfig": {
"SSID": {
"hex": "43484C412D52656D6F7465",
"name": "CHLA-Remote"
},
"nonBroadcast": "false"
},
"connectionType": "ESS",
"connectionMode": "auto",
"autoSwitch": "true",
"MSM": {
"security": {
"authEncryption": {
"authentication": "WPA2",
"encryption": "AES",
"useOneX": "true"
},
"PMKCacheMode": "enabled",
"PMKCacheTTL": "720",
"PMKCacheSize": "128",
"preAuthThrottle": "3",
"OneX": {
"cacheUserData": "false",
"heldPeriod": "1",
"authPeriod": "30",
"startPeriod": "5",
"maxStart": "5",
"maxAuthFailures": "5",
"authMode": "machineOrUser",
"EAPConfig": {
"EapHostConfig": {
"EapMethod": {
"Type": "55",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "311"
},
"Config": {
"EapTeap": {
"ServerValidation": {
"TrustedRootCAHash": [
"32 eb 83 1e da 15 9 8f fc fb 8f 30 33 4e bf 71 ac 43 ac db fe a8 48 59 d4 ef 74 f8 1 0 4f b4",
"d0 38 9b 27 d7 26 97 7f 2f c0 8a 7e af 71 8a bb 5b 9a a6 ac 2f fb 8e e8 68 a8 17 58 5e 29 35 f",
"cb 3c cb b7 60 31 e5 e0 13 8f 8d d3 9a 23 f9 de 47 ff c3 5e 43 c1 14 4c ea 27 d4 6a 5a b1 cb 5f",
"c0 6e 30 7f 7c fc 1d 32 fa 72 a4 c0 33 c8 7b 90 1 9a f2 16 f0 77 5d 64 97 8a 2e ca 6c 8a 23 e"
],
"DisablePrompt": "false",
"DownloadTrustedServerRoot": "false"
},
"Phase2Authentication": {
"InnerMethodConfig": [
{
"EapHostConfig": {
"EapMethod": {
"Type": "13",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "0"
},
"Config": {
"Eap": {
"Type": "13",
"EapType": {
"CredentialsSource": {
"CertificateStore": {
"SimpleCertSelection": "true"
}
},
"ServerValidation": {
"DisableUserPromptForServerValidation": "false",
"ServerNames": null,
"TrustedRootCA": [
"1d 6d 28 88 a1 8f 32 96 77 b6 0f a8 31 74 74 c1 69 bc e7 95",
"71 59 9a 14 19 84 7d b0 6b b5 99 36 17 44 0c 06 4e a8 c3 23",
"df 3c 24 f9 bf d6 66 76 1b 26 80 73 fe 06 d1 cc 8d 4f 82 a4",
"8b 3c 5b 9b 86 7d 4b e4 6d 1c b5 a0 1d 45 d6 7d c8 e9 40 82"
]
},
"DifferentUsername": "false",
"PerformServerValidation": "true",
"AcceptServerName": "false",
"TLSExtensions": {
"FilteringInfo": {
"ClientAuthEKUList": {}
}
}
}
}
}
}
},
{
"EapHostConfig": {
"EapMethod": {
"Type": "13",
"VendorId": "0",
"VendorType": "0",
"AuthorId": "0"
},
"Config": {
"Eap": {
"Type": "13",
"EapType": {
"CredentialsSource": {
"CertificateStore": {
"SimpleCertSelection": "true"
}
},
"ServerValidation": {
"DisableUserPromptForServerValidation": "false",
"ServerNames": null,
"TrustedRootCA": [
"1d 6d 28 88 a1 8f 32 96 77 b6 0f a8 31 74 74 c1 69 bc e7 95",
"71 59 9a 14 19 84 7d b0 6b b5 99 36 17 44 0c 06 4e a8 c3 23",
"df 3c 24 f9 bf d6 66 76 1b 26 80 73 fe 06 d1 cc 8d 4f 82 a4",
"8b 3c 5b 9b 86 7d 4b e4 6d 1c b5 a0 1d 45 d6 7d c8 e9 40 82"
]
},
"DifferentUsername": "false",
"PerformServerValidation": "true",
"AcceptServerName": "false",
"TLSExtensions": {
"FilteringInfo": {
"ClientAuthEKUList": {}
}
}
}
}
}
}
}
]
},
"Phase1Identity": {
"IdentityPrivacy": "true",
"AnonymousIdentity": "anonymous"
}
}
}
}
}
}
}
}
}
]
Export Profiles as XML
mkdir C:\temp -ErrorAction SilentlyContinue
netsh wlan export profile name="CHLA-Staff" folder=C:\temp key=clear
netsh wlan export profile name="CHLA_Remote" folder=C:\temp key=clear
dir C:\temp\*CHLA*
Parse WiFi Profile XML — EAP Configuration
[xml]$profile = Get-Content "C:\temp\Wi-Fi-CHLA-Staff.xml"
Write-Host "=== Connection Settings ==="
Write-Host "SSID: $($profile.WLANProfile.SSIDConfig.SSID.name)"
Write-Host "Auth: $($profile.WLANProfile.MSM.security.authEncryption.authentication)"
Write-Host "Encryption: $($profile.WLANProfile.MSM.security.authEncryption.encryption)"
Write-Host "802.1X: $($profile.WLANProfile.MSM.security.OneX.authMode)"
$eapType = $profile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.EapMethod.Type.'#text'
Write-Host "EAP Type: $eapType (13=TLS, 25=PEAP, 21=TTLS)"
Write-Host "`n=== Certificate Config ==="
$profile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig | Select-Xml -XPath "//*[local-name()='ServerValidation']" | ForEach-Object { $_.Node.OuterXml }
$profile.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig | Select-Xml -XPath "//*[local-name()='TLSExtensions']" | ForEach-Object { $_.Node.OuterXml }
Compare-Object (Get-Content "C:\temp\Wi-Fi-CHLA-Staff.xml") (Get-Content "C:\temp\Wi-Fi-CHLA_Remote.xml")
Profile Source — GPO or Intune?
Get-ChildItem "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Wireless\GPTWirelessPolicy" -Recurse -ErrorAction SilentlyContinue | Format-List
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\WlanSvc\Profiles" -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
$props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
if($props) { [PSCustomObject]@{Path=$_.PSPath; Props=($props.PSObject.Properties | Where-Object {$_.Name -notmatch 'PS'} | ForEach-Object {"$($_.Name)=$($_.Value)"}) -join "; "} }
} | Format-List
dsregcmd /status | Select-String -Pattern "AzureAd|Domain|MDM|Tenant|Device"
Part 2: GPO Analysis
Phase 3: GPO 802.1X & Wireless Policy
gpresult /r /scope:computer
gpresult /r /scope:computer 2>$null | Select-String -Pattern "wireless|wifi|wlan|802|dot1x|certificate|CHLA" -CaseSensitive:$false
gpresult /x C:\temp\gpo-report.xml /f
[xml]$gpo = Get-Content "C:\temp\gpo-report.xml"
$gpo.Rsop.ComputerResults.ExtensionData | ForEach-Object {
$_.Extension | Select-Xml -XPath "//*[local-name()='WLanPolicy' or local-name()='WiredPolicy' or local-name()='Dot3' or local-name()='Wireless']" | ForEach-Object {
Write-Host "=== Found 802.1X/Wireless GPO Setting ==="
$_.Node.OuterXml
}
}
# Wireless 802.1X GPO settings
Get-ChildItem "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Wireless\GPTWirelessPolicy" -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
Write-Host "=== $($_.PSPath) ==="
Get-ItemProperty $_.PSPath | Format-List
}
Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WiredL2\GP_Policy" -ErrorAction SilentlyContinue | Format-List
Get-ChildItem "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Wireless" -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
$props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
$props.PSObject.Properties | Where-Object { $_.Value -match '[A-Fa-f0-9]{40}' } | ForEach-Object {
Write-Host "THUMBPRINT FOUND: $($_.Name) = $($_.Value)"
}
}
Get-Service dot3svc | Format-List Name, Status, StartType
netsh lan show profiles
netsh lan show profile name="*" | Select-String -Pattern "Name|EAP|Certificate|Auth"
Phase 3.5: AD CS Autoenrollment — GPO Cert Delivery
gpresult /r /scope:computer | Select-String -Pattern "autoenroll|certificate" -CaseSensitive:$false
Get-WinEvent -FilterHashtable @{
LogName='Application'
ProviderName='Microsoft-Windows-CertificateServicesClient-AutoEnrollment'
StartTime=(Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue | Format-Table TimeCreated, Id, Message -Wrap
certutil -template | Select-String "WiFi|Computer|Machine|Workstation|802|CHLA"
Part 3: Intune / MDM Investigation
Phase 4: Intune Certificate Delivery
dsregcmd /status | Select-String "MDM"
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Enrollments" -ErrorAction SilentlyContinue | ForEach-Object {
Get-ItemProperty $_.PSPath | Select-Object ProviderID, EnrollmentState, UPN
} | Format-Table -AutoSize
Get-Content "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log" -Tail 500 | Select-String -Pattern "SCEP|certificate|pkcs|enroll|WiFi|wireless"
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin'
StartTime=(Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match "SCEP|certificate|pkcs|enroll"
} | Format-Table TimeCreated, Id, @{N='Message';E={$_.Message.Substring(0,[Math]::Min(200,$_.Message.Length))}} -Wrap
Get-Content "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\IntuneManagementExtension.log" -Tail 500 | Select-String -Pattern "profile|cert|connect" | Select-Object -First 30
Get-Process -Name ClientCertCheck -ErrorAction SilentlyContinue | Format-Table Id, ProcessName, StartTime -AutoSize
Get-WinEvent -FilterHashtable @{LogName='Application'; StartTime=(Get-Date).AddDays(-1)} -ErrorAction SilentlyContinue | Where-Object { $_.Message -match "ClientCertCheck" } | Format-Table TimeCreated, Message -Wrap
Intune-Pushed WiFi Profiles — Event Viewer
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin'
StartTime=(Get-Date).AddDays(-7)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match "WiFi|WLAN|CHLA-Staff|CHLA.Remote|CHLA_Remote|wireless|profile"
} | Format-Table TimeCreated, Id, @{N='Message';E={$_.Message.Substring(0,[Math]::Min(250,$_.Message.Length))}} -Wrap
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Admin'
StartTime=(Get-Date).AddDays(-7)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Id -in @(800,801,802,803,804,805,806,807,808,809,810,811,812,813,814,815)
} | Select-Object TimeCreated, Id, @{N='Message';E={$_.Message.Substring(0,[Math]::Min(250,$_.Message.Length))}} | Format-Table -Wrap
# Registry path where Intune WiFi profiles land
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\WlanSvc\Profiles\*" -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
$xml = (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue).ProfileXml
if($xml -match "CHLA") {
Write-Host "=== $($_.PSPath) ==="
# Parse the XML to extract SSID and auth settings
[xml]$p = $xml
Write-Host "SSID: $($p.WLANProfile.SSIDConfig.SSID.name)"
Write-Host "Auth: $($p.WLANProfile.MSM.security.authEncryption.authentication)"
Write-Host "EAP: $($p.WLANProfile.MSM.security.OneX.EAPConfig.EapHostConfig.EapMethod.Type.'#text')"
Write-Host "Source: MDM/Intune"
Write-Host ""
}
}
Write-Host "=== GPO WiFi Profiles ==="
Get-ChildItem "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Wireless\GPTWirelessPolicy" -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
$props = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
if($props.ProfileName) { Write-Host " GPO: $($props.ProfileName)" }
}
Write-Host "`n=== Intune/MDM WiFi Profiles ==="
netsh wlan show profiles | Select-String "CHLA" | ForEach-Object {
$name = ($_ -split ":\s+")[1].Trim()
$detail = netsh wlan show profile name="$name" | Select-String "Applied From"
Write-Host " $name — $detail"
}
Write-Host "`n=== All Profile Sources ==="
netsh wlan show profiles | Select-String "All User" | ForEach-Object {
$name = ($_ -split ":\s+")[1].Trim()
Write-Host " $name"
}
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-WLAN-AutoConfig/Operational'
StartTime=(Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match "CHLA-Staff|CHLA.Remote|CHLA_Remote"
} | Format-Table TimeCreated, Id, @{N='Message';E={$_.Message.Substring(0,[Math]::Min(250,$_.Message.Length))}} -Wrap
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-WLAN-AutoConfig/Operational'
Id=@(8001,8002,8003,8004,11001,11002,11004,11005,11010,11011)
StartTime=(Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue | Select-Object TimeCreated, Id, @{N='Event';E={
switch($_.Id){
8001{"Connected"}; 8002{"Connect failed"}; 8003{"Disconnected"}; 8004{"Profile changed"}
11001{"Auth started"}; 11002{"Auth succeeded"}; 11004{"Auth failed"}; 11005{"Auth rejected"}
11010{"Cert selected"}; 11011{"Cert rejected"}
}
}}, @{N='Detail';E={$_.Message.Substring(0,[Math]::Min(150,$_.Message.Length))}} | Format-Table -Wrap
Part 4: Supplicant Event Logs
Phase 5: What the Supplicant Actually Did
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-WLAN-AutoConfig/Operational'
StartTime=(Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match "802\.1X|EAP|certificate|authentication|CHLA"
} | Format-Table TimeCreated, Id, @{N='Message';E={$_.Message.Substring(0,[Math]::Min(200,$_.Message.Length))}} -Wrap
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-Wired-AutoConfig/Operational'
StartTime=(Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue | Where-Object {
$_.Message -match "802\.1X|EAP|certificate|authentication"
} | Format-Table TimeCreated, Id, @{N='Message';E={$_.Message.Substring(0,[Math]::Min(200,$_.Message.Length))}} -Wrap
Get-WinEvent -FilterHashtable @{
LogName='Microsoft-Windows-EapHost/Operational'
StartTime=(Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue | Format-Table TimeCreated, Id, @{N='Message';E={$_.Message.Substring(0,[Math]::Min(200,$_.Message.Length))}} -Wrap
Part 5: Control Comparison
Phase 6: Working Machine vs Affected Machine — Side by Side
$hostname = $env:COMPUTERNAME
Write-Host "=== $hostname — Certificate & WiFi Audit ==="
Write-Host "`n--- Client Auth Certs (Machine Store) ---"
Get-ChildItem Cert:\LocalMachine\My | Where-Object {
$_.EnhancedKeyUsageList.ObjectId -contains "1.3.6.1.5.5.7.3.2"
} | Select-Object Subject, Issuer, NotAfter, @{N='Days';E={($_.NotAfter-(Get-Date)).Days}}, Thumbprint | Format-Table -AutoSize
Write-Host "`n--- Client Auth Certs (User Store) ---"
Get-ChildItem Cert:\CurrentUser\My | Where-Object {
$_.EnhancedKeyUsageList.ObjectId -contains "1.3.6.1.5.5.7.3.2"
} | Select-Object Subject, Issuer, NotAfter, @{N='Days';E={($_.NotAfter-(Get-Date)).Days}}, Thumbprint | Format-Table -AutoSize
Write-Host "`n--- WiFi Profiles ---"
netsh wlan show profiles | Select-String "All User|CHLA"
Write-Host "`n--- Current Connection ---"
netsh wlan show interfaces | Select-String "Name|State|SSID|Auth|Profile"
Write-Host "`n--- Device Registration ---"
dsregcmd /status | Select-String "AzureAd|Domain|MDM|Device"
Write-Host "`n--- GPO Wireless Policy ---"
gpresult /r /scope:computer 2>$null | Select-String "wireless|wifi|802|dot1x|cert" -CaseSensitive:$false
Write-Host "`n--- Last 5 WLAN-AutoConfig Events ---"
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-WLAN-AutoConfig/Operational'} -MaxEvents 5 -ErrorAction SilentlyContinue | Select-Object TimeCreated, @{N='Msg';E={$_.Message.Substring(0,[Math]::Min(120,$_.Message.Length))}} | Format-Table -Wrap
Write-Host "`n--- Cert Autoenroll (last event) ---"
Get-WinEvent -FilterHashtable @{LogName='Application';ProviderName='Microsoft-Windows-CertificateServicesClient-AutoEnrollment'} -MaxEvents 1 -ErrorAction SilentlyContinue | Select-Object TimeCreated, Message | Format-List
The diff between the two outputs proves where the problem is. ISE sees the same policy for both machines — if one works and one doesn’t, the difference is on the client.
Part 6: ISE Validation (Supporting Evidence — Not Root Cause)
| ISE does not push certs. ISE validates what the supplicant presents. These queries prove ISE is working correctly. |
Phase 7: ISE — What Does ISE See?
# Load credentials (d001 at work, d000 at home lab)
dsource d001 dev/network/ise # WORK
# dsource d000 dev/network/ise # HOME LAB
# Verify credentials loaded (safe — no values shown)
env | grep ISE_ | awk -F= '{printf "%-25s %s\n", $1, (length($2) > 0 ? "SET (" length($2) " chars)" : "EMPTY")}'
# REQUIRED: dc_query is NOT auto-loaded — copy-paste this function into your terminal
# Source: data/shared/partials/ise-dataconnect/helpers.adoc
dc_query() {
local sql="$1"
~/atelier/_projects/personal/netapi/.venv/bin/python -c "
import oracledb, os, json, ssl
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
conn = oracledb.connect(
user=os.environ['ISE_DATACONNECT_USER'],
password=os.environ['ISE_DATACONNECT_PASS'],
dsn=os.environ['ISE_DATACONNECT_DSN'],
ssl_context=ssl_context,
ssl_server_dn_match=False)
cursor = conn.cursor()
cursor.execute('''${sql}''')
columns = [col[0] for col in cursor.description]
rows = [dict(zip(columns, row)) for row in cursor.fetchall()]
print(json.dumps(rows, indent=2, default=str))
conn.close()
"
}
# Verify function loaded
type dc_query 2>&1 | grep -E "function|not found"
# Quick connectivity test
dc_query "SELECT COUNT(*) AS total FROM radius_authentications" | jq '.[0].TOTAL'
bash data/shared/scripts/dc-run-sql.sh <file.sql>
bash data/shared/scripts/dc-run-sql.sh <file.sql> auto # auto-save to output/
| Error | Fix |
|---|---|
|
Don’t use |
|
Never |
|
DSN must use FQDN, not IP. Oracle TLS validates hostname. |
|
Function not auto-loaded. Copy-paste from |
|
String type: |
Time intervals |
Use |
dc_query "
SELECT TIMESTAMP_TIMEZONE, CALLING_STATION_ID, USERNAME,
AUTHENTICATION_METHOD, AUTHENTICATION_PROTOCOL,
PASSED, FAILURE_REASON, POLICY_SET_NAME,
ENDPOINT_PROFILE, NAS_IP_ADDRESS
FROM radius_authentications
WHERE PASSED = 'Fail'
AND AUTHENTICATION_PROTOCOL LIKE '%EAP-TLS%'
ORDER BY TIMESTAMP_TIMEZONE DESC
FETCH FIRST 25 ROWS ONLY
" | jq -r '.[] | "\(.TIMESTAMP_TIMEZONE) \(.CALLING_STATION_ID) \(.FAILURE_REASON)"' | column -t
12516 — Expired client cert → Intune/GPO not renewing 12514 — Identity mismatch → WiFi profile CN ≠ cert CN 12308 — Cert not in trust store → ISE needs CA cert imported 12500 — Chain incomplete → Client not sending intermediate 12520 — Generic handshake fail → Supplicant not presenting cert No failure in ISE → Device never reached RADIUS
# Substitute MAC from failures above
dc_query "
SELECT TIMESTAMP_TIMEZONE, CALLING_STATION_ID, USERNAME,
AUTHENTICATION_METHOD, AUTHENTICATION_PROTOCOL,
PASSED, FAILURE_REASON
FROM radius_authentications
WHERE CALLING_STATION_ID = '<MAC_ADDRESS>'
ORDER BY TIMESTAMP_TIMEZONE DESC
FETCH FIRST 20 ROWS ONLY
" | jq -r '.[] | "\(.TIMESTAMP_TIMEZONE) \(.PASSED) \(.AUTHENTICATION_PROTOCOL) \(.FAILURE_REASON)"' | column -t
Phase 7.5: ISE Bulk — Scope the Problem
dc_query "
SELECT COUNT(DISTINCT CALLING_STATION_ID) AS AFFECTED_DEVICES,
COUNT(*) AS TOTAL_FAILURES,
MIN(TIMESTAMP_TIMEZONE) AS FIRST_OCCURRENCE,
MAX(TIMESTAMP_TIMEZONE) AS LATEST
FROM radius_authentications
WHERE PASSED = 'Fail'
AND AUTHENTICATION_PROTOCOL LIKE '%EAP-TLS%'
AND TIMESTAMP_TIMEZONE > SYSTIMESTAMP - INTERVAL '30' DAY
" | jq '.'
dc_query "
SELECT FAILURE_REASON, COUNT(*) AS FAIL_COUNT,
MIN(TIMESTAMP_TIMEZONE) AS FIRST_SEEN,
MAX(TIMESTAMP_TIMEZONE) AS LAST_SEEN
FROM radius_authentications
WHERE PASSED = 'Fail'
AND AUTHENTICATION_PROTOCOL LIKE '%EAP-TLS%'
AND TIMESTAMP_TIMEZONE > SYSTIMESTAMP - INTERVAL '7' DAY
GROUP BY FAILURE_REASON
ORDER BY FAIL_COUNT DESC
" | jq -r '.[] | "\(.FAIL_COUNT)\t\(.FAILURE_REASON)\t\(.FIRST_SEEN)\t\(.LAST_SEEN)"' | column -t -s $'\t'
dc_query "
SELECT CALLING_STATION_ID AS MAC,
COUNT(*) AS FAIL_COUNT,
MAX(TIMESTAMP_TIMEZONE) AS LAST_FAIL,
MAX(FAILURE_REASON) AS REASON,
MAX(USERNAME) AS USERNAME
FROM radius_authentications
WHERE PASSED = 'Fail'
AND AUTHENTICATION_PROTOCOL LIKE '%EAP-TLS%'
AND TIMESTAMP_TIMEZONE > SYSTIMESTAMP - INTERVAL '7' DAY
GROUP BY CALLING_STATION_ID
ORDER BY FAIL_COUNT DESC
" | tee /tmp/intune-cert-failures.json | jq -r '.[] | "\(.MAC)\t\(.FAIL_COUNT)\t\(.LAST_FAIL)\t\(.REASON)"' | column -t -s $'\t'
dc_query "
SELECT a.CALLING_STATION_ID AS MAC,
SUM(CASE WHEN a.PASSED = 'Fail' THEN 1 ELSE 0 END) AS FAILS,
SUM(CASE WHEN a.PASSED = 'Pass' THEN 1 ELSE 0 END) AS PASSES,
MAX(CASE WHEN a.PASSED = 'Fail' THEN a.TIMESTAMP_TIMEZONE END) AS LAST_FAIL,
MAX(CASE WHEN a.PASSED = 'Pass' THEN a.TIMESTAMP_TIMEZONE END) AS LAST_PASS
FROM radius_authentications a
WHERE a.AUTHENTICATION_PROTOCOL LIKE '%EAP-TLS%'
AND a.TIMESTAMP_TIMEZONE > SYSTIMESTAMP - INTERVAL '7' DAY
GROUP BY a.CALLING_STATION_ID
HAVING SUM(CASE WHEN a.PASSED = 'Fail' THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN a.PASSED = 'Pass' THEN 1 ELSE 0 END) > 0
ORDER BY FAILS DESC
" | jq -r '.[] | "\(.MAC)\tFail:\(.FAILS)\tPass:\(.PASSES)\tLastFail:\(.LAST_FAIL)\tLastPass:\(.LAST_PASS)"' | column -t -s $'\t'
jq -r '["MAC","FAIL_COUNT","LAST_FAIL","REASON","USERNAME"], (.[] | [.MAC,.FAIL_COUNT,.LAST_FAIL,.REASON,.USERNAME]) | @csv' /tmp/intune-cert-failures.json > /tmp/intune-cert-failures.csv
echo "Saved: /tmp/intune-cert-failures.csv ($(wc -l < /tmp/intune-cert-failures.csv) devices)"
Phase 8: ISE Cert Trust Store
curl -sk -u "${ISE_ERS_USER}:${ISE_ERS_PASS}" \
-H "Accept: application/json" \
"https://${ISE_PAN_FQDN}:9060/ers/config/trustedcertificate" | \
jq -r '.SearchResult.resources[] | "\(.id) \(.name)"'
curl -sk -u "${ISE_ERS_USER}:${ISE_ERS_PASS}" \
-H "Accept: application/json" \
"https://${ISE_PAN_FQDN}:9060/ers/config/trustedcertificate" | \
jq -r '.SearchResult.resources[] | select(.name | test("intune|scep|mdm|microsoft|enterprise|chla"; "i")) | "\(.id) \(.name)"'
mnt "/Session/ActiveList" | \
xq -r '.activeList.activeSession[] | "\(.calling_station_id)\t\(.user_name)\t\(.nas_ip_address)\t\(.framed_ip_address)"' | \
column -t -s $'\t'
Root Cause Determination
After Parts 1-6, fill in:
| Check | Result | Implication |
|---|---|---|
How many client auth certs on affected device? |
_ |
If 2+ from different CAs → supplicant picking wrong one |
Cert chain valid on affected device? |
_ |
If chain broken → missing intermediate or untrusted root |
Any expired certs on affected device? |
_ |
If expired → GPO/Intune not renewing |
WiFi profile source (GPO or Intune)? |
_ |
If both → conflict, last write wins |
WiFi profile EAP type? |
_ |
Must be 13 (TLS). If 25 (PEAP) → wrong profile |
GPO pushing cert thumbprint? |
_ |
If pinned to old thumbprint → new cert won’t match |
Intune SCEP logs show cert delivered? |
_ |
If no cert event → Intune not delivering |
WLAN-AutoConfig shows cert selection? |
_ |
If "no suitable cert" → wrong store or wrong EKU |
ISE failure reason code? |
_ |
12516=expired, 12514=mismatch, 12308=untrusted, none=never reached ISE |
Working machine has same cert issuer? |
_ |
Control comparison — must match |
Troubleshooting: rsyslog ← ASA Syslog
Context
Pointed ASA to centralized rsyslog collector (Debian server). Just given SSH access. Need to verify logs are arriving, parse correctly, and troubleshoot if not.
Screen Share Layout (tmux)
# Pane 1 (left, large): SSH to CHLXSYSLOG01
ssh erosado@10.248.13.254
# Pane 2 (top right): this file as reference
bat docs/modules/ROOT/partials/worklog/daily-notes/2026-06-08/rsyslog-asa-troubleshooting.adoc
# Pane 3 (bottom right): ISE queries from workstation
dsource d001 dev/network/ise
Phase 0: Login and Reconnaissance
# If hostname is in SSH config:
ssh rsyslog-01
# If not — direct:
ssh <username>@<rsyslog-server-ip>
# If you need to go through a jump host:
ssh -J <jumphost> <username>@<rsyslog-server-ip>
# System summary — one shot
hostnamectl | awk '/hostname|Operating|Kernel|Virtualization/{print}'
# Who am I and what can I sudo?
id | awk -F'[()]' '{for(i=2;i<=NF;i+=2) printf "%s\n", $i}' | head -5
sudo -l
# Disk — formatted, only what matters
df -h /var /home /boot | awk 'NR==1 || /mapper|sda/{printf "%-45s %5s %5s %4s\n", $1, $2, $4, $5}'
# Log volume
du -sh /var/log 2>/dev/null
# Network — IPs and listeners
ip -br addr | awk '/UP/{print}'
ss -tulnp | awk 'NR==1 || /514|syslog/'
# Is rsyslog running? One-liner status
systemctl is-active rsyslog && echo "PID: $(pgrep rsyslogd)"
# Version — determines config syntax (v8+ uses RainerScript)
/usr/sbin/rsyslogd -v 2>/dev/null || rsyslogd -v
# Config files — what exists?
ls -la /etc/rsyslog.conf /etc/rsyslog.d/
# Full config — strip comments, show only active lines
sudo awk '/^[^#]/ && NF' /etc/rsyslog.conf
# All drop-in configs
for f in /etc/rsyslog.d/*.conf; do echo "=== $f ==="; sudo awk '/^[^#]/ && NF' "$f"; echo; done
# Recent syslog entries
sudo tail -50 /var/log/syslog
# Any ASA messages already?
sudo grep -c '%ASA' /var/log/syslog && echo "ASA logs found" || echo "NO ASA logs yet"
# Log rotation config
cat /etc/logrotate.d/rsyslog
# Unique source hosts with message counts — dashboard view
sudo awk '{print $4}' /var/log/syslog | sort | uniq -c | sort -rn | head -20
# Source IPs from recent messages
sudo awk '/from /{match($0,/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/); if(RSTART) print substr($0,RSTART,RLENGTH)}' /var/log/syslog | sort | uniq -c | sort -rn | head -20
Phase 0.5: Decrypt d001 Design Partial (on YOUR workstation, not the server)
d001 open siem-qradar
bat data/d001/projects/siem-qradar-to-sentinel/partials/rsyslog-central-collector.adoc
Phase 1: Verify ASA Is Sending
show logging
show logging setting
show running-config logging
logging enable logging timestamp logging host <interface> <rsyslog-ip> [tcp|udp]/<port> logging trap <level> ! informational (6) or debugging (7) for full capture logging facility <number> ! default 20 (local4)
logging enable
logging timestamp
logging host management <rsyslog-ip> udp/514
logging trap informational
logging facility 20
Phase 2: Verify rsyslog Is Receiving
ss -ulnp | grep 514
ss -tlnp | grep 514
grep -rn 'imudp\|imtcp\|514' /etc/rsyslog.conf /etc/rsyslog.d/
# /etc/rsyslog.conf or /etc/rsyslog.d/00-input.conf
module(load="imudp")
input(type="imudp" port="514")
# For TCP (more reliable):
module(load="imtcp")
input(type="imtcp" port="514")
sudo systemctl restart rsyslog
sudo systemctl status rsyslog
ss -ulnp | grep 514
Phase 3: Test Connectivity
# Quick packet capture on port 514
sudo tcpdump -i any port 514 -nn -c 20
# More detail — show payload
sudo tcpdump -i any port 514 -nn -A -c 10
# Send test message to rsyslog
echo "<14>Test from workstation $(date)" | nc -u -w1 <rsyslog-ip> 514
! Generate a syslog event
logging message 199999 level 6
! Or just make a config change and revert — it logs
Phase 4: Check Logs Are Landing
# Default syslog
tail -f /var/log/syslog | grep -i asa
# Or messages
tail -f /var/log/messages | grep -i asa
# If you configured a dedicated ASA log file
tail -f /var/log/asa/asa.log
grep '<asa-ip>' /var/log/syslog | tail -20
Phase 5: Firewall — Is Traffic Being Blocked?
sudo iptables -L -n | grep 514
sudo nft list ruleset | grep 514
sudo firewall-cmd --list-all | grep 514
# If not open:
sudo firewall-cmd --add-port=514/udp --permanent
sudo firewall-cmd --add-port=514/tcp --permanent
sudo firewall-cmd --reload
# On VyOS — check if syslog port is allowed between zones
show firewall statistics
Phase 6: ASA-Specific Log Parsing
# /etc/rsyslog.d/10-asa.conf
template(name="AsaFile" type="string"
string="/var/log/asa/%HOSTNAME%/%$YEAR%-%$MONTH%-%$DAY%.log")
if $fromhost-ip == '<asa-ip>' then {
action(type="omfile" dynaFile="AsaFile")
stop
}
# Count messages by ASA message ID
sudo grep -oP '%ASA-\d-\d+' /var/log/syslog | sort | uniq -c | sort -rn | head -20
# By severity level
sudo awk '/%ASA/{match($0,/%ASA-([0-9])/,a); sev[a[1]]++} END{for(s in sev) printf "Level %s: %d\n", s, sev[s]}' /var/log/syslog | sort
Phase 6.5: Firewall Rule Added — Verify Logs Now Landing
Firewall team added inside rule permitting syslog from ASA to CHLXSYSLOG01. Run these NOW on the Debian server.
sudo tcpdump -i any port 514 -nn -c 10
sudo tail -20 /var/log/syslog
sudo grep '%ASA' /var/log/syslog | tail -20
sudo journalctl -u rsyslog --since "5 min ago" --no-pager
# Check what rsyslog is doing with incoming UDP
sudo awk '/^[^#]/ && NF' /etc/rsyslog.conf | head -40
# Check if imudp module is loaded
sudo awk '/imudp|imtcp|514/' /etc/rsyslog.conf /etc/rsyslog.d/*.conf 2>/dev/null
# Check rsyslog internal stats
sudo rsyslogd -N1 2>&1 | head -20
# What's your IP?
ip -br addr | awk '/UP/{print}'
# Can you reach the ASA from here?
ping -c 2 172.16.57.177 2>/dev/null || echo "Cannot reach internal syslog server"
# Check local firewall on this Debian box
sudo iptables -L -n 2>/dev/null | grep -i '514\|syslog\|ACCEPT\|DROP'
sudo nft list ruleset 2>/dev/null | grep 514
# How many ASA messages right now?
sudo grep -c '%ASA' /var/log/syslog
# Messages per source device
sudo awk '/%ASA/{match($0,/from ([0-9.]+)/,a); if(a[1]) src[a[1]]++} END{for(s in src) printf "%6d %s\n", src[s], s}' /var/log/syslog | sort -rn
# Last 10 messages — formatted
sudo grep '%ASA' /var/log/syslog | tail -10 | awk '{match($0,/%ASA-[0-9]-[0-9]+/); printf "%-18s %-16s\n", $1" "$2" "$3, substr($0,RSTART,RLENGTH)}'
# Live tail — ASA only
sudo tail -f /var/log/syslog | awk '/%ASA/'
Phase 7: Live Monitoring (screen share power moves)
sudo tail -f /var/log/syslog | awk '/%ASA/{match($0,/%ASA-[0-9]-[0-9]+/); id=substr($0,RSTART,RLENGTH); count[id]++; printf "\r%s: %d ", id, count[id]}'
sudo tail -f /var/log/syslog | awk '/%ASA/{
match($0,/%ASA-[0-9]-[0-9]+/);
id=substr($0,RSTART,RLENGTH);
ts=$1" "$2" "$3;
printf "%-18s %-16s %s\n", ts, id, substr($0,RSTART+RLENGTH+2,80)
}'
sudo tcpdump -i any port 514 -nn -l 2>/dev/null | awk '{
match($0,/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/);
ip=substr($0,RSTART,RLENGTH);
count[ip]++;
printf "\r";
for(i in count) printf "%s: %d ", i, count[i]
}'
sudo awk '/%ASA/{print $1, $2, $3}' /var/log/syslog | awk '{print $1, $2, substr($3,1,5)}' | sort | uniq -c | tail -10
%ASA-4-106023 — Deny by ACL (firewall drops) %ASA-6-302013 — Built TCP connection %ASA-6-302014 — Teardown TCP connection %ASA-6-302015 — Built UDP connection %ASA-6-302016 — Teardown UDP connection %ASA-4-733100 — Threat detection (scanning, DoS) %ASA-5-111008 — User executed command (admin activity) %ASA-6-605005 — Login permitted (admin login) %ASA-3-710003 — Deny TCP connection (management ACL)
Quick Validation Checklist
| Check | Command | Expected |
|---|---|---|
ASA logging enabled |
|
|
ASA target configured |
|
|
rsyslog listening |
|
UDP *:514 |
Packets arriving |
|
Packets from ASA IP |
Logs on disk |
|
ASA messages present |
Firewall open |
|
ACCEPT rule for 514 |
Investigation: Spam Bypass — SCL 9 Delivered to Inbox
Incident Summary
| Field | Value |
|---|---|
Date Reported |
2026-06-08 |
Sender |
|
Subject |
Nick Mezza, CFO, Healthcare Financial Executive |
SCL |
9 (definitive spam) |
BCL |
2 (low bulk) |
Expected Action |
Quarantine or Junk |
Actual Action |
Delivered to inbox |
Affected |
|
Not Affected |
csandoval@chla.usc.edu (correctly filtered) |
SCL 9 is the highest spam confidence level — Microsoft classifies this as definitive spam. Default EOP behavior at SCL ≥ 6 is to move to Junk. At SCL 9, most orgs quarantine. Delivery to inbox means something is overriding the verdict.
Hypothesis Tree
-
Per-user safe sender list — user or Outlook rule whitelisted the sender/domain
-
Transport rule override — Exchange mail flow rule setting SCL to -1 (bypass)
-
Connection filter IP allow list — sender IP whitelisted at org level
-
Anti-spam policy scope — affected users not covered by the correct policy
-
Junk mail disabled — per-mailbox junk email processing turned off
ROOT CAUSE IDENTIFIED: "Allowed by user policy / trusted recipient domain list"
EOP message headers show system override SFV:SKA — message allowed by safe sender/domain list at user level. This means the user (or an admin policy) has whitelisted the sender or the sender’s domain in their junk email configuration. The SCL 9 verdict was correct but overridden at delivery time.
Phase 0: Confirm the Override (run FIRST)
# Get the message trace ID
$trace = Get-MessageTrace -SenderAddress "dccs@dccsconsulting.com" -RecipientAddress "erosado@chla.usc.edu" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date)
$trace | Format-List MessageTraceId, Received, Status, Subject
# Get detailed events — look for the override
Get-MessageTraceDetail -MessageTraceId $trace.MessageTraceId -RecipientAddress "erosado@chla.usc.edu" | Format-List Date, Event, Action, Detail, Data
# Same for wcox
$trace2 = Get-MessageTrace -SenderAddress "dccs@dccsconsulting.com" -RecipientAddress "wcox@chla.usc.edu" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date)
Get-MessageTraceDetail -MessageTraceId $trace2.MessageTraceId -RecipientAddress "wcox@chla.usc.edu" | Format-List Date, Event, Action, Detail, Data
# Control — csandoval (should show filtered)
$trace3 = Get-MessageTrace -SenderAddress "dccs@dccsconsulting.com" -RecipientAddress "csandoval@chla.usc.edu" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date)
if ($trace3) { Get-MessageTraceDetail -MessageTraceId $trace3.MessageTraceId -RecipientAddress "csandoval@chla.usc.edu" | Format-List Date, Event, Action, Detail, Data }
Phase 1: Per-User Junk Configuration — Safe Sender/Domain Lists
# Connect to Exchange Online (if not already)
Connect-ExchangeOnline
# Affected users — compare settings
Get-MailboxJunkEmailConfiguration "erosado@chla.usc.edu" | Format-List Enabled, TrustedSendersAndDomains, BlockedSendersAndDomains, ContactsTrusted, TrustedListsOnly
Get-MailboxJunkEmailConfiguration "wcox@chla.usc.edu" | Format-List Enabled, TrustedSendersAndDomains, BlockedSendersAndDomains, ContactsTrusted, TrustedListsOnly
# Control — not affected
Get-MailboxJunkEmailConfiguration "csandoval@chla.usc.edu" | Format-List Enabled, TrustedSendersAndDomains, BlockedSendersAndDomains, ContactsTrusted, TrustedListsOnly
Enabled: False ← PROBLEM: junk mail processing disabled entirely
ContactsTrusted: True ← PROBLEM: all contacts auto-whitelisted
TrustedSendersAndDomains contains "dccsconsulting.com" or "dccs@dccsconsulting.com"
← PROBLEM: sender explicitly whitelisted — THIS IS THE MOST LIKELY CAUSE
# Full safe sender list — erosado
(Get-MailboxJunkEmailConfiguration "erosado@chla.usc.edu").TrustedSendersAndDomains | Sort-Object
# Full safe sender list — wcox
(Get-MailboxJunkEmailConfiguration "wcox@chla.usc.edu").TrustedSendersAndDomains | Sort-Object
# Search specifically for the offending domain
(Get-MailboxJunkEmailConfiguration "erosado@chla.usc.edu").TrustedSendersAndDomains | Where-Object { $_ -like "*dccsconsulting*" }
(Get-MailboxJunkEmailConfiguration "wcox@chla.usc.edu").TrustedSendersAndDomains | Where-Object { $_ -like "*dccsconsulting*" }
# Control — csandoval should NOT have it
(Get-MailboxJunkEmailConfiguration "csandoval@chla.usc.edu").TrustedSendersAndDomains | Where-Object { $_ -like "*dccsconsulting*" }
@("erosado", "wcox", "csandoval") | ForEach-Object {
$config = Get-MailboxJunkEmailConfiguration "$_@chla.usc.edu"
[PSCustomObject]@{
User = $_
Enabled = $config.Enabled
ContactsTrusted = $config.ContactsTrusted
TrustedCount = $config.TrustedSendersAndDomains.Count
BlockedCount = $config.BlockedSendersAndDomains.Count
}
} | Format-Table -AutoSize
# WARNING: slow on large orgs — run during off-hours or limit scope
Get-Mailbox -ResultSize Unlimited | ForEach-Object {
$junk = Get-MailboxJunkEmailConfiguration $_.PrimarySmtpAddress -ErrorAction SilentlyContinue
if ($junk.TrustedSendersAndDomains -like "*dccsconsulting*") {
[PSCustomObject]@{
User = $_.PrimarySmtpAddress
Match = ($junk.TrustedSendersAndDomains | Where-Object { $_ -like "*dccsconsulting*" })
}
}
} | Format-Table -AutoSize
# Remove specific sender from safe list — erosado
Set-MailboxJunkEmailConfiguration "erosado@chla.usc.edu" -TrustedSendersAndDomains @{Remove="dccs@dccsconsulting.com"}
Set-MailboxJunkEmailConfiguration "erosado@chla.usc.edu" -TrustedSendersAndDomains @{Remove="dccsconsulting.com"}
# Remove specific sender from safe list — wcox
Set-MailboxJunkEmailConfiguration "wcox@chla.usc.edu" -TrustedSendersAndDomains @{Remove="dccs@dccsconsulting.com"}
Set-MailboxJunkEmailConfiguration "wcox@chla.usc.edu" -TrustedSendersAndDomains @{Remove="dccsconsulting.com"}
# Verify removal
(Get-MailboxJunkEmailConfiguration "erosado@chla.usc.edu").TrustedSendersAndDomains | Where-Object { $_ -like "*dccsconsulting*" }
(Get-MailboxJunkEmailConfiguration "wcox@chla.usc.edu").TrustedSendersAndDomains | Where-Object { $_ -like "*dccsconsulting*" }
Phase 1.5: Check Outlook Inbox Rules (users can create forwarding/move rules that bypass junk)
# Check inbox rules for erosado — look for rules moving spam to inbox or auto-forwarding
Get-InboxRule -Mailbox "erosado@chla.usc.edu" | Format-List Name, Enabled, Description, MoveToFolder, ForwardTo, RedirectTo, MarkAsRead, StopProcessingRules
# Check inbox rules for wcox
Get-InboxRule -Mailbox "wcox@chla.usc.edu" | Format-List Name, Enabled, Description, MoveToFolder, ForwardTo, RedirectTo, MarkAsRead, StopProcessingRules
# Look for rules that match the sender domain
Get-InboxRule -Mailbox "erosado@chla.usc.edu" | Where-Object { $_.Description -like "*dccsconsulting*" -or $_.FromAddressContainsWords -like "*dccs*" }
Get-InboxRule -Mailbox "wcox@chla.usc.edu" | Where-Object { $_.Description -like "*dccsconsulting*" -or $_.FromAddressContainsWords -like "*dccs*" }
Phase 1.6: Anti-Spam Policy Override Settings
# Check if policies allow user overrides (safe sender lists)
Get-HostedContentFilterPolicy | Format-List Name, AllowedSenders, AllowedSenderDomains, EnableUserOverrides
# If EnableUserOverrides is True — users CAN whitelist senders that override SCL verdicts
# To PREVENT users from overriding high-confidence spam:
# Set-HostedContentFilterPolicy -Identity "Default" -EnableUserOverrides $false
# WARNING: org-wide impact — discuss with team before applying
Phase 2: Message Trace — What Actually Happened
# Trace for affected users
Get-MessageTrace -SenderAddress "dccs@dccsconsulting.com" -RecipientAddress "erosado@chla.usc.edu" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) | Format-List Received, Subject, Status, ToIP, FromIP, Size, MessageTraceId
Get-MessageTrace -SenderAddress "dccs@dccsconsulting.com" -RecipientAddress "wcox@chla.usc.edu" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) | Format-List Received, Subject, Status, ToIP, FromIP, Size, MessageTraceId
# Control — should show FilteredAsSpam or similar
Get-MessageTrace -SenderAddress "dccs@dccsconsulting.com" -RecipientAddress "csandoval@chla.usc.edu" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) | Format-List Received, Subject, Status, ToIP, FromIP, Size, MessageTraceId
# Get MessageTraceId from above, then:
$traceId = "<MessageTraceId from erosado trace>"
Get-MessageTraceDetail -MessageTraceId $traceId -RecipientAddress "erosado@chla.usc.edu" | Format-List Date, Event, Action, Detail, Data
Phase 3: Anti-Spam Policy — Which Policy Applies
# All content filter policies
Get-HostedContentFilterPolicy | Format-List Name, SpamAction, HighConfidenceSpamAction, BulkSpamAction, BulkThreshold, PhishSpamAction, HighConfidencePhishAction, IsDefault
# All content filter rules (who gets which policy)
Get-HostedContentFilterRule | Format-List Name, HostedContentFilterPolicy, SentTo, SentToMemberOf, RecipientDomainIs, Priority, State
HighConfidenceSpamAction: MoveToJmf or Quarantine ← correct HighConfidenceSpamAction: NoAction or Redirect ← PROBLEM # Check if affected users are excluded from a stricter policy # or covered by a permissive one
Phase 4: Transport Rules — SCL Override Check
# Find any rule that sets SCL to -1 (bypass spam filtering)
Get-TransportRule | Where-Object { $_.SetSCL -eq -1 } | Format-List Name, State, Priority, Conditions, FromAddressContainsWords, SenderDomainIs
# Broader — any rule touching SCL
Get-TransportRule | Where-Object { $_.SetSCL -ne $null } | Format-List Name, SetSCL, State, Conditions
# Full dump of all active transport rules (look for sender/domain matches)
Get-TransportRule | Where-Object { $_.State -eq 'Enabled' } | Format-List Name, Priority, Conditions, Actions, SetSCL
Phase 5: Connection Filter — IP Allow List
Get-HostedConnectionFilterPolicy | Format-List Name, IPAllowList, IPBlockList, EnableSafeList
Phase 6: Org-Wide Safe Sender (Tenant Allow/Block List)
# Check if sender or domain is in tenant allow list
Get-TenantAllowBlockListItems -ListType Sender | Where-Object { $_.Value -like "*dccsconsulting*" } | Format-List Value, Action, ExpirationDate, Notes
# Also check URL list (sometimes domains end up here)
Get-TenantAllowBlockListItems -ListType Url | Where-Object { $_.Value -like "*dccsconsulting*" }
Phase 7: Abnormal Security — Did It See This?
# In Abnormal Security console: # Search → dccs@dccsconsulting.com # Check: Was it analyzed? Was it flagged? Did Abnormal remediate? # If Abnormal is in read-only mode, it may have detected but not acted.
Root Cause Matrix
After running Phase 1-6, fill in:
| Check | Result | Implication |
|---|---|---|
Junk Enabled (erosado) |
_ |
If False — junk mail disabled, all spam delivers to inbox |
Junk Enabled (wcox) |
_ |
Same |
Junk Enabled (csandoval) |
_ |
Expected: True (control case) |
Safe Sender match |
_ |
If sender/domain in TrustedSendersAndDomains — user override |
Transport Rule SCL -1 |
_ |
If match — org-level bypass, affects all users in scope |
Anti-Spam Policy SCL 9 Action |
_ |
If NoAction — policy misconfigured |
Connection Filter IP Allow |
_ |
If sender IP allowed — all messages from that IP bypass spam |
Tenant Allow List |
_ |
If sender allowed — explicit org override |
Immediate Remediation (pending root cause)
# If junk mail is disabled on affected mailboxes — re-enable:
Set-MailboxJunkEmailConfiguration "erosado@chla.usc.edu" -Enabled $true
Set-MailboxJunkEmailConfiguration "wcox@chla.usc.edu" -Enabled $true
# If sender is in safe sender list — remove:
# (Get specific list first, then remove the entry)
# Block the sender immediately at tenant level:
New-TenantAllowBlockListItems -ListType Sender -Entries "dccs@dccsconsulting.com" -Block -NoExpiration -Notes "SCL 9 spam delivered to inbox — investigation 2026-06-08"
Timesheet: 2026-06-01 — 2026-06-05
Monday 06-01
| Project | Code | Hours | Activity Description |
|---|---|---|---|
Tube System Upgrade |
_ |
3.0 |
ISE batch onboard script debugging — fixed 7 |
Abnormal Security (ESA→EOP) |
_ |
2.0 |
Policy review prep — EOP cmdlet mapping (5 cmdlets), Jihad spreadsheet review, Tyler migration doc review |
ISE Policy / Change Management |
_ |
2.5 |
CR board prep — Guest ACL Hardening + Anonymous Identity Test. CR document review, lab validation planning |
CISSP Study / Admin |
_ |
0.5 |
Exam scheduling coordination |
Total |
8.0 |
Tuesday 06-02
| Project | Code | Hours | Activity Description |
|---|---|---|---|
Change Management |
_ |
2.0 |
CR Board attendance — Guest ACL Hardening (APPROVED), Anonymous Identity Test (APPROVED). Post-approval coordination |
Linux Research / Server Deployment |
_ |
2.0 |
Linux Remediation kickoff meeting + inventory SQL query development (dc-run-sql) |
Abnormal Security (ESA→EOP) |
_ |
2.5 |
DKIM investigation (PowerShell shows disabled), ESA→EOP policy divergence analysis, d001 deliverable |
ISE Annual Cert Renewal |
_ |
1.0 |
Cert renewal meeting — 52 days to expiry, DigiCert CA, CSR generation prep |
Spectrum TV/GetWell iPad Refresh |
_ |
0.5 |
Android handheld scanners — ISE policy scoping for shared device auth (Argam escalation) |
Total |
8.0 |
Wednesday 06-03
| Project | Code | Hours | Activity Description |
|---|---|---|---|
Abnormal Security (ESA→EOP) |
_ |
3.0 |
ESA mail flow policy divergence — full Five Canons analysis. ESA-Bypass skipping blacklist enforcement. EOP anti-spam BCL/SCL policy review |
ISE Annual Cert Renewal |
_ |
1.0 |
CSR generation commands prepared, d001 config staged |
QRadar → Sentinel / Monad |
_ |
1.5 |
Monad pipeline weekly sync with William Cox — FMC/ISE sources, transform design, jq↔GJSON translation reference |
Spectrum TV/GetWell iPad Refresh |
_ |
1.0 |
Getwell iPads — iPSK onboard 2 devices (4C:B9:10:9F:05:9F, C4:12:34:2D:F3:14) |
ISE Policy / General Support |
_ |
1.5 |
Carry-forward task management, CR implementation scheduling coordination (Guest ACL + Anonymous Identity) |
Total |
8.0 |
Thursday 06-04
| Project | Code | Hours | Activity Description |
|---|---|---|---|
QRadar → Sentinel / Monad |
_ |
4.0 |
Monad API discovery — JWT decode, OpenAPI spec, pipeline CRUD operations, edge constraints. Created ise-lab-v2 filtered pipeline via API |
ISE Annual Cert Renewal |
_ |
1.0 |
TLS cert fingerprint script + runbook created (d001 encrypted) |
Centralized rsyslog Server |
_ |
1.0 |
rsyslog central collector partial created (d001 encrypted) |
Abnormal Security (ESA→EOP) |
_ |
1.0 |
ESA-Bypass investigation continued — policy divergence follow-ups |
Admin / PeopleSoft |
_ |
1.0 |
PeopleSoft codes tracker updated — all 28 active projects audited and mapped |
Total |
8.0 |
Friday 06-05
| Project | Code | Hours | Activity Description |
|---|---|---|---|
Abnormal Security (ESA→EOP) |
_ |
2.0 |
ESA-Bypass policy divergence follow-ups, DKIM investigation continued, AutoForwarding remediation |
ISE Policy / General Support |
_ |
2.0 |
Carry-forward items: Tube System re-run coordination, Alex Pena device verification, Android scanners policy (June 8 deadline) |
Change Management |
_ |
1.5 |
Guest ACL + Anonymous Identity implementation scheduling with Tony Sun, David Rukiza, Argam |
Linux Research / Server Deployment |
_ |
1.5 |
Linux inventory discovery — query refinement, dc-run-sql validation |
Documentation / Admin |
_ |
1.0 |
Worklog triage, task prioritization, PeopleSoft code assignment |
Total |
8.0 |
Weekly Summary by Project
| Project | Mon | Tue | Wed | Thu | Fri | Total |
|---|---|---|---|---|---|---|
Abnormal Security (ESA→EOP) |
2.0 |
2.5 |
3.0 |
1.0 |
2.0 |
10.5 |
QRadar → Sentinel / Monad |
— |
— |
1.5 |
4.0 |
— |
5.5 |
Change Management / CRs |
2.5 |
2.0 |
— |
— |
1.5 |
6.0 |
ISE Annual Cert Renewal |
— |
1.0 |
1.0 |
1.0 |
— |
3.0 |
Tube System Upgrade |
3.0 |
— |
— |
— |
— |
3.0 |
Linux Research / Remediation |
— |
2.0 |
— |
— |
1.5 |
3.5 |
Spectrum/GetWell/Scanners |
— |
0.5 |
1.0 |
— |
— |
1.5 |
Centralized rsyslog |
— |
— |
— |
1.0 |
— |
1.0 |
ISE Policy / General Support |
— |
— |
1.5 |
— |
2.0 |
3.5 |
Admin / Documentation |
0.5 |
— |
— |
1.0 |
1.0 |
2.5 |
Daily Total |
8.0 |
8.0 |
8.0 |
8.0 |
8.0 |
40.0 |
Work (CHLA)
| CHARGE TIME IN PEOPLESOFT - CRITICAL. Do this NOW before anything else. |
Critical (P0)
| Project | Description | Owner | Status | Due | Blocker |
|---|---|---|---|---|---|
Linux Research (Xianming Ding) |
EAP-TLS for Linux workstations, dACL, UFW |
Evan |
BEHIND (72 days overdue) |
02-24 |
Certificate "password required" - nmcli fix documented |
iPSK Manager |
Pre-shared key automation |
Ben Castillo |
BEHIND |
— |
DB replication issues |
MSCHAPv2 Migration |
Legacy auth deprecation — 6,227 devices, 5 waves. 6 batch SQL queries + 3-API endpoint profile script added (05-11). Report due. |
Evan |
25% — Report due, batch queries ready |
05-30 |
Report to turn in |
Research Segmentation |
All endpoints to Untrusted VLAN |
Evan |
BLOCKED |
— |
CISO decision pending |
Disaster Recovery |
ISE DR scoping — dot1x closed mode = total blackout |
Evan |
Scoping |
— |
— |
Mandiant Remediation |
Copy 4/16 findings, Guest ACL lab, Q2 assessment |
Evan |
Active |
— |
— |
SIEM QRadar → Sentinel |
Full SIEM platform transition. Monad console error resolved 05-12. Secrets configured. Blocked on DCR creation (Rule ID + Stream Name). Azure private network policy unresolved. |
Evan |
Active — blocked on DCR |
Q2 2026 |
Victor/Mauricio: create DCR, resolve Azure network policy |
Abnormal Security |
AI email platform — ESA cutover. CR assigned, CAB May 12 15:00. Implementation May 14 10:00. |
Evan |
Active — CAB today 15:00 |
05-14 |
Pre-CAB checklist: confirm Tyler, Jason, Sarah |
High Priority (P1)
| Project | Description | Owner | Status | Target |
|---|---|---|---|---|
ISE 3.4 Migration |
Upgrade from 3.2p9 |
Evan |
Blocked — maintenance window needed |
Q2 2026 |
Switch Upgrades |
IOS-XE fleet update (C9300, 3560CX) |
Evan |
Pending |
Q2 2026 |
Spikewell BYOD VPN |
dACL SQL, AD group integration |
Evan |
Active |
— |
Strongline Gateway |
MAC capture, Identity Group setup — 37 days aging |
Evan |
Active — David Rukiza assigned |
— |
Abnormal Security |
AI email security platform research, ESA cutover timeline |
Evan |
Newly assigned |
— |
DMZ Migration |
External services audit behind NetScaler |
Evan |
Audit phase |
— |
Firewall Audit (murus-portae) |
EtherChannel query, prefilter, policy assignments |
Evan |
Scoping — ASA API creds needed |
— |
iPSK Manager HA |
Server 2 config, TLS, SQL security audit |
Evan |
In progress |
— |
Sentinel KQL |
Build proficiency, distinguish from team |
Evan |
Onboarding |
— |
VNC Blocking |
Block and eliminate VNC protocol enterprise-wide |
Evan |
Active — Phase 0 (Discovery) |
Mid-June 2026 |
Strategic (P2)
| Project | Description | Owner | Status |
|---|---|---|---|
HHS Regulatory Compliance |
New HHS security policies implementation |
TBD |
NOT STARTED |
InfoSec Reporting Dashboard |
PowerBI metrics for executives |
TBD |
NOT STARTED |
EDR Migration (AMP → Defender) |
Endpoint protection consolidation |
TBD |
NOT STARTED |
Azure Legacy Migration |
Modern landing zone |
Team |
In Progress |
ChromeOS EAP-TLS |
SCEP + Victor, Paul testing |
Victor |
In Progress |
P0 — Critical / Blocking
Security & Compliance
-
ISE 3.2 Patch 10 upgrade — CVE-2026-20147 CVSS 9.9 / CVE-2026-20148. Propose maintenance window once patch confirmed on software.cisco.com.
-
ISE Advisory sa-ise-rce-traversal-8bYndVrZ — check Patch 10 availability
-
Mandiant Remediation — findings status tracked. Working session prep + defensive posture documented (comms-2026-04-24). Copy 4/16 updates into Excel at work. Guest ACL lockdown (WIR-M-01) pending lab validation. appendix-todos updated with MSCHAPv2 milestones.
-
Guest ACL update — guest redirect ACL work needed. Lab validate GUEST_CWA_REDIRECT_MAX_SECURITY in d000, then joint CR with NE. On today’s task list.
-
Disaster Recovery & Downtime Procedures — ISE top priority (dot1x closed mode = SPOF for network access)
-
ISE DR: Document failover sequence — PAN, MnT, PSN priority order
-
ISE DR: RADIUS dead-server detection on WLCs/switches — critical-auth VLAN fallback
-
ISE DR: Backup/restore procedures — scheduled config backups, tested restores
-
FTD/FMC DR: FMC loss = no policy management
-
Network DR: Core/distribution switch failure, STP reconvergence, HSRP failover
-
Document RTO/RPO per system
-
SIEM Migration (QRadar → Sentinel)
-
SIEM QRadar → Sentinel Migration — LEAD ROLE. 4 collection iterations (Apr 16, 17, 17-streamlined, 20-streamlined). Python chart pipeline built (
qradar-charts.py). Migration XLSX generated. Verification pending. Comms sent Apr 23.-
d001 artifacts: 8 JSON exports, 2 CSV inventories, migration XLSX, top5 source SVG/PNG, verification doc
-
Dependency: Monad pipeline for log source transition
-
Dependency: Sentinel KQL proficiency for query migration
-
-
Monad Pipeline Evaluation (origin: 2026-03-11) — lead role. Console error RESOLVED 05-12. 06-09: Architecture decision — rsyslog (CHLXSYSLOG01) as collection tier → Monad → Sentinel. ISE lab → rsyslog → Monad 6-step execution guide created with 10 API calls. ASA lab logs already flowing through rsyslog. DCR still needed — Victor + Mauricio.
-
Sentinel KQL — build proficiency, distinguish from team. Azure portal access acquired.
-
QRadar log source report — run AQL queries, fetch JSON, generate Python Excel
Active Deployments & Migrations
-
MSCHAPv2 Migration — 6-sheet Standard Report ready. Migration window 05-04 to 05-30 CLOSED. Confirm final report status and next steps with team. 6,227 MSCHAPv2 devices, 14,249 EAP-TLS/TEAP (70% migrated).
-
MSCHAPv2 weekly cadence — recurring Wednesday call established (first 04-22). Completed 2026-04-22.
-
MSCHAPv2 ownership matrix — sent in scoping email 4/24 with manager callouts (@Albert, @John). Completed 2026-04-24.
-
TCP Clocks deployment — Batch 1: 7 clocks validated (OUI 40:AC:8D). Batch 2 (06-09): 9 new MACs (OUI 40:AC:BD) added via ERS. 1 one-off reassigned. New switch deployed without RADIUS/AAA — clocks can’t authenticate. Switch onboarding template + 3 validation queries documented. ERS queries self-contained with
ersfunction. -
SRT Research VLAN — confirm roles with Tony Sun: Tony implementor, Evan tester. CAB approved 04-21.
-
Downtime Computers enforcement — draft ISE AuthZ rule: medigate_724 + Wireless = DenyAccess. Separate CR. d001: DC queries, audit CSVs (v1-v3), wireless violations report delivered 04-21.
-
Enterprise Linux 802.1X — standardize Shahab/Ding deployment (CISO priority). Overdue since 02-24. Blocked by nmcli cert fix.
-
Abnormal Security — CR-2026-05-07. Implemented 05-13. 06-09 update: Full policy review — 20-section EOP validation commands rebuilt, Hoxhunt SCL-1 investigation (intentional bypass confirmed), sclizer junk folder triage (~800 emails), Outlook reactions audit added, Connect-ExchangeOnline msalruntime fix documented. ESA migration expansion in progress — priority to move off ESA to full environment.
-
Team: Cox/William, Landeros/Jason, Rosado/Evan, Naranjo/Mauricio, Sandoval/Carlos
-
-
ASA VPN: Okta RADIUS → Entra SAML — (NEW 06-09) 5-phase migration plan built. ASA baseline captured (2 tunnel groups: CHLA_CORPORATE_USERS, CHLA_BYOD_USERS). 6 ISE policy screenshots. Tony Sun (ASA), Justin Halbmann (Entra/Okta), Evan (ISE). VPN cert expires 07-28. PDF deliverable ready. Share with team this week.
Tube System Upgrade (NEW — 06-01)
-
Tube System Upgrade — iTrack 3528165. 15x 10" TS stations need MAC addresses added to ISE identity group IoT_Onboard. MACs received from vendor (C8:1A:FE:20:xx:xx series). Station list spans ICU (CTICU, PICU, BMT, NICU, NICCU), ED, Surgery, Trauma, Pharmacy. Vendor contact: John Genest. Rationale: manufacturer no longer supports current system; failure risks delayed/missed patient care.
BMS Controller Segmentation (MIGRATED — 06-09)
-
BMS Controller Segmentation — Full migration from Principia LaTeX to d001. 12 partials, 5 Mermaid diagrams, 4 legacy PDFs, ISE screenshots.
d001 open bms-controller. Completed 2026-06-09.
BMS Device Inventory (NEW — 04-24)
-
BMS Device Inventory — 72 devices discovered across 37 switches (04-24). Profile-driven architecture (Claroty/Medigate). 16 queries built. Phase 0 complete. Next: cross-reference with Visio diagrams, classify by function, begin D2 diagrams. Cleanup: delete 4 orphaned test groups, migrate 4 retire-dACL devices, investigate 3 null-profile devices.
VNC Blocking (NEW — 05-11)
-
VNC Blocking — block and eliminate VNC enterprise-wide. Due mid-June 2026. Phase 0: discovery. January AQL query baseline to incorporate. Cross-reference BMS inventory for VNC-capable devices.
Investigations & Audits
-
Murus Portae (WAF) — Phase 0 discovery in progress. FMC cert expired. d001: DMZ NetScaler WAF investigation, zone map, architecture D2 diagrams (v1+v2 SVGs), FMC REST API reference guide, ops script. FMC API returning zero ACP rules — under investigation.
-
Firewall audit — FMC discovery inventory done (d001: fmc-discovery-2026-04-16). EtherChannel query, prefilter, policy assignments pending.
-
IoT Dr. Kim devices — RECURRING. All 4 MACs validated in IoT_iPSK_VLAN1620_Misc (04-24). v2 validation queries built with 7 deep analysis queries (group flapping, credential leakage, profile drift, NAS tracking, remediation timeline, deny audit, OUI scan). Revalidate — confirm no flapping since 04-24.
-
IoT device validation queries — v2 created with partials architecture, 16 queries across ERS/MnT/DataConnect/FMC. Completed 2026-04-24.
Stale Blockers (carried via carryover tracker)
-
k3s NAT verification — rule 170, 10.42.0.0/16 pod network (origin: 2026-03-09). 92 days. Blocks Wazuh indexer recovery → blocks SIEM visibility. Decide: test or defer to Q3.
-
Strongline Gateway VLAN fix — 8 devices wrong identity group (origin: 2026-03-16). 85 days. David Rukiza assigned — follow up on status.
Administrative
-
PeopleSoft — track time for current week
-
iTrack tickets — close open tickets
-
KQL library — build initial queries in codex + d001
-
Linux Research project — finalize and review
-
Tax filing 2025 (MFJ) — see encrypted case file in
data/d000/personal/for details and action items
P1 — Important
-
MSCHAPv2 action-item tracker — owner/status/next-steps per workstream
-
ISE admin MFA enforcement — recommendation tied to advisory (interim control pending Patch 10)
-
DMZ Migration — external services audit behind NetScaler. Linked to Murus Portae investigation.
-
Vocera/Wyse iTrack RCA — complete root cause report
-
GCC ISE Support — 3/4 nodes restored, PSN-04 deferred
-
Wazuh indexer recovery — blocked by k3s NAT (origin: 2026-03-09)
-
Vocera EAP-TLS Supplicant Fix (origin: 2026-03-12)
-
iPSK Manager HA — blocked by DB replication (Ben Castillo)
-
ISE 3.4 Migration — depends on Patch 10 completion first
-
Git history scrub — murus-portae-output.md + ise-analytics CSVs
-
Encrypt
prep-cmds-2026-04-15.adoc— plaintext committed to git -
ISE MnT Messaging Service — enable UDP syslog delivery (maintenance window needed)
Infrastructure (Personal)
-
Borg backups — test and validate on ALL systems (Razer, P16g, vault-01, bind-01, kvm-01, kvm-02)
-
Borg — verify backup script paths updated from dotfiles-optimus to dots-quantum
-
Borg — create initial archive for ThinkPad P16g if none exists
-
Libvirt VLAN hook debug on both KVMs
-
Te1/0/2 cable replacement and re-test
-
Vault Raft cluster — verify vault-01 rejoined
-
Fix EAP-TLS keyring/secrets issue on Razer workstation
Completed (confirmed — do not delete, archive only)
-
CR-2026-04-15 SRT Research VLAN — submitted to iTrack. Completed 2026-04-15.
-
CAB presentation 4/21 — SRT Research VLAN 233 → CHLA-Research. APPROVED. Completed 2026-04-21.
-
Downtime Computers wireless audit — 45 computers, 16 violating, v3 report delivered. Completed 2026-04-21.
-
Git identity fix — dots-quantum/git/.gitconfig email corrected. Completed 2026-04-21.
-
MSCHAPv2 10:30 meeting — next steps + ACL coordination. Completed 2026-04-17.
Service Requests (SR)
| SR# | Request | Requestor | Opened | Status |
|---|---|---|---|---|
3508542 |
Zoll cards connection issue |
— |
— |
STALE — verify in iTrack |
3508524 |
Disable dot1x on (2) network ports - 5th floor 3250 Wilshire (PXE-boot imaging issues) |
— |
— |
STALE — verify in iTrack (issues persisted after disable) |
3528165 |
Tube System Upgrade — 15 stations, MAC addresses for ISE IoT_Onboard identity group |
Genest, John (vendor contact) |
2026-06-01 |
NEW — MACs received, need ISE onboarding |
Incidents (INC)
| INC# | Priority | Description | Opened | SLA | Status |
|---|---|---|---|---|---|
1911859 |
— |
Strongline Gateways in Miscellaneous Subnet |
— |
— |
STALE — verify in iTrack (related to carryover P0) |
Change Requests - Emergency (ECAB)
| CR# | Description | Opened | Scheduled | Status |
|---|---|---|---|---|
No emergency changes |
Change Requests - Normal
| CR# | Description | Opened | Scheduled | Status |
|---|---|---|---|---|
No normal changes |
Change Requests - Scheduled/Standard
| CR# | Description | Opened | Window | Status |
|---|---|---|---|---|
No scheduled changes |
Change Requests - Root Cause / Post-Incident
| CR# | Description | Related INC | Opened | Status |
|---|---|---|---|---|
100451 |
Vocera Phones and Wyse devices went off network |
— |
— |
STALE — verify in iTrack |
Session Accomplishments (Claude Code)
CLAUDE.md Review & Correction
-
Fixed build invariant contradiction (
make | grep→ full output review) -
Added STD-024 (AsciiDoc Build Toolchain) to standards registry
-
Expanded file naming table: 6 missing prefixes (PRJ, SESSION, TEMPLATE, REPORT, INVENTORY, YEAR)
-
Rebuilt directory skeleton: 21 page dirs, 17 partial dirs, examples, images
-
Documented full Makefile target taxonomy (~40 targets)
-
Updated component description: added competencies, sessions
PeopleSoft Timesheet System
-
13 weekly timesheet partials generated (Mar 09 – Jun 05, 520 hours)
-
Scalable architecture:
partials/trackers/work/peoplesoft-codes/timesheet-YYYY-MM-DD.adoc -
PRJ-peoplesoft-time-entry.adoc wired with all 13 timesheets
-
4 agents parallelized for generation across 12 weeks
P1: Spam Bypass Investigation (SCL 9 Delivered to Inbox)
-
Root cause identified: SFV:SKA — user safe sender list override
-
7-phase PowerShell investigation with per-user junk config, message trace, transport rules, tenant allow/block
-
spam-bypass-investigation.adoc— full runbook with root cause matrix
P1: Intune Hybrid Domain Join — Cert Auth Failure
-
Devices failing EAP-TLS WiFi auth, forget/rejoin fixes it
-
Hybrid domain join = dual cert source (GPO AD CS + Intune SCEP/PKCS)
-
ISE DataConnect bulk queries: failure grouping, affected device list, forget/rejoin pattern confirmation
-
Control comparison commands (working machine vs affected)
-
intune-cert-investigation.adoc— full runbook with endpoint + ISE queries
rsyslog CHLXSYSLOG01 Deployment
-
First SSH login to Debian 13 syslog collector (10.248.13.254)
-
Server recon: rsyslog 8.2504.0, UDP+TCP 514 listening, 12G /var, 436G /home
-
ASA logging config documented: AnyC-FP-2140-1 + USC-TEST-RAVPN-5515
-
rsyslog-asa-troubleshooting.adoc— tmux layout, Phase 0-7 with live monitoring -
rsyslog-ops.adoc— full command reference, fixed for ISO 8601 + MSWinEventLog (encrypted in d001) -
Windows Event Logs confirmed arriving from L-N63064 (carlsandoval), L-N41999 (mnaranjo)
build-antora-page.sh — Nested Include Fix
-
Fixed recursive partial resolution — copies entire partials/examples trees, rewrites every file
-
PDFs went from 172K (broken, missing sections) to 1.8-2.2MB (complete)
-
Zero errors on both WRKLOG-2026-06-07 and WRKLOG-2026-06-08
API Rate Limits Reference
-
data/d001/api/max-api-calls.adoc.age— ISE, FMC, Graph, Sentinel, Monad, QRadar, Abnormal -
Retry strategy and rate limit detection patterns included
Project Audit
-
CHLA: P0/P1/P2 trackers 47 days stale, 6 projects missing from trackers, Abnormal duplicate
-
Personal: 106 project partials, 27 pages, 79 partials without pages
-
Identified stale blockers: k3s NAT (59 days), Strongline (52 days)
Personal
In Progress
| Project | Description | Status | Notes |
|---|---|---|---|
k3s Platform |
Production k3s cluster on kvm-01 |
Active |
Prometheus, Grafana, Wazuh deployed |
Wazuh Archives |
Enable archives indexing in Filebeat |
Active |
PVC fix pending |
kvm-02 Hardware |
Supermicro B deployment |
Active |
Hardware ready, RAM upgrade done |
Planned
| Project | Description | Target | Blocked By |
|---|---|---|---|
Vault HA (3-node) |
vault-02, vault-03 on kvm-02 |
Q2 2026 (slipped from Q1) |
kvm-02 deployment |
k3s HA (3-node) |
Control plane HA |
Q2 2026 (slipped from Q1) |
kvm-02 deployment |
ArgoCD GitOps |
k3s GitOps deployment |
After k3s stable |
— |
MinIO S3 |
Object storage for k3s |
After ArgoCD |
— |
Personal asset management (YAML + CLI + AsciiDoc) |
Q2 2026 |
Schema approved |
Active — Infrastructure
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
Wazuh agent deployment |
Deploy agents to all infrastructure hosts |
P2 |
Pending |
After archives fix |
k3s Platform |
Production k3s cluster on kvm-01 |
P1 |
In Progress |
— |
Wazuh Archives |
Enable archives indexing in Filebeat, PVC fix |
P1 |
In Progress |
— |
kvm-02 Hardware |
Supermicro B deployment, RAM upgrade done |
P1 |
In Progress |
— |
Active — Security & Encryption
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
Configure 4th YubiKey |
SSH FIDO2 keys |
P1 |
TODO |
— |
Cold storage M-DISC backup |
age-encrypted archives |
P1 |
TODO |
After YubiKey setup |
Active — Development & Tools
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
netapi Commercialization |
Go CLI rewrite with Cobra-style argument discovery, package for distribution |
P0 |
Active |
— |
Ollama API Service |
FastAPI (17 endpoints), productize — config audit, doc tools, runbook gen |
P0 |
Active |
— |
Shell functions (fe, fec, fef) |
File hunting helpers |
P3 |
TODO |
— |
Active — Documentation
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
D2 Catppuccin Mocha styling |
domus-* spoke repos (177 files total) |
P3 |
In Progress |
— |
Active — Financial
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
Amazon order history import |
Download CSV from Privacy Central → parse with awk → populate subscriptions tracker |
P1 |
Waiting |
Pending Amazon data export (requested 2026-04-04) |
Active — Education
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
No active education tasks — see education trackers |
Active — Personal & Life Admin
| Task | Details | Priority | Status | Due |
|---|---|---|---|---|
ThinkPad T16g Setup |
Arch install, stow dotfiles, Ollama stack, netapi dev env |
P0 |
Pending |
— |
P50 Arch to Ubuntu migration |
P2 |
In Progress |
— |
|
X1 Carbon Ubuntu installs |
2 laptops, LUKS encryption |
P2 |
In Progress |
— |
P50 Steam Test |
Test Flatpak Steam + apt cleanup of broken i386 packages |
P3 |
Pending |
— |
Documentation Sites
-
docs.domusdigitalis.dev - Private documentation hub
-
docs.architectus.dev - Public portfolio site
Education
Claude Code Mastery
| Resource | Details | Progress | Status |
|---|---|---|---|
Claude Code Full Course (4 hrs) |
Nick Saraev - YouTube comprehensive course |
26:49 / 4:00:00 |
IN PROGRESS |
Claude Code Certification |
Anthropic official certification (newly released) |
Not started |
GOAL |
Active Tracks (Focus)
-
Don Quijote - Primera Parte
Skills Mastery (Critical)
-
Regex Mastery - 10-module curriculum
-
AsciiDoc Docs - Documentation format
-
Antora Docs - Documentation pipeline
Certification Deadlines
-
CISSP - July 12, 2026 (10-week plan active — Week 1)
-
RHCSA 9 - Q3 2026 (after CISSP)
-
LPIC-1 - Renewal required (blocks LPIC-2)
Spanish C1 Certification Goals
| Certification | Provider | Target | Status | Strategy |
|---|---|---|---|---|
Instituto Cervantes / UNAM / Salamanca |
Q2 2026 |
ACTIVE |
Computer-based, faster results - take FIRST |
|
Q3/Q4 2026 |
PLANNED |
After SIELE success, harder exam |
||
2027 |
FUTURE |
Mastery level - requires extensive immersion |
| SIELE is computer-adaptive, results in 3 weeks. DELE is paper-based, results in 3-4 months. Do SIELE first to validate readiness. |
Don Quijote Writing Practice - DELE C1/C2 Initiative
Method:
-
Read chapter in original Spanish
-
Write personal analysis/understanding en espanol
-
AI review for grammar, vocabulary, register
-
Build comprehensive understanding of literary elements
Today’s Study
-
Focus: CISSP (41 days to July 12 exam — schedule exam today 06-01), MSCHAPv2 migration wrap-up
-
Secondary: RHCSA curriculum, Spanish SIELE C1
-
CISSP — Security & Risk Management (continuing). Schedule exam this afternoon.
-
RHCSA — continue curriculum phase
-
Spanish — Don Quijote reading + analysis (DTLA study day)
-
MSCHAPv2 — migration window closed 05-30, review final report
Regex Training (CRITICAL)
-
Status: 52 days carried over (since 2026-03-16)
-
Priority: After PeopleSoft, before Quijote
-
Session: Character classes, word boundaries
Infrastructure
Documentation Sites
| Site | URL | Status | Actions Needed |
|---|---|---|---|
Domus Digitalis |
Active |
Validate, harden, improve |
|
Architectus |
Active |
Public portfolio site - maintain |
HA Deployment Status
| System | Description | Status | Notes |
|---|---|---|---|
VyOS HA |
vyos-01 (kvm-01) + vyos-02 (kvm-02) with VRRP VIP |
✅ COMPLETE |
2026-03-07 - pfSense decommissioned |
BIND DNS HA |
bind-01 (kvm-01) + bind-02 (kvm-02) with AXFR |
✅ COMPLETE |
Zone transfer operational |
Vault HA |
Raft cluster (vault-01/02/03) |
✅ COMPLETE |
Integrated with PKI |
Keycloak Rebuild |
keycloak-01 corrupted, rebuild from scratch |
🔄 NEXT |
Priority P3 - SSO broken |
FreeIPA HA |
ipa-02 replica planned |
📋 PLANNED |
Linux auth redundancy |
AD DC HA |
home-dc02 replication |
📋 PLANNED |
Windows auth redundancy |
iPSK Manager HA |
ipsk-mgr-02 with MySQL replication |
📋 PLANNED |
PSK portal redundancy |
ISE HA |
PAN HA (ise-01 reconfigure) |
⏳ DEFERRED |
Wait until ise-02 stable |
ISE 3.5 Migration |
Upgrade path: 3.2p9 → 3.4 (P1) → 3.5 (target) |
📋 PLANNED |
After 3.4 Migration completes (Q2 2026) |
Single Points of Failure (CRITICAL)
| These systems have NO redundancy - outage impacts production. |
| System | Impact if Down | Mitigation |
|---|---|---|
ISE (ise-02) |
All 802.1X stops - wired and wireless auth fails |
ise-01 reconfiguration deferred until ise-02 stable |
Keycloak (keycloak-01) |
SAML/OIDC SSO broken (ISE admin, Grafana, etc.) |
NEXT PRIORITY - Rebuild runbook |
FreeIPA (ipa-01) |
Linux auth, sudo rules, HBAC fails |
ipa-02 replica planned |
AD DC (home-dc01) |
Windows auth, Kerberos, GPO fails |
home-dc02 replica planned |
iPSK Manager |
Self-service PSK portal unavailable |
ipsk-mgr-02 with MySQL replication planned |
Validation Tasks
| Task | Details | Status |
|---|---|---|
docs.domusdigitalis.dev validation |
Test all cross-references, search, rendering |
TODO |
docs.domusdigitalis.dev hardening |
HTTPS, CSP headers, security review |
TODO |
docs.architectus.dev validation |
Public site content review |
TODO |
Hub-spoke sync verification |
All components building correctly |
Ongoing |
Quick Commands
Git & GitHub CLI
gh repo create <name> --private --source . --remote origin --push
gh repo clone EvanusModestus/PowerShell ~/atelier/_projects/work/PowerShell
gh repo clone defaults to SSH. If key is passphrase-protected, load agent first: eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519_github
|
for repo in ~/atelier/_bibliotheca/domus-*/ ~/atelier/_projects/personal/domus-*/; do
[ -d "$repo/.git" ] || continue
name=$(basename "$repo")
git -C "$repo" log --since="2026-04-06" --until="2026-04-07" --format="%h %aI %s" 2>/dev/null |
awk -v r="$name" '{print r, $0}'
done
git log --oneline -- $(find . -name "*.adoc" -type f -newermt "$(date +%F)")
git restore --staged data/d001/api/ise-dataconnect/output/output-2026-04-24
Safe — removes from staging area only. Working tree is untouched. Use when you accidentally git add a plaintext or output file.
gh CLI — repo discovery and filtering
gh repo list --limit 100 --json name,description \
| jq -r '.[] | select(.name | test("domus|antora|asciidoc"; "i")) | "\(.name)\t\(.description)"'
gh repo list --limit 100 --json name,description,updatedAt \
| jq -r 'sort_by(.updatedAt) | reverse | .[:20] | .[] | "\(.updatedAt[:10])\t\(.name)\t\(.description)"'
gh repo list --limit 100 --json name,diskUsage \
| jq -r '.[] | "\(.diskUsage)\t\(.name)"' | sort -rn | head -10
gh repo clone EvanusModestus/<repo-name> ~/atelier/_bibliotheca/<repo-name>
find & grep
find . -name "*.adoc" -type f -newermt "$(date +%F)" | sort
-mtime 0 means "last 24 hours", not "today". -newermt "$(date +%F)" compares against midnight — exact.
|
find . -iname "*mschap*" -type f | sort
find . -type f \( -iname "*ise*" -o -iname "*mschap*" \) | sort
find . -type f -iregex '.*\(ise\|mschap\).*'
find . -type f -iname "*meeting*" \
-not -path "*/node_modules/*" \
-not -path "*/.git/*" \
-not -path "*/build/*"
find .drafts -type f -printf '%T@ %Tc %p\n' | sort -rn | awk '{$1="";print}' | head -3
grep -rl "pattern" . --include="*.adoc" # file count (which files)
grep -rn "pattern" . --include="*.adoc" # line matches (every occurrence)
grep -rc "pattern" . --include="*.adoc" | grep -v ':0$' # match count per file
grep -rn -E 'git init|gh repo create' docs/ --include='*.adoc' -B2 -A2
Search codex by content — which files contain a command?
find docs/modules/ROOT/examples/codex/powershell -type f -name "*.adoc" \
-exec grep -l 'Get-Process\|Start-Process\|pipeline\|Where-Object' {} \;
Pattern: find -exec grep -l returns only filenames with matches — like grep -rl but with find’s `-type f -name filtering. Use \| for OR in grep basic regex. Swap the pattern for any cmdlet or keyword to locate coverage across the codex.
find docs/modules/ROOT -name "powershell" -type d \
-exec sh -c 'echo "$1: $(find "$1" -type f | wc -l) files"' _ {} \;
for f in $(find docs/modules/ROOT/examples/codex/powershell -name "*.adoc" -type f); do
base=$(basename "$f")
dir_parent=$(basename $(dirname "$f"))
grep -rq "$dir_parent/$base" docs/modules/ROOT/pages/codex/powershell/ \
docs/modules/ROOT/examples/codex/powershell/*.adoc 2>/dev/null \
|| echo "ORPHAN: $f"
done
find → grep → open in nvim
nvim $(find -path '*oauth*' -name '*.adoc' -type f \
-exec grep -l 'timeout\|expire\|reconfig\|token' {} \;)
Command substitution $(…) feeds all matches as arguments to nvim — opens every hit as a buffer. :bn/:bp to cycle, :ls to list. One file? Opens directly. Five files? All loaded, ready to navigate.
nvim $(find docs/modules/ROOT -name '*.adoc' -type f \
-exec grep -l 'token.*expire\|oauth.*refresh' {} \;)
find -path '*oauth*' -name '*.adoc' -type f \
-exec grep -l 'timeout\|expire' {} \; \
-exec nvim {} \;
Trailing \| in grep patterns matches empty string — every file matches. Always end with a term, not a pipe: 'timeout\|expire\|token' not 'timeout\|expire\|token\|'.
|
Trace Antora partial inclusion chains
grep -rl 'commands/shell' docs/modules/ROOT/partials/
grep -rl 'quick-commands' docs/modules/ROOT | wc -l
file="commands/shell"
grep -rl "$file" docs/modules/ROOT/partials/ | while read f; do
parent=$(basename "$f" .adoc)
echo "$file -> $parent"
grep -rl "$parent" docs/modules/ROOT/pages/ | while read p; do
echo " -> $(basename "$p")"
done
done
Pattern: grep -rl finds which files contain the string. Chain two passes — first finds the assembler partial, second finds every page that includes it. Works for any partial in the Antora include hierarchy.
Multi-pattern file search — worklog partial discovery
find docs/modules/ROOT -name "*urgent.adoc*" -type f
find docs/modules/ROOT -name "*morning.adoc*" -type f
find docs/modules/ROOT -type f -regextype posix-extended \
-regex '.*(urgent|morning|work-chla|personal|education|infrastructure|quick-commands|related)\.adoc' \
| sort
Pattern: -regextype posix-extended enables | alternation without escaping. One process, one sort — versus 8 separate finds. The sort deduplicates visually and groups by path.
find docs/modules/ROOT -type f -name "*.adoc" \
| grep -E 'urgent|morning|work-chla|personal|education|infrastructure|quick-commands|related'
Trade-off: the pipeline version is more readable but spawns two processes. The regex version is a single find — faster on large trees, same result.
Cross-repo literary term search — bibliotheca-wide discovery
When searching for a term across the entire _bibliotheca (multiple repos, mixed file types), these patterns escalate from narrow to broad.
grep -rn --include='*.adoc' -c 'sanchuelo' . | grep -v ':0$'
grep -rl --include='*.adoc' -i 'sanchuelo' ~/atelier/_bibliotheca/ | sort
grep -rn --include='*.adoc' -i -B1 -A1 'sanchuelo' ~/atelier/_bibliotheca/domus-captures/
grep -rl -i 'sanchuelo' ~/atelier/_bibliotheca/ --include='*.txt' --include='*.adoc' | sort
find ~/atelier/_bibliotheca/ -type f \( -name '*.adoc' -o -name '*.txt' \) -print0 \
| xargs -0 grep -li 'sanchuelo' | sort
grep -rl -i 'sanchuelo' ~/atelier/_bibliotheca/ --include='*.adoc' --include='*.txt' | xargs nvim
Pattern escalation: #1 confirms the term exists and where. #2 expands to all repos. #3 shows context without opening files. #4 adds plain text sources (Quijote .txt originals). #5 is the safe version for automation. #6 opens everything for editing.
Trade-off: grep -r --include is faster for known file types. find | xargs grep is safer for paths with spaces and more extensible (add -name '*.md' etc.). For literary searches across the bibliotheca, #4 or #5 is usually the right starting point — the source texts are .txt, not .adoc.
Email thread analysis — extract people, dates, commitments, silence
grep -P '(@\w+|^From:.*<)' comms.adoc
grep -nP '\d{1,2}/\d{1,2}/\d{2,4}|20\d{2}-\d{2}-\d{2}' comms.adoc
grep -niP '(I can |I will |I.ll |we will |we.ll )' comms.adoc
grep -niP '(\?|need to confirm|need to validate|TBD|pending)' comms.adoc
comm — set difference (who hasn’t replied)
# All recipients
grep -oP '<\K[^>]+' comms.adoc | sort -u > /tmp/all-recipients
# All senders
grep -P '^From:' comms.adoc | grep -oP '<\K[^>]+' | sort -u > /tmp/replied
# Who's silent — follow-up targets
comm -23 /tmp/all-recipients /tmp/replied
comm -23 outputs lines only in file 1 (recipients not in senders). Requires sorted input. grep -oP '<\K[^>]+' uses PCRE lookbehind — match < but don’t include it, capture until >.
Sort find results by modification time (newest first)
find discovers files but has no sort. Chain -printf with sort to order by mtime.
awk '{print $2}' truncates filenames with spaces — Familia Romana_ Lingva… becomes Familia. Always use the null-safe or sub() variants below for real data.
|
# Sort by mtime, strip epoch prefix — handles spaces in filenames
find ~/Downloads -maxdepth 1 -name '*.pdf' -printf '%T@ %p\n' | sort -rn | awk '{sub(/^[^ ]+ /,""); print}'
sub(/[ ]+ /,"") removes everything up to and including the first space (the epoch). {print $2} would split on every space — fatal for Familia Romana_ Lingva Latina.
# ISO 8601 timestamps — readable and lexicographically sortable
find ~/Downloads -maxdepth 1 -name '*.pdf' -printf '%T+ %p\n' | sort -r | head -20
%T+ renders YYYY-MM-DD+HH:MM:SS — no epoch math needed, still sorts correctly as text.
# Null-delimited: survives any filename (newlines, quotes, unicode)
find ~/Downloads -maxdepth 1 -name '*.pdf' -printf '%T@\t%p\0' | sort -zrn | awk -v RS='\0' -F'\t' '{print $2}'
-printf '%T@\t%p\0' — tab separates epoch from path, null terminates. sort -z sorts null-delimited records. awk -v RS='\0' -F'\t' reads null-terminated, splits on tab — $2 is now the full path regardless of spaces.
# GNU stat equivalent — works where -printf is unavailable
find ~/Downloads -maxdepth 1 -name '*latin*' -exec stat --format='%Y %n' {} + | sort -rn | awk '{sub(/^[^ ]+ /,""); print}'
-exec … {} + batches all files into one stat call (faster than \;). On macOS, use stat -f '%m %N' instead of --format='%Y %n'.
File intelligence — size, type, duplicates, age
Beyond finding files — interrogating them.
# Size in bytes (-printf %s), human-readable via numfmt
find ~/Downloads -type f -printf '%s\t%p\n' | sort -rn | head -10 | numfmt --to=iec --field=1
numfmt --to=iec --field=1 converts the first field from bytes to K/M/G. sort -rn on raw bytes is exact — ls -lhS rounds and sometimes mis-sorts.
# Files sharing a byte count — likely duplicates (confirm with md5sum)
find ~/Downloads -type f -printf '%s %p\n' | awk '{seen[$1]++; files[$1]=files[$1] "\n " $0} END {for (s in seen) if (seen[s]>1) print files[s]}'
# md5sum only files with duplicate sizes (two-pass: fast then precise)
find ~/Downloads -type f -printf '%s\n' | sort | uniq -d | while read -r size; do
find ~/Downloads -type f -size "${size}c" -exec md5sum {} +
done | sort | uniq -w32 -D
Two-pass: first find duplicate sizes (cheap), then md5sum only those (expensive). uniq -w32 -D compares first 32 chars (the hash) and prints all duplicates.
# Count files by MIME type (not extension — extensions lie)
find ~/Downloads -type f -exec file --mime-type -b {} + | sort | uniq -c | sort -rn
file --mime-type -b reports actual content type. -b suppresses filename. A .pdf that’s really text/html is a failed download.
# Files not accessed in 30 days — candidates for cleanup
find ~/Downloads -maxdepth 1 -type f -atime +30 -printf '%A+ %s\t%p\n' | sort | numfmt --to=iec --field=2
-atime 30` = access time older than 30 days. `-printf '%A' shows last access. Useful for Downloads cleanup without deleting something you just renamed.
# Which subdirectories consume the most space?
find . -maxdepth 1 -type d -exec du -sh {} + 2>/dev/null | sort -rh | head -20
Batch operations — rename, move, transform
# Dry run — show what would change (remove echo to execute)
find ~/Downloads -maxdepth 1 -type f -name '* *' -print0 | while IFS= read -r -d '' f; do
dir=$(dirname "$f")
base=$(basename "$f" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
echo mv "$f" "$dir/$base"
done
IFS= read -r -d '' — the holy trinity for null-safe filename reading. IFS= prevents whitespace trimming. -r prevents backslash interpretation. -d '' reads until null.
# Sort Downloads chaos into folders by type
find ~/Downloads -maxdepth 1 -type f -print0 | while IFS= read -r -d '' f; do
ext="${f##*.}"
case "$ext" in
pdf|epub) dest="books" ;;
jpg|png|svg) dest="images" ;;
sh|py|rb) dest="scripts" ;;
*) dest="other" ;;
esac
mkdir -p ~/Downloads/"$dest"
echo mv "$f" ~/Downloads/"$dest"/
done
${f##.} — parameter expansion: strip longest match of . from front, leaving only the extension. No basename or awk needed.
# Convert all epubs in a directory to asciidoc via pandoc
find . -name '*.epub' -type f -exec sh -c '
for epub; do
adoc="${epub%.epub}.adoc"
pandoc -f epub -t asciidoc "$epub" -o "$adoc" \
&& printf " → %s (%s lines)\n" "$adoc" "$(wc -l < "$adoc")" \
|| printf " ✗ failed: %s\n" "$epub"
done
' _ {} +
-exec sh -c '…' _ {} + — batch mode. _ fills $0 (script name, discarded). All matched files become $1, $2, … iterated by for epub. One sh invocation, not one per file.
xargs power patterns
# Checksum all PDFs in parallel (4 processes)
find ~/Downloads -name '*.pdf' -print0 | xargs -0 -P4 md5sum
-P4 runs 4 md5sum processes simultaneously. -print0 | xargs -0 is the null-safe pipeline — no filename can break it.
# Compare files pairwise with diff
find . -name '*.adoc' -print0 | xargs -0 -n2 diff --brief
-n2 feeds two arguments per invocation. Useful for pairwise comparisons, copy operations (-n2 with cp), or any command taking exactly two args.
# Backup every config file: cp <file> <file>.bak
find /etc -maxdepth 1 -name '*.conf' -print0 | xargs -0 -I{} cp {} {}.bak
-I{} replaces {} with each filename. Slower than + batching (one cp per file) but necessary when the filename must appear in a specific position.
Process substitution — diff without temp files
# What files exist in study-A but not study-B?
diff <(find data/d000/education/ciceron-study -type f -name '*.adoc' | sort) \
<(find data/d000/education/latin-study -type f -name '*.adoc' | sort)
<(cmd) creates a file descriptor from command output. diff sees two "files" — no temp files created, no cleanup needed.
# Side-by-side: file type census of two directories
paste <(find dir1 -type f -exec file --mime-type -b {} + | sort | uniq -c | sort -rn) \
<(find dir2 -type f -exec file --mime-type -b {} + | sort | uniq -c | sort -rn)
awk, sed, jq
awk — field extraction
awk '{print $2}' file.txt
awk -F: '{print $1, $3}' /etc/passwd
awk '/\[source,json\]/{getline; if ($0 ~ /^----/) {p=1; next}} p && /^----/{p=0; next} p' file.adoc
awk '{printf "%-30s %s\n", $1, $2}' file.txt
sed — stream editing
# Before
awk 'NR==73' /etc/ssh/sshd_config
# Change
sed -i '73s/#GSSAPIAuthentication no/GSSAPIAuthentication yes/' /etc/ssh/sshd_config
# After
awk 'NR==73' /etc/ssh/sshd_config
sed -n '10,20p' file.txt
sed — line-targeted replacement (verify-before / change / verify-after)
# 1. LOCATE: find the line number
grep -n 'adoc-pdf' zsh/.zshrc
# 2. VALIDATE: read the exact line before changing
awk 'NR==1760' zsh/.zshrc
# 3. CHANGE: target by line number — only hits that line
sed -i '1760s/alias adoc-pdf=/alias build-adoc=/' zsh/.zshrc
# 4. VERIFY: confirm change AND check for collateral
grep -n 'build-adoc\|adoc-pdf' zsh/.zshrc
Without the line number prefix (1760s/), sed replaces every match in the file — a shotgun. With it, surgical. The line number comes from grep -n.
awk 'NR==1218 || NR==1760' zsh/.zshrc
# grep found the error at line 44164 — read 50 lines of context
awk 'NR>=44160 && NR<=44210' session-dump.adoc
No head | tail chains. No sed -n '44160,44210p'. One awk, two numbers.
grep -oP with \K — value extraction from key-value logs
# ISE syslog — extract failure reasons
grep -oP 'FailureReason=\K[^,;]+' /var/log/syslog | sort | uniq -c | sort -rn
# ISE — extract MAC addresses
grep -oP 'Calling-Station-ID=\K[0-9A-Fa-f:.-]+' /var/log/syslog | sort -u
# ISE — extract NAS IPs
grep -oP 'NAS-IP-Address=\K[0-9.]+' /var/log/syslog | sort -u
# ISE — extract device names
grep -oP 'NetworkDeviceName=\K[^,;]+' /var/log/syslog | sort -u
\K resets the match start — everything before \K is required context but excluded from output. [^,;]+ captures until the next delimiter. Pipe to sort -u for unique, sort | uniq -c | sort -rn for counted frequency.
# Generic form — works for any key=value log format
grep -oP 'FIELD_NAME=\K[^,;]+' logfile | sort | uniq -c | sort -rn | head -20
jq — JSON processing
curl -s localhost:8080/stats | jq '.stats.total_files'
jq '.results[] | select(.category == "standards")' response.json
jq -r '.[] | [.title, .path] | @tsv' response.json | column -t -s $'\t'
gh api "repos/EvanusModestus/domus-captures/commits?path=docs/&per_page=10" |
jq -r '.[] | "\(.commit.author.date[:10]) \(.sha[:7]) \(.commit.message | split("\n")[0])"'
Shell Patterns
xargs — when the next command reads arguments, not stdin
| Next command reads… | Use |
|---|---|
stdin ( |
pipe directly |
arguments ( |
|
-I{} placeholdermkdir -p /tmp/adoc-backup-$(date +%F) && \
find . -name "*.adoc" -type f -newermt "$(date +%F)" | \
xargs -I{} cp {} /tmp/adoc-backup-$(date +%F)/
-P4 runs 4 at a timefind .drafts -name "*.adoc" -type f | xargs -P4 -I{} asciidoctor -o /dev/null {}
find . -name "*.adoc" -type f -print0 | xargs -0 wc -l
Process substitution — <(cmd) treats output as a file
diff <(grep '|' partials/trackers/work/adhoc/carryover.adoc | head -20) \
<(git show HEAD~1:partials/trackers/work/adhoc/carryover.adoc | grep '|' | head -20)
diff <(find docs/modules/ROOT/pages/projects/chla/mschapv2-migration -name "*.adoc" -type f | sort) \
<(grep -oP 'mschapv2-migration/[^[]+\.adoc' docs/modules/ROOT/nav.adoc | sort)
Command substitution — embed output as arguments
nvim "$(find data/ -name '*.adoc' -type f -printf '%T@ %p\n' | sort -rn | awk 'NR==1{print $2}')"
wc -l $(find docs/modules/ROOT -path '*mschapv2*' -name '*.adoc' -type f)
Conditional execution — capture, test, act
files=$(find .drafts -name 'in*' -type f) && [ -n "$files" ] && nvim $files
files=$(grep -rl '\[ \]' .drafts/*.adoc) && [ -n "$files" ] && nvim $files
grep -q 'TODO\|FIXME\|\[ \]' "$file" && nvim "$file"
Pattern: $(capture) → [ -n ] tests non-empty → && only proceeds if true.
grep -q is the idempotent guard — run repeatedly, only opens when there’s work.
Decrypt and open — find .age, decrypt, nvim in one shot
files=$(find . -name "*tcp-clock*.age" -type f) && \
[ -n "$files" ] && echo "$files" | xargs -I{} decrypt-file {} && \
nvim $(echo "$files" | sed 's/\.age$//')
Pattern: find .age only (never tries plaintext), sed derives the decrypted path, guard prevents empty nvim. Change the glob to match any project.
tee_clean — color on screen, clean text in file
tee_clean() {
tee >(sed 's/\x1b\[[0-9;]*m//g' > "$1")
}
# Color output on terminal, stripped in file
jq -C '.' data.json | tee_clean output.json
xq -C '.' data.xml | tee_clean output.json
# Wrap a whole block
{
echo "=== Summary ==="
jq -C '.[] | .name' data.json
} | tee_clean summary.txt
The >(cmd) is process substitution — tee writes to stdout AND to the subshell pipe. sed strips ANSI escape sequences (\x1b\[[0-9;]*m) before they hit the file.
Dependency check — verify toolchain in one shot
for cmd in asciidoctor asciidoctor-pdf pandoc rouge d2 mmdc age; do
printf "%-20s %s\n" "$cmd" "$(command -v $cmd >/dev/null 2>&1 && echo 'OK' || echo 'MISSING')"
done
Pattern: command -v checks if binary exists on PATH. >/dev/null 2>&1 suppresses output — we only care about exit code. Swap the tool list for any project’s dependencies.
printf safety — dashes as data, not options
--- as invalid optionprintf '---\n\n'
--- as dataprintf '%s\n\n' '---'
Kill stuck SSH sessions
lsof -i TCP -n -P | awk '/ssh.*ESTABLISHED/ {print $2, $9}'
lsof -i TCP -n -P | awk '/ssh.*kvm-01.*ESTABLISHED/ {print $2}' | sort -u | xargs kill
lsof -i TCP -n -P | awk '/ssh.*ESTABLISHED/ {print $2}' | sort -u | xargs kill
lsof -i TCP -n -P lists all TCP connections. awk filters for SSH + ESTABLISHED, prints only the PID ($2). sort -u deduplicates (multiple file descriptors per process). xargs kill sends SIGTERM to each.
File Descriptors & Redirection
The three file descriptors
| FD | Name | Purpose |
|---|---|---|
0 |
stdin |
input to the command |
1 |
stdout |
normal output (valid results) |
2 |
stderr |
error messages |
Split stdout and stderr into separate files
find / -name "*.conf" 1>results.txt 2>errors.txt
Suppress errors — 2>/dev/null
find / -name "*.conf" 2>/dev/null
Merge stderr into stdout — 2>&1
command 2>&1 | grep "pattern"
This sends both stdout and stderr through the pipe. Without 2>&1, only stdout reaches grep — errors print to the terminal and bypass the pipeline.
Heredoc patterns
cat <<'EOF'
Line 1
Line 2
EOF
git commit -m "$(cat <<'EOF'
feat: add new feature
Multi-line description here.
EOF
)"
API & curl/jq
domus-api — Documentation System REST API
cd ~/atelier/_projects/personal/domus-api && uv run uvicorn domus_api.main:app --host 0.0.0.0 --port 8080
curl -s localhost:8080/ | jq
curl -s 'localhost:8080/search?q=mandiant' | jq
curl -s 'localhost:8080/search?q=mandiant' | jq '.results[] | {path, title, match_count}'
curl -s 'localhost:8080/pages?category=standards' | jq
curl -s localhost:8080/attributes | jq
GitHub API
gh search code "vault seal" --owner EvanusModestus --json repository,path,textMatches |
jq '.[] | {repo: .repository.full_name, file: .path, match: .textMatches[].fragment}'
gh api 'repos/EvanusModestus/domus-captures/git/trees/main?recursive=1' |
jq '[.tree[] | select(.path | endswith(".adoc"))] | length'
Domus Workflows
Read content from terminal (meeting-ready)
bat docs/modules/ROOT/pages/2026/04/WRKLOG-$(date +%Y-%m-%d).adoc
bat docs/modules/ROOT/partials/trackers/work/priorities/current.adoc
bat docs/modules/ROOT/partials/trackers/work/adhoc/carryover.adoc
bat docs/modules/ROOT/partials/projects/mandiant-remediation/summary.adoc
Search and discovery
grep -rl "MSCHAPv2" docs/modules/ROOT/ --include="*.adoc" | sort
grep -rn "pattern" docs/modules/ROOT/partials/codex/ --include="*.adoc" -B1 -A3
ls -1 docs/modules/ROOT/pages/2026/04/WRKLOG-*.adoc
Tracker aging — calculate days from origin
echo $(( ($(date +%s) - $(date -d "2026-03-09" +%s)) / 86400 ))
Encrypted data access (d001)
age --decrypt -i ~/.secrets/.metadata/keys/master.age.key \
data/d001/projects/mandiant-remediation/findings-status-2026-04-16.adoc.age \
| bat --language asciidoc
for d in data/d001/projects/*/; do
total=$(find "$d" -type f | wc -l)
plain=$(find "$d" -type f ! -name '*.age' ! -name 'README.adoc' ! -name '.gitkeep' ! -name '*.py' | wc -l)
printf "%-25s %s files %s plaintext\n" "$(basename "$d")" "$total" "$plain"
done
d000 study builds
for d in p1-cap-03{7,8,9}; do
for f in data/d000/education/quijote-study/notas/$d/*.adoc; do
d000 build "$d/$(basename "$f" .adoc)" html --variant light-cyan
done
done
d000 build p1-cap-038/texto-anotado html --variant light-cyan
d000 build p1-cap-038/texto-anotado pdf --theme light-cyan
for d in p1-cap-03{7,8,9}; do
for f in data/d000/education/quijote-study/notas/$d/*.adoc; do
d000 build "$d/$(basename "$f" .adoc)" pdf --theme light-cyan
done
done
firefox data/d000/education/quijote-study/notas/p1-cap-03{7,8,9}/output/*.html &
firefox data/d000/education/quijote-study/notas/p1-cap-03{7,8,9}/output/*.pdf &
lp data/d000/education/quijote-study/notas/p1-cap-03{7,8,9}/output/*.pdf
d000 build annotated-text pdf --theme light-cyan
d000 build lpl-study/notas/texto-anotado pdf --theme light-cyan
d000 build de-oratore/libro-i/texto-anotado html --variant light-cyan
Available themes
ls ~/atelier/_bibliotheca/domus-asciidoc-build/themes/pdf/ | sed 's/-theme\.yml//'
# base blue burgundy catppuccin creative dark don-quijote green
# learning light-cyan navy operations orange purple reference royal
~/atelier/_bibliotheca/domus-asciidoc-build/docinfo/compose.sh --list
# light dark catppuccin royal light-cyan
ISE & Network Ops
ISE ERS API — endpoint CRUD
export ISE_HOST="{ise-ip}" ISE_USER="admin" ISE_PASS="$(gopass show -o ise/admin)"
curl -sk "https://$ISE_HOST:{ise-ers-port}/ers/config/identitygroup" \
-H "Accept: application/json" -u "$ISE_USER:$ISE_PASS" | jq '.SearchResult.resources[].name'
curl -sk "https://$ISE_HOST:{ise-ers-port}/ers/config/endpoint?filter=mac.EQ.AA:BB:CC:DD:EE:FF" \
-H "Accept: application/json" -u "$ISE_USER:$ISE_PASS" | jq '.SearchResult.total'
Certificate inspection
openssl x509 -in {cert-dir}/client.pem -text -noout | head -30
openssl x509 -in {cert-dir}/client.pem -enddate -noout
Network diagnostics
ss -tlnp | grep -E ':{port-https}|:{port-ssh}|:{port-ldaps}'
nc -zv {ise-ip} {ise-ers-port}
dig {ise-hostname} +short
ISE eval rotation — backup & restore
# SSH to ISE
ssh admin@ise-02.inside.domusdigitalis.dev
# Verify NAS repo
show repository nas-01
# Get encryption key (on workstation)
dsource d000 dev/storage
echo $ISE_BACKUP_KEY
# Run backup
backup pre-rotation-2026-06 repository nas-01 ise-config encryption-key plain <KEY>
ssh admin@ise-02.inside.domusdigitalis.dev
show repository nas-01
configure terminal
repository nas-01
url nfs://10.50.1.70:/volume1/ise_backups
exit
restore <backup-filename> repository nas-01 encryption-key plain <KEY>
VyOS — VRRP & VLAN inspection
show vrrp
show configuration commands | grep vrrp | grep 'address'
show configuration commands | grep 'firewall zone' | grep 'member'
show dhcp server leases
show arp
show interfaces
CUPS printing — validation & setup
command -v lpstat && echo "CUPS present" || echo "CUPS not installed"
lpstat -r # scheduler running?
lpstat -p -d # printers + default
sudo systemctl enable --now cups # start + persist
lpinfo -v # available backends/URIs
lpinfo -m | grep -i <brand> # available drivers
sudo lpadmin -p <name> -v <uri> -m everywhere -E
lpoptions -d <name>
lp file.pdf # default printer
lp -d <name> -o sides=two-sided-long-edge file.pdf
PowerShell (from zsh)
All PowerShell commands run inside pwsh -NoLogo -Command '…' from zsh. Running them bare fails — zsh interprets $, |, () as shell syntax.
|
Process management
pwsh -NoLogo -Command 'Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 5 ProcessName, Id,
@{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} | Format-Table'
pwsh -NoLogo -Command 'Get-Process | Where-Object {$_.ProcessName -like "*teams*"} | Stop-Process'
pwsh -NoLogo -Command 'Start-Process "ms-teams"'
Export to JSON (pipe to jq)
pwsh -NoLogo -Command 'Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 5 ProcessName, Id,
@{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} | ConvertTo-Json' | jq '.'
Never pipe Format-Table into ConvertTo-Json — it produces layout metadata, not data. Select-Object first, then ConvertTo-Json.
|
Wi-Fi management (netsh)
netsh wlan disconnect interface="Wi-Fi"
netsh wlan show networks mode=bssid
netsh wlan connect name="CHLA-Remote" interface="Wi-Fi"
SSH from PowerShell
ssh evan@modestus-razer.inside.domusdigitalis.dev
WSL ↔ Windows — Cross-Environment Commands
From zsh (WSL) — control Windows
pwsh -NoLogo -Command 'Get-Date'
pwsh -NoLogo -Command "$(cat <<'PS'
$procs = Get-Process | Where-Object { $_.WorkingSet64 -gt 100MB }
$procs | Sort-Object WorkingSet64 -Descending |
Select-Object ProcessName, Id, @{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} |
Format-Table -AutoSize
PS
)"
# Open in default Windows app
wslview /mnt/c/Users/erosado/Documents/report.pdf
# Open Explorer to current WSL directory
explorer.exe .
# Open specific Windows path
explorer.exe 'C:\Users\erosado\Downloads'
# Pipe anything to Windows clipboard
cat file.txt | clip.exe
# Copy a command's output
pwsh -NoLogo -Command 'Get-TransportRule | Format-List Name, State' | clip.exe
# Windows C: drive is at /mnt/c
ls /mnt/c/Users/erosado/Downloads/
# Copy from Windows to WSL
cp /mnt/c/Users/erosado/Downloads/report.pdf ~/atelier/
# Watch a Windows directory for new files
find /mnt/c/Users/erosado/Downloads -maxdepth 1 -mmin -5 -type f -printf '%T+ %p\n' | sort -r
From PowerShell — control WSL
wsl -e bash -c 'grep -rn "Ghost-Sender" ~/atelier/_bibliotheca/domus-captures/docs/'
$result = wsl -e bash -c 'git -C ~/atelier/_bibliotheca/domus-captures log --oneline -5'
$result
Process Management — Windows Side
pwsh -NoLogo -Command '
Get-Process | Sort-Object WorkingSet64 -Descending |
Select-Object -First 20 ProcessName, Id,
@{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}},
@{N="CPU(s)";E={[math]::Round($_.CPU,1)}},
@{N="Handles";E={$_.HandleCount}} |
Format-Table -AutoSize'
pwsh -NoLogo -Command 'Get-Process | Where-Object { $_.ProcessName -like "*teams*" } |
Select-Object ProcessName, Id, @{N="MB";E={[math]::Round($_.WorkingSet64/1MB)}} |
Format-Table -AutoSize'
pwsh -NoLogo -Command 'Stop-Process -Name "Teams" -Force -ErrorAction SilentlyContinue'
pwsh -NoLogo -Command 'Stop-Process -Id 12345 -Force'
pwsh -NoLogo -Command 'Get-NetTCPConnection -State Listen |
Select-Object LocalAddress, LocalPort, OwningProcess,
@{N="Process";E={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} |
Sort-Object LocalPort | Format-Table -AutoSize'
pwsh -NoLogo -Command 'Get-NetTCPConnection -LocalPort 8080 -ErrorAction SilentlyContinue |
Select-Object LocalAddress, LocalPort, RemoteAddress, State,
@{N="Process";E={(Get-Process -Id $_.OwningProcess).ProcessName}}'
Services — Windows Side
pwsh -NoLogo -Command 'Get-Service | Where-Object { $_.Status -eq "Running" } |
Sort-Object DisplayName | Format-Table Name, DisplayName, Status -AutoSize'
pwsh -NoLogo -Command 'Get-Service -Name "WinRM" | Format-List Name, DisplayName, Status, StartType'
Restart-Service -Name "WinRM" -Force
System Info — Quick Health from zsh
pwsh -NoLogo -Command '
Write-Host "=== Windows System ===" -ForegroundColor Cyan
Write-Host "Hostname: $env:COMPUTERNAME"
Write-Host "User: $env:USERNAME"
Write-Host "OS: $((Get-CimInstance Win32_OperatingSystem).Caption)"
Write-Host "Uptime: $((Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime)"
Write-Host "RAM: $([math]::Round((Get-CimInstance Win32_OperatingSystem).TotalVisibleMemorySize/1MB))GB total, $([math]::Round((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory/1MB))GB free"
Write-Host "CPU: $((Get-CimInstance Win32_Processor).Name)"
Write-Host "Disk C: $([math]::Round((Get-PSDrive C).Free/1GB))GB free of $([math]::Round(((Get-PSDrive C).Used + (Get-PSDrive C).Free)/1GB))GB"'
pwsh -NoLogo -Command 'Get-PSDrive -PSProvider FileSystem |
Select-Object Name, @{N="Used(GB)";E={[math]::Round($_.Used/1GB,1)}},
@{N="Free(GB)";E={[math]::Round($_.Free/1GB,1)}},
@{N="Total(GB)";E={[math]::Round(($_.Used+$_.Free)/1GB,1)}} |
Format-Table -AutoSize'
Exchange Online — Connect from zsh
pwsh -NoLogo -Command 'Connect-ExchangeOnline -UserPrincipalName erosado@chla.usc.edu'
MFA prompt opens in the Windows browser. After auth, the session persists in the pwsh process. For multi-command sessions, start pwsh interactively instead of one-shot commands.
|
pwsh -NoLogo
# Then inside pwsh:
# Connect-ExchangeOnline
# Get-TransportRule | Format-List Name, State
# exit
File Transfer Patterns
# WSL → Windows Downloads
cp ~/atelier/_bibliotheca/domus-captures/output/report.pdf /mnt/c/Users/erosado/Downloads/
# Windows → WSL (glob)
cp /mnt/c/Users/erosado/Downloads/*.{png,pdf,jpg} ~/atelier/_staging/
# Bulk move with null safety
find /mnt/c/Users/erosado/Downloads -maxdepth 1 -name '*.pdf' -mmin -60 -print0 |
xargs -0 -I{} cp {} ~/atelier/_staging/
inotifywait -m /mnt/c/Users/erosado/Downloads -e create -e moved_to |
awk '{printf "%s %s\n", strftime("%H:%M:%S"), $3}'
inotifywait requires inotify-tools. Install with sudo pacman -S inotify-tools if not present.
|
Security & Encryption
View encrypted files without writing to disk
age --decrypt -i ~/.secrets/.metadata/keys/master.age.key \
data/d001/projects/mandiant-remediation/findings-status-2026-04-16.adoc.age \
| bat --language asciidoc --file-name "findings-status-2026-04-16.adoc"
Batch re-encrypt — brace expansion + loop
for f in data/d001/projects/mandiant-remediation/{findings-status,guest-acl-update,siem-report}-2026-04-16.adoc; do
rm -f "${f}.age" && echo y | encrypt-file "$f"
done
Always rm -f the .age first. If you skip it, encrypt-file prompts about overwrite and may only delete the plaintext without re-encrypting.
|
Detect stale plaintext — files needing re-encryption
for f in data/d001/projects/*/*.adoc; do
age="${f}.age"
if [ -f "$f" ] && [ -f "$age" ]; then
pt_mod=$(/usr/bin/stat -c'%Y' "$f")
age_mod=$(/usr/bin/stat -c'%Y' "$age")
[ "$pt_mod" -gt "$age_mod" ] && echo "STALE: $f"
fi
done
Secure delete — shred for sensitive plaintext
shred -u data/d001/projects/mandiant-remediation/man-report.txt
On SSD/NVMe, shred is less effective (wear leveling), but better than rm which only removes the directory entry.
|
Pre-push audit — find all unencrypted project files
find data/d001/projects -type f ! -name '*.age' ! -name 'README.adoc' ! -name '.gitkeep' ! -name '*.py' | sort
System & Infrastructure
PipeWire audio validation
wpctl status # PipeWire status
pactl list sinks short # list audio sinks
pw-play /usr/share/sounds/freedesktop/stereo/bell.oga # test default sink
journalctl -b --grep='sof|cs35l56' --no-pager | tail -20 # kernel audio firmware
cat /proc/asound/cards # ALSA sound cards
gopass — personal document management
gopass-personal-docs # interactive entry creation
gopass-query bills # list recurring bills with totals
gopass-query storage # list storage units with gate codes
gopass-query export bills # export category to JSON
Makefile — daily workflow
make new-day # create today's worklog + update attributes
make serve # build + local server (port 8000)
make # build only
make sync-nav # sync worklog nav entries
make update-index # rebuild monthly index
KVM — VM & ISO management
ssh kvm-01 "sudo virsh list --all"
ssh kvm-02 "sudo virsh list --all"
ssh kvm-01 "ls -lh /mnt/nas/isos/*[Ii][Ss][Ee]* /var/lib/libvirt/images/*[Ii][Ss][Ee]* /mnt/onboard-ssd/isos/*[Ii][Ss][Ee]* 2>/dev/null"
ssh kvm-02 "ls -lh /mnt/nas/isos/*[Ii][Ss][Ee]* /mnt/ssd/libvirt/images/*[Ii][Ss][Ee]* 2>/dev/null"
sudo virsh console <vm-name> # Escape: Ctrl+]
ssh kvm-01 "mount | grep nas; ls /mnt/"
Per-project file dashboard
for d in data/d001/projects/*/; do
total=$(find "$d" -type f | wc -l)
plain=$(find "$d" -type f ! -name '*.age' ! -name 'README.adoc' ! -name '.gitkeep' ! -name '*.py' | wc -l)
echo "$(basename "$d") | ${total} files | ${plain} plaintext"
done
USB-C / Thunderbolt Charging Diagnostics
{
echo "=== Power Supply ==="
cat /sys/class/power_supply/*/status
echo ""
cat /sys/class/power_supply/*/type
echo ""
echo "=== UPower ==="
upower -d | grep -E 'state|percentage|energy-rate|voltage'
echo ""
echo "=== dmesg (typec/thunderbolt/PD) ==="
sudo dmesg | grep -iE 'typec|thunderbolt|ucsi|PD|power.delivery|charging' | tail -20
echo ""
echo "=== Pacman log (kernel/typec) ==="
grep -iE 'thunderbolt|typec|ucsi|^.*upgraded linux ' /var/log/pacman.log | tail -20
} | tee /tmp/INC-$(date +%F)-usbc-charging.txt
Pattern: { } groups commands into a single stdout stream. tee writes to file AND displays on screen. Reusable for any multi-command evidence capture — change the commands inside, keep the structure.