From 63f427a95f6d4be2ea516d355057432f12c8437b Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Wed, 10 Jun 2026 16:03:11 -0700 Subject: [PATCH] sync: auto-sync from GURU-5070 at 2026-06-10 16:02:59 Author: Mike Swanson Machine: GURU-5070 Timestamp: 2026-06-10 16:02:59 --- .claude/commands/onboard365.md | 36 ++++++ .claude/memory/MEMORY.md | 1 + .claude/memory/rmm-agent-update-model.md | 42 ++++++ .claude/skills/onboard365/SKILL.md | 120 ++++++++++++++++++ .../customer-consent-instructions.md | 40 ++++++ .../skills/onboard365/scripts/onboard365.sh | 113 +++++++++++++++++ .../2026-06-10-mike-onboard365-skill.md | 75 +++++++++++ 7 files changed, 427 insertions(+) create mode 100644 .claude/commands/onboard365.md create mode 100644 .claude/memory/rmm-agent-update-model.md create mode 100644 .claude/skills/onboard365/SKILL.md create mode 100644 .claude/skills/onboard365/references/customer-consent-instructions.md create mode 100644 .claude/skills/onboard365/scripts/onboard365.sh create mode 100644 session-logs/2026-06/2026-06-10-mike-onboard365-skill.md diff --git a/.claude/commands/onboard365.md b/.claude/commands/onboard365.md new file mode 100644 index 0000000..05889e5 --- /dev/null +++ b/.claude/commands/onboard365.md @@ -0,0 +1,36 @@ +# /onboard365 — Single-consent M365 tenant onboarding + +Onboard a customer Microsoft 365 tenant to the ComputerGuru remediation app suite with **one** +customer admin-consent click. Thin entry point to the `onboard365` skill. + +## Usage + +``` +/onboard365 Smart: print the consent link if not yet consented, + or provision the whole suite if it is. +/onboard365 link Just generate the single Tenant Admin consent URL. +/onboard365 status Dry-run: show current consent / role state. +/onboard365 provision After the customer consents: provision all apps + roles. +``` + +## What it does + +The customer Global Admin consents once to **ComputerGuru Tenant Admin**. Using that grant, +`onboard-tenant.sh` (reused from the `remediation-tool` skill) then creates the service +principals for Security Investigator, Exchange Operator, User Manager, and (if MDE-licensed) +Defender Add-on, grants all their Graph/EXO/Defender permissions, and assigns the required +Entra directory roles — no further customer clicks. + +## Implementation + +1. Read the full playbook in `.claude/skills/onboard365/SKILL.md`. +2. Run `bash .claude/skills/onboard365/scripts/onboard365.sh ` + (the script auto-locates the reused remediation-tool scripts and the vault). +3. Confirm the target tenant with the user before generating a link, and again before + `provision` (high-privilege, customer-facing). +4. After a clean provision, **record it**: set the tenant's `Onboarded` column to `YES` in the + REPO copy of `remediation-tool/references/tenants.md` and note the onboarding in the client + wiki. (See SKILL.md → Recording.) + +This is the front door; once a tenant is onboarded, breach checks and remediation are the +`remediation-tool` skill. diff --git a/.claude/memory/MEMORY.md b/.claude/memory/MEMORY.md index 1dc0b2d..01aa35f 100644 --- a/.claude/memory/MEMORY.md +++ b/.claude/memory/MEMORY.md @@ -22,6 +22,7 @@ - [Gitea Internal API Access](reference_gitea_internal.md) — git.azcomputerguru.com is NOT behind Cloudflare — it's the office Cox IP NAT'd to NPM (openresty) on Jupiter. Prefer internal 172.16.3.20:3000 for reliability (bypasses NPM SSL-renewal reload blips). - [Gitea git-op latency](reference_gitea_git_op_latency.md) — SSH (.20:2222) is SLOWEST (~1.5s); internal HTTP+token ~0.55s; SOPS lookup only ~0.33s. Don't switch to SSH for speed. Gitea SSH is .20:2222 (API ssh_url .21 is wrong). - [GuruRMM technical reference](reference_gururmm.md) — Server (172.16.3.30) layout + downloads dir `/var/www/gururmm/downloads` + `.channel` sidecar rollout control (stable/beta) + privileged server access via the server's OWN root RMM agent (hostname `gururmm`, no SSH needed; plink fallback) + API + `context=user_session` (WTS impersonation) + build-pipeline vendoring at `deploy/build-pipeline/` + Linux agent systemd sandbox trap. +- [RMM agent update model](rmm-agent-update-model.md) — Agent updates are server-PUSH on heartbeat (no self-poll); available versions = filesystem scan needing a `.sha256`; promote flips `.channel` sidecars beta→stable globally. Two stranders: beta-first freezes stable until an explicit promote; agents older than ~0.6.50 re-enroll with a NEW device_id/agent row when updated. - [Trebesch DESKTOP-QNP3ON5 shell replacement](reference_trebesch_qnp3on5.md) — AT Trebesch box runs an Explorer shell replacement; explorer.exe owner check returns blank — use Win32_ComputerSystem.UserName. GuruRMM SWIFT-LION-2892. ## Users diff --git a/.claude/memory/rmm-agent-update-model.md b/.claude/memory/rmm-agent-update-model.md new file mode 100644 index 0000000..18c6ff5 --- /dev/null +++ b/.claude/memory/rmm-agent-update-model.md @@ -0,0 +1,42 @@ +--- +name: rmm-agent-update-model +description: How GuruRMM agents actually update (server-push on heartbeat, channel-gated, beta-first) and two gotchas that strand agents +metadata: + type: project +--- + +GuruRMM agent updates are **100% server-push** — the agent never self-polls. On every +heartbeat the server (`server/src/ws/mod.rs` ~line 1124) resolves the agent's channel, +calls `UpdateManager::needs_update`, and pushes `ServerMessage::Update` if a newer build +exists. A pending update is re-dispatched on the next heartbeat (the `[RE-DISPATCH]` path). +The only other Update senders are the manual `POST /api/agents/:id/update` and rollback. + +**Available versions = a filesystem scan**, not a DB table. `updates/scanner.rs` scans +`/var/www/gururmm/downloads/` for `gururmm-agent-{os}-{arch}-{ver}.exe` (per-site +`...-site--...` names deliberately fail to parse), requires a `.sha256` companion +(no checksum → silently skipped), and reads channel from a `.channel` sidecar +(absent or non-"beta" ⇒ **stable**). `get_latest_version` for a stable agent returns the +newest binary whose sidecar isn't "beta". Channel resolves agent→site→client→"stable". + +**Promotion** (`POST /api/updates/rollouts/:ver/promote`) just flips every matching +`.channel` sidecar beta→stable (globally — os/arch only scopes the health-gate + rollout +DB row) and rescans. The fleet then pulls it on the next heartbeat. Rollback removes the +sidecars + blocks the version + downgrades. Dashboard admin login: vault +`projects/gururmm/dashboard`. DB: `psql "$DATABASE_URL"` after `source ~/.cargo/env` on +guru@172.16.3.30. + +Two gotchas that strand agents (both hit 2026-06-10): +1. **Beta-first freezes stable.** New builds are tagged beta; stable only advances on an + explicit promote. Stable had been frozen at 0.6.47 (since 2026-05-28) while builds ran + to 0.6.58 beta — so every stable agent silently stopped updating. Promoting 0.6.58 + rolled ~200 agents in minutes. +2. **Old agents re-enroll with a NEW identity.** The device_id format changed (`win-` + → bare ``) somewhere between 0.6.27 and ~0.6.50. An agent old enough to cross that + boundary (e.g. megan, 0.6.27→0.6.58) re-registers as a **new agent row** instead of + updating in place, orphaning its old row (clean up the stale duplicate). Agents already + past the boundary update in place. + +Related: [[reference_gururmm]] (downloads dir + sidecar detail + privileged server access). +Audit/log-feedback work: build/version correlation lives in `log_signatures` + +`log_signature_versions`; server self-errors are captured via `self_log.rs` into the +"GuruRMM Server" pseudo-agent. diff --git a/.claude/skills/onboard365/SKILL.md b/.claude/skills/onboard365/SKILL.md new file mode 100644 index 0000000..c421506 --- /dev/null +++ b/.claude/skills/onboard365/SKILL.md @@ -0,0 +1,120 @@ +--- +name: onboard365 +description: "Single-consent onboarding of a customer Microsoft 365 tenant to the ComputerGuru remediation app suite (Security Investigator / Exchange Operator / User Manager / Tenant Admin / Defender). The customer Global Admin clicks ONE admin-consent link (Tenant Admin); everything else — service principals, Graph/EXO/Defender permissions, and Entra directory roles — is provisioned automatically, no further clicks. Triggers: onboard 365, onboard a tenant, add tenant to remediation tools, single consent, consent link for new client, provision tenant apps, new M365 client onboarding, get a tenant ready for breach checks." +--- + +# Onboard365 — Single-Consent M365 Tenant Onboarding + +Gets a customer M365 tenant ready for the `remediation-tool` suite with **one** customer +action: a single admin-consent click on the **ComputerGuru Tenant Admin** app. After that, +ACG provisions every other app and role programmatically using the Tenant Admin token — the +customer never sees five separate consent prompts. + +This skill is a thin orchestrator. The provisioning logic, role GUIDs, and app IDs live in the +`remediation-tool` skill (`onboard-tenant.sh`) and are reused here so they never drift. Do NOT +duplicate that script — call it. + +## Why "single consent" + +Microsoft admin consent is per-application, so naively onboarding the 5-app suite would mean +5 customer clicks. We avoid that: the **Tenant Admin** app holds `Application.ReadWrite.All` + +`AppRoleAssignment.ReadWrite.All` + `RoleManagement.ReadWrite.Directory`. Once the customer +consents to Tenant Admin, our automation can, on their behalf: + +1. Create the service principal for each other app (this IS admin consent for that app). +2. Grant every required Graph / Exchange Online / Defender app-role assignment. +3. Assign the required Entra directory roles to each SP. + +Net: **one** customer click; the rest is `onboard-tenant.sh`. + +## The flow + +``` +onboard365.sh # smart: prints the consent link if not yet consented, + # or provisions the whole suite if it is +onboard365.sh link # just print the single consent URL + customer instructions +onboard365.sh status # dry-run: show current consent/role state, change nothing +onboard365.sh provision # after the customer consents: provision all apps + roles +``` + +Script lives at `scripts/onboard365.sh` in this skill. It auto-locates the `remediation-tool` +scripts at `$HOME/.claude/skills/remediation-tool/scripts` (repo fallback via +`identity.json.claudetools_root`). + +### Step-by-step (what to actually do) + +1. **Identify the tenant.** Accept a domain (e.g. `acme.com`), an `.onmicrosoft.com`, or a + tenant GUID. Run `onboard365.sh link ` to resolve it and produce the consent URL. +2. **Send the single link to the customer's Global Admin.** Use the template at + `references/customer-consent-instructions.md`. They sign in and click **Accept**. That is + the only thing they do. The app shown will be **"ComputerGuru Tenant Admin"**. +3. **Provision.** Once they confirm they accepted, run `onboard365.sh provision ` + (or just `onboard365.sh ` — it detects consent and proceeds). This runs + `onboard-tenant.sh`, which creates the other SPs, grants all permissions, and assigns the + directory roles. Watch the final status table — every row should be `OK` / `ASSIGNED`. +4. **Verify.** Re-run `onboard365.sh status ` (dry-run). All roles should read + `PRESENT`. Optionally confirm an Exchange path with + `remediation-tool/scripts/assign-exchange-role.sh --verify`. +5. **Record it** (see Recording below). + +## What gets provisioned (handled by onboard-tenant.sh — do not re-implement) + +| App | Graph/EXO/Defender perms | Directory role assigned | +|---|---|---| +| Tenant Admin (consented by customer) | high-privilege Graph (incl. `Policy.Read.All` backfill) | Conditional Access Administrator | +| Security Investigator | Graph read + EXO read | Exchange Administrator | +| Exchange Operator | Graph + EXO write | Exchange Administrator | +| User Manager | Graph user/group/auth write | User Administrator + Authentication Administrator | +| Defender Add-on | Graph + Defender ATP | (Defender API; no directory role) — **MDE-licensed tenants only** | + +The script auto-detects MDE licensing: if the Defender ATP resource SP isn't present, it +skips Defender cleanly (not an error). + +## Recording (durable — do this after a successful provision) + +The script provisions but does NOT write your records. After a clean run: + +1. **Tenant registry — edit the REPO copy** (so it persists + syncs to the fleet), not the + applied global copy. Path: `$CLAUDETOOLS_ROOT/.claude/skills/remediation-tool/references/tenants.md` + (resolve `$CLAUDETOOLS_ROOT` from `identity.json`). Set the tenant's **Onboarded** column to + `YES` and add a dated Notes line listing what was consented + roles assigned. If the tenant + isn't in the table yet, add a row (Display Name | Domain | Tenant ID | Onboarded | Notes). +2. **Client wiki / CONTEXT.** If `wiki/clients/.md` exists, note tenant onboarding under + Cloud/M365 (tenant ID, "remediation suite onboarded YYYY-MM-DD, all apps + roles"). +3. Use UTC dates. + +## Conventions & guardrails + +- **Outward-facing action.** Sending a consent link to a customer and provisioning apps in + their tenant is customer-facing. Confirm the target tenant with the user before generating + the link, and again before running `provision` (the Tenant Admin grant is high-privilege). +- **One link only.** Do not send the customer the per-app consent URLs unless `provision` + reports that a specific app failed programmatic consent — then `onboard-tenant.sh` prints the + fallback per-app URLs. The whole point is a single click. +- **Idempotent.** Re-running `provision` on an already-onboarded tenant is safe — every grant + and role assignment checks-before-creating and treats "already exists" as success. +- **Vault.** Token acquisition uses the SOPS vault via `identity.json.vault_path`. On a machine + where the skill resolves the wrong identity.json (no `vault_path`), export + `VAULT_ROOT_ENV=` before running (known remediation-tool quirk). +- **Break-glass / least privilege.** This skill only provisions the standing app suite. It does + NOT touch customer user accounts, CA policies, or break-glass accounts — that's + `remediation-tool` territory. + +## Common results / troubleshooting + +- `[WARNING] Tenant Admin app not yet consented` (exit 2): the customer hasn't accepted yet, or + accepted as a non-Global-Admin. Re-send the link; confirm they're a Global Administrator. +- `AADSTS7000229`: the SP isn't in the tenant — same as not-consented; resend the link. +- A role row shows `ERROR`: usually transient Graph replication. Re-run `provision` once; if it + persists, the customer may need to re-accept the Tenant Admin consent (the script prints the + re-consent URL). +- `vault_path not set`: export `VAULT_ROOT_ENV` (see Vault above). +- Exchange tasks 403 later despite onboarding: run + `remediation-tool/scripts/assign-exchange-role.sh ` — the Exchange Administrator role + on the Exchange Operator SP is the recurring gap; onboarding assigns it, but verify. + +## Relationship to remediation-tool + +Onboard365 = the front door (get a tenant consented + provisioned). `remediation-tool` = the +work (breach checks, sweeps, mailbox/user/CA remediation) once the tenant is onboarded. After a +successful onboard, a breach check is `remediation-tool` territory. diff --git a/.claude/skills/onboard365/references/customer-consent-instructions.md b/.claude/skills/onboard365/references/customer-consent-instructions.md new file mode 100644 index 0000000..3c82ebd --- /dev/null +++ b/.claude/skills/onboard365/references/customer-consent-instructions.md @@ -0,0 +1,40 @@ +# Customer Consent Instructions (template) + +Use this when handing the single consent link to a customer's Global Administrator. +Fill in `{{CUSTOMER}}`, `{{CONSENT_URL}}` (from `onboard365.sh link `), and your name. + +--- + +**Subject:** Action needed: one-click approval to connect Arizona Computer Guru to your Microsoft 365 + +Hi {{ADMIN_NAME}}, + +To manage and protect your Microsoft 365 environment, we need a one-time approval from a +Microsoft 365 **Global Administrator** at {{CUSTOMER}}. This is a single click — you won't need +to approve anything else after this. + +1. Open this link while signed in as a Global Administrator: + + {{CONSENT_URL}} + +2. Review the screen (it will show **"ComputerGuru Tenant Admin"**) and click **Accept**. + +That's it. Once you've accepted, reply to let us know and we'll finish the setup on our end. + +Thanks, +{{TECH_NAME}} +Arizona Computer Guru · 520.304.8300 + +--- + +## Notes for the tech (not for the customer) + +- The approver MUST be a **Global Administrator**. A User Admin / other role cannot grant + application admin consent — the Accept will fail or be greyed out. +- The single grant is for **ComputerGuru Tenant Admin** only. After they accept, run + `onboard365.sh provision ` — that creates the other app SPs and assigns roles with no + further customer interaction. +- If they report an error like "Need admin approval" / "AADSTS650056" / "AADSTS7000229" on the + link, they almost always signed in with a non-GA account. Have them retry as a GA. +- Don't send the per-app links. One link is the whole point. Per-app fallback URLs only come into + play if `onboard-tenant.sh` reports a specific app failed programmatic consent. diff --git a/.claude/skills/onboard365/scripts/onboard365.sh b/.claude/skills/onboard365/scripts/onboard365.sh new file mode 100644 index 0000000..cefdcb8 --- /dev/null +++ b/.claude/skills/onboard365/scripts/onboard365.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env bash +# Onboard365 — single-consent onboarding of a customer M365 tenant to the +# ComputerGuru remediation app suite. +# +# The customer Global Admin consents ONCE to the "ComputerGuru Tenant Admin" app. +# Everything else (the other SPs, their Graph/EXO/Defender permissions, and the +# Entra directory roles) is provisioned programmatically by the reused +# remediation-tool/onboard-tenant.sh — no further customer clicks. +# +# Usage: +# onboard365.sh # smart: link if not consented, else provision +# onboard365.sh link # print the ONE consent URL + customer steps +# onboard365.sh status # dry-run: show current consent/role state +# onboard365.sh provision # after consent: provision all apps + roles +# +# Exit codes mirror onboard-tenant.sh for provision/status (0 ok, 2 not consented, +# 10 partial). link always exits 0. +set -euo pipefail + +TENANT_ADMIN_APPID="709e6eed-0711-4875-9c44-2d3518c47063" +CONSENT_BASE="https://login.microsoftonline.com" +CONSENT_REDIRECT="https://azcomputerguru.com" + +# ── Locate the reused remediation-tool scripts ──────────────────────────────── +# Prefer the applied global copy (stable path on every fleet machine); fall back +# to the repo copy via identity.json.claudetools_root. +find_rtool() { + local cands=("$HOME/.claude/skills/remediation-tool/scripts") + local idf="$HOME/.claude/identity.json" + if [[ -f "$idf" ]] && command -v jq >/dev/null 2>&1; then + local root + root=$(jq -r '.claudetools_root // empty' "$idf" 2>/dev/null || true) + [[ -n "$root" ]] && cands+=("$root/.claude/skills/remediation-tool/scripts") + fi + local c + for c in "${cands[@]}"; do + [[ -f "$c/onboard-tenant.sh" ]] && { echo "$c"; return 0; } + done + return 1 +} + +RT="$(find_rtool)" || { + echo "[ERROR] remediation-tool scripts not found." >&2 + echo " Expected: \$HOME/.claude/skills/remediation-tool/scripts/onboard-tenant.sh" >&2 + echo " Run a repo sync, or check identity.json.claudetools_root." >&2 + exit 3 +} + +# ── Parse args (allow a bare domain as smart mode) ──────────────────────────── +SUB="${1:-}" +[[ -z "$SUB" ]] && { echo "usage: onboard365.sh " >&2; exit 64; } +case "$SUB" in + link|status|provision|auto) + TARGET="${2:-}" + [[ -z "$TARGET" ]] && { echo "usage: onboard365.sh $SUB " >&2; exit 64; } + ;; + *) + TARGET="$SUB"; SUB="auto" + ;; +esac + +resolve() { "$RT/resolve-tenant.sh" "$1" 2>/dev/null || echo "$1"; } + +print_link() { + local t="$1" + cat </dev/null 2>/tmp/onboard365-tok.err; } + +case "$SUB" in + link) + print_link "$(resolve "$TARGET")" + ;; + status) + echo "[INFO] Onboard365 status (dry-run) for $TARGET — no changes will be made" + "$RT/onboard-tenant.sh" "$TARGET" --dry-run + ;; + provision) + echo "[INFO] Onboard365 provisioning suite for $TARGET (single-consent model)" + "$RT/onboard-tenant.sh" "$TARGET" + ;; + auto) + T="$(resolve "$TARGET")" + if is_consented "$T"; then + echo "[INFO] Tenant Admin already consented for $TARGET — provisioning the suite..." + "$RT/onboard-tenant.sh" "$TARGET" + else + echo "[INFO] Tenant Admin not yet consented for $TARGET — generating the single consent link." + print_link "$T" + exit 2 + fi + ;; +esac diff --git a/session-logs/2026-06/2026-06-10-mike-onboard365-skill.md b/session-logs/2026-06/2026-06-10-mike-onboard365-skill.md new file mode 100644 index 0000000..2cc6765 --- /dev/null +++ b/session-logs/2026-06/2026-06-10-mike-onboard365-skill.md @@ -0,0 +1,75 @@ +# Onboard365 Skill — Single-Consent M365 Tenant Onboarding + +## User +- **User:** Mike Swanson (mike) +- **Machine:** GURU-5070 +- **Role:** admin + +## Session Summary + +Built a new `onboard365` skill (+ `/onboard365` command) that drives single-consent onboarding of a customer M365 tenant to the ComputerGuru remediation app suite. The motivating insight, discovered while reading the existing `remediation-tool` scripts, is that the single-consent model is already implemented inside `onboard-tenant.sh`: the customer Global Admin consents once to the Tenant Admin app (which holds Application.ReadWrite.All + AppRoleAssignment.ReadWrite.All + RoleManagement.ReadWrite.Directory), and the script then programmatically creates the other app service principals, grants all their Graph/EXO/Defender app-role assignments, and assigns the required Entra directory roles — no further customer clicks. Onboard365 is a clean, first-class front-end for that flow. + +The skill was deliberately built as a thin orchestrator that reuses the existing remediation-tool scripts (resolve-tenant.sh, get-token.sh, onboard-tenant.sh) at their stable applied path ($HOME/.claude/skills/remediation-tool/scripts), with a repo fallback resolved via identity.json.claudetools_root. This avoids duplicating the role GUIDs, app IDs, and consent logic, which evolve over time and would otherwise drift between two copies. + +Created four files in the repo (the synced source of truth that propagates to the fleet via sync.sh Phase 5c): the SKILL.md playbook, scripts/onboard365.sh (link/status/provision/smart-auto modes), references/customer-consent-instructions.md (a ready-to-send customer email template), and commands/onboard365.md (thin /onboard365 entry point). The script was made executable. + +Validated end-to-end on this machine: `link` mode resolved grabblaw.com to its tenant GUID and produced the correct Tenant Admin consent URL; `status` (dry-run) ran the full reuse chain (resolve-tenant -> get-token tenant-admin -> onboard-tenant.sh --dry-run) against the already-onboarded grabblaw.com tenant and reported every role PRESENT/[OK] with no changes made, and correctly auto-skipped Defender (no MDE license). Both the skill and command registered (visible in the skill/command lists). + +## Key Decisions + +- Reuse, not duplicate: onboard365.sh calls the existing remediation-tool scripts rather than copying onboard-tenant.sh, so role GUIDs/app IDs/consent logic live in exactly one maintained place. +- Built in the REPO `.claude/skills/` (not the applied global `~/.claude/skills/`), because sync.sh Phase 5c copies repo skills one-way to global on every machine. The repo is the source of truth. +- Cross-skill script reference uses the stable applied path `$HOME/.claude/skills/remediation-tool/scripts` (present on every fleet machine after sync), with a repo fallback via identity.json.claudetools_root. +- Recording (tenant registry update) is a documented Claude step in SKILL.md rather than script logic — and it points at the REPO copy of remediation-tool/references/tenants.md (resolved via claudetools_root) so the Onboarded=YES update persists and syncs, instead of editing the ephemeral applied copy. +- Kept the skill scoped to provisioning the app suite only; user/CA/break-glass actions remain remediation-tool territory. + +## Problems Encountered + +- The remediation-tool scripts resolve their identity.json relative to the skill install dir, so on GURU-5070 they read the HOME identity (no vault_path) and fail token acquisition. Same known quirk as earlier this session; worked around with `export VAULT_ROOT_ENV=D:/vault` for the integration test. Documented the workaround in SKILL.md (Vault section). + +## Configuration Changes + +- Created `.claude/skills/onboard365/SKILL.md` +- Created `.claude/skills/onboard365/scripts/onboard365.sh` (chmod +x) +- Created `.claude/skills/onboard365/references/customer-consent-instructions.md` +- Created `.claude/commands/onboard365.md` + +## Credentials & Secrets + +- None created. The skill uses existing app credentials via the SOPS vault (msp-tools/computerguru-*.sops.yaml) through the reused get-token.sh. +- Tenant Admin app id referenced by the consent URL: `709e6eed-0711-4875-9c44-2d3518c47063` (public app id, not a secret). + +## Infrastructure & Servers + +- Consent URL pattern: `https://login.microsoftonline.com//adminconsent?client_id=709e6eed-0711-4875-9c44-2d3518c47063&redirect_uri=https://azcomputerguru.com&prompt=consent` +- App suite provisioned by onboard-tenant.sh: Security Investigator (bfbc12a4-…), Exchange Operator (b43e7342-…), User Manager (64fac46b-…), Tenant Admin (709e6eed-…), Defender Add-on (dbf8ad1a-…). +- Directory roles assigned: Sec Inv + Exch Op -> Exchange Administrator; User Manager -> User Administrator + Authentication Administrator; Tenant Admin -> Conditional Access Administrator. Defender = ATP API only (no directory role; MDE-licensed tenants only). +- Test tenant: grabblaw.com = `032b383e-96e4-491b-880d-3fd3295672c3` (already onboarded; used for read-only dry-run validation). + +## Commands & Outputs + +```bash +# usage +onboard365.sh # smart: link if not consented, else provision +onboard365.sh link # single Tenant Admin consent URL + customer steps +onboard365.sh status # dry-run consent/role state +onboard365.sh provision # provision all apps + roles after consent + +# validation (this session) +bash -n onboard365.sh # syntax OK +onboard365.sh link grabblaw.com # resolved tenant + built URL +VAULT_ROOT_ENV=D:/vault onboard365.sh status grabblaw.com # all roles PRESENT/[OK], no changes +``` + +## Pending / Incomplete Tasks + +- The four new files are committed via this save's sync (see Post-commit). They propagate to other fleet machines' ~/.claude/skills on their next sync (Phase 5c). +- Not yet exercised against a brand-new (un-consented) tenant end-to-end — provision path is the same onboard-tenant.sh already in production use, but a real first-time onboard would be the final confidence check. +- Fleet quirk still open: remediation-tool scripts need VAULT_ROOT_ENV or a vault_path fix in the home identity.json on GURU-5070. + +## Reference Information + +- Skill dir: `.claude/skills/onboard365/` +- Command: `.claude/commands/onboard365.md` (`/onboard365`) +- Reused scripts: `.claude/skills/remediation-tool/scripts/{resolve-tenant,get-token,onboard-tenant,assign-exchange-role}.sh` +- Tenant registry (update Onboarded=YES here, repo copy): `.claude/skills/remediation-tool/references/tenants.md`