Skip to content

Commit 7d796bf

Browse files
committed
Added config item for extra tags
1 parent f5db18f commit 7d796bf

File tree

7 files changed

+127
-4
lines changed

7 files changed

+127
-4
lines changed

scripts/installer/configs/configuration_items/capture.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
This module contains all configuration items related to live traffic capture settings,
1010
including network interface configuration, capture filters, and capture methods.
1111
"""
12+
from typing import Any, Tuple
1213

1314
from scripts.malcolm_constants import WidgetType
1415
from scripts.malcolm_utils import get_hostname_without_domain
@@ -24,6 +25,7 @@
2425
KEY_CONFIG_ITEM_PCAP_IFACE,
2526
KEY_CONFIG_ITEM_PCAP_NETSNIFF,
2627
KEY_CONFIG_ITEM_PCAP_NODE_NAME,
28+
KEY_CONFIG_ITEM_EXTRA_TAGS,
2729
KEY_CONFIG_ITEM_PCAP_TCPDUMP,
2830
KEY_CONFIG_ITEM_TWEAK_IFACE,
2931
)
@@ -132,6 +134,54 @@
132134
)
133135

134136

137+
class CustomTagsConfigItem(ConfigItem):
138+
"""Custom ConfigItem that converts comma-separated strings to lists for extra tags"""
139+
140+
def set_value(self, value: Any) -> Tuple[bool, str]:
141+
"""Set and validate a new value, with automatic string-to-list conversion."""
142+
# Convert string input to list if needed
143+
if isinstance(value, str):
144+
if value.strip():
145+
converted_value = [s.strip() for s in value.split(",") if s.strip()]
146+
else:
147+
converted_value = []
148+
else:
149+
converted_value = value
150+
151+
# Now validate the converted value
152+
if self.validator:
153+
result = self.validator(converted_value)
154+
if isinstance(result, tuple):
155+
valid, error = result
156+
else:
157+
valid = result
158+
error = "Invalid value" if not valid else ""
159+
160+
if not valid:
161+
return False, error
162+
163+
# Store the converted value
164+
self.is_modified = True
165+
self.value = converted_value
166+
return True, ""
167+
168+
169+
def _validate_extra_tags_list(x):
170+
"""Validate that input is a list of strings (or a single string)"""
171+
return isinstance(x, str) or (isinstance(x, list) and all(isinstance(val, str) for val in x))
172+
173+
174+
CONFIG_ITEM_EXTRA_TAGS = CustomTagsConfigItem(
175+
key=KEY_CONFIG_ITEM_EXTRA_TAGS,
176+
label="Extra Tags",
177+
default_value=[],
178+
accept_blank=True,
179+
validator=_validate_extra_tags_list,
180+
question="Comma-separated list of tags for data generated by Malcolm",
181+
widget_type=WidgetType.TEXT,
182+
)
183+
184+
135185
def get_capture_config_item_dict():
136186
"""Get all ConfigItem objects from this module.
137187

scripts/installer/configs/constants/config_env_var_keys.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
KEY_ENV_PCAP_IFACE = "PCAP_IFACE" # capture interface(s)
102102
KEY_ENV_PCAP_FILTER = "PCAP_FILTER" # capture filter
103103
KEY_ENV_PCAP_NODE_NAME = "PCAP_NODE_NAME" # capture source "node name" for locally processed PCAP files
104+
KEY_ENV_EXTRA_TAGS = "EXTRA_TAGS" # Comma-separated list of tags for data generated by Malcolm
104105
KEY_ENV_PCAP_PIPELINE_POLLING = "PCAP_PIPELINE_POLLING" # Use polling for file watching vs. native
105106

106107
KEY_ENV_PGID = "PGID" # process Group ID

scripts/installer/configs/constants/configuration_item_keys.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
KEY_CONFIG_ITEM_LIVE_ZEEK = "liveZeek"
3131
KEY_CONFIG_ITEM_LIVE_SURICATA = "liveSuricata"
3232
KEY_CONFIG_ITEM_PCAP_NODE_NAME = "pcapNodeName"
33+
KEY_CONFIG_ITEM_EXTRA_TAGS = "extraTags"
3334

3435
# Docker options
3536
KEY_CONFIG_ITEM_MALCOLM_RESTART_POLICY = "malcolmRestartPolicy"

