Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion monitor/sre-agent-event-lab/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ azd down --purge
- predown hook `scripts/cleanup-external.sh --yes`: `evidence/agent-setup.json`에 기록된 구독 범위 Monitoring Contributor 할당만 제거합니다. 기록된 principal·역할·범위가 실제 할당과 모두 일치할 때만 삭제하고, 하나라도 어긋나면 아무것도 지우지 않습니다.
- postdown hook `scripts/cleanup-external.sh --reset-image-env --yes`: 기록된 `SRE_CONTAINER_IMAGE`와 `SRE_IMAGE_TAG`를 비웁니다. 삭제가 실제로 성공한 뒤에만 실행되어야 하므로 predown이 아니라 postdown입니다.

중요: predown hook은 `azd down`이 삭제 **확인** 프롬프트를 띄우기 **전에** 실행됩니다. 그 프롬프트에서 **취소**해도 이미 제거된 Monitoring Contributor 할당은 돌아오지 않습니다. 리소스 그룹은 남지만 Agent의 구독 범위 권한은 사라진 상태이므로, 계속 쓰려면 역할 할당을 다시 만들고 `./scripts/lab.sh acknowledge agent-setup`으로 `evidence/agent-setup.json`을 새 할당 ID로 갱신해야 합니다.
중요: predown hook은 `azd down`이 삭제 **확인** 프롬프트를 띄우기 **전에** 실행됩니다. 그 프롬프트에서 **취소**해도 이미 제거된 Monitoring Contributor 할당은 돌아오지 않습니다. 리소스 그룹은 남지만 Agent의 구독 범위 권한은 사라진 상태이므로, 계속 쓰려면 역할 할당을 다시 만들고 `evidence/agent-setup.json`을 새 할당 ID로 직접 갱신한 뒤 `./scripts/lab.sh acknowledge agent-setup`을 실행해야 합니다.

hook이 실패해 손으로 다시 실행할 때는 아래를 직접 호출합니다. `--yes` 없이는 계획만 출력합니다.

Expand Down
72 changes: 66 additions & 6 deletions monitor/sre-agent-event-lab/guides/01-agent-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,52 @@ Agent가 실습 리소스를 읽고 구독의 경고를 훑을 수 있어야 합

