跳转到内容

演示

本指南将引导您在 F5 Distributed Cloud 上使用 API 完成一个完整的客户端防御练习 — 分为四个阶段,AI 助手或人工操作员可以端到端执行。每个步骤都包含一个可直接运行的 curl 命令,其中包含占位符值,您可以使用页面顶部的表单、.env 文件或任何自动化工具进行自定义。

阶段目标步骤
阶段 1 — 构建部署并验证完整的 CSD 基础设施步骤 1–7
阶段 2 — 攻击生成模拟攻击流量并确认 CSD 已检测到步骤 8–9
阶段 3 — 缓解缓解前后对比证明 — 运行攻击、应用缓解、重新运行攻击、比较结果步骤 1–6
阶段 4 — 拆除在明确确认后移除所有部署对象拆除

在开始阶段 1 之前,请验证环境是否干净。运行以下 API 检查以确定是否存在上次运行遗留的对象:

Terminal window
# Check all Phase 1 objects and compute environment status
HTTP_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 state
HTTPS_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 status
jq -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:验证中的确切命令:

  1. DNS 解析:dig +short xF5XC_DOMAINNAMEx A
  2. HTTP LB 状态:GET .../http_loadbalancers/xF5XC_LB_NAMEx-http 通过管道传递给 jq '{state: .spec.state}' — 必须显示 VIRTUAL_HOST_READY
  3. CSD JS 配置:GET .../js_configuration — 必须包含 scriptTag
  4. CSD 状态:GET .../status 通过管道传递给 jq '{configured: .isConfigured, enabled: .isEnabled}' — 两者都必须为 true

在跳到阶段 2 之前,所有必需的检查(DNS-1、LB-1、CSD-1、CSD-2)都必须通过。 如果任何检查失败,请从失败的步骤开始执行阶段 1。

上述飞行前检查验证环境是否干净。下面的就绪矩阵验证环境是否具备能力 — 即所有前提条件、配额、连接和平台服务是否已就位,以确保演示成功。在每次会议前作为准备阶段的一部分运行此矩阵。

每个检查都有一个测试 ID、一个层级(T0–T5)、通过/失败/警告标准以及修复路径。层级是顺序的 — 较早层级的失败会阻止后续层级的运行。

层级类别阻止演示?目的
T0连接与认证我们能否访问平台并进行认证?
T1配额与容量(达到限制时)是否有空间创建演示对象?
T2平台前提条件租户级服务是否已配置?
T3源站健康警告后端应用程序是否正在响应?
T4环境清洁自动修复是否有上次运行遗留的对象?
T5证书就绪信息性HTTPS 是否可用,还是应该计划仅使用 HTTP?

这些检查确认执行主机能够访问 F5 XC API 且凭据有效。

Terminal window
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
)
}'
Terminal window
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
)
}'
Terminal window
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
)
}'

非破坏性探测测试演示所需每种对象类型的读写权限。读权限通过对列表端点的 GET 测试。写权限通过对已知不存在的对象执行 DELETE 测试 — 如果 RBAC 拒绝操作,API 返回 403,如果操作被允许但对象不存在则返回 404。这种零副作用技术避免了创建临时探测对象。

Terminal window
PROBE_NAME="rbac-probe-nonexistent"
# Read probes
NS_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 matrix
jq -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("; ")
)
}'

这些检查查询租户的配额使用 API,以确定演示所需的每种对象类型的限制、当前使用量和剩余容量。这用单个只读 API 调用替代了探测-删除测试,该调用报告精确的数字。

查询租户级配额使用端点,并为演示所需的每种对象类型计算确定性的 PASS/WARN/FAIL 状态。此端点需要 system 命名空间。单个 API 调用可同时检查所有平台级配额。

关卡定义了一个 demo_needs 数组,指定演示将消耗每种对象类型的数量、该类型是否为必需,以及演示继续所需的最低数量。jq 过滤器将 remainingneeded 进行比较,并确定性地计算 status 字段 — 无需操作员解读。

