Skip to content

Commit e02c6d1

Browse files
authored
SYN flood scenario (krkn-chaos#668)
* scenario config file Signed-off-by: Tullio Sebastiani <[email protected]> * syn flood plugin Signed-off-by: Tullio Sebastiani <[email protected]> * run_krkn.py updaated Signed-off-by: Tullio Sebastiani <[email protected]> * requirements.txt + documentation + config.yaml * set node selector defaults to worker Signed-off-by: Tullio Sebastiani <[email protected]> --------- Signed-off-by: Tullio Sebastiani <[email protected]>
1 parent 04425a8 commit e02c6d1

File tree

8 files changed

+191
-2
lines changed

8 files changed

+191
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Scenario type | Kubernetes
6464
[Network_Chaos](docs/network_chaos.md) | :heavy_check_mark: |
6565
[ManagedCluster Scenarios](docs/managedcluster_scenarios.md) | :heavy_check_mark: |
6666
[Service Hijacking Scenarios](docs/service_hijacking_scenarios.md) | :heavy_check_mark: |
67+
[SYN Flood Scenarios](docs/syn_flood_scenarios.md) | :heavy_check_mark: |
6768

6869

6970
### Kraken scenario pass/fail criteria and report

config/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ kraken:
4444
- scenarios/openshift/network_chaos.yaml
4545
- service_hijacking:
4646
- scenarios/kube/service_hijacking.yaml
47+
- syn_flood:
48+
- scenarios/kube/syn_flood.yaml
4749

4850
cerberus:
4951
cerberus_enabled: False # Enable it when cerberus is previously installed

docs/syn_flood_scenarios.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
### SYN Flood Scenarios
2+
3+
This scenario generates a substantial amount of TCP traffic directed at one or more Kubernetes services within
4+
the cluster to test the server's resiliency under extreme traffic conditions.
5+
It can also target hosts outside the cluster by specifying a reachable IP address or hostname.
6+
This scenario leverages the distributed nature of Kubernetes clusters to instantiate multiple instances
7+
of the same pod against a single host, significantly increasing the effectiveness of the attack.
8+
The configuration also allows for the specification of multiple node selectors, enabling Kubernetes to schedule
9+
the attacker pods on a user-defined subset of nodes to make the test more realistic.
10+
11+
```yaml
12+
packet-size: 120 # hping3 packet size
13+
window-size: 64 # hping 3 TCP window size
14+
duration: 10 # chaos scenario duration
15+
namespace: default # namespace where the target service(s) are deployed
16+
target-service: target-svc # target service name (if set target-service-label must be empty)
17+
target-port: 80 # target service TCP port
18+
target-service-label : "" # target service label, can be used to target multiple target at the same time
19+
# if they have the same label set (if set target-service must be empty)
20+
number-of-pods: 2 # number of attacker pod instantiated per each target
21+
image: quay.io/krkn-chaos/krkn-syn-flood # syn flood attacker container image
22+
attacker-nodes: # this will set the node affinity to schedule the attacker node. Per each node label selector
23+
# can be specified multiple values in this way the kube scheduler will schedule the attacker pods
24+
# in the best way possible based on the provided labels. Multiple labels can be specified
25+
kubernetes.io/hostname:
26+
- host_1
27+
- host_2
28+
kubernetes.io/os:
29+
- linux
30+
31+
```
32+
33+
The attacker container source code is available [here](https://github.com/krkn-chaos/krkn-syn-flood).

kraken/syn_flood/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .syn_flood import *

kraken/syn_flood/syn_flood.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import logging
2+
import os.path
3+
import time
4+
from typing import List
5+
6+
import krkn_lib.utils
7+
import yaml
8+
from krkn_lib.k8s import KrknKubernetes
9+
from krkn_lib.models.telemetry import ScenarioTelemetry
10+
from krkn_lib.telemetry.k8s import KrknTelemetryKubernetes
11+
12+
13+
def run(scenarios_list: list[str], krkn_kubernetes: KrknKubernetes, telemetry: KrknTelemetryKubernetes) -> (list[str], list[ScenarioTelemetry]):
14+
scenario_telemetries: list[ScenarioTelemetry] = []
15+
failed_post_scenarios = []
16+
for scenario in scenarios_list:
17+
scenario_telemetry = ScenarioTelemetry()
18+
scenario_telemetry.scenario = scenario
19+
scenario_telemetry.start_timestamp = time.time()
20+
telemetry.set_parameters_base64(scenario_telemetry, scenario)
21+
22+
try:
23+
pod_names = []
24+
config = parse_config(scenario)
25+
if config["target-service-label"]:
26+
target_services = krkn_kubernetes.select_service_by_label(config["namespace"], config["target-service-label"])
27+
else:
28+
target_services = [config["target-service"]]
29+
30+
for target in target_services:
31+
if not krkn_kubernetes.service_exists(target, config["namespace"]):
32+
raise Exception(f"{target} service not found")
33+
for i in range(config["number-of-pods"]):
34+
pod_name = "syn-flood-" + krkn_lib.utils.get_random_string(10)
35+
krkn_kubernetes.deploy_syn_flood(pod_name,
36+
config["namespace"],
37+
config["image"],
38+
target,
39+
config["target-port"],
40+
config["packet-size"],
41+
config["window-size"],
42+
config["duration"],
43+
config["attacker-nodes"]
44+
)
45+
pod_names.append(pod_name)
46+
47+
logging.info("waiting all the attackers to finish:")
48+
did_finish = False
49+
finished_pods = []
50+
while not did_finish:
51+
for pod_name in pod_names:
52+
if not krkn_kubernetes.is_pod_running(pod_name, config["namespace"]):
53+
finished_pods.append(pod_name)
54+
if set(pod_names) == set(finished_pods):
55+
did_finish = True
56+
time.sleep(1)
57+
58+
except Exception as e:
59+
logging.error(f"Failed to run syn flood scenario {scenario}: {e}")
60+
failed_post_scenarios.append(scenario)
61+
scenario_telemetry.exit_status = 1
62+
else:
63+
scenario_telemetry.exit_status = 0
64+
scenario_telemetry.end_timestamp = time.time()
65+
scenario_telemetries.append(scenario_telemetry)
66+
return failed_post_scenarios, scenario_telemetries
67+
68+
def parse_config(scenario_file: str) -> dict[str,any]:
69+
if not os.path.exists(scenario_file):
70+
raise Exception(f"failed to load scenario file {scenario_file}")
71+
72+
try:
73+
with open(scenario_file) as stream:
74+
config = yaml.safe_load(stream)
75+
except Exception:
76+
raise Exception(f"{scenario_file} is not a valid yaml file")
77+
78+
missing = []
79+
if not check_key_value(config ,"packet-size"):
80+
missing.append("packet-size")
81+
if not check_key_value(config,"window-size"):
82+
missing.append("window-size")
83+
if not check_key_value(config, "duration"):
84+
missing.append("duration")
85+
if not check_key_value(config, "namespace"):
86+
missing.append("namespace")
87+
if not check_key_value(config, "number-of-pods"):
88+
missing.append("number-of-pods")
89+
if not check_key_value(config, "target-port"):
90+
missing.append("target-port")
91+
if not check_key_value(config, "image"):
92+
missing.append("image")
93+
if "target-service" not in config.keys():
94+
missing.append("target-service")
95+
if "target-service-label" not in config.keys():
96+
missing.append("target-service-label")
97+
98+
99+
100+
101+
if len(missing) > 0:
102+
raise Exception(f"{(',').join(missing)} parameter(s) are missing")
103+
104+
if not config["target-service"] and not config["target-service-label"]:
105+
raise Exception("you have either to set a target service or a label")
106+
if config["target-service"] and config["target-service-label"]:
107+
raise Exception("you cannot select both target-service and target-service-label")
108+
109+
if 'attacker-nodes' and not is_node_affinity_correct(config['attacker-nodes']):
110+
raise Exception("attacker-nodes format is not correct")
111+
return config
112+
113+
def check_key_value(dictionary, key):
114+
if key in dictionary:
115+
value = dictionary[key]
116+
if value is not None and value != '':
117+
return True
118+
return False
119+
120+
def is_node_affinity_correct(obj) -> bool:
121+
if not isinstance(obj, dict):
122+
return False
123+
for key in obj.keys():
124+
if not isinstance(key, str):
125+
return False
126+
if not isinstance(obj[key], list):
127+
return False
128+
return True
129+
130+
131+
132+

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ google-api-python-client==2.116.0
1515
ibm_cloud_sdk_core==3.18.0
1616
ibm_vpc==0.20.0
1717
jinja2==3.1.4
18-
krkn-lib==2.1.6
18+
krkn-lib==2.1.7
1919
lxml==5.1.0
2020
kubernetes==28.1.0
2121
oauth2client==4.1.3

run_kraken.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import kraken.prometheus as prometheus_plugin
2828
import kraken.service_hijacking.service_hijacking as service_hijacking_plugin
2929
import server as server
30-
from kraken import plugins
30+
from kraken import plugins, syn_flood
3131
from krkn_lib.k8s import KrknKubernetes
3232
from krkn_lib.ocp import KrknOpenshift
3333
from krkn_lib.telemetry.elastic import KrknElastic
@@ -354,6 +354,10 @@ def main(cfg):
354354
logging.info("Running Service Hijacking Chaos")
355355
failed_post_scenarios, scenario_telemetries = service_hijacking_plugin.run(scenarios_list, wait_duration, kubecli, telemetry_k8s)
356356
chaos_telemetry.scenarios.extend(scenario_telemetries)
357+
elif scenario_type == "syn_flood":
358+
logging.info("Running Syn Flood Chaos")
359+
failed_post_scenarios, scenario_telemetries = syn_flood.run(scenarios_list, kubecli, telemetry_k8s)
360+
chaos_telemetry.scenarios.extend(scenario_telemetries)
357361

358362
# Check for critical alerts when enabled
359363
post_critical_alerts = 0

scenarios/kube/syn_flood.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
packet-size: 120 # hping3 packet size
2+
window-size: 64 # hping 3 TCP window size
3+
duration: 10 # chaos scenario duration
4+
namespace: default # namespace where the target service(s) are deployed
5+
target-service: elasticsearch # target service name (if set target-service-label must be empty)
6+
target-port: 9200 # target service TCP port
7+
target-service-label : "" # target service label, can be used to target multiple target at the same time
8+
# if they have the same label set (if set target-service must be empty)
9+
number-of-pods: 2 # number of attacker pod instantiated per each target
10+
image: quay.io/krkn-chaos/krkn-syn-flood:v1.0.0 # syn flood attacker container image
11+
attacker-nodes: # this will set the node affinity to schedule the attacker node. Per each node label selector
12+
node-role.kubernetes.io/worker: # can be specified multiple values in this way the kube scheduler will schedule the attacker pods
13+
- "" # in the best way possible based on the provided labels. Multiple labels can be specified
14+
# set empty value `attacker-nodes: {}` to let kubernetes schedule the pods
15+
16+

0 commit comments

Comments
 (0)