Monitoring Contributor는 [Azure Monitor 스캐너](https://learn.microsoft.com/azure/sre-agent/azure-monitor-alerts)가 요구하는 유일한 구독 범위 권한입니다. 할당 ID는 아래에서 기록해 두고 정리 단계에서 자동으로 제거합니다.

포털에서 확인한 두 관리 ID의 object ID를 넣고 역할을 만듭니다. Reader는 azd가 삭제할 리소스 그룹 범위이고, Monitoring Contributor 두 건만 구독 범위이므로 ID를 변수에 보관합니다.

```bash
SUBSCRIPTION_ID="$(azd env get-value AZURE_SUBSCRIPTION_ID)"
RESOURCE_GROUP="$(azd env get-value AZURE_RESOURCE_GROUP)"
SUBSCRIPTION_SCOPE="/subscriptions/${SUBSCRIPTION_ID}"
RESOURCE_GROUP_SCOPE="${SUBSCRIPTION_SCOPE}/resourceGroups/${RESOURCE_GROUP}"
AGENT_PRINCIPAL_ID="<Agent 시스템 할당 ID의 objectId>"
AGENT_UAMI_PRINCIPAL_ID="<Agent 사용자 할당 ID의 objectId>"
SRE_AGENT_ENDPOINT="https://<agent>.<region>.azuresre.ai"

az role assignment create \
--assignee-object-id "${AGENT_PRINCIPAL_ID}" \
--assignee-principal-type ServicePrincipal \
--role "Reader" \
--scope "${RESOURCE_GROUP_SCOPE}" \
--output none
az role assignment create \
--assignee-object-id "${AGENT_UAMI_PRINCIPAL_ID}" \
--assignee-principal-type ServicePrincipal \
--role "Reader" \
--scope "${RESOURCE_GROUP_SCOPE}" \
--output none

MONITORING_CONTRIBUTOR_ASSIGNMENT_ID="$(az role assignment create \
--assignee-object-id "${AGENT_PRINCIPAL_ID}" \
--assignee-principal-type ServicePrincipal \
--role "Monitoring Contributor" \
--scope "${SUBSCRIPTION_SCOPE}" \
--query id -o tsv 2>/dev/null || az role assignment list \
--assignee-object-id "${AGENT_PRINCIPAL_ID}" \
--role "Monitoring Contributor" \
--scope "${SUBSCRIPTION_SCOPE}" \
--query "[0].id" -o tsv)"
UAMI_MONITORING_CONTRIBUTOR_ASSIGNMENT_ID="$(az role assignment create \
--assignee-object-id "${AGENT_UAMI_PRINCIPAL_ID}" \
--assignee-principal-type ServicePrincipal \
--role "Monitoring Contributor" \
--scope "${SUBSCRIPTION_SCOPE}" \
--query id -o tsv 2>/dev/null || az role assignment list \
--assignee-object-id "${AGENT_UAMI_PRINCIPAL_ID}" \
--role "Monitoring Contributor" \
--scope "${SUBSCRIPTION_SCOPE}" \
--query "[0].id" -o tsv)"
```

## Builder > Incident platform 연결

1. 왼쪽에서 **Builder > Incident platform**을 엽니다.
Expand Down Expand Up @@ -98,17 +144,31 @@ azd env set SRE_KNOWLEDGE_PATH "runbooks/incident-response.md"
## 근거 파일 만들기

`evidence/agent-setup.json`은 Git에서 제외되며, 정리 단계가 되돌릴 대상을 이 파일에서만 읽습니다. 값은 모두 식별자와 엔드포인트 URL입니다.
아래 블록은 역할을 만든 위 블록과 같은 셸에서 실행해야 앞에서 설정한 변수와 할당 ID를 그대로 사용합니다.

```bash
cat > evidence/agent-setup.json <<'JSON'
mkdir -p evidence
cat > evidence/agent-setup.json <<JSON
{
"agent_principal_id": "<Agent 시스템 할당 ID의 objectId>",
"agent_user_assigned_principal_id": "<Agent 사용자 할당 ID의 objectId>",
"agent_endpoint": "https://<agent>.<region>.azuresre.ai",
"monitoring_contributor_assignment_id": "/subscriptions/<id>/providers/Microsoft.Authorization/roleAssignments/<guid>",
"uami_monitoring_contributor_assignment_id": "/subscriptions/<id>/providers/Microsoft.Authorization/roleAssignments/<guid>"
"agent_principal_id": "${AGENT_PRINCIPAL_ID}",
"agent_user_assigned_principal_id": "${AGENT_UAMI_PRINCIPAL_ID}",
"agent_endpoint": "${SRE_AGENT_ENDPOINT}",
"monitoring_contributor_assignment_id": "${MONITORING_CONTRIBUTOR_ASSIGNMENT_ID}",
"uami_monitoring_contributor_assignment_id": "${UAMI_MONITORING_CONTRIBUTOR_ASSIGNMENT_ID}"
}
JSON

jq -e \
--arg scope "${SUBSCRIPTION_SCOPE}/providers/Microsoft.Authorization/roleAssignments/" \
'(.monitoring_contributor_assignment_id | startswith($scope))
and (.uami_monitoring_contributor_assignment_id | startswith($scope))
and (.agent_principal_id | length > 0)
and (.agent_user_assigned_principal_id | length > 0)
and (.agent_endpoint | test("^https://[^<>]+$"))' \
evidence/agent-setup.json >/dev/null || {
echo "agent-setup.json is incomplete; re-check the principals, endpoint, and assignment IDs above." >&2
false
}
```

## 점검과 승인
Expand Down
2 changes: 1 addition & 1 deletion monitor/sre-agent-event-lab/guides/02-scenario-s1.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ cd monitor/sre-agent-event-lab
1. Container App의 활성 revision이 다시 정상입니다.
2. 이 실행이 발생시킨 경고가 Azure Monitor에서 `Resolved`가 됩니다.

경고 해제는 최대 15분, 워크로드 정상화는 최대 10분까지 기다립니다. 둘 중 하나라도 시간 안에 확인되지 않으면 실행은 실패로 기록되고 S2는 계속 막힙니다. 실패한 실행은 원인을 고친 뒤 `./scripts/lab.sh run s1`을 다시 실행하면 새 시도로 이어집니다. 다시 실행하는 순간 이전 시도의 `s1_recovered`와 `s1_captured` 기록은 장애를 주입하기 전에 지워지므로, 새 시도가 복구되고 `capture`까지 끝날 때까지 S2는 다시 막힙니다. 이미 성공한 시나리오를 한 번 더 돌릴 때도 같습니다.
경고 해제는 최대 25분, 워크로드 정상화는 최대 10분까지 기다립니다. 1분 주기의 stateful log alert는 실패 요청이 5분 조회 창에서 빠진 뒤에도 조건이 10분간 불충족이어야 `Resolved`가 되므로 여유 시간을 포함합니다. 둘 중 하나라도 시간 안에 확인되지 않으면 실행은 실패로 기록되고 S2는 계속 막힙니다. 실패한 실행은 원인을 고친 뒤 `./scripts/lab.sh run s1`을 다시 실행하면 새 시도로 이어집니다. 다시 실행하는 순간 이전 시도의 `s1_recovered`와 `s1_captured` 기록은 장애를 주입하기 전에 지워지므로, 새 시도가 복구되고 `capture`까지 끝날 때까지 S2는 다시 막힙니다. 이미 성공한 시나리오를 한 번 더 돌릴 때도 같습니다.

되돌리기 자체가 실패하면(예: `az containerapp update` 거부, 새 revision이 준비되지 않음) 스크립트는 `CRITICAL:` 두 줄을 출력하고 0이 아닌 코드로 끝냅니다. 주입한 장애가 그대로 남아 있다는 뜻이므로, 다음 시나리오를 실행하기 전에 `FAILURE_MODE=none`을 수동으로 되돌리고 revision이 정상인지 확인하세요.

Expand Down
21 changes: 17 additions & 4 deletions monitor/sre-agent-event-lab/guides/04-scenario-s3.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ cd monitor/sre-agent-event-lab
| 순서 | 변화 |
|---|---|
| 1 | 워크로드 관리 ID의 `Storage Blob Data Reader` 할당이 Blob 컨테이너 범위에서 삭제됩니다 |
| 2 | `/api/documents`에 요청 60건(동시 4)이 들어가고 모두 HTTP 503을 받습니다 |
| 3 | Application Insights workspace 테이블 `AppDependencies`에 Storage 대상 `ResultCode == "403"`이 쌓입니다 |
| 4 | 5분 창의 403 의존성 실패가 5건을 넘으면 `alert-sre-lab-s3-storage-rbac`(Sev2)이 발생합니다 |
| 5 | 복구로 같은 이름·같은 범위의 역할 할당이 다시 만들어집니다 |
| 2 | 데이터 평면에 권한 삭제가 전파되어 단건 probe가 HTTP 503을 받을 때까지 최대 5분 기다립니다 |
| 3 | `/api/documents`에 요청 60건(동시 4)이 들어가고 모두 HTTP 503을 받습니다 |
| 4 | Application Insights workspace 테이블 `AppDependencies`에 Storage 대상 `ResultCode == "403"`이 쌓입니다 |
| 5 | 5분 창의 403 의존성 실패가 5건을 넘으면 `alert-sre-lab-s3-storage-rbac`(Sev2)이 발생합니다 |
| 6 | 복구로 같은 이름·같은 범위의 역할 할당이 다시 만들어집니다 |

삭제와 복구는 출력에 기록된 Blob 컨테이너 범위의 단일 역할에만 적용됩니다. 구독이나 리소스 그룹 범위의 다른 할당은 건드리지 않습니다.

단건 probe가 제한 시간 안에 503을 확인하지 못하면 `Storage RBAC deletion did not produce HTTP 503 within 300s`를 출력하고 본 부하를 시작하지 않은 채 역할을 복구합니다.

## SRE Agent에서 확인할 항목

- 앱이 돌려준 503과 실제 원인인 Storage 403을 구분하는지
Expand Down Expand Up @@ -58,6 +61,16 @@ cd monitor/sre-agent-event-lab

역할 복구 자체가 실패하면 스크립트는 `CRITICAL:` 두 줄을 출력하고 0이 아닌 코드로 끝냅니다. 워크로드에 Blob 권한이 없는 상태가 그대로 남으므로, 같은 이름·같은 범위의 `Storage Blob Data Reader` 할당을 수동으로 다시 만든 뒤 다음 단계로 넘어가세요.

```bash
az role assignment create \
--name "$(azd env get-value AZURE_BLOB_ROLE_ASSIGNMENT_NAME)" \
--assignee-object-id "$(azd env get-value AZURE_CONTAINER_APP_PRINCIPAL_ID)" \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Reader" \
--scope "$(azd env get-value AZURE_STORAGE_CONTAINER_SCOPE)" \
--output none
```

## 다음 단계

수집한 근거를 채점합니다: [05-results.md](05-results.md)
2 changes: 2 additions & 0 deletions monitor/sre-agent-event-lab/guides/05-results.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ cd monitor/sre-agent-event-lab

시나리오의 `run_status`가 `recovered`가 아니면 채점기는 capture 내용과 관계없이 모든 항목을 `FAIL`·0점으로 기록합니다.

한 번 `conclusion`으로 끝난 capture는 같은 실행에서 다시 수집할 수 없습니다. 두 번째 결과가 필요하면 해당 시나리오를 다시 실행해 새 attempt와 evidence 디렉터리를 만든 뒤 capture합니다.

`impact_scope`는 alert 규칙 자체의 scope를 그대로 옮겨 적는 것으로는 채워지지
않습니다. 이 랩의 모든 alert는 Log Analytics workspace scope입니다
(`infra/alerts.bicep`의 `scopes`/`targetResourceTypes:
Expand Down
19 changes: 17 additions & 2 deletions monitor/sre-agent-event-lab/scripts/capture-scenario.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,17 @@ readonly EVIDENCE_DIR
readonly TIMELINE_FILE="${EVIDENCE_DIR}/timeline.json"
readonly NORMALIZED_FILE="${EVIDENCE_DIR}/normalized-timeline.json"

CURRENT_CAPTURE_STATE="$(lab_state capture-status "${SCENARIO}")"
if [[ "${CURRENT_CAPTURE_STATE}" == "conclusion" ]]; then
echo "${SCENARIO} already has a conclusion for this run." >&2
echo "To collect another capture, start a new attempt first: ./scripts/lab.sh run ${SCENARIO}" >&2
echo "To regenerate only the rendered assets, run: ${PYTHON} ${SCRIPT_DIR}/render_capture.py ${NORMALIZED_FILE} ${ASSET_DIR} --scenario ${SCENARIO}" >&2
exit 1
fi

if [[ ! -f "${AGENT_SETUP_FILE}" ]]; then
echo "Missing Agent setup evidence: ${AGENT_SETUP_FILE}" >&2
echo "Create evidence/agent-setup.json as shown in guides/01-agent-setup.md." >&2
exit 1
fi
if [[ ! -f "${TIMELINE_FILE}" ]]; then
Expand All @@ -55,8 +64,14 @@ fi

AGENT_ENDPOINT="$(jq -r '.agent_endpoint // empty' "${AGENT_SETUP_FILE}")"
ALERT_ID="$(jq -r '.alert_id // empty' "${TIMELINE_FILE}")"
if [[ -z "${AGENT_ENDPOINT}" || -z "${ALERT_ID}" ]]; then
echo "Agent endpoint or alert ID is missing from evidence." >&2
if [[ -z "${AGENT_ENDPOINT}" || "${AGENT_ENDPOINT}" != https://* || "${AGENT_ENDPOINT}" == *"<"* || "${AGENT_ENDPOINT}" == *">"* ]]; then
echo "agent_endpoint is missing or is not a valid HTTPS endpoint in ${AGENT_SETUP_FILE}." >&2
echo "Update evidence/agent-setup.json as shown in guides/01-agent-setup.md." >&2
exit 1
fi
if [[ -z "${ALERT_ID}" ]]; then
echo "alert_id is missing from ${TIMELINE_FILE}." >&2
echo "Create a new scenario timeline by running: ./scripts/lab.sh run ${SCENARIO}" >&2
exit 1
fi
readonly AGENT_ENDPOINT ALERT_ID
Expand Down
29 changes: 18 additions & 11 deletions monitor/sre-agent-event-lab/scripts/cleanup-external.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,6 @@ require_command() {
}
}

lowercase() {
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}

if [[ "${MODE}" == "image-env" ]]; then
require_command azd
if [[ "${CONFIRMED}" -ne 1 ]]; then
Expand Down Expand Up @@ -147,20 +143,24 @@ fi
if ! RECORDED_ASSIGNMENTS="$(jq -r '
[
{
assignment_key: "monitoring_contributor_assignment_id",
assignment: (.monitoring_contributor_assignment_id // ""),
principal_key: "agent_principal_id",
principal: (.agent_principal_id // "")
},
{
assignment_key: "uami_monitoring_contributor_assignment_id",
assignment: (.uami_monitoring_contributor_assignment_id // ""),
principal_key: "agent_user_assigned_principal_id",
principal: (.agent_user_assigned_principal_id // "")
}
]
| .[]
| [.assignment, .principal]
| @tsv
| [.assignment_key, .assignment, .principal_key, .principal]
| join("|")
' "${CLEANUP_SETUP_FILE}" 2>/dev/null)"; then
echo "Agent setup evidence is not valid JSON: ${CLEANUP_SETUP_FILE}" >&2
echo "Recreate it: lab.sh acknowledge agent-setup" >&2
echo "Rewrite evidence/agent-setup.json as shown in guides/01-agent-setup.md, then run: lab.sh acknowledge agent-setup" >&2
exit 1
fi
readonly RECORDED_ASSIGNMENTS
Expand Down Expand Up @@ -252,13 +252,20 @@ verify_recorded_assignment() {
# a duplicate. Bash 3.2 (macOS) aborts under `set -u` when an empty array
# is expanded, which is why this stays a string instead of an array.
VERIFIED_ASSIGNMENT_IDS=$'\n'
while IFS=$'\t' read -r assignment_id expected_principal_id; do
if [[ -z "${assignment_id}" ]]; then
while IFS='|' read -r assignment_key assignment_id principal_key expected_principal_id; do
if [[ -z "${assignment_id}" && -z "${expected_principal_id}" ]]; then
continue
fi
if [[ -z "${assignment_id}" ]]; then
echo "${assignment_key} is empty for principal ${expected_principal_id:-unknown}." >&2
echo "Find the live ID: az role assignment list --assignee-object-id ${expected_principal_id:-<principalId>} --role \"Monitoring Contributor\" --scope ${SUBSCRIPTION_SCOPE} --query \"[0].id\" -o tsv" >&2
echo "If no assignment is returned and teardown should continue, clear both ${assignment_key} and ${principal_key} in the evidence file." >&2
echo "Update evidence/agent-setup.json as shown in guides/01-agent-setup.md, then run: lab.sh acknowledge agent-setup" >&2
exit 1
fi
if [[ -z "${expected_principal_id}" ]]; then
echo "Incomplete Agent setup evidence: ${assignment_id} was recorded without its Agent principal ID." >&2
echo "Recreate it: lab.sh acknowledge agent-setup" >&2
echo "Incomplete Agent setup evidence: ${assignment_id} was recorded without ${principal_key}." >&2
echo "Update evidence/agent-setup.json as shown in guides/01-agent-setup.md, then run: lab.sh acknowledge agent-setup" >&2
exit 1
fi
case "${VERIFIED_ASSIGNMENT_IDS}" in
Expand Down
6 changes: 5 additions & 1 deletion monitor/sre-agent-event-lab/scripts/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ require_commands() {
done
}

lowercase() {
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}

# azd_value NAME -- the current azd environment's value for NAME, or empty
# when azd has no such value (e.g. before the environment was provisioned).
#
Expand Down Expand Up @@ -410,7 +414,7 @@ alert_monitor_condition() {
# an open incident, and both incidents' evidence would be unreadable.
wait_for_alert_resolved() {
local alert_id="$1"
local timeout_seconds="${2:-900}"
local timeout_seconds="${2:-1500}"
local interval_seconds="${3:-20}"
local deadline=$(( SECONDS + timeout_seconds ))
local condition=""
Expand Down
15 changes: 13 additions & 2 deletions monitor/sre-agent-event-lab/scripts/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -275,16 +275,27 @@ fi
if [[ "${AZURE_SAFE}" -eq 1 ]]; then
RESOURCE_GROUP_SCOPE="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}"
if [[ ! -f "${AGENT_SETUP_FILE}" ]]; then
report "Reader role assignment" FAIL "Agent setup evidence missing: ${AGENT_SETUP_FILE}. Run: lab.sh acknowledge agent-setup after recording the Agent identities."
report "Reader role assignment" FAIL "Agent setup evidence missing: ${AGENT_SETUP_FILE}. Create evidence/agent-setup.json as shown in guides/01-agent-setup.md, then run: lab.sh acknowledge agent-setup."
elif ! AGENT_SETUP_JSON="$(jq '.' "${AGENT_SETUP_FILE}" 2>/dev/null)"; then
# A hand-edited or truncated evidence file is one FAIL row, not a raw
# `jq` abort: under `set -e` an unguarded parse would kill the run and
# swallow every remaining check, including the MANUAL rows an operator
# still needs.
report "Reader role assignment" FAIL "Agent setup evidence is not valid JSON: ${AGENT_SETUP_FILE}. Recreate it: lab.sh acknowledge agent-setup"
report "Reader role assignment" FAIL "Agent setup evidence is not valid JSON: ${AGENT_SETUP_FILE}. Rewrite evidence/agent-setup.json as shown in guides/01-agent-setup.md, then run: lab.sh acknowledge agent-setup."
else
agent_principal_id="$(jq -r '.agent_principal_id // empty' <<<"${AGENT_SETUP_JSON}" 2>/dev/null || true)"
agent_uami_principal_id="$(jq -r '.agent_user_assigned_principal_id // empty' <<<"${AGENT_SETUP_JSON}" 2>/dev/null || true)"
monitoring_assignment_id="$(jq -r '.monitoring_contributor_assignment_id // empty' <<<"${AGENT_SETUP_JSON}" 2>/dev/null || true)"
uami_monitoring_assignment_id="$(jq -r '.uami_monitoring_contributor_assignment_id // empty' <<<"${AGENT_SETUP_JSON}" 2>/dev/null || true)"
EXPECTED_ASSIGNMENT_PREFIX="/subscriptions/${SUBSCRIPTION_ID}/providers/Microsoft.Authorization/roleAssignments/"
MISSING_CLEANUP_KEYS=()
[[ "$(lowercase "${monitoring_assignment_id}")" == "$(lowercase "${EXPECTED_ASSIGNMENT_PREFIX}")"* ]] || MISSING_CLEANUP_KEYS+=("monitoring_contributor_assignment_id")
[[ "$(lowercase "${uami_monitoring_assignment_id}")" == "$(lowercase "${EXPECTED_ASSIGNMENT_PREFIX}")"* ]] || MISSING_CLEANUP_KEYS+=("uami_monitoring_contributor_assignment_id")
if [[ "${#MISSING_CLEANUP_KEYS[@]}" -gt 0 ]]; then
report "Monitoring Contributor cleanup evidence" FAIL "Missing or invalid: ${MISSING_CLEANUP_KEYS[*]}. Find each live ID with: az role assignment list --assignee-object-id <principalId> --role \"Monitoring Contributor\" --scope /subscriptions/${SUBSCRIPTION_ID} --query \"[0].id\" -o tsv; then update ${AGENT_SETUP_FILE}."
else
report "Monitoring Contributor cleanup evidence" PASS "Both subscription-scoped role assignment IDs are recorded."
fi
if [[ -z "${agent_principal_id}" || -z "${agent_uami_principal_id}" ]]; then
report "Reader role assignment" FAIL "Agent setup evidence is missing agent_principal_id/agent_user_assigned_principal_id: ${AGENT_SETUP_FILE}."
else
Expand Down
Loading