Terminal window
# Step 1: Fetch quota data
curl -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 status
jq '
. 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(次要)。

CSD 受保护域不会出现在平台配额使用 API 中。使用基于探测的检查:创建并立即删除一个探测受保护域。

Terminal window
# Create probe and capture both HTTP code and response body
PROBE_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 status
echo "$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

如果 PF-T1-0 失败(配额使用 API 返回 403404 或意外格式),请回退到基于探测-删除的检查来验证健康检查、源站池、端点和负载均衡器配额。这些检查创建一个临时对象并立即删除它 — 如果创建返回错误代码 8 且带有”exhausted limits”消息,则配额已满。

每个回退探测使用相同的模式:创建一个临时对象,从响应中计算确定性状态,然后删除探测。如果对象已创建(.metadata.name 存在),status 字段为 PASS;如果错误代码为 8(限制已耗尽),根据该类型是否为必需,为 WARNFAIL

健康检查探测(耗尽时为 WARN — 健康检查对 CSD 是可选的):

Terminal window
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 — 两者都是必需的):

Terminal window
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/null

HTTP 负载均衡器探测(耗尽时为 FAIL — LB 是必需的):

Terminal window
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/null

这些检查验证演示所依赖的租户级服务。

Terminal window
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
)
}'
Terminal window
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-2 返回 200(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 '{
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)
}'

如需要则自动修复: 如果 statusWARN 且 PF-T2-4 显示 F5 XC 域名服务器(ns1.f5clouddns.comns2.f5clouddns.com),使用 GET+PUT 自动启用托管记录:

Terminal window
# Get current zone config
ZONE_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 enabled
echo "$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 .

然后重新检查以确认更新已生效:

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'
结果DNS 权威状态修复措施
true任何PASSLB 托管 DNS 记录将自动创建
false/nullF5 XC自动修复通过 GET+PUT 启用,验证,报告结果
false/null外部信息托管记录不适用于外部 DNS — 阶段 1 步骤 4 将使用选项 B(手动记录创建)
自动修复失败F5 XCFAIL令牌可能缺少系统命名空间写入权限 — 联系租户管理员
Terminal window
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
)
}'

这些检查验证后端应用程序是否可达。

跳过条件检查: 在运行连接测试之前,计算源站 IP 是否为 RFC 5737 TEST-NET 地址:

Terminal window
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)
}'

如果 statusSKIP,将 PF-T3-1 和 PF-T3-2 记录为 SKIP 并转到 T4。否则,运行以下检查。

Terminal window
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-1 返回有效 HTTP 状态时运行:

Terminal window
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 contentPASS源站提供 HTML 页面(CSD JS 注入所需)
WARN: No HTML detectedWARN源站可能是纯 API 服务或返回非 HTML 内容 — CSD JS 注入需要 HTML 页面响应

这些检查验证没有命名的 F5 XC 配置对象从 先前的演示运行中残留 — HTTP 负载均衡器、HTTPS 负载均衡器、 源站池、健康检查、受保护域和缓解域。 T4 关注的是对象级清理:可能与阶段 1 创建冲突的 API 对象是否仍然存在。它不测试 IP 地址、网络连接或源站健康(这些属于 T3 的关注范围)。

运行上面飞行前检查部分的六个飞行前命令。应用决策逻辑确定后续步骤。如果对象存在,在准备阶段执行自动拆除(无需确认)。

同时检查并删除先前中断的飞行前运行遗留的过时探测对象。这些探测仅在配额使用 API 不可用且使用了回退基于探测的检查时创建,或用于始终使用基于探测检查的受保护域探测(PF-T1-4):

Terminal window
# 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 — 两者都是预期的。

这些检查评估 HTTPS 是否可用或演示是否应计划仅使用 HTTP。

检查是否最近为演示域签发了 Let’s Encrypt 证书。频繁的创建/销毁周期可能耗尽每周速率限制(每个域每周 5 个重复证书)。

仅在先前运行中存在 HTTPS LB 时运行(PF-T4 发现了对象):

Terminal window
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

运行所有层级后,呈现一份综合就绪报告:

