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]"
|
||||
|
||||
Reference in New Issue
Block a user