콘텐츠로 이동

데모

이 가이드는 API를 사용하여 F5 Distributed Cloud에서 완전한 Client-Side Defense 실습을 진행하는 과정을 안내합니다 — AI 어시스턴트 또는 인간 운영자가 처음부터 끝까지 실행할 수 있도록 4단계로 구성되어 있습니다. 각 단계에는 페이지 상단의 폼, .env 파일, 또는 자동화 도구를 사용하여 사용자 정의할 수 있는 플레이스홀더 값이 포함된 즉시 실행 가능한 curl 명령이 포함되어 있습니다.

단계목표스텝
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-httpjq '{state: .spec.state}'로 파이프 — VIRTUAL_HOST_READY가 표시되어야 합니다
  3. CSD JS 구성: GET .../js_configurationscriptTag가 포함되어야 합니다
  4. CSD 상태: GET .../statusjq '{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 호출로 대체합니다.

테넌트 전체 할당량 사용 엔드포인트를 조회하고 데모에 필요한 모든 객체 종류에 대해 결정적 통과/경고/실패 상태를 계산합니다. 이 엔드포인트는 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 — 하나 이상의 종류가 용량이 줄었지만 진행에 필요한 최소치는 충족됩니다(예: 2개가 아닌 1개의 LB 슬롯만 사용 가능, 또는 헬스체크 할당량 소진). 데모는 제한 사항과 함께 진행할 수 있습니다.
  • 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조치
PASSPF-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가 403, 404 또는 예상치 못한 형식을 반환), 헬스체크, 오리진 풀, 엔드포인트 및 로드 밸런서 할당량에 대한 프로브 후 삭제 검사로 대체합니다. 이 검사들은 임시 객체를 생성하고 즉시 삭제합니다 — 생성 시 오류 코드 8과 “exhausted limits”가 반환되면 할당량이 가득 찬 것입니다.

각 대체 프로브는 동일한 패턴을 사용합니다: 임시 객체를 생성하고 응답에서 결정적 상태를 계산한 다음 프로브를 삭제합니다. status 필드는 객체가 생성되면(.metadata.name 존재) PASS, 오류 코드 8(한도 소진)이면 해당 종류가 필수인지 여부에 따라 WARN 또는 FAIL입니다.