## 演示就绪状态:就绪 / 未就绪 / 有警告的就绪
### T0:连接与认证
| 检查 | 结果 | 状态 |
|---|---|---|
| PF-T0-1: API 连接 | 200 | PASS |
| PF-T0-2: 命名空间访问 | 200 | PASS |
| PF-T0-3: CSD API 访问 | 200 | PASS |
### T1:配额与容量
| 检查 | 类型 | 限制 | 使用量 | 剩余 | 需要 | 状态 |
|---|---|---|---|---|---|---|
| PF-T1-0: 配额使用关卡 | `healthcheck` | 150 | 148 | 2 | 1 | PASS |
| PF-T1-0: 配额使用关卡 | `origin_pool` | unlimited | 420 | unlimited | 1 | PASS |
| PF-T1-0: 配额使用关卡 | `endpoint` | 500 | 498 | 2 | 1 | PASS |
| PF-T1-0: 配额使用关卡 | `http_loadbalancer` | unlimited | 116 | unlimited | 2 | PASS |
| PF-T1-0: 配额使用关卡 | **关卡** | — | — | — | — | **PASS** |
| PF-T1-4: 受保护域 | — | — | — | — | 1 | PASS(探测) |
### T2:平台前提条件
| 检查 | 结果 | 状态 |
|---|---|---|
| PF-T2-1: CSD 租户状态 | 已配置 + 已启用 | PASS |
| PF-T2-2: DNS 区域存在 | 200 | PASS |
| PF-T2-3: DNS 托管记录 | true | PASS |
| PF-T2-4: DNS 域名服务器权威 | f5clouddns.com | PASS |
### T3:源站健康
| 检查 | 结果 | 状态 |
|---|---|---|
| PF-T3-1: 源站连接 | TEST-NET 地址 (192.0.2.1) | SKIP |
| PF-T3-2: HTML 内容 | TEST-NET 地址 | SKIP |
### T4:环境清洁
| 检查 | 结果 | 状态 |
|---|---|---|
| HTTP LB | 404 | PASS |
| HTTPS LB | 404 | PASS |
| 源站池 | 404 | PASS |
| 健康检查 | 404 | PASS |
| 受保护域 | 0 | PASS |
| 缓解域 | 0 | PASS |
### T5:证书就绪
| 检查 | 结果 | 状态 |
|---|---|---|
| PF-T5-2: 证书状态 | SKIP(无 HTTPS LB) | INFO |
### 警告
- (列出任何带有上下文的 WARN 或 INFO 条目)

整体状态规则:

条件状态
所有 T0–T4 检查通过就绪
所有 T0–T4 检查通过但 T3 或 T5 有 WARN/INFO有警告的就绪
任何 T0、T1 或 T2 检查为 FAIL未就绪 — 在继续之前解决
T4 有遗留对象自动修复(拆除),然后重新检查

本节定义了 AI 助手(Claude Code、Copilot 等)执行 API 自动化步骤的确定性工作流。遵循此协议可消除猜测 — 每个决策点都有定义的解决路径。

按以下确切顺序解析每个变量。在第一个提供非占位符值的来源处停止:

  1. 检查 .env 文件 — 在仓库根目录查找 .env。如果存在,解析所有 KEY=VALUE 对。
  2. 检查 shell 环境 — 运行 env | grep F5XC_ 查找当前会话中已导出的任何值。
  3. 识别缺失值 — 将已解析的值与下面必需/可选表进行比较。如果值不存在、为空或仍设置为占位符默认值(例如 example-api-tokenexample-tenantexample-namespaceapp.example.comuser@example.com),则视为”缺失”。
  4. 提示人工操作员 — 对于每个缺失的必需变量,要求操作员提供值。在所有必需变量解析完成之前不要继续。
  5. 应用默认值 — 对于每个缺失的可选变量,使用下表中的默认值,无需提示。

