Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🤖 F5 BIG-IP LTM Bot Defense Profile Terraform Module

Manages a single bigip_ltm_profile_bot_defense resource — the Bot Defense profile a BIG-IP virtual server attaches to detect and mitigate automated/bot traffic. Targets F5Networks/bigip ~> 1.28.

Terraform Provider Module Type Resources Posture


🧩 Overview

  • 🤖 Manages exactly one bigip_ltm_profile_bot_defense resource — a single flat Bot Defense profile with no nested blocks and no for_each children.
  • 🎯 Seeds mitigation/verification defaults from a template (balanced, relaxed, strict).
  • 🚦 Sets enforcement posture explicitly via enforcement_mode (transparent monitor-only vs. blocking actively enforced).
  • 🔗 Inherits settings from a parent profile via defaults_from — defaults to the device-shipped /Common/bot-defense baseline, but can chain to a sibling custom profile instead.
  • 🧵 Emits name/id for full-path consumption by terraform-bigip-ltm-virtual-server.

💡 Why it matters: Bot Defense is one of the few LTM profile types where the default value materially changes the device's security posture. Leaving enforcement_mode (or template) null defers to whatever BIG-IP applies server-side — which may not be the deliberate "actively blocking" stance a production application needs. This module surfaces that decision as an explicit, typed input rather than letting it hide behind a silent default.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


🗺️ Where this fits

flowchart LR
 BASE["/Common/bot-defense (device-shipped baseline)"]:::ext
 THIS["terraform-bigip-ltm-profile-bot-defense"]:::this
 SAAS["terraform-bigip-saas-bot-defense-profile"]:::ext
 HTTP["terraform-bigip-ltm-profile-http"]:::sib
 CSSL["terraform-bigip-ltm-profile-client-ssl"]:::sib
 SSSL["terraform-bigip-ltm-profile-server-ssl"]:::sib
 TCP["terraform-bigip-ltm-profile-tcp"]:::sib
 VS["terraform-bigip-ltm-virtual-server"]:::target

 BASE -- "defaults_from (optional)" --> THIS
 THIS -- "name (full path)" --> VS
 HTTP -- "name (full path)" --> VS
 CSSL -- "name (full path)" --> VS
 SSSL -- "name (full path)" --> VS
 TCP -- "name (full path)" --> VS
 SAAS -. "separate family, not consumed here".-> VS

 classDef this fill:#E4002B,color:#ffffff,stroke:#333;
 classDef target fill:#000000,color:#ffffff,stroke:#333;
 classDef sib fill:#e0e0e0,color:#222222,stroke:#999;
 classDef ext fill:#f5f5f5,color:#222222,stroke:#999,stroke-dasharray: 3 3;
Loading

This module (red) owns only the Bot Defense profile record itself. terraform-bigip-ltm-virtual-server (black — the consuming target) attaches this profile, alongside sibling terraform-bigip-ltm-profile-* modules, by full-path name. terraform-bigip-saas-bot-defense-profile is a related but entirely separate module family wrapping F5's cloud/SaaS Bot Defense integration — it is never the same Terraform resource as this module.


🧬 What this builds

flowchart TB
 subgraph Inputs
 NAME["name"]
 DEF["defaults_from"]
 DESC["description"]
 TMPL["template"]
 ENF["enforcement_mode"]
 end

 RES["bigip_ltm_profile_bot_defense.this"]:::this

 NAME --> RES
 DEF --> RES
 DESC --> RES
 TMPL --> RES
 ENF --> RES

 RES -- "emits" --> OUT_NAME["output: name"]
 RES -- "emits" --> OUT_ID["output: id"]

 classDef this fill:#E4002B,color:#ffffff,stroke:#333;
Loading
Resource Count Notes
bigip_ltm_profile_bot_defense.this 1 Keystone; flat attribute set (name, defaults_from, description, template, enforcement_mode) — no nested blocks, no for_each children

✅ Provider / Versions

Requirement Value
Terraform >= 1.12.0
Provider F5Networks/bigip ~> 1.28 — re-verify against the current Terraform Registry listing before each new module wave (F5 ships frequent minor releases)
Provider block None — the caller configures provider "bigip" {}; this module never declares one

