cascades: CA unblock + Phase B buildout + onboard-tenant.sh CA Admin backfill
Day-long session unblocking the Cascades CA reconciliation that was paused on
the Tenant Admin SP directory-role gap. Discovered Microsoft also tightened
the OAuth scope for /identity/conditionalAccess/* reads (Policy.Read.All now
required, Policy.ReadWrite.ConditionalAccess no longer accepted for reads).
Patched Tenant Admin manifest accordingly and re-consented in Cascades.
Phase B Intune state turned out to be far more built than the 4/20 log
suggested -- compliance policy, Wi-Fi, device restrictions, both SDM app
configs (Authenticator + Teams), and 7 of 8 apps were already deployed and
assigned. PATCHed device restrictions to block camera/Bluetooth/roaming
and enabled Managed Home Screen multi-app kiosk (ALIS + Teams visible,
10-min auto-signout). PATCHed Cascades named location to add primary WAN
(184.191.143.62/32). Howard added Outlook from Managed Play; SMB encryption
enabled on \CS-SERVER\homes.
CA bypass design corrected -- original §5 plan in user-account-rollout-plan.md
called for "block off-site + MFA on-site" which doesn't match the actual goal
of bypass when network + device assurance present. Reshaped to three policies
that produce on-site-compliant = password only, anything else = MFA or block.
onboard-tenant.sh patched to:
1. Backfill Policy.Read.All on Tenant Admin SP if missing (idempotent --
for tenants consented before the 2026-04-29 manifest update).
2. Assign Conditional Access Administrator directory role to Tenant Admin
SP at onboard time. Mirrors the Exchange Operator fix Mike landed in
16f95e8.
Validated with --dry-run against Cascades. Customer-facing tenants already
onboarded should be re-run with this script to backfill both items.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ DEFENDER_APP_ID="fc780465-2017-40d4-a0c5-307022471b92"
|
||||
ROLE_EXCHANGE_ADMIN="29232cdf-9323-42fd-ade2-1d097af3e4de"
|
||||
ROLE_USER_ADMIN="fe930be7-5e62-47db-91af-98c3a49a38b1"
|
||||
ROLE_AUTH_ADMIN="c4e39bd9-1100-46d3-8c65-fb160da0071f"
|
||||
ROLE_CA_ADMIN="b1be1c3e-b65d-4f19-8427-f6fa0d97feb9"
|
||||
|
||||
# ── Graph appRole GUIDs per app (from requiredResourceAccess in home tenant) ──
|
||||
# Security Investigator — Graph
|
||||
@@ -435,6 +436,29 @@ if [[ -z "$GRAPH_SP_OID" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Step 3.5: Backfill Tenant Admin SP — Policy.Read.All ─────────────────────
|
||||
# Microsoft tightened LIST /identity/conditionalAccess/* to require Policy.Read.All
|
||||
# (Policy.ReadWrite.ConditionalAccess no longer accepted for reads). Added to manifest
|
||||
# 2026-04-29; tenants consented before that need backfill.
|
||||
POLICY_READ_ALL_ID="246dd0d5-5bd0-4def-940b-0421030a5b68"
|
||||
HAS_POLICY_READ=$(curl -s --max-time 15 \
|
||||
-H "Authorization: Bearer $TENANT_ADMIN_TOKEN" \
|
||||
"https://graph.microsoft.com/v1.0/servicePrincipals/$TA_SP_OID/appRoleAssignments" \
|
||||
| jq --arg rid "$POLICY_READ_ALL_ID" '[.value[]? | select(.appRoleId == $rid)] | length > 0')
|
||||
|
||||
if [[ "$HAS_POLICY_READ" != "true" ]]; then
|
||||
echo ""
|
||||
echo "[INFO] Tenant Admin SP missing Policy.Read.All — backfilling..."
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo " [DRY-RUN] Would grant Policy.Read.All ($POLICY_READ_ALL_ID) to Tenant Admin SP"
|
||||
elif grant_app_role "$TENANT_ADMIN_TOKEN" "$TA_SP_OID" "$GRAPH_SP_OID" "$POLICY_READ_ALL_ID"; then
|
||||
echo " [OK] Policy.Read.All granted to Tenant Admin SP"
|
||||
else
|
||||
echo " [WARNING] Could not grant Policy.Read.All programmatically — customer re-consent may be needed"
|
||||
echo " Re-consent URL: ${CONSENT_BASE}/${TENANT_ID}/adminconsent?client_id=${APP_TENANT_ADMIN}&redirect_uri=${CONSENT_REDIRECT}&prompt=consent"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Step 4: Programmatic consent — create SPs + grant appRoleAssignments ──────
|
||||
echo ""
|
||||
echo "[INFO] Consenting app suite in tenant (programmatic — no customer click needed)..."
|
||||
@@ -522,6 +546,36 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# Tenant Admin -> Conditional Access Administrator
|
||||
# Required because Microsoft Graph evaluates SP membership in CA-relevant directory
|
||||
# roles in addition to OAuth scopes when calling /identity/conditionalAccess/* endpoints.
|
||||
# Without this role, the Tenant Admin SP gets 403 even with Policy.ReadWrite.ConditionalAccess
|
||||
# in its token. Discovered Cascades 2026-04-28; backfilled here so future tenants don't trip on it.
|
||||
echo ""
|
||||
echo "[CHECK] Tenant Admin SP: $TA_SP_OID"
|
||||
# Activate CA Admin role in tenant first (idempotent — returns 400 if already activated)
|
||||
ACTIVATE_RESP=$(curl -s --max-time 15 \
|
||||
-H "Authorization: Bearer $TENANT_ADMIN_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
"https://graph.microsoft.com/v1.0/directoryRoles" \
|
||||
-d "{\"roleTemplateId\":\"$ROLE_CA_ADMIN\"}" 2>/dev/null || true)
|
||||
# Don't fail on activation — directly-assigned role POST handles the case where already active
|
||||
|
||||
IS_CA=$(role_assigned "$TENANT_ADMIN_TOKEN" "$TA_SP_OID" "$ROLE_CA_ADMIN")
|
||||
if [[ "$IS_CA" == "true" ]]; then
|
||||
echo " Conditional Access Administrator: PRESENT"
|
||||
STATUS_MAP["Tenant Admin:Conditional Access Administrator"]="OK"
|
||||
else
|
||||
echo " Conditional Access Administrator: MISSING -> ASSIGNING..."
|
||||
if assign_role "$TENANT_ADMIN_TOKEN" "$TA_SP_OID" "$ROLE_CA_ADMIN" "Conditional Access Administrator"; then
|
||||
STATUS_MAP["Tenant Admin:Conditional Access Administrator"]=$( [[ "$DRY_RUN" == "true" ]] && echo "DRY-RUN" || echo "ASSIGNED" )
|
||||
else
|
||||
STATUS_MAP["Tenant Admin:Conditional Access Administrator"]="ERROR"
|
||||
PARTIAL_FAILURE=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# User Manager -> User Administrator + Authentication Administrator
|
||||
if [[ -z "$USER_MGR_OID" ]]; then
|
||||
echo "[WARNING] User Manager SP still not found after consent attempt"
|
||||
@@ -573,6 +627,10 @@ else
|
||||
fi
|
||||
|
||||
echo "SP roles status:"
|
||||
TA_CA="${STATUS_MAP["Tenant Admin:Conditional Access Administrator"]:-SKIPPED}"
|
||||
echo " Tenant Admin:"
|
||||
printf " Conditional Access Administrator: %s\n" "[$TA_CA]"
|
||||
|
||||
SEC_EXCH="${STATUS_MAP["Security Investigator:Exchange Administrator"]:-SKIPPED}"
|
||||
echo " Security Investigator:"
|
||||
printf " Exchange Administrator: %s\n" "[$SEC_EXCH]"
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
# 2026-04-29 — Cascades CA unblock + Phase B Intune buildout + bypass pilot prep
|
||||
|
||||
## User
|
||||
- **User:** Howard Enos (howard)
|
||||
- **Machine:** HOWARD-HOME
|
||||
- **Role:** tech
|
||||
- **Session span:** 2026-04-28 evening into 2026-04-29 early morning (continuous, ~7 hours)
|
||||
|
||||
## Resume point (READ THIS FIRST when picking back up)
|
||||
|
||||
We are on the **Cascades caregiver shared-phone bypass pilot**, Path B (cloud-only test user). Phase B Intune is fully built. Cascades named location has both WANs trusted. Phase A "Track" plan reshaped — original §5 design did not match Howard's actual goal of MFA bypass on trusted-network + compliant-device.
|
||||
|
||||
**Where we paused:** awaiting Howard's next decision after Phase B is fully populated and one phone successfully completes a real user sign-in flow to flip compliant. Outlook is being added by Howard via Managed Play.
|
||||
|
||||
### What's still to do, in order
|
||||
|
||||
1. Howard finishes Outlook add (Managed Play), then sync and tell me. I'll assign it + add to MHS visible apps (currently ALIS + Teams; will become ALIS + Teams + Outlook).
|
||||
2. Howard adds LinkRx + Helpany web apps in Intune portal when he has the URLs.
|
||||
3. Pilot user + cloud group (Path B):
|
||||
- Create cloud-only Entra group `SG-Caregivers-Pilot`
|
||||
- Create cloud-only test user (or use an existing cloud user; Howard's `howard.enos` cloud account doesn't exist yet because Entra Connect is in staging)
|
||||
- License test user with Business Premium (one of 34 spare seats)
|
||||
- Add user to `SG-Caregivers-Pilot`
|
||||
4. **Break-glass admin** — design + create. Need: cloud-only Global Admin, FIDO2 (Howard has 1 YubiKey), excluded from all CA, vaulted password, sign-in alerts. Pre-flight before CA changes.
|
||||
5. New CA bypass policies (corrected design — see §"CA design correction" below):
|
||||
- DELETE `Require multifactor authentication for all users` (existing)
|
||||
- CREATE `CSC - Require MFA off Cascades network` (all users, location not Cascades → require MFA)
|
||||
- CREATE `CSC - Require MFA on non-compliant device` (all users, device.isCompliant -eq False → require MFA)
|
||||
- CREATE `CSC - Block off-network unless allow-listed` (all except `SG-External-Signin-Allowed` and break-glass, location not Cascades → block)
|
||||
- CREATE `CSC - Sign-in frequency 8h` session control on `SG-Caregivers-Pilot` (and later SG-Caregivers)
|
||||
6. Stage all new CA in **Report-only** for 24-48h, review logs, fix gaps, then flip to On.
|
||||
7. Phone enrollment dry-run with the pilot user → expect no MFA on Cascades Wi-Fi + compliant device, expect block off-network.
|
||||
|
||||
### Decisions still pending
|
||||
|
||||
- Pilot user identity (create new `pilot.test@` vs use existing cloud user)
|
||||
- Audit retention: deferred — Azure pay-as-you-go subscription still needs Howard or Mike billing call
|
||||
- Whether to backfill `onboard-tenant.sh` against other already-onboarded tenants for the new CA Admin + Policy.Read.All (script now handles this idempotently when re-run; just needs a re-run per tenant)
|
||||
|
||||
---
|
||||
|
||||
## Session Summary
|
||||
|
||||
Picked up from the 2026-04-28 morning save where the Tenant Admin SP had no directory role in Cascades and every `/identity/conditionalAccess/*` call returned 403. Howard granted the Conditional Access Administrator role via PIM (Active assignment, permanent, Path B from yesterday's options). After that landed, CA endpoints **still** returned 403 with the same scope-missing error. Investigation showed Microsoft tightened `LIST /identity/conditionalAccess/namedLocations` to require `Policy.Read.All` as the application permission — `Policy.ReadWrite.ConditionalAccess` (which the SP carried) is no longer accepted for reads on this endpoint. Patched the Tenant Admin app manifest in ACG home to add `Policy.Read.All`, granted the appRoleAssignment in ACG home, Howard re-consented in Cascades. CA reads then worked.
|
||||
|
||||
Reading the CA state revealed the existing `Require multifactor authentication for all users` policy is in `enabled` state (not Report-only), which makes the original Track A "Gate A7 — flip from Report-only to On" obsolete. Howard re-stated the pilot goal in plain language: caregivers signing into shared phones on Cascades Wi-Fi with a compliant Intune-managed device should bypass MFA entirely; off-site they should be blocked (no personal device for MFA fallback exists). Eventually this rolls out to all users at Cascades. The existing `user-account-rollout-plan.md §5` design said "Block sign-in unless from Cascades + MFA" — meaning even on-site users get MFA prompted — which doesn't match the goal. Reshaped the CA design to three policies that together produce on-site-compliant = password only, anything else = MFA or block.
|
||||
|
||||
In parallel, discovered the `ComputerGuru - Intune Manager` app was still single-tenant, so it couldn't be consented in Cascades. PATCHed `signInAudience` to `AzureADMultipleOrgs` in ACG home, Howard re-consented in Cascades, then queried Intune state. **Phase B is far more built than the 4/20 session log indicated** — at some point between 4/20 and 4/21 someone (likely Howard) finished it. The compliance policy (`CSC - Android Compliance`, type `androidDeviceOwnerCompliancePolicy`), Wi-Fi config (`CSC - CSCNet Wi-Fi`), device restrictions (`CSC - Android Shared Phones Restrictions`), and both Shared Device Mode app config policies (Authenticator + Teams, both with `shared_device_mode_enabled=true`) all exist and are assigned to the `Cascades - Shared Phones` dynamic group. 7 of 8 apps are deployed and assigned (Microsoft Launcher is the only unassigned one).
|
||||
|
||||
PATCHed the Cascades named location to add `184.191.143.62/32` (primary WAN). Both WANs now `isTrusted=true`. PATCHed device restrictions to block camera, Bluetooth, and data roaming (per Howard's spec). Enabled Managed Home Screen multi-app kiosk mode with ALIS + Microsoft Teams as visible apps, 10-min idle auto-signout with 1-min warning, no MHS-level PIN (relies on the device PIN already required by compliance). Howard added Microsoft Outlook from Managed Play in the Intune portal. Walked through Wave 0 HIPAA pre-flight: HIPAA BAA is automatic via Microsoft Online Services Terms (no separate signature needed); SMB encryption on `\\CS-SERVER\homes` was False, Howard ran `Set-SmbShare -Name homes -EncryptData $true -Force` to flip it; Diagnostic Settings deferred (will set up Azure pay-as-you-go later for Log Analytics workspace). After the session, ran `/sync` which surfaced Mike's note from earlier in the day approving Path A for the CA fix — superseded by Path B, which Howard already executed. Patched `onboard-tenant.sh` to programmatically assign Conditional Access Administrator role to the Tenant Admin SP and backfill `Policy.Read.All` on existing tenants where the manifest update post-dates their consent.
|
||||
|
||||
## Key Decisions
|
||||
|
||||
- **Path B (manual portal PIM assignment) for the CA Admin role** instead of the Graph-API Path A Mike approved. Both achieve the same end state. Path B was faster on the day; Path A is now the backfilled default in `onboard-tenant.sh`.
|
||||
- **Patch `Policy.Read.All` into Tenant Admin manifest**. Microsoft tightened `LIST /identity/conditionalAccess/*` endpoints to require `Policy.Read.All` as the application permission. `Policy.ReadWrite.ConditionalAccess` (legacy) is no longer accepted for reads. New tenants get this from the manifest at consent time; existing tenants get it programmatically via the patched onboard script.
|
||||
- **Reshape the CA bypass design**. Replace the existing all-users-MFA policy with three policies that together produce: on-site + compliant = password only, off-site or non-compliant = MFA or block. The original Track A §5 design conflated "block off-site" with "require MFA on-site," which doesn't match the actual goal of bypassing MFA when network + device assurance are present.
|
||||
- **Path B for the pilot** (cloud-only test user) instead of Path A (exit Entra Connect staging first). Faster path to a meaningful test; the production rollout will use the synced `SG-Caregivers` group once staging exits, but that's a separate operation gated on the AD prereq cleanup (Wave 0.5 G1 from `user-account-rollout-plan.md`).
|
||||
- **Sign-in frequency: 8 hours** for caregivers (revised from initial "every hour" — caregivers re-auth at start of shift and work through without re-prompts; HIPAA-defensible as one strong factor among three).
|
||||
- **Off-network behavior: block for caregivers, MFA for office allow-list**. Caregivers have no personal device for MFA, so off-network access is denied entirely. Office staff in `SG-External-Signin-Allowed` get MFA off-network without compliant-device requirement (Howard's call — simpler, less brittle).
|
||||
- **Path X (Managed Home Screen multi-app kiosk)** instead of standard Android launcher. Provides keyboard lockdown, restricted app access, and HIPAA-defensible least-privilege UI for caregivers.
|
||||
- **MHS auto-signout: 10 min idle, 1 min warning**. Standard healthcare convention for shared-device session lifetime, separate from the device's 1-min screen lock and the 8-hour CA sign-in frequency.
|
||||
- **Microsoft Launcher: leave unassigned**. With MHS as the kiosk launcher, the Microsoft Launcher app is redundant.
|
||||
- **Intune Manager app: convert to multi-tenant**. Single-tenant gave AADSTS700016 when consenting in Cascades. PATCH on `signInAudience` to `AzureADMultipleOrgs` was the cleaner fix vs. adding DeviceManagement scopes to Tenant Admin.
|
||||
- **Audit retention: defer to later**. Microsoft default 30-day Entra retention plus Purview 180-day retention covers near-term forensics. Long-term 6-year HIPAA retention will be added via Azure Storage Account or Log Analytics workspace once an Azure pay-as-you-go subscription is set up. Cost estimate: Storage Account ~$0.20/mo at year-6 max; LAW with live monitoring ~$0.50-1/mo.
|
||||
- **HIPAA BAA: confirmed automatic via Microsoft Online Services Terms**. The old Admin Center checkbox is gone. Business Premium covers HIPAA-eligible services by default.
|
||||
|
||||
## Problems Encountered
|
||||
|
||||
- **CA endpoints 403 after CA Admin role assigned** — root cause not directory role missing (already fixed) but Microsoft tightening the OAuth scope. Fixed by patching `Policy.Read.All` into Tenant Admin manifest and re-consenting in Cascades.
|
||||
- **Intune Manager app single-tenant** — couldn't be consented in Cascades; got AADSTS700016. Fixed by PATCH on `signInAudience` to `AzureADMultipleOrgs` in ACG home.
|
||||
- **Direct Graph add of Managed Play apps not supported** — POST to `/v1.0/deviceAppManagement/mobileApps` with `androidManagedStoreApp` returns 400 because Managed Play apps must be approved through Google's Play console iframe. Howard added apps via UI; assignments + config policies done via Graph after.
|
||||
- **Graph v1.0 mobileApps endpoint hides Managed Play apps** — initially saw only ALIS in the inventory. Beta endpoint returned all 8 apps. Switched all subsequent app queries to beta.
|
||||
- **Kiosk app schema discovery iteration** — `kioskModeApps` requires items typed as `microsoft.graph.appListItem` with required `name` field. Two failed attempts (`androidDeviceOwnerKioskModeManagedAppItem`, `androidDeviceOwnerKioskModeAppPositionItem`) before landing on the correct shape.
|
||||
- **Phones still noncompliant after all policy work** — expected. Compliance requires a real user sign-in flow to set the 6-digit PIN and trigger Authenticator SDM registration. Will resolve when the pilot user signs into one of the test phones.
|
||||
- **Existing all-users-MFA policy is enforced, not Report-only** — invalidates Gate A7 from Track A. Reshaped CA design described above.
|
||||
- **Diagnostic Settings page requires Azure subscription** — no Azure subscription on Cascades yet. Audit retention plan deferred until subscription decision (Cascades direct billing vs ACG bundles it into MSP invoice).
|
||||
- **Sync notification surfaced Mike's note (now superseded)** — Mike approved Path A while we were already doing Path B. Net result is the same; need a follow-up note to Mike to close the loop.
|
||||
|
||||
---
|
||||
|
||||
## Configuration Changes
|
||||
|
||||
### ComputerGuru ACG home tenant (`ce61461e-81a0-4c84-bb4a-7b354a9a356d`)
|
||||
|
||||
- **`ComputerGuru - Tenant Admin` app manifest:** added `Policy.Read.All` (`246dd0d5-5bd0-4def-940b-0421030a5b68`) to `requiredResourceAccess`. Granted appRoleAssignment in home tenant.
|
||||
- **`ComputerGuru - Intune Manager` app manifest:** flipped `signInAudience` from `AzureADMyOrg` to `AzureADMultipleOrgs`.
|
||||
|
||||
### Cascades tenant (`207fa277-e9d8-4eb7-ada1-1064d2221498`)
|
||||
|
||||
- **Tenant Admin SP** (`a5fa89a9-b735-4e10-b664-f042e265d137`) now holds **Conditional Access Administrator** directory role (PIM-managed, Active assignment, permanent, no expiration). Status `Provisioned` since 2026-04-28T18:45:50Z.
|
||||
- **Tenant Admin SP** now also has `Policy.Read.All` appRoleAssignment (added via re-consent).
|
||||
- **Intune Manager SP** consented + provisioned in Cascades after multi-tenant flip.
|
||||
- **Cascades named location** (id `061c6b06-b980-40de-bff9-6a50a4071f6f`):
|
||||
- Was: `72.211.21.217/32` only (secondary WAN)
|
||||
- Now: `72.211.21.217/32` + `184.191.143.62/32` (both WANs)
|
||||
- `isTrusted: true`
|
||||
- **`CSC - Android Shared Phones Restrictions`** (id `070a76c2-a8c3-4f7f-9ba7-1f4ac5084184`) — version bumped 2 → 4:
|
||||
- `cameraBlocked: true`
|
||||
- `bluetoothBlockConfiguration: true`
|
||||
- `dataRoamingBlocked: true`
|
||||
- `kioskModeUseManagedHomeScreenApp: "multiAppMode"`
|
||||
- `kioskModeApps: [ALIS, Microsoft Teams]`
|
||||
- `kioskModeManagedHomeScreenAutoSignout: true`
|
||||
- `kioskModeManagedHomeScreenInactiveSignOutDelayInSeconds: 600` (10 min)
|
||||
- `kioskModeManagedHomeScreenInactiveSignOutNoticeInSeconds: 60` (1 min warning)
|
||||
- `kioskModeManagedHomeScreenPinRequired: false`
|
||||
- **Microsoft Outlook** added to mobile apps via Intune portal (Managed Play). Pending sync, then assignment + add to MHS app list.
|
||||
|
||||
### CS-SERVER (Cascades on-prem DC, `192.168.2.254`)
|
||||
|
||||
- `\\CS-SERVER\homes` SMB share: `EncryptData` was `False`, Howard ran `Set-SmbShare -Name homes -EncryptData $true -Force` to flip it.
|
||||
|
||||
### Repo
|
||||
|
||||
- **`.claude/skills/remediation-tool/scripts/onboard-tenant.sh`** patched:
|
||||
- Added `ROLE_CA_ADMIN` constant (`b1be1c3e-b65d-4f19-8427-f6fa0d97feb9`)
|
||||
- Added Tenant Admin SP → Conditional Access Administrator assignment block (with role activation, idempotent)
|
||||
- Added `Policy.Read.All` backfill block before the consent_app loop (idempotent — only grants if missing)
|
||||
- Added Tenant Admin row to the final status table
|
||||
|
||||
---
|
||||
|
||||
## Commands & Outputs
|
||||
|
||||
### Cascades named location PATCH
|
||||
|
||||
```bash
|
||||
TOKEN=$(bash .claude/skills/remediation-tool/scripts/get-token.sh \
|
||||
207fa277-e9d8-4eb7-ada1-1064d2221498 tenant-admin)
|
||||
NL_ID="061c6b06-b980-40de-bff9-6a50a4071f6f"
|
||||
curl -X PATCH \
|
||||
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||||
"https://graph.microsoft.com/v1.0/identity/conditionalAccess/namedLocations/$NL_ID" \
|
||||
-d '{
|
||||
"@odata.type": "#microsoft.graph.ipNamedLocation",
|
||||
"isTrusted": true,
|
||||
"ipRanges": [
|
||||
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "72.211.21.217/32"},
|
||||
{"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "184.191.143.62/32"}
|
||||
]
|
||||
}'
|
||||
# HTTP 204
|
||||
```
|
||||
|
||||
### Tenant Admin manifest patch (Policy.Read.All)
|
||||
|
||||
Inline bash (mirrored Mike's `patch-tenant-admin-manifest.sh` pattern):
|
||||
|
||||
```bash
|
||||
POLICY_READ_ALL_ID="246dd0d5-5bd0-4def-940b-0421030a5b68"
|
||||
# Patch requiredResourceAccess to include Policy.Read.All
|
||||
# Then grant appRoleAssignment in ACG home
|
||||
# Howard re-consented in Cascades via:
|
||||
# https://login.microsoftonline.com/207fa277-e9d8-4eb7-ada1-1064d2221498/adminconsent?client_id=709e6eed-0711-4875-9c44-2d3518c47063&redirect_uri=https://azcomputerguru.com&prompt=consent
|
||||
```
|
||||
|
||||
### Intune Manager multi-tenant flip
|
||||
|
||||
```bash
|
||||
INTUNE_APP_OBJ_ID="31017446-c01a-4775-864f-aef96ce43797"
|
||||
curl -X PATCH \
|
||||
-H "Authorization: Bearer $MGMT_TOKEN" -H "Content-Type: application/json" \
|
||||
"https://graph.microsoft.com/v1.0/applications/$INTUNE_APP_OBJ_ID" \
|
||||
-d '{"signInAudience": "AzureADMultipleOrgs"}'
|
||||
# HTTP 204 (initial verify lagged; re-verified after 8s — confirmed)
|
||||
```
|
||||
|
||||
### Device restrictions PATCHes
|
||||
|
||||
```bash
|
||||
TOKEN=$(bash .claude/skills/remediation-tool/scripts/get-token.sh \
|
||||
207fa277-e9d8-4eb7-ada1-1064d2221498 intune-manager)
|
||||
RESTRICT_ID="070a76c2-a8c3-4f7f-9ba7-1f4ac5084184"
|
||||
|
||||
# Block camera + bluetooth + roaming
|
||||
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||||
"https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations/$RESTRICT_ID" \
|
||||
-d '{
|
||||
"@odata.type": "#microsoft.graph.androidDeviceOwnerGeneralDeviceConfiguration",
|
||||
"cameraBlocked": true,
|
||||
"bluetoothBlockConfiguration": true,
|
||||
"dataRoamingBlocked": true
|
||||
}'
|
||||
|
||||
# Enable MHS multi-app kiosk
|
||||
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||||
"https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations/$RESTRICT_ID" \
|
||||
-d '{
|
||||
"@odata.type": "#microsoft.graph.androidDeviceOwnerGeneralDeviceConfiguration",
|
||||
"kioskModeUseManagedHomeScreenApp": "multiAppMode",
|
||||
"kioskModeApps": [
|
||||
{"@odata.type": "#microsoft.graph.appListItem", "name": "ALIS", "appId": "fcbf803d-ceb7-4f4e-93ed-2be1b91a05f3"},
|
||||
{"@odata.type": "#microsoft.graph.appListItem", "name": "Microsoft Teams", "appId": "0eb81676-299b-4eb2-bfd9-8be914a82f91"}
|
||||
],
|
||||
"kioskModeManagedHomeScreenAutoSignout": true,
|
||||
"kioskModeManagedHomeScreenInactiveSignOutDelayInSeconds": 600,
|
||||
"kioskModeManagedHomeScreenInactiveSignOutNoticeInSeconds": 60,
|
||||
"kioskModeManagedHomeScreenPinRequired": false
|
||||
}'
|
||||
```
|
||||
|
||||
### CS-SERVER SMB encryption
|
||||
|
||||
```powershell
|
||||
Set-SmbShare -Name homes -EncryptData $true -Force
|
||||
Get-SmbShare -Name homes | Select Name,EncryptData
|
||||
# Name EncryptData
|
||||
# ---- -----------
|
||||
# homes True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CA design correction (revised from `user-account-rollout-plan.md` §5)
|
||||
|
||||
Existing all-users MFA policy is `enabled` not Report-only. Original §5 design said "block off-site, require MFA on-site" — that conflicts with Howard's actual goal of bypass on-site + compliant. Corrected design (deferred for build until break-glass exists):
|
||||
|
||||
| # | Name | Users | Conditions | Grant |
|
||||
|---|---|---|---|---|
|
||||
| — | (DELETE) `Require multifactor authentication for all users` | — | — | — |
|
||||
| 1 | `CSC - Require MFA off Cascades network` | All users (admins still hit by separate admin policy) | Locations: include All, exclude Cascades named location | Require MFA |
|
||||
| 2 | `CSC - Require MFA on non-compliant device` | All users | Filter for devices: include `device.isCompliant -eq False` | Require MFA |
|
||||
| 3 | `CSC - Block off-network unless allow-listed` | All users **except** `SG-External-Signin-Allowed` and break-glass | Locations: include All, exclude Cascades named location | Block |
|
||||
| 4 | `CSC - Sign-in frequency 8h` (session control) | `SG-Caregivers-Pilot` (then `SG-Caregivers`) | (none — applies to all sessions for the group) | Session control: re-auth every 8 hours |
|
||||
|
||||
**Logic verification:** caregiver on Cascades + compliant phone → no policy applies → password only. Caregiver off-network → policy 3 blocks. Caregiver on Cascades + non-compliant → policy 2 → MFA (which they can't do → effectively blocked, by design). External-allowed user off-site + compliant device → policy 1 → MFA only. Office user not allow-listed off-site → policies 1 + 3 → block wins.
|
||||
|
||||
Keep existing risk-based + admin-targeted policies as defense in depth (they'll continue to add MFA on top in suspect scenarios, even when the bypass would otherwise apply — which is correct for HIPAA).
|
||||
|
||||
---
|
||||
|
||||
## Pending / Incomplete Tasks
|
||||
|
||||
### Immediate (resume here)
|
||||
- [ ] Howard finishes Outlook Managed Play sync, tells me, I assign + add to MHS app list
|
||||
- [ ] Howard adds LinkRx + Helpany web apps when URLs available
|
||||
- [ ] Pick a phone and run real user sign-in flow → verify compliance flips to compliant
|
||||
|
||||
### Path B pilot
|
||||
- [ ] Decide: create new `pilot.test@cascadestucson.com` cloud user, or use an existing cloud user
|
||||
- [ ] Create cloud-only Entra group `SG-Caregivers-Pilot`, add pilot user
|
||||
- [ ] Assign Business Premium license to pilot user
|
||||
|
||||
### Wave 0 HIPAA pre-flight (before any CA change goes live)
|
||||
- [ ] Break-glass admin: design + create. Cloud-only Global Admin, FIDO2 (1 YubiKey on hand), excluded from all CA, vaulted password, sign-in alerts to Howard + Mike
|
||||
- [ ] Audit retention 6yr: decide Cascades-billed vs ACG-billed, then create Azure pay-as-you-go subscription, Log Analytics workspace, Diagnostic Settings export
|
||||
- [ ] ALIS BAA: ask Meredith (Howard sends email)
|
||||
- [ ] Risk Analysis doc: draft via Ollama, Mike reviews
|
||||
|
||||
### CA bypass build
|
||||
- [ ] Stage 4 new policies in **Report-only**, assigned to `SG-Caregivers-Pilot` initially
|
||||
- [ ] Run pilot validation tests (the 8 from the earlier write-up)
|
||||
- [ ] After 24-48h Report-only and break-glass verified working, flip to On
|
||||
|
||||
### Production rollout (later)
|
||||
- [ ] AD prereq cleanup (G1 from §7) — renames, UPN suffix add, former-employee deletes, etc.
|
||||
- [ ] Exit Entra Connect staging mode (G3-G5)
|
||||
- [ ] Replace `SG-Caregivers-Pilot` with synced `SG-Caregivers` in CA policy targets
|
||||
- [ ] Roll Phase 3 (all users at Cascades on Cascades net + compliant device → bypass)
|
||||
- [ ] Run patched `onboard-tenant.sh` against all other already-onboarded ACG customer tenants to backfill CA Admin role + Policy.Read.All
|
||||
|
||||
### Side-track from 4/25 (still in limbo)
|
||||
- [ ] 7-mailbox shared conversion + Jodi Ramstack license removal (blocked on Exchange RBAC propagation lag)
|
||||
|
||||
---
|
||||
|
||||
## 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`
|
||||
- Intune Manager SP appId: `46986910-aa47-4e5e-b596-f65c6b485abb`
|
||||
- Cascades named location id: `061c6b06-b980-40de-bff9-6a50a4071f6f`
|
||||
- Trusted IPs (both WANs): `72.211.21.217/32` (secondary), `184.191.143.62/32` (primary)
|
||||
- Vault: `clients/cascades-tucson/wifi-cscnet.sops.yaml`, `clients/cascades-tucson/mdm-service-account.sops.yaml`
|
||||
|
||||
### Cascades Intune resources
|
||||
- Compliance policy: `CSC - Android Compliance` id `27eeaeda-8390-462e-a514-7d2a558f412c`
|
||||
- Wi-Fi config: `CSC - CSCNet Wi-Fi (WPA2-Personal)` id `b572ba54-1a04-4c40-9ac5-dd5aec444611`
|
||||
- Device restrictions: `CSC - Android Shared Phones Restrictions` id `070a76c2-a8c3-4f7f-9ba7-1f4ac5084184`
|
||||
- SDM Authenticator config: id `a1bfbda0-a36c-45e5-8844-8470f80ecc8d`
|
||||
- SDM Teams config: id `3c6a354c-1616-434b-ac81-4dad7795e67b`
|
||||
- Enrollment profile: `CSC - Android Shared Phones`, token `MVDVVDMPSHYJAGDAJOCN` (expires 2026-06-22)
|
||||
- Target group: `Cascades - Shared Phones` (cloud, dynamic, security) id `ea96f4b7-3000-45da-ab1f-ddb28f509526`, rule: `device.enrollmentProfileName -eq "CSC - Android Shared Phones"`
|
||||
|
||||
### Microsoft directory role template IDs
|
||||
- Conditional Access Administrator: `b1be1c3e-b65d-4f19-8427-f6fa0d97feb9`
|
||||
- Security Administrator: `194ae4cb-b126-40b2-bd5b-6091b380977d`
|
||||
- Global Reader: `f2ef992c-3afb-46b9-b7cf-a126ee74c451`
|
||||
|
||||
### Microsoft Graph appRole IDs
|
||||
- `Policy.Read.All`: `246dd0d5-5bd0-4def-940b-0421030a5b68`
|
||||
- `Policy.ReadWrite.ConditionalAccess`: `01c0a623-fc9b-48e9-b794-0756f8e8f067`
|
||||
- `RoleManagement.ReadWrite.Directory`: `9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8`
|
||||
|
||||
### Currently-enrolled test phones (both noncompliant pending real sign-in flow)
|
||||
- Device 1: SM-A146U, enrolled 2026-04-20, last sync 2026-04-23, isEncrypted=true, jailBroken=Unknown
|
||||
- Device 2: SM-A146U, enrolled 2026-04-23, last sync 2026-04-27, isEncrypted=true, jailBroken=Unknown
|
||||
|
||||
---
|
||||
|
||||
## Note for Mike
|
||||
|
||||
Closing the loop on your 2026-04-28 note re: Cascades CA fix.
|
||||
|
||||
**Path A vs Path B:** I executed Path B (PIM portal click as `sysadmin@cascadestucson.com`, Active assignment, permanent) before seeing your note approving Path A. Both produce the same end state — the directory role is in place on the Tenant Admin SP since 2026-04-28T18:45:50Z. No follow-up needed on the Cascades-specific assignment; it's done.
|
||||
|
||||
**The actual blocker turned out to be different:** even with the directory role, CA endpoints kept returning 403 with "required scopes are missing in the token." Microsoft tightened `LIST /identity/conditionalAccess/namedLocations` and `/policies` to require `Policy.Read.All` as the application permission — `Policy.ReadWrite.ConditionalAccess` (which the SP carried) is no longer accepted for reads. Patched the Tenant Admin app manifest to add `Policy.Read.All`, granted in ACG home, Howard re-consented in Cascades. CA reads then worked.
|
||||
|
||||
**`onboard-tenant.sh` patch — done.** Per the new tool-vs-project approval workflow, Howard approved this and I patched. Two changes:
|
||||
1. Tenant Admin SP now gets `Conditional Access Administrator` directory role assigned at onboard time (idempotent — handles already-assigned via the existing Conflict-fallback in `assign_role`).
|
||||
2. New backfill block: detects whether Tenant Admin SP has `Policy.Read.All` and grants it programmatically if missing. Required for tenants where Tenant Admin was consented before the manifest update on 2026-04-29.
|
||||
|
||||
Validated with `bash onboard-tenant.sh 207fa277-e9d8-4eb7-ada1-1064d2221498 --dry-run` — clean.
|
||||
|
||||
**Recommended backfill against other onboarded tenants:** re-run `bash onboard-tenant.sh <tenant-id>` against every customer where Tenant Admin was consented before 2026-04-29 today's manifest update. Idempotent — safe to re-run. If a tenant has a pre-existing PIM-managed CA Admin assignment (like Cascades does after Howard's PIM click), the role_assigned helper might mistakenly report MISSING because PIM-managed assignments may not appear in the legacy `roleAssignments` query. The actual `assign_role` call will hit `Conflict` and the helper treats that as success — so the script is still correct, just slightly noisy in its reporting. Worth a follow-up to teach `role_assigned` about `roleAssignmentSchedules` too, but not blocking.
|
||||
|
||||
**Phase B Intune state at Cascades:** done in full (compliance, Wi-Fi, restrictions w/ MHS multi-app kiosk, both SDM app configs, all 8 apps assigned except Microsoft Launcher). Pilot phone enrollment will validate the full flow once Outlook landed (Howard added it tonight).
|
||||
|
||||
**Wave 0 HIPAA pre-flight status:** BAA confirmed automatic via MOST. Break-glass admin not yet created — that's the next pre-flight before we touch CA policies. Audit retention deferred until Cascades-billed vs ACG-billed decision (your call when you're back). The CA bypass design needed reshaping — original §5 plan didn't actually achieve the bypass goal; corrected design is in this log under "CA design correction."
|
||||
|
||||
If anything in the corrected CA design or the onboard-tenant patch isn't to your liking, ping back and I'll adjust.
|
||||
|
||||
---
|
||||
|
||||
**Session duration:** ~7 hours continuous (2026-04-28 ~16:00 → 2026-04-29 ~07:30 PT, with breaks for portal tasks)
|
||||
**Commits during session:** 1 sync auto-commit (`ff4577c`) before this log; this log + onboard-tenant patch will land in a fresh commit after `/save`
|
||||
**Files changed in this log's commit:** 2 (`.claude/skills/remediation-tool/scripts/onboard-tenant.sh`, this session log)
|
||||
Reference in New Issue
Block a user