展示
本指南將引導您在 F5 Distributed Cloud 上使用 API 完成一個完整的用戶端防禦演練 — 結構化為四個階段,AI 助理或人工操作員可端對端執行。每個步驟包含一個可立即執行的 curl 命令,其中包含佔位符值,您可以使用頁面頂部的表單、.env 檔案或任何自動化工具進行自訂。
| 階段 | 目標 | 步驟 |
|---|---|---|
| 第 1 階段 — 建置 | 部署並驗證完整的 CSD 基礎架構 | 步驟 1–7 |
| 第 2 階段 — 攻擊 | 產生模擬攻擊流量並確認 CSD 已偵測到 | 步驟 8–9 |
| 第 3 階段 — 緩解 | 緩解前後對比證明 — 執行攻擊、套用緩解措施、重新執行攻擊、比較結果 | 步驟 1–6 |
| 第 4 階段 — 拆除 | 在明確確認後移除所有部署物件 | 拆除 |
在開始第 1 階段之前,請驗證環境是否乾淨。執行這些 API 檢查以確定是否存在先前執行遺留的物件:
# Check all Phase 1 objects and compute environment statusHTTP_LB=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http")HTTPS_LB=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https")ORIGIN=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools/xF5XC_ORIGIN_POOLx")HC=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks/xF5XC_HC_NAMEx")PD_COUNT=$(curl -s -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains" \ | jq '[.items // [] | .[] | select(.metadata.name != null)] | length')MD_COUNT=$(curl -s -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/mitigated_domains" \ | jq '[.items // [] | .[] | select(.metadata.name != null)] | length')
# If HTTPS LB exists, fetch body to detect skeleton stateHTTPS_IS_SKELETON="false"if [ "$HTTPS_LB" = "200" ]; then HTTPS_LB_BODY=$(curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https") HTTPS_IS_SKELETON=$(echo "$HTTPS_LB_BODY" | jq ' ((.spec.default_route_pools // []) | length == 0) and (.spec.client_side_defense == null) ')fi
# Compute deterministic environment statusjq -n \ --argjson http_lb "$HTTP_LB" \ --argjson https_lb "$HTTPS_LB" \ --argjson origin "$ORIGIN" \ --argjson hc "$HC" \ --argjson pd "$PD_COUNT" \ --argjson md "$MD_COUNT" \ --argjson https_skeleton "$HTTPS_IS_SKELETON" \'{ objects: [ { name: "http_lb", http_code: $http_lb, exists: ($http_lb == 200) }, { name: "https_lb", http_code: $https_lb, exists: ($https_lb == 200), is_skeleton: $https_skeleton }, { name: "origin_pool", http_code: $origin, exists: ($origin == 200) }, { name: "healthcheck", http_code: $hc, exists: ($hc == 200) }, { name: "protected_domains", count: $pd, exists: ($pd > 0) }, { name: "mitigated_domains", count: $md, exists: ($md > 0) } ], any_infra_exists: ($http_lb == 200 or ($https_lb == 200 and ($https_skeleton | not)) or $origin == 200 or $hc == 200), any_csd_exists: ($pd > 0 or $md > 0), status: ( if ($http_lb == 404 and $https_lb == 404 and $origin == 404 and $hc == 404 and $pd == 0 and $md == 0) then "CLEAN" elif ($https_lb == 200 and $https_skeleton and $http_lb == 404 and $origin == 404 and $hc == 404 and $pd == 0 and $md == 0) then "HTTPS_SKELETON" elif ($http_lb == 200 and $origin == 200) then "ALL_EXIST" elif ($http_lb == 200 or ($https_lb == 200 and ($https_skeleton | not)) or $origin == 200 or $hc == 200) then "TEARDOWN_NEEDED" elif ($md > 0 and $http_lb == 404 and ($https_lb == 404 or ($https_lb == 200 and $https_skeleton)) and $origin == 404 and $hc == 404) then "MITIGATIONS_ONLY" else "TEARDOWN_NEEDED" end ), action: ( if ($http_lb == 404 and $https_lb == 404 and $origin == 404 and $hc == 404 and $pd == 0 and $md == 0) then "Proceed to Phase 1" elif ($https_lb == 200 and $https_skeleton and $http_lb == 404 and $origin == 404 and $hc == 404 and $pd == 0 and $md == 0) then "Proceed to Phase 1 (HTTPS LB skeleton will be restored via PUT)" elif ($http_lb == 200 and $origin == 200) then "All Phase 1 objects exist — verify health, optionally skip to Phase 2" elif ($http_lb == 200 or ($https_lb == 200 and ($https_skeleton | not)) or $origin == 200 or $hc == 200) then "Run Phase 4 Teardown first, then re-check" elif ($md > 0 and $http_lb == 404 and ($https_lb == 404 or ($https_lb == 200 and $https_skeleton)) and $origin == 404 and $hc == 404) then "Delete mitigated domains inline, then proceed" else "Run Phase 4 Teardown first, then re-check" end )}'當所有第 1 階段物件存在(200)且您計劃跳到第 2 階段時,
請執行第 1 階段步驟 7 的驗證命令以確認基礎架構
健康狀態後再跳過。使用來自
第 1 階段 — 步驟 7:驗證的確切命令:
- DNS 解析:
dig +short xF5XC_DOMAINNAMEx A - HTTP LB 狀態:
GET .../http_loadbalancers/xF5XC_LB_NAMEx-http導向jq '{state: .spec.state}'— 必須顯示VIRTUAL_HOST_READY - CSD JS 設定:
GET .../js_configuration— 必須包含scriptTag - CSD 狀態:
GET .../status導向jq '{configured: .isConfigured, enabled: .isEnabled}'— 兩者都必須為true
所有必要檢查(DNS-1、LB-1、CSD-1、CSD-2)必須全部通過後才能 跳到第 2 階段。如果任何檢查失敗,請從失敗的步驟開始執行第 1 階段。
就緒驗證矩陣
Section titled “就緒驗證矩陣”上述飛行前檢查驗證環境是否乾淨。下方的就緒矩陣驗證環境是否具備能力 — 即所有先決條件、配額、連線能力和平台服務是否已就位以成功進行展示。在每次會議前作為準備階段的一部分執行此矩陣。
每個檢查都有一個測試 ID、一個層級(T0–T5)、通過/失敗/警告標準和一個修復路徑。層級是循序的 — 較早層級的失敗會阻止後續層級的執行。
| 層級 | 類別 | 是否阻止展示? | 目的 |
|---|---|---|---|
| T0 | 連線能力與驗證 | 是 | 我們能否連接到平台並進行身份驗證? |
| T1 | 配額與容量 | 是(達到限制時) | 是否有空間建立展示物件? |
| T2 | 平台先決條件 | 是 | 租戶層級的服務是否已設定? |
| T3 | 來源健康狀態 | 警告 | 後端應用程式是否有回應? |
| T4 | 環境清潔 | 自動修復 | 是否有先前執行遺留的物件? |
| T5 | 憑證就緒 | 資訊性 | HTTPS 是否可用,還是應該規劃僅使用 HTTP? |
T0:連線能力與驗證
Section titled “T0:連線能力與驗證”這些檢查確認執行主機能否連接到 F5 XC API 以及憑證是否有效。
PF-T0-1:API 連線能力
Section titled “PF-T0-1:API 連線能力”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' --connect-timeout 10 --max-time 15 \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/web/namespaces")echo "{\"http_code\": $HTTP_CODE}" | jq '{ check: "PF-T0-1", http_code: .http_code, status: ( if .http_code == 200 then "PASS" elif .http_code == 401 then "FAIL" else "FAIL" end ), detail: ( if .http_code == 200 then "API reachable, token valid" elif .http_code == 401 then "Token expired or invalid — regenerate under Administration > Credentials > API Credentials" elif .http_code == 0 then "Network unreachable — check connectivity, VPN, or TLS compatibility (try --tlsv1.2 --tls-max 1.2)" else "Unexpected HTTP \(.http_code)" end )}'PF-T0-2:命名空間存取
Section titled “PF-T0-2:命名空間存取”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers")echo "{\"http_code\": $HTTP_CODE}" | jq '{ check: "PF-T0-2", http_code: .http_code, status: ( if .http_code == 200 then "PASS" elif .http_code == 404 then "WARN" else "FAIL" end ), detail: ( if .http_code == 200 then "Token has namespace access" elif .http_code == 403 then "Token lacks permissions for namespace — check role bindings" elif .http_code == 404 then "Namespace does not exist — will be created in Phase 1 Step 0" else "Unexpected HTTP \(.http_code)" end )}'PF-T0-3:CSD API 存取
Section titled “PF-T0-3:CSD API 存取”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/status")echo "{\"http_code\": $HTTP_CODE}" | jq '{ check: "PF-T0-3", http_code: .http_code, status: ( if .http_code == 200 then "PASS" elif .http_code == 404 then "WARN" else "FAIL" end ), detail: ( if .http_code == 200 then "Token has CSD/Shape API permissions" elif .http_code == 403 then "Token lacks CSD role binding — contact tenant administrator" elif .http_code == 404 then "Namespace does not exist — CSD access will be verified after namespace creation in Phase 1" else "Unexpected HTTP \(.http_code)" end )}'PF-T0-4:RBAC 權限矩陣
Section titled “PF-T0-4:RBAC 權限矩陣”非破壞性探測測試展示所需的每種物件類型的讀取和寫入權限。讀取透過列表端點的 GET 測試。寫入透過對已知不存在物件的 DELETE 測試 — 如果 RBAC 拒絕操作,API 回傳 403;如果操作被允許但物件不存在,則回傳 404。這種零副作用技術避免了建立臨時探測物件。
PROBE_NAME="rbac-probe-nonexistent"
# Read probesNS_R=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/web/namespaces/xF5XC_NAMESPACEx")HC_R=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks")OP_R=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools")LB_R=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers")CSD_R=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/status")PD_R=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains")MD_R=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/mitigated_domains")
# Write probes (non-destructive: DELETE/cascade_delete on non-existent objects)NS_W=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -X POST -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d "{\"name\":\"$PROBE_NAME\"}" \ "xF5XC_API_URLx/api/web/namespaces/$PROBE_NAME/cascade_delete")HC_W=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks/$PROBE_NAME")OP_W=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools/$PROBE_NAME")LB_W=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/$PROBE_NAME")PD_W=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains/$PROBE_NAME.example.com")MD_W=$(curl -s -o /dev/null -w '%\{http_code\}' --max-time 10 \ -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/mitigated_domains/$PROBE_NAME.example.com")
# Compute deterministic permission matrixjq -n \ --argjson ns_r "$NS_R" --argjson ns_w "$NS_W" \ --argjson hc_r "$HC_R" --argjson hc_w "$HC_W" \ --argjson op_r "$OP_R" --argjson op_w "$OP_W" \ --argjson lb_r "$LB_R" --argjson lb_w "$LB_W" \ --argjson csd_r "$CSD_R" \ --argjson pd_r "$PD_R" --argjson pd_w "$PD_W" \ --argjson md_r "$MD_R" --argjson md_w "$MD_W" \'{ check: "PF-T0-4", permissions: [ { object: "namespace", read: ($ns_r != 403), write: ($ns_w != 403), required: false, note: "conditional — only if ns must be created" }, { object: "healthcheck", read: ($hc_r != 403), write: ($hc_w != 403), required: false, note: "optional for CSD" }, { object: "origin_pool", read: ($op_r != 403), write: ($op_w != 403), required: true, note: "" }, { object: "http_loadbalancer", read: ($lb_r != 403), write: ($lb_w != 403), required: true, note: "" }, { object: "csd_status", read: ($csd_r != 403), write: true, required: true, note: "read-only check" }, { object: "protected_domain", read: ($pd_r != 403), write: ($pd_w != 403), required: true, note: "" }, { object: "mitigated_domain", read: ($md_r != 403), write: ($md_w != 403), required: false, note: "Phase 3 only" } ], status: ( if [ ($op_r == 403), ($op_w == 403), ($lb_r == 403), ($lb_w == 403), ($csd_r == 403), ($pd_r == 403), ($pd_w == 403) ] | any then "FAIL" elif ($ns_w == 403 or $hc_w == 403 or $md_w == 403) then "WARN" else "PASS" end ), detail: ( [ (if ($op_r == 403 or $op_w == 403) then "Origin pool: permission denied" else null end), (if ($lb_r == 403 or $lb_w == 403) then "Load balancer: permission denied" else null end), (if $csd_r == 403 then "CSD API: permission denied — CSD may not be enabled for this namespace" else null end), (if ($pd_r == 403 or $pd_w == 403) then "Protected domain: permission denied" else null end), (if $ns_w == 403 then "Namespace: write denied — namespace must already exist (cannot create)" else null end), (if $hc_w == 403 then "Healthcheck: write denied — will skip healthcheck creation" else null end), (if $md_w == 403 then "Mitigated domain: write denied — Phase 3 mitigation will be skipped" else null end) ] | map(select(. != null)) | join("; ") )}'T1:配額與容量
Section titled “T1:配額與容量”這些檢查查詢租戶的配額使用 API,以確定展示所需的每種物件類型的限制、目前使用量和剩餘容量。這用單一唯讀 API 呼叫取代了探測和刪除測試,可報告確切數字。
PF-T1-0:配額使用閘門
Section titled “PF-T1-0:配額使用閘門”查詢租戶級配額使用端點,並為展示所需的每種物件類型計算確定性的通過/警告/失敗狀態。此端點需要 system 命名空間。單一 API 呼叫可一次檢查所有平台級配額。
閘門定義了一個 demo_needs 陣列,指定展示將消耗每種物件類型的數量、該類型是否必要,以及展示繼續所需的最低數量。jq 過濾器將 remaining 與 needed 進行比較,並確定性地計算 status 欄位 — 無需操作員解讀。
# Step 1: Fetch quota datacurl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/web/namespaces/system/quota/usage?namespace=system" \ > /tmp/quota.json
# Step 2: Compute gate statusjq ' . as $data | [ { kind: "healthcheck", needed: 1, required: false, min_proceed: 0 }, { kind: "origin_pool", needed: 1, required: true, min_proceed: 1 }, { kind: "endpoint", needed: 1, required: true, min_proceed: 1 }, { kind: "http_loadbalancer", needed: 2, required: true, min_proceed: 1 } ] | map( . as $req | $data.objects[$req.kind] as $obj | $obj.limit.maximum as $limit | $obj.usage.current as $usage | (if $limit == -1 then null else ($limit - $usage) end) as $remaining | { kind: $req.kind, limit: (if $limit == -1 then "unlimited" else $limit end), usage: $usage, remaining: (if $remaining == null then "unlimited" else $remaining end), needed: $req.needed, status: ( if $remaining == null then "PASS" elif $remaining >= $req.needed then "PASS" elif $remaining >= $req.min_proceed then "WARN" else (if $req.required then "FAIL" else "WARN" end) end ) } ) | { checks: ., gate: (if any(.[]; .status == "FAIL") then "FAIL" elif any(.[]; .status == "WARN") then "WARN" else "PASS" end) }' /tmp/quota.json閘門輸出 — gate 欄位是單一的確定性判定結果:
PASS— 所有物件類型的remaining >= needed。展示可以繼續。WARN— 至少一種類型的容量減少,但已達到繼續所需的最低值(例如,只有 1 個 LB 插槽可用而非 2 個,或健康檢查配額已耗盡)。展示可以在有限制的情況下繼續。FAIL— 至少一種必要類型的remaining < min_proceed。在釋放配額之前,展示無法繼續。
範例輸出(WARN — 端點已達容量上限,健康檢查接近滿額):
{ "checks": [ { "kind": "healthcheck", "limit": 150, "usage": 149, "remaining": 1, "needed": 1, "status": "PASS" }, { "kind": "origin_pool", "limit": "unlimited", "usage": 420, "remaining": "unlimited", "needed": 1, "status": "PASS" }, { "kind": "endpoint", "limit": 500, "usage": 500, "remaining": 0, "needed": 1, "status": "FAIL" }, { "kind": "http_loadbalancer", "limit": "unlimited", "usage": 116, "remaining": "unlimited", "needed": 2, "status": "PASS" } ], "gate": "FAIL"}gate 值 | 動作 |
|---|---|
| PASS | 繼續至 PF-T1-4(受保護網域檢查),然後 T2 |
| WARN | 在就緒報告中記錄限制,以降低能力繼續進行 |
| FAIL | 停止 — 報告哪些類型已耗盡以及下方的修復步驟 |
按類型修復:
| 類型 | 修復方式 |
|---|---|
healthcheck | 刪除未使用的健康檢查以釋放容量。展示可在沒有健康檢查的情況下繼續(CSD 不需要健康檢查)。 |
origin_pool | 刪除未使用的來源池或聯繫您的管理員以增加租戶限制。 |
endpoint | 刪除其他命名空間中未使用的來源池以釋放端點容量(端點是來源池的子物件),或聯繫您的管理員。 |
http_loadbalancer | 刪除未使用的負載平衡器或聯繫您的管理員。如果只有 1 個插槽可用,HTTP LB(主要)將被建立,但 HTTPS LB(次要)將被跳過。 |
PF-T1-4:受保護網域配額
Section titled “PF-T1-4:受保護網域配額”CSD 受保護網域不會出現在平台配額使用 API 中。使用基於探測的檢查:建立並立即刪除一個探測受保護網域。
# Create probe and capture both HTTP code and response bodyPROBE_BODY=$(curl -s -w '\n%\{http_code\}' -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "preflight-probe.example.com", "namespace": "xF5XC_NAMESPACEx" }, "spec": { "protected_domain": "example.com" } }' \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains")PROBE_HTTP=$(echo "$PROBE_BODY" | tail -1)PROBE_JSON=$(echo "$PROBE_BODY" | sed '$d')
# Compute statusecho "$PROBE_JSON" | jq --argjson http "$PROBE_HTTP" '{ check: "PF-T1-4", http_code: $http, status: ( if $http == 409 then "PASS" elif (.code // 0) == 8 then "FAIL" elif .metadata.name then "PASS" else "FAIL" end ), detail: ( if $http == 409 then "example.com already registered — quota not exhausted" elif (.code // 0) == 8 then "Protected domain quota exhausted — delete unused protected domains" elif .metadata.name then "Probe created — quota available" else "Unexpected response" end )}'
# Cleanup probe (404 is expected if 409 occurred)curl -s -X DELETE \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains/preflight-probe.example.com" \ > /dev/null回退:基於探測的配額檢查
Section titled “回退:基於探測的配額檢查”如果 PF-T1-0 失敗(配額使用 API 回傳 403、404 或意外格式),請回退到健康檢查、來源池、端點和負載平衡器配額的探測和刪除檢查。這些檢查建立一個臨時物件並立即刪除它 — 如果建立回傳錯誤碼 8 並顯示「exhausted limits」,則配額已滿。
每個回退探測使用相同的模式:建立一個臨時物件,從回應中計算確定性狀態,然後刪除探測。如果物件已建立(.metadata.name 存在),status 欄位為 PASS;如果錯誤碼為 8(限制已耗盡),則根據該類型是否為必要項目而為 WARN 或 FAIL。
健康檢查探測(如果耗盡則為 WARN — 健康檢查對 CSD 是選用的):
RESULT=$(curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "preflight-quota-probe", "namespace": "xF5XC_NAMESPACEx" }, "spec": { "http_health_check": { "use_origin_server_name": {}, "path": "/", "use_http2": false }, "timeout": 3, "interval": 15, "unhealthy_threshold": 1, "healthy_threshold": 3 } }' \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks")echo "$RESULT" | jq '{ check: "fallback-healthcheck", status: (if .metadata.name then "PASS" elif (.code // 0) == 8 then "WARN" else "FAIL" end), detail: (if .metadata.name then "Quota available" elif (.code // 0) == 8 then "Quota full — healthcheck optional, demo proceeds" else "Unexpected: \(.message // "unknown error")" end)}'curl -s -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks/preflight-quota-probe" \ > /dev/null來源池與端點探測(如果耗盡則為 FAIL — 兩者都是必要的):
RESULT=$(curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "preflight-origin-probe", "namespace": "xF5XC_NAMESPACEx" }, "spec": { "origin_servers": [{ "public_ip": { "ip": "192.0.2.1" }, "labels": {} }], "no_tls": {}, "port": 80, "same_as_endpoint_port": {}, "healthcheck": [], "loadbalancer_algorithm": "LB_OVERRIDE", "endpoint_selection": "LOCAL_PREFERRED" } }' \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools")echo "$RESULT" | jq '{ check: "fallback-origin-pool", status: (if .metadata.name then "PASS" elif (.code // 0) == 8 then "FAIL" else "FAIL" end), detail: (if .metadata.name then "Quota available" elif (.code // 0) == 8 then "Quota exhausted — \(.message // "limit reached")" else "Unexpected: \(.message // "unknown error")" end)}'curl -s -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools/preflight-origin-probe" \ > /dev/nullHTTP 負載平衡器探測(如果耗盡則為 FAIL — LB 是必要的):
RESULT=$(curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "preflight-lb-probe", "namespace": "xF5XC_NAMESPACEx", "disable": false }, "spec": { "domains": ["preflight-probe.example.com"], "http": { "dns_volterra_managed": false, "port": 80 }, "advertise_on_public_default_vip": {}, "default_route_pools": [], "disable_rate_limit": {}, "no_service_policies": {}, "round_robin": {}, "disable_waf": {}, "no_challenge": {}, "disable_bot_defense": {}, "disable_api_definition": {}, "disable_api_discovery": {}, "disable_ip_reputation": {}, "disable_malicious_user_detection": {}, "single_lb_app": { "disable_discovery": {}, "disable_ddos_detection": {}, "disable_malicious_user_detection": {} }, "disable_trust_client_ip_headers": {}, "user_id_client_ip": {}, "disable_threat_mesh": {}, "l7_ddos_action_default": {}, "system_default_timeouts": {}, "default_sensitive_data_policy": {}, "disable_malware_protection": {}, "disable_api_testing": {} } }' \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers")echo "$RESULT" | jq '{ check: "fallback-http-lb", status: (if .metadata.name then "PASS" elif (.code // 0) == 8 then "FAIL" else "FAIL" end), detail: (if .metadata.name then "Quota available" elif (.code // 0) == 8 then "Quota exhausted — \(.message // "limit reached")" else "Unexpected: \(.message // "unknown error")" end)}'curl -s -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/preflight-lb-probe" \ > /dev/nullT2:平台先決條件
Section titled “T2:平台先決條件”這些檢查驗證展示所依賴的租戶級服務。
PF-T2-1:CSD 租戶狀態
Section titled “PF-T2-1:CSD 租戶狀態”curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/status" \ | jq '{ check: "PF-T2-1", configured: .isConfigured, enabled: .isEnabled, status: (if .isConfigured and .isEnabled then "PASS" else "FAIL" end), detail: ( if .isConfigured and .isEnabled then "CSD is active" elif (.isConfigured | not) then "CSD not enabled at tenant level — contact F5 XC administrator" else "CSD configured but not active — contact administrator" end ) }'PF-T2-2:DNS 區域存在
Section titled “PF-T2-2:DNS 區域存在”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx")echo "{\"http_code\": $HTTP_CODE}" | jq '{ check: "PF-T2-2", http_code: .http_code, status: ( if .http_code == 200 then "PASS" elif .http_code == 404 then "WARN" elif .http_code == 403 then "WARN" else "FAIL" end ), detail: ( if .http_code == 200 then "DNS zone exists in F5 XC" elif .http_code == 404 then "No F5 XC DNS zone — external DNS may be in use" elif .http_code == 403 then "Token lacks DNS zone read access (system namespace)" else "Unexpected HTTP \(.http_code)" end )}'PF-T2-3:DNS 託管記錄已啟用
Section titled “PF-T2-3:DNS 託管記錄已啟用”僅在 PF-T2-2 回傳 200(F5 XC DNS 區域存在)時執行。
檢查目前狀態:
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx" \ | jq '{ check: "PF-T2-3", managed_records: (.spec.primary.allow_http_lb_managed_records // false), status: (if .spec.primary.allow_http_lb_managed_records == true then "PASS" else "WARN" end), detail: (if .spec.primary.allow_http_lb_managed_records == true then "LB-managed DNS records enabled" else "Managed records not enabled — auto-remediation may be needed" end) }'如需自動修復: 如果 status 為 WARN 且 PF-T2-4 顯示 F5 XC 名稱伺服器(ns1.f5clouddns.com、ns2.f5clouddns.com),使用 GET+PUT 自動啟用託管記錄:
# Get current zone configZONE_CONFIG=$(curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx")
# Update with managed records enabledecho "$ZONE_CONFIG" \ | jq '.spec.primary.allow_http_lb_managed_records = true' \ | curl -s -X PUT \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d @- \ "xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx" \ | jq .然後重新檢查以確認更新已生效:
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx" \ | jq '.spec.primary.allow_http_lb_managed_records'| 結果 | DNS 授權 | 狀態 | 修復方式 |
|---|---|---|---|
true | 任何 | 通過 | LB 管理的 DNS 記錄將自動建立 |
false/null | F5 XC | 自動修復 | 透過 GET+PUT 啟用、驗證、報告結果 |
false/null | 外部 | 資訊 | 託管記錄不適用於外部 DNS — 第 1 階段步驟 4 將使用選項 B(手動建立記錄) |
| 自動修復失敗 | F5 XC | 失敗 | 權杖可能缺少 system 命名空間的寫入權限 — 聯繫租戶管理員 |
PF-T2-4:DNS 名稱伺服器授權
Section titled “PF-T2-4:DNS 名稱伺服器授權”NS_RECORDS=$(dig +short NS xF5XC_ROOT_DOMAINx)echo "$NS_RECORDS" | jq -Rs '{ check: "PF-T2-4", nameservers: (split("\n") | map(select(length > 0))), status: ( if (split("\n") | map(select(length > 0)) | length) == 0 then "FAIL" elif test("f5clouddns\\.com") then "PASS" else "INFO" end ), detail: ( if (split("\n") | map(select(length > 0)) | length) == 0 then "No NS records — DNS is broken for this domain" elif test("f5clouddns\\.com") then "F5 XC is authoritative — automatic DNS management available" else "External DNS provider — Phase 1 Step 4 will use Option B (manual record creation)" end )}'T3:來源健康狀態
Section titled “T3:來源健康狀態”這些檢查驗證後端應用程式是否可連線。
跳過條件檢查: 在執行連線測試之前,計算來源 IP 是否為 RFC 5737 TEST-NET 位址:
echo "xF5XC_ORIGIN_IPx" | jq -Rs '{ check: "PF-T3-skip", origin_ip: (rtrimstr("\n")), is_test_net: (rtrimstr("\n") | test("^192\\.0\\.2\\.|^198\\.51\\.100\\.|^203\\.0\\.113\\.")), status: (if (rtrimstr("\n") | test("^192\\.0\\.2\\.|^198\\.51\\.100\\.|^203\\.0\\.113\\.")) then "SKIP" else "CONTINUE" end), detail: (if (rtrimstr("\n") | test("^192\\.0\\.2\\.|^198\\.51\\.100\\.|^203\\.0\\.113\\.")) then "RFC 5737 TEST-NET address — not routable, connectivity testing skipped" else "Routable IP — proceed with connectivity tests" end)}'如果 status 為 SKIP,將 PF-T3-1 和 PF-T3-2 記錄為 SKIP 並移至 T4。否則,執行以下檢查。
PF-T3-1:來源伺服器連線能力
Section titled “PF-T3-1:來源伺服器連線能力”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' --connect-timeout 10 --max-time 15 \ "http://xF5XC_ORIGIN_IPx:xF5XC_ORIGIN_PORTx/")echo "{\"http_code\": $HTTP_CODE}" | jq '{ check: "PF-T3-1", http_code: .http_code, status: (if .http_code >= 200 and .http_code < 600 then "PASS" elif .http_code == 0 then "WARN" else "WARN" end), detail: ( if .http_code >= 200 and .http_code < 600 then "Origin responding with HTTP \(.http_code)" elif .http_code == 0 then "Origin unreachable from this network — LB may use a different path" else "Unexpected response code \(.http_code)" end )}'PF-T3-2:來源提供 HTML 內容
Section titled “PF-T3-2:來源提供 HTML 內容”僅在 PF-T3-1 回傳有效 HTTP 狀態時執行:
curl -s --max-time 10 "http://xF5XC_ORIGIN_IPx:xF5XC_ORIGIN_PORTx/" \ | grep -qi '</html>' && echo "PASS: HTML content" || echo "WARN: No HTML detected"| 結果 | 狀態 | 修復方式 |
|---|---|---|
PASS: HTML content | 通過 | 來源提供 HTML 頁面(CSD JS 注入所需) |
WARN: No HTML detected | 警告 | 來源可能是純 API 服務或回傳非 HTML — CSD JS 注入需要 HTML 頁面回應 |
T4:環境清潔
Section titled “T4:環境清潔”這些檢查驗證先前展示執行是否遺留了具名的 F5 XC 設定物件 — HTTP 負載平衡器、HTTPS 負載平衡器、來源池、健康檢查、受保護網域和緩解網域。 T4 是關於物件級別的清理:會與第 1 階段建立衝突的 API 物件是否仍然存在。它不測試 IP 位址、網路連線能力或來源健康狀態(這些屬於 T3 的範疇)。
執行上述飛行前檢查區段中的六個飛行前命令。套用決策邏輯以確定後續步驟。如果物件存在,自動拆除將在準備階段執行(無需確認)。
同時檢查並刪除先前中斷的飛行前執行遺留的過時探測物件。這些探測僅在配額使用 API 不可用且使用了回退基於探測的檢查時建立,或用於始終使用基於探測檢查的受保護網域探測(PF-T1-4):
# Stale probe cleanup (delete in any order — probes have no dependencies)curl -s -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks/preflight-quota-probe" \ > /dev/null
curl -s -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/preflight-lb-probe" \ > /dev/null
curl -s -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools/preflight-origin-probe" \ > /dev/null
curl -s -X DELETE -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains/preflight-probe.example.com" \ > /dev/null如果物件存在,每個 DELETE 回傳 200(空 {});如果不存在,則回傳 404 — 兩者都是預期的。
T5:憑證就緒
Section titled “T5:憑證就緒”這些檢查評估 HTTPS 是否可用,或展示是否應規劃僅使用 HTTP。
PF-T5-1:近期憑證簽發歷史
Section titled “PF-T5-1:近期憑證簽發歷史”檢查是否最近為展示網域簽發了 Let’s Encrypt 憑證。頻繁的建立/銷毀週期可能會耗盡每週速率限制(每個網域每週 5 個重複憑證)。
PF-T5-2:現有 HTTPS LB 憑證狀態
Section titled “PF-T5-2:現有 HTTPS LB 憑證狀態”僅在先前執行存在 HTTPS LB 時執行(PF-T4 發現物件):
CERT_BODY=$(curl -s -w '\n%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https")CERT_HTTP=$(echo "$CERT_BODY" | tail -1)CERT_JSON=$(echo "$CERT_BODY" | sed '$d')
if [ "$CERT_HTTP" = "404" ]; then echo '{"check":"PF-T5-2","cert_state":null,"status":"SKIP","detail":"No HTTPS LB — certificate state assessed after Phase 1 Step 3"}'else echo "$CERT_JSON" | jq '{ check: "PF-T5-2", cert_state: .spec.cert_state, status: ( if .spec.cert_state == "CertificateValid" then "PASS" elif .spec.cert_state == "AutoCertDomainRateLimited" then "INFO" elif (.spec.cert_state | test("Pending|Started")) then "INFO" else "INFO" end ), detail: ( if .spec.cert_state == "CertificateValid" then "Certificate healthy — HTTPS will work" elif .spec.cert_state == "AutoCertDomainRateLimited" then "Let'\''s Encrypt rate limit hit — plan for HTTP-only demo" elif (.spec.cert_state | test("Pending|Started")) then "Certificate provisioning in progress" else "Certificate state: \(.spec.cert_state // "unknown")" end ) }'fi就緒報告格式
Section titled “就緒報告格式”執行完所有層級後,呈現彙整的就緒報告:
## 展示就緒:就緒 / 未就緒 / 就緒但有警告
### T0:連線能力與驗證| 檢查 | 結果 | 狀態 ||---|---|---|| PF-T0-1: API 連線能力 | 200 | 通過 || PF-T0-2: 命名空間存取 | 200 | 通過 || PF-T0-3: CSD API 存取 | 200 | 通過 |
### T1:配額與容量| 檢查 | 類型 | 限制 | 使用量 | 剩餘 | 需要 | 狀態 ||---|---|---|---|---|---|---|| PF-T1-0: 配額使用閘門 | `healthcheck` | 150 | 148 | 2 | 1 | 通過 || PF-T1-0: 配額使用閘門 | `origin_pool` | unlimited | 420 | unlimited | 1 | 通過 || PF-T1-0: 配額使用閘門 | `endpoint` | 500 | 498 | 2 | 1 | 通過 || PF-T1-0: 配額使用閘門 | `http_loadbalancer` | unlimited | 116 | unlimited | 2 | 通過 || PF-T1-0: 配額使用閘門 | **閘門** | — | — | — | — | **通過** || PF-T1-4: 受保護網域 | — | — | — | — | 1 | 通過(探測) |
### T2:平台先決條件| 檢查 | 結果 | 狀態 ||---|---|---|| PF-T2-1: CSD 租戶狀態 | 已設定 + 已啟用 | 通過 || PF-T2-2: DNS 區域存在 | 200 | 通過 || PF-T2-3: DNS 託管記錄 | true | 通過 || PF-T2-4: DNS 名稱伺服器授權 | f5clouddns.com | 通過 |
### T3:來源健康狀態| 檢查 | 結果 | 狀態 ||---|---|---|| PF-T3-1: 來源連線能力 | TEST-NET 位址 (192.0.2.1) | 跳過 || PF-T3-2: HTML 內容 | TEST-NET 位址 | 跳過 |
### T4:環境清潔| 檢查 | 結果 | 狀態 ||---|---|---|| HTTP LB | 404 | 通過 || HTTPS LB | 404 | 通過 || Origin Pool | 404 | 通過 || Healthcheck | 404 | 通過 || Protected Domains | 0 | 通過 || Mitigated Domains | 0 | 通過 |
### T5:憑證就緒| 檢查 | 結果 | 狀態 ||---|---|---|| PF-T5-2: 憑證狀態 | 跳過(無 HTTPS LB) | 資訊 |
### 警告- (列出任何警告或資訊項目及其上下文)整體狀態規則:
| 條件 | 狀態 |
|---|---|
| 所有 T0–T4 檢查通過 | 就緒 |
| 所有 T0–T4 檢查通過但 T3 或 T5 有警告/資訊 | 就緒但有警告 |
| 任何 T0、T1 或 T2 檢查失敗 | 未就緒 — 在繼續之前解決 |
| T4 有遺留物件 | 自動修復(拆除),然後重新檢查 |
AI 助理執行協議
Section titled “AI 助理執行協議”本節定義了 AI 助理(Claude Code、Copilot 等)執行 API 自動化步驟的確定性工作流程。遵循此協議可消除猜測 — 每個決策點都有定義的解決路徑。
變數解析協議
Section titled “變數解析協議”按照以下確切順序解析每個變數。在第一個提供非佔位符值的來源處停止:
- 檢查
.env檔案 — 在存儲庫根目錄尋找.env。如果存在,解析所有KEY=VALUE對。 - 檢查 shell 環境 — 執行
env | grep F5XC_以查找目前工作階段中已匯出的任何值。 - 識別缺少的值 — 將已解析的值與下方的必要/選用表進行比較。如果值不存在、為空或仍設定為佔位符預設值(例如,
example-api-token、example-tenant、example-namespace、app.example.com、user@example.com),則該值為「缺少」。 - 提示人工操作員 — 對於每個缺少的必要變數,請操作員提供值。在所有必要變數解析完成之前不要繼續。
- 套用預設值 — 對於每個缺少的選用變數,使用下表中的預設值,無需提示。
空字串 = 缺少: 以空值匯出的變數 (例如,
F5XC_HC_NAME="")與未設定的變數處理方式相同 — 套用預設值。使用 shell 參數展開 (${F5XC_HC_NAME:-csd-hc})可一步套用預設值。
- 顯示確認 — 將最終解析的變數表顯示給操作員,並在執行任何 API 呼叫之前等待批准。
準備階段覆寫: 在第 1 階段準備期間,跳過 步驟 6 中的等待。顯示已解析的變數表作為記錄,然後 立即繼續。步驟 1–5 仍然適用 — 如果在檢查
.env和 shell 後任何必要變數 仍然缺少,則停止並報告缺少的變數。
必要與選用變數
Section titled “必要與選用變數”| 變數 | 必要 | 預設值 | 佔位符(視為缺少) |
|---|---|---|---|
F5XC_API_TOKEN | 是 | — | example-api-token |
F5XC_API_URL | 是 | — | https://example-tenant.console.ves.volterra.io |
F5XC_NAMESPACE | 是 | — | example-namespace |
F5XC_DOMAINNAME | 是 | — | app.example.com |
F5XC_ROOT_DOMAIN | 是 | — | example.com |
F5XC_LB_NAME | 是 | — | example-lb-name、example-lb |
F5XC_EMAIL | 是 | — | user@example.com |
F5XC_HC_NAME | 選用 | csd-hc | — |
F5XC_ORIGIN_IP | 選用 | 44.232.69.192 | — |
F5XC_ORIGIN_POOL | 選用 | csd-origin | — |
F5XC_ORIGIN_PORT | 選用 | 3000 | — |
AI 助理在展示期間以三種模式之一運作:
| 模式 | 啟用時機 | 行為 |
|---|---|---|
| 正常 | 準備、執行、拆除期間的預設 | 僅使用文件記載的逐字命令 |
| 除錯 | 失敗時自動啟動 | 創造性疑難排解、更新文件 |
| 問答 | 問答階段期間 | 即興發揮 — 允許臨時命令以回答觀眾問題 |
正常模式(預設):
- 每個 API 呼叫、驗證查詢和 shell 命令必須逐字來自階段檔案(第 1–4 階段)或上述飛行前檢查區段
- 僅將
xTOKENx佔位符替換為已解析的變數值 - 不要從一般知識或推論中構建 API 端點、jq 過濾器或 cURL 命令
- 如果需要的命令未記載,停止並向操作員報告: 「此驗證步驟未包含在階段文件中」
除錯模式(失敗時自動啟動):
- 當記載的命令產生意外結果時自動啟動:非 2xx HTTP 回應、jq 解析錯誤、命令逾時或回應本文與證據表矛盾
- 在除錯模式中,AI 助理可以構建診斷命令、檢查原始 API 回應、測試端點變體,並使用創造性疑難排解來找到根本原因
- 所有除錯輸出前綴
[DEBUG],以便操作員可以區分診斷活動和正常執行 - 記錄您學到的內容:在解決問題後,使用以下內容更新相關的階段檔案或疑難排解區段:
- 失敗情境(出了什麼問題)
- 嘗試過但未奏效的方法(以便未來的執行不會重複)
- 有效的解決方案(解決問題的命令或修復方法)
- 除錯模式的目標是消除自身 — 每次除錯工作階段都應產生文件更新,使下次執行完全確定性
- 一旦文件更新且問題解決,回到正常模式並從最後一個成功的記載步驟繼續
- 如果除錯模式無法解決問題,向操作員報告發現並停止 — 不要繼續到下一個階段
問答模式(問答階段期間):
- 僅在問答會議階段期間啟用,在展示結論之後
- AI 助理可以構建臨時 API 呼叫、執行診斷命令、導航到未腳本化的頁面,並修改即時展示環境以說明對觀眾問題的回答
- 無
[DEBUG]前綴 — 這是有意的即興行為,而非錯誤恢復 - 使用
DEMO_EXECUTOR.md中的 CSD 產品專業知識區段作為產品問題的知識庫
瀏覽器上下文管理
Section titled “瀏覽器上下文管理”initScript 累積是展示失敗的常見原因。每個帶有 initScript 參數的
navigate_page 呼叫都會將腳本新增到持久清單中,該清單在每次後續文件載入時執行。遵循以下規則:
- 始終先導航到
about:blank,然後再使用initScript導航,以清除先前執行累積的腳本 - 在切換展示階段時使用帶有
isolatedContext的new_page(例如,第 2 階段 → 第 3 階段)以確保完全乾淨的瀏覽器狀態 - 從無回應頁面恢復 — 如果發生
take_screenshot或take_snapshot逾時,瀏覽器上下文已資源耗盡。使用帶有isolatedContext的new_page建立全新上下文,然後從about:blank導航步驟重試 - 請參閱第 2 階段攻擊模擬附註以獲取有關暫時性來源故障和資源耗盡恢復的詳細指南
證據顯示協議
Section titled “證據顯示協議”在每次 API 呼叫後,AI 助理必須使用以下格式向人工操作員呈現結構化證據:
建立步驟(POST):
| 欄位 | 值 | 狀態 |
|---|---|---|
| HTTP 狀態 | 200 | 通過 |
| 物件名稱 | csd-origin | — |
| 關鍵屬性 | (透過 jq 提取) | — |
在每個建立步驟後,執行 GET 以確認物件存在並顯示其關鍵屬性。如果 GET 回傳 404,報告失敗並停止。
驗證步驟(GET/dig):
| 測試 | 結果 | 狀態 |
|---|---|---|
| DNS-1:A 記錄 | 198.51.100.10 | 通過 |
| LB-1:HTTP LB 狀態 | VIRTUAL_HOST_READY | 通過 |
| LB-2:HTTPS LB 狀態 | VIRTUAL_HOST_READY | 資訊(選用) |
| TLS-1:憑證狀態 | CertificateValid | 資訊(選用) |
| CSD-1:JS 標籤 | scriptTag 存在 | 通過 |
參考診斷與驗證測試案例 ID(DNS-1、TLS-1、LB-1、CSD-1 等)作為每一層的驗證標準。
執行流程摘要
Section titled “執行流程摘要”階段執行是循序且閘控的: 每個階段必須在所有必要檢查通過後才能開始下一個階段。展示遵循四階段會議生命週期 — 請參閱 DEMO_EXECUTOR.md 中的會議階段區段以獲取觸發語句和行為規則。
AI 助理遵循以下順序:
- 準備 — 解析變數、執行飛行前檢查、確認乾淨環境(可在會議前單獨執行)
- 介紹 — SE 自我介紹並陳述成果目標(用戶端威脅的可見性、PCI 合規性、即時偵測)
- 執行第 1 階段(步驟 1–7)— 基礎架構建立和驗證;所有第 1 階段檢查必須通過後才能繼續
- 執行第 2 階段(步驟 8–9)— 透過 API 進行攻擊模擬和偵測驗證;具有瀏覽器自動化的 AI 助理直接執行瀏覽器步驟,沒有瀏覽器工具的操作員手動執行
- 執行第 3 階段 — 為所有偵測到的網域套用緩解措施、重新執行攻擊、驗證阻擋是否有效
- 結論 — 重述成果目標、摘要每個階段的證據、突出關鍵偵測和緩解措施
- 問答 — 即興階段,展示保持運行,SE 回答觀眾問題並提出回饋問題
- 拆除(會議後)— 第 4 階段,需要明確的操作員確認,按反向依賴順序刪除所有物件,確認環境清潔
如果任何步驟回傳失敗,在繼續之前停止並報告失敗以及相關的疑難排解區段連結。
- 一個 F5 XC API 權杖 — 在 Administration → Credentials → API Credentials 下產生
- 本機安裝
curl和jq - 一個具有建立健康檢查、來源池和 HTTP 負載平衡器權限的命名空間
使用您的環境值建立 .env 檔案。存儲庫中提供了範本:
cp .env.example .env使用您的實際值編輯 .env:
# Required — your environmentF5XC_API_TOKEN=example-api-tokenF5XC_API_URL=https://example-tenant.console.ves.volterra.ioF5XC_DOMAINNAME=app.example.comF5XC_EMAIL=user@example.comF5XC_LB_NAME=example-lb-nameF5XC_NAMESPACE=example-namespaceF5XC_ROOT_DOMAIN=example.com
# Optional — Juice Shop demo defaults are used when not set# F5XC_HC_NAME=csd-hc# F5XC_ORIGIN_IP=44.232.69.192# F5XC_ORIGIN_POOL=csd-origin# F5XC_ORIGIN_PORT=3000載入檔案以將變數載入您的 shell 工作階段:
set -a && source .env && set +acurl 命令中的每個 xTOKENx 佔位符直接對應到一個環境變數 — 例如,xF5XC_API_TOKENx 對應到 $F5XC_API_TOKEN。您可以使用頁面頂部的互動表單替換這些值,或讓 Claude Code 等 AI 助理讀取您的 .env 並為您建構命令。
| 權杖 | 預設值 | 描述 |
|---|---|---|
xF5XC_API_URLx | https://example-tenant.console.ves.volterra.io | XC Console API URL |
xF5XC_API_TOKENx | example-api-token | API 憑證權杖 |
xF5XC_EMAILx | user@example.com | CSD 通知電子郵件地址 |
xF5XC_NAMESPACEx | example-namespace | 命名空間 |
xF5XC_LB_NAMEx | example-lb | HTTP 負載平衡器基礎名稱(建立 ${name}-http 和 ${name}-https) |
xF5XC_DOMAINNAMEx | app.example.com | 要保護的 FQDN |
xF5XC_ROOT_DOMAINx | example.com | 根網域(eTLD+1),用於 CSD 受保護網域 |
xF5XC_ORIGIN_POOLx | csd-origin | 來源池名稱 |
xF5XC_ORIGIN_IPx | 44.232.69.192 | 來源伺服器 IP |
xF5XC_ORIGIN_PORTx | 3000 | 來源伺服器連接埠 |
xF5XC_HC_NAMEx | csd-hc | 健康檢查名稱 |
本節摘要完整的演練工作流程,供腳本或自動化使用。
- 複製存儲庫並複製環境範本:
cp .env.example .env - 使用您的租戶 URL、API 權杖、命名空間和網域值編輯
.env - 載入環境:
set -a && source .env && set +a - 按順序執行每個階段,在繼續下一個階段之前驗證每個證據區塊的通過狀態
使用AI 助理執行協議中定義的確定性協議解析值:
.env檔案 — 從存儲庫根目錄解析KEY=VALUE對- Shell 環境 — 檢查
env | grep F5XC_以獲取已匯出的值 - 佔位符偵測 — 將任何符合佔位符預設值的值(例如,
example-api-token、example-namespace)標記為缺少 - 提示操作員 — 詢問每個缺少的必要變數
- 套用預設值 — 對缺少的選用變數使用內建預設值
- 確認 — 顯示已解析的變數表並等待操作員批准
- 第 1 階段 — 建置:部署基礎架構(健康檢查、來源池、HTTP LB + HTTPS LB),設定 DNS,啟用 CSD,註冊受保護網域,驗證所有元件。HTTP LB 是主要展示目標;HTTPS LB 為選用。
- 第 2 階段 — 攻擊:使用
http://URL 在瀏覽器中執行攻擊模擬,等待 5–10 分鐘,透過 API(/scripts、/detected_domains、/formFields)驗證偵測 - 第 3 階段 — 緩解:確認乾淨基線、執行攻擊(之前的證明)、對每個網域 POST 到
/mitigated_domains、驗證緩解措施已套用、使用http://URL 重新執行攻擊(之後的證明)、呈現前後對比 - 第 4 階段 — 拆除 (需要明確的人工確認):刪除 HTTPS LB → HTTP LB → 來源池 → DNS 區域清理(僅限手動記錄) → 健康檢查 → 受保護網域。不要刪除 DNS 區域。
| 權杖 | 描述 | 預設值 |
|---|---|---|
xF5XC_API_URLx | XC Console API URL | https://example-tenant.console.ves.volterra.io |
xF5XC_API_TOKENx | API 憑證權杖 | (使用者提供) |
xF5XC_EMAILx | CSD 通知電子郵件 | user@example.com |
xF5XC_NAMESPACEx | 命名空間 | example-namespace |
xF5XC_LB_NAMEx | HTTP 負載平衡器基礎名稱(建立 ${name}-http 和 ${name}-https) | example-lb |
xF5XC_DOMAINNAMEx | 要保護的 FQDN | app.example.com |
xF5XC_ROOT_DOMAINx | 根網域(eTLD+1) | example.com |
xF5XC_ORIGIN_POOLx | 來源池名稱 | csd-origin |
xF5XC_ORIGIN_IPx | 來源伺服器 IP | 44.232.69.192 |
xF5XC_ORIGIN_PORTx | 來源伺服器連接埠 | 3000 |
xF5XC_HC_NAMEx | 健康檢查名稱 | csd-hc |
oneOf 選擇群組
Section titled “oneOf 選擇群組”HTTP 負載平衡器規格使用 oneOf 選擇群組,每個群組必須恰好設定一個選項。在群組中設定零個或多於一個選項會導致 422 錯誤。
與 CSD 相關的關鍵選擇:
| 選擇群組 | 選項 | CSD 預設值 |
|---|---|---|
client_side_defense_choice | client_side_defense、disable_client_side_defense | client_side_defense |
java_script_choice(CSD 中的巢狀) | disable_js_insert、js_insert_all_pages、js_insert_all_pages_except、js_insertion_rules | js_insert_all_pages |
監聽器類型選擇(HTTP 與 HTTPS):
展示以不同的監聽器設定建立兩個 LB:
| LB | 監聽器選擇 | 設定 |
|---|---|---|
${F5XC_LB_NAME}-http(主要) | http | "http": { "dns_volterra_managed": true, "port": 80 } |
${F5XC_LB_NAME}-https(次要) | https_auto_cert | "https_auto_cert": { "http_redirect": true, "port": 443, ... } |
http 和 https_auto_cert 鍵是互斥的 — 每個 LB 恰好使用一個。
HTTPS 自動憑證巢狀選擇(僅次要 LB):
| 選擇群組 | 選項 | 預設值 |
|---|---|---|
| port | port(數字) | 443 |
server_header_choice | default_header、server_name、append_server_name | default_header |
path_normalize_choice | enable_path_normalize、disable_path_normalize | enable_path_normalize |
mtls_choice | no_mtls、use_mtls | no_mtls |
default_loadbalancer_choice | default_loadbalancer、non_default_loadbalancer | default_loadbalancer |
single_lb_app 巢狀選擇(ML 設定):
single_lb_app 物件有自己的必要 oneOf 群組。設定 single_lb_app: \{\} 而未包含這些巢狀選擇會導致 400 錯誤。
| 選擇群組 | 選項 | 預設值 |
|---|---|---|
api_discovery_choice | disable_discovery、enable_discovery | disable_discovery |
ddos_detection_choice | disable_ddos_detection、enable_ddos_detection | disable_ddos_detection |
malicious_user_detection_choice | disable_malicious_user_detection、enable_malicious_user_detection | disable_malicious_user_detection |
其他 LB 級別選擇(全部設定為停用/預設):
disable_rate_limit · no_service_policies · round_robin · disable_waf · no_challenge · disable_bot_defense · disable_api_definition · disable_api_discovery · disable_ip_reputation · disable_malicious_user_detection · single_lb_app · disable_trust_client_ip_headers · user_id_client_ip · disable_threat_mesh · l7_ddos_action_default · system_default_timeouts · default_sensitive_data_policy · disable_malware_protection · disable_api_testing
- 401 Unauthorized — API 權杖無效或已過期。在 Administration → Credentials 下重新產生。
- 403 Forbidden — 權杖缺少對命名空間的權限。檢查角色繫結。
- 404 Not Found — 命名空間或物件名稱不正確。使用
GET /api/config/namespaces/\{namespace\}/\{object_type\}列出物件。 - 409 Conflict — 物件已存在。對於受保護網域,帶有「domain already exists (in uriList)」的
409表示根網域已在租戶上註冊 — 這是成功條件,而非錯誤。對於其他物件,先刪除或使用PUT更新。 - 422 Unprocessable Entity — JSON 架構驗證失敗。常見原因:缺少
oneOf選擇、同一群組中設定了多個選擇、欄位類型錯誤。檢查錯誤訊息中的特定欄位。 - 物件限制已耗盡(錯誤碼
8,訊息"Object kind {kind} has exhausted limits({N})") — 租戶已達到物件配額限制。API 回傳 HTTP 200 並帶有包含"code": 8的 JSON 錯誤本文,而非 HTTP 429。您可以使用配額使用 API(GET /api/web/namespaces/system/quota/usage?namespace=system)在遇到此錯誤之前主動檢查配額容量。行為取決於物件類型:- Healthcheck(限制 ~150):非阻擋 — 跳過第 1 階段步驟 1 並在沒有健康檢查參考的情況下建立來源池。CSD 不依賴健康監控。
- Endpoint(限制 ~500):阻擋 — 端點是在來源池內建立的子物件。如果達到此限制,來源池建立失敗。刪除未使用的來源池(這會釋放其端點子物件)或聯繫您的管理員以增加租戶限制。
- Origin pool:阻擋 — 刪除未使用的來源池或聯繫您的管理員。
- HTTP load balancer:阻擋 — 刪除未使用的負載平衡器或聯繫您的管理員。
- Protected domain:阻擋 — 刪除未使用的受保護網域或聯繫您的管理員。
健康檢查未連結到來源池
Section titled “健康檢查未連結到來源池”如果第 1 階段步驟 2(驗證健康檢查已連結)顯示空陣列 []:
- 刪除來源池:
DELETE /api/config/namespaces/{namespace}/origin_pools/{pool_name} - 驗證健康檢查存在:
GET /api/config/namespaces/{namespace}/healthchecks/{hc_name} - 使用正確的健康檢查參考重新建立來源池
LB 停滯在 VIRTUAL_HOST_PENDING_A_RECORD
Section titled “LB 停滯在 VIRTUAL_HOST_PENDING_A_RECORD”如果負載平衡器 state 在第 1 階段步驟 4 之後仍為 VIRTUAL_HOST_PENDING_A_RECORD:
-
驗證 DNS 區域存在(僅限 F5 XC 託管 DNS):
Terminal window curl -s \-H "Authorization: APIToken xF5XC_API_TOKENx" \"xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx" \| jq '.metadata.name'404表示 DNS 區域尚未在 F5 XC 中建立。 -
檢查
allow_http_lb_managed_records— 如果區域存在但 LB 仍在等待,託管記錄可能已停用:Terminal window curl -s \-H "Authorization: APIToken xF5XC_API_TOKENx" \"xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx" \| jq '.spec.primary.allow_http_lb_managed_records'如果為
false或null,使用第 1 階段步驟 4,選項 A 中的PUT命令啟用它。 -
外部 DNS — 如果 F5 XC 不具授權性,請在您的 DNS 提供商手動建立 A 和 ACME CNAME 記錄(請參閱第 1 階段步驟 4,選項 B)。
-
驗證解析 — 修復 DNS 後確認:
Terminal window dig +short xF5XC_DOMAINNAMEx A如果 A 記錄解析成功,LB 將轉換為
VIRTUAL_HOST_READY。每 30 秒輪詢一次,最多 4 次迭代(共 2 分鐘)。如果 2 分鐘後仍為VIRTUAL_HOST_PENDING_A_RECORD,重新檢查 DNS 傳播並向操作員報告。
CSD 狀態顯示 isConfigured: false
Section titled “CSD 狀態顯示 isConfigured: false”CSD 必須在租戶層級啟用。聯繫您的 F5 XC 管理員為您的租戶啟用 Client-Side Defense。這是無法透過 API 設定的租戶級設定。
JS 注入不運作
Section titled “JS 注入不運作”- 驗證 CSD 已在租戶層級啟用(第 1 階段步驟 5)
- 確認負載平衡器規格中已設定
client_side_defense - 檢查 JS 設定端點是否回傳
scriptTag - 在瀏覽器中造訪受保護網域並檢視頁面原始碼以確認腳本已注入
受保護網域註冊回傳 409
Section titled “受保護網域註冊回傳 409”帶有「domain already exists (in uriList)」的 409 表示根網域已在租戶上註冊。受保護網域是租戶範圍的 — 它們不屬於任何單一命名空間。這是成功條件:網域已受到保護,無需進一步操作。繼續至第 1 階段步驟 7。
AutoCertDomainRateLimited
Section titled “AutoCertDomainRateLimited”如果 HTTPS LB cert_state 顯示 AutoCertDomainRateLimited,這表示 Let’s Encrypt 已對此網域的憑證簽發進行速率限制。這在頻繁建立和銷毀基礎架構的展示環境中是預期的。
影響: HTTPS LB 在速率限制重設之前不會提供流量(通常 1 小時)。HTTP LB 完全不受影響 — 所有展示流量正常透過 http:// 進行。
解決方案: 展示進行無需採取任何動作。HTTP LB(${F5XC_LB_NAME}-http)是主要展示目標,不依賴憑證佈建。如果需要 HTTPS,等待速率限制重設,憑證將自動佈建。
憑證停滯 — 乾淨重建(選用)
Section titled “憑證停滯 — 乾淨重建(選用)”當 HTTPS LB 憑證停滯在 DomainChallengePending 或 PreDomainChallengePending 超過 15 分鐘時,最快的恢復路徑是刪除 HTTPS LB 並重新建立。在 DNS 區域已設定(託管記錄已啟用)的情況下,乾淨重建通常在 5–7 分鐘內產生 CertificateValid — 除非受到速率限制。
- 刪除 HTTPS 負載平衡器:
DELETE .../http_loadbalancers/${F5XC_LB_NAME}-https - 等待 30 秒讓平台完成清理
- 重新建立 HTTPS 負載平衡器(第 1 階段步驟 3)
- 監控憑證狀態(第 1 階段步驟 7)— 預期在 5–10 分鐘內達到
CertificateValid
OpenAPI 參考
Section titled “OpenAPI 參考”F5 Distributed Cloud API 規格的標準來源可在以下位置取得:
https://docs.cloud.f5.com/docs-v2/downloads/f5-distributed-cloud-open-api.zip
此 ZIP 包含所有 API 群組的 OpenAPI 3.0 規格,包括 ves.io.schema.views.http_loadbalancer、ves.io.schema.healthcheck、ves.io.schema.origin_pool 和 ves.io.schema.shape.csd。使用這些規格來驗證 JSON 承載資料並探索其他欄位。