sync: auto-sync from GURU-5070 at 2026-07-01 09:32:17

Author: Mike Swanson
Machine: GURU-5070
Timestamp: 2026-07-01 09:32:17
This commit is contained in:
2026-07-01 09:33:09 -07:00
parent e583bf43a5
commit 6f7f939a62
6 changed files with 175 additions and 1 deletions

View File

@@ -200,3 +200,4 @@
- [GuruScan verification IN TEST / paused](project_guruscan_in_test_paused.md) — multi-engine scanner verify on DESKTOP-MS42HNC paused 2026-06-22 (VM rebooted mid-Emsisoft run); HitmanPro done (36 removed), Emsisoft full-scan unverified; resume `guruscan-agent-test.sh DESKTOP-MS42HNC scan-one Emsisoft`; Defender RTP/Tamper still off on VM
- [GuruRMM fleet dispatch-hang fix](project_gururmm_dispatch_hang_fix.md) — blocking send_to on a full bounded channel to one black-holed agent wedged ALL command dispatch; fixed with try_send (9dae20c, deployed); proper black-hole eviction still missing (was reverted in 80df458) — finish it if it recurs
- [Windows won't-boot / offline DISM repair playbook](windows-offline-dism-repair-gotchas.md) — Automatic Repair loop = boot-critical fault (disk/registry/wedged update), NOT shell/appx store corruption (that's a symptom); `FaultyPackageInProgress` + 100s of Install/Uninstall-Pending packages = wedged CU -> RevertPendingActions or clean install. Offline DISM rejects `wim:` source (0x800f082e) -> MOUNT the wim, source `\Windows`. Ventoy breaks WIM mount (0xc1420134) -> use Rufus. 25H2(26200)=24H2(26100)+enablement, so match 26100 media. First hit: Four Paws AvImark #32447.
- [Remediation-tool has full M365 access (incl. SharePoint)](reference_remediation_tool_365_access.md) — the app suite covers Graph/EXO/Defender/SharePoint; don't declare "no access" on an accessDenied. SharePoint app-only needs a CERT (secret = "Unsupported app only token"); use get-token.sh `sharepoint`/`sharepoint-admin` tiers + CSOM admin API (Graph /admin/sharepoint/settings scope not held). Full map: skill references/app-permissions-and-sharepoint.md.

View File

@@ -0,0 +1,32 @@
---
name: reference_remediation_tool_365_access
description: The remediation-tool app suite has full M365 access (incl. SharePoint via cert); don't declare "no access" on an accessDenied
metadata:
type: reference
---
The ComputerGuru remediation-tool apps collectively have **broad, working access across ALL of
M365** — Graph, Exchange Online, Defender, AND SharePoint Online. When a call fails it's almost
always wrong-tier / wrong-endpoint / not-consented / the SharePoint cert gotcha — **not** a real
lack of access. Do NOT tell the user "the tool can't do X" without checking the live permission
map first (decode the token `roles` claim).
Key facts:
- **SharePoint app-only requires a CERTIFICATE.** A `client_secret` token is rejected on every
SharePoint endpoint (REST `/_api` and CSOM `/_vti_bin/client.svc/ProcessQuery`) with
`"Unsupported app only token"`. The Tenant Admin app has a cert in the vault and holds
SharePoint-resource `Sites.FullControl.All`.
- `get-token.sh` now has **`sharepoint`** (content) and **`sharepoint-admin`** (tenant admin)
tiers — cert-forced, tenant resource auto-resolved from Graph `/sites/root`
(override `SP_RESOURCE_ENV`). Added 2026-07-01.
- Graph `GET /admin/sharepoint/settings` needs `SharePointTenantSettings.Read.All`, which NO app
holds → that route 403s. Read/write SharePoint tenant settings via the **CSOM admin API**
(`sharepoint-admin` tier) instead. Tenant settings live on the Tenant object
(TypeId `{268004ae-ef6b-4e9b-8425-127220d84719}`) — e.g. `SelfServiceSiteCreationDisabled`.
- Restricting employee SharePoint site creation = `SelfServiceSiteCreationDisabled=true` (CSOM)
AND restrict M365 Group creation (Entra `Group.Unified` directory setting via `user-manager`);
neither affects edit rights on existing sites.
Full detail (live per-tier permission map + CSOM examples):
`.claude/skills/remediation-tool/references/app-permissions-and-sharepoint.md`. Surfaced by
Syncro #32492 (Birth Biologic). See also [[feedback_syncro_billing]].