空字符串 = 缺失: 以空值导出的变量 (例如 F5XC_HC_NAME="")与未设置的变量视为相同 — 应用默认值。使用 shell 参数扩展 (${F5XC_HC_NAME:-csd-hc})一步应用默认值。

  1. 显示确认 — 向操作员显示最终解析的变量表,等待批准后再执行任何 API 调用。

准备阶段覆盖: 在第 1 阶段准备期间,跳过 步骤 6 中的等待。显示已解析的变量表作为记录,然后 立即继续。步骤 1–5 仍然适用 — 如果在检查 .env 和 shell 后 任何必需变量仍然缺失,则停止并报告 缺失的变量。

变量必需默认值占位符(视为缺失)
F5XC_API_TOKENexample-api-token
F5XC_API_URLhttps://example-tenant.console.ves.volterra.io
F5XC_NAMESPACEexample-namespace
F5XC_DOMAINNAMEapp.example.com
F5XC_ROOT_DOMAINexample.com
F5XC_LB_NAMEexample-lb-nameexample-lb
F5XC_EMAILuser@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] 为前缀,以便操作员能够区分诊断活动和正常执行
  • 记录您学到的内容:在解决问题后,使用以下内容更新相关阶段文件或故障排除部分:
    1. 失败场景(发生了什么问题)
    2. 尝试过但未奏效的方法(以便将来的运行不会重复)
    3. 有效的解决方案(解决问题的命令或修复方法)
  • 调试模式的目标是消除自身 — 每次调试会话都应产生文档更新,使下次执行完全确定
  • 文档更新且问题解决后,返回正常模式并从最后一个成功的记录步骤恢复
  • 如果调试模式无法解决问题,向操作员报告发现并停止 — 不要继续到下一个阶段

问答模式(问答阶段期间):

  • 仅在问答会议阶段(演示结论之后)活动
  • AI 助手可以构造临时 API 调用、运行诊断命令、导航到未脚本化的页面,并修改实时演示环境以说明对观众问题的回答
  • 没有 [DEBUG] 前缀 — 这是故意的即兴行为,不是错误恢复
  • 使用 DEMO_EXECUTOR.md 中的 CSD 产品专业知识部分作为产品问题的知识库

initScript 累积是演示失败的常见原因。每次使用 initScript 参数的 navigate_page 调用都会将脚本添加到一个持久列表中,该列表在每次后续文档加载时运行。请遵循以下规则:

  • 在使用 initScript 进行任何导航之前,始终导航到 about:blank,以清除先前运行累积的脚本
  • 在演示阶段之间切换时(例如阶段 2 → 阶段 3),使用带有 isolatedContextnew_page 以确保完全干净的浏览器状态
  • 从无响应页面恢复 — 如果 take_screenshottake_snapshot 超时,浏览器上下文资源已耗尽。使用带有 isolatedContextnew_page 创建新的上下文,然后从 about:blank 导航步骤重试
  • 请参阅阶段 2 攻击模拟附注,了解有关瞬态源站故障和资源耗尽恢复的详细指导

在每次 API 调用后,AI 助手必须使用以下格式向人工操作员呈现结构化证据:

创建步骤(POST):

字段状态
HTTP 状态200PASS
对象名称csd-origin
关键属性(通过 jq 提取)

在每个创建步骤后,运行 GET 确认对象存在并显示其关键属性。如果 GET 返回 404,报告 FAIL 并停止。

验证步骤(GET/dig):

测试结果状态
DNS-1: A 记录198.51.100.10PASS
LB-1: HTTP LB 状态VIRTUAL_HOST_READYPASS
LB-2: HTTPS LB 状态VIRTUAL_HOST_READYINFO(可选)
TLS-1: 证书状态CertificateValidINFO(可选)
CSD-1: JS 标签scriptTag 存在PASS

参考诊断与验证测试用例 ID(DNS-1、TLS-1、LB-1、CSD-1 等)作为每一层的验证标准。

阶段执行是顺序且有关卡的: 每个阶段必须在所有必需检查上达到 PASS 后才能开始下一阶段。演示遵循四阶段会议生命周期 — 请参阅 DEMO_EXECUTOR.md 中的会议阶段部分了解触发短语和行为规则。