Schema notes that bite:

  • Full-path name is BIG-IP's real identity (/Partition/name). Renaming this profile or moving it to a different partition forces destroy/recreate, not an in-place update.
  • template and enforcement_mode are Optional+Computed with no documented literal default — leaving either null defers to whatever BIG-IP applies server-side, which this module does not control or predict.
  • description is likewise Optional+Computed when left null.
  • defaults_from defaults to /Common/bot-defense in this module because that matches the provider's own documented default — it is not an invented partition default.

🔑 Required BIG-IP User Role / Partition Access

Resource Administrator. Unlike most application-layer LTM profiles (where Manager scoped to the target partition is normally sufficient), a Bot Defense profile shapes device-wide bot-mitigation and request-enforcement posture — template-seeded detection/mitigation defaults and the transparent-vs-blocking enforcement decision — broadly enough that partition-scoped Manager access is insufficient. Provision this role deliberately, not as a default grant.

F5 BIG-IP Prerequisites

  • iControl REST enabled and reachable on the target device.
  • TMOS >= v12.1.1 (provider floor per house standard).
  • Bot Defense is an ASM/Bot-Defense-provisioned feature — the target device must have the relevant security module provisioned before this profile has any practical mitigation effect. This authoring pass verified the Terraform-facing argument schema only; confirm the exact minimum TMOS version and provisioning requirement for your target release against clouddocs.f5.com before relying on this in production — that device-side provisioning prerequisite is not something terraform validate or the provider schema can surface.
  • Target partition must already exist before this module runs.
  • The parent profile named in defaults_from (default /Common/bot-defense, which ships built-in) must already exist on the device — any custom parent must be created and verified first.

📁 Module Structure

terraform-bigip-ltm-profile-bot-defense/
├── providers.tf # required_version, F5Networks/bigip ~> 1.28 pin — no provider block
├── variables.tf # name, defaults_from, description, template, enforcement_mode
├── main.tf # bigip_ltm_profile_bot_defense.this — flat keystone, no children
├── outputs.tf # name, id
├── SCOPE.md # cross-module contract (this doc's source of truth for role/prereqs)
└── README.md # this file

⚙️ Quick Start

# Caller configures the provider — this module never does.
provider "bigip" {
  address  = var.bigip_address
  username = var.bigip_username
  password = var.bigip_password
}

module "bot_defense_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/app01-bot-defense"
  enforcement_mode = "blocking"
}

🔌 Cross-Module Contract

Consumes

Input Type Source module
defaults_from (optional) string (full-path name) /Common/bot-defense (device-shipped baseline, module default) or a sibling terraform-bigip-ltm-profile-bot-defense instance

Emits

Output Description Consumed by
name Full-path name of the Bot Defense profile (e.g. /Common/my-bot-defense-profile) — the practical cross-reference key terraform-bigip-ltm-virtual-server
id Provider-internal id of the Bot Defense profile rarely consumed directly

📚 Example Library

1 · Minimal blocking profile

The smallest real call for a production posture — explicit enforcement_mode rather than relying on whatever BIG-IP defaults to server-side.

module "bot_defense_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/app01-bot-defense"
  enforcement_mode = "blocking"
}

⚠️ Omitting enforcement_mode (leaving it null) defers to a BIG-IP-side default this module does not control — set it explicitly for any profile meant to actively block bot traffic.

2 · Transparent (monitor-only) staging profile

Rolling out Bot Defense against a new application without yet blocking traffic — observe detections before flipping to enforcement.

module "bot_defense_staging" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/app02-bot-defense-staging"
  enforcement_mode = "transparent"
  description      = "Staged rollout — monitor only, no blocking action taken"
}

ℹ️ transparent mode does not block detected bot traffic. Use this deliberately for a staged/observe-first rollout, not as a lasting production posture.

3 · Strict template with blocking enforcement

Highest sensitivity mitigation defaults, actively enforced — appropriate for a high-value/high-risk application surface.

module "bot_defense_strict" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/payments-bot-defense"
  template         = "strict"
  enforcement_mode = "blocking"
  description      = "Payments API — strict template, blocking enforcement"
}
4 · Relaxed template for a low-risk internal app

An internal-only application where false positives are more costly than a lighter mitigation touch.