View File

@@ -20,6 +20,9 @@ Five multi-tenant apps cover distinct privilege tiers. Use only what the task re
| `user-manager` | ComputerGuru User Manager | `64fac46b-8b44-41ad-93ee-7da03927576c` | `computerguru-user-manager.sops.yaml` | Graph user/group write |
| `tenant-admin` | ComputerGuru Tenant Admin | `709e6eed-0711-4875-9c44-2d3518c47063` | `computerguru-tenant-admin.sops.yaml` | Graph high-privilege |
| `defender` | ComputerGuru Defender Add-on | `dbf8ad1a-54f4-4bb8-8a9e-ea5b9634635b` | `computerguru-defender-addon.sops.yaml` | Defender ATP (MDE only) |
| `sharepoint` / `sharepoint-admin` | ComputerGuru Tenant Admin | `709e6eed-0711-4875-9c44-2d3518c47063` | `computerguru-tenant-admin.sops.yaml` | **SharePoint Online** (Sites.FullControl.All) — cert-only |
**The suite has broad, working access across ALL of M365 — Graph, Exchange Online, Defender, AND SharePoint Online.** Before concluding "the tool can't do X / access denied," verify against the live permission map in `references/app-permissions-and-sharepoint.md` (decode the token `roles` claim). An `accessDenied` usually means wrong tier or wrong endpoint for a scope we DO hold — not a real gap. Two recurring traps: (1) **SharePoint app-only requires a certificate** — a `client_secret` token is rejected everywhere in SharePoint with `"Unsupported app only token"` (get-token.sh forces cert for the `sharepoint*` tiers); (2) Graph `GET /admin/sharepoint/settings` needs a scope no app holds — read/write SharePoint tenant settings via the **CSOM/REST admin API** (`sharepoint-admin` tier) instead. Full map, gotchas, and CSOM examples: `references/app-permissions-and-sharepoint.md`.
**Default for breach checks:** use `investigator` (Graph) + `investigator-exo` (Exchange read). Escalate to write tiers only when remediating.

View File