AI 助手遵循以下序列:

  1. 准备 — 解析变量,运行飞行前检查,确认干净环境(可在会议前单独运行)
  2. 介绍 — SE 自我介绍并阐述成果目标(对客户端威胁的可见性、PCI 合规性、实时检测)
  3. 执行阶段 1(步骤 1–7)— 基础设施创建和验证;在继续之前所有阶段 1 检查必须通过
  4. 执行阶段 2(步骤 8–9)— 通过 API 进行攻击模拟和检测验证;具有浏览器自动化功能的 AI 助手直接执行浏览器步骤,没有浏览器工具的操作员手动执行
  5. 执行阶段 3 — 对所有检测到的域应用缓解,重新运行攻击,验证阻止是否有效
  6. 结论 — 重申成果目标,总结每个阶段的证据,突出关键检测和缓解
  7. 问答 — 即兴阶段,演示保持实时状态,SE 回答观众问题并提出回馈问题
  8. 拆除(会议后)— 阶段 4,需要操作员明确确认,按反向依赖顺序删除所有对象,确认环境干净

如果任何步骤返回 FAIL,在继续之前停止并报告失败及相关故障排除部分链接。

  • 一个 F5 XC API 令牌 — 在 AdministrationCredentialsAPI Credentials 下生成
  • 本地安装 curljq
  • 一个具有创建健康检查、源站池和 HTTP 负载均衡器权限的命名空间

使用您的环境值创建 .env 文件。仓库中提供了一个模板:

Terminal window
cp .env.example .env

使用您的实际值编辑 .env

.env
# Required — your environment
F5XC_API_TOKEN=example-api-token
F5XC_API_URL=https://example-tenant.console.ves.volterra.io
F5XC_DOMAINNAME=app.example.com
F5XC_EMAIL=user@example.com
F5XC_LB_NAME=example-lb-name
F5XC_NAMESPACE=example-namespace
F5XC_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 会话中:

Terminal window
set -a && source .env && set +a

curl 命令中的每个 xTOKENx 占位符直接对应一个环境变量 — 例如,xF5XC_API_TOKENx 对应 $F5XC_API_TOKEN。您可以使用页面顶部的交互式表单替换这些值,或让 Claude Code 等 AI 助手读取您的 .env 并为您构建命令。