헬스체크 프로브 (소진 시 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-3: DNS 관리 레코드 활성화 여부

섹션 제목: “PF-T2-3: DNS 관리 레코드 활성화 여부”

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.com, ns2.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모든 경우통과LB 관리 DNS 레코드가 자동 생성됩니다
false/nullF5 XC자동 해결GET+PUT으로 활성화, 확인, 결과 보고
false/null외부정보외부 DNS에는 관리 레코드가 적용되지 않음 — 1단계 스텝 4에서 옵션 B(수동 레코드 생성)를 사용합니다
자동 해결 실패F5 XC실패토큰에 system 네임스페이스 쓰기 접근 권한이 없을 수 있음 — 테넌트 관리자에게 문의
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-2: 오리진 HTML 콘텐츠 제공 여부

섹션 제목: “PF-T3-2: 오리진 HTML 콘텐츠 제공 여부”

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 content통과오리진이 HTML 페이지를 제공합니다(CSD JS 삽입에 필요)
WARN: No HTML detected경고오리진이 API 전용 서비스이거나 비HTML을 반환할 수 있음 — CSD JS 삽입에는 HTML 페이지 응답이 필요합니다

이 검사들은 이전 데모 실행에서 명명된 F5 XC 구성 객체가 남아 있지 않은지 확인합니다 — HTTP 로드 밸런서, HTTPS 로드 밸런서, 오리진 풀, 헬스체크, 보호 도메인 및 완화 도메인. T4는 객체 수준 정리에 관한 것입니다: 1단계 생성과 충돌할 수 있는 API 객체가 여전히 존재하는지 여부입니다. IP 주소, 네트워크 연결성 또는 오리진 상태는 테스트하지 않습니다(해당 사항은 T3에 해당).

위의 사전 점검 섹션에서 6개의 사전 점검 명령을 실행합니다. 판단 로직을 적용하여 다음 단계를 결정합니다. 객체가 존재하는 경우, 준비 단계에서 자동 해제가 수행됩니다(확인 필요 없음).

또한 이전에 중단된 사전 점검 실행에서 남은 오래된 프로브 객체를 확인하고 삭제합니다. 이러한 프로브는 할당량 사용 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

모든 계층을 실행한 후, 통합 준비 상태 보고서를 제시합니다:

## 데모 준비 상태: READY / NOT READY / READY WITH WARNINGS
### 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: 할당량 사용 게이트 | **gate** | — | — | — | — | **PASS** |
| PF-T1-4: 보호 도메인 | — | — | — | — | 1 | PASS (probe) |
### T2: 플랫폼 전제 조건
| 검사 | 결과 | 상태 |
|---|---|---|
| PF-T2-1: CSD 테넌트 상태 | configured + enabled | 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 address (192.0.2.1) | SKIP |
| PF-T3-2: HTML 콘텐츠 | TEST-NET address | SKIP |
### T4: 환경 정리
| 검사 | 결과 | 상태 |
|---|---|---|
| HTTP LB | 404 | PASS |
| HTTPS LB | 404 | PASS |
| Origin Pool | 404 | PASS |
| Healthcheck | 404 | PASS |
| Protected Domains | 0 | PASS |
| Mitigated Domains | 0 | PASS |
### T5: 인증서 준비 상태
| 검사 | 결과 | 상태 |
|---|---|---|
| PF-T5-2: 인증서 상태 | SKIP (no HTTPS LB) | INFO |
### 경고
- (컨텍스트와 함께 WARN 또는 INFO 항목 나열)

전체 상태 규칙:

조건상태
모든 T0–T4 검사 통과READY
모든 T0–T4 검사 통과하지만 T3 또는 T5에 WARN/INFO 있음READY WITH WARNINGS
T0, T1 또는 T2 검사 중 하나라도 FAILNOT READY — 진행 전 해결 필요
T4에 남은 객체 있음자동 해결(해제) 후 재확인

이 섹션은 API 자동화 단계를 실행하는 AI 어시스턴트(Claude Code, Copilot 등)를 위한 결정적 워크플로우를 정의합니다. 이 프로토콜을 따르면 추측이 제거됩니다 — 모든 의사 결정 지점에 정의된 해결 경로가 있습니다.

각 변수를 정확히 이 순서로 확인합니다. 비플레이스홀더 값을 제공하는 첫 번째 소스에서 멈춥니다:

  1. .env 파일 확인 — 저장소 루트에서 .env를 찾습니다. 존재하면 모든 KEY=VALUE 쌍을 파싱합니다.
  2. 셸 환경 확인env | grep F5XC_를 실행하여 현재 세션에 이미 내보내진 값을 찾습니다.
  3. 누락된 값 식별 — 확인된 값을 아래의 필수/선택 테이블과 비교합니다. 값이 없거나 비어 있거나 플레이스홀더 기본값(예: example-api-token, example-tenant, example-namespace, app.example.com, user@example.com)으로 설정된 경우 “누락”입니다.
  4. 인간 운영자에게 질문 — 누락된 각 필수 변수에 대해 운영자에게 값을 제공하도록 요청합니다. 모든 필수 변수가 확인될 때까지 진행하지 않습니다.
  5. 기본값 적용 — 누락된 각 선택 변수에 대해 프롬프트 없이 아래 테이블의 기본값을 사용합니다.

빈 문자열 = 누락: 빈 값으로 내보낸 변수 (예: F5XC_HC_NAME="")는 설정되지 않은 변수와 동일하게 처리됩니다 — 기본값을 적용합니다. 셸 매개변수 확장 (${F5XC_HC_NAME:-csd-hc})을 사용하여 한 단계로 기본값을 적용합니다.

  1. 확인 표시 — 최종 확인된 변수 테이블을 운영자에게 보여주고 API 호출을 실행하기 전에 승인을 기다립니다.

준비 단계 재정의: 1단계 준비 중에는 6단계의 대기를 건너뜁니다. 기록을 위해 확인된 변수 테이블을 표시한 다음 즉시 진행합니다. 1–5단계는 여전히 적용됩니다 — .env 및 셸을 확인한 후에도 필수 변수가 누락된 경우 중지하고 누락된 변수를 보고합니다.

변수필수기본값플레이스홀더 (누락으로 처리)
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-name, example-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 어시스턴트는 데모 중 세 가지 모드 중 하나로 작동합니다:

모드활성화 시점동작
일반준비, 실행, 해제 중 기본문서화된 명령만 그대로 실행
디버그실패 시 자동 활성화창의적 문제 해결, 문서 업데이트
Q&AQ&A 단계 중즉흥적 — 청중 질문에 답하기 위한 임시 명령 허용

일반 모드 (기본):

  • 모든 API 호출, 검증 쿼리, 셸 명령은 단계 파일 (1–4단계) 또는 위의 사전 점검 섹션에서 그대로 가져와야 합니다
  • xTOKENx 플레이스홀더만 확인된 변수 값으로 대체합니다
  • 일반 지식이나 추론으로 API 엔드포인트, jq 필터 또는 cURL 명령을 구성하지 않습니다
  • 필요한 명령이 문서화되어 있지 않으면 중지하고 운영자에게 보고합니다: “이 검증 단계는 단계 문서에 포함되어 있지 않습니다”

디버그 모드 (실패 시 자동 활성화):

  • 문서화된 명령이 예상치 못한 결과를 생성할 때 자동으로 활성화됩니다: 비2xx HTTP 응답, jq 파싱 오류, 명령 시간 초과, 또는 증거 테이블과 모순되는 응답 본문
  • 디버그 모드에서 AI 어시스턴트는 진단 명령을 구성하고, 원시 API 응답을 검사하고, 엔드포인트 변형을 테스트하며, 근본 원인을 찾기 위해 창의적 문제 해결을 사용할 수 있습니다
  • 모든 디버그 출력에 [DEBUG] 접두사를 붙여 운영자가 진단 활동과 일반 실행을 구분할 수 있도록 합니다
  • 배운 것을 문서화합니다: 문제를 해결한 후 관련 단계 파일 또는 문제 해결 섹션을 다음으로 업데이트합니다:
    1. 실패 시나리오 (무엇이 잘못되었는지)
    2. 시도했지만 효과가 없었던 것 (향후 실행에서 반복하지 않도록)
    3. 작동하는 해결 방법 (문제를 해결한 명령 또는 수정)
  • 디버그 모드의 목표는 자신을 제거하는 것입니다 — 모든 디버그 세션은 다음 실행을 완전히 결정적으로 만드는 문서 업데이트를 생성해야 합니다
  • 문서가 업데이트되고 문제가 해결되면 일반 모드로 돌아가고 마지막으로 성공한 문서화된 단계에서 재개합니다
  • 디버그 모드가 문제를 해결할 수 없는 경우, 운영자에게 결과를 보고하고 중지합니다 — 다음 단계로 계속하지 않습니다

Q&A 모드 (Q&A 단계 중):

  • 데모 결론 후 Q&A 미팅 단계에서만 활성화됩니다
  • AI 어시스턴트는 임시 API 호출을 구성하고, 진단 명령을 실행하고, 스크립트에 없는 페이지로 이동하며, 청중 질문에 대한 답변을 설명하기 위해 라이브 데모 환경을 수정할 수 있습니다
  • [DEBUG] 접두사 없음 — 이는 의도적인 즉흥 동작이며 오류 복구가 아닙니다
  • DEMO_EXECUTOR.md의 CSD 제품 전문 지식 섹션을 제품 질문의 지식 기반으로 사용합니다

initScript 누적은 데모 실패의 일반적인 원인입니다. initScript 매개변수가 있는 각 navigate_page 호출은 이후의 모든 문서 로드에서 실행되는 영구 목록에 스크립트를 추가합니다. 다음 규칙을 따르십시오:

  • initScript가 있는 탐색 전에 항상 about:blank로 이동하여 이전 실행에서 누적된 스크립트를 지웁니다
  • 데모 단계 간(예: 2단계 → 3단계) 전환 시 new_pageisolatedContext를 사용하여 완전히 깨끗한 브라우저 상태를 보장합니다
  • 응답 없는 페이지 복구take_screenshot 또는 take_snapshot 시간 초과가 발생하면 브라우저 컨텍스트가 리소스 소진 상태입니다. new_pageisolatedContext를 사용하여 새 컨텍스트를 생성한 다음 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 등)를 참조합니다.

