# 2026-04-28 — Cascades CA reconciliation blocked on SP directory role; AD-side pilot prep done

## User
- **User:** Howard Enos (howard)
- **Machine:** Howard-Home
- **Role:** tech
- **Session span:** 2026-04-28 morning (short save-point session, picking up from 2026-04-25 pause)

## Resume point (READ THIS FIRST when picking back up)

We are **mid-Track-A-Gate-2 / CA reconciliation**. Thread 1 (AD-side prep) is done. Thread 2 (CA portal/Graph reconciliation) is **blocked on a directory-role gap on the Tenant Admin SP in Cascades** — Howard needs to choose Path A or Path B before we proceed.

**Original pilot target was Monday 2026-04-27** — slipped one day, now in flight on 2026-04-28.

### Decision pending from Howard
The `ComputerGuru - Tenant Admin` SP (appId `709e6eed-0711-4875-9c44-2d3518c47063`) has **zero directory role assignments** in the Cascades tenant. Its Graph token carries `Policy.ReadWrite.ConditionalAccess`, but Microsoft Graph also requires the SP itself to hold a directory role (Conditional Access Admin / Security Admin / Global Reader) for CA endpoints. All `/identity/conditionalAccess/*` and `/policies/conditionalAccessPolicies` calls return `AccessDenied`.

Two paths offered to Howard:

- **Path A (recommended):** Assign `Conditional Access Administrator` (role template id `b1be1c3e-b65d-4f19-8427-f6fa0d97feb9`) to the Tenant Admin SP via Graph. Token has `RoleManagement.ReadWrite.Directory` — should work app-only without elevation. After it lands, also patch `.claude/skills/remediation-tool/scripts/onboard-tenant.sh` so future tenants get this assignment automatically (parallels the Exchange Admin → Exchange Operator fix Mike committed in `16f95e8`).
- **Path B:** Howard adds the role manually in the Cascades Entra portal as `admin@cascadestucson.com`: **Roles & administrators → Conditional Access Administrator → Add assignments → "ComputerGuru - Tenant Admin"**. Faster, no programmatic role-management on our side.

### Sequence after the role assignment lands
1. **CA reconciliation read-side** — pull existing `Cascades` Named Location object (currently `72.211.21.217/32` only, per 4/25 portal review) and the existing `Require multifactor authentication for all users` policy state (Report-only / On / Off).
2. **CA reconciliation write-side** — PATCH the `Cascades` Named Location to add `184.191.143.62/32`. Decide whether to add a single caregiver-specific compliant-device policy targeting `howard.enos@cascadestucson.com` UPN (Report-only) or rely on existing all-users MFA policy.
3. **Gate A3 partial — Entra App Registration for ALIS SSO.** Howard creates in Entra portal (or via Graph), captures Directory ID + App ID + Client Secret. Vault them. ALIS-side install of the SSO app is still blocked on ALIS App Store update.
4. **Gate A5 — exit Entra Connect staging.** Howard runs on CS-SERVER:
   ```powershell
   Set-ADSyncScheduler -SyncCycleEnabled $true   # if needed
   # Remove staging mode via the Entra Connect wizard (Configure → Configure staging mode → uncheck)
   Start-ADSyncSyncCycle -PolicyType Initial
   ```
   Then verify howard.enos lands in M365 as hybrid user, assign Entra ID P2 license (unassigned seat available from Sandra Fish cleanup).
5. **Gate A6 — phone enrollment.** QR from `CSC - Android Shared Phones` profile → sign in as howard.enos → verify no MFA prompt on Cascades Wi-Fi → Authenticator/Teams sign-in flow. ALIS sign-in flow can't be tested until ALIS App Store comes back and the SSO app is installed there.
6. **Gate A7 — flip CA from Report-only to On.**

---

## Session Summary

Picked up from the 4/25 save point. Howard ran Thread 1 (AD-side prep on CS-SERVER) successfully — `howard.enos` now has the proxyAddresses SMTP entry set and the password was reset to a memorable value while still in staging mode (so PHS will sync the real password to M365 instead of the random temp once we exit staging). I attempted Thread 2 (CA reconciliation read-side) via the Tenant Admin SP and discovered the SP has no directory roles assigned in the Cascades tenant, blocking all Conditional Access endpoints. Two unblock paths surfaced; awaiting Howard's pick. ALIS App Store still inaccessible.

