Files
claudetools/.claude/skills/remediation-tool/references/app-suite.md
Mike Swanson 8152476ee4 remediation-tool: document the 365 app suite + build consent-audit
Root-caused the recurring '365 suite isn't documented' pain: the apps are fine (tiered by
privilege) but per-tenant consent is NOT uniform and there was no way to see a tenant's
actual grant state. VWP had the Tenant Admin app but no SharePoint app-only role -> silent
401s until this session.

- references/app-suite.md: authoritative, live-verified map of every app, App ID, and
  actually-granted permission per tier; the consent-drift problem + both fix methods
  (adminconsent URL, direct appRoleAssignment grant).
- scripts/consent-audit.sh: audits a tenant (or --all) vs the baseline, grades
  GREEN/AMBER/RED, prints the exact fix per gap. Extends the assign-exchange-role --verify
  pattern to Graph scopes + SharePoint role + EXO role. Verified: BirthBio GREEN, VWP/Cascades
  AMBER (caught real drift - both missing grants).
- SKILL.md: run consent-audit FIRST on any tenant task. Memory + errorlog correction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:15:08 -07:00

12 KiB

ComputerGuru M365 App Suite — Authoritative Reference

This is the single source of truth for the ComputerGuru multi-tenant app suite: every app, its App ID, its ACTUAL granted permissions, the get-token.sh tiers, and — the part that keeps biting us — how per-tenant consent differs and how to audit + fix a tenant that only has PARTIAL consent.

Live-verified 2026-07-02 against Birth Biologic (fully-consented reference tenant). Anything here is re-checkable with the commands below; when a call fails, verify against reality before concluding "the tool can't do it."


The apps

All are multi-tenant app registrations owned in the ACG home tenant (azcomputerguru.com). A customer tenant gets a service principal for each app when it consents. The App ID is the same everywhere; the SP object id differs per tenant.

App (display name) App ID Auth Vault file (in msp-tools/)
ComputerGuru - Security Investigator bfbc12a4-f0dd-4e12-b06d-997e7271e10c secret computerguru-security-investigator.sops.yaml
ComputerGuru - Exchange Operator b43e7342-5b4b-492f-890f-bb5a4f7f40e9 secret computerguru-exchange-operator.sops.yaml
ComputerGuru - User Manager 64fac46b-8b44-41ad-93ee-7da03927576c secret computerguru-user-manager.sops.yaml
ComputerGuru - Tenant Admin 709e6eed-0711-4875-9c44-2d3518c47063 secret (Graph/EXO) + CERT (SharePoint) computerguru-tenant-admin.sops.yaml
ComputerGuru - Defender Add-on dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b secret computerguru-defender-addon.sops.yaml
ComputerGuru - Intune Manager 46986910-aa47-4e5e-b596-f65c6b485abb secret computerguru-intune-manager.sops.yaml
ComputerGuru - Mailbox (ACG-INTERNAL ONLY) 1873b1b0-3377-485c-a848-bae9b2f8f1f5 CERT computerguru-mailbox.sops.yaml

The Mailbox app is single-tenant (azcomputerguru.com only) — it is NOT part of the customer suite; it 401s on any other tenant.


get-token.sh tiers (tier -> app -> resource)

bash scripts/get-token.sh <tenant> <tier> -> a bearer token on stdout (cached 55 min in /tmp/remediation-tool/<tenant>/<tier>.jwt; delete that file to force a fresh mint after a consent change).