@@ -0,0 +1,97 @@
# App permissions (authoritative) + SharePoint access
Purpose: stop the recurring "I can't do this / access denied" friction. The ComputerGuru
app suite has **broad, working access across M365 — Graph, Exchange Online, Defender, AND
SharePoint Online**. When a call fails, the cause is almost always one of: wrong tier, wrong
endpoint for the scope we hold, a not-consented tenant (AADSTS7000229), or the SharePoint
**secret-vs-cert** gotcha below — NOT a genuine lack of access.
**Discipline: before ever telling the user "the tool can't do X", verify against reality.**
Decode the token's `roles` claim and check it against this map:
```bash
TOK=$(bash scripts/get-token.sh <tenant> <tier>)
python - "$TOK" <<'PY' # or: cut -d. -f2 | base64 -d | jq .roles
import sys,base64,json
p=sys.argv[1].split('.')[1]; p+='='*(-len(p)%4)
print(json.dumps(json.loads(base64.urlsafe_b64decode(p)).get('roles',[]),indent=2))
PY
```
## Live-verified application-permission map (decoded 2026-07-01, Birth Biologic tenant)
Application (app-only) roles actually present in each app's token. Fetch live to confirm for
a given tenant (consent can differ), but this is the baseline the apps are built to.
| Tier (get-token.sh) | App | Resource | Key application roles |
|---|---|---|---|
| `investigator` | Security Investigator | Graph | Directory.Read.All, User.Read.All, AuditLog.Read.All, Application.Read.All, Organization.Read.All, Policy.Read.All, **Sites.Read.All**, Mail.Read, MailboxSettings.Read, BitlockerKey.Read.All, IdentityRiskyUser.ReadWrite.All, IdentityRiskEvent.ReadWrite.All, IdentityRiskyServicePrincipal.ReadWrite.All, IdentityRiskyAgent.ReadWrite.All, UserAuthenticationMethod.Read.All |
| `investigator-exo` | Security Investigator | Exchange Online | EXO read (Get-InboxRule, Get-Mailbox, Get-*) |
| `exchange-op` | Exchange Operator | Exchange Online | EXO write (Set-Mailbox, Remove-InboxRule, revoke sessions) |
| `user-manager` | User Manager | Graph | **Directory.ReadWrite.All, Group.ReadWrite.All, User.ReadWrite.All**, Device.ReadWrite.All, User.RevokeSessions.All, UserAuthenticationMethod.ReadWrite.All, Organization.Read.All |
| `tenant-admin` | Tenant Admin | Graph | **Application.ReadWrite.All, AppRoleAssignment.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 |
| `tenant-admin` | Tenant Admin | **SharePoint Online** | **Sites.FullControl.All** (SharePoint resource `00000003-0000-0ff1-ce00-000000000000`) — the SharePoint tiers use this |
| `defender` | Defender Add-on | Defender ATP | MDE machine/alert actions (MDE-licensed tenants only) |
| `intune-manager` | Intune Manager | Graph | DeviceManagement* (Intune) |
| `mailbox` | ACG Mailbox | Graph | ACG-INTERNAL ONLY — Mail.ReadWrite/Send, Contacts.ReadWrite |
Notes:
- **CA policies ARE manageable** (tenant-admin holds `Policy.ReadWrite.ConditionalAccess` + the Conditional Access Administrator role) — report-only + break-glass exclusion first (see SKILL scope boundaries).
- **Directory settings** (M365 Group creation restriction, etc.) are writable via `user-manager` (Directory.ReadWrite.All) or `tenant-admin`.
## SharePoint Online — the two gotchas
1. **SharePoint app-only REQUIRES a certificate. A client_secret token is rejected** with
`"Unsupported app only token"` on every SharePoint endpoint (REST `/_api/...` and CSOM
`/_vti_bin/client.svc/ProcessQuery`). The Tenant Admin app has a cert in the vault
(`cert_thumbprint_b64url` + `cert_private_key_pem_b64`); `get-token.sh` forces cert for the
SharePoint tiers automatically. If you hand-roll a token, use the cert (client_assertion),
not the secret.
2. **The Graph endpoint `GET /admin/sharepoint/settings` needs `SharePointTenantSettings.Read.All`,
which NONE of the apps currently hold** — so that route returns `accessDenied`. That is NOT
"no SharePoint access". Use the SharePoint **CSOM/REST admin API** (cert token, `Sites.FullControl.All`)
instead — that path has full tenant-settings read/write. (If you'd rather use the Graph route,
add `SharePointTenantSettings.Read.All`/`.ReadWrite.All` to the Tenant Admin app manifest and
re-consent — `patch-tenant-admin-manifest.sh` + the tenant admin-consent URL.)
## SharePoint tiers (get-token.sh)
```bash
# Tenant admin resource ("<name>-admin.sharepoint.com") — tenant settings, site provisioning
TOK=$(bash scripts/get-token.sh <tenant> sharepoint-admin)
# Content resource ("<name>.sharepoint.com") — site/list/file operations
TOK=$(bash scripts/get-token.sh <tenant> sharepoint)
# Host is auto-resolved via Graph /sites/root; override if needed:
SP_RESOURCE_ENV=contoso.sharepoint.com bash scripts/get-token.sh <tenant> sharepoint-admin
```
### Read tenant settings (e.g. site-creation) via CSOM
```bash
ADMIN="https://<name>-admin.sharepoint.com"
TOK=$(bash scripts/get-token.sh <tenant> sharepoint-admin)
BODY='<Request xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="ACG"><Actions><ObjectPath Id="2" ObjectPathId="1"/><Query Id="3" ObjectPathId="1"><Query SelectAllProperties="true"><Properties/></Query></Query></Actions><ObjectPaths><Constructor Id="1" TypeId="{268004ae-ef6b-4e9b-8425-127220d84719}"/></ObjectPaths></Request>'
curl -s -X POST "$ADMIN/_vti_bin/client.svc/ProcessQuery" \
-H "Authorization: Bearer $TOK" -H "Content-Type: text/xml" --data-binary "$BODY" \
| jq -r '.[] | select(type=="object") | to_entries[]
| select(.key|test("SiteCreation|SelfService|Sharing";"i")) | "\(.key) = \(.value)"'
```
Relevant tenant properties: `SelfServiceSiteCreationDisabled` (false = users CAN create sites),
`AllowClassicPublishingSiteCreation`, `SharingCapability`. Writing a setting is the same CSOM
surface with a `SetProperty` action (gate behind explicit YES, like any remediation write).
### Modern site creation = M365 Group creation
A modern team/communication site creates an M365 Group, gated by the Entra directory setting
`Group.Unified` -> `EnableGroupCreation` / `GroupCreationAllowedGroupId`. Read via Graph
`GET /groupSettings` (empty result = defaults = anyone can create). Write via `user-manager`
(Directory.ReadWrite.All): POST/PATCH `/groupSettings` using the `Group.Unified` template.
So fully restricting employee site creation = (a) `SelfServiceSiteCreationDisabled=true` (CSOM)
**and** (b) restrict `Group.Unified` group creation (Graph). Neither touches edit rights on an
existing site.
---
_First documented 2026-07-01 (Mike). Trigger: Syncro #32492 (Birth Biologic SharePoint site-creation
lockdown) surfaced the secret-vs-cert + no-SharePoint-tier gaps. Added `sharepoint`/`sharepoint-admin`
tiers to get-token.sh the same day._

