Aller au contenu

Déploiement

Tous les fichiers Terraform se trouvent dans le répertoire terraform/. Clonez le dépôt et déployez directement :

Fenêtre de terminal
git clone https://github.com/f5-sales-demo/traffic-generator.git
cd traffic-generator/terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your Azure subscription ID and target FQDN

Le répertoire terraform contient 9 fichiers conformes au standard des Ressources de démonstration :

  • versions.tf — Contraintes de versions pour Terraform et les fournisseurs (azurerm ~> 4.0, azuread ~> 3.0)
  • providers.tf — Configuration des fournisseurs Azure RM et Azure AD
  • data.tf — Sources de données Azure AD pour la résolution automatique du déployeur
  • locals.tf — Résolution du déployeur, nommage des ressources selon le Cloud Adoption Framework Azure, balises standard
  • main.tf — Groupe de ressources (nommé rg-traffic-generator-{environment}-{deployer})
  • variables.tf — Toutes les variables d’entrée (2 obligatoires, 9 optionnelles) avec résolution automatique du déployeur via Azure AD
  • network.tf — VNet (10.201.0.0/16), sous-réseau, IP publique, NSG (port 22 SSH), NIC
  • vm.tf — VM Ubuntu 24.04 avec cloud-init via templatefile()
  • outputs.tf — 17 sorties : 15 sorties standard partagées par toutes les ressources de démonstration (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) ainsi que 2 sorties spécifiques au composant (target_fqdn, status_check)

main.tf configure le fournisseur Azure :

resource "azurerm_resource_group" "main" {
name = local.name.resource_group
location = var.location
tags = local.tags
}

variables.tf définit tous les paramètres configurables. La valeur par défaut de vm_size est Standard_F16s_v2 (16 vCPU à calcul optimisé) — voir Dimensionnement de la VM pour les recommandations de mise à l’échelle :

# ---------------------------------------------------------
# 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\"."
}
}

network.tf crée le VNet, le sous-réseau, l’IP publique, le NSG (port 22 SSH) et le 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
}

vm.tf crée la VM Ubuntu 24.04 et transmet le modèle cloud-init avec les variables cibles et d’outillage :

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.yaml est la configuration validée en production, vérifiée au cours de plus de 48 heures de tests de charge continue, incluant 4 cycles de tests de torture de l’origine. Il utilise une bibliothèque d’assistance avec logique de réessai, journalisation de la progression vers /var/log/cloud-init-progress.log, 9 phases de provisionnement et un rapport de statut conditionnel (ready/degraded dans status.json). Il provisionne :

  • Réglage du noyausomaxconn=131072, tcp_max_tw_buckets=4000000, tcp_fin_timeout=5, tampons de socket de 64 MiB, file-max=4194304
  • Optimisation NIC — RPS/RFS sur tous les cœurs, tampons en anneau maximisés via ethtool, THP=always
  • Paquets APT — nikto, nmap, masscan, sqlmap, hydra, medusa, ncrack, tshark, hping3, tcpdump, netcat, ngrep, iperf3, mtr, sslscan, socat, dirb, whatweb, wrk, hey, vegeta, ethtool
  • Versions GitHub — nuclei, dalfox, ffuf, gobuster, feroxbuster, subfinder, httpx, amass
  • Paquets Python — mitmproxy, sslyze, dnsrecon, fierce, theHarvester, playwright, scapy, impacket, arjun
  • Node.js 20.x — Puppeteer avec plugin stealth pour la simulation de bots
  • Automatisation du navigateur — Playwright Chromium pour les tests de navigateur sans interface graphique
  • Clones Git — testssl.sh, recon-ng, spiderfoot, SecLists, waf-bypass, PayloadsAllTheThings
  • Déploiement des suites — 19 suites de trafic clonées depuis ce dépôt vers /opt/traffic-generator/suites/
  • Fichier de configurationconfig.env avec TARGET_FQDN, TARGET_ORIGIN_IP et CRAPI_PORT

Le cloud-init utilise les variables de modèle Terraform : ${target_fqdn}, ${target_origin_ip} et ${tool_tier}. Les variables shell telles que $NPROC sont échappées en $$NPROC dans le templatefile Terraform.

#cloud-config
package_update: true
package_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 expose 17 sorties incluant l’IP publique/privée, la commande SSH, les identifiants de ressources et les valeurs spécifiques au composant (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"
}

Copiez terraform.tfvars.example vers terraform.tfvars et renseignez vos valeurs. Le fichier .gitignore exclut terraform.tfvars pour éviter la validation accidentelle des identifiants :

# 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"

Remplacez subscription_id par votre identifiant d’abonnement Azure (obtenu via az account show --query id -o tsv) et target_fqdn par votre domaine d’équilibreur de charge F5 XC.

