harness: fleet-wide functional-error + correction + friction logging
Add .claude/scripts/log-skill-error.sh — the canonical agent error log helper (writes errorlog.md in DATE | MACHINE | skill | [type] error format, soft-fails). Three categories: execution failures (default), user corrections (--correction), and preventable self-inflicted friction (--friction; cite ref= when it repeats a documented gotcha). Goal: stop paying tokens twice for the same avoidable mistake. - CLAUDE.md: make logging mandatory for all skills + corrections + friction. - skill-creator: new skills must wire in the helper (guidance + checklist). - Retrofit every skill script's genuine failure branches to call the helper (b2/bitdefender/mailprotector/packetdial/coord python CLIs; remediation-tool + onboard365 bash; vault, rmm-auth, post-bot-alert, agy, grok, 1password, run-onboarding-diagnostic). Handled conditions + self-tests left alone. - errorlog.md: broaden header to cover skills + harness + corrections; seed this session's corrections (INKY, Mail.Send token-audience, omnibox-strictness) and friction (git-bash /tmp, env-persistence, argv-limit, PowerShell var-case). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -86,8 +86,10 @@ process_one() {
|
||||
case "$rc" in
|
||||
201) echo "ASSIGNED (Exchange Admin -> Exchange Operator SP)" ;;
|
||||
400) if echo "$body" | grep -qiE 'conflicting object|already (exist|present)'; then echo "OK (already assigned)"
|
||||
else echo "ERROR (HTTP 400: $(echo "$body" | jqr '.error.message // .' | head -c 120))"; fi ;;
|
||||
*) echo "ERROR (HTTP $rc: $(echo "$body" | jqr '.error.message // .' | head -c 120))" ;;
|
||||
else echo "ERROR (HTTP 400: $(echo "$body" | jqr '.error.message // .' | head -c 120))"
|
||||
bash "$REPO_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "assign-exchange-role: role assignment POST failed" --context "tenant=$tgt http=400 msg=$(echo "$body" | jqr '.error.message // .' | head -c 80)" >/dev/null 2>&1 || true; fi ;;
|
||||
*) echo "ERROR (HTTP $rc: $(echo "$body" | jqr '.error.message // .' | head -c 120))"
|
||||
bash "$REPO_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "assign-exchange-role: role assignment POST failed" --context "tenant=$tgt http=$rc msg=$(echo "$body" | jqr '.error.message // .' | head -c 80)" >/dev/null 2>&1 || true ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -95,7 +97,7 @@ echo "=== assign-exchange-role [mode=$MODE] ==="
|
||||
echo "Role: Exchange Administrator ($EXCH_ADMIN_TEMPLATE) -> SP: Exchange Operator ($EXCHANGE_OP_APPID)"
|
||||
echo "------------------------------------------------------------------------"
|
||||
if [ "$TARGET" = "--all" ]; then
|
||||
[ -f "$TENANTS_MD" ] || { echo "[ERROR] tenants.md not found: $TENANTS_MD" >&2; exit 66; }
|
||||
[ -f "$TENANTS_MD" ] || { echo "[ERROR] tenants.md not found: $TENANTS_MD" >&2; bash "$REPO_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "assign-exchange-role: --all run but references/tenants.md not found" --context "path=$TENANTS_MD" >/dev/null 2>&1 || true; exit 66; }
|
||||
# extract tenant GUIDs from the markdown table (column 3)
|
||||
grep -oE '[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}' "$TENANTS_MD" \
|
||||
| sort -u | while read -r tid; do process_one "$tid"; done
|
||||
|
||||
@@ -239,6 +239,7 @@ case "$AUTH_OVERRIDE" in
|
||||
if [[ -z "$CERT_X5T" || -z "$CERT_KEY_B64" ]]; then
|
||||
echo "ERROR: REMEDIATION_AUTH=cert but cert fields missing in vault ($VAULT_PATH)" >&2
|
||||
echo " Required fields under credentials: cert_thumbprint_b64url, cert_private_key_pem_b64" >&2
|
||||
bash "$CLAUDETOOLS_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "get-token: cert auth forced but cert fields missing in vault" --context "tier=$TIER vault=$VAULT_PATH" >/dev/null 2>&1 || true
|
||||
exit 4
|
||||
fi
|
||||
AUTH_METHOD="cert"
|
||||
@@ -251,6 +252,7 @@ case "$AUTH_OVERRIDE" in
|
||||
if [[ -z "$CLIENT_SECRET" ]]; then
|
||||
echo "ERROR: REMEDIATION_AUTH=secret but client_secret missing in vault ($VAULT_PATH)" >&2
|
||||
echo " Check field: credentials.client_secret (or credentials.credential for older entries)" >&2
|
||||
bash "$CLAUDETOOLS_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "get-token: secret auth forced but client_secret missing in vault" --context "tier=$TIER vault=$VAULT_PATH" >/dev/null 2>&1 || true
|
||||
exit 4
|
||||
fi
|
||||
AUTH_METHOD="secret"
|
||||
@@ -269,6 +271,7 @@ case "$AUTH_OVERRIDE" in
|
||||
echo "ERROR: no usable credential found in $VAULT_PATH" >&2
|
||||
echo " Need either credentials.cert_thumbprint_b64url + credentials.cert_private_key_pem_b64," >&2
|
||||
echo " or credentials.client_secret (or legacy credentials.credential)." >&2
|
||||
bash "$CLAUDETOOLS_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "get-token: no usable credential (cert or client_secret) in vault" --context "tier=$TIER vault=$VAULT_PATH" >/dev/null 2>&1 || true
|
||||
exit 4
|
||||
fi
|
||||
AUTH_METHOD="secret"
|
||||
@@ -336,6 +339,7 @@ PY
|
||||
if [[ $ASSERT_RC -ne 0 || -z "$CLIENT_ASSERTION" ]]; then
|
||||
echo "ERROR: failed to build client_assertion JWT" >&2
|
||||
[[ -n "$CLIENT_ASSERTION" ]] && echo "$CLIENT_ASSERTION" >&2
|
||||
bash "$CLAUDETOOLS_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "get-token: failed to build client_assertion JWT (cert auth)" --context "tier=$TIER rc=$ASSERT_RC" >/dev/null 2>&1 || true
|
||||
exit 4
|
||||
fi
|
||||
|
||||
@@ -371,11 +375,13 @@ if [[ -z "$TOKEN" ]]; then
|
||||
echo " After the admin accepts, run onboard-tenant.sh to assign required directory roles:" >&2
|
||||
SCRIPT_DIR_ERR="$(dirname "${BASH_SOURCE[0]}")"
|
||||
echo " bash ${SCRIPT_DIR_ERR}/onboard-tenant.sh ${TARGET}" >&2
|
||||
bash "$CLAUDETOOLS_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "get-token: AADSTS7000229 — app not consented in tenant" --context "tenant=$TENANT_ID tier=$TIER auth=$AUTH_METHOD" >/dev/null 2>&1 || true
|
||||
exit 5
|
||||
fi
|
||||
|
||||
echo "ERROR: token request failed (tenant=$TENANT_ID tier=$TIER auth=$AUTH_METHOD)" >&2
|
||||
echo "$RESP" >&2
|
||||
bash "$CLAUDETOOLS_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "get-token: token request failed (no access_token)" --context "tenant=$TENANT_ID tier=$TIER auth=$AUTH_METHOD err=${ERROR_CODE:-none}" >/dev/null 2>&1 || true
|
||||
exit 5
|
||||
fi
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
__ROOT="${CLAUDETOOLS_ROOT:-$(cd "$SCRIPT_DIR/../../../.." && pwd)}"
|
||||
|
||||
TARGET="${1:?Usage: onboard-tenant.sh <domain-or-tenant-id> [--dry-run]}"
|
||||
DRY_RUN=false
|
||||
@@ -182,6 +183,7 @@ create_sp_if_missing() {
|
||||
return 0
|
||||
fi
|
||||
echo " [ERROR] Failed to create SP for $app_name: $(echo "$resp" | jq -r '.error.message // empty')" >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "onboard-tenant: failed to create service principal" --context "app=$app_name appId=$app_id msg=$(echo "$resp" | jq -r '.error.message // empty' | head -c 80)" >/dev/null 2>&1 || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -239,6 +241,7 @@ grant_app_role() {
|
||||
return 0
|
||||
fi
|
||||
echo " [ERROR] grant_app_role failed for $role_id: $(echo "$resp" | jq -r '.error.message // "unknown"')" >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "onboard-tenant: grant_app_role appRoleAssignment failed" --context "role=$role_id msg=$(echo "$resp" | jq -r '.error.message // "unknown"' | head -c 80)" >/dev/null 2>&1 || true
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@@ -380,6 +383,7 @@ assign_role() {
|
||||
fi
|
||||
echo " [ERROR] Failed to assign $role_name" >&2
|
||||
echo " Response: $resp" >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "onboard-tenant: failed to assign directory role" --context "role=$role_name sp=$sp_oid msg=$(echo "$resp" | jq -r '.error.message // empty' | head -c 80)" >/dev/null 2>&1 || true
|
||||
return 1
|
||||
fi
|
||||
echo " [OK] $role_name assigned (assignment id=$assigned_id)"
|
||||
@@ -390,6 +394,7 @@ echo "[INFO] Resolving tenant: $TARGET"
|
||||
TENANT_ID=$("$SCRIPT_DIR/resolve-tenant.sh" "$TARGET")
|
||||
if [[ -z "$TENANT_ID" ]]; then
|
||||
echo "[ERROR] Could not resolve tenant ID for: $TARGET" >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "onboard-tenant: could not resolve tenant ID" --context "target=$TARGET" >/dev/null 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -412,6 +417,7 @@ if [[ $GET_TOKEN_EXIT -ne 0 ]]; then
|
||||
fi
|
||||
echo "[ERROR] Failed to acquire Tenant Admin token (exit $GET_TOKEN_EXIT)" >&2
|
||||
echo "$TOKEN_ERR" >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "onboard-tenant: failed to acquire Tenant Admin token" --context "tenant=$TENANT_ID exit=$GET_TOKEN_EXIT" >/dev/null 2>&1 || true
|
||||
exit 5
|
||||
fi
|
||||
TENANT_ADMIN_TOKEN="$TENANT_ADMIN_TOKEN_OUT"
|
||||
@@ -440,6 +446,7 @@ DEFENDER_SP_OID=$(get_sp_oid "$TENANT_ADMIN_TOKEN" "$DEFENDER_APP_ID")
|
||||
|
||||
if [[ -z "$GRAPH_SP_OID" ]]; then
|
||||
echo "[ERROR] Microsoft Graph SP missing — cannot grant app permissions" >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "onboard-tenant: Microsoft Graph SP missing in tenant — cannot grant app permissions" --context "tenant=$TENANT_ID" >/dev/null 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ ROLE_MGMT_PERMISSION_ID="9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLAUDETOOLS_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
IDENTITY_FILE="$CLAUDETOOLS_ROOT/.claude/identity.json"
|
||||
# Repo root for the functional-error logger (4 levels up from this scripts dir).
|
||||
__ELOG_ROOT="${CLAUDETOOLS_ROOT_ENV:-$(cd "$SCRIPT_DIR/../../../.." && pwd)}"
|
||||
|
||||
VAULT_ROOT="${VAULT_PATH:-}"
|
||||
if [[ -z "$VAULT_ROOT" && -f "$IDENTITY_FILE" ]]; then
|
||||
@@ -39,7 +41,7 @@ CLIENT_SECRET=$(bash "$VAULT_ROOT/scripts/vault.sh" get-field "$MANAGEMENT_VAULT
|
||||
if [[ -z "$CLIENT_SECRET" ]]; then
|
||||
CLIENT_SECRET=$(bash "$VAULT_ROOT/scripts/vault.sh" get-field "$MANAGEMENT_VAULT_PATH" credentials.credential 2>/dev/null | tr -d '\r\n' || true)
|
||||
fi
|
||||
[[ -z "$CLIENT_SECRET" ]] && { echo "[ERROR] Could not read secret from $MANAGEMENT_VAULT_PATH" >&2; exit 4; }
|
||||
[[ -z "$CLIENT_SECRET" ]] && { echo "[ERROR] Could not read secret from $MANAGEMENT_VAULT_PATH" >&2; bash "$__ELOG_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "patch-tenant-admin-manifest: could not read Management app secret from vault" --context "vault=$MANAGEMENT_VAULT_PATH" >/dev/null 2>&1 || true; exit 4; }
|
||||
echo "[OK] Management app secret retrieved"
|
||||
|
||||
# ── Step 2: Get Management app token (home tenant) ───────────────────────────
|
||||
@@ -55,6 +57,7 @@ MGMT_TOKEN=$(echo "$TOKEN_RESP" | jq -r '.access_token // empty')
|
||||
if [[ -z "$MGMT_TOKEN" ]]; then
|
||||
echo "[ERROR] Failed to acquire Management app token" >&2
|
||||
echo "$TOKEN_RESP" >&2
|
||||
bash "$__ELOG_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "patch-tenant-admin-manifest: failed to acquire Management app token (home tenant)" --context "err=$(echo "$TOKEN_RESP" | jq -r '.error // empty' 2>/dev/null)" >/dev/null 2>&1 || true
|
||||
exit 5
|
||||
fi
|
||||
echo "[OK] Management app token acquired"
|
||||
@@ -73,6 +76,7 @@ APP_DISPLAY=$(echo "$APP_RESP" | jq -r '.value[0].displayName // empty')
|
||||
if [[ -z "$APP_OBJ_ID" ]]; then
|
||||
echo "[ERROR] Tenant Admin application not found (appId=$TENANT_ADMIN_APP_ID)" >&2
|
||||
echo "Response: $APP_RESP" >&2
|
||||
bash "$__ELOG_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "patch-tenant-admin-manifest: Tenant Admin application registration not found" --context "appId=$TENANT_ADMIN_APP_ID" >/dev/null 2>&1 || true
|
||||
exit 6
|
||||
fi
|
||||
echo "[OK] Found app: $APP_DISPLAY (objectId=$APP_OBJ_ID)"
|
||||
@@ -108,6 +112,7 @@ else
|
||||
echo "[OK] App manifest patched (HTTP 204)"
|
||||
else
|
||||
echo "[ERROR] PATCH returned HTTP $PATCH_RESP" >&2
|
||||
bash "$__ELOG_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "patch-tenant-admin-manifest: app manifest PATCH failed" --context "appObjId=$APP_OBJ_ID http=$PATCH_RESP" >/dev/null 2>&1 || true
|
||||
exit 7
|
||||
fi
|
||||
fi
|
||||
@@ -121,7 +126,7 @@ TA_SP_RESP=$(curl -s --max-time 15 \
|
||||
--data-urlencode "\$select=id,displayName" \
|
||||
"https://graph.microsoft.com/v1.0/servicePrincipals")
|
||||
TA_SP_OID=$(echo "$TA_SP_RESP" | jq -r '.value[0].id // empty')
|
||||
[[ -z "$TA_SP_OID" ]] && { echo "[ERROR] Tenant Admin SP not found in home tenant" >&2; exit 8; }
|
||||
[[ -z "$TA_SP_OID" ]] && { echo "[ERROR] Tenant Admin SP not found in home tenant" >&2; bash "$__ELOG_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "patch-tenant-admin-manifest: Tenant Admin SP not found in home tenant" --context "appId=$TENANT_ADMIN_APP_ID" >/dev/null 2>&1 || true; exit 8; }
|
||||
echo "[OK] Tenant Admin SP: $TA_SP_OID"
|
||||
|
||||
echo "[INFO] Locating Microsoft Graph SP in home tenant..."
|
||||
@@ -132,7 +137,7 @@ GRAPH_SP_RESP=$(curl -s --max-time 15 \
|
||||
--data-urlencode "\$select=id" \
|
||||
"https://graph.microsoft.com/v1.0/servicePrincipals")
|
||||
GRAPH_SP_OID=$(echo "$GRAPH_SP_RESP" | jq -r '.value[0].id // empty')
|
||||
[[ -z "$GRAPH_SP_OID" ]] && { echo "[ERROR] Microsoft Graph SP not found in home tenant" >&2; exit 8; }
|
||||
[[ -z "$GRAPH_SP_OID" ]] && { echo "[ERROR] Microsoft Graph SP not found in home tenant" >&2; bash "$__ELOG_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "patch-tenant-admin-manifest: Microsoft Graph SP not found in home tenant" >/dev/null 2>&1 || true; exit 8; }
|
||||
echo "[OK] Microsoft Graph SP: $GRAPH_SP_OID"
|
||||
|
||||
# ── Step 6: Check if appRoleAssignment already granted ────────────────────────
|
||||
@@ -165,6 +170,7 @@ else
|
||||
if [[ -z "$GRANT_ID" ]]; then
|
||||
echo "[ERROR] Failed to grant appRoleAssignment" >&2
|
||||
echo "$GRANT_RESP" >&2
|
||||
bash "$__ELOG_ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "patch-tenant-admin-manifest: failed to grant RoleManagement.ReadWrite.Directory appRoleAssignment" --context "msg=$(echo "$GRANT_RESP" | jq -r '.error.message // empty' 2>/dev/null | head -c 80)" >/dev/null 2>&1 || true
|
||||
exit 9
|
||||
fi
|
||||
echo "[OK] appRoleAssignment granted (id=$GRANT_ID)"
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
__ROOT="${CLAUDETOOLS_ROOT:-$(cd "$SCRIPT_DIR/../../../.." && pwd)}"
|
||||
|
||||
TENANT_INPUT="${1:?usage: reset-password.sh <tenant|domain> <upn> <new-password> [--force-change]}"
|
||||
UPN="${2:?usage: reset-password.sh <tenant|domain> <upn> <new-password> [--force-change]}"
|
||||
@@ -43,7 +44,7 @@ G="https://graph.microsoft.com/v1.0"
|
||||
# --- resolve target user object id ---
|
||||
UID_=$(curl -s "${GH[@]}" "$G/users/${UPN}?\$select=id" | tr -d '\000-\037' \
|
||||
| python -c "import sys,json;print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||
[[ -z "$UID_" ]] && { echo "[ERROR] user not found: $UPN" >&2; exit 1; }
|
||||
[[ -z "$UID_" ]] && { echo "[ERROR] user not found: $UPN" >&2; bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "reset-password: target user not found / Graph returned no id" --context "tenant=$TENANT_ID upn=$UPN" >/dev/null 2>&1 || true; exit 1; }
|
||||
echo "[info] tenant=$TENANT_ID target=$UPN id=$UID_ force_change=$FORCE_CHANGE"
|
||||
|
||||
# --- build payload (single-quoted heredoc would block $NEWPW; use python to emit JSON safely) ---
|
||||
@@ -61,6 +62,7 @@ fi
|
||||
if [[ "$CODE" != "403" ]]; then
|
||||
echo "[ERROR] unexpected HTTP $CODE on password PATCH" >&2
|
||||
curl -s -X PATCH "${GH[@]}" "$G/users/$UID_" --data-binary "$PAYLOAD" | tr -d '\000-\037' >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "reset-password: unexpected HTTP on password PATCH" --context "tenant=$TENANT_ID upn=$UPN http=$CODE" >/dev/null 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -69,7 +71,7 @@ echo "[info] 403 on direct reset (target likely holds an admin role) -> JIT elev
|
||||
# --- resolve tenant-admin SP object id ---
|
||||
SPID=$(curl -s "${GH[@]}" "$G/servicePrincipals(appId='$TENANT_ADMIN_APPID')?\$select=id" | tr -d '\000-\037' \
|
||||
| python -c "import sys,json;print(json.load(sys.stdin).get('id',''))")
|
||||
[[ -z "$SPID" ]] && { echo "[ERROR] could not resolve Tenant Admin service principal" >&2; exit 1; }
|
||||
[[ -z "$SPID" ]] && { echo "[ERROR] could not resolve Tenant Admin service principal" >&2; bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "reset-password: could not resolve Tenant Admin SP for JIT elevation" --context "tenant=$TENANT_ID" >/dev/null 2>&1 || true; exit 1; }
|
||||
|
||||
# --- does the SP already hold Privileged Authentication Administrator? ---
|
||||
EXISTING=$(curl -s "${GH[@]}" "$G/roleManagement/directory/roleAssignments?\$filter=principalId+eq+'$SPID'+and+roleDefinitionId+eq+'$PAA_ROLE_ID'" \
|
||||
@@ -82,7 +84,7 @@ else
|
||||
ASSIGN_BODY=$(SPID="$SPID" RID="$PAA_ROLE_ID" python -c "import os,json;print(json.dumps({'principalId':os.environ['SPID'],'roleDefinitionId':os.environ['RID'],'directoryScopeId':'/'}))")
|
||||
CREATED_ASSIGNMENT=$(curl -s -X POST "${GH[@]}" "$G/roleManagement/directory/roleAssignments" --data-binary "$ASSIGN_BODY" \
|
||||
| tr -d '\000-\037' | python -c "import sys,json;d=json.load(sys.stdin);print(d.get('id',''))" 2>/dev/null || true)
|
||||
[[ -z "$CREATED_ASSIGNMENT" ]] && { echo "[ERROR] failed to assign Privileged Authentication Administrator to SP" >&2; exit 1; }
|
||||
[[ -z "$CREATED_ASSIGNMENT" ]] && { echo "[ERROR] failed to assign Privileged Authentication Administrator to SP" >&2; bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "reset-password: failed to assign Privileged Authentication Administrator to SP (JIT elevation)" --context "tenant=$TENANT_ID sp=$SPID" >/dev/null 2>&1 || true; exit 1; }
|
||||
echo "[info] assigned Privileged Authentication Administrator to SP (assignment $CREATED_ASSIGNMENT)"
|
||||
fi
|
||||
|
||||
@@ -90,7 +92,7 @@ fi
|
||||
cleanup() {
|
||||
if [[ -n "$CREATED_ASSIGNMENT" ]]; then
|
||||
DC=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE "${GH[@]}" "$G/roleManagement/directory/roleAssignments/$CREATED_ASSIGNMENT")
|
||||
if [[ "$DC" == "204" ]]; then echo "[info] removed JIT role assignment (de-elevated)"; else echo "[WARNING] failed to remove JIT role assignment $CREATED_ASSIGNMENT (HTTP $DC) - REMOVE MANUALLY" >&2; fi
|
||||
if [[ "$DC" == "204" ]]; then echo "[info] removed JIT role assignment (de-elevated)"; else echo "[WARNING] failed to remove JIT role assignment $CREATED_ASSIGNMENT (HTTP $DC) - REMOVE MANUALLY" >&2; bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "reset-password: failed to remove JIT Privileged Auth Admin role - standing privilege left behind, REMOVE MANUALLY" --context "tenant=$TENANT_ID assignment=$CREATED_ASSIGNMENT http=$DC" >/dev/null 2>&1 || true; fi
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
@@ -108,4 +110,5 @@ done
|
||||
|
||||
echo "[ERROR] password reset still failing after elevation (last HTTP $CODE)" >&2
|
||||
curl -s -X PATCH "${GH[@]}" "$G/users/$UID_" --data-binary "$PAYLOAD" | tr -d '\000-\037' >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "reset-password: reset still failing after JIT elevation + retries" --context "tenant=$TENANT_ID upn=$UPN http=$CODE" >/dev/null 2>&1 || true
|
||||
exit 1
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
# Output (stdout): tenant GUID. Exit 0 on success, 1 on failure.
|
||||
set -euo pipefail
|
||||
|
||||
__ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)}"
|
||||
|
||||
INPUT="${1:?usage: resolve-tenant.sh <domain|upn|tenant-id>}"
|
||||
|
||||
# If it looks like a GUID already, pass through.
|
||||
@@ -31,6 +33,7 @@ TENANT_ID=$(echo "$RESP" | jq -r '.issuer // empty' | sed -E 's|^https://login\.
|
||||
if [[ -z "$TENANT_ID" ]] || [[ ! "$TENANT_ID" =~ ^[0-9a-fA-F]{8}- ]]; then
|
||||
echo "ERROR: could not resolve tenant for domain: $DOMAIN" >&2
|
||||
echo "Response: $RESP" >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "resolve-tenant: OpenID discovery did not return a tenant GUID" --context "domain=$DOMAIN" >/dev/null 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
||||
__ROOT="${CLAUDETOOLS_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)}"
|
||||
|
||||
TENANT_INPUT="${1:?usage: user-breach-check.sh <tenant-id|domain> <upn>}"
|
||||
UPN="${2:?usage: user-breach-check.sh <tenant-id|domain> <upn>}"
|
||||
@@ -28,6 +29,7 @@ UID_=$(jq -r '.id // empty' "$OUT/00_user.json")
|
||||
if [[ -z "$UID_" ]]; then
|
||||
echo "ERROR: user not found or Graph returned error" >&2
|
||||
cat "$OUT/00_user.json" >&2
|
||||
bash "$__ROOT/.claude/scripts/log-skill-error.sh" "remediation-tool" "user-breach-check: user not found or Graph returned error resolving user object" --context "tenant=$TENANT_ID upn=$UPN err=$(jq -r '.error.code // empty' "$OUT/00_user.json" 2>/dev/null)" >/dev/null 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
echo "[info] object id: $UID_"
|
||||
|
||||
Reference in New Issue
Block a user