redteamer logo

Module

HCL Fundamentals

Understand how redteamer’s HCL-based language works. This guide covers syntax, blocks, expressions, operators, and functions that form the foundation of every module.


Overview

redteamer uses HCL (HashiCorp Configuration Language) to describe modules. Each .rt.hcl file is a structured configuration made of blocks, arguments, and expressions.

  • A block declares something
  • An argument assigns a value
  • An expression defines or computes that value

Block Structure

Every block follows this form:

<BLOCK TYPE> "<BLOCK LABEL>" {
  <NAME> = <VALUE>
}

Example:

action "scan_dir" {
  with     = "core/tools/gobuster/dir.rt.hcl"
  base_url = "http://192.168.0.155/"
  wordlist = "/usr/share/wordlists/dirb/common.txt"
}

In this example:

  • action is the block type
  • "scan_dir" is the label
  • The body defines its arguments

Arguments

Arguments assign values to names inside a block.

target  = "127.0.0.1"
threads = 10
enabled = true

Identifiers must start with a letter and may include letters, digits, underscores, and hyphens.


Meta Arguments

Meta arguments control repetition and conditional execution.

Meta argumentDescription
for_eachCreates one block per element in a collection
countCreates a specific number of block instances
whenRuns the block only when the condition is true

Example

action "scan_dir" {
  for_each = toset(["192.168.0.10", "192.168.0.11"])
  with     = "core/tools/gobuster/dir.rt.hcl"
  base_url = "http://${each.value}/"
  when     = var.enable_scanning
}

Types and Values

Expressions generate values. Each value has a type that defines how it behaves.

TypeExampleDescription
string"admin"Text value
filepath"/etc/passwd"File system path
number42Integer or float
booltrueBoolean value
list / tuple["one", "two"]Ordered sequence
map / object{ key = "value" }Key-value pairs
nullnullNo value

Expressions

Expressions compute or transform values. They can be literal, interpolated, or function-based.

Literals

target  = "10.0.0.5"
timeout = 30
enabled = true

Interpolation

Insert variables or computed values into strings using ${}.

url = "http://${var.host}:${var.port}/"

Arithmetic and Logic

threads     = 2 * 4
should_run  = var.is_admin && !var.is_blocked

Operators

Arithmetic

OperatorMeaning
+Addition
-Subtraction
*Multiplication
/Division
%Modulo
sum = 10 + 5

Comparison

OperatorMeaning
==Equal
!=Not equal
<Less than
<=Less than or equal
>Greater than
>=Greater than or equal
is_greater = 10 > 5

Logical

OperatorMeaning
&&AND
||OR
!NOT
should_run = var.enabled && !var.error

Accessing Collection Elements

Use indices or keys to access collection members.

Lists

ips = ["10.0.0.1", "10.0.0.2"]
first = ips[0]

Maps

services = {
  http  = 80
  https = 443
}
tls_port = services["https"]

References

References connect values between blocks. Use dot notation to access attributes.

output "loot" {
  value = action.exfil.results
}

Cross-block examples

output "urls" {
  value = action.web_scan.results
}

Blocks created with for_each or count can be accessed by key or index.

output "scan_one" {
  value = action.web_scan["192.168.0.10"].results
}

Strings and Templates

Quoted Strings

Use " and escape sequences.

msg = "Scan complete for ${var.target}"

Heredoc Strings

For multi-line content:

report = <<-EOT
Scan Report
-----------
Target: ${var.target}
Status: Success
EOT

Escape Sequences

SequenceMeaning
\nNew line
\tTab
\\Backslash
\"Double quote
\uNNNNUnicode
$${Escape interpolation

Conditional Expressions

Use the ternary form to choose between values.

access_level = var.is_admin ? "Full Access" : "Restricted"

For Expressions

Build lists or maps by iterating over collections.

# List result
urls = [for ip in var.targets : "http://${ip}/"]

# Filtered list
active = [for h in var.hosts : h if h.is_active]

# Map result
ports_by_name = { for s in var.services : s.name => s.port }

You can group attributes with ... to merge objects.

users_by_role = { for user in var.users : user.role => user... }

Splat Expressions

Splat expressions extract a single field from each element of a list.

usernames = var.logins[*].username

When a block uses count, you can collect all results with a splat:

action "scan" {
  count  = 2
  with   = "core/tools/nmap/scan.rt.hcl"
  target = "10.0.0.${count.index + 1}"
}

all_results = action.scan[*].results

If the source is a map (from for_each), wrap it with values() before splatting:

results = values(action.scan)[*].results

Flatten nested lists using col::flatten():

all_files = col::flatten(values(action.exfil)[*].files)

Functions

Functions compute or transform values. Call them with parentheses and comma-separated arguments.

timeout = num::max(5, var.default_timeout + 10)
CategoryExamplePurpose
Stringstr::upper("redteamer")String operations
Numericnum::min(3, 7)Math operations
Collectioncol::length(var.targets)Collection utilities
Validationval::url(var.target)Input checks
Systemsys::arch()System info

Argument Expansion

Use ... to unpack lists.

scores = [2, 3, 4]
lowest = num::min(scores...)

Comments

Use #, //, or /* */.

# single line
// another
/* multi-line */

File Encoding and Line Endings

  • Files must be UTF-8 encoded
  • LF endings are preferred, but CRLF is accepted