Demo
This guide covers the direct API ownership mode for a complete Client-Side Defense exercise.
Terraform users must follow the Terraform deployment group instead. Both
modes create the same logical architecture: one origin pool and one HTTPS auto-certificate HTTP
load balancer named client-side-defense for client-side-defense.f5-sales-demo.com, with HTTP
redirect and CSD JavaScript insertion on all pages.
The AWS reference supplies the Application Load Balancer hostname through the origin pool’s
public_name; an Azure alternate supplies its address through public_ip.
Choose one ownership mode. Never run the API create/update/delete workflow against resources present in Terraform state.
Exercise Phases
Section titled “Exercise Phases”| Phase | Goal | Steps |
|---|---|---|
| Phase 1 — API Build | Deploy and validate the shared CSD architecture with API ownership | Steps 1–7 |
| Phase 2 — Attack | Generate simulated attack traffic and confirm CSD detected it | Steps 8–9 |
| Phase 3 — Mitigate | Before/after mitigation proof — run attack, apply mitigations, re-run attack, compare | Steps 1–6 |
| Phase 4 — Teardown | Remove all API-owned deployment objects after explicit confirmation | Teardown |
Pre-flight Check
Section titled “Pre-flight Check”Establish XCSH_CSD_DEPLOYMENT_MODE before any request. Run these GET-only checks only in API mode. Terraform mode stops here and uses only its configured backend and state.
Before the API build, inspect the exact load balancer, origin pool, optional healthcheck, protected-domain resource, and any known mitigated-domain resource names. Skip the healthcheck GET when XCSH_HC_NAME is empty; omission is valid.
Exact detail GETs are authoritative for configuration and ownership classification. List endpoints are discovery summaries only: configuration lists expose items[].name and may omit get_spec, so use a discovered name with its detail GET before classifying an object. Do not reduce protected or mitigated domains to global counts.
case "${XCSH_CSD_DEPLOYMENT_MODE:-}" in api) ;; terraform) echo "INFO: Terraform owns lifecycle"; exit 0 ;; *) echo "FAIL: invalid deployment mode"; exit 1 ;;esac
curl -sS -o /dev/null -w 'load_balancer %{http_code}\n' \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/config/namespaces/xXCSH_NAMESPACEx/http_loadbalancers/xXCSH_LB_NAMEx"curl -sS -o /dev/null -w 'origin_pool %{http_code}\n' \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/config/namespaces/xXCSH_NAMESPACEx/origin_pools/xXCSH_ORIGIN_POOLx"if [ -n "${XCSH_HC_NAME:-}" ]; then curl -sS -o /dev/null -w 'healthcheck %{http_code}\n' \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/config/namespaces/xXCSH_NAMESPACEx/healthchecks/xXCSH_HC_NAMEx"ficurl -sS -o /dev/null -w 'protected_domain %{http_code}\n' \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/shape/csd/namespaces/xXCSH_NAMESPACEx/protected_domains/xXCSH_LB_NAMEx"
# Discovery only: list summaries identify candidate names; detail GETs classify them.MITIGATED_LIST=$(curl -sS -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/shape/csd/namespaces/xXCSH_NAMESPACEx/mitigated_domains")printf '%s' "$MITIGATED_LIST" | jq '[.items[]? | {name, get_spec}]'printf '%s' "$MITIGATED_LIST" | jq -er '.items[]?.name' | while IFS= read -r name; do curl -sS -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/shape/csd/namespaces/xXCSH_NAMESPACEx/mitigated_domains/$name" \ | jq '{name: .metadata.name, mitigated_domain: .spec.mitigated_domain}'doneAny exact detail GET returning 200 for an object not recorded by the current run’s ledger is pre-existing and blocks creation. Any 403, unexpected response, duplicate candidate name, missing required detail, or ambiguous ownership is unknown and blocks mutation.
A 404 detail response is absent. A list entry is never enough to classify ownership: follow its items[].name with the exact detail GET. Never auto-teardown these findings.
Readiness Verification Matrix
Section titled “Readiness Verification Matrix”The pre-flight check above verifies the environment is clean. The readiness matrix below verifies the environment is capable — that all prerequisites, quotas, connectivity, and platform services are in place for a successful demo. Run this matrix before every meeting as part of the Prepare stage.
Each check has a test ID, a tier (T0–T5), a PASS/FAIL/WARN criteria, and a remediation path. Tiers are sequential — a FAIL in an earlier tier blocks later tiers from running.
Tier Summary
Section titled “Tier Summary”| Tier | Category | Blocks Demo? | Purpose |
|---|---|---|---|
| T0 | Connectivity & Auth | Yes | Can we reach the platform and authenticate? |
| T1 | Quotas & Capacity | Yes (if at limit) | Is there room to create demo objects? |
| T2 | Platform Prerequisites | Yes | Are tenant-level services configured? |
| T3 | Origin Health | Warn | Is the backend application responding? |
| T4 | Ownership & Existing State | Yes | Is lifecycle ownership known and consistent? |
| T5 | Certificate Readiness | Yes | Is the required HTTPS automatic certificate valid? |
T0: Connectivity & Auth
Section titled “T0: Connectivity & Auth”These checks confirm the execution host can reach the F5 XC API and the credentials are valid.
PF-T0-1: API Connectivity
Section titled “PF-T0-1: API Connectivity”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' --connect-timeout 10 --max-time 15 \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_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: Namespace Access
Section titled “PF-T0-2: Namespace Access”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/config/namespaces/xXCSH_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 — follow the API Build namespace step" else "Unexpected HTTP \(.http_code)" end )}'PF-T0-3: CSD API Access
Section titled “PF-T0-3: CSD API Access”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/shape/csd/namespaces/xXCSH_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 — verify CSD access after the API Build namespace step" else "Unexpected HTTP \(.http_code)" end )}'PF-T0-4: Read-Only Access Matrix
Section titled “PF-T0-4: Read-Only Access Matrix”Establish the deployment owner before any mutation. XCSH_CSD_DEPLOYMENT_MODE must be exactly api or terraform. In API mode, use GET requests on the namespace and required list/detail endpoints to establish access and classify each target as absent, pre-existing, or unknown. In Terraform mode, stop this API workflow and use only the configured Terraform backend and state.
Do not infer write permission by deleting a nonexistent object or calling namespace cascade deletion. Those requests are mutations and are not valid readiness probes. A successful read proves visibility only; mutation authorization is established by the approved Phase 1 operation and its response.
case "${XCSH_CSD_DEPLOYMENT_MODE:-}" in api|terraform) ;; *) echo "FAIL: XCSH_CSD_DEPLOYMENT_MODE must be api or terraform"; exit 1 ;;esac
if [ "$XCSH_CSD_DEPLOYMENT_MODE" = terraform ]; then echo "INFO: Terraform owns lifecycle; stop the API workflow and use its configured state." exit 0fi
for endpoint in \ "api/web/namespaces/$XCSH_NAMESPACE" \ "api/config/namespaces/$XCSH_NAMESPACE/origin_pools/$XCSH_ORIGIN_POOL" \ "api/config/namespaces/$XCSH_NAMESPACE/http_loadbalancers/$XCSH_LB_NAME" \ "api/shape/csd/namespaces/$XCSH_NAMESPACE/status" \ "api/shape/csd/namespaces/$XCSH_NAMESPACE/protected_domains/$XCSH_LB_NAME" \ "api/shape/csd/namespaces/$XCSH_NAMESPACE/mitigated_domains"do curl -sS -o /dev/null -w "$endpoint %{http_code}\n" --max-time 10 \ -H "Authorization: APIToken $XCSH_API_TOKEN" \ "$XCSH_API_URL/$endpoint"done
if [ -n "${XCSH_HC_NAME:-}" ]; then endpoint="api/config/namespaces/$XCSH_NAMESPACE/healthchecks/$XCSH_HC_NAME" curl -sS -o /dev/null -w "$endpoint %{http_code}\n" --max-time 10 \ -H "Authorization: APIToken $XCSH_API_TOKEN" \ "$XCSH_API_URL/$endpoint"else echo "healthcheck SKIP (optional and omitted)"fiTreat 403 as FAIL for required resources, 404 as absence only for an exact detail endpoint, and unexpected responses as UNKNOWN. For the mitigated-domain list, use only items[].name and documented get_spec summary data; follow every relevant name with its exact detail GET before evaluating spec or ownership. Do not mutate to refine an UNKNOWN result.
T1: Quotas & Capacity
Section titled “T1: Quotas & Capacity”These checks query the tenant’s Quota Usage API to determine limits, current usage, and remaining capacity for each object kind the demo needs. This is a read-only capacity check; there is no probe-and-delete fallback.
PF-T1-0: Quota Usage Gate
Section titled “PF-T1-0: Quota Usage Gate”Query the tenant-wide quota usage endpoint and compute a deterministic PASS/WARN/FAIL status for every object kind the demo needs. This endpoint requires the system namespace. A single API call checks all platform-level quotas at once.
The gate defines a demo_needs array that specifies how many of each object kind the demo will consume, whether the kind is required, and the minimum needed for the demo to proceed. The jq filter compares remaining against needed and computes the status field deterministically — no operator interpretation required.
# Step 1: Fetch quota datacurl -s \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_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: 1, 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.jsonGate output — the gate field is the single deterministic verdict:
PASS— all object kinds haveremaining >= needed. Demo can proceed.WARN— an optional object such as the healthcheck has no remaining capacity. The demo can proceed without that optional object.FAIL— at least one required kind hasremaining < min_proceed. Demo cannot proceed until quota is freed.
Example output (WARN — endpoint at capacity, healthcheck nearly full):
{ "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": 1, "status": "PASS" } ], "gate": "FAIL"}gate value | Action |
|---|---|
| PASS | Proceed to PF-T1-4 (protected domain check), then T2 |
| WARN | Note limitations in the readiness report, proceed with reduced capability |
| FAIL | Stop — report which kinds are exhausted and remediation steps below |
Remediation by kind:
| Kind | Remediation |
|---|---|
healthcheck | Delete unused healthchecks to free capacity. Demo proceeds without healthcheck (CSD does not require one). |
origin_pool | Delete unused origin pools or contact your administrator to increase the tenant limit. |
endpoint | Delete unused origin pools in other namespaces to free endpoint capacity (endpoints are sub-objects of origin pools), or contact your administrator. |
http_loadbalancer | Free one load balancer slot or contact your administrator. The single HTTPS load balancer is required. |
PF-T1-4: Protected Domain Capacity
Section titled “PF-T1-4: Protected Domain Capacity”CSD protected-domain capacity is not exposed by the platform Quota Usage API. Perform only read-only configuration checks: list current protected domains, confirm the target domain is not already registered under an unknown owner, and record capacity as UNKNOWN unless an administrator supplies the applicable limit and usage.
A capacity value of UNKNOWN is not evidence that quota is available. Do not create, delete, or interpret a 409 from a fixed-name probe as a readiness result.
No Mutation-Based Quota Fallback
Section titled “No Mutation-Based Quota Fallback”If PF-T1-0 returns 403, 404, or an unexpected format, stop the quota gate with UNKNOWN capacity and request a read-only administrator quota/configuration check. Readiness never creates temporary healthchecks, origin pools, load balancers, protected domains, or namespaces.
Mutation probes are outside readiness and are not a supported fallback. If capacity or access remains unknown, stop and obtain read-only administrator evidence; do not create or delete temporary resources.
T2: Platform Prerequisites
Section titled “T2: Platform Prerequisites”These checks verify tenant-level services that the demo depends on.
PF-T2-1: CSD Tenant Status
Section titled “PF-T2-1: CSD Tenant Status”curl -s \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/shape/csd/namespaces/xXCSH_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 Zone Exists
Section titled “PF-T2-2: DNS Zone Exists”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/config/dns/namespaces/system/dns_zones/xXCSH_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 Managed Records Configuration
Section titled “PF-T2-3: DNS Managed Records Configuration”Only run if PF-T2-2 returned 200 (F5 XC DNS zone exists). Read and report the current setting; readiness must not update the shared DNS zone.
curl -sS \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/config/dns/namespaces/system/dns_zones/xXCSH_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 disabled; choose the documented external/manual DNS path or obtain separately approved DNS-owner change control" end) }'Do not automatically PUT the zone. A shared DNS change requires separate approval from its established owner, a reviewed complete spec containing only permitted metadata and spec fields, and post-change verification.
| Result | DNS Authority | Status | Remediation |
|---|---|---|---|
true | Any | PASS | LB-managed DNS records are enabled |
false/null | F5 XC | WARN | Use external/manual DNS, or obtain separate DNS-owner approval for a reviewed change |
false/null | External | INFO | Use DNS recovery guidance |
PF-T2-4: DNS Nameserver Authority
Section titled “PF-T2-4: DNS Nameserver Authority”NS_RECORDS=$(dig +short NS xXCSH_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 — use the DNS recovery guidance in the API Build phase" end )}'T3: Origin Health
Section titled “T3: Origin Health”These checks verify the backend application is reachable.
Origin contract guard: validate the owner, representation, exclusivity, port, application marker, and placeholders before connectivity checks.
case "${XCSH_CSD_DEPLOYMENT_MODE:-}" in api|terraform) ;; *) echo "FAIL: invalid deployment mode"; exit 1;; esacAPPLICATION_MARKER=${XCSH_APPLICATION_MARKER:-}[ -n "$APPLICATION_MARKER" ] || { echo "FAIL: application marker is required"; exit 1; }case "$APPLICATION_MARKER" in replace-with-application-marker|replace-with-azure-application-marker) echo "FAIL: replace the application marker placeholder"; exit 1;; esaccase "${XCSH_ORIGIN_KIND:-}" in public_name) ORIGIN_VALUE=${XCSH_ORIGIN_HOSTNAME:-} [ -z "${XCSH_ORIGIN_IP:-}" ] || { echo "FAIL: IP must be empty"; exit 1; } ;; public_ip) ORIGIN_VALUE=${XCSH_ORIGIN_IP:-} [ -z "${XCSH_ORIGIN_HOSTNAME:-}" ] || { echo "FAIL: hostname must be empty"; exit 1; } ;; *) echo "FAIL: invalid origin kind"; exit 1 ;;esac[ "$XCSH_CSD_DEPLOYMENT_MODE" != api ] || [ -n "$ORIGIN_VALUE" ] || { echo "FAIL: missing API origin"; exit 1; }case "$ORIGIN_VALUE" in origin.example.com|192.0.2.*|198.51.100.*|203.0.113.*) echo "FAIL: example origin"; exit 1;; esaccase "${XCSH_ORIGIN_PORT:-}" in ''|*[!0-9]*) echo "FAIL: origin port must be an integer"; exit 1;; esac[ "$XCSH_ORIGIN_PORT" -ge 1 ] && [ "$XCSH_ORIGIN_PORT" -le 65535 ] || { echo "FAIL: origin port out of range"; exit 1; }PF-T3-1: Origin Server Connectivity
Section titled “PF-T3-1: Origin Server Connectivity”HTTP_CODE=$(curl -s -o /dev/null -w '%\{http_code\}' --connect-timeout 10 --max-time 15 \ "http://$ORIGIN_VALUE:$XCSH_ORIGIN_PORT/")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: Origin Serves HTML Content
Section titled “PF-T3-2: Origin Serves HTML Content”Only run if PF-T3-1 returned a valid HTTP status:
curl -s --max-time 10 "http://$ORIGIN_VALUE:$XCSH_ORIGIN_PORT/" \ | grep -qi '</html>' && echo "PASS: HTML content" || echo "WARN: No HTML detected"| Result | Status | Remediation |
|---|---|---|
PASS: HTML content | PASS | Origin serves HTML pages (required for CSD JS injection) |
WARN: No HTML detected | WARN | Origin may be an API-only service or returning non-HTML — CSD JS injection requires HTML page responses |
T4: Ownership and Existing State
Section titled “T4: Ownership and Existing State”Establish ownership before any mutation. API mode expects the named resources to be absent, or to appear in the current run’s atomic ownership ledger. Terraform mode must use the configured backend and state and must not read or mutate those resources through the API workflow.
The API ledger must be executable data and updated atomically after every GET and POST result. Each entry contains an allowed resource kind, exact name, namespace, and one status: created, pre-existing, or unknown. There must be exactly one entry per kind/name/namespace. A 409 is always pre-existing, never created. Phase 3 also records every mitigated domain it creates.
Phase 4 derives deletion targets only from ledger entries marked created, fails if the ledger environment does not exactly match the active API URL and namespace, and requires explicit approval. Namespace cascade deletion requires a second, separate approval and is permitted only for a namespace recorded as created by this run.
Do not auto-teardown unknown, mixed-owner, pre-existing, fixed-name probe, or Terraform-owned resources. Readiness performs GET-only inspection and reports unresolved ownership as FAIL.
T5: Certificate Readiness
Section titled “T5: Certificate Readiness”These checks determine whether the required HTTPS endpoint and automatic certificate are ready. A certificate failure blocks this one-load-balancer architecture; there is no HTTP-only fallback.
PF-T5-1: Recent Certificate Issuance History
Section titled “PF-T5-1: Recent Certificate Issuance History”Check whether a Let’s Encrypt certificate was recently issued for the demo domain. Frequent create/destroy cycles can exhaust certificate issuance limits.
PF-T5-2: Existing Load Balancer Certificate State
Section titled “PF-T5-2: Existing Load Balancer Certificate State”Run only if the load balancer exists from a prior API-owned deployment:
CERT_BODY=$(curl -s -w '\n%\{http_code\}' \ -H "Authorization: APIToken xXCSH_API_TOKENx" \ "xXCSH_API_URLx/api/config/namespaces/xXCSH_NAMESPACEx/http_loadbalancers/xXCSH_LB_NAMEx")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 load balancer — assess certificate state after Phase 1"}'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 "Certificate issuance rate limited — deployment not ready" elif (.spec.cert_state | test("Pending|Started")) then "Certificate provisioning in progress" else "Certificate state: \(.spec.cert_state // "unknown")" end ) }'fiReadiness Report Format
Section titled “Readiness Report Format”After running all tiers, present a consolidated readiness report:
## Demo Readiness: READY / NOT READY / READY WITH WARNINGS
### T0: Connectivity & Auth| Check | Result | Status ||---|---|---|| PF-T0-1: API Connectivity | 200 | PASS || PF-T0-2: Namespace Access | 200 | PASS || PF-T0-3: CSD API Access | 200 | PASS |
### T1: Quotas & Capacity| Check | Kind | Limit | Usage | Remaining | Needed | Status ||---|---|---|---|---|---|---|| PF-T1-0: Quota Usage Gate | `healthcheck` | 150 | 148 | 2 | 1 | PASS || PF-T1-0: Quota Usage Gate | `origin_pool` | unlimited | 420 | unlimited | 1 | PASS || PF-T1-0: Quota Usage Gate | `endpoint` | 500 | 498 | 2 | 1 | PASS || PF-T1-0: Quota Usage Gate | `http_loadbalancer` | unlimited | 116 | unlimited | 1 | PASS || PF-T1-0: Quota Usage Gate | **gate** | — | — | — | — | **PASS** || PF-T1-4: Protected Domain | — | — | — | — | — | UNKNOWN (administrator confirmation required) |
### T2: Platform Prerequisites| Check | Result | Status ||---|---|---|| PF-T2-1: CSD Tenant Status | configured + enabled | PASS || PF-T2-2: DNS Zone Exists | 200 | PASS || PF-T2-3: DNS Managed Records | true | PASS || PF-T2-4: DNS Nameserver Authority | f5clouddns.com | PASS |
### T3: Origin Health| Check | Result | Status ||---|---|---|| PF-T3-1: Origin Connectivity | independently supplied origin | PASS/WARN || PF-T3-2: HTML Content | observed response | PASS/WARN |
### T4: Ownership and Existing State| Check | Result | Status ||---|---|---|| HTTP Load Balancer | 404 | PASS || Origin Pool | 404 | PASS || Healthcheck | 404 | PASS || Protected Domains | 0 | PASS || Mitigated Domains | 0 | PASS |
### T5: Certificate Readiness| Check | Result | Status ||---|---|---|| PF-T5-2: Cert State | SKIP (no load balancer) | INFO |
### Warnings- (list any WARN or INFO items with context)Overall status rules:
| Condition | Status |
|---|---|
| All T0–T4 checks PASS | READY |
| All T0–T4 checks PASS but T3 or T5 have WARN/INFO | READY WITH WARNINGS |
| Any T0, T1, or T2 check is FAIL | NOT READY — resolve before proceeding |
| T4 has unknown, mixed, or conflicting ownership | NOT READY — resolve ownership without mutation |
AI Assistant Execution Protocol
Section titled “AI Assistant Execution Protocol”This section defines a deterministic workflow for AI assistants (Claude Code, Copilot, etc.) executing the API automation steps. Following this protocol eliminates guesswork — every decision point has a defined resolution path.
Variable Resolution Protocol
Section titled “Variable Resolution Protocol”Resolve and validate the environment before any API request:
- Read
.env, then the shell environment. - Require
XCSH_CSD_DEPLOYMENT_MODEto equalapiorterraform; this establishes the lifecycle owner. - In API mode, require independently supplied origin values. Do not read Terraform or AWS state.
- Require
XCSH_ORIGIN_KINDto equalpublic_nameorpublic_ip. - For
public_name, require a non-placeholderXCSH_ORIGIN_HOSTNAMEand requireXCSH_ORIGIN_IPempty. - For
public_ip, require a non-placeholderXCSH_ORIGIN_IPand requireXCSH_ORIGIN_HOSTNAMEempty. - Always require a non-empty, non-placeholder
XCSH_APPLICATION_MARKER, independent of origin representation. The repository reference defaults toOWASP Juice Shop; non-reference API and Azure scenarios must set a stable, deployment-specific marker. - Require
XCSH_ORIGIN_PORTto be an integer from 1 through 65535. Reject example credentials,origin.example.com, and RFC 5737 TEST-NET addresses. - Preserve an empty
XCSH_HC_NAME; empty means omit the optional healthcheck. - Display the resolved contract and obtain approval before mutation. Prepare may continue with GET-only checks without mutation approval.
Prepare stage override: During Stage 1 Prepare, skip the wait in step 6. Display the resolved variable table for the record, then proceed immediately. Steps 1–5 still apply — if any required variable is missing after checking
.envand shell, stop and report the missing variables.
Required vs Optional Variables
Section titled “Required vs Optional Variables”| Variable | Required | Default | Placeholder (reject) |
|---|---|---|---|
XCSH_CSD_DEPLOYMENT_MODE | Yes | api | anything except api or terraform |
XCSH_API_TOKEN | API mode | — | example-api-token |
XCSH_API_URL | API mode | — | https://example-tenant.console.ves.volterra.io |
XCSH_NAMESPACE | Yes | client-side-defense | — |
XCSH_DOMAINNAME | Yes | client-side-defense.f5-sales-demo.com | — |
XCSH_ROOT_DOMAIN | Yes | f5-sales-demo.com | — |
XCSH_LB_NAME | Yes | client-side-defense | — |
XCSH_EMAIL | Yes | — | user@example.com |
XCSH_ORIGIN_KIND | API mode | public_name | anything except public_name or public_ip |
XCSH_ORIGIN_HOSTNAME | Conditional | — | origin.example.com |
XCSH_ORIGIN_IP | Conditional | — | RFC 5737 TEST-NET addresses |
XCSH_APPLICATION_MARKER | Yes | OWASP Juice Shop | empty, replace-with-application-marker, or replace-with-azure-application-marker |
XCSH_ORIGIN_POOL | Yes | csd-juice-shop | — |
XCSH_ORIGIN_PORT | API mode | 80 | non-integer or outside 1–65535 |
XCSH_HC_NAME | Optional | empty (omit) | — |
Execution Modes
Section titled “Execution Modes”The AI assistant operates in one of three modes during the demo:
| Mode | When active | Behavior |
|---|---|---|
| Normal | Default during Prepare, Execute, Teardown | Verbatim documented commands only |
| Debug | Auto-activates on failure | Creative troubleshooting, update docs |
| Q&A | During Q&A stage | Improvisational — ad-hoc commands allowed to answer audience questions |
Normal mode (default):
- Every API call, verification query, and shell command must come verbatim from the phase files (Phase 1–4) or from the Pre-flight Check section above
- Substitute only
xTOKENxplaceholders with resolved variable values - Do not construct API endpoints, jq filters, or cURL commands from general knowledge or inference
- If a needed command is not documented, stop and report to the operator: “This verification step is not covered by the phase documentation”
Debug mode (auto-activates on failure):
- Activates automatically when a documented command produces an unexpected result: non-2xx HTTP response, jq parse error, command timeout, or response body that contradicts the evidence table
- In debug mode, the AI assistant may construct diagnostic commands, inspect raw API responses, test endpoint variations, and use creative troubleshooting to find the root cause
- Prefix all debug output with
[DEBUG]so the operator can distinguish diagnostic activity from normal execution - Document what you learn: after resolving an issue, update the
relevant phase file or troubleshooting section with:
- The failure scenario (what went wrong)
- What was tried and did NOT work (so future runs don’t repeat it)
- The working resolution (the command or fix that solved it)
- The goal of debug mode is to eliminate itself — every debug session should produce a documentation update that makes the next execution fully deterministic
- Once the documentation is updated and the issue is resolved, return to normal mode and resume from the last successful documented step
- If debug mode cannot resolve the issue, report findings to the operator and stop — do not continue to the next phase
Q&A mode (during Q&A stage):
- Active only during the Q&A meeting stage, after the demo conclusion
- The AI assistant may construct ad-hoc API calls, run diagnostic commands, navigate to unscripted pages, and modify the live demo environment to illustrate answers to audience questions
- No
[DEBUG]prefix — this is intentional improvisational behavior, not error recovery - Uses the CSD Product Expertise section in
DEMO_EXECUTOR.mdas the knowledge base for product questions
Browser Context Management
Section titled “Browser Context Management”initScript accumulation is a common source of demo failures. Each
navigate_page call with an initScript parameter adds the script to
a persistent list that runs on every subsequent document load. Follow
these rules:
- Always navigate to
about:blankbefore any navigation withinitScriptto clear accumulated scripts from prior runs - Use
new_pagewithisolatedContextwhen switching between demo phases (e.g., Phase 2 → Phase 3) to ensure a completely clean browser state - Recovery from unresponsive pages — if
take_screenshotortake_snapshottimeouts occur, the browser context is resource-exhausted. Usenew_pagewithisolatedContextto create a fresh context, then retry from theabout:blanknavigation step - See the Phase 2 attack simulation asides for detailed guidance on transient origin failures and resource exhaustion recovery
Evidence Display Protocol
Section titled “Evidence Display Protocol”After every API call, the AI assistant must present structured evidence to the human operator using this format:
Creation steps (POST):
| Field | Value | Status |
|---|---|---|
| HTTP Status | 200 | PASS |
| Object Name | csd-juice-shop | — |
| Key Property | (extracted via jq) | — |
After each creation step, run a GET to confirm the object exists and display its key properties. If the GET returns 404, report FAIL and stop.
Verification steps (GET/dig):
| Test | Result | Status |
|---|---|---|
| DNS-1: A Record | (observed deployed address) | PASS |
| LB-1: HTTP LB State | VIRTUAL_HOST_READY | PASS |
| LB-2: HTTPS LB State | VIRTUAL_HOST_READY | INFO (optional) |
| TLS-1: Cert State | CertificateValid | INFO (optional) |
| CSD-1: JS Tag | scriptTag present | PASS |
Reference the Diagnostics & Verification test case IDs (DNS-1, TLS-1, LB-1, CSD-1, etc.) as the verification standard for each layer.
Execution Flow Summary
Section titled “Execution Flow Summary”Phase execution is sequential and gated: each phase must reach PASS on all required checks before the next phase begins. The demo follows a four-stage meeting lifecycle — see the Meeting Stages section in DEMO_EXECUTOR.md for trigger phrases and behavioral rules.
The AI assistant follows this sequence:
- Prepare — resolve variables, run pre-flight checks, confirm clean environment (can be run separately before the meeting)
- Introduction — SE introduces themselves and states outcome goals (visibility into client-side threats, PCI compliance, real-time detection)
- Execute Phase 1 (Steps 1–7) — infrastructure creation and verification; all Phase 1 checks must PASS before proceeding
- Execute Phase 2 (Steps 8–9) — attack simulation and detection verification via API; AI assistants with browser automation execute the browser steps directly, operators without browser tools perform them manually
- Execute Phase 3 — apply mitigation for all detected domains, re-run attack, verify blocking is effective
- Conclusion — restate outcome goals, summarize evidence from each phase, highlight key detections and mitigations
- Q&A — improvisational stage, demo stays live, SE answers audience questions and asks return questions
- Teardown (post-meeting) — Phase 4, explicit operator confirmation required, delete all objects in reverse dependency order, confirm clean environment
If any step returns FAIL, stop and report the failure with the relevant troubleshooting section link before continuing.
Prerequisites
Section titled “Prerequisites”- An F5 XC API token — generate one under Administration → Credentials → API Credentials
curlandjqinstalled locally- Access to namespace
client-side-defensewith permissions for one origin pool, one HTTP load balancer, and the protected domain; healthcheck permission is optional
Environment Setup
Section titled “Environment Setup”Create a .env file with your environment values. A template is provided in the repository:
cp .env.example .envEdit .env with your actual values:
# Required — environment and ownerXCSH_API_TOKEN=example-api-tokenXCSH_API_URL=https://example-tenant.console.ves.volterra.ioXCSH_EMAIL=user@example.comXCSH_CSD_DEPLOYMENT_MODE=apiXCSH_NAMESPACE=client-side-defenseXCSH_LB_NAME=client-side-defenseXCSH_DOMAINNAME=client-side-defense.f5-sales-demo.comXCSH_ROOT_DOMAIN=f5-sales-demo.com
# API-owned origin: independent input, never Terraform/AWS stateXCSH_ORIGIN_KIND=public_nameXCSH_ORIGIN_HOSTNAME=origin.example.comXCSH_ORIGIN_IP=# Set a stable scenario-specific marker for non-reference API or Azure deployments.XCSH_APPLICATION_MARKER=OWASP Juice ShopXCSH_ORIGIN_POOL=csd-juice-shopXCSH_ORIGIN_PORT=80
# Empty means omit the optional healthcheckXCSH_HC_NAME=Source the file to load variables into your shell session:
set -a && source .env && set +aEach xTOKENx placeholder in the cURL commands maps directly to an environment variable — for example, xXCSH_API_TOKENx corresponds to $XCSH_API_TOKEN. You can substitute these values using the interactive form at the top of the page, or let an AI assistant like Claude Code read your .env and build the commands for you.
Placeholder Tokens
Section titled “Placeholder Tokens”| Token | Default | Description |
|---|---|---|
xXCSH_CSD_DEPLOYMENT_MODEx | api | Exclusive lifecycle owner: api or terraform |
xXCSH_API_URLx | https://example-tenant.console.ves.volterra.io | XC Console API URL; example value is rejected |
xXCSH_API_TOKENx | example-api-token | API credential token; example value is rejected |
xXCSH_EMAILx | user@example.com | CSD notification email; example value is rejected |
xXCSH_NAMESPACEx | client-side-defense | Namespace |
xXCSH_LB_NAMEx | client-side-defense | Single HTTP Load Balancer name |
xXCSH_DOMAINNAMEx | client-side-defense.f5-sales-demo.com | FQDN to protect |
xXCSH_ROOT_DOMAINx | f5-sales-demo.com | Root domain (eTLD+1) |
xXCSH_ORIGIN_KINDx | public_name | Exactly public_name or public_ip |
xXCSH_ORIGIN_HOSTNAMEx | origin.example.com | Independent API origin hostname; example value is rejected |
xXCSH_ORIGIN_IPx | empty | Independent API origin IP; TEST-NET values are rejected |
xXCSH_APPLICATION_MARKERx | OWASP Juice Shop | Required application/scenario marker, independent of origin representation; non-reference API and Azure scenarios must set a stable value |
xXCSH_ORIGIN_POOLx | csd-juice-shop | Origin pool name |
xXCSH_ORIGIN_PORTx | 80 | Integer origin port |
xXCSH_HC_NAMEx | empty | Optional healthcheck; empty means omitted |
Automation Reference
Section titled “Automation Reference”This section summarizes the full exercise workflow for scripting or automation.
Quick Start
Section titled “Quick Start”- Clone the repository and copy the environment template:
cp .env.example .env - Edit
.envwith your tenant URL, API token, namespace, and domain values - Source the environment:
set -a && source .env && set +a - Execute each phase in order, verifying PASS at each Evidence block before proceeding to the next phase
Variable Resolution
Section titled “Variable Resolution”Values are resolved using the deterministic protocol defined in AI Assistant Execution Protocol:
.envfile — parseKEY=VALUEpairs from the repository root- Shell environment — check
env | grep XCSH_for exported values - Placeholder detection — flag unresolved credentials and provider-specific origin placeholders as missing
- Prompt operator — ask for each missing required variable
- Apply defaults — use built-in defaults for missing optional variables
- Confirm — display the resolved variable table and wait for operator approval
Execution Order
Section titled “Execution Order”- Phase 1 — API Build: Deploy the optional healthcheck, provider-specific origin pool, and single HTTPS auto-certificate HTTP load balancer; configure DNS and CSD; then prove HTTP redirect, HTTPS service, application rendering, script injection, and collection traffic.
- Phase 2 — Attack: Run the authorized browser simulation over
https://, then verify detections through the API. - Phase 3 — Mitigate: Establish the baseline, apply mitigations, repeat the HTTPS simulation, and compare the result.
- Phase 4 — Teardown (requires explicit human confirmation): Delete the API-owned load balancer, origin pool, optional healthcheck, and protected domain in dependency order. Do not delete the DNS zone.
Variables
Section titled “Variables”| Token | Description | Default |
|---|---|---|
xXCSH_CSD_DEPLOYMENT_MODEx | Exclusive lifecycle owner | api |
xXCSH_API_URLx | XC Console API URL | (user-provided) |
xXCSH_API_TOKENx | API credential token | (user-provided) |
xXCSH_EMAILx | CSD notification email | (user-provided) |
xXCSH_NAMESPACEx | Namespace | client-side-defense |
xXCSH_LB_NAMEx | Single HTTP Load Balancer name | client-side-defense |
xXCSH_DOMAINNAMEx | FQDN to protect | client-side-defense.f5-sales-demo.com |
xXCSH_ROOT_DOMAINx | Root domain (eTLD+1) | f5-sales-demo.com |
xXCSH_ORIGIN_KINDx | Origin representation | public_name |
xXCSH_ORIGIN_HOSTNAMEx | API-owned hostname origin | (required for public_name) |
xXCSH_ORIGIN_IPx | API-owned IP origin | (required for public_ip) |
xXCSH_APPLICATION_MARKERx | Application/scenario content marker, independent of origin representation | OWASP Juice Shop; set a stable value for non-reference API and Azure scenarios |
xXCSH_ORIGIN_POOLx | Origin pool name | csd-juice-shop |
xXCSH_ORIGIN_PORTx | Integer origin server port | 80 |
xXCSH_HC_NAMEx | Optional healthcheck name | empty (omit) |
oneOf Choice Groups
Section titled “oneOf Choice Groups”The HTTP Load Balancer spec uses oneOf choice groups where exactly one option must be set per group. Setting zero or more than one option in a group causes a 422 error.
Key CSD-related choices:
| Choice Group | Options | CSD Default |
|---|---|---|
client_side_defense_choice | client_side_defense, disable_client_side_defense | client_side_defense |
java_script_choice (nested in CSD) | disable_js_insert, js_insert_all_pages, js_insert_all_pages_except, js_insertion_rules | js_insert_all_pages |
Listener type choice:
The single ${XCSH_LB_NAME} resource uses https_auto_cert with http_redirect: true. It
advertises on the public default VIP; HTTP requests redirect to the same HTTPS virtual host.
Do not create a separate plaintext load balancer.
HTTPS auto-cert nested choices:
| Choice Group | Options | Default |
|---|---|---|
| port | port (number) | 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 nested choices (ML config):
The single_lb_app object has its own required oneOf groups. Setting single_lb_app: \{\} without these nested choices causes a 400 error.
| Choice Group | Options | Default |
|---|---|---|
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 |
Other LB-level choices (all set to disable/default):
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
Error Handling
Section titled “Error Handling”- 401 Unauthorized — API token is invalid or expired. Regenerate under Administration → Credentials.
- 403 Forbidden — token lacks permissions for the namespace. Check role bindings.
- 404 Not Found — namespace or object name is incorrect. List objects with
GET /api/config/namespaces/\{namespace\}/\{object_type\}. - 409 Conflict — object already exists. For protected domains, a
409with “domain already exists (in uriList)” means the root domain is already registered on the tenant — this is a success condition, not an error. For other objects, classify ownership with the exact detail GET; update only an API-owned object and never delete a pre-existing object as conflict recovery. - 422 Unprocessable Entity — JSON schema validation failed. Common causes: missing
oneOfchoice, multiple choices set in same group, wrong field type. Check the error message for the specific field. - Object limit exhausted (error code
8, message"Object kind {kind} has exhausted limits({N})") — tenant has hit an object quota limit. The API returns HTTP 200 with a JSON error body containing"code": 8, not HTTP 429. Check capacity first with PF-T1-0. Behavior depends on the object type:- Healthcheck (limit ~150): Non-blocking — omit the optional healthcheck and create the origin pool without a healthcheck reference. CSD does not depend on health monitoring.
- Endpoint (limit ~500): Blocking — endpoints are sub-objects created inside origin pools. If this limit is hit, origin pool creation fails. Delete unused origin pools (which frees their endpoint sub-objects) or contact your administrator to increase the tenant limit.
- Origin pool: Blocking — delete unused origin pools or contact your administrator.
- HTTP load balancer: Blocking — delete unused load balancers or contact your administrator.
- Protected domain: Blocking — delete unused protected domains or contact your administrator.
Troubleshooting
Section titled “Troubleshooting”Healthcheck Not Linked to Origin Pool
Section titled “Healthcheck Not Linked to Origin Pool”If the origin-pool detail GET omits the healthcheck reference and XCSH_HC_NAME is non-empty:
- Stop; do not delete or recreate anything during readiness.
- Verify the exact healthcheck with
GET /api/config/namespaces/{namespace}/healthchecks/{hc_name}. - During the approved API build, update only an API-owned origin pool using the documented payload and verify it with an exact detail GET. If
XCSH_HC_NAMEis empty, the omitted reference is valid and needs no remediation.
LB Stuck in VIRTUAL_HOST_PENDING_A_RECORD
Section titled “LB Stuck in VIRTUAL_HOST_PENDING_A_RECORD”If the load balancer state remains VIRTUAL_HOST_PENDING_A_RECORD after Phase 1 Step 5:
-
Verify DNS zone exists (F5 XC managed DNS only):
Terminal window curl -s \-H "Authorization: APIToken xXCSH_API_TOKENx" \"xXCSH_API_URLx/api/config/dns/namespaces/system/dns_zones/xXCSH_ROOT_DOMAINx" \| jq '.metadata.name'A
404means the DNS zone has not been created in F5 XC. -
Check
allow_http_lb_managed_records— if the zone exists but the LB is pending, managed records may be disabled:Terminal window curl -s \-H "Authorization: APIToken xXCSH_API_TOKENx" \"xXCSH_API_URLx/api/config/dns/namespaces/system/dns_zones/xXCSH_ROOT_DOMAINx" \| jq '.spec.primary.allow_http_lb_managed_records'If
falseornull, do not change it during readiness. Follow the DNS recovery guidance, and obtain separate approval from the established DNS-zone owner before any reviewed update. -
External DNS — if F5 XC is not authoritative, create the A and ACME CNAME records manually at your DNS provider (see the DNS recovery guidance).
-
Verify resolution — after fixing DNS, confirm:
Terminal window dig +short xXCSH_DOMAINNAMEx AIf the A record resolves, the LB transitions to
VIRTUAL_HOST_READY. Poll every 30 seconds, up to 4 iterations (2 minutes total). If stillVIRTUAL_HOST_PENDING_A_RECORDafter 2 minutes, re-check DNS propagation and report to operator.
CSD Status Shows isConfigured: false
Section titled “CSD Status Shows isConfigured: false”CSD must be enabled at the tenant level. Contact your F5 XC administrator to enable Client-Side Defense for your tenant. This is a tenant-wide setting that cannot be configured via API.
JS Injection Not Working
Section titled “JS Injection Not Working”- Verify CSD is enabled at tenant level before the API build
- Confirm the load balancer has
client_side_defenseset in the spec - Check the JS configuration endpoint returns a
scriptTag - Visit the protected domain in a browser and view page source to confirm the script is injected
Protected Domain Registration Returns 409
Section titled “Protected Domain Registration Returns 409”A 409 with “domain already exists (in uriList)” means the root domain is already registered on the tenant. Protected domains are tenant-scoped — they do not belong to any single namespace. This is a success condition: the domain is already protected and no further action is needed. Continue to complete-path verification.
Automatic Certificate Not Ready
Section titled “Automatic Certificate Not Ready”If cert_state reports AutoCertDomainRateLimited, DomainChallengePending, or
PreDomainChallengePending, the required HTTPS endpoint is not ready. Stop the demo and resolve
certificate issuance or DNS propagation before continuing; do not fall back to a separate HTTP
load balancer.
For API-owned resources, retain the same namespace, domain, and object names. Correct DNS or wait for propagation, then retry the documented API update and verification steps. For Terraform-owned resources, retain the same backend and state, correct the input or external condition, and create, review, and apply a fresh saved plan. Do not edit or delete the resource in the console.
API Reference
Section titled “API Reference”Validate Client-Side Defense payloads against the enriched Shape API reference and HTTP load balancer, origin pool, and healthcheck payloads against the enriched Virtual API reference.