令牌默认值描述
xF5XC_API_URLxhttps://example-tenant.console.ves.volterra.ioXC 控制台 API URL
xF5XC_API_TOKENxexample-api-tokenAPI 凭据令牌
xF5XC_EMAILxuser@example.comCSD 通知邮箱地址
xF5XC_NAMESPACExexample-namespace命名空间
xF5XC_LB_NAMExexample-lbHTTP 负载均衡器基础名称(创建 ${name}-http${name}-https
xF5XC_DOMAINNAMExapp.example.com要保护的 FQDN
xF5XC_ROOT_DOMAINxexample.com根域(eTLD+1),用于 CSD 受保护域
xF5XC_ORIGIN_POOLxcsd-origin源站池名称
xF5XC_ORIGIN_IPx44.232.69.192源站服务器 IP
xF5XC_ORIGIN_PORTx3000源站服务器端口
xF5XC_HC_NAMExcsd-hc健康检查名称

本节总结了用于脚本或自动化的完整练习工作流。

  1. 克隆仓库并复制环境模板:cp .env.example .env
  2. 使用您的租户 URL、API 令牌、命名空间和域值编辑 .env
  3. 加载环境:set -a && source .env && set +a
  4. 按顺序执行每个阶段,在继续到下一阶段之前验证每个证据块中的 PASS

值使用AI 助手执行协议中定义的确定性协议进行解析:

  1. .env 文件 — 从仓库根目录解析 KEY=VALUE
  2. Shell 环境 — 检查 env | grep F5XC_ 获取已导出的值
  3. 占位符检测 — 将匹配占位符默认值(例如 example-api-tokenexample-namespace)的任何值标记为缺失
  4. 提示操作员 — 询问每个缺失的必需变量
  5. 应用默认值 — 对缺失的可选变量使用内置默认值
  6. 确认 — 显示已解析的变量表并等待操作员批准
  1. 阶段 1 — 构建:部署基础设施(健康检查、源站池、HTTP LB + HTTPS LB),配置 DNS,启用 CSD,注册受保护域,验证所有组件。HTTP LB 是主要演示目标;HTTPS LB 是可选的。
  2. 阶段 2 — 攻击:使用 http:// URL 在浏览器中运行攻击模拟,等待 5–10 分钟,通过 API 验证检测(/scripts/detected_domains/formFields
  3. 阶段 3 — 缓解:确认干净基线,运行攻击(之前的证明),将每个域 POST 到 /mitigated_domains,验证缓解已应用,使用 http:// URL 重新运行攻击(之后的证明),展示前后对比
  4. 阶段 4 — 拆除 (需要人工明确确认):删除 HTTPS LB → HTTP LB → 源站池 → DNS 区域清理(仅手动记录) → 健康检查 → 受保护域。不要删除 DNS 区域。
令牌描述默认值
xF5XC_API_URLxXC 控制台 API URLhttps://example-tenant.console.ves.volterra.io
xF5XC_API_TOKENxAPI 凭据令牌(用户提供)
xF5XC_EMAILxCSD 通知邮箱user@example.com
xF5XC_NAMESPACEx命名空间example-namespace
xF5XC_LB_NAMExHTTP 负载均衡器基础名称(创建 ${name}-http${name}-httpsexample-lb
xF5XC_DOMAINNAMEx要保护的 FQDNapp.example.com
xF5XC_ROOT_DOMAINx根域(eTLD+1)example.com
xF5XC_ORIGIN_POOLx源站池名称csd-origin
xF5XC_ORIGIN_IPx源站服务器 IP44.232.69.192
xF5XC_ORIGIN_PORTx源站服务器端口3000
xF5XC_HC_NAMEx健康检查名称csd-hc

HTTP 负载均衡器规范使用 oneOf 选择组,其中每个组必须恰好设置一个选项。在一个组中设置零个或多个选项会导致 422 错误。

与 CSD 相关的关键选择:

选择组选项CSD 默认值
client_side_defense_choiceclient_side_defensedisable_client_side_defenseclient_side_defense
java_script_choice(CSD 中嵌套)disable_js_insertjs_insert_all_pagesjs_insert_all_pages_exceptjs_insertion_rulesjs_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, ... }

httphttps_auto_cert 键是互斥的 — 每个 LB 恰好使用一个。

HTTPS 自动证书嵌套选择(仅次要 LB):

选择组选项默认值
portport(数字)443
server_header_choicedefault_headerserver_nameappend_server_namedefault_header
path_normalize_choiceenable_path_normalizedisable_path_normalizeenable_path_normalize
mtls_choiceno_mtlsuse_mtlsno_mtls
default_loadbalancer_choicedefault_loadbalancernon_default_loadbalancerdefault_loadbalancer

single_lb_app 嵌套选择(ML 配置):

single_lb_app 对象有自己的必需 oneOf 组。设置 single_lb_app: \{\} 而不包含这些嵌套选择会导致 400 错误。

选择组选项默认值
api_discovery_choicedisable_discoveryenable_discoverydisable_discovery
ddos_detection_choicedisable_ddos_detectionenable_ddos_detectiondisable_ddos_detection
malicious_user_detection_choicedisable_malicious_user_detectionenable_malicious_user_detectiondisable_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 令牌无效或已过期。在 AdministrationCredentials 下重新生成。
  • 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 返回带有 JSON 错误正文(包含 "code": 8)的 HTTP 200,而非 HTTP 429。您可以在遇到此错误之前使用配额使用 APIGET /api/web/namespaces/system/quota/usage?namespace=system)主动检查配额容量。行为取决于对象类型:
    • 健康检查(限制 ~150):非阻塞 — 跳过阶段 1 步骤 1 并在没有健康检查引用的情况下创建源站池。CSD 不依赖健康监控。
    • 端点(限制 ~500):阻塞 — 端点是在源站池内创建的子对象。如果达到此限制,源站池创建将失败。删除未使用的源站池(这将释放其端点子对象)或联系管理员增加租户限额。
    • 源站池阻塞 — 删除未使用的源站池或联系管理员。
    • HTTP 负载均衡器阻塞 — 删除未使用的负载均衡器或联系管理员。
    • 受保护域阻塞 — 删除未使用的受保护域或联系管理员。