단계 실행은 순차적이며 게이트됩니다: 다음 단계가 시작되기 전에 각 단계가 모든 필수 검사에서 통과해야 합니다. 데모는 4단계 미팅 수명 주기를 따릅니다 — 트리거 구문 및 동작 규칙은 DEMO_EXECUTOR.md의 미팅 단계 섹션을 참조하십시오.

AI 어시스턴트는 이 순서를 따릅니다:

  1. 준비 — 변수 확인, 사전 점검 실행, 깨끗한 환경 확인 (미팅 전 별도로 실행 가능)
  2. 소개 — SE가 자신을 소개하고 결과 목표를 명시합니다 (클라이언트 측 위협에 대한 가시성, PCI 준수, 실시간 탐지)
  3. 1단계 실행 (스텝 1–7) — 인프라 생성 및 검증; 진행하기 전에 모든 1단계 검사가 통과해야 합니다
  4. 2단계 실행 (스텝 8–9) — API를 통한 공격 시뮬레이션 및 탐지 검증; 브라우저 자동화가 있는 AI 어시스턴트는 브라우저 단계를 직접 실행하고, 브라우저 도구가 없는 운영자는 수동으로 수행합니다
  5. 3단계 실행 — 탐지된 모든 도메인에 대한 완화 적용, 공격 재실행, 차단이 효과적인지 검증
  6. 결론 — 결과 목표 재진술, 각 단계의 증거 요약, 주요 탐지 및 완화 강조
  7. Q&A — 즉흥 단계, 데모는 라이브로 유지, 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

