Session log: multi-user setup, audit + gap fixes, Howard onboarding package
Two session logs: - session-logs/2026-04-16-session.md: cross-cutting (multi-user, audit, infrastructure) - guru-rmm session log appended: MSI installer, Len's Auto Brokerage, Uranus, migration drift Gap fixes: GrepAI initialized + MCP server added, Ollama models pulling, settings.json created (bypassPermissions), MCP_SERVERS.md written. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
98
.claude/skills/remediation-tool/scripts/get-token.sh
Normal file
98
.claude/skills/remediation-tool/scripts/get-token.sh
Normal file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
# Acquire a client-credentials token for the Claude-MSP-Access (ComputerGuru - AI Remediation) app.
|
||||
# Usage: get-token.sh <tenant-id-or-domain> <scope>
|
||||
# <scope>: graph | exchange | defender | sharepoint
|
||||
# Output (stdout): token. Exit 0 on success.
|
||||
# Cache: /tmp/remediation-tool/{tenant-id}/{scope}.jwt (55-min TTL).
|
||||
set -euo pipefail
|
||||
|
||||
CLIENT_ID="fabb3421-8b34-484b-bc17-e46de9703418"
|
||||
|
||||
TARGET="${1:?usage: get-token.sh <tenant-id|domain> <scope>}"
|
||||
SCOPE_NAME="${2:?usage: get-token.sh <tenant-id|domain> <scope>}"
|
||||
|
||||
# Resolve to tenant-id
|
||||
if [[ "$TARGET" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then
|
||||
TENANT_ID="$TARGET"
|
||||
else
|
||||
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
||||
TENANT_ID=$("$SCRIPT_DIR/resolve-tenant.sh" "$TARGET")
|
||||
fi
|
||||
|
||||
case "$SCOPE_NAME" in
|
||||
graph) SCOPE_URL="https://graph.microsoft.com/.default" ;;
|
||||
exchange) SCOPE_URL="https://outlook.office365.com/.default" ;;
|
||||
defender) SCOPE_URL="https://api.securitycenter.microsoft.com/.default" ;;
|
||||
sharepoint)
|
||||
# SharePoint token scope depends on tenant hostname. Caller must set SHAREPOINT_HOST=contoso.sharepoint.com.
|
||||
SCOPE_URL="https://${SHAREPOINT_HOST:?set SHAREPOINT_HOST for sharepoint scope}/.default" ;;
|
||||
*) echo "ERROR: unknown scope '$SCOPE_NAME'. Expected: graph|exchange|defender|sharepoint" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
CACHE_DIR="/tmp/remediation-tool/$TENANT_ID"
|
||||
mkdir -p "$CACHE_DIR"
|
||||
CACHE_FILE="$CACHE_DIR/${SCOPE_NAME}.jwt"
|
||||
|
||||
# Reuse cache if less than 55 minutes old.
|
||||
if [[ -f "$CACHE_FILE" ]] && [[ $(find "$CACHE_FILE" -mmin -55 2>/dev/null) ]]; then
|
||||
cat "$CACHE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Locate the vault repo.
|
||||
VAULT_ROOT=""
|
||||
for candidate in "D:/vault" "$HOME/vault" "/d/vault"; do
|
||||
[[ -d "$candidate" ]] && VAULT_ROOT="$candidate" && break
|
||||
done
|
||||
[[ -z "$VAULT_ROOT" ]] && { echo "ERROR: SOPS vault repo not found at D:/vault or ~/vault" >&2; exit 3; }
|
||||
|
||||
SOPS_FILE="$VAULT_ROOT/msp-tools/claude-msp-access-graph-api.sops.yaml"
|
||||
[[ ! -f "$SOPS_FILE" ]] && { echo "ERROR: SOPS file not found: $SOPS_FILE" >&2; exit 3; }
|
||||
|
||||
# Try vault.sh first; fall back to direct sops+python if vault.sh is broken (e.g. yq shim permission issues on Windows).
|
||||
CLIENT_SECRET=""
|
||||
if [[ -f "$VAULT_ROOT/scripts/vault.sh" ]]; then
|
||||
CLIENT_SECRET=$(bash "$VAULT_ROOT/scripts/vault.sh" get-field msp-tools/claude-msp-access-graph-api.sops.yaml credentials.credential 2>/dev/null | tr -d '\r\n' || true)
|
||||
fi
|
||||
|
||||
if [[ -z "$CLIENT_SECRET" ]]; then
|
||||
# Direct fallback: sops decrypt + python YAML parse. Works without vault.sh / yq.
|
||||
PYTHON_BIN=""
|
||||
for p in python python3 py; do command -v "$p" >/dev/null 2>&1 && PYTHON_BIN="$p" && break; done
|
||||
[[ -z "$PYTHON_BIN" ]] && { echo "ERROR: neither vault.sh worked nor python is available for fallback parse" >&2; exit 3; }
|
||||
command -v sops >/dev/null 2>&1 || { echo "ERROR: sops not on PATH (needed for fallback)" >&2; exit 3; }
|
||||
|
||||
CLIENT_SECRET=$(sops -d "$SOPS_FILE" 2>/dev/null | "$PYTHON_BIN" -c "
|
||||
import sys, re
|
||||
t = sys.stdin.read()
|
||||
# minimal YAML: find 'credentials:' block then 'credential:' key
|
||||
m = re.search(r'^credentials:\s*\n((?:[ \t]+.*\n)+)', t, re.MULTILINE)
|
||||
if not m: sys.exit(1)
|
||||
for line in m.group(1).splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith('credential:'):
|
||||
print(line.split(':', 1)[1].strip().strip('\"').strip(\"'\"))
|
||||
break
|
||||
" | tr -d '\r\n')
|
||||
fi
|
||||
|
||||
[[ -z "$CLIENT_SECRET" ]] && { echo "ERROR: could not read client secret from vault (vault.sh and sops+python fallback both failed)" >&2; exit 4; }
|
||||
|
||||
# Request token.
|
||||
RESP=$(curl -s --max-time 15 -X POST "https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token" \
|
||||
--data-urlencode "client_id=${CLIENT_ID}" \
|
||||
--data-urlencode "client_secret=${CLIENT_SECRET}" \
|
||||
--data-urlencode "scope=${SCOPE_URL}" \
|
||||
--data-urlencode "grant_type=client_credentials")
|
||||
|
||||
TOKEN=$(echo "$RESP" | jq -r '.access_token // empty')
|
||||
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
echo "ERROR: token request failed for tenant=$TENANT_ID scope=$SCOPE_NAME" >&2
|
||||
echo "$RESP" >&2
|
||||
exit 5
|
||||
fi
|
||||
|
||||
echo "$TOKEN" > "$CACHE_FILE"
|
||||
chmod 600 "$CACHE_FILE" 2>/dev/null || true
|
||||
echo "$TOKEN"
|
||||
Reference in New Issue
Block a user