如果阶段 1 步骤 2(验证健康检查已链接)显示空数组 []

  1. 删除源站池:DELETE /api/config/namespaces/{namespace}/origin_pools/{pool_name}
  2. 验证健康检查存在:GET /api/config/namespaces/{namespace}/healthchecks/{hc_name}
  3. 使用正确的健康检查引用重新创建源站池

如果负载均衡器 state 在阶段 1 步骤 4 之后仍为 VIRTUAL_HOST_PENDING_A_RECORD

  1. 验证 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 中创建。

  2. 检查 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'

    如果为 falsenull,使用阶段 1 步骤 4,选项 A 中的 PUT 命令启用它。

  3. 外部 DNS — 如果 F5 XC 不是权威的,请在您的 DNS 提供商处手动创建 A 和 ACME CNAME 记录(参见阶段 1 步骤 4,选项 B)。

  4. 验证解析 — 修复 DNS 后,确认:

    Terminal window
    dig +short xF5XC_DOMAINNAMEx A

    如果 A 记录解析成功,LB 将转换为 VIRTUAL_HOST_READY。每 30 秒轮询一次,最多 4 次迭代(共 2 分钟)。如果 2 分钟后仍为 VIRTUAL_HOST_PENDING_A_RECORD,重新检查 DNS 传播并向操作员报告。

CSD 必须在租户级别启用。联系您的 F5 XC 管理员为您的租户启用客户端防御。这是一个租户级设置,无法通过 API 配置。

  1. 验证 CSD 在租户级别已启用(阶段 1 步骤 5)
  2. 确认负载均衡器的规范中设置了 client_side_defense
  3. 检查 JS 配置端点是否返回 scriptTag
  4. 在浏览器中访问受保护域并查看页面源代码以确认脚本已注入

带有”domain already exists (in uriList)“的 409 表示根域已在租户上注册。受保护域是租户范围的 — 它们不属于任何单一命名空间。这是成功条件:域已受保护,无需进一步操作。继续到阶段 1 步骤 7。

如果 HTTPS LB cert_state 显示 AutoCertDomainRateLimited,这意味着 Let’s Encrypt 已对此域的证书签发进行了速率限制。这在频繁创建和销毁基础设施的演示环境中是预期的

影响: HTTPS LB 在速率限制重置(通常为 1 小时)之前将无法服务流量。HTTP LB 完全不受影响 — 所有演示流量通过 http:// 正常进行。

解决方案: 演示推进无需任何操作。HTTP LB(${F5XC_LB_NAME}-http)是主要演示目标,不依赖于证书配置。如果需要 HTTPS,等待速率限制重置,证书将自动配置。

当 HTTPS LB 证书在 DomainChallengePendingPreDomainChallengePending 状态超过 15 分钟时,最快的恢复路径是删除 HTTPS LB 并重新创建。在 DNS 区域已配置(托管记录已启用)的情况下,清洁重建通常在 5-7 分钟内产生 CertificateValid — 除非被速率限制。

  1. 删除 HTTPS 负载均衡器:DELETE .../http_loadbalancers/${F5XC_LB_NAME}-https
  2. 等待 30 秒让平台清理
  3. 重新创建 HTTPS 负载均衡器(阶段 1 步骤 3
  4. 监控证书状态(阶段 1 步骤 7)— 预期在 5-10 分钟内达到 CertificateValid

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_loadbalancerves.io.schema.healthcheckves.io.schema.origin_poolves.io.schema.shape.csd。使用这些规范验证 JSON 负载并发现其他字段。