concap is a framework designed to capture realistic cyberattacks in controlled, containerized environments for the purpose of dataset creation. By creating a scenario file containing an attacker and target(s), concap will parse the scenario and execute it. All traffic towards the target(s) will be captured and automatically extracted for flow features.
- Execute cyberattack scenarios in a controlled Kubernetes environment.
- Support for both single-target and multi-target scenarios.
- Capture network traffic and extract flow features.
- Fine-grained network flow labeling.
- Automate the creation and management of attack and target pods.
- Download results to the local machine for further (ML) analysis.
- Kubernetes cluster with access configured via
kubeconfig. - Go environment for running the framework.
- Docker images for the attack and target pods.
- A
concapnamespace containing aghcr-credsDocker registry secret. - Nodes labeled for deterministic attacker and target placement.
Concap creates all scenario and processing pods in the concap namespace. Every generated pod explicitly references the ghcr-creds image pull secret. Configure the namespace and credentials from a working local Docker login:
kubectl create namespace concap
kubectl -n concap create secret generic ghcr-creds \
--from-file=.dockerconfigjson="$HOME/.docker/config.json" \
--type=kubernetes.io/dockerconfigjsonAttackers and targets are deliberately placed on different physical nodes. Label the more powerful traffic-generator node for attackers and the other node for targets:
kubectl label node rgbcore concap-role=attacker --overwrite
kubectl label node nuccore concap-role=target --overwriteConcap validates the namespace and pull secret at startup. Scenario pods remain pending if the required role-labeled node is unavailable; they do not silently fall back to same-node placement.
Operational checklist, scenario lifecycle, and failure triage: Concap K3s Scenario Runbook.
Build the repository using the provided build script:
./build.shOr use the Makefile:
make build-d, --dir(required): The mount path on the host.-w, --workers(optional): The number of concurrent workers that will execute scenarios, default is1.-s, --scenario(optional): The scenario to run, default isall.
./concap --dir ./exampleOr use the Makefile:
make runEnsure your Kubernetes cluster is up and running.
Place your scenario and processing YAML files in the specified directories.
Execute the framework using the command above.
The framework will:
- Parse the processing and scenario files.
- Create the necessary pods.
- Asynchronously execute the attacks.
- Capture all traffic received by the target(s) to raw pcap file(s).
- Normalize captured pcaps into timestamp order.
- Perform flow reconstruction and feature extraction to csv file(s).
- Preserve labels in the completed scenario YAML for audit and downstream dataset packaging.
- Download output files to your machine.
Concap supports two types of scenarios:
- Single-Target Scenario: One attacker pod and one target pod.
- Multi-Target Scenario: One attacker pod and multiple target pods.
A scenario file is a YAML file defining the attacker and target(s). The filename must be unique and no more than 58 characters.
A single-target scenario consists of one attacker pod and one target pod. This is the default scenario type if no type is specified. The attacker executes commands against the target, and the traffic is captured for analysis.
Example single-target scenario file:
type: single-target # Optional, defaults to single-target if not specifiedname: http-flood-attackattacker:
name: http-flooderimage: attacker/http-flooder:latestatkCommand: ./http-flood.sh $TARGET_IP 80 100atkTime: 30scpuRequest: 100mmemRequest: 250Miprivileged: true # <-- Enable privileged mode for attacker podtarget:
name: web-serverimage: nginx:latestcommandArgs: -g "daemon off;" # Optional: command arguments for target containerfilter: "((dst host $ATTACKER_IP and src host $TARGET_IP) or (dst host $TARGET_IP and src host $ATTACKER_IP)) and not arp"cpuRequest: 100mmemRequest: 250Miprivileged: true # <-- Enable privileged mode for target podnetwork:
bandwidth: 10MbitqueueSize: 100msdelay: 20mslabels:
label: 1category: "dos"subcategory: "http-flood"In a single-target scenario, the following environment variables are available in the attack command:
$ATTACKER_IP: IP address of the attacker pod$TARGET_IP: IP address of the target pod
You can enable privileged mode for the attacker or target pod by adding privileged: true under the respective section. This will run the container in privileged mode, which may be required for certain attack tools or services that need extended permissions.
A multi-target scenario consists of one attacker pod and multiple target pods. This allows for more complex attack scenarios, such as distributed attacks or attacks that target multiple services.
Example multi-target scenario file:
type: multi-target # Required for multi-target scenariosname: distributed-scan-attackattacker:
name: port-scannerimage: attacker/port-scanner:latestatkCommand: ./scan.sh $TARGET_IPSatkTime: 60scpuRequest: 100mmemRequest: 250Miprivileged: true # <-- Enable privileged mode for attacker podtargets:
- name: web-server-1image: httpd:2.4.38commandArgs: -DFOREGROUND # Optional: command arguments for target containercpuRequest: 100mmemRequest: 250Miprivileged: true # <-- Enable privileged mode for this target podlabels:
service: "web"port: "80"
- name: web-server-2image: httpd:2.4.38cpuRequest: 100mmemRequest: 250Milabels:
service: "web"port: "80"
- name: web-server-3image: httpd:2.4.38cpuRequest: 100mmemRequest: 250Milabels:
service: "web"port: "80"network: # Global network settings, used as defaults for all targets and attackerbandwidth: 100MbitqueueSize: 100msdelay: 5mslabels: # Global labels, merged with target-specific labelslabel: 1category: "scanning"subcategory: "port-scan"Note: The global network and labels fields are used during scenario parsing to set defaults and merge with target-specific configurations.
In a multi-target scenario, the following environment variables are available in the attack command:
$ATTACKER_IP: IP address of the attacker pod$TARGET_IPS: Comma-separated list of all target IP addresses$TARGET_IP_0,$TARGET_IP_1, etc.: IP addresses of individual target pods (zero-based indexing, where$TARGET_IP_0is the first target)
When a scenario is executed, the deployment information is captured and included in the output YAML file. For multi-target scenarios, this includes the IP addresses of the attacker and all target pods:
deployment:
attacker: "10.244.0.15"target_0: "10.244.0.16"target_1: "10.244.0.17"target_2: "10.244.0.18"This information is useful for post-processing and analysis of the captured traffic.
You can specify target-specific network configurations. The global network configuration serves as a default, and target-specific configurations override these defaults.
type: multi-targetname: mixed-network-attackattacker:
name: mixed-attackerimage: attacker/mixed:latestatkCommand: ./attack.sh $TARGET_IPSatkTime: 60stargets:
- name: fast-targetimage: nginx:latestnetwork:
bandwidth: 1GbitqueueSize: 100msdelay: 1ms
- name: slow-targetimage: nginx:latestnetwork:
bandwidth: 10MbitqueueSize: 100msdelay: 100msnetwork: # Default network settings for targets without specific settingsbandwidth: 100MbitqueueSize: 100msdelay: 10msSimilarly, you can specify target-specific labels that will be merged with the scenario-level labels. Target-specific labels take precedence over global labels. Labels are stored in the completed scenario YAML; processing outputs keep the schema produced by the processor.
type: multi-targetname: labeled-targetsattacker:
name: labeled-attackerimage: attacker/labeled:latestatkCommand: ./attack.sh $TARGET_IPStargets:
- name: web-targetimage: nginx:latestlabels:
service: "web"port: "80"
- name: db-targetimage: postgres:latestlabels:
service: "database"port: "5432"labels: # These labels will be applied to all targetsattack: "true"category: "mixed"Each target can have its own custom traffic capture filter. The filter is used by tcpdump to determine which packets to capture. You can use special variables in your filter strings that will be automatically replaced with the actual IP addresses during execution:
type: multi-targetname: custom-filter-targetsattacker:
name: port-scannerimage: attacker/port-scanner:latestatkCommand: ./scan.sh $TARGET_IPStargets:
- name: web-targetimage: nginx:latestfilter: "((dst host $ATTACKER_IP and src host $TARGET_IP) or (dst host $TARGET_IP and src host $ATTACKER_IP)) and not arp"
- name: db-targetimage: postgres:latestfilter: "host $TARGET_IP and (host $ATTACKER_IP or host $TARGET_IP_0)"If no filter is specified for a target, the following default filter will be used:
((dst host $ATTACKER_IP and src host $TARGET_IP) or (dst host $TARGET_IP and src host $ATTACKER_IP)) and not arp
This default filter captures all traffic between the attacker and the target, excluding ARP packets.
The following variables are available in filter strings:
$ATTACKER_IP: IP address of the attacker pod$TARGET_IP: IP address of the current target pod (the one where tcpdump is running)$TARGET_IP_0,$TARGET_IP_1, etc.: IP addresses of specific target pods in the scenario (zero-based indexing)
This allows you to create sophisticated capture filters that can include or exclude traffic between specific pods in your multi-target scenario.
You can optionally configure startup probes for each target container to ensure proper initialization before the attack begins. This is particularly useful for services that need (a long) time to start up or require health checks. When this is not provided the pod will be asumed ready after a successful start.
type: multi-targetname: health-check-targetsattacker:
name: health-check-attackerimage: attacker/health-check:latestatkCommand: ./attack.sh $TARGET_IPStargets:
- name: web-targetimage: nginx:lateststartupProbe:
httpGet:
path: /port: 80initialDelaySeconds: 5periodSeconds: 10failureThreshold: 3
- name: api-targetimage: api-server:lateststartupProbe:
tcpSocket:
port: 8080initialDelaySeconds: 10periodSeconds: 15failureThreshold: 5The startup probe configuration supports all Kubernetes probe types:
httpGet: HTTP endpoint health checktcpSocket: TCP port health checkgrpc: gRPC remote procedure call health checkexec: Command execution health check
Each probe can be configured with:
initialDelaySeconds: Delay before the first probe (default: 0)periodSeconds: How often to perform the probe (default: 10)timeoutSeconds: Timeout for the probe (default: 1)successThreshold: Number of consecutive successes required (default: 1)failureThreshold: Number of consecutive failures required (default: 3)
Processing pods analyze the traffic received by the target(s) during scenario execution. This traffic is captured by tcpdump as dump.raw.pcap, normalized into timestamp order as dump.pcap, and then passed to the configured processors. Processor output files are downloaded without Concap-added label or metadata columns. Each processing pod requires the following specifications:
- Name: A unique identifier for the processing pod. Will be used as filename for output files.
- Container Image: The Docker image to be used for the processing pod.
- Command: The command that starts the processing of the pcap file.
- CPU/Memory Request: Helps K8s with scheduling the pods.
- Environment Variables:
$INPUT_FILE: The file path to the pcap file to be processed.$OUTPUT_FILE: The file path where the processing results should be written. This file will be downloaded byconcap.$INPUT_FILE_NAME: A unique value for each scenario, equal to the filename of$INPUT_FILEwithout the '.pcap' extension.
- Ensure output files are unique to avoid concurrency issues when multiple scenarios run concurrently. Use
$INPUT_FILE_NAMEto generate unique output file names.
name: cicflowmetercontainerImage: ghcr.io/idlab-discover/concap/cicflowmeter:tools-1.0.0command: > mkdir -p /data/output/$INPUT_FILE_NAME/ && pcapfix $INPUT_FILE -o $INPUT_FILE && /CICFlowMeter/bin/cfm $INPUT_FILE /data/output/$INPUT_FILE_NAME/ && mv /data/output/$INPUT_FILE_NAME/$INPUT_FILE_NAME.pcap_Flow.csv $OUTPUT_FILESee example/processingpods for more configurations of popular flow exporters such as argus, nfstream, and rustiflow.
The project is organized as follows:
concap/
├── cmd/ # Command-line applications
│ └── main.go # Entry point
├── internal/ # Private application code
│ ├── controller/ # Controller logic
│ │ └── controller.go # Scenario scheduling and execution
│ ├── kubernetes/ # Kubernetes interaction
│ │ ├── exec.go # Pod execution
│ │ ├── api.go # Kubernetes API interactions
│ │ └── watcher.go # Pod watching
│ └── scenarios/ # Scenario implementations
│ ├── scenario.go # Base scenario and interface
│ ├── factory.go # Scenario factory
│ ├── multi_target.go # Multi-target scenario
│ ├── network.go # Network configuration
│ ├── podbuilder.go # Pod building utilities
│ ├── processingpod.go # Processing pod logic
│ ├── single_target.go # Single-target scenario
│ ├── types.go # Common type definitions
│ └── utils.go # Utility functions
├── example/ # Example directory to run concap with scenarios and processing pods
├── go.mod # Go module file
└── README.md # Project README