module "bot_defense_internal" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/intranet-bot-defense"
  template         = "relaxed"
  enforcement_mode = "blocking"
}
5 · Explicit balanced template

Documenting the middle-ground posture explicitly rather than leaving template unset and inheriting whatever BIG-IP defaults to.

module "bot_defense_balanced" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/portal-bot-defense"
  template         = "balanced"
  enforcement_mode = "blocking"
}

💡 Setting template explicitly — even to the middle-ground "balanced" value — documents the intended posture in code instead of leaving it to whatever the device happens to default to.

6 · Custom description for change-audit trail

Using the free-text description field to record ownership/change context for reviewers.

module "bot_defense_audited" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/checkout-bot-defense"
  enforcement_mode = "blocking"
  description      = "Owned by Checkout team — CR-4821 — blocking since 2026-05-01"
}
7 · Chaining defaults_from to a sibling custom profile

A hardened base profile that other application-specific profiles inherit from, instead of every profile inheriting directly from the system-shipped /Common/bot-defense baseline.

module "bot_defense_base" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/org-bot-defense-base"
  template         = "strict"
  enforcement_mode = "blocking"
}

module "bot_defense_app" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/app03-bot-defense"
  defaults_from    = module.bot_defense_base.name
  enforcement_mode = "blocking"
}

💡 Wiring defaults_from to module.bot_defense_base.name (rather than a literal string) lets Terraform's implicit dependency graph order creation correctly — the base profile before the one that inherits from it.

8 · Non-/Common partition placement

Placing the profile in a tenant-scoped partition rather than the default /Common.

module "bot_defense_tenant_a" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Tenant-A/app-bot-defense"
  enforcement_mode = "blocking"
}

🔒 The target partition (/Tenant-A here) must already exist before this module runs — this module does not create partitions, only the profile inside one.

9 · Overriding defaults_from away from the system baseline

Pointing at a pre-existing, already-hardened custom parent profile instead of the device-shipped /Common/bot-defense default.

module "bot_defense_custom_parent" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/app04-bot-defense"
  defaults_from    = "/Common/org-hardened-bot-defense"
  enforcement_mode = "blocking"
}

⚠️ The full path supplied to defaults_from must already exist on the device (or be created by another Terraform resource ordered ahead of this one) — this module only references it by name.

10 · Strict template paired with transparent mode

High-fidelity detection signal without yet enforcing — useful when validating a strict template's false-positive rate before switching to blocking.

module "bot_defense_strict_observe" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/app05-bot-defense-observe"
  template         = "strict"
  enforcement_mode = "transparent"
  description      = "Validating strict template false-positive rate before blocking cutover"
}
11 · Caller-side for_each over multiple profiles

This module owns a single flat profile; a fleet of profiles is composed by the caller, not by for_each inside this module.

locals {
  bot_defense_profiles = {
    app06 = { name = "/Common/app06-bot-defense", enforcement_mode = "blocking" }
    app07 = { name = "/Common/app07-bot-defense", enforcement_mode = "transparent" }
  }
}

module "bot_defense_fleet" {
  source   = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"
  for_each = local.bot_defense_profiles

  name             = each.value.name
  enforcement_mode = each.value.enforcement_mode
}

ℹ️ Keying for_each on a stable map key (app06, app07) — never a list index — means adding a seventh profile never forces Terraform to touch the first two.

12 · Wiring the emitted name into a virtual server (illustrative)

Illustrates the cross-module contract — this module's name output is the value terraform-bigip-ltm-virtual-server consumes; it does not create the virtual server itself.

module "bot_defense_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/app08-bot-defense"
  enforcement_mode = "blocking"
}

# Illustrative only — see terraform-bigip-ltm-virtual-server's own README for its real variable
# shape. The point of this example is the cross-reference, not the virtual-server call itself.
# virtual_server_profiles = [module.bot_defense_profile.name]
13 · Explicit posture documented via description + template + mode together

Combining all three optional posture-shaping fields deliberately, rather than leaving any of them to defer to a BIG-IP-side default.

module "bot_defense_documented" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/app09-bot-defense"
  template         = "balanced"
  enforcement_mode = "blocking"
  description      = "Balanced template, blocking enforcement — approved 2026-06-30, ticket CR-5010"
}
🏗️ 14 · End-to-end composition