## Key Decisions

- **Pre-stage password reset before exiting staging.** Howard reset `howard.enos` AD password to a memorable value while Entra Connect is still in staging mode. Rationale: PHS will overwrite any cloud password once staging exits, so setting the AD password first ensures the *real* password is what lands in M365 — not the random temp generated at account creation.
- **proxyAddresses convention applied.** Added `SMTP:howard.enos@cascadestucson.com` to match the G1 convention. Confirmed via `Get-ADUser -Properties proxyAddresses,memberOf`.

## Problems Encountered

- **CA endpoints all return `AccessDenied` despite token carrying `Policy.ReadWrite.ConditionalAccess`.** Confirmed by decoding the JWT — the role IS in the token. Root cause: `roleAssignments?$filter=principalId eq <sp-id>` returns empty for the Tenant Admin SP in Cascades. Microsoft Graph evaluates CA endpoints against directory-role membership of the calling SP in addition to the OAuth scope; without an applicable role assignment (CA Admin / Security Admin / Global Reader), the call is denied.

  This appears to be a gap in `onboard-tenant.sh` similar to the one Mike fixed for Exchange Operator on `16f95e8`. Recommend follow-up: extend the script to assign Conditional Access Administrator to Tenant Admin SP at onboard time.

---

## Commands & Outputs

### Thread 1 (Howard, on CS-SERVER PowerShell)
```powershell
Set-ADAccountPassword -Identity howard.enos -Reset -NewPassword (Read-Host -AsSecureString "New password")
Set-ADUser howard.enos -Add @{proxyAddresses="SMTP:howard.enos@cascadestucson.com"}
Get-ADUser howard.enos -Properties proxyAddresses,memberOf | Select Name,UserPrincipalName,proxyAddresses,memberOf
```

Output:
```
Name        UserPrincipalName              proxyAddresses                        memberOf
----        -----------------              --------------                        --------
Howard Enos howard.enos@cascadestucson.com {SMTP:howard.enos@cascadestucson.com} {CN=SG-Caregivers,OU=Groups,DC=casc...
```

(New password is what will sync to M365 via PHS once staging exits. Not vaulted — Howard knows the value.)

### Thread 2 (me, via Tenant Admin SP)

Token acquisition succeeded for Cascades tenant `207fa277-e9d8-4eb7-ada1-1064d2221498`. Decoded JWT shows roles:
```
['Policy.ReadWrite.ConditionalAccess', 'User.ReadWrite.All', 'SecurityEvents.Read.All',
 'Application.ReadWrite.All', 'Directory.ReadWrite.All', 'AppRoleAssignment.ReadWrite.All',
 'RoleManagement.ReadWrite.Directory']
```

All CA endpoints returned 403 AccessDenied:
- `GET /v1.0/identity/conditionalAccess/namedLocations`
- `GET /v1.0/identity/conditionalAccess/policies`
- `GET /v1.0/policies/conditionalAccessPolicies`

SP lookup in Cascades:
```
ObjectId: a5fa89a9-b735-4e10-b664-f042e265d137
DisplayName: ComputerGuru - Tenant Admin
```

Role assignments: `[]` (empty — confirmed root cause).

---

## Configuration Changes

### Active Directory (cascades.local)
- **`howard.enos`** password reset to memorable value (Howard knows; not vaulted by request — temp pilot account, will be reset post-pilot or replaced when production CA rollout swaps to group targeting)
- **`howard.enos`** gained `proxyAddresses=SMTP:howard.enos@cascadestucson.com`

### Cascades M365 tenant
- No changes this session (CA reconciliation blocked).

### Repo
- This session log only.

---

## Pending / Incomplete Tasks

### Immediate (this morning's blocker)
- [ ] Howard picks Path A or Path B to grant Conditional Access Administrator role to Tenant Admin SP