scripts/installer/core/config_env_mapper.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
Includes reverse import from existing env files and lookup helpers.
1010
"""
1111

12+
try:
13+
from collections.abc import Iterable
14+
except ImportError:
15+
from collections import Iterable
1216
from collections import defaultdict
1317
from dataclasses import dataclass, field
1418
from typing import Any, Callable, Dict, List, Optional
@@ -51,6 +55,23 @@ def _default_string_reverse_transform(value: str):
5155
return value
5256

5357

58+
def _default_list_of_strings_transform(value: Any) -> List[str]:
59+
if value is None:
60+
result = []
61+
elif isinstance(value, str):
62+
result = [value]
63+
elif isinstance(value, Iterable):
64+
result = [str(v) for v in value]
65+
else:
66+
result = [str(value)]
67+
68+
return ",".join([true_or_false_no_quotes(x).strip() for x in result])
69+
70+
71+
def _default_list_of_strings_reverse_transform(value: str) -> List[str]:
72+
return [s.strip() for s in value.split(",") if s.strip()] if value.strip() else []
73+
74+
5475
# 1. Boolean transform logic (single config item only)
5576
_BOOLEAN_VARS = [
5677
KEY_ENV_ARKIME_MANAGE_PCAP_FILES,
@@ -130,8 +151,13 @@ def _default_string_reverse_transform(value: str):
130151
KEY_ENV_ZEEK_VTOT_API2_KEY,
131152
]
132153

154+
# 3. List-of-strings transform logic
155+
_LIST_OF_STRING_VARS = [
156+
KEY_ENV_EXTRA_TAGS,
157+
]
158+
133159

134-
# 3. Custom transform logic
160+
# 4. Custom transform logic
135161
@dataclass(frozen=True)
136162
class TransformHook:
137163
forward: Callable
@@ -366,6 +392,9 @@ def __init__(self):
366392

367393
if map_key_constant_value in _CUSTOM_VARS:
368394
self._handle_custom_transform(env_var_instance, map_key_constant_value)
395+
elif map_key_constant_value in _LIST_OF_STRING_VARS:
396+
env_var_instance.transform = _default_list_of_strings_transform
397+
env_var_instance.reverse_transform = _default_list_of_strings_reverse_transform
369398
elif map_key_constant_value in _STRING_VARS:
370399
env_var_instance.transform = _default_string_transform
371400
env_var_instance.reverse_transform = _default_string_reverse_transform
@@ -568,6 +597,7 @@ def __init__(self):
568597
self.env_var_by_map_key[KEY_ENV_PCAP_IFACE].config_items = [KEY_CONFIG_ITEM_PCAP_IFACE]
569598
self.env_var_by_map_key[KEY_ENV_PCAP_FILTER].config_items = [KEY_CONFIG_ITEM_PCAP_FILTER]
570599
self.env_var_by_map_key[KEY_ENV_PCAP_NODE_NAME].config_items = [KEY_CONFIG_ITEM_PCAP_NODE_NAME]
600+
self.env_var_by_map_key[KEY_ENV_EXTRA_TAGS].config_items = [KEY_CONFIG_ITEM_EXTRA_TAGS]
571601
self.env_var_by_map_key[KEY_ENV_PCAP_PIPELINE_POLLING].config_items = [
572602
KEY_CONFIG_ITEM_DOCKER_ORCHESTRATION_MODE
573603
]

scripts/installer/core/dependencies.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,13 @@ def __repr__(self) -> str:
265265
)
266266
),
267267
# Malcolm profile children
268+
KEY_CONFIG_ITEM_EXTRA_TAGS: DependencySpec(
269+
visibility=VisibilityRule(
270+
depends_on=KEY_CONFIG_ITEM_MALCOLM_PROFILE,
271+
condition=lambda profile: profile in (PROFILE_HEDGEHOG, PROFILE_MALCOLM),
272+
ui_parent=KEY_CONFIG_ITEM_MALCOLM_PROFILE,
273+
)
274+
),
268275
KEY_CONFIG_ITEM_MALCOLM_MAINTAIN_OPENSEARCH: DependencySpec(
269276
visibility=VisibilityRule(
270277
depends_on=KEY_CONFIG_ITEM_MALCOLM_PROFILE,

scripts/installer/core/malcolm_config.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
SURICATA_LOG_CONTAINER_PATH,
3333
YAMLDynamic,
3434
)
35-
from scripts.malcolm_utils import deep_get, get_main_script_path, same_file_or_dir, touch
35+
from scripts.malcolm_utils import deep_get, get_main_script_path, same_file_or_dir, touch, unwrap_method
3636

3737
from scripts.installer.configs.constants.config_env_var_keys import *
3838
from scripts.installer.configs.constants.configuration_item_keys import *
@@ -70,6 +70,12 @@
7070
from scripts.installer.core.transform_registry import apply_inbound
7171

7272

73+
def overrides_set_value(obj):
74+
item_func = unwrap_method(obj.__class__.set_value)
75+
base_func = unwrap_method(ConfigItem.set_value)
76+
return item_func is not base_func
77+
78+
7379
class MalcolmConfig(ObservableStoreMixin):
7480
"""Configuration store managing items, dependencies, and env mapping."""
7581

@@ -291,8 +297,16 @@ def apply_default(
291297
if item.get_value() == value:
292298
return
293299

294-
# Apply without flipping is_modified so future syncs can adjust
295-
item.value = value
300+
if not overrides_set_value(item):
301+
# set value directly without set_value (i.e., without flipping is_modified) so future syncs can adjust
302+
item.value = value
303+
else:
304+
# this config item overrides set_value so they must want to do something special,
305+
# so call its set_value but preserve its is_modified flag
306+
old_modified = item.is_modified
307+
item.set_value(value)
308+
item.is_modified = old_modified
309+
296310
try:
297311
self._notify_observers(key, item.get_value())
298312
except Exception as e:

scripts/malcolm_utils.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import contextlib
77
import fnmatch
8+
import functools
89
import glob
910
import hashlib
1011
import inspect
@@ -884,6 +885,25 @@ def temporary_filename(suffix=None):
884885
os.unlink(tmp_name)
885886

886887

888+
###################################################################################################
889+
# Returns the raw underlying function behind a method, classmethod, staticmethod, or functools.partial/wrapped method.
890+
def unwrap_method(method):
891+
892+
# Handle classmethod / staticmethod
893+
if isinstance(method, (classmethod, staticmethod)):
894+
method = method.__func__
895+
896+
# Unwrap functools.partial, wraps, etc.
897+
while hasattr(method, "__wrapped__"):
898+
method = method.__wrapped__
899+
900+
# functools.partialmethod stores the underlying function in `func`
901+
if isinstance(method, functools.partial):
902+
method = method.func
903+
904+
return method
905+
906+
887907
###################################################################################################
888908
# open a file and close it, updating its access time
889909
def touch(filename):

0 commit comments

Comments
 (0)