A base hardened profile, an application-specific profile chained from it, and the illustrative hand-off to the virtual-server module that ultimately attaches it — the full path from this module's output to its real-world consumer.

provider "bigip" {
  address  = var.bigip_address
  username = var.bigip_username
  password = var.bigip_password
}

# 1. Org-wide hardened base profile — strict template, blocking, /Common.
module "bot_defense_base" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/org-bot-defense-base"
  template         = "strict"
  enforcement_mode = "blocking"
  description      = "Org-wide hardened Bot Defense baseline — all app profiles inherit from this"
}

# 2. Application-specific profile chained from the hardened base.
module "bot_defense_checkout" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0"

  name             = "/Common/checkout-bot-defense"
  defaults_from    = module.bot_defense_base.name
  enforcement_mode = "blocking"
  description      = "Checkout app — inherits org hardened baseline"
}

# 3. Sibling LTM profiles the virtual server also attaches (each its own module family).
module "checkout_http_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http.git?ref=v1.0.0"
  name   = "/Common/checkout-http"
}

# 4. The virtual server that ties everything together — illustrative shape only; see
# terraform-bigip-ltm-virtual-server's own README for its real variable contract.
# module "checkout_vs" {
# source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-virtual-server.git?ref=v1.0.0"
#
# name = "/Common/checkout-vs"
# destination = "10.20.30.40:443"
# profiles = [
# module.bot_defense_checkout.name,
# module.checkout_http_profile.name,
# ]
# }

💡 The base-then-app chain (module.bot_defense_base.namedefaults_from) is the pattern to reach for whenever more than one application needs the same hardened Bot Defense posture — author the shared baseline once, then chain application-specific overrides from it.


📥 Inputs

Variable Type Default Required Description
name string yes Full-path name of the Bot Defense profile, e.g. /Common/my-bot-defense-profile
defaults_from string "/Common/bot-defense" no Full-path name of the parent profile this profile inherits from
description string null no Free-text description; provider-computed if left null
template string null no One of balanced, relaxed, strict; provider-computed if left null
enforcement_mode string null no One of transparent, blocking; provider-computed if left null
Full variable definitions

The live bigip_ltm_profile_bot_defense schema is a flat attribute set with no nested blocks, so there are no object-typed fields in this module — every variable below is a scalar string.

variable "name" {
  type = string
  # Immutable in practice: BIG-IP's full-path identity is partition-specific, so renaming or
  # moving this profile between partitions forces destroy/recreate rather than an in-place update.
}

variable "defaults_from" {
  type    = string
  default = "/Common/bot-defense"
  # Provider default: the system-supplied "/Common/bot-defense" profile — baked in here because
  # it matches the provider's own documented default.
}

variable "description" {
  type    = string
  default = null
  # Provider-computed if left null; no literal default value is documented.
}

variable "template" {
  type    = string
  default = null
  # One of: "balanced", "relaxed", "strict". Provider-computed if left null.
  validation {
    condition     = var.template == null ? true : contains(["balanced", "relaxed", "strict"], var.template)
    error_message = "template must be one of: balanced, relaxed, strict."
  }
}

variable "enforcement_mode" {
  type    = string
  default = null
  # One of: "transparent", "blocking". Provider-computed if left null.
  validation {
    condition     = var.enforcement_mode == null ? true : contains(["transparent", "blocking"], var.enforcement_mode)
    error_message = "enforcement_mode must be one of: transparent, blocking."
  }
}

🧾 Outputs

Output Description Sensitive / Conditional
name Full-path name of the Bot Defense profile, e.g. /Common/my-bot-defense-profile. Consumed by full-path name from terraform-bigip-ltm-virtual-server. no
id Provider-internal id of the Bot Defense profile. no

🧠 Architecture Notes

  • main.tf is a total, unconditional passthrough: every variable maps directly onto the matching bigip_ltm_profile_bot_defense.this argument — no dynamic blocks, no try(x, null) guards, because the live schema has no nested/repeating structure to guard against.
  • There is no for_each in this module because the resource owns no child collection; a fleet of profiles is a caller-side composition concern (see Example 11).
  • Ordering: when defaults_from chains to a sibling instance of this same module (Example 7), wire it via module.<base>.name rather than a literal string — this lets Terraform's implicit dependency graph create the base profile before the one that inherits from it.
  • Full-path identity is immutable. name's partition segment is part of BIG-IP's real identity; changing it is a destroy/recreate, not an in-place rename, for both this profile and anything referencing its old full path.
  • No AS3/DO involvement — this is a discrete LTM resource, not a declarative-JSON escape hatch.

