部署
所有 Terraform 檔案均位於 terraform/ 目錄中。複製儲存庫並直接部署:
git clone https://github.com/f5-sales-demo/traffic-generator.gitcd traffic-generator/terraformcp terraform.tfvars.example terraform.tfvars# Edit terraform.tfvars with your Azure subscription ID and target FQDNTerraform 設定
Section titled “Terraform 設定”Terraform 檔案結構
Section titled “Terraform 檔案結構”terraform 目錄包含 9 個遵循示範資源標準的檔案:
versions.tf— Terraform 與提供者版本限制(azurerm ~> 4.0、azuread ~> 3.0)providers.tf— Azure RM 與 Azure AD 提供者設定data.tf— 用於部署者自動解析的 Azure AD 資料來源locals.tf— 部署者解析、Azure Cloud Adoption Framework 資源命名、標準標籤main.tf— 資源群組(命名為rg-traffic-generator-{environment}-{deployer})variables.tf— 所有輸入變數(2 個必填、9 個選填),透過 Azure AD 進行部署者自動解析network.tf— VNet(10.201.0.0/16)、子網路、公用 IP、NSG(連接埠 22 SSH)、NICvm.tf— 透過 templatefile() 使用 cloud-init 的 Ubuntu 24.04 VMoutputs.tf— 17 個輸出:15 個所有示範資源共用的標準輸出(deployer、public_ip、private_ip、ssh_command、resource_group_name、vm_name、nsg_name、vnet_name、subnet_id、component、environment、resource_group_id、vm_id、nsg_id、location),以及 2 個元件專屬輸出(target_fqdn、status_check)
提供者與變數
Section titled “提供者與變數”main.tf 設定 Azure 提供者:
resource "azurerm_resource_group" "main" { name = local.name.resource_group location = var.location tags = local.tags}variables.tf 定義所有可設定參數。vm_size 預設值為 Standard_F16s_v2(16 vCPU 計算最佳化)— 請參閱 VM 規格調整 以了解擴充指引:
# ---------------------------------------------------------# General# ---------------------------------------------------------
variable "subscription_id" { description = "Azure subscription ID" type = string}
variable "deployer" { description = "Override for deployer identifier (auto-resolved from Azure AD if empty). Required for service principal or managed identity authentication." type = string default = ""}
variable "location" { description = "Azure region for all resources" type = string default = "eastus2"}
variable "environment" { description = "Environment label used in resource group naming and tags" type = string default = "lab"}
variable "tags" { description = "Additional tags merged with standard tags (component, environment, deployer, managed_by)" type = map(string) default = {}}
# ---------------------------------------------------------# Compute# ---------------------------------------------------------
variable "vm_size" { description = "Azure VM size (F16s_v2: 16 vCPU compute-optimized, validated by benchmark)" type = string default = "Standard_F16s_v2"}
variable "admin_username" { description = "SSH admin username for the VM" type = string default = "azureuser"}
variable "ssh_public_key_path" { description = "Path to the SSH public key file" type = string default = "~/.ssh/id_ed25519.pub"}
variable "disk_size_gb" { description = "OS disk size in GB" type = number default = 64}
# ---------------------------------------------------------# Component-Specific# ---------------------------------------------------------
variable "target_fqdn" { description = "FQDN of the F5 XC load balancer to target" type = string}
variable "target_origin_ip" { description = "Direct origin IP for bypass testing" type = string default = ""}
variable "tool_tier" { description = "Tool installation tier: standard (default) or full (includes ZAP, Metasploit)" type = string default = "standard"
validation { condition = contains(["standard", "full"], var.tool_tier) error_message = "tool_tier must be \"standard\" or \"full\"." }}網路基礎架構
Section titled “網路基礎架構”network.tf 建立 VNet、子網路、公用 IP、NSG(連接埠 22 SSH)及 NIC:
resource "azurerm_virtual_network" "main" { name = local.name.virtual_network address_space = ["10.201.0.0/16"] location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name
tags = azurerm_resource_group.main.tags}
resource "azurerm_subnet" "main" { name = local.name.subnet resource_group_name = azurerm_resource_group.main.name virtual_network_name = azurerm_virtual_network.main.name address_prefixes = ["10.201.1.0/24"]}
resource "azurerm_public_ip" "main" { name = local.name.public_ip location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name allocation_method = "Static" sku = "Standard"
tags = azurerm_resource_group.main.tags}
resource "azurerm_network_security_group" "main" { name = local.name.nsg location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name
security_rule { name = "AllowSSH" priority = 100 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "22" source_address_prefix = "*" destination_address_prefix = "*" }
tags = azurerm_resource_group.main.tags}
resource "azurerm_network_interface" "main" { name = local.name.network_interface location = azurerm_resource_group.main.location resource_group_name = azurerm_resource_group.main.name
ip_configuration { name = "internal" subnet_id = azurerm_subnet.main.id private_ip_address_allocation = "Dynamic" public_ip_address_id = azurerm_public_ip.main.id }
tags = azurerm_resource_group.main.tags}
resource "azurerm_network_interface_security_group_association" "main" { network_interface_id = azurerm_network_interface.main.id network_security_group_id = azurerm_network_security_group.main.id}搭配 Cloud-Init 的虛擬機器
Section titled “搭配 Cloud-Init 的虛擬機器”vm.tf 建立 Ubuntu 24.04 VM,並傳遞包含目標及工具變數的 cloud-init 範本:
resource "azurerm_linux_virtual_machine" "main" { name = local.name.virtual_machine resource_group_name = azurerm_resource_group.main.name location = azurerm_resource_group.main.location size = var.vm_size
admin_username = var.admin_username disable_password_authentication = true
admin_ssh_key { username = var.admin_username public_key = file(pathexpand(var.ssh_public_key_path)) }
network_interface_ids = [azurerm_network_interface.main.id]
os_disk { caching = "ReadWrite" storage_account_type = "Premium_LRS" disk_size_gb = var.disk_size_gb }
source_image_reference { publisher = "Canonical" offer = "ubuntu-24_04-lts" sku = "server" version = "latest" }
custom_data = base64encode(templatefile("${path.module}/cloud-init.yaml", { target_fqdn = var.target_fqdn target_origin_ip = var.target_origin_ip tool_tier = var.tool_tier }))
tags = azurerm_resource_group.main.tags}Cloud-Init 佈建
Section titled “Cloud-Init 佈建”cloud-init.yaml 是經過生產環境驗證的設定,已通過超過 48 小時的持續負載測試,包含 4 輪來源壓力測試。它使用具備重試邏輯的輔助程式庫、將進度記錄至 /var/log/cloud-init-progress.log、9 個佈建階段,以及條件式狀態回報(status.json 中的 ready/degraded)。其佈建內容包括:
- 核心調校 —
somaxconn=131072、tcp_max_tw_buckets=4000000、tcp_fin_timeout=5、64 MiB 通訊端緩衝區、file-max=4194304 - NIC 最佳化 — 跨所有核心的 RPS/RFS、透過 ethtool 最大化環形緩衝區、THP=always
- APT 套件 — nikto、nmap、masscan、sqlmap、hydra、medusa、ncrack、tshark、hping3、tcpdump、netcat、ngrep、iperf3、mtr、sslscan、socat、dirb、whatweb、wrk、hey、vegeta、ethtool
- GitHub Releases — nuclei、dalfox、ffuf、gobuster、feroxbuster、subfinder、httpx、amass
- Python 套件 — mitmproxy、sslyze、dnsrecon、fierce、theHarvester、playwright、scapy、impacket、arjun
- Node.js 20.x — 搭載隱身外掛的 Puppeteer,用於機器人模擬
- 瀏覽器自動化 — 用於無頭瀏覽器測試的 Playwright Chromium
- Git 複製 — testssl.sh、recon-ng、spiderfoot、SecLists、waf-bypass、PayloadsAllTheThings
- 套件部署 — 從此儲存庫複製 19 個流量套件至
/opt/traffic-generator/suites/ - 設定檔 — 包含
TARGET_FQDN、TARGET_ORIGIN_IP及CRAPI_PORT的config.env
cloud-init 使用 Terraform 範本變數:${target_fqdn}、${target_origin_ip} 及 ${tool_tier}。Shell 變數(如 $NPROC)在 Terraform templatefile 中以 $$NPROC 形式轉義。
#cloud-configpackage_update: truepackage_upgrade: true
packages: # System essentials - ca-certificates - curl - gnupg - lsb-release - jq - git - unzip - build-essential - python3 - python3-pip - python3-venv - python3-dev - libffi-dev - libssl-dev - libpcap-dev # Network analysis - nmap - masscan - tshark - hping3 - socat - tcpdump - netcat-openbsd - ngrep - iperf3 - mtr-tiny - whois - dnsutils # Web scanners - nikto - sqlmap - dirb - whatweb - sslscan # Password/credential testing - hydra - medusa - ncrack - john - hashcat # Forensics/analysis - binwalk - strace - ltrace # Performance monitoring - sysstat - htop - iotop - ethtool # Load testing - apache2-utils - wrk # Headless Chrome dependencies - fonts-noto-color-emoji - fonts-liberation - libnss3 - libatk-bridge2.0-dev - libdrm2 - libxkbcommon0 - libgbm1 - libasound2-dev
write_files: - path: /usr/local/bin/ghlatest permissions: '0755' content: | #!/bin/sh set -e REPO="$1" if [ -z "$REPO" ]; then echo "Usage: ghlatest owner/repo" >&2; exit 1; fi R=0; M=3 while [ "$R" -lt "$M" ]; do RESP=$(curl -fsSL -w "\n%%{http_code}" "https://api.github.com/repos/$REPO/releases/latest" 2>/dev/null) || true HTTP_CODE=$(echo "$RESP" | tail -1) BODY=$(echo "$RESP" | sed '$d') if [ "$HTTP_CODE" = "403" ] || [ "$HTTP_CODE" = "429" ]; then echo "WARN: ghlatest rate-limited ($HTTP_CODE) for $REPO" >&2; exit 1 fi V=$(echo "$BODY" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"v\{0,1\}\([^"]*\)".*/\1/') if [ -n "$V" ]; then echo "$V"; exit 0; fi R=$((R + 1)); sleep $((R * 3)) done echo "ERROR: ghlatest failed for $REPO" >&2; exit 1
- path: /etc/sysctl.d/99-traffic-generator.conf content: | net.core.somaxconn = 131072 net.ipv4.tcp_max_syn_backlog = 131072 net.core.netdev_max_backlog = 131072 net.ipv4.ip_local_port_range = 1024 65535 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 5 net.ipv4.tcp_max_tw_buckets = 4000000 net.ipv4.tcp_keepalive_time = 30 net.ipv4.tcp_keepalive_intvl = 5 net.ipv4.tcp_keepalive_probes = 3 net.core.rmem_max = 67108864 net.core.wmem_max = 67108864 net.ipv4.tcp_rmem = 4096 87380 67108864 net.ipv4.tcp_wmem = 4096 65536 67108864 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_slow_start_after_idle = 0 net.ipv4.tcp_no_metrics_save = 1 net.ipv4.tcp_timestamps = 1 net.ipv4.tcp_window_scaling = 1 fs.file-max = 4194304
- path: /etc/security/limits.d/99-traffic-generator.conf content: | * soft nofile 524288 * hard nofile 524288 root soft nofile 524288 root hard nofile 524288 * soft nproc 65535 * hard nproc 65535
- path: /etc/profile.d/traffic-generator.sh permissions: '0644' content: | export NODE_PATH=/usr/lib/node_modules export PATH=$PATH:/usr/local/bin
- path: /usr/local/bin/tgen-profile permissions: '0755' content: | #!/bin/bash set -uo pipefail report() { echo "================================================================" echo " TRAFFIC GENERATOR PLATFORM PROFILE — $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "================================================================" echo "" echo "--- CPU ---" echo "Cores: $(nproc)" uptime echo "" echo "--- Memory ---" free -h echo "" echo "--- Disk I/O ---" iostat -x 1 1 2>/dev/null | grep -A20 'Device' || echo "iostat not available" echo "" echo "--- Network connections ---" ss -s echo "" echo "--- TCP state distribution ---" ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn echo "" echo "--- TIME_WAIT count ---" ss -tan state time-wait | wc -l echo "" echo "--- Ephemeral port usage ---" RANGE=$(sysctl -n net.ipv4.ip_local_port_range) LOW=$(echo "$RANGE" | awk '{print $1}') HIGH=$(echo "$RANGE" | awk '{print $2}') USED=$(ss -tan | awk -v low="$LOW" -v high="$HIGH" '{split($4,a,":"); p=a[length(a)]; if(p>=low && p<=high) count++} END {print count+0}') TOTAL=$((HIGH - LOW)) echo " Range: $LOW-$HIGH ($TOTAL ports)" echo " Used: $USED ($((USED * 100 / TOTAL))%)" echo "" echo "--- Open file descriptors ---" echo " System: $(awk '{print $1}' /proc/sys/fs/file-nr) / $(cat /proc/sys/fs/file-max)" echo " Limit: $(ulimit -n) (soft) / $(ulimit -Hn) (hard)" echo "" echo "--- Kernel TCP tuning ---" for k in net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.ip_local_port_range net.ipv4.tcp_tw_reuse net.ipv4.tcp_fin_timeout net.ipv4.tcp_max_tw_buckets net.core.rmem_max net.core.wmem_max; do printf " %-40s %s\n" "$k" "$(sysctl -n $k 2>/dev/null || echo N/A)" done } if [ "$${1:-}" = "--watch" ]; then SECS="$${2:-10}" while true; do clear; report; sleep "$SECS"; done else report fi
- path: /opt/traffic-generator/config.env content: | TARGET_FQDN=${target_fqdn} TARGET_ORIGIN_IP=${target_origin_ip} TARGET_PROTOCOL=http CRAPI_PORT=8888 NODE_PATH=/usr/lib/node_modules
- path: /opt/traffic-generator/status.json content: | {"status":"provisioning","tool_tier":"${tool_tier}"}
- path: /usr/local/lib/cloud-init-helpers.sh permissions: "0644" content: | #!/bin/sh PROGRESS_LOG="/var/log/cloud-init-progress.log" log_phase() { _phase="$1"; shift _msg="$${*:-started}" _ts=$(date -u +%Y-%m-%dT%H:%M:%SZ) printf '[%s] [%s] %s\n' "$_ts" "$_phase" "$_msg" | tee -a "$PROGRESS_LOG" >&2 } retry_cmd() { _max="$1"; _base="$2"; shift 2 _attempt=1 while [ "$_attempt" -le "$_max" ]; do if "$@"; then return 0; fi if [ "$_attempt" -lt "$_max" ]; then _wait=$(( _base * _attempt )) log_phase "retry" "attempt $_attempt/$_max failed ($1) — retrying in $${_wait}s" sleep "$_wait" fi _attempt=$(( _attempt + 1 )) done log_phase "retry" "FAILED after $_max attempts: $1" return 1 } fetch_url() { _url="$1"; _out="$2"; _desc="$${3:-$1}" log_phase "fetch" "$_desc" retry_cmd 4 5 curl -fsSL --connect-timeout 15 --max-time 300 -o "$_out" "$_url" } install_packages() { log_phase "apt" "installing: $*" retry_cmd 3 10 apt-get install -y -o DPkg::Lock::Timeout=60 "$@" } clone_repo() { _url="$1"; _dest="$2"; _depth="$${3:-1}" log_phase "git" "cloning $_url -> $_dest" retry_cmd 3 10 git clone --depth "$_depth" --single-branch "$_url" "$_dest" } wait_for_http() { _url="$1"; _max="$2"; _desc="$${3:-$_url}" log_phase "health" "waiting for $_desc (max $${_max}s)" _elapsed=0 while [ "$_elapsed" -lt "$_max" ]; do if curl -sf --max-time 5 "$_url" >/dev/null 2>&1; then log_phase "health" "$_desc ready after $${_elapsed}s" return 0 fi sleep 5; _elapsed=$(( _elapsed + 5 )) done log_phase "health" "TIMEOUT: $_desc not ready after $${_max}s" return 1 }
runcmd: # --- Phase 0: Kernel tuning, NIC optimization, system limits --- - sysctl --system - systemctl daemon-reload - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase0" "kernel tuning and NIC optimization" # RPS/RFS: distribute NIC softirqs across all cores NCPU=$(nproc) RPS_MASK=$(printf '%x' $(( (1 << NCPU) - 1 ))) NIC=$(ip -o link show | awk -F': ' '/state UP/ && !/lo/{print $2; exit}') if [ -n "$NIC" ]; then RFS_ENTRIES=$((32768 * NCPU)) echo "$RFS_ENTRIES" > /proc/sys/net/core/rps_sock_flow_entries for rxq in /sys/class/net/"$NIC"/queues/rx-*/rps_cpus; do echo "$RPS_MASK" > "$rxq" done for rxq in /sys/class/net/"$NIC"/queues/rx-*/rps_flow_cnt; do RXQ_COUNT=$(ls -d /sys/class/net/"$NIC"/queues/rx-* | wc -l) echo "$((RFS_ENTRIES / RXQ_COUNT))" > "$rxq" done # Maximize NIC ring buffers if command -v ethtool >/dev/null 2>&1; then RX_MAX=$(ethtool -g "$NIC" 2>/dev/null | awk '/Pre-set.*:/,/^$/{if(/RX:/){print $2; exit}}') TX_MAX=$(ethtool -g "$NIC" 2>/dev/null | awk '/Pre-set.*:/,/^$/{if(/TX:/){print $2; exit}}') [ -n "$RX_MAX" ] && [ -n "$TX_MAX" ] && ethtool -G "$NIC" rx "$RX_MAX" tx "$TX_MAX" 2>/dev/null fi echo "NIC tuning: RPS mask=$RPS_MASK on $NIC" fi # Transparent Huge Pages for large connection pools echo "always" > /sys/kernel/mm/transparent_hugepage/enabled 2>/dev/null || true
# --- Phase 1: Node.js 24 --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase1" "installing Node.js 24" retry_cmd 3 10 sh -c 'curl -fsSL https://deb.nodesource.com/setup_24.x | bash -' install_packages nodejs
# --- Phase 2: Python toolchain (uv) --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase2" "installing Python uv" retry_cmd 4 5 sh -c 'curl -fsSL https://astral.sh/uv/install.sh | sh' cp /root/.local/bin/uv /usr/local/bin/uv || true cp /root/.local/bin/uvx /usr/local/bin/uvx || true
# --- Phase 3: Security binaries (Go tools via ghlatest) --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase3" "installing security binaries" export DPKG_ARCH=$(dpkg --print-architecture) export PATH=/usr/local/bin:$PATH
echo "Installing nuclei..." NUCLEI_VER=$(ghlatest projectdiscovery/nuclei) fetch_url "https://github.com/projectdiscovery/nuclei/releases/download/v$${NUCLEI_VER}/nuclei_$${NUCLEI_VER}_linux_$${DPKG_ARCH}.zip" /tmp/nuclei.zip "nuclei" unzip -oq /tmp/nuclei.zip nuclei -d /usr/local/bin && rm /tmp/nuclei.zip
echo "Installing subfinder..." SUBFINDER_VER=$(ghlatest projectdiscovery/subfinder) fetch_url "https://github.com/projectdiscovery/subfinder/releases/download/v$${SUBFINDER_VER}/subfinder_$${SUBFINDER_VER}_linux_$${DPKG_ARCH}.zip" /tmp/subfinder.zip "subfinder" unzip -oq /tmp/subfinder.zip subfinder -d /usr/local/bin && rm /tmp/subfinder.zip
echo "Installing httpx..." HTTPX_VER=$(ghlatest projectdiscovery/httpx) fetch_url "https://github.com/projectdiscovery/httpx/releases/download/v$${HTTPX_VER}/httpx_$${HTTPX_VER}_linux_$${DPKG_ARCH}.zip" /tmp/httpx.zip "httpx" unzip -oq /tmp/httpx.zip httpx -d /usr/local/bin && rm /tmp/httpx.zip
echo "Installing ffuf..." FFUF_VER=$(ghlatest ffuf/ffuf) retry_cmd 4 5 sh -c 'curl -fsSL "https://github.com/ffuf/ffuf/releases/download/v$${FFUF_VER}/ffuf_$${FFUF_VER}_linux_$${DPKG_ARCH}.tar.gz" | tar -xz -C /usr/local/bin ffuf'
echo "Installing gobuster..." GOBUSTER_VER=$(ghlatest OJ/gobuster) retry_cmd 4 5 sh -c 'curl -fsSL "https://github.com/OJ/gobuster/releases/download/v$${GOBUSTER_VER}/gobuster_Linux_$(uname -m).tar.gz" | tar -xz -C /usr/local/bin gobuster'
echo "Installing feroxbuster..." FEROX_VER=$(ghlatest epi052/feroxbuster) fetch_url "https://github.com/epi052/feroxbuster/releases/download/v$${FEROX_VER}/feroxbuster_$${DPKG_ARCH}.deb.zip" /tmp/ferox.zip "feroxbuster" unzip -oq /tmp/ferox.zip -d /tmp && dpkg -i /tmp/feroxbuster_*.deb && rm -f /tmp/ferox.zip /tmp/feroxbuster_*.deb
echo "Installing dalfox..." DALFOX_VER=$(ghlatest hahwul/dalfox) fetch_url "https://github.com/hahwul/dalfox/releases/download/v$${DALFOX_VER}/dalfox-linux-$${DPKG_ARCH}.tar.gz" /tmp/dalfox.tar.gz "dalfox" tar -xzf /tmp/dalfox.tar.gz -C /tmp && mv /tmp/dalfox-linux-$${DPKG_ARCH} /usr/local/bin/dalfox && chmod +x /usr/local/bin/dalfox && rm /tmp/dalfox.tar.gz
echo "Installing amass..." AMASS_VER=$(ghlatest owasp-amass/amass) fetch_url "https://github.com/owasp-amass/amass/releases/download/v$${AMASS_VER}/amass_linux_$${DPKG_ARCH}.tar.gz" /tmp/amass.tar.gz "amass" mkdir -p /tmp/amass && tar -xzf /tmp/amass.tar.gz -C /tmp/amass && cp /tmp/amass/amass_linux_$${DPKG_ARCH}/amass /usr/local/bin/ && rm -rf /tmp/amass /tmp/amass.tar.gz
echo "Phase 3 complete"
# --- Phase 3b: High-performance load testing tools --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase3b" "installing load testing tools" export DPKG_ARCH=$(dpkg --print-architecture) export PATH=/usr/local/bin:$PATH
echo "Installing hey (via go install — no prebuilt binaries available)..." install_packages golang-go mkdir -p /opt/go/bin chown -R azureuser:azureuser /opt/go retry_cmd 3 15 sh -c 'GOPATH=/opt/go go install github.com/rakyll/hey@latest' cp /opt/go/bin/hey /usr/local/bin/hey
echo "Installing gotestwaf..." retry_cmd 3 15 sh -c 'GOPATH=/opt/go go install github.com/wallarm/gotestwaf/cmd/gotestwaf@latest' cp /opt/go/bin/gotestwaf /usr/local/bin/gotestwaf mkdir -p /opt/gotestwaf cp -r /opt/go/pkg/mod/github.com/wallarm/gotestwaf@*/config.yaml /opt/gotestwaf/ 2>/dev/null || true cp -r /opt/go/pkg/mod/github.com/wallarm/gotestwaf@*/testcases /opt/gotestwaf/ 2>/dev/null || true chown -R azureuser:azureuser /opt/gotestwaf
echo "Installing vegeta..." VEGETA_VER=$(ghlatest tsenart/vegeta) retry_cmd 4 5 sh -c 'curl -fsSL "https://github.com/tsenart/vegeta/releases/download/v$${VEGETA_VER}/vegeta_$${VEGETA_VER}_linux_$${DPKG_ARCH}.tar.gz" | tar -xz -C /usr/local/bin vegeta'
echo "Phase 3b complete"
# --- Phase 4: Python security tools --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase4" "installing Python security tools" retry_cmd 3 10 pip install --retries 3 --break-system-packages scapy impacket arjun hashid pwntools retry_cmd 3 10 pip install --retries 3 --break-system-packages --ignore-installed typing_extensions mitmproxy sslyze retry_cmd 3 10 pip install --retries 3 --break-system-packages wfuzz
# --- Phase 5: Browser automation --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase5" "installing browser automation tools" retry_cmd 3 15 npm install -g playwright puppeteer puppeteer-extra puppeteer-extra-plugin-stealth - | . /usr/local/lib/cloud-init-helpers.sh retry_cmd 3 30 npx --yes playwright install chromium retry_cmd 3 30 sh -c 'su - azureuser -c "export NODE_PATH=/usr/lib/node_modules && npx --yes playwright install chromium"' npx --yes playwright install-deps chromium 2>/dev/null || true
# --- Phase 6: Git-cloned tools --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase6" "cloning tool repositories" clone_repo "https://github.com/drwetter/testssl.sh.git" /opt/testssl.sh ln -sf /opt/testssl.sh/testssl.sh /usr/local/bin/testssl clone_repo "https://github.com/danielmiessler/SecLists.git" /opt/SecLists clone_repo "https://github.com/lanmaster53/recon-ng.git" /opt/recon-ng clone_repo "https://github.com/smicallef/spiderfoot.git" /opt/spiderfoot clone_repo "https://github.com/nemesida-waf/waf-bypass.git" /opt/waf-bypass clone_repo "https://github.com/swisskyrepo/PayloadsAllTheThings.git" /opt/PayloadsAllTheThings
# --- Phase 7: ZAP (standard tier) + heavy frameworks (full tier) --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase7" "installing ZAP and optional tools" export PATH=/usr/local/bin:$PATH echo "Installing ZAP..." ZAP_VER=$(ghlatest zaproxy/zaproxy) retry_cmd 4 5 sh -c 'curl -fsSL "https://github.com/zaproxy/zaproxy/releases/download/v$${ZAP_VER}/ZAP_$${ZAP_VER}_Linux.tar.gz" | tar -xz -C /opt' mv /opt/ZAP_$${ZAP_VER} /opt/zaproxy 2>/dev/null || true printf '#!/bin/sh\nexec /opt/zaproxy/zap.sh "$@"\n' > /usr/local/bin/zap && chmod +x /usr/local/bin/zap
if [ "${tool_tier}" = "full" ]; then echo "Installing Metasploit (full tier)..." curl -fsSL https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb > /tmp/msfinstall chmod +x /tmp/msfinstall && /tmp/msfinstall fi
# --- Phase 8: Clone traffic-generator suites --- - mkdir -p /opt/traffic-generator/suites /opt/traffic-generator/results - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase8" "cloning traffic generator suites" clone_repo "https://github.com/f5-sales-demo/traffic-generator.git" /opt/traffic-generator/repo 1 || log_phase "warning" "traffic-generator repo clone failed — suites will be from cloud-init" - | if [ -d /opt/traffic-generator/repo/suites ]; then cp -r /opt/traffic-generator/repo/suites/* /opt/traffic-generator/suites/ chmod -R +x /opt/traffic-generator/suites/ fi - chown -R azureuser:azureuser /opt/traffic-generator
# --- Phase 9: Smoke test and status update --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "phase9" "running smoke test" export NODE_PATH=/usr/lib/node_modules export PATH=/usr/local/bin:$PATH PASS=0; FAIL=0 for tool in nmap nikto sqlmap nuclei dalfox ffuf gobuster feroxbuster subfinder httpx sslscan hydra john hashcat masscan hping3 tshark wrk hey vegeta ab zap testssl arjun wfuzz gotestwaf node npx playwright; do if command -v "$tool" >/dev/null 2>&1; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "MISSING: $tool"; fi done if [ "$FAIL" -eq 0 ]; then STATUS="ready"; else STATUS="degraded"; fi log_phase "phase9" "smoke test complete: $PASS passed, $FAIL failed" printf '{"status":"%s","tool_tier":"${tool_tier}","tools_pass":%d,"tools_fail":%d,"timestamp":"%s"}\n' \ "$STATUS" "$PASS" "$FAIL" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > /opt/traffic-generator/status.json
# --- Completion --- - | . /usr/local/lib/cloud-init-helpers.sh log_phase "complete" "traffic-generator provisioned"outputs.tf 公開 17 個輸出,包括公用/私人 IP、SSH 命令、資源識別碼及元件專屬值(target_fqdn、status_check):
# ---------------------------------------------------------# Standard Outputs (present in every demo resource)# ---------------------------------------------------------
output "deployer" { description = "Resolved deployer identifier" value = local.deployer}
output "resource_group_name" { description = "Name of the resource group" value = azurerm_resource_group.main.name}
output "resource_group_id" { description = "Resource ID of the resource group" value = azurerm_resource_group.main.id}
output "location" { description = "Azure region" value = azurerm_resource_group.main.location}
output "public_ip" { description = "Public IP address of the VM" value = azurerm_public_ip.main.ip_address}
output "private_ip" { description = "Private IP address of the VM" value = azurerm_network_interface.main.private_ip_address}
output "ssh_command" { description = "SSH command to connect to the VM" value = "ssh ${var.admin_username}@${azurerm_public_ip.main.ip_address}"}
output "vm_name" { description = "Name of the virtual machine" value = azurerm_linux_virtual_machine.main.name}
output "vm_id" { description = "Resource ID of the virtual machine" value = azurerm_linux_virtual_machine.main.id}
output "nsg_name" { description = "Name of the network security group" value = azurerm_network_security_group.main.name}
output "nsg_id" { description = "Resource ID of the network security group" value = azurerm_network_security_group.main.id}
output "vnet_name" { description = "Name of the virtual network" value = azurerm_virtual_network.main.name}
output "subnet_id" { description = "Resource ID of the subnet" value = azurerm_subnet.main.id}
output "component" { description = "Component name" value = local.component}
output "environment" { description = "Environment label" value = var.environment}
# ---------------------------------------------------------# Component-Specific Outputs# ---------------------------------------------------------
output "target_fqdn" { description = "Target FQDN the traffic generator is configured to attack" value = var.target_fqdn}
output "status_check" { description = "SSH command to check provisioning status" value = "ssh ${var.admin_username}@${azurerm_public_ip.main.ip_address} cat /opt/traffic-generator/status.json"}範例變數檔案
Section titled “範例變數檔案”將 terraform.tfvars.example 複製為 terraform.tfvars 並填入您的值。.gitignore 已排除 terraform.tfvars 以防止提交憑證:
# Copy this file to terraform.tfvars and fill in your values.# terraform.tfvars is gitignored — never commit real credentials.
# --- Required ---subscription_id = "00000000-0000-0000-0000-000000000000"target_fqdn = "demo.example.com"
# --- Optional overrides (defaults shown) ---# deployer = "" # auto-resolved from Azure AD# location = "eastus2"# environment = "lab"# vm_size = "Standard_F16s_v2"# disk_size_gb = 64# admin_username = "azureuser"# ssh_public_key_path = "~/.ssh/id_ed25519.pub"# tags = {}# target_origin_ip = ""# tool_tier = "standard"將 subscription_id 替換為您的 Azure 訂閱 ID(從 az account show --query id -o tsv 取得),並將 target_fqdn 替換為您的 F5 XC 負載平衡器網域。
terraform init
terraform plan
terraform apply成功部署後,Terraform 會輸出公用 IP、SSH 命令及目標 FQDN。
VM 規格調整
Section titled “VM 規格調整”建議針對此 CPU 密集型流量產生工作負載使用 F 系列計算最佳化 VM。Cloud-init 核心調校及工具並行數會隨 vCPU 數量自動擴充。
| VM SKU | vCPU | RAM | 網路 | 使用情境 |
|---|---|---|---|---|
| Standard_F4s_v2 | 4 | 8 GiB | 10 Gbps | 輕量實驗室/示範 |
| Standard_F8s_v2 | 8 | 16 GiB | 12.5 Gbps | 標準示範 |
| Standard_F16s_v2 | 16 | 32 GiB | 12.5 Gbps | 負載測試(預設) |
| Standard_F32s_v2 | 32 | 64 GiB | 16 Gbps | 大量基準測試 |
| Standard_D16s_v3 | 16 | 64 GiB | 12.5 Gbps | 記憶體密集工作負載 |
Cloud-init 佈建標準層需要 15-20 分鐘,完整層約需 25 分鐘。VM 可透過 SSH 立即存取,但在 cloud-init 完成之前,工具仍在持續安裝中。
等待 Cloud-Init
Section titled “等待 Cloud-Init”透過 SSH 連線至 VM 並等待佈建完成:
ssh azureuser@$(terraform output -raw public_ip)
cloud-init status --wait完成後的預期輸出:
status: done檢查狀態檔案
Section titled “檢查狀態檔案”cloud-init 腳本完成後會寫入狀態檔案:
cat /opt/traffic-generator/status.json預期輸出:
{ "status": "ready", "tier": "standard", "target_fqdn": "demo.example.com", "completed": "2026-04-25T14:30:00Z", "tools_installed": true}驗證關鍵工具
Section titled “驗證關鍵工具”執行驗證迴圈以確認每個關鍵工具均可在 PATH 中使用:
for tool in nikto sqlmap nuclei dalfox ffuf gobuster feroxbuster nmap masscan \ hydra medusa ncrack sslscan tshark hping3 mitmproxy sslyze subfinder httpx \ amass arjun mtr whatweb dirb socat netcat ngrep iperf3 whois dig wrk hey; do if command -v "$tool" > /dev/null 2>&1; then echo "OK: $tool" else echo "MISSING: $tool" fidone驗證套件執行器
Section titled “驗證套件執行器”確認執行器與套件已安裝:
ls /opt/traffic-generator/suites/runner.shls /opt/traffic-generator/suites/*/
/opt/traffic-generator/suites/runner.sh web-app-attacks --dry-runTerraform 輸出
Section titled “Terraform 輸出”| 輸出 | 說明 | 範例 |
|---|---|---|
deployer | 已解析的部署者識別碼 | jsmith |
public_ip | 流量產生器 VM 的公用 IP 位址 | 20.12.78.200 |
private_ip | VM 的私人 IP 位址 | 10.201.0.4 |
ssh_command | 用於連線至 VM 的 SSH 命令 | ssh azureuser@20.12.78.200 |
resource_group_name | 資源群組名稱 | rg-traffic-generator-dev-jsmith |
resource_group_id | 資源群組 Azure 資源 ID | /subscriptions/.../resourceGroups/rg-traffic-generator-dev-jsmith |
vm_name | 虛擬機器名稱 | vm-traffic-generator-dev-jsmith |
vm_id | 虛擬機器 Azure 資源 ID | /subscriptions/.../virtualMachines/vm-traffic-generator-dev-jsmith |
nsg_name | 網路安全性群組名稱 | nsg-traffic-generator-dev-jsmith |
nsg_id | 網路安全性群組 Azure 資源 ID | /subscriptions/.../networkSecurityGroups/nsg-traffic-generator-dev-jsmith |
vnet_name | 虛擬網路名稱 | vnet-traffic-generator-dev-jsmith |
subnet_id | 子網路 Azure 資源 ID | /subscriptions/.../subnets/snet-traffic-generator-dev-jsmith |
component | 元件識別碼 | traffic-generator |
environment | 部署環境 | dev |
location | Azure 區域 | eastus2 |
target_fqdn | 流量套件的已設定目標 FQDN | demo.example.com |
status_check | 用於檢查佈建狀態的命令 | ssh azureuser@20.12.78.200 cat /opt/traffic-generator/status.json |
擷取任何輸出:
terraform output -raw public_ipterraform output -raw ssh_command從上游元件串接
Section titled “從上游元件串接”流量產生器以位於來源伺服器前端的 F5 XC HTTP 負載平衡器為目標。請先部署來源伺服器、設定 F5 XC,然後將 target_fqdn 設定為負載平衡器的 FQDN:
# After deploying origin-server and creating an F5 XC HTTP load balancer:cat > terraform.tfvars <<EOFsubscription_id = "your-subscription-id"target_fqdn = "your-xc-load-balancer.example.com"target_origin_ip = "$(cd ../../origin-server/terraform && terraform output -raw public_ip)"EOF| 必填變數 | 來源 | 說明 |
|---|---|---|
target_fqdn | F5 XC 主控台 | 保護來源伺服器的 HTTP 負載平衡器 FQDN |
target_origin_ip | 來源伺服器 public_ip 輸出 | 用於繞過測試的直接來源 IP(選填) |