Splunk Attack Range v5: a two-phase build, mounted cloud credentials, and a manifest stuck at 3.1.2
A tool that allows you to create vulnerable instrumented local or cloud environments to simulate attacks against and collect the data into Splunk
At a glance
- What is it?
- Splunk Attack Range builds instrumented AWS, Azure and GCP labs with Terraform and Ansible, simulates adversary activity in them and forwards the telemetry into Splunk for detection work. It is a lab builder, not a scanner: every range is something you create yourself and pay for yourself, and the page is clearer about that than most tools in this category. What it does not do is keep its own metadata straight.
- Who is it for?
- Use this project if you own the cloud account, you are entitled to run adversary emulation in it, and you want telemetry to test detections against. Do not point it at infrastructure you do not control, and read the credential section first, because the documented setup mounts your own cloud credential directories into the containers.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The first command block has a placeholder and a directory that does not match the repository
The preferred path on the page is Docker Compose, and it opens with three lines:
git clone <repo-url>
cd attack_range_2
docker compose -f docker/docker-compose.yml upTwo things in that block are wrong on their face. The clone URL is a literal placeholder, `<repo-url>`, never filled in with the actual address. And the directory it tells you to enter is `attack_range_2`, while the repository this page describes is `attack_range` and the compose file it references lives under `docker/`. Anyone following the block verbatim gets an error at the `cd`, or worse, ends up in a directory of that name from somewhere else.
Once running, the block pays off: the web app is on port 4321 and the REST API on port 4000 with interactive docs at `/openapi/swagger`, so the documented promise of no local Python, Ansible or Terraform is real. It is just that the first line of the instructions does not work as written.
The build is a two-phase handshake that waits for a human on the VPN
Building a range is not one call. You pick a template, for example `aws/splunk_minimal_aws`, and start the build; the run then stops at a status called Waiting for VPN. At that point you download the WireGuard configuration, connect with WireGuard, and the build continues. The API version of the same dance is spelled out: `POST /attack-range/build` with a template, poll `GET /attack-range/status/<id>`, use the returned WireGuard config, connect, then `POST /attack-range/build` again with only the `attack_range_id`.
That second call carries nothing but an identifier, which is the point. The configuration phase cannot run until a human is inside the network, so the tool has a real admission step rather than a flag that claims authorisation. It is also why sharing matters as a first-class feature: additional client configs are generated so a colleague can attach to the same range.
The whole mechanism assumes the range is yours. Nothing in the page describes a target outside the account whose credentials you supplied, and there is no scan mode.
Your host cloud credentials are mounted into the containers
The credential instruction is one sentence in the quick reference: set up `~/.aws`, `~/.azure` or `~/.config/gcloud` and mount them into the containers, with the actual mounting left to `docker/docker-compose.yml`. Terraform and the provider SDKs then act as you, against your account, for as long as the range exists.
That is the security surface of this project in one line, and it deserves attention before a first run rather than after. The containers are given a live credential directory, not a scoped service account created for the range, and the page does not say which credentials the compose file mounts or whether they can be narrowed. If you keep production credentials in those directories, the lab inherits them.
The rest of the quick reference is about layout rather than access: each range gets its own config file at `config/<attack_range_id>.yml`, and the templates are organised by cloud under `templates/{aws,azure,gcp}/`. So a range is identified by an id that names its own configuration, and every Terraform and Ansible artefact hangs off that.
The packaging manifest still says version 3.1.2 while the page is v5
The repository is titled Splunk Attack Range v5, the most recent tag is v5.0.0 from 2026-02-09, and the default branch is `develop` rather than a release branch. The build metadata says something else:
[tool.poetry]
name = "attack-range"
version = "3.1.2"
description = ""A v3 version string, two majors behind the tags, and an empty description field. The rest of that file is also in the older Poetry layout rather than the standard project table, with dependencies under `[tool.poetry.dependencies]` and dev tools under `[tool.poetry.dev-dependencies]`, and it declares a source named pypi-public pointing at `https://pypi.org/simple/`. The listed author address is at splunk.com.
None of this stops the software from running, and it is a plausible leftover from a migration. But if you are pinning the package, the number in the manifest is the number a resolver sees, and it is not the number on the page.
pyproject.toml and requirements.txt disagree about Ansible and the Azure SDKs
Two dependency declarations ship in the repository and they do not agree. The manifest constrains `ansible = "<10.0.0"` while the requirements file pins `ansible==14.3.1` with `ansible-core==2.21.3`. The Azure packages are the same story: `azure-mgmt-compute = "^33.0.0"` against `azure-mgmt-compute==38.3.0`, `azure-mgmt-network = "^27.0.0"` against `32.0.0`, `azure-mgmt-resource = "^23.1.1" against `26.0.0`, and `azure-identity = "^1.18.0" against `1.25.3`. The Google Cloud packages go the other way, as unpinned asterisks in the manifest and exact versions in the requirements file.
So the two files differ on version policy as well as on versions: carets, wildcards and exact pins for the same project across two files, with an Ansible constraint that is not merely loose but incompatible with what the other file installs.
The practical consequence is that which file your install path reads decides what you get. The Docker route builds inside an image, so it resolves one of them, and a local install resolves the other.
The web app and API dependencies appear only in the requirements file
The page makes the web app on port 4321 and the REST API on port 4000 the primary interface. The Poetry manifest, however, does not list a single web dependency: no Flask, no OpenAPI layer, no CORS handler, no prompt or terminal toolkit. All of those appear in the requirements file instead, which carries `flask==3.1.3`, `flask-openapi3==4.3.2`, `flask-cors==6.0.5`, `prompt-toolkit==3.0.53`, `pydantic==2.13.4`, `msal==1.38.0`, `pycryptodome==3.23.0` and `xmltodict==1.0.4`, among others.
Installing from the manifest alone therefore gives you a library with no interface, and the documented Docker path is the one that actually produces the two ports the page advertises.
There is a third clue about the interface stack. The repository root holds a `package-lock.json`, which belongs to a JavaScript build, and directories named both `app/` and `apps/`. Nothing on the page mentions Node, a frontend build step or which of those two directories is current, so the interface layer is the part of the layout you will have to read the tree to understand.
A pytest marker exists for tests that deploy real cloud infrastructure
The test configuration registers one marker, and its description is the sentence to read twice:
markers = [
"e2e: end-to-end tests that deploy real cloud infrastructure",
]So the suite has a category whose stated purpose is to create actual cloud resources. Development dependencies are pytest at 8.0 or newer and moto with its s3 extra, which is how S3 is faked when it is not being deployed for real, so the project supports both modes and tells you which is which in one line of configuration.
Run the wrong target and you pay for it. The documented actions are build, destroy, simulate, apply-role and share, and there is no dry-run flag and no cost estimate anywhere on the page. Budget for the lab's running cost and treat destroy as part of the procedure rather than an afterthought, because the infrastructure is created in your account and the credential it uses is yours.
Editorial conclusion
Use this project if you own the cloud account, you are entitled to run adversary emulation in it, and you want telemetry to test detections against. Do not point it at infrastructure you do not control, and read the credential section first, because the documented setup mounts your own cloud credential directories into the containers. Three things to check before a first build. Whether you are on v5 at all, since the page is titled v5 while the packaging manifest still carries version 3.1.2 and the two dependency files disagree with each other about Ansible and the Azure SDKs. Which install path you use, since the recommended Docker route and the documented local CLI route make different claims about needing Python, Ansible and Terraform. And how you will clean up, since a range is real infrastructure created in your account and the destroy action is the only thing standing between a lab and a bill.
Frequently asked questions
What is the Splunk Attack Range and what is it used for?
It builds instrumented cloud environments on AWS, Azure or GCP, simulates attacks in them, and forwards the resulting data into Splunk for detection development and testing. Labs are deployed with Terraform and Ansible, attacks are generated with Atomic Red Team and other techniques, and access is shared over a WireGuard VPN.
How do I run Splunk Attack Range v5 on my own machine?
Docker Compose is the preferred route: clone the repository, run docker compose -f docker/docker-compose.yml up, then open the web app on port 4321 or the REST API on port 4000, with interactive docs at /openapi/swagger. The page claims this needs no local Python, Ansible or Terraform.
Why does the Splunk Attack Range build stop at Waiting for VPN?
The build is two-phase by design. You start it with a template such as aws/splunk_minimal_aws, and when the status reaches Waiting for VPN you download the WireGuard config and connect; only then does the second build call, carrying nothing but the attack_range_id, let configuration continue.
Where does Splunk Attack Range keep its configuration and templates?
Each range has its own file at config/<attack_range_id>.yml, and the templates are organised by cloud under templates/{aws,azure,gcp}/. Cloud credentials come from ~/.aws, ~/.azure or ~/.config/gcloud, mounted into the containers as described in docker/docker-compose.yml.
Do the Splunk Attack Range tests create real cloud resources?
The pytest configuration registers an e2e marker described as end-to-end tests that deploy real cloud infrastructure. Development dependencies include moto with its s3 extra, which fakes S3 when a real deployment is not wanted.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/splunk-attack-range)