🧱 Design Principles

Concern Secure default in this module Opt-out (caller must type extra)
Enforcement posture enforcement_mode is left null (provider-computed) rather than the module inventing a hardcoded default — the README and variable description flag that a null value does not guarantee a blocking stance Caller sets enforcement_mode = "blocking" explicitly for a deliberate production enforcement posture (recommended for anything internet-facing)
Mitigation template template is left null (provider-computed); the module never silently picks a template on the caller's behalf Caller sets template explicitly (balanced/relaxed/strict) to document a deliberate posture
Parent profile / partition scope defaults_from defaults to /Common/bot-defense only because that matches the provider's own documented default — no invented partition default Caller supplies a custom defaults_from full path (own partition or a hardened custom parent)
Secrets N/A — this resource's live schema exposes no secret-shaped field

🚀 Runbook

cd C:\GitHubCode\newf5modules\bigip\terraform-bigip-ltm-profile-bot-defense
terraform init -backend=false
terraform validate
terraform fmt -check

Pin ?ref=v1.0.0 (or the current release tag) whenever consuming this module via source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-bot-defense.git?ref=v1.0.0".


🧪 Testing

terraform init -backend=false && terraform validate && terraform fmt -check is a plan-only, schema-level proof gate — it confirms the .tf files are internally consistent (types, required_version, provider pin, closed-enum validation{} blocks) without ever contacting a real device.

What it does not prove, and what only a human-run terraform plan/apply against a real BIG-IP (from CI, never from this authoring process) can:

  • That the defaults_from parent profile actually exists on the target device/partition.
  • That ASM/Bot Defense is provisioned on the target device, so the profile has a real mitigation effect rather than merely existing as a configuration object.
  • That the applying credential actually holds Resource Administrator rights.

💬 Example Output

$ terraform apply

 # module.bot_defense_profile.bigip_ltm_profile_bot_defense.this will be created
 + resource "bigip_ltm_profile_bot_defense" "this" {
 + defaults_from = "/Common/bot-defense"
 + enforcement_mode = "blocking"
 + id = (known after apply)
 + name = "/Common/app01-bot-defense"
 }

Plan: 1 to add, 0 to change, 0 to destroy.

Outputs:

id = "/Common/app01-bot-defense"
name = "/Common/app01-bot-defense"

🔍 Troubleshooting

Symptom Cause Fix
defaults_from reference not found at apply Custom parent Bot Defense profile hasn't been created/applied yet Ensure the parent module/resource applies first, or wire it via a module output (module.<base>.name) so Terraform's dependency graph orders it correctly
Profile creates successfully but has no mitigation effect ASM/Bot Defense is not provisioned on the target device Provision ASM/Bot Defense on the BIG-IP before relying on enforcement — see F5 BIG-IP Prerequisites
Renaming this profile forces an unexpected destroy/recreate Full-path name (partition + name) is immutable BIG-IP identity, not a mutable label Treat name/partition as effectively write-once; cut over to a new resource instead of an in-place rename
template must be one of: balanced, relaxed, strict validation error Caller passed an unsupported template string Use one of the three documented values, or leave template unset (null)
Virtual server can't resolve this profile's full path Wrong partition or a typo in the literal string passed downstream Confirm the full path including partition; wire the virtual server's profile list from this module's name output rather than a hand-typed literal

🔗 Related Docs

  • F5Networks/bigip provider — bigip_ltm_profile_bot_defense resource (Terraform Registry).
  • clouddocs.f5.com — Bot Defense feature overview and ASM/Bot-Defense provisioning guidance.
  • Sibling modules: terraform-bigip-ltm-virtual-server (consumer), terraform-bigip-saas-bot-defense-profile (related but separate cloud/SaaS Bot Defense family), terraform-bigip-waf-policy (related ASM family).
  • This module's SCOPE.md.

Releases

Packages

Contributors

Languages