- Home
- Client-Side Defense
- demo
- Phase 1 — Build
Phase 1 — Build
Phase 1 deploys and validates the full CSD infrastructure. Complete all steps in order — each step must PASS before proceeding. Return to the index to complete Environment Setup and variable resolution before running these commands.
Step 0: Check & Create Namespace (Conditional)
Section titled “Step 0: Check & Create Namespace (Conditional)”Check if the target namespace already exists on the tenant. If it does not exist, create it. Track the result in the NAMESPACE_CREATED shell variable — Phase 4 teardown uses this to decide whether to delete the namespace.
NS_CHECK=$(curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/web/namespaces/xF5XC_NAMESPACEx")
NAMESPACE_CREATED="false"if [ "$NS_CHECK" = "404" ]; then curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{"metadata": {"name": "xF5XC_NAMESPACEx"}, "spec": {}}' \ "xF5XC_API_URLx/api/web/namespaces" | jq . NAMESPACE_CREATED="true"fiEvidence
Section titled “Evidence”Confirm the namespace exists and record whether it was created:
curl -s -o /dev/null -w '%\{http_code\}' \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/web/namespaces/xF5XC_NAMESPACEx"echo "NAMESPACE_CREATED=$NAMESPACE_CREATED"| Field | Expected | Status |
|---|---|---|
| HTTP Status | 200 | PASS if returned, FAIL if 404 or other |
NAMESPACE_CREATED | true (created) or false (pre-existing) | Informational — used by Phase 4 teardown |
Step 1: Create Healthcheck (Optional)
Section titled “Step 1: Create Healthcheck (Optional)”Create an HTTP healthcheck that the origin pool can use to monitor backend health.
curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "xF5XC_HC_NAMEx", "namespace": "xF5XC_NAMESPACEx", "labels": {}, "annotations": {}, "disable": false }, "spec": { "http_health_check": { "use_origin_server_name": {}, "path": "/", "use_http2": false, "headers": {}, "request_headers_to_remove": [], "expected_status_codes": ["200"] }, "timeout": 3, "interval": 15, "jitter": 0, "unhealthy_threshold": 1, "healthy_threshold": 3, "jitter_percent": 30 } }' \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks" \ | jq .A 200 response with the created object confirms the healthcheck was created. If the response contains "code": 8 with a message like "Object kind healthcheck has exhausted limits(150)", the tenant has hit its healthcheck limit — skip to Step 2 and omit the healthcheck reference. CSD does not depend on health monitoring.
Evidence
Section titled “Evidence”Confirm the healthcheck exists:
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/healthchecks/xF5XC_HC_NAMEx" \ | jq '{name: .metadata.name, namespace: .metadata.namespace, path: .spec.http_health_check.path, interval: .spec.interval}'| Field | Expected | Status |
|---|---|---|
| HTTP Status | 200 with object | PASS if returned, FAIL if 404 |
name | Matches F5XC_HC_NAME | PASS |
path | / | PASS |
Step 1 skipped (error code 8, limit exhausted) | — | PASS (healthcheck is optional for CSD) |
Step 2: Create Origin Pool
Section titled “Step 2: Create Origin Pool”Create an origin pool pointing to your backend server. If you created a healthcheck in Step 1, include the healthcheck reference. If Step 1 was skipped, use an empty array.
With healthcheck (Step 1 succeeded):
curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "xF5XC_ORIGIN_POOLx", "namespace": "xF5XC_NAMESPACEx", "labels": {}, "annotations": {}, "description": "Origin pool for CSD demo", "disable": false }, "spec": { "origin_servers": [{ "public_ip": { "ip": "xF5XC_ORIGIN_IPx" }, "labels": {} }], "no_tls": {}, "port": xF5XC_ORIGIN_PORTx, "same_as_endpoint_port": {}, "healthcheck": [{ "namespace": "xF5XC_NAMESPACEx", "name": "xF5XC_HC_NAMEx", "kind": "healthcheck" }], "loadbalancer_algorithm": "LB_OVERRIDE", "endpoint_selection": "LOCAL_PREFERRED" } }' \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools" \ | jq .Without healthcheck (Step 1 skipped):
curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "xF5XC_ORIGIN_POOLx", "namespace": "xF5XC_NAMESPACEx", "labels": {}, "annotations": {}, "description": "Origin pool for CSD demo", "disable": false }, "spec": { "origin_servers": [{ "public_ip": { "ip": "xF5XC_ORIGIN_IPx" }, "labels": {} }], "no_tls": {}, "port": xF5XC_ORIGIN_PORTx, "same_as_endpoint_port": {}, "healthcheck": [], "loadbalancer_algorithm": "LB_OVERRIDE", "endpoint_selection": "LOCAL_PREFERRED" } }' \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools" \ | jq .A 200 response confirms the origin pool was created.
Verify Healthcheck is Linked
Section titled “Verify Healthcheck is Linked”If you included a healthcheck reference, confirm it is properly linked:
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools/xF5XC_ORIGIN_POOLx" \ | jq '.spec.healthcheck'A populated array confirms the link. An empty array [] is expected if Step 1 was skipped, or means the healthcheck was not found if you intended to link one.
Evidence
Section titled “Evidence”Confirm the origin pool exists and has the correct configuration:
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/origin_pools/xF5XC_ORIGIN_POOLx" \ | jq '{name: .metadata.name, origin_ip: .spec.origin_servers[0].public_ip.ip, port: .spec.port, healthcheck_count: (.spec.healthcheck | length)}'| Field | Expected | Status |
|---|---|---|
| HTTP Status | 200 | PASS if returned, FAIL if 404 |
name | Matches F5XC_ORIGIN_POOL | PASS |
origin_ip | Matches F5XC_ORIGIN_IP | PASS |
port | Matches F5XC_ORIGIN_PORT | PASS |
healthcheck_count | 1 (with HC) or 0 (without) | PASS either way |
Step 3: Create HTTP Load Balancers with CSD
Section titled “Step 3: Create HTTP Load Balancers with CSD”Create two load balancers with Client-Side Defense enabled — an HTTP LB (primary, port 80) and an HTTPS LB (secondary, port 443 with automatic certificate management). The HTTP LB is the default for all demo traffic. The HTTPS LB is a nice to have that depends on Let’s Encrypt certificate provisioning, which can hit rate limits in demo environments.
HTTP Load Balancer (Primary)
Section titled “HTTP Load Balancer (Primary)”This is the primary load balancer for demos. It uses an http listener on port 80 with F5 XC managed DNS. It does not depend on TLS certificate provisioning, so it becomes ready immediately after DNS resolves.
curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "xF5XC_LB_NAMEx-http", "namespace": "xF5XC_NAMESPACEx", "labels": {}, "annotations": {}, "description": "HTTP LB with Client-Side Defense enabled (primary demo LB)", "disable": false }, "spec": { "domains": ["xF5XC_DOMAINNAMEx"], "http": { "dns_volterra_managed": true, "port": 80 }, "advertise_on_public_default_vip": {}, "default_route_pools": [{ "pool": { "namespace": "xF5XC_NAMESPACEx", "name": "xF5XC_ORIGIN_POOLx", "kind": "origin_pool" }, "weight": 1, "priority": 1 }], "client_side_defense": { "policy": { "js_insert_all_pages": {} } }, "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" \ | jq .A 200 response confirms the HTTP load balancer was created with CSD enabled.
HTTPS Load Balancer (Secondary)
Section titled “HTTPS Load Balancer (Secondary)”This is the secondary load balancer. It uses https_auto_cert on port 443 with automatic Let’s Encrypt certificate provisioning. Before creating it, check if a skeleton HTTPS LB exists from a previous teardown — if so, restore it via PUT instead of POST to preserve the existing Let’s Encrypt certificate and avoid rate limits (5 duplicate certificates per exact identifier set per 7 days).
Check if the HTTPS LB already exists:
HTTPS_CHECK=$(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")If HTTPS_CHECK is 200, a skeleton HTTPS LB exists — use Path A (PUT). If 404, use Path B (POST).
Path A: Restore Skeleton via PUT (HTTPS LB exists)
Section titled “Path A: Restore Skeleton via PUT (HTTPS LB exists)”When a skeleton HTTPS LB exists from a prior teardown, restore the full configuration via PUT. This re-attaches the origin pool and re-enables CSD without triggering a new Let’s Encrypt certificate request — the existing certificate remains valid.
curl -s -X PUT \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "xF5XC_LB_NAMEx-https", "namespace": "xF5XC_NAMESPACEx", "labels": {}, "annotations": {}, "description": "HTTPS LB with Client-Side Defense enabled (secondary, cert-dependent)", "disable": false }, "spec": { "domains": ["xF5XC_DOMAINNAMEx"], "https_auto_cert": { "http_redirect": false, "add_hsts": false, "port": 443, "default_header": {}, "enable_path_normalize": {}, "no_mtls": {}, "default_loadbalancer": {} }, "advertise_on_public_default_vip": {}, "default_route_pools": [{ "pool": { "namespace": "xF5XC_NAMESPACEx", "name": "xF5XC_ORIGIN_POOLx", "kind": "origin_pool" }, "weight": 1, "priority": 1 }], "client_side_defense": { "policy": { "js_insert_all_pages": {} } }, "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/xF5XC_LB_NAMEx-https" \ | jq .A 200 response confirms the skeleton was restored with the full configuration. The certificate state should remain CertificateValid — no new Let’s Encrypt provisioning is triggered.
Path B: Create New via POST (HTTPS LB does not exist)
Section titled “Path B: Create New via POST (HTTPS LB does not exist)”When no HTTPS LB exists (first run or after a full teardown), create it via POST. This triggers Let’s Encrypt certificate provisioning, which may take 5–10 minutes.
curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "xF5XC_LB_NAMEx-https", "namespace": "xF5XC_NAMESPACEx", "labels": {}, "annotations": {}, "description": "HTTPS LB with Client-Side Defense enabled (secondary, cert-dependent)", "disable": false }, "spec": { "domains": ["xF5XC_DOMAINNAMEx"], "https_auto_cert": { "http_redirect": false, "add_hsts": false, "port": 443, "default_header": {}, "enable_path_normalize": {}, "no_mtls": {}, "default_loadbalancer": {} }, "advertise_on_public_default_vip": {}, "default_route_pools": [{ "pool": { "namespace": "xF5XC_NAMESPACEx", "name": "xF5XC_ORIGIN_POOLx", "kind": "origin_pool" }, "weight": 1, "priority": 1 }], "client_side_defense": { "policy": { "js_insert_all_pages": {} } }, "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" \ | jq .A 200 response confirms the HTTPS load balancer was created. Certificate provisioning begins automatically.
Evidence
Section titled “Evidence”Confirm both load balancers exist with CSD enabled. Always use a GET for evidence — the POST response returns transient state values that may not reflect the settled configuration.
HTTP LB (primary):
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \ | jq '{name: .metadata.name, domains: .spec.domains, csd_enabled: (.spec.client_side_defense != null), state: .spec.state}'| Field | Expected | Status |
|---|---|---|
| HTTP Status | 200 | PASS if returned, FAIL if 404 |
name | ${F5XC_LB_NAME}-http | PASS |
domains | Contains F5XC_DOMAINNAME | PASS |
csd_enabled | true | PASS — CSD is configured on this LB |
state | VIRTUAL_HOST_READY or VIRTUAL_HOST_PENDING_A_RECORD | Expected — HTTP LB transitions to READY once DNS resolves. Any other state (e.g. VIRTUAL_HOST_FAILED) is a FAIL — report to operator and do not proceed |
HTTPS LB (secondary):
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https" \ | jq '{name: .metadata.name, domains: .spec.domains, csd_enabled: (.spec.client_side_defense != null), state: .spec.state, cert_state: .spec.cert_state}'| Field | Expected | Status |
|---|---|---|
| HTTP Status | 200 | PASS if returned, FAIL if 404 |
name | ${F5XC_LB_NAME}-https | PASS |
domains | Contains F5XC_DOMAINNAME | PASS |
csd_enabled | true | PASS — CSD is configured on this LB |
creation_method | PUT (restored skeleton) or POST (new) | INFO — PUT preserves existing certificate |
state | VIRTUAL_HOST_READY (PUT) or VIRTUAL_HOST_PENDING_A_RECORD (POST) | Expected — PUT path may already be READY since DNS persisted from skeleton |
cert_state | CertificateValid (PUT) or PreDomainChallengePending (POST) | PUT preserves existing cert; POST triggers new provisioning |
Step 4: Configure DNS
Section titled “Step 4: Configure DNS”After creating the load balancer, it enters VIRTUAL_HOST_PENDING_A_RECORD status and the automatic certificate stays in PreDomainChallengePending. Two DNS records must exist before the LB becomes fully operational:
- A record —
xF5XC_DOMAINNAMExpointing to the VIP IP address (fromdns_infoin the LB response) - ACME challenge record —
_acme-challenge.xF5XC_DOMAINNAMExfor TLS certificate validation (managed automatically when using F5 XC DNS with managed records; manual CNAME required for external DNS)
The approach depends on whether F5 XC is the authoritative DNS provider for your domain.
Detect DNS Authority
Section titled “Detect DNS Authority”Check the nameservers for your root domain:
dig +short NS xF5XC_ROOT_DOMAINxIf the response includes ns1.f5clouddns.com and ns2.f5clouddns.com, F5 XC is the authoritative DNS provider — follow Option A. Otherwise, follow Option B for external DNS.
Option A: F5 XC Managed DNS
Section titled “Option A: F5 XC Managed DNS”When F5 XC is the authoritative DNS provider, the platform can automatically create both the A record and the ACME challenge CNAME — but only when the DNS zone has allow_http_lb_managed_records enabled.
1. Check if managed records are enabled:
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'If the response is true, the platform will auto-create DNS records for load balancers — skip to 3. Verify DNS resolution. If false or null, continue to the next step.
2. Enable managed records:
Retrieve the current zone configuration, enable managed records, and update:
# Get current zone configZONE_CONFIG=$(curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx")
# Update with managed records enabledecho "$ZONE_CONFIG" \ | jq '.spec.primary.allow_http_lb_managed_records = true' \ | curl -s -X PUT \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d @- \ "xF5XC_API_URLx/api/config/dns/namespaces/system/dns_zones/xF5XC_ROOT_DOMAINx" \ | jq .A 200 response (empty \{\}) confirms the zone was updated. F5 XC will create the A record and ACME challenge record in the zone’s auto-managed record group. If the managed records do not appear within 60 seconds, re-apply the load balancer to trigger record creation:
LB_CONFIG=$(curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http")echo "$LB_CONFIG" | curl -s -X PUT \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d @- \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \ | jq .This GET+PUT re-apply does not change the LB configuration — it simply triggers the platform to re-evaluate the DNS zone and create managed records. If the A record still does not resolve within 60 seconds after the re-apply, stop and report to the operator — do not retry the re-apply more than once.
3. Verify DNS resolution:
dig +short xF5XC_DOMAINNAMEx AThe response should return the VIP IP address. DNS propagation is typically immediate for F5 XC managed zones, but allow up to 60 seconds.
Option B: External DNS Provider
Section titled “Option B: External DNS Provider”When your domain uses an external DNS provider, you must create the records manually. Extract the required values from the load balancer:
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \ | jq '{ vip_ip: .spec.dns_info[0].ip_address, acme_target: .spec.auto_cert_info.dns_records }'Create these records at your DNS provider:
| Type | Name | Value |
|---|---|---|
| A | xF5XC_DOMAINNAMEx | VIP IP from dns_info[0].ip_address |
| CNAME | _acme-challenge.xF5XC_DOMAINNAMEx | *.autocerts.ves.volterra.io |
After creating the records, verify resolution:
dig +short xF5XC_DOMAINNAMEx Adig +short _acme-challenge.xF5XC_DOMAINNAMEx CNAMEEvidence
Section titled “Evidence”| Test | Command | Expected | Status |
|---|---|---|---|
| DNS-1: A Record | dig +short $F5XC_DOMAINNAME A | VIP IP address returned | PASS if IP returned, FAIL if empty |
| DNS-2: ACME CNAME | dig +short _acme-challenge.$F5XC_DOMAINNAME CNAME | *.autocerts.ves.volterra.io | PASS if CNAME returned |
| DNS authority | dig +short NS $F5XC_ROOT_DOMAIN | F5 XC or external nameservers | Informational — determines Option A vs B |
If DNS-1 returns empty after 60 seconds, see Troubleshooting — LB Stuck in VIRTUAL_HOST_PENDING_A_RECORD.
Step 5: Verify CSD is Enabled
Section titled “Step 5: Verify CSD is Enabled”Check that Client-Side Defense is enabled for the tenant. CSD is configured at the tenant level — if it has not been enabled yet, contact your F5 XC administrator.
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/status" \ | jq .A response containing "isConfigured": true and "isEnabled": true confirms CSD is active.
Evidence
Section titled “Evidence”curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/status" \ | jq '{configured: .isConfigured, enabled: .isEnabled}'| Field | Expected | Status |
|---|---|---|
configured | true | PASS |
enabled | true | PASS |
Either is false | — | FAIL — contact F5 XC administrator to enable CSD at tenant level |
Step 6: Register Protected Domain
Section titled “Step 6: Register Protected Domain”Register the root domain that CSD will monitor. The protected_domain field must be the eTLD+1 root domain (e.g., f5demos.com), not the full FQDN.
Check if Already Registered
Section titled “Check if Already Registered”Before creating, check whether the domain is already registered on the tenant. A 409 response to the POST means the domain already exists — this is a success condition, not an error.
curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d '{ "metadata": { "name": "xF5XC_DOMAINNAMEx", "namespace": "xF5XC_NAMESPACEx" }, "spec": { "protected_domain": "xF5XC_ROOT_DOMAINx" } }' \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains" \ | jq .| Response | Meaning | Action |
|---|---|---|
200 | Domain registered successfully | Continue to Step 7 |
409 (domain already exists) | Domain was previously registered on this tenant | Already done — continue to Step 7 |
"code": 8 (exhausted limits) | Protected domain quota full | Blocking — protected domains are required for CSD. Delete unused protected domains or contact your administrator. |
Evidence
Section titled “Evidence”The POST response itself is the primary evidence — it returns the registered domain object with spec.protected_domain matching your root domain. Alternatively, list all protected domains to confirm:
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/protected_domains" \ | jq '.items | length'| Field | Expected | Status |
|---|---|---|
POST returned protected_domain | Matches F5XC_ROOT_DOMAIN | PASS |
POST returned 200 or 409 | Domain registered or already exists | PASS |
| List items count | > 0 | PASS |
Step 7: Verify
Section titled “Step 7: Verify”DNS Resolution
Section titled “DNS Resolution”Confirm the A record is resolving and the ACME challenge CNAME is in place:
dig +short xF5XC_DOMAINNAMEx Adig +short _acme-challenge.xF5XC_DOMAINNAMEx CNAMEThe A record should return the VIP IP address. The CNAME should point to *.autocerts.ves.volterra.io (or a related autocerts target).
Load Balancer State
Section titled “Load Balancer State”Check that both load balancers have transitioned out of their pending states. The HTTP LB is the primary check — it should reach VIRTUAL_HOST_READY once DNS resolves, with no certificate dependency. The HTTPS LB certificate state is informational only.
HTTP LB (primary — must be READY to proceed):
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-http" \ | jq '{state: .spec.state}'| Field | Expected | Intermediate States |
|---|---|---|
state | VIRTUAL_HOST_READY | VIRTUAL_HOST_PENDING_A_RECORD — DNS not configured (see Step 4) |
HTTPS LB (secondary — informational, does not block progression):
curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/config/namespaces/xF5XC_NAMESPACEx/http_loadbalancers/xF5XC_LB_NAMEx-https" \ | jq '{state: .spec.state, cert_state: .spec.cert_state}'| Field | Expected | Intermediate States |
|---|---|---|
state | VIRTUAL_HOST_READY | VIRTUAL_HOST_PENDING_A_RECORD — DNS not configured; VIRTUAL_HOST_DNS_A_RECORD_ADDED — A record found, waiting for cert |
cert_state | CertificateValid | PreDomainChallengePending — waiting for ACME CNAME; DomainChallengeStarted — ACME challenge in progress; AutoCertDomainRateLimited — Let’s Encrypt rate limit hit (expected in demo environments, does not affect HTTP LB) |
JS Configuration
Section titled “JS Configuration”curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/js_configuration" \ | jq .The response contains a scriptTag field with the full HTML <script> tag that the load balancer injects into page responses.
Detected Domains
Section titled “Detected Domains”curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/detected_domains" \ | jq '{summary: .domain_summary, domains: .domains_list}'Detected Scripts
Section titled “Detected Scripts”The scripts endpoint requires a time range using epoch timestamps (seconds since Unix epoch). The example below queries the last 7 days.
NOW=$(date +%s)START=$(( NOW - 604800 ))curl -s -X POST \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ -H "Content-Type: application/json" \ -d "{\"startTime\": \"$START\", \"endTime\": \"$NOW\"}" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/scripts" \ | jq '[.scripts[]? | {script_name: .script_name, risk_level: .risk_level}]'Form Fields
Section titled “Form Fields”The form fields endpoint requires a time range using epoch timestamps, passed as query parameters.
NOW=$(date +%s)START=$(( NOW - 604800 ))curl -s \ -H "Authorization: APIToken xF5XC_API_TOKENx" \ "xF5XC_API_URLx/api/shape/csd/namespaces/xF5XC_NAMESPACEx/formFields?startTime=$START&endTime=$NOW" \ | jq '.form_fields'Phase 1 Evidence Summary
Section titled “Phase 1 Evidence Summary”After completing all verification checks, the AI assistant should present a consolidated status table:
| Test ID | Check | Expected | Required | Status |
|---|---|---|---|---|
| DNS-1 | A Record resolves | VIP IP returned | Yes | PASS / FAIL |
| DNS-2 | ACME CNAME exists | *.autocerts.ves.volterra.io | No | PASS / PENDING |
| LB-1 | HTTP LB state | VIRTUAL_HOST_READY | Yes | PASS / PENDING |
| LB-2 | HTTPS LB state | VIRTUAL_HOST_READY | No | PASS / PENDING / INFO |
| TLS-1 | Certificate state | CertificateValid | No | PASS / PENDING / INFO |
| CSD-1 | JS configuration | scriptTag present | Yes | PASS / FAIL |
| CSD-2 | CSD status | isEnabled: true | Yes | PASS / FAIL |
| CSD-3 | Protected domain | Domain registered | Yes | PASS / FAIL |
If LB-1 shows PENDING, poll every 30 seconds for up to 4 iterations (2 minutes total). If LB-1 is still not VIRTUAL_HOST_READY after 4 iterations, check DNS resolution with dig and report to operator — do not proceed until LB-1 reaches READY. For LB-2 and TLS-1, poll every 60 seconds for up to 10 iterations (10 minutes). If still in an intermediate state after 10 iterations, record the current state as INFO and proceed — these are informational and do not block demo progression.
Phase 1 complete. Proceed to Phase 2 — Attack to run the attack simulation.