| layout | wallarm |
|---|---|
| page_title | Creating Rules from Hits |
| description | How to create false positive suppression rules from Wallarm hit data using Terraform. |
The hits-to-rules workflow creates false positive suppression rules directly from Wallarm hit data. Rules persist in Terraform state even after hits expire from the API.
Wallarm hits are ephemeral -- they have a retention period and can be dropped from the API at any time. The data.wallarm_hits data source should only be called once per request ID to perform the initial fetch. After that, the rules data must be cached in Terraform state so that subsequent plans do not re-fetch from the API. If hits have expired, re-fetching would return empty results and Terraform would destroy the rules.
This module handles this automatically: data.wallarm_hits is gated to only fetch new (uncached) request IDs, and terraform_data.cache persists the data in state permanently.
Two rule types are supported:
wallarm_rule_disable_stamp-- allows specific attack signatures (stamps) at a given request pointwallarm_rule_disable_attack_type-- allows specific attack types at a given request point
~> Note: xxe and invalid_xml attack types do not produce stamps. Hits of these types can only be suppressed via disable_attack_type rules. When filtering with rule_types = ["disable_stamp"], these attack types will not generate any rules -- use disable_attack_type or both rule types (default).
Copy the example from examples/hits-to-rules/. Find the request IDs of the false positive hits in the Wallarm Console (Attacks section). Add them to terraform.tfvars:
request_ids = {
"4666dee205d69757fd45cf94d2f0d7eb" = "{}"
}Then apply:
terraform applyThis creates the wallarm_hits_index, fetches the hits, caches the data, and creates the suppression rules.
Add new entries to terraform.tfvars and apply again. Only new (uncached) request IDs trigger API calls. Existing rules are preserved from state.
request_ids = {
"4666dee205d69757fd45cf94d2f0d7eb" = "{}"
"b7ae0af153248f1b1d4f3bea3cd5ea90" = "{}"
}Each request ID maps to a JSON config string. Use "{}" for defaults.
request_ids = {
# Default: request mode, all rule types, all attack types
"abc123" = "{}"
# Attack mode: expand to all related hits by attack_id
"def456" = "{\"mode\":\"attack\"}"
# Filter: only generate disable_stamp rules
"ghi789" = "{\"rule_types\":[\"disable_stamp\"]}"
# Filter: only create rules for sqli hits
"jkl012" = "{\"attack_types\":[\"sqli\"]}"
# Combined: attack mode, only xss and rce hits
"mno345" = "{\"mode\":\"attack\", \"attack_types\":[\"xss\",\"rce\"]}"
}| Key | Values | Default | Description |
|---|---|---|---|
mode |
request, attack |
request |
request fetches direct hits only. attack expands to all related hits sharing the same attack campaign. |
rule_types |
["disable_stamp"], ["disable_attack_type"] |
all types | Filter which rule types to generate. |
attack_types |
["sqli"], ["xss","rce"], etc. |
all standard types | Filter which attack types produce rules. In attack mode, also controls which types to fetch from the API. |
The module uses three components:
wallarm_hits_index-- tracks which request IDs have been fetched. Exposesready(false on first create, true after) andcached_request_ids(set of known IDs) for gating.data.wallarm_hits-- fetches hit data from the API. Gated bywallarm_hits_indexto only query new request IDs.terraform_data.cache-- stores theaggregatedoutput fromdata.wallarm_hitsper request_id withignore_changeson input. Data persists even after hits expire.
HCL locals then build a deduplicated map keyed by action_hash -- multiple request IDs sharing the same action (same host and path) are merged, with stamps unioned and new point groups added. Actions are stored separately to avoid duplication. Rules are expanded from this deduplicated map.
On subsequent plans, the data source is skipped for cached request IDs, and groups are read from terraform_data.cache state. This means:
- No API calls for previously fetched hits
- Rules survive even after hits expire from the API
- Adding new request IDs only fetches the new ones
On the very first apply with request IDs, wallarm_hits_index is created with ready=false (known at plan time via CustomizeDiff). This causes all request IDs to be fetched, cached, and rules created in a single apply. On subsequent applies, ready=true and only new request IDs are fetched.
Rule resources use for_each keys derived from hash prefixes:
wallarm_rule_disable_stamp.this["{action_hash}_{point_hash}_{attack_type}_{stamp}"]
wallarm_rule_disable_attack_type.this["{action_hash}_{point_hash}_{attack_type}"]
Where:
action_hash(16 hex chars) -- identifies the action scope (Host + path + pool). Derived from a SHA256 of the sorted action conditions (Ruby-compatibleConditionsHash).point_hash(16 hex chars) -- identifies the detection point (e.g., query parameter, header). Derived from a SHA256 of the point structure.stamporattack_type-- the specific signature or attack type being suppressed.
Example: wallarm_rule_disable_stamp.this["ed1d2ad7a1b2c3d4_48c0e969f1e2d3c4_6961"]
All groups are keyed by action_hash_point_hash_attack_type. Each stamp belongs to a specific attack type, so stamps are grouped per type for traceability.
Optionally generate standalone .tf files for reference or migration:
terraform apply -var='generate_configs=true'This uses wallarm_rule_generator with source = "rules" to write HCL files from the cached rules data.
Generated files include moved blocks that map from the for_each-based resources to standalone named resources. This lets you migrate without destroying and recreating rules.
Example generated output:
resource "wallarm_rule_disable_stamp" "fp_ed1d2ad7a1b2c3d4_48c0e969f1e2d3c4_6961" {
client_id = 8649
comment = "Managed by Terraform"
variativity_disabled = true
stamp = 6961
# ...
}
moved {
from = wallarm_rule_disable_stamp.this["ed1d2ad7a1b2c3d4_48c0e969f1e2d3c4_6961"]
to = wallarm_rule_disable_stamp.fp_ed1d2ad7a1b2c3d4_48c0e969f1e2d3c4_6961
}Migration steps:
-
Generate configs:
terraform apply -var='generate_configs=true' -
Copy generated files into your working directory:
cp ./generated_rules/*.tf .
-
Remove the
for_each-based resource blocks (wallarm_rule_disable_stamp.thisandwallarm_rule_disable_attack_type.this) frommain.tf. -
Verify with plan -- should show
movedoperations, no destroy/create:terraform plan
-
Apply:
terraform apply
-
Remove the
movedblocks from the generated files after one successful apply -- they are only needed for the migration.
Multiple request IDs may produce identical rules -- for example, when the same request was repeated multiple times generating hits with different request IDs but the same structure. Deduplication happens in HCL locals: groups from different terraform_data.cache entries sharing the same action_hash and detection point are merged. Stamps are unioned via distinct(flatten(...)). This means only one Terraform resource is created per unique rule regardless of how many request IDs produced it.
Different request IDs with different actions (different hosts or paths) produce separate groups and separate rules -- they are never merged.
This prevents drift loops where the Wallarm API would deduplicate identical rules, causing Terraform to detect changes on every plan.
Remove a request ID from terraform.tfvars and apply. Terraform will:
- Remove the ID from the
wallarm_hits_index - Destroy the corresponding
terraform_data.cacheentry - The deduplicated locals recompute -- if no other request ID references the same action, the rules are destroyed
| Variable | Type | Default | Description |
|---|---|---|---|
api_token |
string |
-- | Wallarm API token (sensitive) |
api_host |
string |
https://us1.api.wallarm.com |
Wallarm API endpoint |
client_id |
number |
null |
Client ID (uses provider default if null) |
request_ids |
map(string) |
{} |
Map of request_id to config JSON |
default_mode |
string |
request |
Default fetch mode |
include_instance |
bool |
true |
Include instance (pool ID) in action conditions. Set to false if your account excludes instance from actions. |
generate_configs |
bool |
false |
Generate HCL config files |
output_dir |
string |
./generated_rules |
Output directory for generated configs |
| Output | Description |
|---|---|
rules_created |
Count of rules by type and total |
rule_ids |
Map of rule key to Wallarm rule ID |