### Track A blockers (after the role assignment)
- [ ] Add `184.191.143.62/32` to existing `Cascades` Named Location
- [ ] Verify state of existing `Require multifactor authentication for all users` CA policy (Report-only / On / Off)
- [ ] Decide on caregiver-specific compliant-device CA targeting `howard.enos` UPN (Report-only) — or skip
- [ ] **Gate A3 partial:** create Entra App Registration for ALIS SSO, vault Directory ID + App ID + Client Secret
- [ ] **Gate A5:** exit Entra Connect staging mode → sync → verify howard.enos in M365 → assign Entra ID P2 license
- [ ] **Gate A6:** phone enrollment via QR from `CSC - Android Shared Phones` profile (ALIS sign-in test deferred until ALIS App Store back)
- [ ] **Gate A7:** flip CA from Report-only to On

### ALIS-side (still blocked on ALIS App Store update)
- [ ] Howard installs "Entra SSO" app in ALIS App Store
- [ ] Pastes Directory ID + App ID + Secret Value into ALIS Outbound Connections
- [ ] Howard updates his ALIS staff profile email to match Entra UPN
- [ ] Tests SSO link triggering for his own account only

### Follow-up onboard hardening
- [ ] **Patch `.claude/skills/remediation-tool/scripts/onboard-tenant.sh`** to assign Conditional Access Administrator role to Tenant Admin SP at onboard time (mirror of `16f95e8` Exchange Admin → Exchange Operator fix). Should also re-run against any tenant where Tenant Admin SP currently lacks the role.

### Side-track from 4/25 (still in limbo)
- 7-mailbox shared conversion + Jodi Ramstack license removal — blocked on Exchange RBAC propagation lag. Three fallback paths in `reports/2026-04-24-jeff-restore-ashley-access.md`. Not on the phone-pilot critical path.

---

## Reference Information

### Cascades tenant
- Tenant ID: `207fa277-e9d8-4eb7-ada1-1064d2221498`
- Tenant Admin SP appId: `709e6eed-0711-4875-9c44-2d3518c47063`
- Tenant Admin SP objectId in Cascades: `a5fa89a9-b735-4e10-b664-f042e265d137`
- Vault: `msp-tools/computerguru-tenant-admin.sops.yaml`

### Microsoft directory role template IDs (for the role assignment)
- Conditional Access Administrator: `b1be1c3e-b65d-4f19-8427-f6fa0d97feb9`
- Security Administrator: `194ae4cb-b126-40b2-bd5b-6091b380977d`
- Global Reader: `f2ef992c-3afb-46b9-b7cf-a126ee74c451`

### Path A — Graph role assignment payload (for when Howard greenlights)
```bash
# Activate the role first if it's not yet activated in this tenant (idempotent)
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/directoryRoles" \
  -d '{"roleTemplateId":"b1be1c3e-b65d-4f19-8427-f6fa0d97feb9"}'

# Assign Conditional Access Administrator to Tenant Admin SP
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
  -d '{
    "principalId": "a5fa89a9-b735-4e10-b664-f042e265d137",
    "roleDefinitionId": "b1be1c3e-b65d-4f19-8427-f6fa0d97feb9",
    "directoryScopeId": "/"
  }'
```

### Path B — portal click path
Entra admin center (signed in as `admin@cascadestucson.com` or `sysadmin@cascadestucson.com`) →
**Roles & administrators** → search "Conditional Access Administrator" → open → **Add assignments** → search "ComputerGuru - Tenant Admin" → Add.

---

## Note for Mike

Short save-point session this morning. Howard ran the AD-side prep (password + proxyAddresses for `howard.enos`) cleanly. Hit a directory-role gap on the Tenant Admin SP — it has zero role assignments in the Cascades tenant, so all CA Graph endpoints 403 even with `Policy.ReadWrite.ConditionalAccess` on the token. Same flavor of gap you fixed for Exchange Operator on `16f95e8`. Recommend extending `onboard-tenant.sh` to assign Conditional Access Administrator to Tenant Admin SP at onboard time. Howard is choosing between Graph-side fix (Path A) and portal-click (Path B) when he resumes; either way we should backfill the script after.