Fenêtre de terminal
terraform init
terraform plan
terraform apply

Terraform affiche l’IP publique, la commande SSH et le FQDN cible après un déploiement réussi.

Les VM à calcul optimisé de série F sont recommandées pour cette charge de travail de génération de trafic intensive en CPU. Le réglage du noyau cloud-init et la concurrence des outils se mettent à l’échelle automatiquement en fonction du nombre de vCPU.

Référence VMvCPURAMRéseauCas d’utilisation
Standard_F4s_v248 Gio10 GbpsLab léger/démonstration
Standard_F8s_v2816 Gio12,5 GbpsDémonstrations standard
Standard_F16s_v21632 Gio12,5 GbpsTests de charge (défaut)
Standard_F32s_v23264 Gio16 GbpsBenchmarking intensif
Standard_D16s_v31664 Gio12,5 GbpsCharges de travail gourmandes en RAM

Le provisionnement cloud-init prend 15 à 20 minutes pour le niveau standard et environ 25 minutes pour le niveau complet. La VM est accessible via SSH immédiatement, mais les outils sont encore en cours d’installation jusqu’à la fin de cloud-init.

Connectez-vous en SSH à la VM et attendez la fin du provisionnement :

Fenêtre de terminal
ssh azureuser@$(terraform output -raw public_ip)
cloud-init status --wait

Sortie attendue à la fin :

status: done

Le script cloud-init écrit un fichier de statut à la fin de l’exécution :

Fenêtre de terminal
cat /opt/traffic-generator/status.json

Sortie attendue :

{
"status": "ready",
"tier": "standard",
"target_fqdn": "demo.example.com",
"completed": "2026-04-25T14:30:00Z",
"tools_installed": true
}

Exécutez une boucle de vérification pour confirmer que chaque outil critique est disponible dans le PATH :

Fenêtre de terminal
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"
fi
done

Confirmez que le lanceur et les suites sont installés :

Fenêtre de terminal
ls /opt/traffic-generator/suites/runner.sh
ls /opt/traffic-generator/suites/*/
/opt/traffic-generator/suites/runner.sh web-app-attacks --dry-run
SortieDescriptionExemple
deployerIdentifiant de déployeur résolujsmith
public_ipAdresse IP publique de la VM du Générateur de trafic20.12.78.200
private_ipAdresse IP privée de la VM10.201.0.4
ssh_commandCommande SSH pour se connecter à la VMssh azureuser@20.12.78.200
resource_group_nameNom du groupe de ressourcesrg-traffic-generator-dev-jsmith
resource_group_idIdentifiant de ressource Azure du groupe de ressources/subscriptions/.../resourceGroups/rg-traffic-generator-dev-jsmith
vm_nameNom de la machine virtuellevm-traffic-generator-dev-jsmith
vm_idIdentifiant de ressource Azure de la machine virtuelle/subscriptions/.../virtualMachines/vm-traffic-generator-dev-jsmith
nsg_nameNom du groupe de sécurité réseaunsg-traffic-generator-dev-jsmith
nsg_idIdentifiant de ressource Azure du groupe de sécurité réseau/subscriptions/.../networkSecurityGroups/nsg-traffic-generator-dev-jsmith
vnet_nameNom du réseau virtuelvnet-traffic-generator-dev-jsmith
subnet_idIdentifiant de ressource Azure du sous-réseau/subscriptions/.../subnets/snet-traffic-generator-dev-jsmith
componentIdentifiant du composanttraffic-generator
environmentEnvironnement de déploiementdev
locationRégion Azureeastus2
target_fqdnFQDN cible configuré pour les suites de traficdemo.example.com
status_checkCommande pour vérifier le statut du provisionnementssh azureuser@20.12.78.200 cat /opt/traffic-generator/status.json

Récupérez n’importe quelle sortie :

Fenêtre de terminal
terraform output -raw public_ip
terraform output -raw ssh_command

Le Générateur de trafic cible un équilibreur de charge HTTP F5 XC qui protège le Serveur d’origine. Déployez d’abord le Serveur d’origine, configurez F5 XC, puis définissez target_fqdn sur le FQDN de l’équilibreur de charge :

Fenêtre de terminal
# After deploying origin-server and creating an F5 XC HTTP load balancer:
cat > terraform.tfvars <<EOF
subscription_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
Variable obligatoireSourceDescription
target_fqdnConsole F5 XCFQDN de l’équilibreur de charge HTTP protégeant le Serveur d’origine
target_origin_ipSortie public_ip du Serveur d’origineIP d’origine directe pour les tests de contournement (optionnel)

Passez au Catalogue d’outils pour la référence complète des outils ou aux Suites pour commencer à générer du trafic.