Tier App Audience / resource Auth
investigator Security Investigator Graph (https://graph.microsoft.com) secret
investigator-exo Security Investigator Exchange Online (https://outlook.office365.com) secret
exchange-op Exchange Operator Exchange Online secret
exchange-op-graph Exchange Operator Graph (added 2026-07-01) secret
user-manager User Manager Graph secret
tenant-admin Tenant Admin Graph secret
tenant-admin-onboard Tenant Admin Graph (onboarding alias) secret
sharepoint Tenant Admin SharePoint content (<name>.sharepoint.com) CERT
sharepoint-admin Tenant Admin SharePoint admin (<name>-admin.sharepoint.com) CERT
defender Defender Add-on Defender ATP (https://api.securitycenter.microsoft.com) secret
intune-manager Intune Manager Graph secret
mailbox Mailbox (internal) Graph CERT

SharePoint host is auto-resolved via Graph /sites/root; override with SP_RESOURCE_ENV=<name>-admin.sharepoint.com (needed when the app lacks the Graph Sites scope to self-resolve — e.g. mid-onboarding).


Live-verified permissions (application/app-only roles)

Decoded from real tokens on Birth Biologic (full consent). These are the roles each app is BUILT to hold. A given tenant may hold a SUBSET — see the audit section.

Graph roles

App / tier Graph application roles
investigator Application.Read.All, AuditLog.Read.All, BitlockerKey.Read.All, Directory.Read.All, IdentityRiskEvent.ReadWrite.All, IdentityRiskyAgent.ReadWrite.All, IdentityRiskyServicePrincipal.ReadWrite.All, IdentityRiskyUser.Read.All, IdentityRiskyUser.ReadWrite.All, Mail.Read, MailboxSettings.Read, Organization.Read.All, Policy.Read.All, Sites.Read.All, User.Read.All, UserAuthenticationMethod.Read.All
exchange-op-graph Mail.ReadWrite, MailboxSettings.ReadWrite, Organization.Read.All, User.Read.All, User.RevokeSessions.All
user-manager Device.ReadWrite.All, Directory.ReadWrite.All, Group.ReadWrite.All, User.ReadWrite.All, Organization.Read.All, User.RevokeSessions.All, UserAuthenticationMethod.ReadWrite.All
tenant-admin AppRoleAssignment.ReadWrite.All, Application.ReadWrite.All, RoleManagement.ReadWrite.Directory, Directory.ReadWrite.All, Policy.ReadWrite.ConditionalAccess, Sites.FullControl.All, Sites.ReadWrite.All, User.ReadWrite.All, SecurityEvents.Read.All, Policy.Read.All, UserAuthenticationMethod.ReadWrite.All

SharePoint Online roles (resource 00000003-0000-0ff1-ce00-000000000000, CERT auth)

Tier SharePoint application roles
sharepoint / sharepoint-admin Sites.FullControl.All

Exchange Online (app-only, EXO audience — role, not a Graph roles claim)

EXO app-only access is via the Exchange Administrator directory role assigned to the SP PLUS full_access_as_app + Exchange.ManageAsApp app permissions. It does NOT show in a Graph roles decode — it's an EXO RBAC assignment.

Tier EXO capability
investigator-exo EXO read (Get-Mailbox, Get-InboxRule, Get-*) — needs Exchange Admin role on the Investigator SP
exchange-op EXO write / all-access (Set-Mailbox, New/Remove-InboxRule, Add-MailboxPermission, InvokeCommand, move mail) — Exchange Admin role + full_access_as_app + Exchange.ManageAsApp on the Exchange Operator SP. This is the all-access mail tier; do NOT claim "no tier can write mail."

Every failure of the form "we can't do X on tenant Y" has been one of these, NOT a missing capability:

  1. The app is consented but with an OLD/PARTIAL permission set. A tenant onboarded before a scope was added to the app manifest keeps the old grant until re-consented. Example: VWP (Valley Wide Plastering, 5c53ae9f-7071-4248-b834-8685b646450f) had the Tenant Admin app but NO SharePoint Sites.FullControl.All — every SharePoint call 401'd with a valid-looking token (aud = SharePoint, roles = empty). Fixed 2026-07-02 (see below).
  2. The SharePoint secret-vs-cert gotcha. SharePoint app-only REJECTS a client_secret token ("Unsupported app only token"). get-token.sh forces cert for the sharepoint* tiers; if you hand-roll, use the cert.
  3. Not consented at all (AADSTS7000229 / AADSTS700016) — the tenant never onboarded the app. Use the admin-consent URL.
  4. EXO role not assigned — the app is consented but the SP lacks the Exchange Admin directory role (EXO calls 403). Fix with assign-exchange-role.sh.

Discipline: before telling the user "the tool can't do it", AUDIT the tenant.

Just run the audit command — it decodes every app's actual grants, diffs against this baseline, grades GREEN/AMBER/RED, and prints the exact fix for each gap:

bash scripts/consent-audit.sh <domain|tenant-guid>     # one tenant, full detail + fixes
bash scripts/consent-audit.sh --all                    # every tenant in references/tenants.md

Run it at the START of any tenant task (or on a schedule) so a partial-consent gap surfaces up front, not as a 401 mid-task. It checks: investigator / exchange-op / user-manager / tenant-admin Graph roles, the SharePoint app-only Sites.FullControl.All (cert), and the EXO Exchange Administrator role — the full set that has ever caused a "can't do X" failure.

Manual equivalent (what the script does), if you want to eyeball one tier:

TEN=<tenant-guid>
for tier in investigator exchange-op-graph user-manager tenant-admin; do
  echo "== $tier =="
  rm -f /tmp/remediation-tool/$TEN/$tier.jwt
  T=$(bash scripts/get-token.sh $TEN $tier 2>/dev/null | tail -1)
  python - "$T" <<'PY'
import sys,base64,json
c=json.loads(base64.urlsafe_b64decode((lambda p:p+'='*(-len(p)%4))(sys.argv[1].split('.')[1])))
print("  roles:", ", ".join(sorted(c.get("roles",[]))) or "(NONE — not consented / partial)")
PY
done
# SharePoint (cert): roles should include Sites.FullControl.All
SP_RESOURCE_ENV=<name>-admin.sharepoint.com bash scripts/get-token.sh $TEN sharepoint-admin \
  | tail -1 | cut -d. -f2 | ...decode roles...

An empty roles on a token whose aud is correct = the app is present but that permission set was never granted on this tenant. That is the signal to fix consent, NOT to give up.

Method A — re-consent the whole app manifest (preferred, grants everything the app requests). A tenant Global Admin clicks:

https://login.microsoftonline.com/<tenant>/adminconsent?client_id=<app-id>

Grants ALL of the app's requiredResourceAccess. NOTE (VWP lesson): this reliably grants the Graph permissions but the SharePoint app-only role sometimes does NOT attach from the consent flow — verify with the audit above and use Method B for the leftover.

Method B — grant a specific missing app role directly (no user interaction; needs the tenant-admin token, which holds AppRoleAssignment.ReadWrite.All). This is how VWP's missing SharePoint role was fixed:

TEN=<tenant>; TA=$(bash scripts/get-token.sh $TEN tenant-admin | tail -1); G=https://graph.microsoft.com/v1.0
# the SP receiving the role (e.g. Tenant Admin app):
RECIP=$(curl -s -G "$G/servicePrincipals" --data-urlencode "\$filter=appId eq '<app-id>'" -H "Authorization: Bearer $TA" | jq -r '.value[0].id')
# the resource SP that DEFINES the role (SharePoint = 00000003-0000-0ff1-ce00-...):
RES=$(curl -s -G "$G/servicePrincipals" --data-urlencode "\$filter=appId eq '00000003-0000-0ff1-ce00-000000000000'" --data-urlencode "\$select=id,appRoles" -H "Authorization: Bearer $TA")
RESID=$(echo "$RES" | jq -r '.value[0].id')
ROLE=$(echo "$RES" | jq -r '.value[0].appRoles[]|select(.value=="Sites.FullControl.All").id')
# grant it:
curl -s -X POST "$G/servicePrincipals/$RECIP/appRoleAssignments" -H "Authorization: Bearer $TA" \
  -H "Content-Type: application/json" \
  -d "{\"principalId\":\"$RECIP\",\"resourceId\":\"$RESID\",\"appRoleId\":\"$ROLE\"}"

Only use Method B to complete an intent the customer already consented to (they clicked the consent link / authorized the access). It propagates to a fresh token within seconds.

Fix a missing EXO role: bash scripts/assign-exchange-role.sh <domain> (idempotent; --all --verify to audit the fleet). EXO propagation is 15-60 min.


Per-tenant onboarding status (living matrix — update as verified)

Legend: F = full consent verified · P = partial (note the gap) · ? = unverified.

Tenant Guid investigator exchange-op user-manager tenant-admin (Graph) SharePoint Notes
Birth Biologic 19a568e8-… F F F F F reference/baseline
Valley Wide Plastering (VWP) 5c53ae9f-… ? F (JIT-proven) F F (Graph Sites added 2026-07-02) F (fixed 2026-07-02 via Method B) was the "old app only" gap
Cascades of Tucson (resolve) ? ? ? ? ? flagged "old app only" — audit
Dataforth (resolve) ? ? ? ? ? flagged "old app only" — audit

Keep this matrix current — it is the answer to "is tenant X onboarded?" that we keep not having.


Gotchas index

  • SharePoint app-only = CERT, not secret (Unsupported app only token). get-token.sh forces cert for sharepoint*.
  • Graph GET /admin/sharepoint/settings 403 — no app holds SharePointTenantSettings.*. Read/write SP tenant settings via the CSOM/REST admin API (sharepoint-admin, Sites.FullControl.All).
  • adminconsent grants Graph but may skip the SharePoint app role — verify + Method-B the gap.
  • JIT Privileged Auth Admin can't self-remove — reset-password leaves standing PAA on the Tenant Admin SP; a human GA removes it (or use PIM / a second principal).
  • EXO role propagation 15-60 min; verify membership via roleManagement/directory/roleAssignments (not the laggy directoryRoles/members list).
  • Mailbox app is ACG-internal only — never expect it on a customer tenant.

Adding a scope to an app manifest is a portal action (patch-tenant-admin-manifest.sh + adminconsent); the multi-tenant app registrations themselves are managed manually, not via the tool.