셸 세션에 변수를 로드하기 위해 파일을 소싱합니다:

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.comCSD 보호 도메인의 루트 도메인(eTLD+1)
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. 각 단계를 순서대로 실행하며, 다음 단계로 진행하기 전에 각 증거 블록에서 통과를 확인합니다

값은 AI 어시스턴트 실행 프로토콜에 정의된 결정적 프로토콜을 사용하여 확인됩니다:

  1. .env 파일 — 저장소 루트에서 KEY=VALUE 쌍을 파싱합니다
  2. 셸 환경env | grep F5XC_로 내보내진 값을 확인합니다
  3. 플레이스홀더 감지 — 플레이스홀더 기본값(예: example-api-token, example-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단계 — 완화: 깨끗한 기준선 확인, 공격 실행(전 증명), 각 도메인을 /mitigated_domains에 POST, 완화 적용 확인, 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}-https 생성)example-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 선택 그룹을 사용합니다. 그룹에서 0개 또는 2개 이상의 옵션을 설정하면 422 오류가 발생합니다.

주요 CSD 관련 선택:

선택 그룹옵션CSD 기본값
client_side_defense_choiceclient_side_defense, disable_client_side_defenseclient_side_defense
java_script_choice (CSD 내 중첩)disable_js_insert, js_insert_all_pages, js_insert_all_pages_except, js_insertion_rulesjs_insert_all_pages