View File

@@ -38,6 +38,9 @@ else
TENANT_ID=$("$SCRIPT_DIR/resolve-tenant.sh" "$TARGET")
fi
# SharePoint tiers resolve their (tenant-specific) resource host after decrypt; default empty.
SP_KIND=""
# Map tier -> client_id, vault SOPS path, resource scope
case "$TIER" in
investigator)
@@ -91,9 +94,23 @@ case "$TIER" in
VAULT_PATH="msp-tools/computerguru-mailbox.sops.yaml"
SCOPE_URL="https://graph.microsoft.com/.default"
;;
sharepoint|sharepoint-admin)
# SharePoint Online. Uses the Tenant Admin app, which holds the SharePoint-resource
# role Sites.FullControl.All. SharePoint app-only REJECTS client_secret tokens with
# "Unsupported app only token" — CERT AUTH IS MANDATORY (the cert is in the vault).
# The resource is tenant-specific, so it can't be a fixed SCOPE_URL like Graph; it is
# resolved from Graph /sites/root after decrypt. Override with SP_RESOURCE_ENV=<host>.
# sharepoint -> content resource ("<name>.sharepoint.com") site/list/file CSOM+REST
# sharepoint-admin -> tenant admin resource ("<name>-admin.sharepoint.com") tenant settings via CSOM ProcessQuery
CLIENT_ID="709e6eed-0711-4875-9c44-2d3518c47063"
VAULT_PATH="msp-tools/computerguru-tenant-admin.sops.yaml"
SCOPE_URL=""
SP_KIND="content"; [[ "$TIER" == "sharepoint-admin" ]] && SP_KIND="admin"
REMEDIATION_AUTH="cert" # SharePoint app-only requires a certificate
;;
*)
echo "ERROR: unknown tier '$TIER'." >&2
echo "Valid tiers: investigator | investigator-exo | exchange-op | user-manager | tenant-admin | tenant-admin-onboard | defender | intune-manager | mailbox" >&2
echo "Valid tiers: investigator | investigator-exo | exchange-op | user-manager | tenant-admin | tenant-admin-onboard | defender | intune-manager | mailbox | sharepoint | sharepoint-admin" >&2
exit 2
;;
esac
@@ -281,6 +298,28 @@ esac
echo "[INFO] auth=$AUTH_METHOD" >&2
# SharePoint tiers: resolve the tenant-specific resource host (SharePoint's resource URL is
# per-tenant, unlike Graph's fixed URL). Uses a Graph /sites/root lookup (Sites.Read via the
# Tenant Admin app), or SP_RESOURCE_ENV if the caller supplies the host explicitly.
if [[ -z "$SCOPE_URL" && -n "${SP_KIND:-}" ]]; then
SP_HOST="${SP_RESOURCE_ENV:-}"
if [[ -z "$SP_HOST" ]]; then
SP_GRAPH_TOKEN=$("$SCRIPT_DIR/get-token.sh" "$TENANT_ID" tenant-admin 2>/dev/null || true)
if [[ -n "$SP_GRAPH_TOKEN" ]]; then
SP_HOST=$(curl -s --max-time 15 "https://graph.microsoft.com/v1.0/sites/root" \
-H "Authorization: Bearer $SP_GRAPH_TOKEN" | jq -r '.siteCollection.hostname // empty')
fi
fi
if [[ -z "$SP_HOST" ]]; then
echo "ERROR: could not resolve SharePoint host for tenant $TENANT_ID." >&2
echo " Set SP_RESOURCE_ENV=<tenant>.sharepoint.com and retry." >&2
exit 6
fi
[[ "$SP_KIND" == "admin" ]] && SP_HOST="${SP_HOST/.sharepoint.com/-admin.sharepoint.com}"
SCOPE_URL="https://${SP_HOST}/.default"
echo "[INFO] sharepoint resource: $SCOPE_URL" >&2
fi
# Build request and POST.
TOKEN_URL="https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token"