리스너 유형 선택 (HTTP vs 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_header, server_name, append_server_namedefault_header
path_normalize_choiceenable_path_normalize, disable_path_normalizeenable_path_normalize
mtls_choiceno_mtls, use_mtlsno_mtls
default_loadbalancer_choicedefault_loadbalancer, non_default_loadbalancerdefault_loadbalancer

single_lb_app 중첩 선택 (ML 구성):

single_lb_app 객체에는 자체적인 필수 oneOf 그룹이 있습니다. 이러한 중첩 선택 없이 single_lb_app: \{\}를 설정하면 400 오류가 발생합니다.

선택 그룹옵션기본값
api_discovery_choicedisable_discovery, enable_discoverydisable_discovery
ddos_detection_choicedisable_ddos_detection, enable_ddos_detectiondisable_ddos_detection
malicious_user_detection_choicedisable_malicious_user_detection, enable_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는 HTTP 429가 아닌 "code": 8이 포함된 JSON 오류 본문과 함께 HTTP 200을 반환합니다. 할당량 사용 API(GET /api/web/namespaces/system/quota/usage?namespace=system)를 사용하여 이 오류를 만나기 전에 사전에 할당량 용량을 확인할 수 있습니다. 동작은 객체 유형에 따라 다릅니다:
    • Healthcheck (한도 ~150): 비차단 — 1단계 스텝 1을 건너뛰고 헬스체크 참조 없이 오리진 풀을 생성합니다. CSD는 상태 모니터링에 의존하지 않습니다.
    • Endpoint (한도 ~500): 차단 — 엔드포인트는 오리진 풀 내부에 생성되는 하위 객체입니다. 이 한도에 도달하면 오리진 풀 생성이 실패합니다. 사용하지 않는 오리진 풀을 삭제하거나(엔드포인트 하위 객체가 해제됨) 관리자에게 테넌트 한도 증가를 요청하십시오.
    • Origin pool: 차단 — 사용하지 않는 오리진 풀을 삭제하거나 관리자에게 문의하십시오.
    • HTTP load balancer: 차단 — 사용하지 않는 로드 밸런서를 삭제하거나 관리자에게 문의하십시오.
    • Protected domain: 차단 — 사용하지 않는 보호 도메인을 삭제하거나 관리자에게 문의하십시오.

헬스체크가 오리진 풀에 연결되지 않음

섹션 제목: “헬스체크가 오리진 풀에 연결되지 않음”

1단계 스텝 2(헬스체크 연결 확인)에서 빈 배열 []이 표시되는 경우:

  1. 오리진 풀을 삭제합니다: DELETE /api/config/namespaces/{namespace}/origin_pools/{pool_name}
  2. 헬스체크가 존재하는지 확인합니다: GET /api/config/namespaces/{namespace}/healthchecks/{hc_name}
  3. 올바른 헬스체크 참조로 오리진 풀을 다시 생성합니다

LB가 VIRTUAL_HOST_PENDING_A_RECORD 상태에서 멈춤

섹션 제목: “LB가 VIRTUAL_HOST_PENDING_A_RECORD 상태에서 멈춤”

1단계 스텝 4 이후 로드 밸런서 stateVIRTUAL_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는 F5 XC에 DNS 존이 생성되지 않았음을 의미합니다.

  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'

    false 또는 null이면 1단계 스텝 4, 옵션 APUT 명령을 사용하여 활성화합니다.

  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 상태가 isConfigured: false를 표시

섹션 제목: “CSD 상태가 isConfigured: false를 표시”

CSD는 테넌트 수준에서 활성화해야 합니다. 테넌트에 대해 Client-Side Defense를 활성화하려면 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_stateAutoCertDomainRateLimited를 표시하면, 이 도메인에 대한 Let’s Encrypt 인증서 발급이 속도 제한되었음을 의미합니다. 이는 인프라가 자주 생성 및 삭제되는 데모 환경에서 예상되는 동작입니다.

영향: 속도 제한이 재설정될 때까지(일반적으로 1시간) HTTPS LB가 트래픽을 제공하지 않습니다. HTTP LB는 완전히 영향받지 않습니다 — 모든 데모 트래픽은 http://를 통해 정상적으로 진행됩니다.

해결: 데모 진행을 위한 조치가 필요하지 않습니다. HTTP LB(${F5XC_LB_NAME}-http)가 기본 데모 대상이며 인증서 프로비저닝에 의존하지 않습니다. HTTPS가 필요한 경우, 속도 제한이 재설정될 때까지 기다리면 인증서가 자동 프로비저닝됩니다.

인증서 멈춤 — 깨끗한 재생성 (선택 사항)

섹션 제목: “인증서 멈춤 — 깨끗한 재생성 (선택 사항)”

HTTPS LB 인증서가 15분 이상 DomainChallengePending 또는 PreDomainChallengePending 상태에서 멈춘 경우, 가장 빠른 복구 경로는 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에는 ves.io.schema.views.http_loadbalancer, ves.io.schema.healthcheck, ves.io.schema.origin_pool, ves.io.schema.shape.csd를 포함한 모든 API 그룹에 대한 OpenAPI 3.0 스펙이 포함되어 있습니다. 이 스펙을 사용하여 JSON 페이로드를 검증하고 추가 필드를 확인하십시오.